Repository: w23/xash3d-fwgs Branch: vulkan Commit: 700218fbc8b8 Files: 1012 Total size: 8.0 MB Directory structure: gitextract_lutwbsjd/ ├── .cirrus.yml ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── crash-report.md │ │ └── visual-glitches-report.md │ └── workflows/ │ └── c-cpp.yml ├── .gitignore ├── .gitmodules ├── 3rdparty/ │ ├── bzip2/ │ │ └── wscript │ ├── extras/ │ │ └── wscript │ ├── gl4es/ │ │ └── wscript │ ├── libbacktrace/ │ │ └── wscript │ ├── libogg/ │ │ └── wscript │ ├── opus/ │ │ └── wscript │ ├── opusfile/ │ │ └── wscript │ └── vorbis/ │ └── wscript ├── CONTRIBUTING.md ├── Documentation/ │ ├── bug-compatibility.md │ ├── cross-compiling-for-windows-with-wine.md │ ├── debugging-using-minidumps.md │ ├── donate.md │ ├── engine-porting-guide.md │ ├── environment-variables.md │ ├── extensions/ │ │ ├── addon-folders.md │ │ ├── console-scripting.md │ │ ├── entity-tools.md │ │ ├── expanded-common-structures.md │ │ ├── input-interface-ru.md │ │ ├── input-interface.md │ │ ├── library-naming.md │ │ ├── mp3-loops.md │ │ ├── native-object.md │ │ └── sounds.lst.md │ ├── gameinfo.md │ ├── goldsrc-protocol-support.md │ ├── hd-textures.md │ ├── mod-porting-guide.md │ ├── musl.md │ ├── nat-bypass-usage.md │ ├── not-supported-mod-list-and-reasons-why.md │ ├── opensource-mods.md │ ├── ports.md │ ├── psvita.md │ ├── supported-mod-list.md │ └── touch-controls.md ├── README.md ├── android/ │ ├── .gitignore │ ├── app/ │ │ ├── build.gradle.kts │ │ ├── proguard-rules.pro │ │ ├── run-python │ │ ├── run-python.bat │ │ └── src/ │ │ ├── asan/ │ │ │ ├── res/ │ │ │ │ └── values/ │ │ │ │ └── strings.xml │ │ │ └── resources/ │ │ │ └── lib/ │ │ │ ├── arm64-v8a/ │ │ │ │ └── wrap.sh │ │ │ ├── armeabi-v7a/ │ │ │ │ └── wrap.sh │ │ │ └── x86_64/ │ │ │ └── wrap.sh │ │ ├── continuous/ │ │ │ └── res/ │ │ │ └── values/ │ │ │ └── strings.xml │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── su/ │ │ │ └── xash/ │ │ │ └── engine/ │ │ │ ├── DedicatedActivity.kt │ │ │ ├── DedicatedService.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainApplication.kt │ │ │ ├── XashActivity.java │ │ │ ├── adapters/ │ │ │ │ └── GameAdapter.kt │ │ │ ├── model/ │ │ │ │ ├── BackgroundBitmap.kt │ │ │ │ └── Game.kt │ │ │ ├── ui/ │ │ │ │ ├── library/ │ │ │ │ │ ├── LibraryFragment.kt │ │ │ │ │ └── LibraryViewModel.kt │ │ │ │ └── settings/ │ │ │ │ ├── AppSettingsFragment.kt │ │ │ │ ├── AppSettingsPreferenceFragment.kt │ │ │ │ ├── GameSettingsFragment.kt │ │ │ │ └── GameSettingsPreferenceFragment.kt │ │ │ └── util/ │ │ │ ├── AndroidBug5497Workaround.java │ │ │ └── TGAReader.java │ │ └── res/ │ │ ├── drawable/ │ │ │ ├── ic_baseline_add_24.xml │ │ │ ├── ic_baseline_delete_24.xml │ │ │ ├── ic_baseline_folder_open_24.xml │ │ │ ├── ic_baseline_play_arrow_24.xml │ │ │ ├── ic_baseline_settings_24.xml │ │ │ └── ic_baseline_terminal_24.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── card_game.xml │ │ │ ├── edit_text_preference.xml │ │ │ ├── fragment_app_settings.xml │ │ │ ├── fragment_game_settings.xml │ │ │ ├── fragment_library.xml │ │ │ ├── list_preference.xml │ │ │ └── switch_preference.xml │ │ ├── menu/ │ │ │ ├── menu_game_settings.xml │ │ │ └── menu_library.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── ic_launcher_monochrome.xml │ │ │ └── ic_launcher_round.xml │ │ ├── navigation/ │ │ │ └── nav_graph.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── strings.xml │ │ │ ├── styles.xml │ │ │ └── themes.xml │ │ ├── values-es/ │ │ │ └── strings.xml │ │ ├── values-pt-rBR/ │ │ │ └── strings.xml │ │ ├── values-ru/ │ │ │ └── strings.xml │ │ └── xml/ │ │ ├── app_preferences.xml │ │ └── game_preferences.xml │ ├── build.gradle.kts │ ├── debug.keystore │ ├── gradle/ │ │ ├── libs.versions.toml │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle.kts ├── common/ │ ├── backends.h │ ├── beamdef.h │ ├── bspfile.h │ ├── cl_entity.h │ ├── com_image.h │ ├── com_model.h │ ├── con_nprint.h │ ├── const.h │ ├── cvardef.h │ ├── defaults.h │ ├── demo_api.h │ ├── dlight.h │ ├── enginefeatures.h │ ├── entity_state.h │ ├── entity_types.h │ ├── event_api.h │ ├── event_args.h │ ├── event_flags.h │ ├── gameinfo.h │ ├── hltv.h │ ├── ivoicetweak.h │ ├── kbutton.h │ ├── lightstyle.h │ ├── mathlib.h │ ├── net_api.h │ ├── netadr.h │ ├── particledef.h │ ├── pmtrace.h │ ├── port.h │ ├── qfont.h │ ├── r_efx.h │ ├── r_studioint.h │ ├── ref_device.h │ ├── ref_params.h │ ├── render_api.h │ ├── screenfade.h │ ├── studio_event.h │ ├── synctype.h │ ├── triangleapi.h │ ├── usercmd.h │ ├── wadfile.h │ ├── weaponinfo.h │ ├── wrect.h │ └── xash3d_types.h ├── engine/ │ ├── alias.h │ ├── anorms.h │ ├── cdll_exp.h │ ├── cdll_int.h │ ├── client/ │ │ ├── avi/ │ │ │ ├── avi.h │ │ │ ├── avi_ffmpeg.c │ │ │ └── avi_ffmpeg.h │ │ ├── cl_cmds.c │ │ ├── cl_custom.c │ │ ├── cl_debug.c │ │ ├── cl_demo.c │ │ ├── cl_efrag.c │ │ ├── cl_efx.c │ │ ├── cl_events.c │ │ ├── cl_font.c │ │ ├── cl_frame.c │ │ ├── cl_game.c │ │ ├── cl_gameui.c │ │ ├── cl_main.c │ │ ├── cl_mobile.c │ │ ├── cl_netgraph.c │ │ ├── cl_parse.c │ │ ├── cl_parse_48.c │ │ ├── cl_parse_gs.c │ │ ├── cl_pmove.c │ │ ├── cl_qparse.c │ │ ├── cl_remap.c │ │ ├── cl_render.c │ │ ├── cl_scrn.c │ │ ├── cl_securedstub.c │ │ ├── cl_spray.c │ │ ├── cl_tent.c │ │ ├── cl_tent.h │ │ ├── cl_video.c │ │ ├── cl_view.c │ │ ├── client.h │ │ ├── console.c │ │ ├── gamma.c │ │ ├── identification.c │ │ ├── in_joy.c │ │ ├── in_touch.c │ │ ├── input.c │ │ ├── input.h │ │ ├── keys.c │ │ ├── mod_dbghulls.c │ │ ├── ref_common.c │ │ ├── ref_common.h │ │ ├── s_dsp.c │ │ ├── s_load.c │ │ ├── s_main.c │ │ ├── s_mix.c │ │ ├── s_mouth.c │ │ ├── s_stream.c │ │ ├── s_utils.c │ │ ├── s_vox.c │ │ ├── sound.h │ │ ├── soundlib/ │ │ │ ├── libmpg/ │ │ │ │ ├── dct36.c │ │ │ │ ├── dct64.c │ │ │ │ ├── fmt123.h │ │ │ │ ├── format.c │ │ │ │ ├── frame.c │ │ │ │ ├── frame.h │ │ │ │ ├── getbits.h │ │ │ │ ├── huffman.h │ │ │ │ ├── index.c │ │ │ │ ├── index.h │ │ │ │ ├── layer3.c │ │ │ │ ├── libmpg.c │ │ │ │ ├── libmpg.dsp │ │ │ │ ├── libmpg.h │ │ │ │ ├── mpeghead.h │ │ │ │ ├── mpg123.c │ │ │ │ ├── mpg123.h │ │ │ │ ├── parse.c │ │ │ │ ├── reader.c │ │ │ │ ├── reader.h │ │ │ │ ├── sample.h │ │ │ │ ├── synth.c │ │ │ │ ├── synth.h │ │ │ │ └── tabinit.c │ │ │ ├── ogg_filestream.c │ │ │ ├── ogg_filestream.h │ │ │ ├── snd_main.c │ │ │ ├── snd_mp3.c │ │ │ ├── snd_ogg_opus.c │ │ │ ├── snd_ogg_vorbis.c │ │ │ └── snd_wav.c │ │ ├── titles.c │ │ ├── vgui/ │ │ │ ├── vgui_draw.c │ │ │ └── vgui_draw.h │ │ ├── vid_common.c │ │ ├── vid_common.h │ │ ├── voice.c │ │ ├── voice.h │ │ └── vox.h │ ├── common/ │ │ ├── base_cmd.c │ │ ├── base_cmd.h │ │ ├── cfgscript.c │ │ ├── cmd.c │ │ ├── com_strings.h │ │ ├── common.c │ │ ├── common.h │ │ ├── con_utils.c │ │ ├── custom.c │ │ ├── cvar.c │ │ ├── cvar.h │ │ ├── dedicated.c │ │ ├── filesystem_engine.c │ │ ├── host.c │ │ ├── host_state.c │ │ ├── hpak.c │ │ ├── hpak.h │ │ ├── imagelib/ │ │ │ ├── imagelib.h │ │ │ ├── img_bmp.c │ │ │ ├── img_bmp.h │ │ │ ├── img_dds.c │ │ │ ├── img_dds.h │ │ │ ├── img_ktx2.c │ │ │ ├── img_ktx2.h │ │ │ ├── img_main.c │ │ │ ├── img_png.c │ │ │ ├── img_png.h │ │ │ ├── img_quant.c │ │ │ ├── img_tga.c │ │ │ ├── img_tga.h │ │ │ ├── img_utils.c │ │ │ └── img_wad.c │ │ ├── infostring.c │ │ ├── ipv6text.c │ │ ├── ipv6text.h │ │ ├── launcher.c │ │ ├── lib_common.c │ │ ├── library.h │ │ ├── masterlist.c │ │ ├── mod_alias.c │ │ ├── mod_bmodel.c │ │ ├── mod_local.h │ │ ├── mod_sprite.c │ │ ├── mod_studio.c │ │ ├── model.c │ │ ├── munge.c │ │ ├── net_buffer.c │ │ ├── net_buffer.h │ │ ├── net_chan.c │ │ ├── net_encode.c │ │ ├── net_encode.h │ │ ├── net_http.c │ │ ├── net_ws.c │ │ ├── net_ws.h │ │ ├── net_ws_private.h │ │ ├── netchan.h │ │ ├── pm_local.h │ │ ├── pm_surface.c │ │ ├── pm_trace.c │ │ ├── protocol.h │ │ ├── soundlib/ │ │ │ ├── snd_utils.c │ │ │ └── soundlib.h │ │ ├── sounds.c │ │ ├── sys_con.c │ │ ├── system.c │ │ ├── system.h │ │ ├── tests.h │ │ ├── whereami.c │ │ ├── whereami.h │ │ ├── world.c │ │ ├── world.h │ │ └── zone.c │ ├── cursor_type.h │ ├── custom.h │ ├── customentity.h │ ├── edict.h │ ├── eiface.h │ ├── key_modifiers.h │ ├── keydefs.h │ ├── menu_int.h │ ├── mobility_int.h │ ├── physint.h │ ├── platform/ │ │ ├── android/ │ │ │ ├── android.c │ │ │ ├── dlsym-weak.c │ │ │ ├── dlsym-weak.h │ │ │ ├── lib_android.c │ │ │ ├── lib_android.h │ │ │ └── linker.h │ │ ├── apple/ │ │ │ ├── lib_ios.c │ │ │ └── lib_ios.h │ │ ├── dos/ │ │ │ ├── in_dos.c │ │ │ ├── ld.sh │ │ │ ├── objcopy.sh │ │ │ ├── sys_dos.c │ │ │ └── vid_dos.c │ │ ├── irix/ │ │ │ ├── dladdr.c │ │ │ ├── dladdr.h │ │ │ └── xash-exec │ │ ├── linux/ │ │ │ ├── in_evdev.c │ │ │ ├── s_alsa.c │ │ │ ├── sys_linux.c │ │ │ └── vid_fbdev.c │ │ ├── misc/ │ │ │ ├── kmalloc.c │ │ │ ├── lib_static.c │ │ │ ├── sbrk.c │ │ │ └── swap.h │ │ ├── nswitch/ │ │ │ ├── sys_nswitch.c │ │ │ └── xash3d-fwgs.nacp │ │ ├── platform.h │ │ ├── posix/ │ │ │ ├── con_posix.c │ │ │ ├── crash.h │ │ │ ├── crash_glibc.c │ │ │ ├── crash_libbacktrace.c │ │ │ ├── crash_posix.c │ │ │ ├── lib_posix.c │ │ │ ├── net.h │ │ │ └── sys_posix.c │ │ ├── psvita/ │ │ │ ├── in_psvita.c │ │ │ ├── net_psvita.h │ │ │ ├── sce_sys/ │ │ │ │ └── livearea/ │ │ │ │ └── contents/ │ │ │ │ └── template.xml │ │ │ └── sys_psvita.c │ │ ├── sdl1/ │ │ │ ├── host_sdl1.c │ │ │ ├── in_sdl1.c │ │ │ ├── platform_sdl1.h │ │ │ ├── s_sdl1.c │ │ │ ├── sys_sdl1.c │ │ │ └── vid_sdl1.c │ │ ├── sdl2/ │ │ │ ├── host_sdl2.c │ │ │ ├── in_sdl2.c │ │ │ ├── joy_sdl2.c │ │ │ ├── platform_sdl2.h │ │ │ ├── s_sdl2.c │ │ │ ├── sys_sdl2.c │ │ │ └── vid_sdl2.c │ │ ├── sdl3/ │ │ │ ├── in_sdl3.c │ │ │ ├── platform_sdl3.h │ │ │ └── sys_sdl3.c │ │ ├── stub/ │ │ │ ├── net_stub.h │ │ │ └── s_stub.c │ │ └── win32/ │ │ ├── con_win.c │ │ ├── crash_win.c │ │ ├── lib_custom_win.c │ │ ├── lib_win.c │ │ ├── lib_win.h │ │ ├── net.h │ │ └── sys_win.c │ ├── progdefs.h │ ├── ref_api.h │ ├── ref_vulkan.h │ ├── server/ │ │ ├── server.h │ │ ├── sv_client.c │ │ ├── sv_cmds.c │ │ ├── sv_custom.c │ │ ├── sv_filter.c │ │ ├── sv_frame.c │ │ ├── sv_game.c │ │ ├── sv_init.c │ │ ├── sv_log.c │ │ ├── sv_main.c │ │ ├── sv_move.c │ │ ├── sv_phys.c │ │ ├── sv_pmove.c │ │ ├── sv_query.c │ │ ├── sv_save.c │ │ └── sv_world.c │ ├── shake.h │ ├── sprite.h │ ├── studio.h │ ├── vgui_api.h │ ├── warpsin.h │ └── wscript ├── filesystem/ │ ├── VFileSystem009.cpp │ ├── VFileSystem009.h │ ├── android.c │ ├── dir.c │ ├── exports.txt │ ├── filesystem.c │ ├── filesystem.h │ ├── filesystem_internal.h │ ├── fscallback.h │ ├── pak.c │ ├── tests/ │ │ ├── caseinsensitive.c │ │ ├── interface.cpp │ │ └── no-init.c │ ├── wad.c │ ├── wscript │ └── zip.c ├── game_launch/ │ ├── game.cpp │ ├── game.rc │ └── wscript ├── pm_shared/ │ ├── pm_defs.h │ ├── pm_info.h │ └── pm_movevars.h ├── public/ │ ├── build.c │ ├── build.h │ ├── build_vcs.c │ ├── buildenums.h │ ├── crclib.c │ ├── crclib.h │ ├── crtlib.c │ ├── crtlib.h │ ├── dllhelpers.c │ ├── getopt.c │ ├── getopt.h │ ├── ktx2.h │ ├── matrixlib.c │ ├── miniz.c │ ├── miniz.h │ ├── pstdint.h │ ├── tests/ │ │ ├── test_atoi.c │ │ ├── test_build.c │ │ ├── test_efp.c │ │ ├── test_filebase.c │ │ ├── test_fileext.c │ │ ├── test_parsefile.c │ │ ├── test_strings.c │ │ └── test_validate_target.c │ ├── utflib.c │ ├── utflib.h │ ├── wscript │ ├── xash3d_mathlib.c │ └── xash3d_mathlib.h ├── ref/ │ ├── gl/ │ │ ├── exports.txt │ │ ├── gl2_shim/ │ │ │ ├── fragment.glsl.inc │ │ │ ├── gl2_shim.c │ │ │ ├── gl2_shim.h │ │ │ └── vertex.glsl.inc │ │ ├── gl_alias.c │ │ ├── gl_backend.c │ │ ├── gl_beams.c │ │ ├── gl_context.c │ │ ├── gl_cull.c │ │ ├── gl_decals.c │ │ ├── gl_draw.c │ │ ├── gl_export.h │ │ ├── gl_frustum.c │ │ ├── gl_frustum.h │ │ ├── gl_image.c │ │ ├── gl_local.h │ │ ├── gl_opengl.c │ │ ├── gl_rlight.c │ │ ├── gl_rmain.c │ │ ├── gl_rmath.c │ │ ├── gl_rmisc.c │ │ ├── gl_rpart.c │ │ ├── gl_rsurf.c │ │ ├── gl_sprite.c │ │ ├── gl_studio.c │ │ ├── gl_triapi.c │ │ ├── gl_warp.c │ │ ├── vgl_shim/ │ │ │ ├── vgl_shaders/ │ │ │ │ ├── fragment.cg.inc │ │ │ │ └── vertex.cg.inc │ │ │ ├── vgl_shim.c │ │ │ ├── vgl_shim.h │ │ │ └── wscript │ │ └── wscript │ ├── null/ │ │ ├── r_context.c │ │ └── wscript │ ├── soft/ │ │ ├── adivtab.h │ │ ├── r_aclip.c │ │ ├── r_beams.c │ │ ├── r_bsp.c │ │ ├── r_context.c │ │ ├── r_decals.c │ │ ├── r_draw.c │ │ ├── r_edge.c │ │ ├── r_glblit.c │ │ ├── r_image.c │ │ ├── r_light.c │ │ ├── r_local.h │ │ ├── r_main.c │ │ ├── r_math.c │ │ ├── r_misc.c │ │ ├── r_part.c │ │ ├── r_polyse.c │ │ ├── r_rast.c │ │ ├── r_scan.c │ │ ├── r_sprite.c │ │ ├── r_studio.c │ │ ├── r_surf.c │ │ ├── r_trialias.c │ │ ├── r_triapi.c │ │ └── wscript │ └── vk/ │ ├── NOTES.md │ ├── TODO.md │ ├── camera.c │ ├── camera.h │ ├── common_geometry.c │ ├── data/ │ │ ├── bshift/ │ │ │ ├── luchiki/ │ │ │ │ └── maps/ │ │ │ │ ├── ba_canal1.patch │ │ │ │ ├── ba_canal2.patch │ │ │ │ ├── ba_elevator.patch │ │ │ │ ├── ba_security1.patch │ │ │ │ ├── ba_security2.patch │ │ │ │ ├── ba_tram2.patch │ │ │ │ └── ba_yard5a.patch │ │ │ └── maps/ │ │ │ ├── ba_canal1.rad │ │ │ ├── ba_canal1b.rad │ │ │ ├── ba_elevator.rad │ │ │ ├── ba_hazard1.rad │ │ │ ├── ba_hazard2.rad │ │ │ ├── ba_hazard3.rad │ │ │ ├── ba_hazard4.rad │ │ │ ├── ba_hazard5.rad │ │ │ ├── ba_hazard6.rad │ │ │ ├── ba_power1.rad │ │ │ ├── ba_power2.rad │ │ │ ├── ba_security2.rad │ │ │ ├── ba_teleport1.rad │ │ │ ├── ba_teleport2.rad │ │ │ ├── ba_yard1.rad │ │ │ ├── ba_yard2.rad │ │ │ ├── ba_yard3.rad │ │ │ ├── ba_yard3a.rad │ │ │ ├── ba_yard3b.rad │ │ │ ├── ba_yard4.rad │ │ │ ├── ba_yard4a.rad │ │ │ ├── ba_yard5.rad │ │ │ ├── ba_yard5a.rad │ │ │ └── lights.rad │ │ ├── cstrike/ │ │ │ ├── luchiki/ │ │ │ │ └── maps/ │ │ │ │ └── de_dust2.patch │ │ │ └── maps/ │ │ │ ├── cs_747.rad │ │ │ ├── cs_assault.rad │ │ │ ├── cs_delta_assault.rad │ │ │ ├── cs_italy.rad │ │ │ └── fy_pool_day.rad │ │ └── valve/ │ │ ├── luchiki/ │ │ │ └── maps/ │ │ │ ├── c0a0.patch │ │ │ ├── c0a0a.patch │ │ │ ├── c0a0b.patch │ │ │ ├── c0a0c.patch │ │ │ ├── c0a0d.patch │ │ │ ├── c0a0e.patch │ │ │ ├── c1a0.patch │ │ │ ├── c1a0a.patch │ │ │ ├── c1a0b.patch │ │ │ ├── c1a0c.patch │ │ │ ├── c1a0d.patch │ │ │ ├── c1a0e.patch │ │ │ ├── c1a1.patch │ │ │ ├── c1a1a.patch │ │ │ ├── c1a1b.patch │ │ │ ├── c1a1c.patch │ │ │ ├── c1a1d.patch │ │ │ ├── c1a1f.patch │ │ │ ├── c1a2.patch │ │ │ ├── c1a2a.patch │ │ │ ├── c1a2b.patch │ │ │ ├── c1a2c.patch │ │ │ ├── c1a2d.patch │ │ │ ├── c1a3.patch │ │ │ ├── c1a3a.patch │ │ │ ├── c1a3b-dayone.patch │ │ │ ├── c1a3b.patch │ │ │ ├── c1a3c-dayone.patch │ │ │ ├── c1a3d.patch │ │ │ ├── c1a4d.patch │ │ │ ├── c1a4e.patch │ │ │ ├── c1a4f.patch │ │ │ ├── c1a4g.patch │ │ │ ├── c1a4k.patch │ │ │ ├── c2a1.patch │ │ │ ├── c2a1b.patch │ │ │ ├── c2a2h.patch │ │ │ ├── c2a3.patch │ │ │ ├── c2a3a.patch │ │ │ ├── c2a3b.patch │ │ │ ├── c2a3d.patch │ │ │ ├── c2a3e.patch │ │ │ ├── c2a4a.patch │ │ │ ├── c2a4e.patch │ │ │ ├── c2a4f.patch │ │ │ ├── c2a4g.patch │ │ │ ├── c2a5.patch │ │ │ ├── c2a5c.patch │ │ │ ├── c2a5e.patch │ │ │ ├── c2a5f.patch │ │ │ ├── c3a1.patch │ │ │ ├── c3a1a.patch │ │ │ ├── c3a2.patch │ │ │ ├── c3a2d.patch │ │ │ ├── c3a2e.patch │ │ │ ├── c4a1a.patch │ │ │ ├── c5a1.patch │ │ │ ├── hldemo1.patch │ │ │ ├── hldemo2.patch │ │ │ └── t0a0.patch │ │ └── maps/ │ │ ├── README.txt │ │ ├── boot_camp.rad │ │ ├── c0a0.rad │ │ ├── c0a0a.rad │ │ ├── c0a0b.rad │ │ ├── c0a0c.rad │ │ ├── c0a0d.rad │ │ ├── c0a0e.rad │ │ ├── c1a0.rad │ │ ├── c1a0a.rad │ │ ├── c1a0b.rad │ │ ├── c1a0c.rad │ │ ├── c1a0d.rad │ │ ├── c1a0e.rad │ │ ├── c1a1.rad │ │ ├── c1a1a.rad │ │ ├── c1a1b.rad │ │ ├── c1a1c.rad │ │ ├── c1a1d.rad │ │ ├── c1a1f.rad │ │ ├── c1a2.rad │ │ ├── c1a2a.rad │ │ ├── c1a2b.rad │ │ ├── c1a2c.rad │ │ ├── c1a2d.rad │ │ ├── c1a3.rad │ │ ├── c1a3a.rad │ │ ├── c1a3b.rad │ │ ├── c1a3c.rad │ │ ├── c1a3d.rad │ │ ├── c1a4.rad │ │ ├── c1a4b.rad │ │ ├── c1a4d.rad │ │ ├── c1a4e.rad │ │ ├── c1a4f.rad │ │ ├── c1a4g.rad │ │ ├── c1a4i.rad │ │ ├── c1a4j.rad │ │ ├── c1a4k.rad │ │ ├── c2a1.rad │ │ ├── c2a1a.rad │ │ ├── c2a1b.rad │ │ ├── c2a2.rad │ │ ├── c2a2a.rad │ │ ├── c2a2b1.rad │ │ ├── c2a2b2.rad │ │ ├── c2a2c.rad │ │ ├── c2a2d.rad │ │ ├── c2a2e.rad │ │ ├── c2a2f.rad │ │ ├── c2a2g.rad │ │ ├── c2a2h.rad │ │ ├── c2a3.rad │ │ ├── c2a3a.rad │ │ ├── c2a3b.rad │ │ ├── c2a3c.rad │ │ ├── c2a3d.rad │ │ ├── c2a3e.rad │ │ ├── c2a4a.rad │ │ ├── c2a4b.rad │ │ ├── c2a4c.rad │ │ ├── c2a4d.rad │ │ ├── c2a4e.rad │ │ ├── c2a4f.rad │ │ ├── c2a4g.rad │ │ ├── c2a5.rad │ │ ├── c2a5a.rad │ │ ├── c2a5d.rad │ │ ├── c2a5e.rad │ │ ├── c2a5f.rad │ │ ├── c2a5x.rad │ │ ├── c3a1.rad │ │ ├── c3a1a.rad │ │ ├── c3a1b.rad │ │ ├── c3a2.rad │ │ ├── c3a2a.rad │ │ ├── c3a2b.rad │ │ ├── c3a2c.rad │ │ ├── c3a2d.rad │ │ ├── c3a2e.rad │ │ ├── c3a2f.rad │ │ ├── c4a1b.rad │ │ ├── c4a1c.rad │ │ ├── c4a1d.rad │ │ ├── c4a1e.rad │ │ ├── c4a1f.rad │ │ ├── c4a2.rad │ │ ├── c4a2a.rad │ │ ├── c4a2b.rad │ │ ├── c4a3.rad │ │ ├── c5a1.rad │ │ ├── cornell.rad │ │ ├── crossfire.rad │ │ ├── datacore.rad │ │ ├── doublecross.rad │ │ ├── frenzy.rad │ │ ├── gasworks.rad │ │ ├── hldemo1.rad │ │ ├── hldemo2.rad │ │ ├── lambda_bunker.rad │ │ ├── lights.rad │ │ ├── rapidcore.rad │ │ ├── rustmill.rad │ │ ├── snark_pit.rad │ │ ├── stalkyard.rad │ │ ├── subtransit.rad │ │ ├── t0a0.rad │ │ ├── t0a0a.rad │ │ ├── t0a0b.rad │ │ ├── t0a0b1.rad │ │ ├── t0a0b2.rad │ │ ├── t0a0c.rad │ │ ├── t0a0d.rad │ │ ├── team9.rad │ │ ├── thehill.rad │ │ ├── undertow.rad │ │ └── xen_dm.rad │ ├── dumbspter.c │ ├── infotool.c │ ├── r_block.c │ ├── r_block.h │ ├── r_decals.c │ ├── r_decals.h │ ├── r_speeds.c │ ├── r_speeds.h │ ├── r_textures.c │ ├── r_textures.h │ ├── ray_materials.md │ ├── rlight.c │ ├── rt_kusochki.c │ ├── rt_kusochki.h │ ├── sebastian.py │ ├── shaders/ │ │ ├── 2d.frag │ │ ├── 2d.vert │ │ ├── additive.rahit │ │ ├── alphamask.rahit │ │ ├── atrous.glsl │ │ ├── bluenoise.glsl │ │ ├── bounce.comp │ │ ├── brdf.glsl │ │ ├── brdf.h │ │ ├── brush.frag │ │ ├── brush.vert │ │ ├── color_spaces.glsl │ │ ├── debug.glsl │ │ ├── denoiser.comp │ │ ├── denoiser_config.glsl │ │ ├── denoiser_utils.glsl │ │ ├── diffuse_gi_sh_atrous.glsl │ │ ├── diffuse_gi_sh_denoise_init.comp │ │ ├── diffuse_gi_sh_denoise_pass_1.comp │ │ ├── diffuse_gi_sh_denoise_pass_2.comp │ │ ├── diffuse_gi_sh_denoise_pass_3.comp │ │ ├── diffuse_gi_sh_denoise_pass_4.comp │ │ ├── diffuse_gi_sh_denoise_pass_5.comp │ │ ├── diffuse_gi_sh_denoise_save.comp │ │ ├── empty.rmiss │ │ ├── indirect_diffuse_atrous1.comp │ │ ├── light.glsl │ │ ├── light_common.glsl │ │ ├── light_polygon.glsl │ │ ├── noise.glsl │ │ ├── peters2021-sampling/ │ │ │ ├── math_constants.glsl │ │ │ ├── polygon_clipping.glsl │ │ │ └── polygon_sampling.glsl │ │ ├── ray_common.glsl │ │ ├── ray_common_alphatest.rahit │ │ ├── ray_interop.h │ │ ├── ray_kusochki.glsl │ │ ├── ray_light_direct.glsl │ │ ├── ray_light_direct_point.comp │ │ ├── ray_light_direct_poly.comp │ │ ├── ray_primary.comp │ │ ├── ray_primary.rchit │ │ ├── ray_primary.rgen │ │ ├── ray_primary.rmiss │ │ ├── ray_primary_common.glsl │ │ ├── ray_primary_hit.glsl │ │ ├── ray_shadow.rchit │ │ ├── ray_shadow.rmiss │ │ ├── ray_shadow_interface.glsl │ │ ├── rt.json │ │ ├── rt_geometry.glsl │ │ ├── sky.frag │ │ ├── sky.vert │ │ ├── skybox.glsl │ │ ├── spatial_reconstruction.glsl │ │ ├── spatial_reconstruction_pass1.comp │ │ ├── spatial_reconstruction_pass2.comp │ │ ├── spherical_harmonics.glsl │ │ ├── trace_decals.glsl │ │ ├── trace_simple_blending.glsl │ │ └── utils.glsl │ ├── spirv.py │ ├── std/ │ │ ├── alolcator.c │ │ ├── alolcator.h │ │ ├── arrays.c │ │ ├── arrays.h │ │ ├── bitarray.c │ │ ├── bitarray.h │ │ ├── debugbreak.h │ │ ├── flipping.c │ │ ├── flipping.h │ │ ├── pcg.h │ │ ├── profiler.c │ │ ├── profiler.h │ │ ├── stringview.c │ │ ├── stringview.h │ │ ├── unordered_roadmap.c │ │ └── unordered_roadmap.h │ ├── tests/ │ │ └── unordered_roadmap.c │ ├── vk_beams.c │ ├── vk_beams.h │ ├── vk_brush.c │ ├── vk_brush.h │ ├── vk_common.h │ ├── vk_const.h │ ├── vk_core.c │ ├── vk_core.h │ ├── vk_cvar.c │ ├── vk_cvar.h │ ├── vk_entity_data.c │ ├── vk_entity_data.h │ ├── vk_framectl.c │ ├── vk_framectl.h │ ├── vk_geometry.c │ ├── vk_geometry.h │ ├── vk_light.c │ ├── vk_light.h │ ├── vk_lightmap.c │ ├── vk_lightmap.h │ ├── vk_logs.c │ ├── vk_logs.h │ ├── vk_mapents.c │ ├── vk_mapents.h │ ├── vk_materials.c │ ├── vk_materials.h │ ├── vk_math.c │ ├── vk_math.h │ ├── vk_overlay.c │ ├── vk_overlay.h │ ├── vk_ray_accel.h │ ├── vk_ray_internal.h │ ├── vk_ray_model.c │ ├── vk_render.c │ ├── vk_render.h │ ├── vk_renderstate.c │ ├── vk_renderstate.h │ ├── vk_rmain.c │ ├── vk_rpart.c │ ├── vk_rpart.h │ ├── vk_rtx.c │ ├── vk_rtx.h │ ├── vk_scene.c │ ├── vk_scene.h │ ├── vk_speeds.c │ ├── vk_speeds.h │ ├── vk_sprite.c │ ├── vk_sprite.h │ ├── vk_studio.c │ ├── vk_studio.h │ ├── vk_studio_model.c │ ├── vk_studio_model.h │ ├── vk_textures.c │ ├── vk_textures.h │ ├── vk_triapi.c │ ├── vk_triapi.h │ ├── vulkan/ │ │ ├── VBarrier.c │ │ ├── VBarrier.h │ │ ├── VBuffer.c │ │ ├── VBuffer.h │ │ ├── VCombuf.c │ │ ├── VCombuf.h │ │ ├── VCommandPool.c │ │ ├── VCommandPool.h │ │ ├── VDescriptor.c │ │ ├── VDescriptor.h │ │ ├── VDevice.c │ │ ├── VDevice.h │ │ ├── VDevmem.c │ │ ├── VDevmem.h │ │ ├── VImage.c │ │ ├── VImage.h │ │ ├── VImageExtra.h │ │ ├── VMeatpipe.c │ │ ├── VMeatpipe.h │ │ ├── VMisc.c │ │ ├── VNvAftermath.c │ │ ├── VNvAftermath.h │ │ ├── VPass.c │ │ ├── VPass.h │ │ ├── VPerfQuery.c │ │ ├── VPerfQuery.h │ │ ├── VPipeline.c │ │ ├── VPipeline.h │ │ ├── VRayAccel.c │ │ ├── VResource.c │ │ ├── VResource.h │ │ ├── VStaging.c │ │ ├── VStaging.h │ │ ├── VSwapchain.c │ │ └── VSwapchain.h │ └── wscript ├── scripts/ │ ├── build-ninja.py │ ├── cirrus/ │ │ └── build_freebsd.sh │ ├── configure-ninja.py │ ├── flatpak/ │ │ ├── run.sh │ │ ├── su.xash.Engine.Compat.i386.desktop │ │ └── su.xash.Engine.Compat.i386.yml │ ├── gha/ │ │ ├── build_android.sh │ │ ├── build_apple.sh │ │ ├── build_linux-e2k.sh │ │ ├── build_linux.sh │ │ ├── build_motomagx.sh │ │ ├── build_nswitch.sh │ │ ├── build_nswitch_docker.sh │ │ ├── build_psvita.sh │ │ ├── build_win32.sh │ │ ├── deps_android.sh │ │ ├── deps_apple.sh │ │ ├── deps_linux-e2k.sh │ │ ├── deps_linux.sh │ │ ├── deps_motomagx.sh │ │ ├── deps_nswitch.sh │ │ ├── deps_psvita.sh │ │ ├── deps_win32.sh │ │ └── linux/ │ │ ├── AppRun │ │ └── xash3d-fwgs.desktop │ ├── lib-e2k.sh │ ├── lib.sh │ ├── makepak.py │ ├── sailfish/ │ │ ├── build.sh │ │ ├── deploy.sh │ │ ├── harbour-xash3d-fwgs.desktop │ │ ├── harbour-xash3d-fwgs.spec │ │ └── run.sh │ ├── waifulib/ │ │ ├── c_emscripten.py │ │ ├── compiler_optimizations.py │ │ ├── glslc.py │ │ ├── ninja.py │ │ ├── ninja_syntax.py │ │ ├── nswitch.py │ │ ├── owcc.py │ │ ├── psp.py │ │ ├── psvita.py │ │ ├── sdl2.py │ │ ├── sebastian.py │ │ ├── vgui.py │ │ ├── xcompile.py │ │ ├── xshlib.py │ │ └── zip.py │ └── xashds@.service ├── uncrustify.cfg ├── utils/ │ ├── mdldec/ │ │ ├── mdldec.c │ │ ├── mdldec.h │ │ ├── qc.c │ │ ├── qc.h │ │ ├── res/ │ │ │ └── activities.txt │ │ ├── settings.h │ │ ├── smd.c │ │ ├── smd.h │ │ ├── texture.c │ │ ├── texture.h │ │ ├── utils.c │ │ ├── utils.h │ │ ├── version.h │ │ └── wscript │ ├── run-fuzzer/ │ │ ├── run-fuzzer.c │ │ └── wscript │ └── xar/ │ ├── wscript │ └── xar.c ├── waf ├── waf.bat └── wscript ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cirrus.yml ================================================ task: name: freebsd-14-amd64 freebsd_instance: image_family: freebsd-14-2 setup_script: - pkg update - pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh task: name: freebsd-15-amd64 freebsd_instance: image_family: freebsd-15-0-snap setup_script: - pkg update - pkg install -y pkgconf git sdl2 python fontconfig libvorbis opusfile bzip2 libbacktrace - git submodule update --init --recursive test_script: - ./scripts/cirrus/build_freebsd.sh ================================================ FILE: .editorconfig ================================================ # this file is just a suggestion, you might follow it, you might not root = true [*] charset = utf-8 end_of_line = lf indent_style = tab insert_final_newline = true trim_trailing_whitespace = true ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] # Visual C++ Formatting settings cpp_indent_braces = false cpp_indent_multi_line_relative_to = innermost_parenthesis cpp_indent_within_parentheses = indent cpp_indent_preserve_within_parentheses = true cpp_indent_case_contents = true cpp_indent_case_labels = false cpp_indent_case_contents_when_block = false cpp_indent_lambda_braces_when_parameter = true cpp_indent_goto_labels = one_left cpp_indent_preprocessor = leftmost_column cpp_indent_access_specifiers = false cpp_indent_namespace_contents = true cpp_indent_preserve_comments = false cpp_new_line_before_open_brace_namespace = new_line cpp_new_line_before_open_brace_type = new_line cpp_new_line_before_open_brace_function = new_line cpp_new_line_before_open_brace_block = new_line cpp_new_line_before_open_brace_lambda = new_line cpp_new_line_scope_braces_on_separate_lines = true cpp_new_line_close_brace_same_line_empty_type = false cpp_new_line_close_brace_same_line_empty_function = false cpp_new_line_before_catch = true cpp_new_line_before_else = true cpp_new_line_before_while_in_do_while = false cpp_space_before_function_open_parenthesis = remove cpp_space_within_parameter_list_parentheses = true cpp_space_between_empty_parameter_list_parentheses = true cpp_space_after_keywords_in_control_flow_statements = false cpp_space_within_control_flow_statement_parentheses = true cpp_space_before_lambda_open_parenthesis = false cpp_space_within_cast_parentheses = false cpp_space_after_cast_close_parenthesis = false cpp_space_within_expression_parentheses = true cpp_space_before_block_open_brace = true cpp_space_between_empty_braces = true cpp_space_before_initializer_list_open_brace = false cpp_space_within_initializer_list_braces = true cpp_space_preserve_in_initializer_list = true cpp_space_before_open_square_bracket = false cpp_space_within_square_brackets = false cpp_space_before_empty_square_brackets = false cpp_space_between_empty_square_brackets = false cpp_space_group_square_brackets = true cpp_space_within_lambda_brackets = false cpp_space_between_empty_lambda_brackets = false cpp_space_before_comma = false cpp_space_after_comma = true cpp_space_remove_around_member_operators = true cpp_space_before_inheritance_colon = true cpp_space_before_constructor_colon = true cpp_space_remove_before_semicolon = true cpp_space_after_semicolon = true cpp_space_remove_around_unary_operator = true cpp_space_around_binary_operator = insert cpp_space_around_assignment_operator = insert cpp_space_pointer_reference_alignment = right cpp_space_around_ternary_operator = insert cpp_use_unreal_engine_macro_formatting = true cpp_wrap_preserve_blocks = one_liners # IDEA settings ij_c_add_brief_tag = false ij_c_add_getter_prefix = true ij_c_add_setter_prefix = true ij_c_align_dictionary_pair_values = false ij_c_align_group_field_declarations = false ij_c_align_init_list_in_columns = true ij_c_align_multiline_array_initializer_expression = false ij_c_align_multiline_assignment = false ij_c_align_multiline_binary_operation = false ij_c_align_multiline_chained_methods = false ij_c_align_multiline_for = true ij_c_align_multiline_ternary_operation = false ij_c_array_initializer_comma_on_next_line = false ij_c_array_initializer_new_line_after_left_brace = false ij_c_array_initializer_right_brace_on_new_line = false ij_c_array_initializer_wrap = off ij_c_assignment_wrap = off ij_c_binary_operation_sign_on_next_line = false ij_c_binary_operation_wrap = off ij_c_blank_lines_after_class_header = 0 ij_c_blank_lines_after_imports = 1 ij_c_blank_lines_around_class = 1 ij_c_blank_lines_around_field = 0 ij_c_blank_lines_around_field_in_interface = 0 ij_c_blank_lines_around_method = 1 ij_c_blank_lines_around_method_in_interface = 1 ij_c_blank_lines_around_namespace = 0 ij_c_blank_lines_around_properties_in_declaration = 0 ij_c_blank_lines_around_properties_in_interface = 0 ij_c_blank_lines_before_imports = 1 ij_c_blank_lines_before_method_body = 0 ij_c_block_brace_placement = next_line ij_c_block_brace_style = next_line ij_c_block_comment_at_first_column = true ij_c_catch_on_new_line = false ij_c_class_brace_style = next_line ij_c_class_constructor_init_list_align_multiline = true ij_c_class_constructor_init_list_comma_on_next_line = false ij_c_class_constructor_init_list_new_line_after_colon = never ij_c_class_constructor_init_list_new_line_before_colon = if_long ij_c_class_constructor_init_list_wrap = normal ij_c_copy_is_deep = false ij_c_create_interface_for_categories = true ij_c_declare_generated_methods = true ij_c_description_include_member_names = true ij_c_discharged_short_ternary_operator = false ij_c_do_not_add_breaks = false ij_c_do_while_brace_force = never ij_c_else_on_new_line = false ij_c_enum_constants_comma_on_next_line = false ij_c_enum_constants_wrap = off ij_c_for_brace_force = never ij_c_for_statement_new_line_after_left_paren = false ij_c_for_statement_right_paren_on_new_line = false ij_c_for_statement_wrap = off ij_c_function_brace_placement = next_line ij_c_function_call_arguments_align_multiline = true ij_c_function_call_arguments_align_multiline_pars = false ij_c_function_call_arguments_comma_on_next_line = false ij_c_function_call_arguments_new_line_after_lpar = false ij_c_function_call_arguments_new_line_before_rpar = false ij_c_function_call_arguments_wrap = normal ij_c_function_non_top_after_return_type_wrap = normal ij_c_function_parameters_align_multiline = true ij_c_function_parameters_align_multiline_pars = false ij_c_function_parameters_comma_on_next_line = false ij_c_function_parameters_new_line_after_lpar = false ij_c_function_parameters_new_line_before_rpar = false ij_c_function_parameters_wrap = normal ij_c_function_top_after_return_type_wrap = normal ij_c_generate_additional_eq_operators = true ij_c_generate_additional_rel_operators = true ij_c_generate_class_constructor = true ij_c_generate_comparison_operators_use_std_tie = false ij_c_generate_instance_variables_for_properties = ask ij_c_generate_operators_as_members = true ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} ij_c_if_brace_force = never ij_c_in_line_short_ternary_operator = true ij_c_indent_block_comment = true ij_c_indent_c_struct_members = 4 ij_c_indent_case_from_switch = true ij_c_indent_class_members = 4 ij_c_indent_directive_as_code = false ij_c_indent_implementation_members = 0 ij_c_indent_inside_code_block = 4 ij_c_indent_interface_members = 0 ij_c_indent_interface_members_except_ivars_block = false ij_c_indent_namespace_members = 4 ij_c_indent_preprocessor_directive = 0 ij_c_indent_visibility_keywords = 0 ij_c_insert_override = true ij_c_insert_virtual_with_override = false ij_c_introduce_auto_consts = false ij_c_introduce_auto_vars = false ij_c_introduce_const_params = false ij_c_introduce_const_vars = false ij_c_introduce_constexpr_consts = false ij_c_introduce_generate_property = false ij_c_introduce_generate_synthesize = true ij_c_introduce_globals_to_header = true ij_c_introduce_prop_to_private_category = false ij_c_introduce_static_consts = true ij_c_introduce_use_ns_types = false ij_c_ivars_prefix = _ ij_c_ivars_suffix = ij_c_keep_blank_lines_before_end = 2 ij_c_keep_blank_lines_before_right_brace = 2 ij_c_keep_blank_lines_in_code = 2 ij_c_keep_blank_lines_in_declarations = 2 ij_c_keep_case_expressions_in_one_line = false ij_c_keep_control_statement_in_one_line = false ij_c_keep_directive_at_first_column = true ij_c_keep_first_column_comment = false ij_c_keep_line_breaks = true ij_c_keep_nested_namespaces_in_one_line = false ij_c_keep_simple_blocks_in_one_line = false ij_c_keep_simple_methods_in_one_line = false ij_c_keep_structures_in_one_line = false ij_c_lambda_capture_list_align_multiline = false ij_c_lambda_capture_list_align_multiline_bracket = false ij_c_lambda_capture_list_comma_on_next_line = false ij_c_lambda_capture_list_new_line_after_lbracket = false ij_c_lambda_capture_list_new_line_before_rbracket = false ij_c_lambda_capture_list_wrap = off ij_c_line_comment_add_space = false ij_c_line_comment_at_first_column = true ij_c_method_brace_placement = end_of_line ij_c_method_call_arguments_align_by_colons = true ij_c_method_call_arguments_align_multiline = false ij_c_method_call_arguments_special_dictionary_pairs_treatment = true ij_c_method_call_arguments_wrap = off ij_c_method_call_chain_wrap = off ij_c_method_parameters_align_by_colons = true ij_c_method_parameters_align_multiline = false ij_c_method_parameters_wrap = off ij_c_namespace_brace_placement = next_line ij_c_parentheses_expression_new_line_after_left_paren = false ij_c_parentheses_expression_right_paren_on_new_line = false ij_c_place_assignment_sign_on_next_line = false ij_c_property_nonatomic = true ij_c_put_ivars_to_implementation = true ij_c_refactor_compatibility_aliases_and_classes = true ij_c_refactor_properties_and_ivars = true ij_c_release_style = ivar ij_c_retain_object_parameters_in_constructor = true ij_c_semicolon_after_method_signature = false ij_c_shift_operation_align_multiline = true ij_c_shift_operation_wrap = normal ij_c_show_non_virtual_functions = false ij_c_space_after_colon = true ij_c_space_after_colon_in_foreach = true ij_c_space_after_colon_in_selector = false ij_c_space_after_comma = true ij_c_space_after_cup_in_blocks = false ij_c_space_after_dictionary_literal_colon = true ij_c_space_after_for_semicolon = true ij_c_space_after_init_list_colon = true ij_c_space_after_method_parameter_type_parentheses = false ij_c_space_after_method_return_type_parentheses = false ij_c_space_after_pointer_in_declaration = false ij_c_space_after_quest = true ij_c_space_after_reference_in_declaration = false ij_c_space_after_reference_in_rvalue = false ij_c_space_after_structures_rbrace = true ij_c_space_after_superclass_colon = true ij_c_space_after_type_cast = false ij_c_space_after_visibility_sign_in_method_declaration = true ij_c_space_before_autorelease_pool_lbrace = true ij_c_space_before_catch_keyword = true ij_c_space_before_catch_left_brace = true ij_c_space_before_catch_parentheses = false ij_c_space_before_category_parentheses = true ij_c_space_before_chained_send_message = true ij_c_space_before_class_left_brace = true ij_c_space_before_colon = true ij_c_space_before_colon_in_foreach = true ij_c_space_before_comma = false ij_c_space_before_dictionary_literal_colon = true ij_c_space_before_do_left_brace = true ij_c_space_before_else_keyword = true ij_c_space_before_else_left_brace = true ij_c_space_before_export_lbrace = true ij_c_space_before_for_left_brace = true ij_c_space_before_for_parentheses = false ij_c_space_before_for_semicolon = false ij_c_space_before_if_left_brace = true ij_c_space_before_if_parentheses = false ij_c_space_before_init_list = false ij_c_space_before_init_list_colon = true ij_c_space_before_method_call_parentheses = false ij_c_space_before_method_left_brace = true ij_c_space_before_method_parentheses = false ij_c_space_before_namespace_lbrace = true ij_c_space_before_pointer_in_declaration = true ij_c_space_before_property_attributes_parentheses = false ij_c_space_before_protocols_brackets = true ij_c_space_before_quest = true ij_c_space_before_reference_in_declaration = true ij_c_space_before_superclass_colon = true ij_c_space_before_switch_left_brace = true ij_c_space_before_switch_parentheses = false ij_c_space_before_template_call_lt = false ij_c_space_before_template_declaration_lt = true ij_c_space_before_try_left_brace = true ij_c_space_before_while_keyword = true ij_c_space_before_while_left_brace = true ij_c_space_before_while_parentheses = false ij_c_space_between_adjacent_brackets = false ij_c_space_between_operator_and_punctuator = false ij_c_space_within_empty_array_initializer_braces = true ij_c_spaces_around_additive_operators = true ij_c_spaces_around_assignment_operators = true ij_c_spaces_around_bitwise_operators = true ij_c_spaces_around_equality_operators = true ij_c_spaces_around_lambda_arrow = true ij_c_spaces_around_logical_operators = true ij_c_spaces_around_multiplicative_operators = true ij_c_spaces_around_pm_operators = false ij_c_spaces_around_relational_operators = true ij_c_spaces_around_shift_operators = true ij_c_spaces_around_unary_operator = false ij_c_spaces_within_array_initializer_braces = true ij_c_spaces_within_braces = false ij_c_spaces_within_brackets = false ij_c_spaces_within_cast_parentheses = false ij_c_spaces_within_catch_parentheses = true ij_c_spaces_within_category_parentheses = false ij_c_spaces_within_empty_braces = false ij_c_spaces_within_empty_function_call_parentheses = true ij_c_spaces_within_empty_function_declaration_parentheses = true ij_c_spaces_within_empty_lambda_capture_list_bracket = false ij_c_spaces_within_empty_template_call_ltgt = false ij_c_spaces_within_empty_template_declaration_ltgt = false ij_c_spaces_within_for_parentheses = true ij_c_spaces_within_function_call_parentheses = true ij_c_spaces_within_function_declaration_parentheses = true ij_c_spaces_within_if_parentheses = true ij_c_spaces_within_lambda_capture_list_bracket = false ij_c_spaces_within_method_parameter_type_parentheses = false ij_c_spaces_within_method_return_type_parentheses = false ij_c_spaces_within_parentheses = true ij_c_spaces_within_property_attributes_parentheses = false ij_c_spaces_within_protocols_brackets = false ij_c_spaces_within_send_message_brackets = false ij_c_spaces_within_structured_binding_list_bracket = false ij_c_spaces_within_switch_parentheses = true ij_c_spaces_within_template_call_ltgt = false ij_c_spaces_within_template_declaration_ltgt = false ij_c_spaces_within_template_double_gt = false ij_c_spaces_within_while_parentheses = true ij_c_special_else_if_treatment = true ij_c_structured_binding_list_align_multiline = false ij_c_structured_binding_list_align_multiline_bracket = false ij_c_structured_binding_list_comma_on_next_line = false ij_c_structured_binding_list_new_line_after_lbracket = false ij_c_structured_binding_list_new_line_before_rbracket = false ij_c_structured_binding_list_wrap = off ij_c_superclass_list_after_colon = never ij_c_superclass_list_align_multiline = true ij_c_superclass_list_before_colon = if_long ij_c_superclass_list_comma_on_next_line = false ij_c_superclass_list_wrap = on_every_item ij_c_tag_prefix_of_block_comment = at ij_c_tag_prefix_of_line_comment = back_slash ij_c_template_call_arguments_align_multiline = false ij_c_template_call_arguments_align_multiline_pars = false ij_c_template_call_arguments_comma_on_next_line = false ij_c_template_call_arguments_new_line_after_lt = false ij_c_template_call_arguments_new_line_before_gt = false ij_c_template_call_arguments_wrap = off ij_c_template_declaration_function_body_indent = false ij_c_template_declaration_function_wrap = split_into_lines ij_c_template_declaration_struct_body_indent = false ij_c_template_declaration_struct_wrap = split_into_lines ij_c_template_parameters_align_multiline = false ij_c_template_parameters_align_multiline_pars = false ij_c_template_parameters_comma_on_next_line = false ij_c_template_parameters_new_line_after_lt = false ij_c_template_parameters_new_line_before_gt = false ij_c_template_parameters_wrap = off ij_c_ternary_operation_signs_on_next_line = false ij_c_ternary_operation_wrap = off ij_c_type_qualifiers_placement = before ij_c_use_modern_casts = true ij_c_use_setters_in_constructor = true ij_c_while_brace_force = never ij_c_while_on_new_line = false ij_c_wrap_property_declaration = off ================================================ FILE: .gitattributes ================================================ *.c text eol=lf diff=cpp *.h text eol=lf diff=cpp wscript text eol=lf diff=python ================================================ FILE: .github/FUNDING.yml ================================================ custom: https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/donate.md ================================================ FILE: .github/ISSUE_TEMPLATE/crash-report.md ================================================ --- name: Crash report about: The renderer crashed. Let us know title: '' labels: bug, crash assignees: '' --- Note that this is only for Vulkan/Ray tracing renderer. Prior to submitting anything here make sure that the game does not crash with native GL renderer (`-ref gl`). **To Reproduce** 1. Map name or attached save file 2. Actions to perform (e.g. go to that room and do this; screenshots appreciated) **Artifacts** E.g. attach last few lines of logs (it may be an assert that's informative). **Moar context:** - Commit hash of the build - OS - GPU vendor and model - Driver version ================================================ FILE: .github/ISSUE_TEMPLATE/visual-glitches-report.md ================================================ --- name: Visual glitches report about: Something doesn't look right title: '' labels: bug, ray tracing, visual bug assignees: '' --- Note that: - this is only for Vulkan/Ray tracing renderer. Prior to submitting anything here make sure that the game looks correct with native GL renderer (`-ref gl`). - the renderer is WIP so there are way too many known visual bugs. Make sure to search issues first for it is very likely that we already know about it. - Traditional rasterizer is not being actively maintained, so visual glitches in that won't be addressed for a while (unless they stall rt renderer progress). **To reproduce** 1. Map name or attached save file 2. Steps to do (e.g. go to a specific room and perform some action) **Screenshots** 1. The thing that looks wrong 2. How it's supposed to look, e.g.: - screenshot from the same angle made using vanilla GL renderer - screenshot from a production ready PBR/RT renderer of a similar scene with similar materials and lighting parameters. **Moar context** - Commit hash - OS - GPU vendor and model - Driver version ================================================ FILE: .github/workflows/c-cpp.yml ================================================ name: Build & Deploy Engine on: push: paths-ignore: - '**.md' - 'ref/vk/data/**' pull_request: paths-ignore: - '**.md' - 'ref/vk/data/**' jobs: # cleanup: # runs-on: self-hosted # steps: # - name: Cleanup # run: rm -rf .* || true build: runs-on: ${{ matrix.os }} continue-on-error: true strategy: fail-fast: false matrix: include: # FIXME Linux build specifically want oldest Ubuntu as possible # to be crossdistribution compatible, otherwise use ubuntu-latest - os: ubuntu-22.04 targetos: linux targetarch: amd64 - os: ubuntu-22.04 targetos: linux targetarch: i386 - os: ubuntu-22.04 targetos: linux targetarch: arm64 cross: true - os: ubuntu-22.04 targetos: linux targetarch: armhf cross: true # FIXME currently vulkan build fails for these, as Vulkan SDK is not easily available. # - os: ubuntu-24.04 # riscv64 would benefit from having latest compilers # targetos: linux # targetarch: riscv64 # cross: true # - os: ubuntu-22.04 # targetos: linux # targetarch: ppc64el # cross: true # - os: ubuntu-aarch64-22.04 # targetos: linux # targetarch: aarch64 # - os: ubuntu-latest # targetos: linux # targetarch: e2k-8c # cross: true # FIXME Enable Vulkan for Android too # - os: ubuntu-latest # targetos: android # targetarch: multiarch # - os: ubuntu-22.04 # targetos: motomagx # targetarch: armv6 # - os: ubuntu-20.04 # targetos: nswitch # targetarch: arm64 # - os: ubuntu-20.04 # targetos: psvita # targetarch: armv7hf - os: windows-latest targetos: win32 targetarch: amd64 - os: windows-2022 # always use the oldest possible for 32-bit because of older compilers, and better support of certain legacy OSes targetos: win32 targetarch: i386 # FIXME Vulkan doesn't care about these for now # - os: macos-14 # arm64 as per github documentation # targetos: apple # targetarch: arm64 # - os: macos-13 # x86 as per github documentation (will they fix it before they deprecate this version?..) # targetos: apple # targetarch: amd64 env: SDL_VERSION: 2.32.8 FFMPEG_VERSION: 7.1 VULKAN_SDK_VERSION: 1.4.321.1 GH_CPU_ARCH: ${{ matrix.targetarch }} GH_CPU_OS: ${{ matrix.targetos }} GH_CROSSCOMPILING: ${{ matrix.cross }} steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive - name: Install dependencies run: bash scripts/gha/deps_${{ matrix.targetos }}.sh - name: Install Vulkan SDK uses: jakoch/install-vulkan-sdk-action@v1 with: vulkan_version: ${{ env.VULKAN_SDK_VERSION }} install_runtime: false cache: true stripdown: true - name: Build engine env: FWGS_PFX_PASSWORD: ${{ secrets.FWGS_PFX_PASSWORD }} run: bash scripts/gha/build_${{ matrix.targetos }}.sh - name: Upload engine (artifacts) uses: actions/upload-artifact@v4 with: name: artifact-${{ matrix.targetos }}-${{ matrix.targetarch }} path: artifacts/* flatpak: runs-on: ubuntu-latest continue-on-error: true strategy: matrix: include: - app: su.xash.Engine.Compat.i386 container: image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08 options: --privileged steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive - name: Build flatpak (Compat.i386) uses: FWGS/flatpak-github-actions/flatpak-builder@v6.5 with: bundle: ${{ matrix.app }}.flatpak manifest-path: scripts/flatpak/${{ matrix.app }}.yml cache: false release: name: "Upload releases" runs-on: ubuntu-latest needs: [build, flatpak] if: ${{ github.event_name == 'push' }} steps: - name: Remove old release, fetch artifacts, repackage binaries and upload new release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ github.ref_name == 'master' && 'continuous' || format('continuous-{0}', github.ref_name) }} run: | gh release delete "$RELEASE_TAG" \ --yes \ --cleanup-tag \ --repo "$GITHUB_REPOSITORY" || true gh run download "$GITHUB_RUN_ID" \ --dir artifacts/ \ --repo "$GITHUB_REPOSITORY" pushd artifacts/ echo "Found artifacts:" ls for i in $(find -mindepth 1 -maxdepth 1 -type d); do mv "$i"/* . rm -rf "$i" done echo "Repackaged artifacts:" ls -R popd sleep 20s gh release create "$RELEASE_TAG" artifacts/* \ --title "Xash3D FWGS Continuous ${{ github.ref_name }} Build" \ --target $GITHUB_SHA \ --repo "$GITHUB_REPOSITORY" \ --prerelease ================================================ FILE: .gitignore ================================================ # Binaries *.o *.so *.a *.framework # Other *.save prefix/ # Qt Creator for some reason creates *.user.$version files, so exclude it too *.user* *~ ### Xcode ### build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.xcuserstate ### OSX ### .DS_Store .AppleDouble .LSOverride # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### CMake ### CMakeCache.txt CMakeFiles Makefile cmake_install.cmake install_manifest.txt CMakeLists.txt* CMakeScripts Testing compile_commands.json _deps # makedepend Makefile.dep *.bak ALL_BUILD.* INSTALL.* ZERO_CHECK.* CMakeLists.txt # Visual Studio *.obj *.dll *.exp *.lib *.suo *.sdf Debug/ Release/ ipch/ *.opensdf # Use CMake for generating projects *.vcxproj.filters *.vcxproj *.sln ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ orleans.codegen.cs # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # PVS Studio for Linux output *.cl.cfg # Kate *.kate-swp *.swp # QtCreator build-* # Android *.apk *.config *.creator *.includes *.files # Waf build_current *waf-*/ *waf3-*/ .lock-waf* *.lastbuildstate *.unsuccessfulbuild __pycache__ *.pyc .waf* # MSVC projects *.vcproj *.sln *.vcxproj # vim/cscope/coc/clangd compile_commands.json cscope.out core # Visual Studio Code .vscode/* *.code-workspace .history/* .cache/* enc_temp_folder/ # KDevelop4 *.kdev4 # ccls langauge server .ccls-* # JetBrains .idea/ cmake-build-* # some Android-specific build stuff 3rdparty/SDL 3rdparty/hlsdk-portable !scripts/build-ninja.py ================================================ FILE: .gitmodules ================================================ [submodule "mainui"] path = 3rdparty/mainui url = https://github.com/FWGS/mainui_cpp [submodule "ref_gl/nanogl"] path = 3rdparty/nanogl url = https://github.com/FWGS/nanogl [submodule "ref_gl/gl-wes-v2"] path = 3rdparty/gl-wes-v2 url = https://github.com/FWGS/gl-wes-v2 [submodule "ref_gl/gl4es"] path = 3rdparty/gl4es/gl4es url = https://github.com/ptitSeb/gl4es [submodule "vgui_support"] path = 3rdparty/vgui_support url = https://github.com/FWGS/vgui_support [submodule "opus"] path = 3rdparty/opus/opus url = https://github.com/xiph/opus [submodule "3rdparty/xash-extras"] path = 3rdparty/extras/xash-extras url = https://github.com/FWGS/xash-extras [submodule "3rdparty/bzip2/bzip2"] path = 3rdparty/bzip2/bzip2 url = https://gitlab.com/bzip2/bzip2 [submodule "3rdparty/MultiEmulator"] path = 3rdparty/MultiEmulator url = https://github.com/FWGS/MultiEmulator [submodule "3rdparty/libogg/libogg"] path = 3rdparty/libogg/libogg url = https://github.com/xiph/ogg.git [submodule "3rdparty/vorbis/vorbis-src"] path = 3rdparty/vorbis/vorbis-src url = https://github.com/xiph/vorbis.git [submodule "3rdparty/opusfile/opusfile"] path = 3rdparty/opusfile/opusfile url = https://github.com/xiph/opusfile.git [submodule "3rdparty/libbacktrace/libbacktrace"] path = 3rdparty/libbacktrace/libbacktrace url = https://github.com/ianlancetaylor/libbacktrace [submodule "3rdparty/maintui"] path = 3rdparty/maintui url = https://github.com/numas13/xash3d-maintui.git ================================================ FILE: 3rdparty/bzip2/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 def options(opt): pass def configure(conf): conf.define('_GNU_SOURCE', 1) conf.define('BZ_NO_STDIO', 1) if conf.env.DEST_OS == 'win32': conf.define('BZ_LCCWIN32', 1) else: conf.define('BZ_UNIX', 1) def build(bld): bld(features = 'subst', source = 'bzip2/bz_version.h.in', target = 'bzip2/bz_version.h', BZ_VERSION='1.1.0-fwgs', name = 'bz_version') bz_sources = ['bzip2/blocksort.c', 'bzip2/huffman.c', 'bzip2/crctable.c', 'bzip2/randtable.c', 'bzip2/compress.c', 'bzip2/decompress.c', 'bzip2/bzlib.c'] bld.stlib( source = bz_sources, target = 'bzip2', use = 'bz_version', includes = 'bzip2/', export_includes = 'bzip2/' ) ================================================ FILE: 3rdparty/extras/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 import os def options(opt): pass def configure(conf): if not conf.path.find_dir('xash-extras'): conf.fatal('Can\'t find xash-extras submodule.') return conf.load('zip') def build(bld): srcdir = bld.path.find_dir('xash-extras') if bld.env.DEST_OS in ['android']: install_path = bld.env.PREFIX else: install_path = os.path.join(bld.env.SHAREDIR, bld.env.GAMEDIR) bld(features='zip', name = 'extras.pk3', files = srcdir.ant_glob('**/*'), relative_to = srcdir, install_path = install_path) ================================================ FILE: 3rdparty/gl4es/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 import os def options(opt): pass def configure(conf): if not conf.path.find_dir('gl4es') or not conf.path.find_dir('gl4es/src'): conf.fatal('Can\'t find gl4es submodule. Run `git submodule update --init --recursive`.') return def build(bld): gl4es_srcdir = bld.path.find_node('gl4es/src') cflags = [] if bld.env.COMPILER_CC != 'msvc': cflags += ['-w', '-fvisibility=hidden', '-std=gnu99'] bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']), target = 'gl4es', includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'], defines = ['NOX11', 'NO_GBM', 'NO_INIT_CONSTRUCTOR', 'DEFAULT_ES=2', 'NOEGL', 'NO_LOADER', 'STATICLIB'], cflags = cflags, export_includes = '.') ================================================ FILE: 3rdparty/libbacktrace/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 from waflib import TaskGen from waflib.Tools.c_config import DEFKEYS FRAGMENT_ATOMIC='''int i; int main(void) { __atomic_load_n(&i, __ATOMIC_ACQUIRE); __atomic_store_n(&i, 1, __ATOMIC_RELEASE); return 0; }''' FRAGMENT_SYNC='''int i; int main (void) { __sync_bool_compare_and_swap (&i, i, i); __sync_lock_test_and_set (&i, 1); __sync_lock_release (&i); return 0; }''' FRAGMENT_GETPAGESIZE='''#include int main(void) { return getpagesize() }''' FRAGMENT_GETEXECNAME='''#include int main(void) { return getexecname() != 0 }''' FRAGMENT_STRNLEN='''#include int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }''' FRAGMENT_DL_ITERATE_PHDR='''#include <%s> int main(void) { return dl_iterate_phdr(0, 0); }''' FRAGMENT_FCNTL='''#include int main(void) { return fcntl(0, 0, 0); }''' FRAGMENT_GETIPINFO='''#include "unwind.h" struct _Unwind_Context *context; int ip_before_insn = 0; int main(void) { return _Unwind_GetIPInfo(context, &ip_before_insn); }''' FRAGMENT_LOADQUERY='''#include #include int main(void) { return loadquery(0, 0, 0); }''' FRAGMENT_KERN_PROC='''#include #if !defined(%s) || !defined(KERN_PROC_PATHNAME) #error #endif int main(void) { return 0; }''' FRAGMENT_LSTAT='''#include struct stat st; int main(int argc, char **argv) { return lstat(argv[0], &st); }''' FRAGMENT_READLINK='''#include char buf[100]; int main(int argc, char **argv) { return readlink(argv[0], buf, sizeof(buf)); }''' def options(opt): pass def configure(conf): # add unsupported platforms here if conf.env.DEST_OS in ['nswitch', 'psvita', 'dos']: conf.env.DISABLE_LIBBACKTRACE = True return # win32 has it's own dbghelp-based backtrace, that's why we ship PDBs if conf.env.COMPILER_CC == 'msvc': conf.env.DISABLE_LIBBACKTRACE = True return if not conf.path.find_dir('libbacktrace') or not conf.path.find_dir('libbacktrace/config'): conf.fatal('Can\'t find libbacktrace submodule. Run `git submodule update --init --recursive`.') return conf.define('BACKTRACE_ELF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32) conf.define('BACKTRACE_XCOFF_SIZE', 64 if conf.env.DEST_SIZEOF_VOID_P == 8 else 32) conf.define('_ALL_SOURCE', 1) conf.define('_GNU_SOURCE', 1) conf.define('_POSIX_PTHREAD_SEMANTICS', 1) conf.define('_TANDEM_SOURCE', 1) conf.define('__EXTENSIONS__', 1) conf.define('_DARWIN_USE_64_BIT_INODE', 1) conf.define('_LARGE_FILES', 1) conf.check_large_file(compiler='c', execute=False, mandatory=False) # sets _FILE_OFFSET_BITS conf.env.CFLAGS_EXTRAFLAGS = conf.filter_cflags(['-funwind-tables', '-g'], []) if conf.filter_cflags(['-frandom-seed=test'], []): conf.env.HAVE_FRANDOM_SEED = True def check_header(hdr): return {'header_name':hdr, 'msg':'... %s header' % hdr, 'mandatory':False, 'id':hdr, 'compiler': 'c'} def check_frag(frag, msg, define, **kw): return dict({'fragment': frag, 'msg':'... %s' % msg, 'mandatory': False, 'compiler': 'c'}, **kw) conf.multicheck( check_header('dlfcn.h'), check_header('inttypes.h'), check_header('link.h'), check_header('sys/link.h'), check_header('mach-o/dyld.h'), check_header('memory.h'), check_header('stdint.h'), check_header('stdlib.h'), check_header('strings.h'), check_header('string.h'), check_header('sys/ldr.h'), check_header('sys/mman.h'), check_header('sys/stat.h'), check_header('sys/types.h'), check_header('tlhelp32.h'), check_header('unistd.h'), check_header('windows.h'), check_frag(FRAGMENT_ATOMIC, '__atomic extensions', 'HAVE_ATOMIC_FUNCTIONS'), check_frag(FRAGMENT_SYNC, ' __sync extensions', 'HAVE_SYNC_FUNCTIONS'), check_frag(FRAGMENT_GETPAGESIZE, ' getpagesize function', 'HAVE_DECL_GETPAGESIZE'), check_frag(FRAGMENT_STRNLEN, ' strnlen function', 'HAVE_DECL_STRNLEN'), check_frag(FRAGMENT_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['link.h']), check_frag(FRAGMENT_DL_ITERATE_PHDR % 'sys/link.h', ' dl_iterate_phdr function in sys/link.h', 'HAVE_DL_ITERATE_PHDR', after_tests=['sys/link.h']), check_frag(FRAGMENT_FCNTL, 'fnctl function', 'HAVE_FCNTL'), check_frag(FRAGMENT_GETEXECNAME, 'getexecname function','HAVE_GETEXECNAME'), check_frag(FRAGMENT_GETIPINFO, '_Unwind_GetIPInfo function', 'HAVE_GETIPINFO'), check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC', 'KERN_PROC and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC'), check_frag(FRAGMENT_KERN_PROC % 'KERN_PROC_ARGS', 'KERN_PROC_ARGS and KERN_PROC_PATHNAME defines', 'HAVE_KERN_PROC_ARGS'), check_frag(FRAGMENT_LOADQUERY, 'loadquery function', 'HAVE_LOADQUERY'), check_frag(FRAGMENT_LSTAT, 'lstat function', 'HAVE_LSTAT'), check_frag(FRAGMENT_READLINK, 'readlink function', 'HAVE_READLINK'), # {'lib':'lzma', 'define_name':'HAVE_LIBLZMA', 'uselib_store':'lzma', 'msg':'... lzma library', 'mandatory':False}, # {'lib':'z', 'define_name':'HAVE_ZLIB', 'uselib_store':'z', 'msg':'... zlib library', 'mandatory':False}, # {'lib':'zstd', 'define_name':'HAVE_ZSTD', 'uselib_store':'zstd', 'msg':'... zstd library', 'mandatory':False}, msg='Checking for in parallel' ) conf.env[DEFKEYS].sort() conf.write_config_header() conf.define('BACKTRACE_SUPPORTED', 1) conf.define('BACKTRACE_USES_MALLOC', 0) conf.define('BACKTRACE_SUPPORTS_THREADS', 1) conf.define('BACKTRACE_SUPPORTS_DATA', conf.env.DEST_BINFMT in ['elf', 'mac-o']) conf.write_config_header('backtrace-supported.h') @TaskGen.feature('frandomseed') @TaskGen.after_method('propagate_uselib_vars') def process_frandom_seed(ctx): tasks = getattr(ctx, 'compiled_tasks', []) for task in tasks: out = task.outputs[0] task.env.CFLAGS = list(task.env.CFLAGS) # need a copy task.env.CFLAGS += ['-frandom-seed=%s' % out.path_from(out.ctx.bldnode)] def build(bld): if bld.env.DISABLE_LIBBACKTRACE: return # we specifically only want mmap-based allocators because calling malloc is not safe from signal handlers sources = ['atomic.c', 'dwarf.c', 'fileline.c', 'posix.c', 'print.c', 'sort.c', 'state.c', 'backtrace.c', 'simple.c', 'mmap.c', 'mmapio.c'] if bld.env.DEST_BINFMT == 'pe': sources += ['pecoff.c'] elif bld.env.DEST_BINFMT == 'mac-o': sources += ['macho.c'] elif bld.env.DEST_BINFMT == 'elf': sources += ['elf.c'] else: sources += ['unknown.c'] task = bld.stlib( source = ['libbacktrace/' + i for i in sources], target = 'backtrace', features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '', use = 'EXTRAFLAGS lzma z zstd', includes = '. libbacktrace/', export_defines = 'HAVE_LIBBACKTRACE=1', export_includes = 'libbacktrace/' ) ================================================ FILE: 3rdparty/libogg/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 def options(opt): pass def configure(conf): if not conf.path.find_dir('libogg') or not conf.path.find_dir('libogg/src'): conf.fatal('Can\'t find libogg submodule. Run `git submodule update --init --recursive`.') return conf.env.INCLUDE_INTTYPES_H = 0 conf.env.INCLUDE_SYS_TYPES_H = 0 conf.env.INCLUDE_STDINT_H = 0 if conf.check_cc(header_name='inttypes.h', mandatory = False): conf.env.INCLUDE_INTTYPES_H = 1 elif conf.check_cc(header_name='sys/types.h', mandatory = False): conf.env.INCLUDE_SYS_TYPES_H = 1 elif conf.check_cc(header_name='stdint.h', mandatory = False): conf.env.INCLUDE_STDINT_H = 1 def build(bld): sources = bld.path.ant_glob([ 'libogg/src/*.c' ]) bld( features = 'subst', name = 'libogg_config_types', source = 'libogg/include/ogg/config_types.h.in', target = 'libogg/include/ogg/config_types.h', INCLUDE_INTTYPES_H = bld.env.INCLUDE_INTTYPES_H, INCLUDE_SYS_TYPES_H = bld.env.INCLUDE_SYS_TYPES_H, INCLUDE_STDINT_H = bld.env.INCLUDE_STDINT_H, SIZE16 = 'int16_t', USIZE16 = 'uint16_t', SIZE32 = 'int32_t', USIZE32 = 'uint32_t', SIZE64 = 'int64_t', USIZE64 = 'uint64_t' ) bld.stlib( source = sources, target = 'ogg', use = 'libogg_config_types', includes = 'libogg/include/', export_includes = 'libogg/include/' ) ================================================ FILE: 3rdparty/opus/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 import os FRAGMENT_VLA='''int main (int argc, char **argv) { char a[argc]; a[sizeof( a ) - 1] = 0; int N; return a[0]; }''' FRAGMENT_ALLOCA_H='''#include int main (void) { int foo=10; int * array = alloca(foo); }''' FRAGMENT_STDLIB_H='''#include #include int main (void) { int foo=10; int * array = alloca(foo); }''' FRAGMENT_LRINT='''#include #include int main (int argc, char **argv) { return lrint%s((%s)atof(argv[1])); }''' # assuming MSVC always enables NEON FRAGMENT_NEON='''#if !defined __ARM_NEON__ && !defined _MSC_VER #error #endif''' def options(opt): pass def configure(conf): if not conf.path.find_dir('opus') or not conf.path.find_dir('opus/src'): conf.fatal('Can\'t find opus submodule. Run `git submodule update --init --recursive`.') return if conf.check_cc(fragment=FRAGMENT_LRINT % ('', 'double'), msg = 'Checking for C99 lrint', use = 'M', mandatory = False): conf.define('HAVE_LRINT', 1) if conf.check_cc(fragment=FRAGMENT_LRINT % ('f', 'float'), msg = 'Checking for C99 lrintf', use = 'M', mandatory = False): conf.define('HAVE_LRINTF', 1) # Check for C99 variable-size arrays, or alloca() as fallback if conf.check_cc(fragment=FRAGMENT_VLA, msg = 'Checking for C99 VLA support', mandatory = False): conf.define('VAR_ARRAYS', 1) elif conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False): conf.define('USE_ALLOCA', 1) conf.define('HAVE_ALLOCA_H', 1) elif conf.check_cc(fragment=FRAGMENT_STDLIB_H, msg = 'Checking for alloca.h in stdlib.h', mandatory = False): conf.define('USE_ALLOCA', 1) if conf.env.DEST_CPU in ['thumb', 'arm']: if conf.check(header_name='arm_neon.h', mandatory = False): if conf.check(fragment=FRAGMENT_NEON, msg = 'Checking if compiler enabled NEON', mandatory = False): conf.env.HAVE_NEON = True conf.define('OPUS_ARM_MAY_HAVE_NEON_INTR', 1) conf.define('OPUS_ARM_PRESUME_NEON', 1) elif conf.env.DEST_CPU.startswith('x86'): if conf.check(header_name='xmmintrin.h', mandatory = False): if conf.env.COMPILER_CC != 'msvc': conf.env.CFLAGS += ['-msse'] conf.define('OPUS_X86_MAY_HAVE_SSE', 1) if conf.env.DEST_SIZEOF_VOID_P > 4: conf.define('OPUS_X86_PRESUME_SSE', 1) if conf.check(header_name='emmintrin.h', mandatory = False): if conf.env.COMPILER_CC != 'msvc': conf.env.CFLAGS += ['-msse2'] conf.define('OPUS_X86_MAY_HAVE_SSE2', 1) if conf.env.DEST_SIZEOF_VOID_P > 4: conf.define('OPUS_X86_PRESUME_SSE2', 1) # on x86_64 we enable both SSE and SSE2, so RTCD can be disabled (it's actually doesn't even build with RTCD enabled) if conf.env.DEST_SIZEOF_VOID_P == 4: if conf.check(header_name='cpuid.h', mandatory=False): conf.define('CPU_INFO_BY_C', 1) else: conf.define('CPU_INFO_BY_ASM', 1) conf.define('OPUS_HAVE_RTCD', 1) # if conf.check(header_name='smmintrin.h', mandatory = False): # if conf.env.COMPILER_CC != 'msvc': # conf.env.CFLAGS += ['-msse4.1'] # conf.define('OPUS_X86_MAY_HAVE_SSE4_1', 1) # if conf.check(header_name='immintrin.h', mandatory = False): # if conf.env.COMPILER_CC != 'msvc': # conf.env.CFLAGS += ['-mavx'] # conf.define('OPUS_X86_MAY_HAVE_AVX', 1) # TODO: ARM/x86 intrinsics detection # TODO: maybe call autotools/cmake/meson instead? def build(bld): sources = bld.path.ant_glob([ 'opus/src/*.c', 'opus/celt/*.c', 'opus/silk/*.c', 'opus/silk/float/*.c' ], excl = [ 'opus/src/repacketizer_demo.c', 'opus/src/opus_demo.c', 'opus/src/opus_compare.c', 'opus/celt/opus_custom_demo.c' ]) includes = ['opus', 'opus/include/', 'opus/celt/', 'opus/silk/', 'opus/silk/float/'] if bld.env.DEST_CPU in ['thumb', 'arm']: if bld.env.HAVE_NEON: sources += bld.path.ant_glob(['opus/silk/arm/*.c', 'opus/celt/arm/*.c']) includes += ['opus/silk/arm', 'opus/celt/arm'] elif bld.env.DEST_CPU.startswith('x86'): sources += ['opus/silk/x86/x86_silk_map.c', 'opus/celt/x86/pitch_sse.c', 'opus/celt/x86/pitch_sse2.c', 'opus/celt/x86/vq_sse2.c', 'opus/celt/x86/x86_celt_map.c', 'opus/celt/x86/x86cpu.c'] includes += ['opus/silk/x86', 'opus/celt/x86'] defines = ['OPUS_BUILD', 'FLOAT_APPROX', 'PACKAGE_VERSION="1.4.0"', 'CUSTOM_MODES', 'ENABLE_HARDENING'] bld.stlib( source = sources, target = 'opus', features = 'c', includes = includes, defines = defines, export_includes = ['opus/include/'] ) ================================================ FILE: 3rdparty/opusfile/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 def options(opt): pass def configure(conf): if not conf.path.find_dir('opusfile') or not conf.path.find_dir('opusfile/src'): conf.fatal('Can\'t find opusfile submodule. Run `git submodule update --init --recursive`.') return if conf.env.COMPILER_CC == 'msvc': conf.define('_CRT_SECURE_NO_WARNINGS', 1) conf.define('_CRT_SECURE_NO_DEPRECATE', 1) conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1) if conf.env.DEST_OS == 'android': # HACKHACK: set it to 32 here because opusfile can't be built on Android SDK < 24 # with _FILE_OFFSET_BITS 64 (which it sets automatically in src/internal.h) # we are not (????) relying on this part of the API, so it should be harmless conf.define('_FILE_OFFSET_BITS', 32) def build(bld): sources = [ 'opusfile/src/info.c', 'opusfile/src/internal.c', 'opusfile/src/opusfile.c', 'opusfile/src/stream.c' ] bld.stlib( source = sources, target = 'opusfile', includes = 'opusfile/include/', use = 'ogg opus', export_includes = 'opusfile/include/' ) ================================================ FILE: 3rdparty/vorbis/wscript ================================================ #! /usr/bin/env python # encoding: utf-8 FRAGMENT_MEMORY_H='''#include int main (void) { return 0; }''' FRAGMENT_ALLOCA_H='''#include int main (void) { int foo=10; int * array = alloca(foo); }''' def options(opt): pass def configure(conf): if not conf.path.find_dir('vorbis-src') or not conf.path.find_dir('vorbis-src/lib'): conf.fatal('Can\'t find Vorbis submodule. Run `git submodule update --init --recursive`.') return if conf.check_cc(fragment=FRAGMENT_MEMORY_H, msg = 'Checking for memory.h header', mandatory = False): conf.define('USE_MEMORY_H', 1) conf.check_cc(fragment=FRAGMENT_ALLOCA_H, msg = 'Checking for alloca in alloca.h header', mandatory = False) if conf.env.COMPILER_CC == 'msvc': conf.define('_CRT_SECURE_NO_WARNINGS', 1) conf.define('_CRT_SECURE_NO_DEPRECATE', 1) conf.define('_CRT_NONSTDC_NO_DEPRECATE', 1) def build(bld): libvorbis_sources = [ 'vorbis-src/lib/mdct.c', 'vorbis-src/lib/smallft.c', 'vorbis-src/lib/block.c', 'vorbis-src/lib/envelope.c', 'vorbis-src/lib/window.c', 'vorbis-src/lib/lsp.c', 'vorbis-src/lib/lpc.c', 'vorbis-src/lib/analysis.c', 'vorbis-src/lib/synthesis.c', 'vorbis-src/lib/psy.c', 'vorbis-src/lib/info.c', 'vorbis-src/lib/floor1.c', 'vorbis-src/lib/floor0.c', 'vorbis-src/lib/res0.c', 'vorbis-src/lib/mapping0.c', 'vorbis-src/lib/registry.c', 'vorbis-src/lib/codebook.c', 'vorbis-src/lib/sharedbook.c', 'vorbis-src/lib/lookup.c', 'vorbis-src/lib/bitrate.c' ] bld.stlib( source = libvorbis_sources, target = 'vorbis', includes = 'vorbis-src/include/', use = 'ogg', export_includes = 'vorbis-src/include/' ) bld.stlib( source = 'vorbis-src/lib/vorbisfile.c', target = 'vorbisfile', includes = 'vorbis-src/include/', use = 'vorbis', export_includes = 'vorbis-src/include/' ) ================================================ FILE: CONTRIBUTING.md ================================================ # Specific instructions for this fork ## Introduction 1. This fork's only concern is the `ref_vk` Vulkan/RT renderer. Engine and other renderers issues and functionality are absolutely out of scope, unless directly and specifically related to `ref_vk`. 2. Primary focus is Ray Tracing with PBR materials. "Traditional" (triangle rasterization) mode is low proirity and has quite a few known issues and deficiencies. 3. The primary development branch is `vulkan`, it should contain the latest working and stable code. Other branches (including `master`) are not supported. 4. Check out the upstream xash3d-fwgs CONTRIBUTING.md too. ## Reporting issues 1. Precondition: you're supposed to know how to build and run stuff manually. It is not ready to be used by non-developers. 2. This is a very actively developing project. There are lots of known issues. Search them first. 3. Run with `-dev 2 -log -vkdebug -vkvalidate -vkverboselogs` and provide `engine.log`. 4. Specify detailed steps to reproduce. A savefile might be helpful too. 5. Although it is deducible from the log, provide the following information explicitly: map, location, OS, GPU, driver version. 6. Attach a screenshot if possible (i.e. if it is not a crash at init time) ## Contributing code We are very glad to hear that you want to help. And there are certainly quite a few issues that could be worked on in parallel. The renderer code is being mostly written as a for-fun-only hobby project by a single person who has neither mental capacity nor time to make and maintain a comprehensive documentation or development structure. Making it a collaboration effort with multiple active participants would require a completely different approach to development, communication, and progress tracking. We might or might not be able to get there eventually. That said, we are still happy to hear that you'd like to help. Your involvement might be instrumental to reorganize and allow more collaborators. Strongly suggested checklist for contributing anything, **before you start writing any code that you'd like to land here**: 1. Find an existing issue (e.g. with `good first issue` label), or suggest your own. 2. Let us know that you'd want to work on it, e.g. by leaving a comment on it. **Why:** any given issue might be stale, no longer relevant, being actively worked on as part of something else, or conflicting with some other approach being deliberated. 3. Work with us on a design review for the issue. **Why:** this is live C codebase, it is rather fragile and is constantly changing. There are no good practices or stable building blocks. It is also a bit idiosyncratic in places. We just know more context about where are we going to, and where we might be heading. You might also get into surprising conflicts with things that we're working on. There are unfortunately no stable scaffolding or guardrails that would allow for easy independent collaboration yet. Working on a design review means that we'll suggest a compatible way of doing things, and will schedule our work to minimize conflicts with yours. 4. Open a draft PR as early as possible, even if it is not ready yet. That way we can coordinate effort, suggest things and anwer any questions. ## Code and PR 1. Do not worry that much about code style. Be reasonable, try to either imitate the surrounding code (which has no strict style yet), or follow upstream recommendations listed below under `Code style` section. 2. Try to limit your changes, e.g. don't re-format lines which are not crucial to your change. 3. Ping us in the PR if you're not hearing any feedback for a couple of days. I'm usually way too busy *with life* to be on the internets all the time, but a little nudge might be able to allocate some attention. --------------------------------------------- # UPSTREAM XASH3D-FWGS CONTRIBUTING.md FOLLOWS --------------------------------------------- ## If you are reporting bugs 1. Check you are using latest version. You can build latest Xash3D FWGS for yourself, look to README.md. 2. Check open issues is your bug is already reported and closed issues if it reported and fixed. Don't send bug if it's already reported. 3. Re-run engine with `-dev 2 -log` arguments, reproduce bug and post engine.log which can be found in your working directory. 3. Describe steps to reproduce bug. 4. Describe which OS and architecture you are using. 6. Attach screenshot if it will help clarify the situation. ## If you are contributing code ### Which branch? * We recommend using `master` branch. ### Third-party libraries * Philosophy of any Xash Project by Uncle Mike: don't be bloated. We follow it too. * Adding new library is allowed only if there is a REAL reason to use it. It's will be nice, if you will leave a possibility to remove new dependency at build-time. * Adding new dependencies for Waf Build System is not welcomed. ### Portability level * Xash3D have it's own crt library. It's recommended to use it. It most cases it's just a wrappers around standart C library. * If your feature need platform-specific code, move it to `engine/platform` and try to implement to every supported OS and every supported compiler or at least leave a stubs. * You must put it under appopriate macro. It's a rule: Xash3D FWGS must compile everywhere. For list of platforms we support, refer to public/build.h file. ### Code style * This project uses mixed Quake's and HLSDK's C/C++ code style convention. * In short: * Use spaces in parenthesis. * Only tabs for indentation. * Any brace must have it's own line. * Short blocks, if statements and loops on single line are allowed. * Avoid magic numbers. * While macros are powerful, it's better to avoid overusing them. * If you unsure, try to mimic code style from anywhere else of engine source code. * **ANY** commit message should start from declaring a tags, in format: `tag: added some bugs` `tag: subtag: fixed some features` Tags can be any: subsystem, simple feature name or even just a filename, without extension. Just keep them always same, it helps keep history clean and commit messages short. ## LLM-based tools usage. While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay. Whether you're reporting bug or contributing the code, you take complete authorship and responsibility over provided content and the same rules will apply to you as for everybody else, so validate the bug report or the patch before sending it. ================================================ FILE: Documentation/bug-compatibility.md ================================================ # Bug-compatibility in Xash3D FWGS Xash3D FWGS has special mode for games that rely on original engine bugs. In this mode, we emulate the behaviour of selected functions that may help running mods relying on engine bugs, but enabling them by default may break majority of other games. At this time, we only have implemented GoldSrc bug-compatibility. It can be enabled with `-bugcomp` command line switch. When `-bugcomp` is specified without argument, it enables everything. This behavior might be changed or removed in future versions. When `-bugcomp` is specified with argument, it interpreted as flags separated with `+`. This way it's possible to combine multiple levels of bug-compatibility. ## GoldSrc bug-compatibility | Flag | Description | Games that require this flag | | ------- | ----------- | ---------------------------- | | `peoei` | Reverts `pfnPEntityOfEntIndex` behavior to GoldSrc, where it returns NULL for last player due to incorrect player index comparison | * Counter-Strike: Condition Zero - Deleted Scenes | | `gsmrf` | Rewrites message at the moment when Game DLL attempts to write an internal engine message, usually specific to GoldSrc protocol.
Right now only supports `svc_spawnstaticsound`, more messages added by request. | * MetaMod/AMXModX based mods | | `sp_attn_none` | Makes sounds with attenuation zero spatialized, i.e. have a stereo effect. | Possibly, every game that was made for GoldSrc. | | `get_game_dir_full` | Makes server return full path in server's `pfnGetGameDir` API function | Mods targetting engine before HL 1.1.1.1, according to MetaMod [documentation](http://metamod.org/engine_notes.html#GetGameDir) | ================================================ FILE: Documentation/cross-compiling-for-windows-with-wine.md ================================================ # Cross-compiling for Windows with Wine This can be useful to test engine in Wine without using virtual machines or dual-booting to Windows. 0. Clone and install https://github.com/mstorsjo/msvc-wine (you can skip CMake part) 1. Set environment variable MSVC_WINE_PATH to the path to installed MSVC toolchain 2. Pre-load wine: `wineserver -k; wineserver -p; wine64 wineboot` 3. Run `PKGCONFIG=/bin/false ./waf configure -T --enable-wine-msvc --sdl2=../SDL2_VC`. Configuration step will take more time than usual. 4. .. other typical steps to build from console ... > [!NOTE] > Notice the usage of PKGCONFIG=/bin/false here. We're disabling pkg-config so we don't accidentally pull > system-wide dependencies and force building them from source. In future builds we might set custom > directory to pull dependencies from, like ffmpeg... ================================================ FILE: Documentation/debugging-using-minidumps.md ================================================ # Debugging your mod using minidump files (Windows only) Minidump files is awesome instrument for debugging your mod after it's being released, or for catch specific crashes which are presented only in one specific configuration, but doesn't happens in other configurations or even on developer machine. It contains a lot of information useful for debugging, and therefore it size is not so small: around hundreds or even thousands of megabytes. But this is not a problem, since minidump files compresses very effectively using common algorithms. There are short algorithm, explaining how to use this debugging instrument: 1. User starts your mod with `-minidumps` startup parameter 2. Finally, crash happened on user's machine, and minidump file being written. This file has `.mdmp` extension and located in same folder, where mod folder located 3. User packs this minidump file to .zip/.7z archive, and somehow sends it to developer 4. Developer just opens minidump file in Visual Studio, then a virtual debugging session is opened and now reasons of crash is pretty easy to detect: you can see call stack, local function variables and global variables, information about exception and a lot of other information You can find more information about minidumps in this [awesome article](https://learn.microsoft.com/en-us/windows/win32/dxtecharts/crash-dump-analysis?source=recommendations#writing-a-minidump). ================================================ FILE: Documentation/donate.md ================================================ # Developers donation page On this page you can find links where you can support each developer individually, who has provided public sponsorship information. ## [a1batross](https://github.com/a1batross) Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of non-commercial Flying With Gauss organization. * [Boosty page](https://boosty.to/a1ba) ## [nekonomicon](https://github.com/nekonomicon) [hlsdk-portable](https://github.com/FWGS/hlsdk-portable), [mdldec](../utils/mdldec), [opensource-mods.md](opensource-mods.md) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=nekonomicon) (*BSD/clang port, PNG support, etc). * [Boosty page](https://boosty.to/nekonomicon) ## [Velaron](https://github.com/Velaron) [cs16-client](https://github.com/Velaron/cs16-client) & [tf15-client](https://github.com/Velaron/tf15-client) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Velaron) (Android port, voice chat, etc). * [Boosty page](https://boosty.to/velaron) ## [SNMetamorph](https://github.com/SNMetamorph) [PrimeXT](https://github.com/SNMetamorph/PrimeXT) & [GoldSrc Monitor](https://github.com/SNMetamorph/goldsrc-monitor) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=SNMetamorph) (Windows port, voice chat, etc). * [Boosty page](https://boosty.to/snmetamorph) * [Other donation methods](https://snmetamorph.github.io/donate) ## [$_Vladislav](https://github.com/Vladislav4KZ) [YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester, editor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087). Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods. * [Boosty page](https://boosty.to/rasstaman1337) ================================================ FILE: Documentation/engine-porting-guide.md ================================================ ## Abstract Before start, I would recommend you to compile and run engine for already supported and well-spread platform, such as GNU/Linux or Windows. Hack it, get familiar with engine. One beauties of Xash3D FWGS Engine is it modularity, so game logic, UI and renderers are located in platform-independent libraries. Every library is loaded at run time and may or may be not optional. For example, if your platform doesn't have OpenGL of any kind, you can skip it and use our software renderer! Of course, there is no real reason to run game engine, if you don't have game logic of any kind, so the minimal build of Xash3D FWGS is headless dedicated server. Historically, Xash3D Engine was even more modular(see github.com/a1batross/Xash3D_ancient), but we thought that to fulfill our crossplatform needs we will keep all platform-specific stuff in the engine itself and libraries must import or expose platform-independent APIs. Ok, get to the point! ## Porting guidelines It will not be a complete tutorial as covering everything in one article is probably just impossible. Instead, I will give you hints on how engine can be ported, how to get your port to upstreamed and how you should maintain your port. 0) Get to know your platform. Maybe I asked for this lately, but you MUST KNOW YOUR PLATFORM, i.e. what's this capable of. The one of unsupported configurations at this time is when platform can't load dynamic libraries(`*.so` or DLLs). We can't help you as supporting full-static ports are violating the GPL license in various ways. The other yet unsupported configuration is the big endian. 1) Setup toolchain. For dirty port, you can write Makefile for yourself, engine is written that way, so **it doesn't relies** on any generated data or any special processing. But I recommend you to use Waf anyway, it's better integrated to engine and self-documentable. Also, I will cover only our Waf build options. 2) Open `public/build.h` file. Add appropriate checks for your operating system and/or CPU architecture. It shouldn't be hard. Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org). We have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information. In short, you need to call your platform in unique way. For engine side it's done in `engine/common/build.c` file in engine source code, for HLSDK side it's done in `cmake/LibraryNaming.cmake` and `scripts/waifulib/library-naming.py` files in HLSDK source code. You can use [predef project wiki](https://sourceforge.net/p/predef/wiki/Home/) for reference. 2) Look at the `engine/platform` directory. We usually try to have all platform-specific stuff inside this folder. * Functions that must be available on your platform are declared in `platform.h` header. Most of them aren't used in headless dedicated build, but I will cover that later. * The POSIX-compliant(for *nix operating systems) goes into `posix` subdirectory. * The SDL-specific code goes into `sdl` subdirectory. Note that even I said before that platform-specific code is in `platform` directory, SDL obviously isn't a __platform__, so you can met checks for XASH_SDL inside whole engine, usually in input. * Custom SWAP implementation for *nix-based systems is in `swap` subdirectory. It's used when hardware may not have enough memory and you can't add swap memory. I will cover that later It relies on a fact that systems with paged memory will load pages into RAM and move unused to mmap()-ed by custom swap area. That allowed to run game engine on music player with MIPS CPU, Linux without SWAP support and * Other folders as `win32`, `android`, `linux` and so on are self-descriptive. 3) As proof of concept, you can try to test that network features and dynamic library loading are implemented correctly. Build a dedicated server with XASH_DEDICATED or `--dedicated` passed to `waf configure`. Start a server and try to connect to it from PC. If everything is fine, then you can move to next step. If you get out of memory issues, you can try Low Memory Mode, it's enabled with `--low-memory-mode=N` passed to `waf configure` where N is 1 or 2. Low Memory Mode 1 means that we will NOT break a network compatibility. 2 is deeper and doesn't guarantee protocol compatibility and available multiplayer. Of course, with Low Memory Mode 2 you can't test your port dedicated server. If you got anything compiling, it's nice time to make a commit. Commit your changes into git and push them somewhere(GitHub, GitLab or what you prefer), so you will not lose them accidentally. 4) Here is most interesting, building the client part. There are three possible situations for you, by increasing difficulty. 1. Do you have SDL2 for your platform? If yes, go ahead and compile engine with client part enabled. 2. Do you have SDL1.2 for your platform? If yes, try to compile it, but SDL1.2 support is very limited and not tested well. 3. You don't have SDL and you can't port it for some reason by yourself or SDL port just don't stable for your platform? Then you need to implement an engine backend. How to implement engine backend? Well, we have a backends system that was introduced in Old Engine. It's simply two files: backends.h and defaults.h in `common` folder in repository root. You will need to add a macros for your backends in `backends.h` and define a logic in `defaults.h`. We have already have some, check them out, as they may be handy for you. Once you did it, go back to `platform` directory and open `platform.h` headers. Create new subdirectory in `platform` folder and start implementing missing platform-specific backends one by one. I can't explain this as for obvious reasons it's too specific. You can use macros you defined before in `build.h` to check that you're compiling for your platform and `backends.h` macros to check if platform-specific backend must be enabled. As you finished, compile engine, fix errors and commit your changes and push to repository. Remember, if something doesn't work and you can't figure out why, you can join our Discord server at discord.me/fwgs. Ping me (@a1batross) or other engine developer and attach link to your repository, so we can discuss and help you in porting. ================================================ FILE: Documentation/environment-variables.md ================================================ ## Environment variables #### Xash3D FWGS The engine respects these environment variables: | Variable | Type | Description | | --------------------- | ---------- | ----------- | | `XASH3D_GAME` | _string_ | Overrides default game directory. Ignored if `-game` command line argument is set | | `XASH3D_BASEDIR` | _string_ | Sets path to base (root) directory, instead of current working directory | | `XASH3D_RODIR` | _string_ | Sets path to read-only base (root) directory. Ignored if `-rodir` command line argument is set | | `XASH3D_EXTRAS_PAK1` | _string_ | Archive file from specified path will be added to virtual filesystem search path in the lowest possible priority | | `XASH3D_EXTRAS_PAK2` | _string_ | Similar to `XASH3D_EXTRAS_PAK1` but next to it in priority list | Environment variables NOT listed in the table above are used internally, and aren't considered as stable interface. #### mdldec | Variable | Type | Description | | --------------------- | ---------- | ----------- | | `MDLDEC_ACT_PATH` | _string_ | If set, will read activities list from this path | ================================================ FILE: Documentation/extensions/addon-folders.md ================================================ # Addon folders in Xash3D FWGS Xash3D FWGS supports both GoldSource-style addon folders and has few own. Each directory can have it's own archives that will be mounted with lower priority than directory itself. Below is the mounts map, in order of precedence from least important to most important. |--------------------|------| | Directory | Note | |--------------------|------| | `$game/downloaded` | Always added. Used to store server downloads.| | `$game` | This is the game directory. | | `$game/custom` | Always added. Used for user modifications content. | | `$game_hd` | Added with `fs_mount_hd` set to non-zero value. Used for high definition content, similar to GoldSrc.. | | `$game_addon` | Added with `fs_mount_addon` set to non-zero value. Used for user modifications content, similar to GoldSrc. | | `$game_lv` | Added with `fs_mount_lv` set to non-zero value. Used for low-violence content, similar to GoldSrc. | | `$game_$language` | Added with `fs_mount_l10n` set to non-zero value. Language is controlled with `ui_language` cvar or `-language` command line switch. Used for localization content, similar to GoldSrc. | ================================================ FILE: Documentation/extensions/console-scripting.md ================================================ ## Console variables Console variables (or CVars) are present in all quake-based games. By default, it is settings, created by engine, server or client libraries. But you can use `set` command to define variables even if they are not created by the engine. For example, you can set cvar before it is registered in code. `set defaultmap crossfire` This works even in server.cfg before server cvars initialization and the engine will reuse its value on cvar creation ## Aliases An alias allows to define new commands. `alias wnext "invnext;wait;wait;+attack;wait;-attack"` You can hook any command by adding an alias to it and unaliasing it, when you want to use original command. ``` alias invnext1 "unalias invnext;wnext;alias invnext invnext1" alias invnext invnext1 ``` ## Scripting extensions This is an extensions of Xash3D FWGS(merged to original Xash3D since build 3887), that can be enabled by cmd_scripting cvar. Enabling scripting: `cmd_scripting 1` This is an archive cvar and it will be saved. ### CVar substitution You can substitute cvar value to any command by adding \$ symbol: `echo $sv_cheats` ### Condition checking Allows checking cvar values. ``` if : :if :: : else : ``` * Values are any string or numeric values (for example, substituted cvars). * Operator is = (or ==), \!=, \<, \>, \<=, \>=. == is same to =. * If single value specified, condition is true when value is non-zero Example: ``` if $sv_cheats == 1 :echo Cheats enabled, adding cheat menu :exec cheatmenu.cfg else :echo Please enable cheats to use this! ``` ================================================ FILE: Documentation/extensions/entity-tools.md ================================================ # Entity tools For some features described below, you need to enable [console scripting](https://github.com/FWGS/xash3d-fwgs/blob/master/Documentation/extensions/console-scripting.md) with command `cmd_scripting 1` in console. To get more information about it, check another according page related to console scripting. ## Commands description ### ent_create Create entity with specified classname and key/values. `ent_create ...` For example: `ent_create monster_zombie targetname zomb1` After creating entity, ent_last_xxx cvars are set to new entity and ent_last_cb called, look at ent_getvars description. ### ent_fire Makes some actions on entity. `ent_fire ` #### Available commands: Set fields (only set entity field, does not call any functions) * health * gravity * movetype * solid * rendermode * rendercolor (vector) * renderfx * renderamt * hullmin (vector) * hullmax (vector) Actions * rename: set entity targetname * settarget: set entity target (only targetnames) * setmodel: set entity model (does not update) * set: set key/value by server library * See game FGD to get list. * command takes two arguments * touch: touch entity by current player. * use: use entity by current player. * movehere: place entity in player fov. * drop2floor: place entity to nearest floor surface * moveup: move entity to 25 units up * moveup (value): move by y axis relatively to specified value Flags (set/clear specified flag bit, arg is bit number): * setflag * clearflag * setspawnflag * clearspawnflag ### ent_info Print information about entity by identificator. `ent_info ` ### ent_getvars Set client cvars containing entity information (useful for [scripting](extensions/console-scripting.md)) and then calls ent_last_cb. `ent_getvars ` These cvars are set: ``` ent_last_name ent_last_num ent_last_inst ent_last_origin ent_last_class ``` ### ent_list Print short information about antities, filtered by pattern. `ent_list ` ## Syntax description #### \ * !cross: entity under aim * !\_\: instance code * Set by ent_getvars command * Entity index * Targetname pattern #### \ Pattern is like identificator, but may filter many entities by classname. #### (vector) Used by ent_fire command. vector means three float values, entered without quotes. #### key/value All entities parameters may be set by specifiing key and value strings. Originally, this mechanizm is used in map/bsp format, but it can be used in enttools too. Keys and values are passed to server library and processed by entity keyvalue function, setting edict and entity owns parameters. If value contains spaces, it must be put in quotes: `ent_fire !cross set origin "0 0 0"` ## Using with scripting ent_create and ent_getvars commands are setting cvars on client It can be used with ent_last_cb alias that is executed after setting cvars. Simple example: ``` ent_create weapon_c4 alias ent_last_cb "ent_fire \$ent_last_inst use" ``` Use weapon_c4 after creating it. Note that you cannot use many different callbacks at the same time. You can set entity name by by pattern and create special script, contatning all callbacks. Example: > example.cfg ``` alias ent_last_cb exec entity_cb.cfg ent create \ targetname my_ent1_$name ent_create \ targetname my_ent2_$name ``` > entity_cb.cfg ``` if $ent_last_name == my_ent1_$name :(ent1 actions) if $ent_last_name == my_ent2_$name :(ent2 actions) ``` Note that scripting cannot be blocking. You cannot wait for server answer and continue. But you can use small scripts, connected with ent_last_cb command. The best usage is user interaction. You can add touch buttons to screen or call user command menu actions by callbacks. ## Server side configuration To enable entity tools on server, set sv_enttools_enable to 1 To change maximum number of entities, touched by ent_fire, change sv_enttools_maxfire to required number. ================================================ FILE: Documentation/extensions/expanded-common-structures.md ================================================ # Expanded structures that used by engine and mods To make porting and developing mods on 64-bit platforms less painful, we decided to expand size of several structures. This information important in case you are using codebase like XashXT, Paranoia 2: Savior and want to compile your mod for platform with 64-bit pointer size: you should replace old definitions with new ones, otherwise your mod will not work with Xash3D FWGS (typically, it's just crashing when starting map). | Structure name | Locates in file | Original size on 64-bit | Current size on 64-bit | |----------------|-----------------|-------------------------|------------------------| |`mfaceinfo_t` | `common/com_model.h` | 176 bytes | 304 bytes | |`decal_s` | `common/com_model.h` | 72 bytes | 88 bytes | |`mextrasurf_t` | `common/com_model.h` | 376 bytes | 504 bytes | ================================================ FILE: Documentation/extensions/input-interface-ru.md ================================================ ## Цель На текущий момент клиенты имеют некоторые части платформозависимого кода внутри себя. Это плохо, т.к. мы не можем использовать одинаковые функции (если не перепишем почти половину кода SDL) на разных платформах. ## Клиентская часть * Клиент будет иметь возможность полностью реализовать touch-ввод. Отрисовка может быть произведена через HUD. * Клиент получит простые события движения и обзора от движка ### Клиентская реализация #### Клиент будет экспортировать некоторые функции движку (опционально): * `int IN_ClientTouchEvent ( int fingerID, float x, float y, float dx, float dy );` Вернёт 1 если касание активно, иначе 0. * `void IN_ClientMoveEvent ( float forwardmove, float sidemove );` Клиент будет накапливать значения величин для команд движения перед созданием команд и очищать их при CreateMove. * `void IN_ClientLookEvent ( float relyaw, float relpitch );` Клиент будет вращать камерой когда нужно, прямо как в реализации мыши ## Движковая часть * Движок будет управлять событиями платформы и вызывать клиентские функции * Движок реализует стандартную систему взгляда и движения если её нет в клиенте ### Движковая реализация #### События касания Перед вызовом ClientMove движок обязан получить события о касании. Если из клиента экспортирована функция IN_ClientTouchEvent, событие будет отправлено клиенту. Иначе, движок будет рисовать свой touch-интерфейс. #### Другие события Интерфейс прикосновений и код джойстика в движке будут генерировать следующие два типа событий: * События движения (функция IN_ClientMoveEvent) * События просмотра (функция IN_ClientLookEvent) Если клиент экспортирует эти функции, события будут отправляться клиенту перед CreateMove Иначе события просмотра будут происходить перед CreateMove, но после MoveEvent. Они будут применены к генерируемым командам ================================================ FILE: Documentation/extensions/input-interface.md ================================================ ## Purpose Clients have different platform-depended input code now. It is bad because we cannot use same functions (if we won't rewrite almost half of SDL) on different platforms. ## Client part * Client will have ability to fully implement touch input. Drawing may be done by HUD. * Client will receive basic motion and look events from engine ### Client implementation #### Client will optionally export some functions to Engine: * `int IN_ClientTouchEvent ( int fingerID, float x, float y, float dx, float dy );` Return 1 if touch is active, 0 otherwise. * `void IN_ClientMoveEvent ( float forwardmove, float sidemove );` Client wil accumulate move values before creating commands and flush it on CreateMove. * `void IN_ClientLookEvent ( float relyaw, float relpitch );` Client will rotate camera when needed as in mouse implementation ## Engine part * Engine will handle platform events and call client functions. * Engine will implement fallback look and movement system when client interface not present ### Engine implementation #### Touch events Before calling ClientMove engine must get touch events. If client exported IN_ClientTouchEvent, event will be sent to client. Otherwise engine will draw own touch interface. #### Other events Engine touch interface and joystick support code will generate two types of events: * Move events (IN_ClientMoveEvent function) * Look events (IN_ClientLookEvent function) If client exported these functions, events will be sent to client before CreateMove Otherwise Look Event will be processed before CreateMove, but MoveEvent after. It will be applied to generated command ================================================ FILE: Documentation/extensions/library-naming.md ================================================ I propose a new library naming scheme, which will allow to distribute mods and games in single archive to different operating systems and CPUs: Legend: * $os -- Q_buildos() return value, in lower case. * $arch -- Q_buildarch() return value, in lower case. * $ext -- OS-specific extension: dll, so, dylib, etc. The scheme will be: 1. Client library: * ```client.$ext``` for **Win/Lin/Mac** with **x86**. * ```client_$arch.$ext``` for **Win/Lin/Mac** with **NON-x86**. * ```client_$os_$arch.$ext``` for everything else. 2. Menu library: * ```menu.$ext``` for **Win/Lin/Mac** with **x86**. * ```menu_$arch.$ext``` for **Win/Lin/Mac** with **NON-x86**. * ```menu_$os_$arch.$ext``` for everything else. 3. Server library: * On **Win/Lin/Mac** with **x86**, it **MUST** use the raw gamedll name for corresponding OS field from `gameinfo.txt`. * On **Win/Lin/Mac** with **NON-x86**, it **MUST** use the raw gamedll name for corresponding OS field from `gameinfo.txt`, but append ```_$arch``` before file extension. Like: ```hl_amd64.so``` or ```cs_e2k.so```. * On everything else, it must use gamedll name from ```gamedll_linux``` field, but append ```_$os_$arch``` before file extension. Like: ```hl_haiku_amd64.so``` or ```cs_freebsd_armhf.so```. Why ```gamedll_linux``` and not ```gamedll```? Because it looks more logic that way, most operating systems are *nix-like and share code with Linux, rather than Windows. 4. Refresh library: not needed, as RefAPI is not stable and it's not intended to distribute with mods. For any libraries distributed **with** engine, naming scheme should be used more convenient for OS port. Issue #0. Inconsistency between ABI and Q_buildarch.\ Resolution: Change Q_buildarch return value to use Debian-styled architectures list: https://www.debian.org/ports/, which includes a special naming for big/little-endian and hard/soft-float ARM. Issue #1: Build-system integration.\ Resolution: implemented as [LibraryNaming.cmake](https://github.com/FWGS/hlsdk-portable/blob/master/cmake/LibraryNaming.cmake) and [library_naming.py](https://github.com/FWGS/hlsdk-portable/blob/master/scripts/waifulib/library_naming.py) extensions, see Issue #2(related to #0): Which ARM flavours we actually need to handle?\ Resolution: Little-endian only, as there is no known big-endian ARM platforms in the wild. Architecture is coded this way: * ```armvxy```, where `x` is ARM instruction set level and `y` is hard-float ABI presence: `hf` where hard float ABI used, otherwise `l`. Issue #3: Some mods (like The Specialists, Tyrian, ...) already apply suffixes _i386, _i686 to the gamedll path:\ Resolution: On x86 on **Win/Lin/Mac**, don't change anything. Otherwise, strip the _i?86 part and follow the usual scheme. See discussion: https://github.com/FWGS/xash3d-fwgs/issues/39 Issue #4: When distributing game libraries on Android inside an APK, they couldn't be loaded. Resolution: Enable `useLegacyPackaging` option in build.gradle, when distributing games in APK. Always force game libraries to have `lib` prefix on Android, regardless if they are packaged in APK or not.. ================================================ FILE: Documentation/extensions/mp3-loops.md ================================================ ## Looping MP3 extension It is now possible to loop MP3 file in Xash3D FWGS by adding a custom text tag with `LOOP_START` or `LOOPSTART` in description and time point (in raw samples) in value. ### Example with foobar2000 1. Open Foobar2000 2. Add your .mp3 file to playlist 3. Right click to newly added file and select Properties 4. In Metadata tab, at the bottom of the table, select "+add new" 5. In newly added line replace `input field name` with `LOOP_START` (without any symbols). 6. Press Tab and enter loop time point in raw samples. For example, `0` will replay sound file from beginning to end indefinitely. ### Possible alternatives 1. Classic WAV files looping. HQ WAV files can take too much disk space, and recommended software supporting cue points is paid, outdated and can't run on modern systems. (Although there is alternative that's proven to work with idTech-based engines called LoopAuditioneer.) 2. Vorbis looping through comment. Engine doesn't support Vorbis but this extension was highly inspired by this hack. ### Known bugs and limitations 1. At this time using MP3 as SFX requires complete decoding. This can cause noticeable stutters, so keep MP3 file length in mind. 2. We deliberately only support modern ID3v2.3 and ID3v2.4 tags. Using ID3v1 is not possible. ================================================ FILE: Documentation/extensions/native-object.md ================================================ # GetNativeObject API To be able to use platform-specific features or get optional engine interfaces, we've added a simple call to MobilityAPI on client DLL and PhysicsAPI for server DLL and extended MenuAPI for menu DLL. It's defined like this: ``` void *pfnGetNativeObject( const char *name ); ``` #### Cross-platform objects Only these objects are guaranteed to be available on all targets. | Object name | Interface | |-------------|-----------| | `VFileSystem009` | Provides C++ interface to filesystem, binary-compatible with Valve's VFileSystem009. | | `XashFileSystemXXX` | Provides C interface to filesystem. This interface is unstable and not recommended for generic use, outside of engine internals. For more info about current version look into `filesystem.h`. | #### Android-specific objects | Object name | Interface | |-------------|-----------| | `JNIEnv` | Allows interfacing with Java Native Interface. | | `ActivityClass` | Returns JNI object for engine Android activity class. | ================================================ FILE: Documentation/extensions/sounds.lst.md ================================================ # sounds.lst.md Using sounds.lst located in scripts folder, modder can override some of the hardcoded sounds in temp entities and server physics. File format: ``` { } ``` * Sounds can use any supported sound format (WAV or MP3). * The path must be relative to the sounds/ folder in the game or base directory root, addon folder, or archive root. * Groups can be empty or omitted from the file to load no sound. * Groups can either list a set of files or specify a format string and a range. * Anything after // will be considered a comment and ignored. * Behavior is undefined if the group was listed multiple times. Currently supported groups are: |Group name|Usage| |----------|-----| |`BouncePlayerShell`|Used for BOUNCE_SHELL tempentity hitsound| |`BounceWeaponShell`|Used for BOUCNE_SHOTSHELL tempentity hitsound| |`BounceConcrete`|Used for BOUNCE_CONCRETE tempentity hitsound| |`BounceGlass`|Used for BOUCNE_GLASS| |`BounceMetal`|Used for BOUNCE_METAL| |`BounceFlesh`|Used for BOUNCE_FLESH| |`BounceWood`|Used for BOUNCE_WOOD| |`Ricochet`|Used for BOUNCE_SHRAP and ricochet tempentities| |`Explode`|Used for tempentity explosions| |`EntityWaterEnter`|Used for entity entering water| |`EntityWaterExit`|Used for entity exiting water| |`PlayerWaterEnter`|Used for player entering water| |`PlayerWaterExit`|Used for player exiting water| ## Example This example is based on defaults sounds used in Half-Life: ``` BouncePlayerShell "player/pl_shell%d.wav" 1 3 BounceWeaponShell "weapons/sshell%d.wav" 1 3 BounceConcrete "debris/concrete%d.wav" 1 3 BounceGlass "debris/glass%d.wav" 1 4 BounceMetal "debris/metal%d.wav" 1 6 BounceFlesh "debris/flesh%d.wav" 1 7 BounceWood "debris/wood%d.wav" 1 4 Ricochet "weapons/ric%d.wav" 1 5 Explode "weapons/explode%d.wav" 3 5 EntityWaterEnter "player/pl_wade%d.wav" 1 4 EntityWaterExit "player/pl_wade%d.wav" 1 4 PlayerWaterEnter { "player/pl_wade1.wav" } PlayerWaterExit { "player/pl_wade2.wav" } ``` ================================================ FILE: Documentation/gameinfo.md ================================================ # Game definition and information file gameinfo.txt is an essential part of any Xash3D based game. It allows basic customization for games creators, like setting game title, DLL paths, etc. This document defines gameinfo.txt syntax, supported keys and liblist.gam conversion rules for the latest version of the engine. Note for engine developers, keep this document in sync with an implementation. ## gameinfo.txt syntax * gameinfo.txt is a simple list of keys and values, separated by newline. * You can add single line comments using double slashes (//). * Keys can accept integer, float, string or boolean values. * Boolean keys use 0 as false and 1 as true value. * To have spaces in string values, you must enclose them in double quotes. Then, to have double quotes, you must escape it with backslash, and to have a backslash you need to use double backslashes. The example: ``` // this is a comment :) // this is another comment some_integer_key 123 this_is_float_key 13.37 // optional comment enable_feature 1 // boolean, 1 to enable, 0 to disable example_string_key string_value example_spaces "string with spaces" example_title "Fate\\Stay Night" // engine will parse it as Fate\Stay Night ``` ## gameinfo.txt keys This is a list of all gameinfo.txt keys supported by the engine. Be aware that the engine will silently skip all unrecognized keys. | Key | Type | Default value | Description | | ---------------- | ---------- | --------------- | ----------- | | `ambient0` | string | Empty string | Automatic ambient sound | | `ambient1` | string | Empty string | Automatic ambient sound | | `ambient2` | string | Empty string | Automatic ambient sound | | `ambient3` | string | Empty string | Automatic ambient sound | | `basedir` | string | `valve` | Game base directory, used to share assets between games | | `date` | string | Empty string | Game release date. Unused. | | `dllpath` | string | `cl_dlls` | Game DLL path. Engine will search custom DLLs (client or menu, for example) in this directory, except `gamedll`, see below. | | `fallback_dir` | string | Empty string | Additional game base directory | | `gamedir` | string | Current gamedir | Game directory, ignored in FWGS, as game directory is defined by the game directory name | | `gamedll` | string | `dlls/hl.dll` | Game server DLL for 32-bit x86 Windows (see LibraryNaming.md for details) | | `gamemode` | string | Empty string | Game type. When set to `singleplayer_only` or `multiplayer_only` marks the game as SP or MP only respectively, hiding the option in game UI. Omitting this key or using custom values mark the game as both MP and SP compatible. | | `icon` | string | `game.ico` | Game icon. Engine will automatically append .ico and may automatically switch to .tga icon as well | | `max_beams` | integer | 128 | Beams limit, 64 min, 512 max | | `max_edicts` | integer | 900 | Entities limit, 600 min, 8192 max (protocol limit). In FWGS, minimum is 64. | | `max_particles` | integer | 4096 | Particles limit, 1024 min, 131072 max | | `max_tempents` | integer | 500 | Temporary entities limit. 300 min, 2048 max | | `mp_entity` | string | `info_player_deathmatch` | Entity used to mark maps as multiplayer | | `mp_filter` | string | Empty string | When set, used to filter multiplayer maps instead of `mp_entity`.
If the map name starts with the same characters as this filter, it's considered a multiplayer map | | `nomodels` | boolean | 0 | When set to 1, disallows changing player model in UI | | `noskills` | boolean | 0 | When set to 1, disallows selection of game difficulty | | `secure` | boolean | 0 | When set to 1, original Unkle Mike's engine will completely disable developer mode. FWGS ignores but preserves this value for compatibility. | | `size` | integer | 0 | Game directory size in bytes, used in Change Game dialog only | | `startmap` | string | `c0a0` | The name of the map used in new game | | `sp_entity` | string | `info_player_start` | Entity used to mark map as single player. Used in map validation | | `title` | string | `New Game` | Game title, used in window title, default server name, etc. | | `trainmap` | string | `t0a0` | The name of the training map (Hazard Course) | | `type` | string | Empty string | Game type, used in Change Game UI. | | `url_info` | string | Empty string | Game homepage URL, used in Change Game UI | | `url_update` | string | Empty string | Game updates URL, used in Settings UI | | `version` | float | 1.0 | Game version, used in Change Game dialog and in server info | ## FWGS-specific gameinfo.txt keys These strings are specific to Xash3D FWGS. | Key | Type | Default value | Description | | ----------------------- | ---------- | ------------------------ | ----------- | | `animated_title` | boolean | 0 | Use animated title in main menu (WON Half-Life logo.avi imitation from Half-Life 25-th anniversary update) | `autosave_aged_count` | integer | 2 | Auto saves limit used in saves rotation | | `gamedll_linux` | string | Generated from `gamedll` | Game server DLL for 32-bit x86 Linux (see LibraryNaming.md for details) | | `gamedll_osx` | string | Generated from `gamedll` | Game server DLL for 32-bit x86 macOS (see LibraryNaming.md for details) | | `hd_background` | boolean | 0 | Use HD background for main menu (Half-Life 25-th anniversary update) | | `internal_vgui_support` | boolean | 0 | Only for programmers! Required to be set as 1 for PrimeXT!
When set to 1, the engine will not load vgui_support DLL, as VGUI support is done (or intentionally ignored) on the game side. | | `render_picbutton_text` | boolean | 0 | When set to 1, the UI will not use prerendered `btns_main.bmp` and dynamically render them instead | | `quicksave_aged_count` | integer | 2 | Quick saves limit used in saves rotation | | `demomap` | string | Empty string | The name of the demo chapter map (Half-Life Uplink) | ## Note on GoldSrc liblist.gam support As Xash3D accidentally supports GoldSrc games, it also supports parsing liblist.gam.\ Xash3D will use this file if gameinfo.txt is absent, or if its modification timestamp is older than liblist.gam. > [!NOTE] > Starting from January 2025, Xash3D FWGS doesn't automatically generate gameinfo.txt from liblist.gam. The key conversion table still remains but if you wish to use gameinfo.txt instead of liblist.gam, you can execute `fs_make_gameinfo` in console. For game creators who plan supporting only Xash3D, using this file is not recommended. The table below defines conversion rules from liblist.gam to gameinfo.txt. Some keys' interpretation does differ from `gameinfo.txt`, in this case a note will be left. If `liblist.gam` key isn't present in this table, it's ignored. | `liblist.gam` key | `gameinfo.txt` key | Note | | ----------------- | ------------------ | ---- | | `animated_title` | `animated_title` | | | `edicts` | `max_edicts` | | | `fallback_dir` | `fallback_dir` | | | `game` | `title` | | | `gamedir` | `gamedir` | | | `gamedll` | `gamedll` | | | `gamedll_linux` | `gamedll_linux` | | | `gamedll_osx` | `gamedll_osx` | | | `hd_background` | `hd_background` | | | `icon` | `icon` | | | `mpentity` | `mp_entity` | | | `mpfilter` | `mp_filter` | | | `nomodels` | `nomodels` | | | `secure` | `secure` | In GoldSrc it's used to mark the multiplayer game as anti-cheat enabled.
Original Xash3D misinterprets its value for disallowing console and developer mode.
FWGS ignores this key but preserves for compatibility. | | `startmap` | `startmap` | | | `size` | `size` | | | `trainingmap` | `trainmap` | | | `trainmap` | `trainmap` | | | `type` | `type` & `gamemode`| In `liblist.gam` this key works as both `type` and `gamemode`.
If value is `singleplayer_only` or `multiplayer_only` the game is marked as SP or MP only, and `gameinfo.txt` type set to `Single` or `Multiplayer`.
Any custom value will mark the game as both SP and MP compatible, and type is set to whatever custom value. | | `url_dl` | `url_update` | | | `url_info` | `url_info` | | | `version` | `version` | | ================================================ FILE: Documentation/goldsrc-protocol-support.md ================================================ # Support for GoldSrc network protocol This feature is still work-in-progress, but for now it's available for all users, and we appreciate any bug-reports and contributions around it. For connecting to GoldSrc-based servers, use this command: ``` connect ip:port gs ``` But keep in mind, there are requirement for server to be able to accept connections from Xash3D-based clients: it should use Reunion. Without this requirement, you will just get "Steam validation rejected" error on connecting. That is because proper authorization with Steam API is not implemented in engine yet (but we have plans on it). Also, we encountered that some GoldSrc-based servers are recognizing Xash3D clients as "fake clients" and banning/kicking them. Maybe this problem will be solved along with better compatibility with GoldSrc behavior, but may be not - we don't know logic behind this fake client checks. ================================================ FILE: Documentation/hd-textures.md ================================================ ### HD (external) textures support Xash3D supports loading texture replacements in TGA format for almost all types of models in the game, except alias models at this time. Textures are expected to be located at: * `modfolder/materials/` - for a specific map * `modfolder/materials/common` - common for all maps * `modfolder/materials/decals` - for decals * `modfolder/materials/models/` - for models (texture name must match the internal texture name in the model) Support for high-resolution textures is enabled setting `host_allow_materials` cvar to `1` or in the menu, in "Video options" section. #### Xash3D FWGS additions In addition to paths above, Xash3D FWGS checks following paths: * `modfolder/materials/sprites/` - for sprites, except HUD sprites Also, to check which texture replacements are loaded successfully, failed or weren't found, a mod developer can set `host_allow_materials` cvar value to `2`. The engine will spew log at any developer level in the following format: ``` Looking for replacement... () ``` Status codes: * `OK` - texture replacement file was found and loaded into GPU memory successfully * `FAIL` - texture file was found but hasn't been parsed or loaded successfully. Refer to engine log for more details. * `MISS` - texture file wasn't found Example: ``` Looking for maps/bounce.bsp:!waterblue tex replacement...OK (materials/common/!waterblue.tga) Looking for maps/bounce.bsp:!waterblue_luma tex replacement...MISS (not found) Looking for {shot2 decal replacement...MISS (materials/decals/{shot2.tga) Looking for {shot4 decal replacement...MISS (materials/decals/{shot4.tga) Looking for {shot3 decal replacement...MISS (materials/decals/{shot3.tga) Looking for models/gman tex replacement...FAIL (materials/models/gman/GMan_Case1.tga) Looking for models/gman tex replacement...FAIL (materials/models/gman/inside_1.tga) ``` ================================================ FILE: Documentation/mod-porting-guide.md ================================================ # Self-made port ## Compatibility with RISC architectures ### Unaligned access Unaligned access on **i386** causes only performance penalty, but on **RISC** it can cause unstable work. For HLSDK at least you need such patches in util.cpp: - https://github.com/FWGS/halflife/commit/7bfefe86e35d67867ae7af830ac1fc38f2908360 - https://github.com/FWGS/hlsdk-portable/commit/617d75545f2ecb9b2d46cc30728dc37c9eb6d35e ### Signed chars `char` type defined as **signed** for **x86** and as **unsigned** for **arm**. And sometimes such difference can break logic. As a solution you can use `signed char` type in code directly or use `-fsigned-char` option for gcc/clang compilers. For HLSDK at least you need such [fix](https://github.com/FWGS/hlsdk-portable/commit/1ca34fcb4381682bd517612b530db22a1354a795) in nodes.cpp/.h. ## Compatibility with 64bit architectures You need list of patches for Studio Model Render, MAKE_STRING macro and nodes: - https://github.com/FWGS/hlsdk-portable/commit/d287ed446332e615ab5fb25ca81b99fa14d18a73 - https://github.com/FWGS/hlsdk-portable/commit/3bce17e3a04f8af10a927a07ceb8ab0f09152ec4 - https://github.com/FWGS/hlsdk-portable/commit/9ebfc981773ec4c7a89ffe52d9c249e1fbef9634 - https://github.com/FWGS/hlsdk-portable/commit/00833188dab87ef5746286479ba5aeb9d83b4a0c - https://github.com/FWGS/hlsdk-portable/commit/4661b5c1a5245b27a5532745c11e44b5540e4172 - https://github.com/FWGS/hlsdk-portable/commit/2b61380146b1d58a8c465f0e312c061b12bda115 - https://github.com/FWGS/hlsdk-portable/commit/8ef6cb2427ee16a763103bd3f315f38e2f01cfe2 ## Mobility API Xash3D FWGS has special extended interface in `mobility_int.h` which adds some new features like vibration on mobile platforms. ## Porting server-side code Original valve's server code was compatible with linux and gcc 2.x. Newer gcc versions have restriction which breaks build. Now, to make it building with gcc 4.x+ or clang, you need to do following: * Go to cbase.h and redefine macros as following ``` #define SetThink( a ) m_pfnThink = static_cast (&a) #define SetTouch( a ) m_pfnTouch = static_cast (&a) #define SetUse( a ) m_pfnUse = static_cast (&a) #define SetBlocked( a ) m_pfnBlocked = static_cast (&a) #define ResetThink( ) m_pfnThink = static_cast (NULL) #define ResetTouch( ) m_pfnTouch = static_cast (NULL) #define ResetUse( ) m_pfnUse = static_cast (NULL) #define ResetBlocked( ) m_pfnBlocked = static_cast (NULL) ... #define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (&a) ``` * Replace all SetThink(NULL), SetTouch(NULL), setUse(NULL) and SetBlocked(NULL) by ResetThink(), ResetTouch(), ResetUse() and ResetBlocked() * Sometimes you may need to add #include if functions tolower or isspace are missing ## Porting client-side code * Redefine all DLLEXPORT defines as empty field (Place it under _WIN32 macro if you want to keep windows compatibility). * Remove hud_servers.cpp and Servers_Init/Servers_Shutdown from hud.cpp. * Fix CAPS filenames in includes (like STDIO.H, replace by stdio.h). * Replace broken macros as DECLARE_MESSAGE, DECLARE_COMMAND by fixed examples from our hlsdk-portable port (cl_util.h). * Add ctype.h where is needed (tolower, isspace functions). * Add string.h where is needed (memcpy, strcpy, etc). * Use in_defs.h from hlsdk-portable. * Add input_xash3d.cpp from hlsdk-portable project to fix input. Now your client should be able to build and work correctly. # Porting mod to hlsdk-portable Look at changes which was made. If there are not too much changes (for example, only some weapons was added), add these changes in hlsdk-portable. You can use diff with original HLSDK and apply it as patch to hlsdk-portable. ``` NOTE: Many an old mods was made on HLSDK 2.0-2.1 and some rare mods on HLSDK 1.0. So you need to have different versions of HLSDK to make diffs. Plus different Spirit of Half-Life versions if mod was made on it. Also, weapons in old HLSDK versions does not use client weapon prediction system and you may be need to port standart half-life weapons to server side. ``` Files must have same line endings (use dos2unix on all files). We recommend to enable ignoring space changes in diff. Move all new files to separate directories. # Possible replacements for non-portable external dependencies 1. If mod uses **fmod.dll** or **base.dll** to play mp3/ogg on client-side or to precache sounds on server-side, you can replace it with: - [pfnPrimeMusicStream](https://github.com/FWGS/hlsdk-portable/blob/master/engine/cdll_int.h#L293=) engine callback; - [miniaudio](https://github.com/mackron/miniaudio); - [phonon](https://community.kde.org/Phonon). 2. If mod uses **OpenGL**, porting code to Xash3D render interface is recommended. 3. If mod uses **cg.dll**, you can try to port code to [NVFX](https://github.com/tlorach/nvFX). 4. If mod uses detours, comment code or try to find replacement somehow by yourself. # Additional recommendations 1. If mod uses STL, you can replace it with [MiniUTL](https://github.com/FWGS/MiniUTL). 2. Avoid to use dynamic casts to make small size binaries. 3. Avoid to use exceptions to make small size binaries. # Writing build scripts Use wscript/CMakeLists.txt files from hlsdk-portable as build scripts example. Get .c and .cpp file lists from Visual Studio project. Add missing include dirs. ================================================ FILE: Documentation/musl.md ================================================ # Xash3D FWGS on `musl` Xash3D FWGS works on `musl` out of the box. However, the engine doesn't try to differentiate glibc and musl anymore. If you see error similar to: ``` Host_InitError: can't initialize cl_dlls/client.so: Error relocating valve/cl_dlls/client.so: __sprintf_chk: symbol not found ``` ... or you know that the game you're running is linked against glibc, you can try using `libgcompat`, like this: ``` $ LD_PRELOAD=/lib/libgcompat.so.0 ./xash3d ... ``` It will automatically add the missing symbols that glibc binaries usually need. In the future we might automatically link engine against `libgcompat` for better compatibility with prebuilt or closed-source games, if there will be any use for this. ================================================ FILE: Documentation/nat-bypass-usage.md ================================================ # NAT bypass feature in Xash3D FWGS Since IPv6 not as widespread as we would like, NAT (Network Address Translation) still being actively used by many internet service providers in an attempts to mitigate IPv4 addresses exhaustion. In short, they uses one IPv4 address and doing some tricks with ports to represent many other users behind this address. But this leads to a problem for users: they cannot accept direct incoming connections anymore. This means that if you are behind provider's NAT and will try to setup Xash3D FWGS server - nobody will be able to connect to it, and the server will not show up in servers public list. ## Is it possible to avoid this problem? In most cases, it is possible to bypass NAT with UDP hole punching, and Xash3D FWGS uses this method too. But this method is not 100% guaranteed to work - it depends on NAT configurations on both server and client side, and there is no way to control it. First of all, server should not be behind symmetric NAT. You can check your NAT type on [this page](https://www.checkmynat.com/). If you get "Symmetric NAT" result in this test, that means you cannot setup publicly available server with internet connection that you are using. Here is more detailed scheme of different NAT types compatibility, it explains are users with different NAT types can connect to each other or not. Client NAT type on rows, server NAT type on columns: | *NAT Type* | Full Cone | Restricted Cone | Port Restricted Cone | Symmetric | | -------------------- | --------- | --------------- | -------------------- | --------- | | Full Cone | ✔️ | ✔️ | ✔️ | ✔️ | | Restricted Cone | ✔️ | ✔️ | ✔️ | ✔️ | | Port Restricted Cone | ✔️ | ✔️ | ✔️ | ❌ | | Symmetric | ✔️ | ✔️ | ❌ | ❌ | ## How to use NAT bypass feature? If you are starting server within the game, you need to enable option "*Use NAT Bypass instead of direct mode*" in bottom left corner of screen. If you are starting dedicated server, you should add console variable `sv_nat 1` to server.cfg file, or add `+sv_nat 1` to server startup parameters. If you need to connect to server behind NAT, there is separate tab named *NAT* for such servers in server browser. Note that it is useless to add NAT server to favorites, or somehow trying manually to store it, because it has changing address and port. ================================================ FILE: Documentation/not-supported-mod-list-and-reasons-why.md ================================================ # Not supported mods and reasons why |Name |Version |Why not working |What was made for that |---- |------- |--------------- |---------------------- |Area 51 |Update 1 |Uses outdated BSP31 map format and custom HLFX SDK libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works. |Arrange Mod: Rebirth |v150 |No idea yet. | |Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/bshift. |Counter Strike |Beta 6.5- |Uses an old WON HL 1.0.0.16- interface. | | |1.4 |Has encrypted blob instead of normal client.dll. |You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works. | |1.5 |Has encrypted blob instead of normal client.dll. |Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845. | |1.6(The latest steam release) |Uses vgui2 library which xash3d does not support. |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. Recreated Client Source Code here: https://github.com/Velaron/cs16-client. |Counter Strike: Condition Zero |The latest steam release |Uses vgui2 library which xash3d does not support. |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. Recreated Client Source Code here: https://github.com/Velaron/cs16-client. |Counter Strike: Condition Zero - Deleted scenes |The latest steam release |Uses vgui2 library which xash3d does not support. Uses new sequences code on engine-side that was never used in any other mods before. |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. |Day of Defeat |The latest steam release |Uses vgui2 library which xash3d does not support. |Some work on vgui2 support was made here: https://github.com/FWGS/xash3d/tree/vinterface. |Half-Life: Extended |Day One demo |Uses many hooks to GoldSource engine and version check. |Just wait new version or use more old version. |Icon of Hell |Beta 0.99 |Uses outdated BSP31 map format and paranoia 2 libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps and use Paranoia 2: The Savior 1.51 libraries but there no warranty if it works. |Paranoia 2: The Savior |All builds older 1.51 |Uses an old renderer interface and engine features. | |Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion. |Sven-Coop |5.0+ |Uses custom GoldSrc engine. | |Time Shadows |Beta 0.1 |Uses Direct3D renderer. | ================================================ FILE: Documentation/opensource-mods.md ================================================ Source code of this mods is available for modders and porters in public access. # Original Work ## Absolute Zero Official gitlab repository - https://gitlab.com/Cobalt-57/half-life-absolute-zero/ ## Adrenaline Gamer Official github repository by Martin \"Bullet\" Webrant - https://github.com/martinwebrant/agmod ## Arrangement Mirrored on github - https://github.com/JoelTroch/am_src_30jan2011 ## Arrangemode: Rebirth Mirrored on github - https://github.com/JoelTroch/am_src_rebirth ## Battle Grounds Mirrored on github - https://github.com/nekonomicon/BattleGrounds ## Bubblemod Download page on official site(WM snapshot) - [http://www.bubblemod.org/dl_default.php](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php) Mirrored on github - https://github.com/HLSources/BubbleMod ## Chicken Fortress Official github repository - https://github.com/CKFDevPowered/CKF3Alpha ## Cold Ice Version 1.9 is available on ModDB - https://www.moddb.com/mods/cold-ice/downloads/cold-ice-sdk Version 1.9 mirrored on github - https://github.com/solidi/hl-mods/tree/master/ci ## Cold Ice Ressurection Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir ## Counter-Life Available on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code ## Cthulhu Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK ## Deathmatch Classic Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/dmc ## Delta Particles Available on ModDB - https://www.moddb.com/mods/half-life-delta/downloads/delta-particles-full-sources-maps-and-c-code ## Earth Special Forces Alpha 2.0 - https://www.gamers-desire.de/details/2830 ## ESHQ Official github repository - https://github.com/adslbarxatov/xash3d-for-ESHQ ## Flat-Life Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_(Flat-Life) ## Gang Wars Mirrored on github - https://github.com/nekonomicon/gw1.45src ## Go-mod Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30 ## GT mod Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) ## Half-Life: Advanced Deathmatch Mirrored on github - https://github.com/solidi/hl-mods/tree/master/hla ## Half-Life: Decay Mirrored on github(PC Port) - https://github.com/hoaxer/Half-Life-Decay ## Half-Life: Echoes Available on ModDB - https://www.moddb.com/mods/half-life-echoes ## Half-Life: Expanded Arsenal Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal ## Half-Life: Gravgun mod Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun ## Half-Life: Invasion Official github repository - https://github.com/jlecorre/hlinvasion ## Half-Life: Pong Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong ## Half-Life: Quest Mode Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030 ## Half-Life: Top-Down Official gitlab repository - https://gitlab.com/Sockman/hltopdown ## Half-Life: Update Official github repository - https://github.com/Fograin/hl-subsmod-ex ## Half-Life: Rally Official gitlab repository - https://gitlab.com/hlrally/src ## Half-Life: Weapon Edition Available on ModDB - https://www.moddb.com/mods/half-life-weapon-edition/downloads/half-life-weapon-edition-1508-alpha-open-src ## Half-Life: Year of the Dragon Available on ModDB - https://www.moddb.com/mods/year-of-the-dragon ## Half-Nuked Available on ModDB - https://www.moddb.com/mods/half-nuked/downloads/half-nuked-src ## Half-Payne Official github repository - https://github.com/suXinjke/HalfPayne ## Half-Quake Official github repository - https://github.com/muddasheep/hqtrilogy ## Half-Rats: Parasomnia Official github repository - https://github.com/HeathGames/half_rats_parasomnia_src ## Half-Screwed Official github repository - https://github.com/desukuran/half-screwed ## Headcrab Frenzy Version 1.3 on ModDB - https://www.moddb.com/mods/headcrab-frenzy/downloads/headcrab-frenzy-13-beta-source-code ## Heart of Evil Mirrored on github - https://github.com/nekonomicon/HeartOfEvil ## Ingram Chillin' Mod Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ ## Master Sword Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic Official github repository of Master Sword Rebirth - https://github.com/MSRevive/MasterSwordRebirth ## MechMod Official github repository - https://github.com/vermagav/mechmod ## Mortal Combat Forever Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Mortal_Combat_Forever_(Polnie_ishodniki) ## Natural Selection Official github repository - https://github.com/unknownworlds/NS ## Overturn Available in mod archive on ModDB - https://www.moddb.com/mods/overturn ## Oz Deathmatch Mirrored on github - https://github.com/nekonomicon/OZDM ## Paranoia Available on ModDB - https://www.moddb.com/mods/paranoia/downloads/paranoia-toolkit ## Paranoia 2: The Savior Prealpha, mirrored on github - https://github.com/a1batross/Paranoia2_ancient Version 1.51, mirrored on github - https://github.com/a1batross/Paranoia2_original ## Raven City *Unfinished mod* Was found on HLFX - http://hlfx.ru/forum/showthread.php?s=2c892dfc52f72be52a89c3f52a397cd0&threadid=1819&postid=44674#post44674 ## Ricochet Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/ricochet ## Spirit of Half-Life [Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life ## The Wastes Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk ## Threewave CTF *Unfinished mod by Valve Software* Available in Valve's Half-Life repository with Deathmatch Classic sources - https://github.com/ValveSoftware/halflife/tree/master/dmc ## Trinity Render Available on ModDB - https://www.moddb.com/mods/half-life-episode-two/downloads/trinity-rendering-engine-v308f ## Tyrian: Ground Assault Available on ModDB - https://www.moddb.com/mods/tyriangroundassault/downloads/tyrianga-v1-0-src ## Wasteland Mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-wasteland-sdk ## Wizard Wars Download page on official site - http://www.thothie.com/ww/ ## XashXT Mirrored on github - https://github.com/a1batross/XashXT_original ## Xen-Warrior *Source code is a part of Spirit of Half-Life 1.0-1.2 under XENWARRIOR macro* [Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life ## Zombie-X Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version ## ZXC Available on ModDB - https://www.moddb.com/mods/zxc-mod-133/downloads/zxc-mod-136-final Mirrored on github - https://github.com/ZXCmod/ZXCmod # Reimplementation ## Absolute Redemption Branch **redempt** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/redempt ## Adrenaline Gamer OpenAG by YaLTeR - https://github.com/YaLTeR/OpenAG ## Afraid of Monsters malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-aom Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom ## Afraid of Monsters: Director's cut Reverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc ## Azure Sheep malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-asheep Reverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep ## Big Lolly malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-biglolly Branch **biglolly** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/biglolly ## Black Ops malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-blackops Branch **blackops** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/blackops ## Bloody Pizza: Vendetta Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Case Closed Branch **caseclosed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/caseclosed ## Cleaner's Adventures Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Counter Strike Reverse-engineered code of client part by a1batross - https://github.com/Velaron/cs16-client Reverse-engineered code of server part by nagist - https://github.com/nagist/cs16nd Reverse-engineered code of server part by s1lentq - https://github.com/s1lentq/ReGameDLL_CS ## Counter Strike Online CSO-like Xash3D-based mod, CSMoE - https://github.com/MoeMod/CSMoE ## Crack-Life Reverse-engineered code: branch **cracklife** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/cracklife ## Crack-Life: Campaign Mode Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_life Reverse-engineered code: branch **clcampaign** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/clcampaign ## Escape from the Darkness malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-eftd Reverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd ## Half-Life: Black Guard *This mod uses dlls from Cleaner's Adventures* Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Half-Life: Blue Shift Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253 Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift ## Half-Life: Induction Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction ## Half-Life: Opposing Force Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d Reverse-engineered code: clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition ## Half-Life: Rebellion Reverse-engineered code: branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion ## Half-Life: Urbicide Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide ## Half-Life: Visitors malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-visitors Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors ## Half-Secret Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret ## Night at the Office malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-nato Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/noffice ## Poke 646 malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646 Reverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 ## Poke 646: Vendetta malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-poke646-vendetta Reverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta ## Residual Life Reverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Residual Point Reverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Sewer Beta Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta ## Team Fortress Classic Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client ## The Gate malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-thegate Reverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate ## They Hunger malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-theyhunger Reverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation ## Times of Troubles malortie's recreation - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-tot Branch **tot** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/tot # Derived work ## Adrenaline Gamer Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aghl ## Bubblemod Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod ## Deathmatch Classic Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc Deathmatch Quaked - https://www.moddb.com/games/deathmatch-classic/downloads/dmq2 Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Half-Life: Echoes Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes ## Half-Life: Invasion Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion Port to Linux by fmoraw - https://github.com/fmoraw/hlinvasion Branch **invasion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/invasion ## Half-Life: Top-Down Branch **hltopdown** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hltopdown ## Half-Screwed Branch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-screwed ## Natural Selection Port to Linux - https://github.com/fmoraw/NS ## Spirit of Half-Life Version 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 ## Swiss Cheese Halloween 2002 Just more playable version by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-shall Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween ## The Wastes Version 1.5: Port to Linux - https://git.mentality.rip/a1batross/halflife-thewastes-sdk ## Threewave CTF Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc ## Xen-Warrior Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 ## Zombie-X Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x ================================================ FILE: Documentation/ports.md ================================================ Xash3D FWGS is intended to be easily portable for various platforms, however main issue is maintaining such ports. This page is about merged ports to main source tree and responsible for it developers. For porting guidelines, read engine-porting-guide.md. Status: * **Supported**: active, confirmed to be fully functional, gets built on CI. * **Orphaned**: some work was done but not finished or actively tested due to lack of human resources. * **In progress**: active, under development. * **Old Engine**: port was for old engine fork. * **Deprecated**: not supported anymore. Table is sorted by status and platform. | Platform | Status | Maintainer | Note | -------- | ------ | ---------- | ---- | Android | Supported | @Velaron | | *BSD | Supported | @nekonomicon | | GNU/Linux | Supported | @a1batross, @mittorn | | macOS | Supported | @sofakng | | PSVita | Supported | @fgsfdsfgs | | Switch | Supported | @fgsfdsfgs | | Windows | Supported | @a1batross, @SNMetamorph | | DOS4GW | Orphaned | N/A | Haven't been confirmed to work for a very long time | Haiku | Orphaned | N/A | Was added by #478 and #483 | IRIX | Orphaned | N/A | Undone, compiles but requires big endian port | MotoMAGX | Orphaned | N/A | Should work but the compiler used for this platform is very unstable and easy to crash (it's GCC 3.4) | SerenityOS | Orphaned | N/A | Works but not throughly tested | Solaris | Orphaned | N/A | Works but not throughly tested | WebAssembly System Interface | Orphaned | N/A | Undone, WASI is missing a lot of APIs we want to use | Dreamcast | In progress | @maximqaxd | [GitHub Repository](https://github.com/maximqaxd/xash3d-fwgs_dc/) | PSP | In progress | @Crow_bar, @Velaron | [GitHub Repository](https://github.com/Crow-bar/xash3d-fwgs) | Wii | In progress | Collaborative effort | [GitHub Repository](https://github.com/saucesaft/xash3d-wii) | Emscripten | Old Engine | N/A | | 3DS | Old Engine fork | N/A | [GitHub Repository](https://github.com/masterfeizz/Xash3DS) | Oculus Quest | Old Engine fork | N/A | [GitHub Repository](https://github.com/DrBeef/Lambda1VR) | iOS | Deprecated | N/A | See GitHub issue #61 ================================================ FILE: Documentation/psvita.md ================================================ ## PlayStation Vita port ### Prerequisites 1. Make sure your PSVita is [set up to run homebrew applications](https://vita.hacks.guide/). 2. Install [kubridge](https://github.com/TheOfficialFloW/kubridge/releases/). It is recommended to use kubridge version `0.1`, because other versions aren't tested, we don't know are they suitable or not. Worth to notice, we got reports that automatic plugins management app EasyPlugin have issues with installing kubridge plugin, so it's better to install it manually: by copying `kubridge.suprx` to your taiHEN plugins folder (usually `ux0:/tai`, but could be `ur0:/tai`) and add it to your `config.txt`, for example: ``` *KERNEL ux0:tai/kubridge.skprx ``` 3. Install `libshacccg.suprx` by following [this guide](https://cimmerian.gitbook.io/vita-troubleshooting-guide/shader-compiler/extract-libshacccg.suprx). ### Installation 1. If you have an old vitaXash3D install, remove it. 2. Get `xash3d-fwgs-psvita.7z` from the latest [automatic build](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous). 3. Install `xash.vpk` from the 7z archive onto your PSVita. 4. Copy the `data` directory from the 7z archive to the root of your PSVita's SD card. 5. Copy the valve folder and any other mod folders from your Half-Life install to `ux0:/data/xash3d/` (you can use other mountpoints instead of `ux0`). **Do not overwrite anything.** ### Build instructions 1. Install [VitaSDK](https://vitasdk.org/). 2. Build and install [vitaGL](https://github.com/Rinnegatamante/vitaGL): ``` git clone https://github.com/Rinnegatamante/vitaGL.git make -C vitaGL NO_TEX_COMBINER=1 HAVE_UNFLIPPED_FBOS=1 HAVE_PTHREAD=1 SINGLE_THREADED_GC=1 MATH_SPEEDHACK=1 DRAW_SPEEDHACK=1 HAVE_CUSTOM_HEAP=1 -j2 install ``` 3. Build and install [vita-rtld](https://github.com/fgsfdsfgs/vita-rtld): ``` git clone https://github.com/fgsfdsfgs/vita-rtld.git && cd vita-rtld mkdir build && cd build cmake -DCMAKE_BUILD_TYPE=Release .. make -j2 install ``` 4. Build and install [this SDL2 fork](https://github.com/Northfear/SDL) with vitaGL integration: ``` git clone https://github.com/Northfear/SDL.git && cd SDL mkdir build && cd build cmake -DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DVIDEO_VITA_VGL=ON .. make -j2 install ``` 5. Use `waf`: ``` ./waf configure -T release --psvita ./waf build ``` 6. Copy all the resulting `.so` files into a single folder: ``` ./waf install --destdir=xash3d ``` 7. `xash.vpk` is located in `build/engine/`. ================================================ FILE: Documentation/supported-mod-list.md ================================================ - [List of mods which work on Linux and other non-Windows platforms without troubles](#list-of-mods-which-work-on-linux-and-other-non-windows-platforms-without-troubles) - [List of Half-life-based mods](#list-of-half-life-based-mods) - [The big singleplayer mods and mappacks](#the-big-singleplayer-mods-and-mappacks) - [Unfinished singleplayer mods and mappacks](#unfinished-singleplayer-mods-and-mappacks) - [Just mappacks](#just-mappacks) - [Singleplayer mods or mappacks with a little incompatibilities](#singleplayer-mods-or-mappacks-with-a-little-incompatibilities) - [Singleplayer mods which has custom gamedll with minor changes and fully playable with vanilla Half-Life libraries](#singleplayer-mods-which-has-custom-gamedll-with-minor-changes-and-fully-playable-with-vanilla-half-life-libraries) - [Singleplayer mods with one map or single maps](#singleplayer-mods-with-one-map-or-single-maps) - [List of mappacks for Half-Life: Blue Shift](#list-of-mappacks-for-half-life-blue-shift) - [List of Cleaner's Adventures-based mods](#list-of-cleaners-adventures-based-mods) - [List of mods which based on Spirit of Half-Life](#list-of-mods-which-based-on-spirit-of-half-life) - [Mods which based on vanilla Spirit of Half-Life 1.2 or older](#mods-which-based-on-vanilla-spirit-of-half-life-12-or-olderfully-playable-with-sohl-12-libraries) - [Mods which based on modified Spirit of Half-Life 1.2 or older](#mods-which-based-on-modified-spirit-of-half-life-12-or-olderpartially-playable-with-sohl-12-libraries-or-not-playable-at-all) - [Mods which based on Spirit of Half-Life 1.4/1.5/1.8](#mods-which-based-on-spirit-of-half-life-141518fully-playable-with-sohl-18-libraries-partially-playable-with-sohl-12-libraries-or-not-playable-at-all) - [Mods which based on Spirit of Half-Life 1.3/1.6/1.7/1.9](#mods-which-based-on-spirit-of-half-life-13161719fully-playable-with-sohl-19-libraries-partially-playable-with-sohl-12-libraries-or-not-playable-at-all) - [List of Opposing Force-based mods](#list-of-opposing-force-based-mods) - [The big singleplayer mods or mappacks](#the-big-singleplayer-mods-or-mappacks) - [Unfinished mods](#unfinished-mods) - [Singleplayer mods with one map or single maps](#singleplayer-mods-with-one-map-or-single-maps-1) - [List of XashXT-based mods](#list-of-xashxt-based-mods) - [List of They Hunger-based mods](#list-of-they-hunger-based-mods) - [The big singleplayers mods](#the-big-singleplayers-mods) - [Single maps](#single-maps) - [List of supported mods from mobile_hacks branch of HLSDK Portable by FWGS](#list-of-supported-mods-from-mobile_hacks-branch-of-hlsdk-portable-by-fwgs) - [List of games and mods with custom gamedll](#list-of-games-and-mods-with-custom-gamedll) - [List of Counter Strike-based mods](#list-of-counter-strike-based-mods) # List of mods which work on Linux and other non-Windows platforms without troubles Most universal way to install and run mods - place your mod folder beside valve folder and launch engine with command-line arguments `-game ` Also, on some platforms you can see "Custom Game" menu in game where you can choose and run any installed mod. For mappacks - place *.bsp files to **valve/custom/maps** folder, *.wad files to **valve/custom** and type **`map `** cmd in game. ## List of Half-Life-based mods Originally this list was written by @Qwertyus3D (*Qortez*) ### The big singleplayer mods and mappacks 1. [101 grunts](https://www.runthinkshootlive.com/posts/101-grunts/) 2. [1986(Black Mesa Circa 1979,1986)](https://www.fileplanet.com/110726/110000/fileinfo/Black-Mesa-Circa-1979,-1986) 3. [2009EB](https://hldm.ucoz.ru/forum/5-185-1) 4. [A Day in the Life of a Coward](https://www.runthinkshootlive.com/posts/a-day-in-the-life-of-a-coward/) 5. **[A dream](https://gamebanana.com/maps/162091)** aka [Map a Dream](https://twhl.info/vault.php?map=5529) by *Skals* (set **fps_max** to **60** to prevent a possible sticking of the box in the elevator at the map **comp28m2**) 6. [Adam](https://www.moddb.com/mods/adam) 7. **[Affliction](https://www.moddb.com/mods/affliction)** 9. [Agamemnon Icarus Glass](https://twhl.info/vault.php?map=1978) 10. **[Aggregate Pain](https://www.runthinkshootlive.com/posts/aggregate-pain/)** 11. **[Alternative Way: Part 1](https://www.runthinkshootlive.com/posts/alternative-way-part-1/)** 12. [Assassin Mark 2](https://www.fileplanet.com/112020/110000/fileinfo/Assassin-Mark-2) 13. [Assault on Roswell v2.0](https://www.moddb.com/mods/assault-on-roswell) 14. **[Back to Xen](https://www.runthinkshootlive.com/posts/back-to-xen/)** 15. **[Back to Xen 2](https://www.runthinkshootlive.com/posts/back-to-xen-2/)** 16. [Be Careful!](https://www.fileplanet.com/110304/110000/fileinfo/Be-Careful!) 17. **[Beginnings](https://www.runthinkshootlive.com/posts/beginnings/)** 18. [Betrayal](https://www.moddb.com/mods/betrayal) (this mod has inner bugs) 19. **[Between Two Worlds](https://www.runthinkshootlive.com/posts/between-two-worlds/)** 20. [Black Mesa Energy Testing Chamber](https://twhl.info/competitions.php?results=17) 21. **[Black Mesa Sideline](https://www.moddb.com/mods/black-mesa-sideline)** 22. [BlackMesa 2007( Black Mesa Missions 2007 )](https://www.moddb.com/addons/blackmesa-2007) 23. [Blood and Bones](https://www.runthinkshootlive.com/posts/blood-and-bones/) 24. [Boom (Huknenn) v1.0](https://gamebanana.com/mods/35976) & [HL Boom: Gold Edition v1.1](https://www.moddb.com/mods/boom) 25. [BoomeNShtein3D: Episode 1](https://www.moddb.com/games/half-life/addons/boomenshtein3d-episode-1) 26. **[Brave Brain](https://www.moddb.com/mods/brave-brain)** 27. [Breakdown](https://www.runthinkshootlive.com/posts/breakdown-1/) 28. [Breakdown 2: Afterwards](https://www.runthinkshootlive.com/posts/breakdown-2-afterwards/) 29. [Buddhist Wars](https://www.moddb.com/mods/buddhist-wars) 30. **[C&C Tiberian Dawn v2.0](https://www.moddb.com/mods/half-life-cc-tiberian-dawn)** 31. [Castle Creep](https://www.runthinkshootlive.com/posts/castle-creep/) (there is an inner bug in the end of 4th map - don't go further of that point where a bomb is placed, otherwise level change will not work) 32. [Castle Disposed](https://www.moddb.com/mods/castle-disposed) 33. **[Chaos Theory](https://www.runthinkshootlive.com/posts/chaos-theory/)** 34. [Chernobyl](https://www.moddb.com/downloads/half-life3) (this mod has inner bugs; in the beginning you have to use **noclip**, to fix a sticking in the bus) 35. [ChickenMix](https://twhl.info/vault.php?map=3118) 36. [ChickenMix 2](https://twhl.info/vault.php?map=3434) (there are a couple of minor bugs, but they are inner bugs of the mod) 37. [Confronted with Consequences](https://www.moddb.com/mods/confronted-with-consequences) & [Confronted with Consequences 2016 Remake](https://www.moddb.com/mods/confronted-with-consequences-2016-remake) 38. [Construction](https://www.runthinkshootlive.com/posts/construction/) 39. [Conundrum](https://www.runthinkshootlive.com/posts/conundrum/) 40. [Conundrum 2](https://www.runthinkshootlive.com/posts/conundrum-2/) 41. [Crash](https://www.runthinkshootlive.com/posts/crash/) (there is a small issue with a lift in a final map - you should stay closely to the lift's button after you've pressed it, otherwise door of the lift won't open) 42. [CWC Board Mappack Initiative](https://www.moddb.com/mods/cwc-pack-initiative) (the mod has inner bugs) 43. **[DAV Sub](https://www.moddb.com/mods/davsub)** 44. **[DAV Train](https://www.moddb.com/mods/davtrain)** 45. [death = power](https://www.fileplanet.com/53953/50000/fileinfo/Death=power) 46. [Death in the Dark](https://www.moddb.com/games/half-life/addons/death-in-the-dark) (there are some minor inner bugs, but they don't interfere with the mod's progress) 47. [DejaVu v2.00](https://www.moddb.com/mods/dejavu) 48. **[Destination Black Mesa](https://www.moddb.com/mods/destination-black-mesa)** (there is a small inner problem that can randomly appear in the mod's ending - the last map with credits may not work properly; type restart command into console if you get this issue) 49. [Destiny](https://twhl.info/competitions.php?results=15) 50. [Deth](https://www.runthinkshootlive.com/posts/deth/) 51. [Do](https://www.runthinkshootlive.com/posts/do/) 52. [Doomed-life](https://www.moddb.com/mods/doomed-life) 53. [Down Time](https://www.runthinkshootlive.com/posts/down-time/) 54. [Drug Barons](https://www.runthinkshootlive.com/posts/drug-barons/) 55. [Dungeon Death](http://darktreemedia.com/dungeondeath/) 56. [Dust Runner v1.1](https://www.runthinkshootlive.com/posts/dust-runner/) 57. [Dwell](https://www.runthinkshootlive.com/posts/dwell/) 58. **[E. T. C. ( Earthquake Testing Centre )](https://www.moddb.com/mods/etc2)** 59. **[E. T. F.](https://www.moddb.com/mods/e-t-f)** 60. **[E7: Black Star](https://www.moddb.com/mods/e7-black-star)** (set **fps_max** to **50** for the map an_ele to prevent a possible sticking of boxes on a big elevator's platform; or you can try to push a box down from the platrofm before the elevator stops) 61. **[Edge of Darkness](https://www.moddb.com/mods/hl-edge-of-darkness)** 62. [Edge of Death - Series 1](https://twhl.info/vault.php?map=5088) 63. [Engineering Mankind](https://twhl.info/competitions.php?results=13) 64. [Episode Power Plant and China](https://www.artpeter.net/Data/HalfLife/Hl.php) 65. [Episode Secret Weapon](https://www.artpeter.net/Data/HalfLife/Hl.php) (you will face a serious inner bug at the second map if you try to play this mod on hard difficulty) 66. **[Escape](https://www.runthinkshootlive.com/posts/escape-by-leo-ring/)** by *Leo Ring* 67. [Escape from the Egyptian Tomb( RacerX Compo 12 Egyptian Museum )](https://twhl.info/vault.php?map=2368) 68. [Espionage](https://www.runthinkshootlive.com/posts/espionage/) 69. [Exodus](https://www.moddb.com/games/half-life/addons/exodus-1) 70. [Exodus 2](https://www.moddb.com/games/half-life/addons/exodus-2) 71. [Failure](https://www.moddb.com/games/half-life/addons/failure) 72. [Faraon](https://www.moddb.com/games/half-life/addons/faraon-1-4)(it's a set of 4 maps which can be combined in one singleplayer episode: [map 1](http://www.fileplanet.com/50963/50000/fileinfo/Fara%C3%B3n-1), [map 2](http://www.fileplanet.com/50459/50000/fileinfo/Faraon2), [maps 3-4](http://www.fileplanet.com/51799/50000/fileinfo/Faraon-3-4)) 73. **[Fate Reversal v1.1](https://www.moddb.com/mods/fate-reversal)** 74. **[Fathom 2.4](https://twhl.info/vault.php?map=4759)** 75. [Freeman](https://www.runthinkshootlive.com/posts/freeman/) 76. [Freeman's Escape](https://www.moddb.com/mods/freeman-escape-french) (2 maps by *JiggZ*) 77. [Freeman's Fight](https://www.runthinkshootlive.com/posts/freemans-fight/) 78. [Freeman's Return](https://www.moddb.com/games/half-life/addons/freemans-return-v12) 79. [Freeman's Return 2](https://www.moddb.com/games/half-life/addons/freemans-return-2-v11) 80. **[Freeman's Revenge](https://www.runthinkshootlive.com/posts/freemans-revenge/)** 81. [Freeman's Tomb( The Crypt )](https://twhl.info/vault.php?map=3787) 82. **[G-Invasion v2.6](https://www.moddb.com/mods/g-man-invasion)** aka G-Man Invasion 83. [G-Man House](https://www.moddb.com/games/half-life/addons/g-man-house) 84. [GameStar( Episode 4 GameStar )](https://www.runthinkshootlive.com/posts/gamestar/) 85. [Gateway](https://www.moddb.com/mods/gateway) 86. [Gateway 2](https://www.moddb.com/mods/gateway-2) 87. [Gina's Adventures](https://www.moddb.com/mods/ginas-adventures) 88. **[Gman Island Part 1](https://www.runthinkshootlive.com/posts/gman-island-part-1/)** 89. **[Gman Island Part 2](https://www.runthinkshootlive.com/posts/gman-island-part-2/)** 90. **[Graber v1.1](https://www.moddb.com/mods/graber-half-life-mod)** 91. [Graf War](https://www.runthinkshootlive.com/posts/graf-war/) 92. **[Ground Zero](https://www.moddb.com/mods/half-life-ground-zero)** by *Derek 'Hellfire' McBurney* 93. **[Gut Reaction](https://www.moddb.com/mods/gut-reaction)** 94. **[Half-Life C.A.G.E.D.](https://store.steampowered.com/app/679990/HalfLife_Caged/)** 95. **[Half-Life: Dreamcast v1.1](https://www.moddb.com/mods/half-life-dreamcast)** 96. [Half-Life: OPS](https://www.runthinkshootlive.com/posts/half-life-ops/) 97. **[Half-Quake](https://www.moddb.com/mods/halfquake-amen)** 98. **[Half-Quake 2: Amen](https://www.moddb.com/mods/halfquake-amen)** 99. [Hard 2](https://www.runthinkshootlive.com/posts/hard-2/) (set **fps_max** to **60** to prevent a possible sticking of Barney on an elevator in the beginning of the mod) 100. [Hardman - In the City](https://www.runthinkshootlive.com/posts/hardman-in-the-city/) (the updated version of the mod has been tested) 101. [Haunted](https://www.runthinkshootlive.com/posts/haunted/) (set **sv_validate_changelevel** to **0** to avoid a problem with level change between maps **pagan7** and **pagan8**) 102. [Hazardous Materials: Episode 1 & 2](https://www.moddb.com/mods/half-life-hazardous-materials) 103. **[Hazardous-Course 2](https://www.moddb.com/mods/hazardous-course-2)** 104. [Help Wanted](https://www.moddb.com/games/half-life/addons/help-wanted) 105. [Hidden Evil v1.01](https://www.runthinkshootlive.com/posts/hidden-evil/) 106. [High Speed](https://www.moddb.com/games/half-life/addons/high-speed-english-version) 107. **[hlife_hotdog_compo26](https://twhl.info/vault.php?map=5181)** 108. [Home Alone -NOT](https://www.fileplanet.com/49895/40000/fileinfo/Home-Alone--NOT) 109. **[Hour-Glass](https://www.moddb.com/mods/hour-glass)** 110. [Idol Hunt](https://www.runthinkshootlive.com/posts/idol-hunt/) 111. [Independence Day](https://www.runthinkshootlive.com/posts/independence-day/) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions) 112. [Infestation](https://www.runthinkshootlive.com/posts/half-life-infestation/) 113. [Infiltracja](https://unquenque.com/tenfour/) 114. **[Infinite Rift](https://www.moddb.com/mods/infinite-rift)** 115. **[Instinct](https://www.moddb.com/mods/instinct)** 116. [Insurrection](https://www.runthinkshootlive.com/posts/insurrection/) 117. [Irreality](https://www.moddb.com/mods/irreality) 118. [Ispitatel](https://www.moddb.com/mods/ispitatel) 119. [Jailbreak](https://www.runthinkshootlive.com/posts/jailbreak/) 120. [Kill All Greenpeace](https://www.moddb.com/mods/kill-all-greenpeace) 121. [Krypton](https://www.runthinkshootlive.com/posts/krypton/) (there is one inner bug on the map with a rocket pad - the rocket can get stuck when you try to launch it) 122. [Lands of Lore v2](https://www.moddb.com/mods/half-life-lands-of-lore) 123. [Last-life](https://www.moddb.com/mods/hl-last-life) 124. **[Life's End](https://www.moddb.com/mods/lifes-end2)** 125. [Macky's Adventure](https://www.moddb.com/mods/mackys-adventure-expansion-set) 126. [MadCrabs](https://www.runthinkshootlive.com/posts/mad-crabs-1/) (there are couple of potential scripting problems, but they seem to be inner problems of the mod, so if you stuck somewhere just try to replay your game from last autosave) 127. [Marine Invasion: Episode 1 & 2](https://gamebanana.com/gamefiles/2537) 128. **[Mario Keys](https://www.moddb.com/mods/mario-keys)** 129. [McBeth](https://www.runthinkshootlive.com/posts/mcbeth/) 130. [Medieval World](https://www.moddb.com/mods/medieval-world) 131. [Mel Soaring 2: Star Rancor](https://www.moddb.com/addons/mel-soaring-2-star-rancor) 132. **[MINIMICUS](https://twhl.info/vault.php?map=163)** 133. [Misantrophy](https://www.runthinkshootlive.com/posts/misanthropy/) 134. **[Mission Failed](https://www.moddb.com/mods/mission-failed)** 135. [Mission MC Poker](https://www.moddb.com/mods/half-life-mission-mc-poker) 136. **[Mission of Mercy](https://www.runthinkshootlive.com/posts/mission-of-mercy/)** 137. [Mission to Kill](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm) 138. [Mission to Kill 2](https://www.vossey.com/mod/Half-Life/Mision-to-kill-Part-1-et-2--i625.htm) 139. [Mistaken Identity](https://www.runthinkshootlive.com/posts/mistaken-identity/) 140. [Mistaken Identity 2](https://www.runthinkshootlive.com/posts/mistaken-identity-part-2/) 141. **[Moonwalker](https://www.moddb.com/mods/moonwalker)** 142. [Mystery House v1.0](https://www.moddb.com/mods/mystery-house) 143. [NewBlackMesaVille](https://www.runthinkshootlive.com/posts/newblackmesaville/) 144. [Night Shooting](https://www.moddb.com/games/half-life/addons/night-shooting) 145. **[No Exit](https://www.moddb.com/mods/no-exit)** 146. [No-Life](https://www.fileplanet.com/120501/120000/fileinfo/No-Life) 147. **[Nuclear Power Plant](https://www.runthinkshootlive.com/posts/nuclear-power-plant/)** 148. [Occupied Territory](https://www.moddb.com/games/half-life/addons/occupied-territory) 149. [Office Commando](https://www.runthinkshootlive.com/posts/office-commando/) 150. **[Operacja Gargantua](https://www.runthinkshootlive.com/posts/operation-gargantua/)** 151. **[Operacja Mirra](https://unquenque.com/tenfour/)** 152. **[Operation Krautsalat v1.1](https://www.runthinkshootlive.com/posts/operation-krautsalat/)** (there is a potential inner bug at third map - a door to the control room will not be opened, until all alien grunts leaved their cells, so don't kill any of them, which are still in cells) 153. **[Operation: Nova](https://www.moddb.com/mods/half-life-operation-nova)** (there is an inner bug with an M60 machinegun on a map with Osprey: it can be activated and used if you are staying in front of it, not behind it) 154. [Optimum Fear](https://www.fileplanet.com/7472/0/fileinfo/Optimum-Fear) 155. [Outrun](https://www.moddb.com/mods/outrun) 156. [Outwards (Day One)](https://web.archive.org/web/20161026104825/http://visgi.info/maps/) 157. [Overhaul Pack](https://www.moddb.com/mods/half-life-overhaul-pack) (Just HD pack for Half-Life) 158. [P.I.Z.D.E.C.](https://www.runthinkshootlive.com/posts/pizdec/) 159. [pagoda](https://www.moddb.com/mods/pagoda1) 160. **[Peaces Like Us v1.0](https://www.runthinkshootlive.com/posts/peaces-like-us/)** 161. [Phobos IV](https://www.moddb.com/mods/phobos-iv) 162. [Pimp My Car](https://www.runthinkshootlive.com/posts/pimp-my-car/) (there is an inner issue with incorrect responses of buttons on combination locks) 163. [Point Blank – Stackdeath Complex](https://www.runthinkshootlive.com/posts/half-life-point-blank-stackdeath-complex/) 164. [Prisoner Escaped](https://www.moddb.com/mods/prisoner-escaped-1-2) 165. **[Prisoner of Event](https://wp.vondur.net/?page_id=40)** 166. [Prisoner of War](https://www.runthinkshootlive.com/posts/prisoner-of-war/) 167. [Project Quantum Leap](https://www.moddb.com/mods/project-quantum-leap) 168. [Project VIP](https://www.pcosmos.ca/mods-hl/downloads/?mod=projectVIP) 169. **[Projekt Einstein](https://unquenque.com/tenfour/)** 170. [Recon( Reconnaissance )](https://www.moddb.com/games/half-life/addons/reconnaissance) 171. [Red Mesa](https://www.moddb.com/mods/red-mesa-1) 172. [Red Mesa 2](https://www.moddb.com/mods/red-mesa-2) 173. [RES v1.0](https://www.fileplanet.com/29002/20000/fileinfo/RES) 174. [Rescue 9-1-Freeman](https://www.moddb.com/mods/rescue-9-1-freeman) 175. [Resistance](https://www.runthinkshootlive.com/posts/resistance/) 176. [Resistance 2](https://www.runthinkshootlive.com/posts/resistance-2/) 177. [Resistance 3](https://www.runthinkshootlive.com/posts/resistance-3/) 178. [Return](https://www.runthinkshootlive.com/posts/return/) 179. [Return 2](https://www.runthinkshootlive.com/posts/half-life-return-2/) (there is an inner problem in the first map with a moving of the second metal box, just keep pushing the box, until it begins to move) 180. [Reviviscence](https://www.moddb.com/mods/reviviscence) 181. [Road of Destiny](https://www.fileplanet.com/10393/10000/fileinfo/Road-of-Destiny) 182. **[Rooms: TWHL COOP Project( Rooms Half-Life or TWHL Rooms )](https://twhl.info/vault.php?map=5394)** 183. [Run for Life](https://www.runthinkshootlive.com/posts/run-for-life/) (the "stolen" version of this mod with changed menu background also known as *Mad Escape*) 184. [S.W.A.T.](https://www.moddb.com/mods/swat2) 185. [Sabotage](https://www.runthinkshootlive.com/posts/sabotage-2/) 186. [Sagharmath](https://www.moddb.com/games/half-life/addons/sagharmath) 187. [Sandscroll](https://gamebanana.com/maps/15988) 188. [Saving Bob](https://www.runthinkshootlive.com/posts/saving-bob/) 189. [Secret Base](https://www.runthinkshootlive.com/posts/secret-base/) 190. **[Secret Santa 2011 for Soup Miner](https://twhl.info/vault.php?map=5724)** 191. [Secret Santa 2011 for Stojke](https://twhl.info/vault.php?map=5728) 192. [Senses in Decay](https://www.moddb.com/mods/senses-in-decay) 193. [Shift-Two v1.1](https://www.moddb.com/mods/shift-two1) 194. [Sky Mesa](https://www.moddb.com/mods/sky-mesa) 195. [Sleephorst v1.5](https://www.moddb.com/mods/sleephorst-v15) 196. [Slimy Situation](https://www.moddb.com/mods/slimy-situation) 197. **[Smart Decoy](https://www.moddb.com/mods/smart-decoy)** 198. **[Somewhere in Time](https://www.runthinkshootlive.com/posts/somewhere-in-time/)** 199. [Sooper2](https://www.runthinkshootlive.com/posts/sooper-2/) 200. **[Split-Second](https://twhl.info/vault.php?map=4343)** 201. **[Subhumanity](https://www.runthinkshootlive.com/posts/sub-humanity/)** 202. Terminal Monstrosity (4 maps by Anthony Plant) 203. [Terror Side](https://www.runthinkshootlive.com/posts/terror-side/) (set **sv_validate_changelevel** to **0** to avoid problem with level change between some maps) 204. [Test Your Skill](https://www.moddb.com/games/half-life/addons/test-your-skill) 205. [The Challenger Deep](https://www.runthinkshootlive.com/posts/challenger-deep/) 206. **[The Challenger Deep 2](https://www.moddb.com/mods/the-challenger-deep-2)** 207. [The Evil Thing](https://www.moddb.com/mods/the-evil-thing) 208. [The Evil World](https://www.moddb.com/mods/the-evil-world) 209. [The Haunted Lab](https://www.runthinkshootlive.com/posts/the-haunted-lab/) 210. **[The Long Night](https://www.moddb.com/mods/the-long-night)** 211. [The Mansion](https://twhl.info/vault.php?map=2438) 212. [The Night Things](https://twhl.info/vault.php?map=2439) 213. [The Night Things 2: Woodstock Manor](https://twhl.info/vault.php?map=2706) 214. [The Returning](https://www.moddb.com/games/half-life/addons/the-returning) 215. [The Ropes](https://www.runthinkshootlive.com/posts/the-ropes/) (set **fps_max** to **60** to prevent a possible sticking of a barrel which you need to lift down with an elevator at the second map) 216. [The Way Is Clear](https://www.runthinkshootlive.com/posts/the-way-is-clear/) 217. [The Way Is Clear 2](https://www.runthinkshootlive.com/posts/the-way-is-clear-2/) 218. **[The World Machine](https://www.moddb.com/mods/half-life-the-world-machine)** 219. **[The Xeno Project](https://www.moddb.com/mods/xeno-project-i-ii)** 220. **[The Xeno Project 2](https://www.moddb.com/mods/xeno-project-i-ii)** 221. [Then and Now](https://twhl.info/vault.php?map=6048) 222. [They Live( Chungo )](https://www.runthinkshootlive.com/posts/they-live/) 223. [Timefall](https://www.moddb.com/mods/hl-timefall) 224. **[Timeline](https://www.moddb.com/mods/timeline-series)** 225. **[Timeline II: Iced Earth](https://www.moddb.com/mods/timeline-series)** 226. **[Todesangst](https://www.moddb.com/mods/todesangst)** 227. **[Tokami Island](https://www.fileplanet.com/110517/110000/fileinfo/Tokami-Island)** 228. [Tower](https://hlperfectzone.narod.ru/mods.htm) (set **sv_validate_changelevel** to **0** to avoid a problem with incorrect level transitions) 229. [Trespasser](https://www.moddb.com/mods/trespasser) 230. [Try, Try Again](https://www.runthinkshootlive.com/posts/try-try-again/) 231. [Tucked Up](https://www.moddb.com/games/half-life/addons/tucked-up) 232. [TWHLmix](https://twhl.info/vault.php?map=2516) 233. [Two Smoking Barrels](https://www.runthinkshootlive.com/posts/two-smoking-barrels/) 234. [Typical Disaster](https://www.moddb.com/mods/typical-disaster) 235. [Typical Disaster: The Lost Levels](https://www.runthinkshootlive.com/posts/typical-disaster-the-lost-levels/) 236. [U-Life](https://www.moddb.com/mods/u-life) 237. **[Underground v2.0](https://www.moddb.com/mods/underground)** (there is an inner crash bug on final titles: some text lines are too long and cannot be properly displayed) 238. [Underground Territory](https://www.moddb.com/games/half-life/addons/underground-territory-part-1) 239. [Undertime](https://twhl.info/vault.php?map=3906) 240. **[Uplink Addon v1.2](https://www.fileplanet.com/82828/80000/fileinfo/Half-Life-Uplink-Addon-1.2)** 241. **[Uplink Lite](https://www.fileplanet.com/7482/0/fileinfo/Uplink-Lite)** & **[Uplink Lite Plus Demo](https://www.fileplanet.com/128970/120000/fileinfo/Uplink-Lite-Plus-Demo)** 242. **[Uplinked](https://www.moddb.com/mods/uplinked)** 243. **[USS Darkstar](https://www.moddb.com/mods/uss-darkstar)** 244. [Vengeance](https://www.moddb.com/mods/half-life-vengeance) 245. [Virtual Reality: The Real World](https://www.runthinkshootlive.com/posts/virtual-reality-the-real-world/) 246. [VLOKAM](https://www.ceskemody.cz/mapy.php?lng=1&clanek=32&razeni_hra=1&pn=vlokam-1) 247. [VLOKAM 2](https://www.ceskemody.cz/mapy.php?lng=1&clanek=33&razeni_hra=1&pn=vlokam-2) 248. [Wail of Death](https://www.runthinkshootlive.com/posts/wail-of-death/) 249. [Wail of Death 2: The Hell Master](https://www.runthinkshootlive.com/posts/wail-of-death-2-the-hell-master/) 250. [War Crimes v1.2](https://www.moddb.com/games/half-life/addons/war-crimes) 251. [Way to Victory](https://www.moddb.com/mods/2gn) 252. [Windmill v2.0](https://www.runthinkshootlive.com/posts/windmill/) 253. [World War III Missions (parts 1, 2 & 3)](https://www.moddb.com/mods/world-war-iii-missions-1-2-3) 254. [Worst Holiday](https://www.moddb.com/games/half-life/addons/worst-holiday) (there are 2 problems with doors; a door at second map can be opened if you sit down near it; to pass the door in laboratory you should run quickly as you can from ventilaion shaft - there is an autosave point on a previous map, if you failed) 255. [Xen](https://www.runthinkshootlive.com/posts/half-life-xen-2/) 256. [You Are in Army Now](https://www.pcosmos.ca/mods-hl/downloads/?mod=armynow) 257. [Zubben](https://www.runthinkshootlive.com/posts/zubben/) ### Unfinished singleplayer mods and mappacks 1. [Alpha Research Facility v0.3](https://www.moddb.com/games/half-life/downloads/alpha-research-facility-v-03-steam) 2. [Back in Future Alpha version](https://www.runthinkshootlive.com/posts/back-in-future/) 3. [Biohazard 2 (unfinished mod version)](https://www.moddb.com/mods/biohazard-2-outside-of-black-mesa) 4. Black Meat (unfinished mappack of 3 maps) 5. [Bloodreign](https://www.runthinkshootlive.com/posts/blood-reign/) (the mod has inner bugs, especially second map) 6. [Boreality: Part 1](https://www.runthinkshootlive.com/posts/boreality/) (there is a visual glitch problem on the map **mell04**, but same happens for original Half-Life too) 7. [Catacombs: Part 1](https://www.runthinkshootlive.com/posts/catacombs/) 8. [Cro-man's Office Mappack](https://twhl.info/vault.php?map=5547) 9. Great Escape (unfinished mappack of 2 maps) 10. [Half-Life Episode Two Demo Alpha v1.0](https://www.moddb.com/games/half-life/downloads/half-life-episode-two-v10-demo) 11. Half-Life: Marine Demo (by EDOC32) 12. [Hazardous-Course (first version of Hazardous-Course 2)](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/) 13. [High Tech v0.2](https://hl.loess.ru/?mod=499) 14. [HL: Paranormal Demo](https://gamebanana.com/mods/157378) 15. [HL Shadows, Part 1](https://www.fileplanet.com/7466/0/fileinfo/'HL-Shadows',-Part-1) 16. [HLNewEnd](https://twhl.info/vault.php?map=2275) 17. [Kleiners Adventures Demo](https://www.moddb.com/mods/kleiners-adventures) 18. [Kleiners Adventures: The White Line Demo](https://www.moddb.com/mods/kleiners-adventure-siemka321-edition) 19. [LV-426: Episode 1](https://www.runthinkshootlive.com/posts/lv426-version-a/) 20. [Malevolence(1.3 and older)](https://www.moddb.com/mods/malevolence) 21. [Meth-Life Demo v1.0](https://www.gamewatcher.com/mods/half-life-mod/meth-life-mod-demo-1-0) 22. [Night-Fire Demo](https://efreeman1.narod.ru/Index.htm) 23. [On the Other Side](https://twhl.info/competitions.php?results=14) aka *OTOS* (there is a train-related bug in the beginning, which makes you to use **noclip**, but it's an inner flaw of the map) 24. [Orion: Part 1( Project Orion )](https://www.moddb.com/mods/orionold) 25. [Padle Mesto Demo](https://www.moddb.com/games/half-life/addons/padle-mesto-demo) (some scripts & buttons are not configured properly in this mod, so they can be activated/deactivated automaically after reloading of a saved game) 26. [Panic at Black Mesa Demo](https://www.moddb.com/mods/half-life-panic-at-black-mesa) 27. [Plan B v1.1](https://www.fileplanet.com/129198/120000/fileinfo/Plan-B-SP-Mappack) 28. [Preview of the "Paradox"](https://www.runthinkshootlive.com/posts/half-life-preview-of-the-paradox/) 29. [Project Focus North v1.6 Demo](https://www.fileplanet.com/84637/80000/fileinfo/Project-Focus-North) 30. [Qortez( Quortez )](https://www.runthinkshootlive.com/posts/qortez/) (it's recommended to use only default low-poly model of scientist for this mod; also set **fps_max** to **60** via console to prevent an issue with one of scripted sequences) 31. [Return to Lambdacore Demo](https://www.moddb.com/games/half-life/addons/return-to-lamdacore) 32. [Route City Beta 1](https://www.moddb.com/mods/route-city) 33. [SHAFT - Part 1](https://hl.loess.ru/?mod=913) 34. [Shortcut v1.0 Beta](https://www.runthinkshootlive.com/posts/shortcut/) 35. Stargate Test (3 maps, it's earliest outlines of known Stargate mod) 36. [Stoka](https://www.ceskemody.cz/mapy.php?lng=1&clanek=222&razeni_hra=1&pn=stoka) 37. [Striker's Compo 26](https://twhl.info/vault.php?map=5190) (buggy) 38. [Technology Test 2](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/) 39. [The Gate Playable Demo](https://www.fileplanet.com/116347/110000/fileinfo/The-Gate-Playable-Demo) 40. [They are Back](https://www.runthinkshootlive.com/posts/they-are-back/) 41. [Tiefseelabor( Deep Sea Laboratory )](https://www.runthinkshootlive.com/posts/deep-sea-laboratory/) 42. [Time-Shift](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/) 43. [Train Single Beta](https://csm.dev/threads/half-strike-rezultaty.36394/) (Remove **gfx.wad** from **TrainSingle** folder) 44. [WAR: The Killer Beta 0.1](https://www.moddb.com/mods/war) 45. [White Force Beta( Residual Point prototype )](https://www.moddb.com/mods/hl-residual-point/downloads/white-force-beta-2002) 46. [Your First Mission Demo](https://twhl.info/forums.php?thread=3925) 47. [Zombieland v1.1](https://www.moddb.com/mods/zombieland) ### Just mappacks 1. [AvsM 1 v1.2](https://www.moddb.com/mods/avsm-1-2003-version-re-release/) 2. [Cook The Headcrab Episodes 1 & 2](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-series) 3. [Cook The Headcrab Episode 3](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-3) 4. [Cook The Headcrab Episode 4](https://www.moddb.com/games/half-life/addons/cook-the-headcrab-episode-4) 5. **[DAV HL Pack 1](https://www.moddb.com/games/half-life/addons/dav-level-pack-1-hlarches-and-hlattack)** 6. [Discoman v2.2](https://www.fileplanet.com/117675/110000/fileinfo/Discoman-Mod) 7. [Dramatic Measures( Surprise! )](https://twhl.info/vault.php?map=5624) 8. **[Funny Map Pack 1](https://www.mediafire.com/file/4iis8uhfj1ct8sa/Funny_Map_Pack_1_by_Richman.zip)** 9. **[Half-Life Shorts](https://twhl.info/vault.php?map=2845)** 10. [Half-Starwars](https://www.pcosmos.ca/mods-hl/downloads/?mod=hlstarwars) 11. [Japanese Episodes](https://www.runthinkshootlive.com/posts/japanese-episodes/) 12. [K7 Trouble](https://www.moddb.com/games/half-life/addons/k7-trouble) (Remove string **secure 1** from **liblist.gam**) 13. [Night Shift Beta](https://www.runthinkshootlive.com/posts/night-shift/) 14. [Pillars of Pain: A Buddy & Kona Saga( PoP-BaKs )](https://www.runthinkshootlive.com/posts/pillars-of-pain/) 15. [Star Wars Half-Life](https://twhl.info/vault.php?map=274) (second map running via console using **map swpart2** cmd) 16. [The Crabulator v0.1.9.6.1](https://www.fileplanet.com/148679/140000/fileinfo/Half-Life---The-Crabulator-v0.1.9.6.1) (Rename mod folder to **TheCrabulator**) 17. [The Mansion](https://twhl.info/competitions.php?results=14) by *Unbreakable* 18. [TS_MF](https://www.runthinkshootlive.com/posts/ts_mf/) 19. [TWHL Cubicles](https://twhl.info/vault.php?map=5499) 20. **[Valve ERC Contest #1 - The martyred pop machine](https://www.runthinkshootlive.com/posts/contest-1-maps/)** 21. **[Valve ERC Contest #2 - Best train-ride sequence](https://www.runthinkshootlive.com/posts/contest-2-maps/)** 22. [VOLCAN Beta 1.3](https://www.moddb.com/games/half-life/addons/volcan-beta13) 23. [Woodpigeon's Map Pack](https://www.fileplanet.com/63729/60000/fileinfo/Woodpigeon's-Map-Pack) ### Singleplayer mods or mappacks with a little incompatibilities 1. [Alternate Path](https://www.runthinkshootlive.com/posts/alternate-path/) (there is an issue with deep sticking in elevator, when you return from map **out3** to map **out2**, so you'll be forced to use **noclip** as solution; also you need to set **sv_validate_changelevel** to **0**, because of unstable level transition between map outside and map **out2**) 2. [Final Run](https://www.runthinkshootlive.com/posts/final-run/) 3. [Hard](https://www.runthinkshootlive.com/posts/hard/) (you can get stuck inside boxes in a moving truck on the second map of the mod - type **restart** command in the console to fix your position when the next map is loaded and truck is stopped) 4. [Pulse Episode One Beta 1](https://www.moddb.com/mods/pulse), [Terrorist Attack](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) (maps of these mods have a "leak" bug, it causes level change problems with incorrect transferring entities between maps, disabled lighting and potential significant performance drops after passing of several maps) 5. [The Hill](https://www.runthinkshootlive.com/posts/the-hill/) (there is a mapper's flaw in changelevel settings between maps **thehill & thesequel**, it can be corrected only manually by editing of entpatches for these maps - you need to rename **"bottom rung"** value to **"bottomrung"**; after editing you new to start new game) 6. [Threatening Skies](https://twhl.info/vault.php?map=3372) *Pre-Demo* (aka *Half-Life 1.5*; you can get stuck in a roof of a train on level change between maps **game010c** and **game010d**; use **noclip** or **restart** command to fix this) ### Singleplayer mods which has custom gamedll with minor changes and fully playable with vanilla Half-Life libraries 1. [Citizen Arms Demo 2](https://www.moddb.com/mods/citizen-arms) (there are few inner bugs in the mod, but it still playable) 2. [DALEK unbidden](https://www.moddb.com/mods/dalek-unbidden) 3. [Fight for Life](https://www.moddb.com/mods/fight-for-life) 4. [Half-Life Baby v1.4](https://www.moddb.com/games/half-life/addons/half-life-baby) (it's an unfinished but playable mod; after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\hlbaby\dlls\hl.dll"** for **gamedll "dlls\hl.dll"**, otherwise you'll not be able to start a game) 5. **[Lost in Black Mesa(first version without HLFX)](https://www.moddb.com/mods/hlfx-lost-in-black-mesa/downloads/lost-in-black-mesa-simple-version)** 6. [Soldier](https://www.moddb.com/mods/half-life-soldier) 7. [Solo Operations](https://www.moddb.com/mods/solo-operations) 8. [The Blood v1.1](https://hl.loess.ru/?mods=&search=The+Blood) (there are some inner bugs in the mod, but they don't interfere with a game progress) 9. [The Escape](https://www.moddb.com/mods/neophus) (there is a couple of strange glitches on a map **evasion7**, but they are not interrupting a gameplay, just don't forget to download and install all of presented fixes for the mod) 10. [Wilson Chronicles: The Unfinished Edition](https://www.gamewatcher.com/mods/half-life-mod/half-life-wilson-chronicles) ### Singleplayer mods with one map or single maps 1. [3rd505th](https://hl.loess.ru/?mod=5) 2. [5 More Ways to Die](https://twhl.info/vault.php?map=3575) 3. [5 Ways to Die](https://twhl.info/vault.php?map=2984) 4. [5 Worse Ways to Die](https://twhl.info/vault.php?map=4029) 5. **[A Bad Day](https://twhl.info/competitions.php?results=6)** 6. [A Disgruntled Christmas](https://www.fileplanet.com/7882/0/fileinfo/A-Disgruntled-Christmas) 7. [Accidental Life Demo](https://www.runthinkshootlive.com/posts/accidental-life/) 8. **[Aftermath](https://www.fileplanet.com/112021/110000/fileinfo/Aftermath)** 9. [Ali Meyer](https://gamebanana.com/maps/178855) 10. [Alien](https://hl.loess.ru/?mods=&search=alien) (map by *Nicolas Gadenne*) 11. [Alien Blast](https://www.fileplanet.com/136194/130000/fileinfo/Alien-Blast) 12. [Alien Survival](https://www.moddb.com/games/half-life/addons/alien-survival-map) 13. [American Training Facility](https://gamebanana.com/maps/146598) 14. [An Escape](https://gamebanana.com/maps/167506) 15. [Area Assault](https://www.fileplanet.com/7462/0/fileinfo/Area-Assault) 16. [Assassination](https://www.runthinkshootlive.com/posts/assassination/) 17. [Atom's Mini Compo by Tetsu0](https://twhl.info/vault.php?map=5983) 18. [Atom's Mini Compo by zeeba-G](https://twhl.info/vault.php?map=5985) 19. [B.O.G aka Black.Orange.Grenade](https://twhl.info/vault.php?map=3411) (you need to have additional textures to play this map properly - **opfor.wad** & **tfc.wad**; also game crashes in the final because of inner mapping flaw ) 20. [Barney's Dream Demo( B-dream Beta )](https://www.fileplanet.com/112446/110000/fileinfo/B-dream-Beta) 21. [Barrel of Grunts](https://twhl.info/vault.php?map=3390) 22. BDMAP1 (MoP1.bsp - map by unknown author) 23. [Beach Party](https://twhl.info/vault.php?map=5572) 24. [Beastie](https://www.runthinkshootlive.com/posts/beastie/) 25. **[Black Mesa South](https://twhl.info/vault.php?map=5309)** (there are few minor glitches, but they are inner flaws of the map and don't interfere with completing a game) 26. [Black Mesa Storage Facility - Bay A2](https://www.fileplanet.com/7479/0/fileinfo/Black-Mesa-Storage-Facility---Bay-A2) 27. [Black Mesa Xmas(A Black Mesa Christmas)](https://twhl.info/vault.php?map=4918) 28. Blame the Scientists (map by Incy247) 29. [Blood and Guts](https://www.runthinkshootlive.com/posts/blood-and-guts/) 30. **[Blood1](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)** 31. [Bhop](https://twhl.info/vault.php?map=5065) 32. Bow (map by Daniel Will) 33. **[Breakout](https://www.runthinkshootlive.com/posts/breakout-by-benny-blanco/)** (map for *Single Mapping Competition* by *BennyBlanco*) 34. [Breakout](https://hl.loess.ru/?mods=&search=Breakout) (map by *Mediocre MapGuy*) 35. Bridge (map by Monkey) 36. [Bullsuid Will Survive](https://www.moddb.com/games/half-life/addons/bullsuid-will-survive) 37. [C2](https://www.runthinkshootlive.com/posts/c2/) 38. [C3](https://www.runthinkshootlive.com/posts/c3/) 39. [C5](https://www.runthinkshootlive.com/posts/c5/) 40. **[Caged](https://web.archive.org/web/20081226051021/http://www.caylegeorge.com/ld_history/cg_maps.htm)** 41. [Camera Puzzle](https://twhl.info/vault.php?map=740) (this map perfectly demonstrates an improved feature of Xash3D Engine - game is correctly saving and restoring 3rd person view for the player after loading a previously saved game) 42. [CataXen](https://twhl.info/vault.php?map=3429) 43. [Cause of Death](https://twhl.info/competitions.php?results=7) 44. [Challenge](https://web.archive.org/web/20231010044342/http://snarkpit.net/index.php?s=maps&map=3266) (map by *tnkqwe*) 45. [CHALLENGE](https://twhl.info/vault.php?map=2229) aka Can You Live (map by *killer487554*; remove spaces in the map name before you play) 46. [Choices](https://www.runthinkshootlive.com/posts/choices/) 47. [Chuck - Texas Rangers](https://twhl.info/vault.php?map=4253) 48. Cleaner's Adventures Begin Demo (map by InvisibleBullet) 49. [Coach](https://www.runthinkshootlive.com/posts/coach/) 50. [Cook The Headcrab](https://www.moddb.com/games/half-life/addons/cook-the-headcrab) 51. [Core01](https://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design competition* by *Flash*) 52. [Crawler](https://www.runthinkshootlive.com/posts/crawler/) 53. [Crysis 3](https://hl.loess.ru/?mod=215) (map for *Single Mapping Competition* by *Raid*) 54. [CUBE](https://twhl.info/competitions.php?results=7) (map by *Jobabob*) 55. [danger1](https://twhl.info/vault.php?map=2302) 56. [Data-base](https://www.runthinkshootlive.com/posts/database/) 57. [de_dust2_azabetfeN](https://csm.dev/threads/half-strike-rezultaty.36394/) (remove **cl_dlls** & **dlls** folders from inside of mod's directory before you start the game) 58. [De-railed](https://twhl.info/competitions.php?results=7) 59. [Dead Shift Beta](https://www.moddb.com/mods/dead-shift) - [Demo 1](https://www.gamewatcher.com/mods/half-life-mod/dead-shift-1-0-beta) & [Demo 2](https://web.archive.org/web/20151030005310/http://www.gamefront.com/files/13532512) 60. [Deep](https://twhl.info/vault.php?map=1669) 61. [Desert Attack](https://fyzzer.narod.ru/index.html) 62. [Desert Combat Demo](https://www.fileplanet.com/190487/190000/fileinfo/Half-Life---Desert-Combat-Mod) (despite a big file size there's only one small unfinished map) 63. [Desert Strike](https://hl.loess.ru/?mod=267) 64. [Devil Mesa](https://hosting.cecak.cz/forum-modifikace/hl1/index.php?text=mod&modifikace=dm) 65. [Disco Party v1.1](https://twhl.info/vault.php?map=1004) 66. **[dissolution](https://twhl.info/vault.php?map=5494)** (you need to have additional textures to play this map properly - **nw.wad** from [Nightwatch Texture Pack](https://www.moddb.com/games/half-life/addons/nightwatch-texture-pack) and **decals.wad** from Opposing Force) 67. [Doomed Demo](https://web.archive.org/web/20231010122104/http://www.snarkpit.net/index.php?s=maps&map=3351) 68. [Dream1_ver3](https://twhl.info/vault.php?map=579), [Dream1_ver2](https://twhl.info/vault.php?map=371), [Dream1](https://twhl.info/vault.php?map=359) 69. [Dressed to Kill](https://www.fileplanet.com/48578/40000/fileinfo/Dressed-to-kill) 70. [Dying at Sea( Signs )](https://twhl.info/vault.php?map=5379) 71. [eif coloseum](https://www.runthinkshootlive.com/posts/eif-coloseum/) 72. [eif_room](https://www.runthinkshootlive.com/posts/eif-room/) 73. [eif_school](https://www.runthinkshootlive.com/posts/eif-school/) 74. [Emissary](https://twhl.info/competitions.php?results=15) 75. **[En Route 66](https://www.moddb.com/mods/en-route-66)** 76. [Enclosed Space](https://twhl.info/vault.php?map=4496) 77. **[Endlevel Boss](https://twhl.info/vault.php?map=786)** 78. [ES](https://csm.dev/threads/hl-smc-es.29017/) (map for *Single Mapping Competition* by *Flash*) 79. [escape](https://twhl.info/vault.php?map=3632) by *killer1102* (there is a potential bug with a scientist, who should open a door for you, but it's an inner scripting problem of the map) 80. [Escape from Black Mesa v1.44](https://twhl.info/vault.php?map=) (map from *TWHL* by *Satchmo*) (wrong link!) 81. [Escape Off](https://hl.loess.ru/?mod=324) (a part of this map was used later in *Friendship 2.0* mod) 82. [Evasion](https://www.fileplanet.com/53430/50000/fileinfo/Half-life-:-Evasion) 83. [Evil Space](https://www.runthinkshootlive.com/posts/evil-space/) 84. **[Experimental Problems](https://www.runthinkshootlive.com/posts/experimental-problems/)** 85. [Extinct Lifeform Hunt](https://www.fileplanet.com/13501/10000/fileinfo/Extinct-Lifeform-Hunt) 86. [Facility](https://twhl.info/vault.php?map=5482) 87. [Facility Escape](https://twhl.info/vault.php?map=3673) 88. [Fallout](https://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map by *simb*) (link dead!) 89. [Final Assault](https://twhl.info/vault.php?map=4500) 90. [Flat](https://hl.loess.ru/?mods=&search=Flat) 91. [Freeman's Allegiance](https://www.runthinkshootlive.com/posts/freemans-allegiance/) 92. [Freeman's Escape](https://www.runthinkshootlive.com/posts/freemans-escape/) (map by *Dave Crabb*) 93. [Freeman's Suicide( Kill Yourself )](https://www.runthinkshootlive.com/posts/kill-yourself/) 94. [Funhouse](https://twhl.info/competitions.php?results=11) 95. [Func_breakable - The Invasion](https://hl.loess.ru/?mod=384) (map from *TWHL* by *Archie* aka *The Hunter*) 96. [Genetic Research Facility](https://twhl.info/vault.php?map=4775) 97. [Gladiator](https://csm.dev/threads/hl-gladiator.36140/) 98. [Go to Xen Awalk(Xen Walk/SP-Offyxen)](https://www.moddb.com/mods/go-to-xen-awalk-half-life-map) 99. [goldsource](https://twhl.info/vault.php?map=5737) (link dead!) 100. [Govnomod](https://half-life.ru/forum/showthread.php?threadid=12118) ([DevTest Demo](http://half-life.ru/forum/attachment.php?postid=226623) was tested; for the proper installation you should have Counter-Strike mod preliminarily installed in your main game folder; demo map text is on russian) (links dead!) 101. [Govnomod: Mysterious Force](http://forums.playground.ru/half-life/g_vnomod-640221/) 102. [GruntMatch](https://www.runthinkshootlive.com/posts/grunt-match/) 103. [Grunts Domain( archiveSP01 )](https://www.runthinkshootlive.com/posts/grunts-domain/) 104. [Gunship v1](https://www.runthinkshootlive.com/posts/gunship/) 105. [Half-Life 1: Traptown E3 2003](https://gamebanana.com/maps/168032) 106. [Half-Life 2 Tech Demo Parody( Half-Life 2 Physics Test Level )](https://www.fileplanet.com/124837/120000/fileinfo/Half-Life-2-Techdemo-HL1-Map-v.2) 107. Hard Map 2 (map from TWHL by Custom) 108. [Hard Way](https://hl.loess.ru/?mod=480) 109. [Headcrab Revenge](https://twhl.info/vault.php?map=3519) 110. [Headcrab-BOSS](https://www.moddb.com/games/half-life/downloads/headcrab-boss) 111. [HL Dance( Half-Dance )](https://twhl.info/vault.php?map=2991) 112. [hl_egzekucja](https://gamebanana.com/maps/50132) 113. **[HLywood](https://twhl.info/vault.php?map=455)** 114. **[Hnaii - Office Komplex](https://www.fileplanet.com/113757/110000/fileinfo/Hnaii---Office-Komplex)** 115. [Hospital](https://gamebanana.com/maps/167446) (you need to edit **liblist.gam** file in the mod's folder - delete *gamedll & type* strings from it before you start to play) 116. [Hostage](https://www.runthinkshootlive.com/posts/hostage-2/) 117. [House](https://www.artpeter.net/Data/HalfLife/Hl.php) 118. [Impulse 101 Fun - The Train](https://web.archive.org/web/20070305075816/http://www.twhl.co.za/mapvault/2817.zip) (map from *TWHL* by *Archie* aka *The Hunter*) 119. [In America](https://web.archive.org/web/20170221213409/http://www.lambda-force.org/load/half_life/karty/half_life_in_america/18-1-0-476) 120. [In the Kitchen](https://twhl.info/vault.php?map=4314) 121. [Infiltration](https://www.fileplanet.com/7467/0/fileinfo/Infiltration) 122. [Interactivity & Lots of Entities](https://twhl.info/vault.php?map=5744) (link dead!) 123. [Interior](https://www.runthinkshootlive.com/posts/interior/) 124. [Into the Frying Pan](https://www.runthinkshootlive.com/posts/into-the-frying-pan/) 125. **[Island Bombing](https://twhl.info/competitions.php?results=13)** 126. **[Ivy](https://twhl.info/vault.php?map=4869)** 127. [Jump Program](https://hl.loess.ru/?mod=575) 128. [Killer on the Run](https://www.runthinkshootlive.com/posts/killer-on-the-run/) 129. [Killing House for Half-Life](https://www.runthinkshootlive.com/posts/killing-house/) 130. [Kosovo 2000](https://www.moddb.com/games/half-life/addons/kosovo-2000) 131. [Kosovo II – The Second Day](htstp://www.moddb.com/games/half-life/addons/kosovo-2) 132. [KotiteolliSuus](https://twhl.info/vault.php?map=533) 133. [Kyo Half-Strike](https://csm.dev/threads/half-strike-rezultaty.36394/) (there are few error messages at start, which can be safely skipped; also the code in the beginning is 3141) 134. **[l33t Test 1: Conclusive Analysis](https://twhl.info/vault.php?map=3023)** 135. [Lab](https://twhl.info/vault.php?map=1797) 136. **[Lambda Station](https://www.runthinkshootlive.com/posts/lambda-station/)** 137. [LC](https://www.runthinkshootlive.com/posts/lc-by-sluxe/) (map for *Fast Level Design* competition by *Slux*) 138. **[LDSF( Laser Deployed Security Force )](https://twhl.info/competitions.php?results=4)** 139. [Locked Up](https://www.fileplanet.com/8842/0/fileinfo/Locked-Up) 140. [Looping Stage](https://web.archive.org/web/20041104030218/http://cariad.co.za/twhl/mapvault_map.php?id=1250) 141. [Lord's Lair](https://www.fileplanet.com/7471/0/fileinfo/Lords-Lair) 142. [Losspower](https://twhl.info/vault.php?map=3919) 143. **[Lost-World](https://gamebanana.com/maps/156214)** 144. [Lounge](https://www.fileplanet.com/7461/0/fileinfo/The-Lounge) 145. Matrixroom v0.7 (map by Joe Hunter) 146. [MatrixTrainstation](https://twhl.info/vault.php?map=2531) 147. **[Maze01a](https://web.archive.org/web/20110512012317/http://www.richmans-maps.ch.vu/)** 148. [Meat, Blood, Gun](https://www.moddb.com/games/half-life/addons/meat-blood-gun) (there are 2 maps, but only first map is playable, in fact) 149. [Merry Christmas to hlife_hotdog aka *Happy Holidays*](https://twhl.info/vault.php?map=5717) (to play the right map, enter map **happyholidays** into console, or edit *startmap* parameter in **liblist.gam** file inside mod's folder) 150. Metro (map by unknown author) 151. Monster Arena v1 & Monster Arena v2 (maps by unknown author) 152. Monster Shoot (map by unknown author) 153. [Museum Lockdown](https://twhl.info/vault.php?map=2211) 154. [My Backyard](https://twhl.info/vault.php?map=1796) 155. [My Black Mesa](https://www.runthinkshootlive.com/posts/my-black-mesa/) (map for *Single Mapping Competition* by *Zanzer*) 156. **[Nameless](https://www.runthinkshootlive.com/posts/nameless/)** (map for *Single Mapping Competition* by *AGRESSOR*) 157. **[Need for Energy(SP-Energy)](https://scrama.3dn.ru/load/2-1-0-2)** 158. **[Nightmare: A horror map](https://twhl.info/vault.php?map=2106)** 159. [Nightshift_FsC](https://twhl.info/vault.php?map=2561) 160. [No Chance](https://gamebanana.com/maps/160536) 161. [No Regret](https://www.runthinkshootlive.com/posts/no-regret/) 162. [Nuclear Aftermath](https://web.archive.org/web/20170708121214/http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html) (map **48h_map1_pre** by *Bluthund*) (download link dead!) 163. Nuclear Plant (map by SaCo) 164. **[Nuke](https://web.archive.org/web/20170708121214/http://www.thewall.de/forum/thread/hl1-sp-48h-mapping-contest/64975.4.html)** (map **tmdnuke** by *the-middleman*) (download link dead!) 165. [Observatory](https://www.runthinkshootlive.com/posts/observatory/) 166. [Ominous Reality: Part 1](https://twhl.info/vault.php?map=1328) 167. [Operation Rainbow](https://twhl.info/vault.php?map=3068) 168. [Operation Randomosity](https://www.moddb.com/games/half-life/addons/operation-randomosity) 169. **[Orb](https://twhl.info/competitions.php?results=20)** 170. [Osprey Chopper Competition](https://twhl.info/competitions.php?results=1) (map by *Andy*) 171. [Otage Beta v0.05](https://hl.loess.ru/?mods=&search=Otage) 172. [Outpost](https://www.fileplanet.com/81860/80000/fileinfo/Outpost) 173. [Oxidum](https://gamebanana.com/maps/162722) (the map is unfinished and after a dead-end there is another interesting place that you can reach only by using of **noclip**) 174. **[Pac-Man](https://twhl.info/competitions.php?results=29)** 175. **[Parallax Beta](https://www.runthinkshootlive.com/posts/parallax/)** (there are 2 maps, but only first map is playable, in fact) 176. [Postal](https://www.runthinkshootlive.com/posts/postal/) 177. [Problems in Building 2](https://gamebanana.com/maps/166032) 178. [Pulse: Demo #1](https://www.moddb.com/mods/pulse/downloads/pulse-demo-1) 179. [Quake](https://www.runthinkshootlive.com/posts/quake/) 180. [Quilted Thought Organ](https://www.runthinkshootlive.com/posts/quilited-thought-organ/) 181. **[Radix](https://unquenque.com/radix.html)** (this map is also a part of *Project Quantum Leap* mod) 182. [Rat Hunt: Quarters](https://twhl.info/vault.php?map=5990) (there is only 1 little issue - rats counter messages do not displayed until all rats are eliminated) 183. [Remember All](https://csm.dev/threads/hl-smc-remember_all.29029/) 184. [Rescue](https://hl.loess.ru/?mods=&search=Rescue) by *SaCo* (there is an inner bug - once you have activated a scientist in the end of the map, you should run quickly from him to the door with a scanner, otherwise he gets stuck into you; so you have to kill all enemies before you activate him) 185. [Rescue 2](https://www.moddb.com/games/half-life/addons/rescue-2) (map by *SaCo*) 186. [Revamp](https://www.runthinkshootlive.com/posts/revamp/) 187. [Rogat](https://www.runthinkshootlive.com/posts/rogat/) 188. **[Rooms](https://twhl.info/vault.php?map=24)** (map by *ghost2*) 189. **[Ruled by Insanity](https://twhl.info/vault.php?map=3580)** 190. **[Rube Goldberg](https://twhl.info/vault.php?map=5014)** aka Entity Challenge 2(map by *Captain Terror*) 191. [Rube Goldberg Machine](https://twhl.info/vault.php?map=5006) (map by *TJB*) 192. [Rum](https://www.runthinkshootlive.com/posts/rum/) 193. [Rumble](https://hl.loess.ru/?mods=&search=Rumble) 194. [Run, Run, Run!](https://www.moddb.com/games/half-life/addons/run-run-run) 195. [Runaway](https://hl.loess.ru/?mods=&search=Runaway) 196. [Runder](https://www.runthinkshootlive.com/posts/runder/) 197. S1_INFIL, aka Infiltration (old unfinished map by *Silencer[=S7=]*) 198. [s_3_LumbdaCore](https://hlfx.ru/forum/showthread.php?threadid=2433) (map for *Fast Level Design* competition by *Sania3*) 199. [Saving Santa](https://twhl.info/vault.php?map=5726) ( Secret Santa 2011 for *Rimrook* by *Urby* ) 200. [sand_01](https://twhl.info/vault.php?map=3166) 201. [SciMaker 1.1 (With Kill Func)](https://twhl.info/vault.php?map=1845) 202. [Scientist Killing](https://twhl.info/vault.php?map=1969) 203. [Scientists Hideout](https://web.archive.org/web/20190622004216/http://www.isolated-design.de:80/half-life-mods/thewall-48h-contest/) (map **48h_m01** by *ToTac*) (download link dead!) 204. [Scramble](https://www.moddb.com/mods/scramble) 205. Second (map by unknown author) 206. [Seek and Destroy](https://www.fileplanet.com/13006/10000/fileinfo/Seek-and-Destroy) 207. **[SelfKill](https://www.moddb.com/games/half-life/addons/selfkill)** 208. [SEMTEX](https://twhl.info/competitions.php?results=7) (map by *kol*) 209. [Sepulcher](https://www.fileplanet.com/7478/0/fileinfo/Sepulcher) 210. [Shootout Alpha 3](https://www.fileplanet.com/111687/110000/fileinfo/Shootout-ALPHA-3) 211. [Small Battle](https://twhl.info/vault.php?map=2337) 212. [Small Battle 2](https://twhl.info/vault.php?map=2393) 213. [Smash Half-Life](https://www.runthinkshootlive.com/posts/smash/) 214. [Snatch](https://jqbros.3dn.ru/load/1-1-0-9) 215. [Soft Boiled](https://www.fileplanet.com/7475/0/fileinfo/Soft-Boiled) 216. **[Someplace Else](https://hylobatidae.org/minerva/parallax/someplace-else.html)** (this map is also a part of *Project Quantum Leap* mod) 217. [Somewhere( Kasperg: Unique Map )](https://twhl.info/competitions.php?results=13) 218. [Southeastern Lan Party](https://www.fileplanet.com/7476/0/fileinfo/Southeastern-Lan-Party) 219. **[sp_valley](https://gamebanana.com/maps/61099)** 220. **[Space_Lasercore](https://twhl.info/vault.php?map=1938)** 221. [Spearhead](https://twhl.info/vault.php?map=1018) 222. [Spellbinder - The Summoning Tower v1.2](https://twhl.info/vault.php?map=1502) 223. [Sproutch Mod!!!](https://www.runthinkshootlive.com/posts/sproutch/) (another variation of this map is [Bullsquids Pet](https://www.fileplanet.com/126221/120000/fileinfo/Bullsquids-Pet)) 224. [Star](https://www.ceskemody.cz/mapy.php?lng=1&clanek=36&razeni_hra=1&pn=star) (there are 2 maps, but only first map is playable, in fact; also changelevel is not working properly there, but it's an inner bug of the mod) 225. [Stacja](https://www.runthinkshootlive.com/posts/stacja/) 226. [Station17](https://gamebanana.com/maps/161063) 227. [Storage Facility](https://hl.loess.ru/?mods=&search=Storage+Facility) (map by *T.J Brosnan*) 228. [Strange Findings Part 1](https://twhl.info/vault.php?map=3617) 229. [Subway - The Longest Fall](https://twhl.info/vault.php?map=3926) (you need to have additional textures to play this map properly: **opfor.wad** from *Opposing Force*, **specialists.wad** from *The Specialists*, **tfc2.wad** from *Team Fortress Classic* and **wanted.wad** from *Wanted!*) 230. **[Surfacerun](https://gamebanana.com/maps/156304)** 231. [SwirusMap (call it "Dream")](https://twhl.info/vault.php?map=1588) 232. [Target Practice](https://twhl.info/vault.php?map=5460) 233. **[Technical Problems](https://www.runthinkshootlive.com/posts/technical-problems/)** 234. [Teh Hammre](https://www.runthinkshootlive.com/posts/teh-hammre/) 235. **[Test Lab 16](https://twhl.info/competitions.php?results=17)** 236. [Test Map](https://www.moddb.com/games/half-life/addons/test-map-update) (map by *NinjaBlack1337* aka *Guillermo_SPY*) 237. [Test of Destruction](https://twhl.info/vault.php?map=3892) 238. [The Abandon](http://half-life.ru/forum/showthread.php?threadid=14267) (link dead!) 239. [The Arena - Room 1](https://twhl.info/vault.php?map=3523) 240. [The Crab Lab](https://twhl.info/vault.php?map=4105) 241. [The Cupboard of Doom](https://www.moddb.com/mods/the-cupboard-of-doom) 242. [The Desert of Doom](https://hl.loess.ru/?mod=265) 243. **[The Gloom](https://www.moddb.com/mods/the-gloom)** 244. [The History Can Be Changed](https://www.runthinkshootlive.com/posts/history-can-be-changed/) (you need to have additional textures to play this map properly - **opfor.wad** from *Opposing Force*) 245. [The House Beta 0.1](https://gamebanana.com/maps/138258) 246. [The Innocent Eternity](https://www.fileplanet.com/10027/10000/fileinfo/The-Innocent-Eternity) (there are 2 maps, but only first map is playable, in fact) 247. [The Interview, Stage 1](https://www.fileplanet.com/7468/0/fileinfo/The-Interview,-stage-1) 248. [The Last Survivor](https://www.moddb.com/games/half-life/addons/the-last-survivor) (there is an issue with saved games in this map - they don't work properly after loading, but it's an inner flaw of the map, not an engine's bug) 249. [The Leech Pits](https://www.runthinkshootlive.com/posts/the-leech-pit/) 250. [The Origin of Symmetry](https://gamebanana.com/maps/56796) 251. **[The Plague](https://scrama.3dn.ru/load/2-1-0-31)** 252. **[The Playtest](https://www.runthinkshootlive.com/posts/the-playtest/)** 253. [The Poseidon Incident( USCM: Infestation Demo )](https://www.runthinkshootlive.com/posts/the-poseidon-incident/) 254. **[The Run](https://twhl.info/vault.php?map=4890)** 255. [The Secret Mission](https://www.geocities.ws/rawrguilds/maps.html) 256. [The Silo Station](https://www.fileplanet.com/7480/0/fileinfo/The-Silo-Station) 257. [The Stupendous Quest of the Annoying Microwave](https://twhl.info/vault.php?map=5937) 258. [The Swimmingpool](https://www.fileplanet.com/54276/50000/fileinfo/The-swimmingpool) (there is an inner bug of unmovable trashcan, so you have to use **noclip** on your way back) 259. [The Transporter( Xen Transportation )](https://twhl.info/vault.php?map=3408) 260. The Trap (map by Keks) 261. [The_Work_Area v1.0](https://twhl.info/vault.php?map=2420) 262. [Torching the Light](https://www.moddb.com/mods/torching-the-light) 263. [Torture a Friendly NPC](https://twhl.info/vault.php?map=3672) 264. [Torture That Alien](https://twhl.info/vault.php?map=3248) 265. [Total Evasion](https://www.moddb.com/mods/total-evasion) 266. [Tower](https://twhl.info/vault.php?map=250) (Required Spirit of Half-Life for item_suit) 267. [TriggerHappy](https://www.fileplanet.com/8929/0/fileinfo/TriggerHappy) 268. [TriggerHappy2](https://www.fileplanet.com/13503/10000/fileinfo/TriggerHappy2) 269. [TriggerHappy2.5](https://www.moddb.com/games/half-life/addons/trigger-happy-v25) 270. [Trouble](https://gamebanana.com/maps/166033) 271. [Tunnels](https://twhl.info/vault.php?map=5350) 272. **[Twisted Hazard Course](https://www.fileplanet.com/52919/50000/fileinfo/Twisted-Hazard-Course)** 273. [Two Towers](https://twhl.info/vault.php?map=2327) 274. [Underground Facility](https://twhl.info/vault.php?map=1762) 275. [Unnamed](https://twhl.info/competitions.php?results=20) by *rowleybob* 276. [Urb's Challenge](https://twhl.info/vault.php?map=2677) 277. [USAF](https://hl.loess.ru/?mod=1085) 278. **[USS Gaspra](http://biomech.itstudios.ru/gallery/uss_gaspra.htm)** (download link dead!) 279. [USSL Blue Mesa](https://web.archive.org/web/20041109135949/http://cariad.co.za/twhl/mapvault_map.php?id=1402) (remove space from the end of the map's name before you play) 280. [Valve Pressure Beta v2](https://www.fileplanet.com/87528/80000/fileinfo/Valve-Pressure---Single-Player-Map) 281. [Vassy Compo](https://twhl.info/vault.php?map=771) 282. [Vilcabamba](https://www.moddb.com/games/half-life/addons/vilcabamba) (this map is also used as a first map in *Idol Hunt* mod) 283. [Vital Signs](https://twhl.info/vault.php?map=3908) 284. [Wake Up and Stay Alive](http://half-life.ru/forum/showthread.php?threadid=11956) (link dead!) 285. [war_coop1](https://twhl.info/vault.php?map=4991) 286. [Warehouse Firefight](https://twhl.info/vault.php?map=2279) 287. [We Got Work to Do!](https://twhl.info/vault.php?map=3107) 288. [Weird Dreams](https://twhl.info/vault.php?map=1473) (Required Spirit of Half-Life for item_suit) 289. [When the Army Came to the Office](https://www.runthinkshootlive.com/posts/when-the-army-came-to-the-office/) 290. [WTF](https://www.runthinkshootlive.com/posts/wtf/) 291. [WWE Bullsquid Royal Rumble](https://www.moddb.com/games/half-life/addons/wwe-bullsquid-royal-rumble) 292. **[Wybuchowka](https://www.runthinkshootlive.com/posts/wybuchowka/)** 293. **[X-treme Violence](https://www.moddb.com/mods/x-treme-violence)** 294. [XargoL's Entry for Vassy's Compo](https://twhl.info/vault.php?map=769) 295. [Xen Again](https://www.fileplanet.com/9132/0/fileinfo/XEN-AGAIN) 296. [Xen World](https://web.archive.org/web/20150926134355/http://www.lambda-force.org/load/0-0-0-481-20) (download link dead!) 297. [XUnil](https://www.fileplanet.com/7883/0/fileinfo/XUNIL) 298. [Zeeba-G's TWHL Compo 26 Entry](https://twhl.info/competitions.php?results=26) 299. [Zombies!](https://gamebanana.com/maps/160812) 300. [Zone: Map 1 (Intro)](https://www.moddb.com/mods/zone/downloads/1-map) 301. [Zone: Map 2 (Part 1)](https://www.moddb.com/mods/zone/downloads/zone-part-1) 302. [Zone: Map 3 (Part 2)](https://www.moddb.com/mods/zone/downloads/zone-map-3) ## List of mappacks for Half-Life: Blue Shift To install - place *.bsp files to **bshift/custom/maps** folder, *.wad files to **bshift/custom** and type **`map `** cmd in game. 1. [The Infinite Shift](https://www.fileplanet.com/archive/p-19156/The-Infinite-Shift) ## List of Cleaner's Adventures-based mods To run this mods on specific platforms you may be need to compile libraries from **CAd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable/tree/CAd). 1. **[Half-Life: Black Guard](https://www.moddb.com/mods/half-lifeblack-guard)** - uses dlls copied from original Cleaner's Adventures. ## List of mods which based on Spirit of Half-Life Originally this list was written by @Qwertyus3D (*Qortez*) Spirit of Half-Life - extended toolkit for HL1 mappers by *Laurie Cheers*. To run this mods on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable/tree/sohl1.2). ### Mods which based on vanilla Spirit of Half-Life 1.2 or older(Fully playable with SoHL 1.2 libraries) 1. [Accidental Assassin](https://www.moddb.com/mods/accidental-assassin) (this mod has inner bugs; type **restart** if you stuck on changelevel) 2. **[Big Scientists](https://www.moddb.com/mods/big-scientists)** 3. [Black Silla Assault DEMO v1.2](https://www.moddb.com/mods/black-silla-assault) 4. **[Blbej Den](https://www.moddb.com/mods/blbej-den)** 5. [Cold Experiment v1](https://www.moddb.com/games/half-life/addons/cold-experiment) (you will need some files from SoHL to play it properly; download SoHL 1.2, extract files and copy following folders into **coldexv1** folder: cl_dlls, dlls, models, sprites) 6. **[Dead Sector v1.0a](https://www.moddb.com/mods/dead-sector)** 7. [Death Is Dead](https://www.moddb.com/games/half-life/addons/death-is-dead) (there is a fog-related inner bug at the first map) 8. [Escape from Black Mesa Alpha](https://www.moddb.com/mods/escape-from-black-mesa) 9. [ESCAPE: Trainingroom](https://www.moddb.com/mods/half-life-escape) 10. [City Crush v0.1](http://half-life.ru/forum/showthread.php?threadid=10671) (link dead!) 11. [Invasion 105 v2.0](https://www.moddb.com/mods/half-life-invasion-105) 12. [Issues( Project Quantum Leap 2 )](https://www.moddb.com/mods/issues) 13. [Mission Impracticable 2](https://www.moddb.com/mods/mission-impracticable-2) 14. **[Prison v2.1](https://www.moddb.com/mods/half-life-prison)** 15. [Prize v1.1](https://www.moddb.com/mods/prize) 16. **[Radiation Alert: Episode 1 v1.1](https://www.moddb.com/mods/radiation-alert-episode-1)** 17. [Run From Hell v1.1b](https://www.runthinkshootlive.com/posts/run-from-hell/) (there is an inner bug with underwater crates in the map **firstmap9** - they can't be broken, so you should use **noclip** to pass through them) 18. **[Santa's Revenge](https://twhl.info/vault.php?map=4332)** (set **fps_max** to **60** to avoid an inner problem of the last map with final scripted sequence, otherwise the mod can not be finished properly) 19. [Sector 6](https://www.moddb.com/mods/sector-6/) 20. [Space Prisoner v1.1](https://www.moddb.com/games/half-life/addons/space-prisoner-v11) (after installing of the mod open **liblist.gam** or **gameinfo.txt** file in the mod's folder and correct the line **gamedll "..\prison\dlls\spirit.dll"** for **gamedll "dlls\spirit.dll"**, otherwise you'll not be able to start a game; there is also a scripting error on a third map of the mod, so you'll be forced to use **noclip** to pass around bugged place) 21. [Terrorist Attack 2](https://www.moddb.com/games/half-life/addons/terrorist-attack-2) 22. **[Timeline III: The Heart of Darkness](https://www.moddb.com/mods/timeline-series)** 23. [Underground 2 Demo](https://www.moddb.com/games/half-life/addons/underground-2-demo) ### Mods which based on modified Spirit of Half-Life 1.2 or older(Partially playable with SoHL 1.2 libraries or not playable at all) 1. [Black Death](https://www.moddb.com/mods/half-life-black-death) 2. [Borderlands v0.4](https://www.moddb.com/games/half-life/addons/borderlands-v04) 3. **[Dark Territory](https://www.moddb.com/mods/dark-territory)** (set **fps_max** to **60** to avoid an inner problem of the map **tau_jungle_02** with a scripted sequence, otherwise you can get a gamebreaking bug) 4. [Dead Way Alpha](https://csm.dev/threads/hl-dead-way-1-nostalgija.12381/) 5. [Emergency](https://www.runthinkshootlive.com/posts/half-life-emergency/) 6. **[ESCAPE (final)](https://www.moddb.com/mods/half-life-escape)** by *DMC Interactive* 7. **[ESCAPE 2](https://www.moddb.com/mods/half-life-escape)** by *DMC Interactive* 8. [Force of Evil](https://www.moddb.com/mods/force-of-evil) 9. [Friendship: Town of half-life.ru mappers v2.0](https://www.runthinkshootlive.com/posts/friendship/) (this mod has inner bugs) 10. [Malevolence v1.4 Open Source Beta](https://www.moddb.com/mods/malevolence) 11. **[Portrait of Freeman v1.1](https://www.runthinkshootlive.com/posts/portrait-of-freeman/)** 12. [Project M.L.P v1.0](https://www.moddb.com/mods/project-mlp) (Remove **halflife.wad** from mod folder) 13. [Snark Planet Demo](https://www.moddb.com/mods/snark-planet) 14. **[Survive in Catacombs](https://www.moddb.com/mods/survive-in-catacombs)** 15. **[Survive in Catacombs 2: Fear (Bloodbath)](https://www.moddb.com/mods/survive-in-catacombs-2)** 16. **[The Lost Hell](https://www.runthinkshootlive.com/posts/the-lost-hell/)** 17. **[The Trap v1.60](https://www.moddb.com/mods/the-trap) (mod by *Reaktor*)** 18. **[Ispitatel 4: Classic](https://www.moddb.com/mods/ispitatel-4-classic)** 19. [Half-Life 2: Classic Demo](https://www.moddb.com/mods/half-life-2-classic) ### Mods which based on Spirit of Half-Life 1.4/1.5/1.8(Fully playable with SoHL 1.8 libraries, partially playable with SoHL 1.2 libraries or not playable at all) 1. **[Before v1.1](https://www.moddb.com/mods/half-life-before)** 2. [Christmas Life v1.0](https://www.moddb.com/mods/christmas-life) (initial mod works properly, but additional mappacks for this mod contain few maps that have some gameplay problems under Xash3D) 3. [COLONY 42 Alpha](https://twhl.info/vault.php?map=6055) 4. [Crazy Crabs Demo 1 & 2](https://www.moddb.com/mods/mad-crabs) 5. [Far Crab Demo v2](https://www.moddb.com/mods/far-crab) 6. [Firefighter Demo v1.1](https://www.moddb.com/mods/firefighter-mod) 7. **[Halfquake 3: Sunrise](https://www.moddb.com/mods/halfquake-amen)** 8. **[Prototype 98](https://www.moddb.com/mods/prototype-98)** (set **fps_max** to **60**, otherwise you can get stuck in some places) 9. **[Reissues v1.1](https://www.moddb.com/mods/reissues)** 10. **[Santa's Revenge 2: Xmas Meltdown](https://twhl.info/vault.php?map=4931)** 11. **[Tactical Espionage Action v1.1](https://www.moddb.com/mods/tactical-espionage-action)** 12. **[TWHL Tower](https://www.moddb.com/mods/twhl-tower/downloads/twhl-tower-steam-xash3d)** ### Mods which based on Spirit of Half-Life 1.3/1.6/1.7/1.9(Fully playable with SoHL 1.9 libraries, partially playable with SoHL 1.2 libraries or not playable at all) 1. [Chaos Theory unfinished](https://hlfx.ru/forum/showthread.php?threadid=1772) 2. [Silent Zhildor Demo](https://www.runthinkshootlive.com/posts/silent-zhildor/) ## List of Opposing Force-based mods Originally this list was written by @Qwertyus3D (*Qortez*) To install mods from this category - first of all place mod folder and **gearbox** folder from *Opposing Force* beside **valve** folder. To run this mods on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable/tree/opfor). For mappacks - place *.bsp files to **gearbox/custom/maps** folder, *.wad files to **gearbox/custom** and type **`map `** cmd in game. ### The big singleplayer mods or mappacks 1. **[Aron](https://csm.dev/threads/hl-aron.37791/)** 2. **[A Soldiers Tale](https://www.moddb.com/mods/a-soldiers-tale)** 3. [Black Bag Operations](https://www.runthinkshootlive.com/posts/black-bag-operations/) 4. **[Bootleg Squadrog](https://www.moddb.com/mods/bootleg-squadrog)** (there is an inner bug - don't save/load your game at the second map of the mod, otherwise a script that giving you weapons in the armory will not work) 5. [Camp Aign](https://twhl.info/vault.php?map=621) 6. [Campaign: Part 1](https://www.runthinkshootlive.com/posts/campaign-part-1/) 7. **[Dark Operations](https://www.moddb.com/mods/op-dark-operations)** 8. [Double Helix](https://www.runthinkshootlive.com/posts/double-helix/) 9. **[Fallback, Fire, and Ice](https://www.runthinkshootlive.com/posts/fallback-fire-and-ice/)**, plus simply Fallback (initial version of the mod with 5 maps) 10. [Fallen Chopper](https://www.runthinkshootlive.com/posts/fallen-chopper/) 11. **[Focalpoint](https://www.moddb.com/mods/focalpoint)** 12. [Foothold](https://www.moddb.com/mods/foothold) 13. [Ground Zero](https://web.archive.org/web/20231010064923/http://www.snarkpit.net/index.php?s=maps&map=3423) by *Necromancer* 14. **[Ground Zero 2: Fallout](https://www.moddb.com/mods/ground-zero-2-fallout)** by *Derek 'Hellfire' McBurney* (there is a potential inner bug with unmovable barrel at the second map; you can break that barrel, but it very durable and has 9000 health points) 15. **[Intolerable Threat](https://www.moddb.com/mods/intolerable-threat)** 16. **[Little Skyscraper of Horrors](https://www.moddb.com/mods/little-sky-scraper-of-horrors)** 17. [Mechanized Death: An Army of None](https://www.moddb.com/games/half-life-opposing-force/addons/mechanized-death-army-of-none) 18. **[Military Duty v1.0.1](https://www.moddb.com/mods/military-duty)** 19. **[Nuclear Winter](https://www.moddb.com/mods/nuclear-winter-opfor)** 20. [Opposing Force Aliens Addon](https://www.fileplanet.com/82829/80000/fileinfo/Half-Life:-Opposing-Force-Aliens-Addon-1.2) 21. [Opposing Life2Life](https://www.moddb.com/games/half-life/addons/opposing-life2life-final-hd) 22. [Realms](https://web.archive.org/web/20231009194345/http://snarkpit.net/index.php?s=maps&map=3483) by *Necromancer* 23. **[Retaliation](https://www.moddb.com/mods/op-retaliation/downloads/retaliation1)** 24. **[Shepard's Adventures](https://www.moddb.com/mods/shepards-adventures)** 25. [Snowy Rock](https://www.moddb.com/games/half-life-opposing-force/addons/snow-rock) 26. [Space Disaster](https://www.runthinkshootlive.com/posts/space-disaster/) 27. **[The Alpha Unit](https://www.moddb.com/mods/na18509)** 28. **[The Evasion](https://www.moddb.com/mods/the-evasion)** 29. **[The Tower](https://www.moddb.com/mods/the-tower1)** 30. [The Xen Campaigns](https://www.moddb.com/mods/the-xen-campaigns) 31. **[Ultimate Attack](https://www.moddb.com/mods/half-life-ultimate-attack)** 32. **[Under the Blackmoon](https://www.moddb.com/mods/under-the-black-moon)** 33. [Warzone](https://www.runthinkshootlive.com/posts/warzone/) ### Unfinished mods 1. [Black Op Mission](https://www.moddb.com/mods/black-op-mission) 2. [Mission Impossible](https://www.runthinkshootlive.com/posts/mission-impossible/) (the mod is not fully finished and changelevel to the last map doesn't work) 3. [Poisonheadcrab Nightmare Mappack](https://www.moddb.com/mods/poisonheadcrab-nightmare-mappack-for-half-life) Demo + Update (there are some bugs, but they are inner bugs of the mod) 4. [Return to Black Mesa](https://web.archive.org/web/20160903151337/http://www.hl-rtbm.wbs.cz/) (download link dead!) 5. [The Bounty Hunter](https://www.moddb.com/mods/half-life-the-bounty-hunter) 6. [Xen Assault](https://www.moddb.com/mods/xen-assault) (there are inner bugs, which interfere with level change in introductury maps **p1vs1** and **xenmp**; you can just skip them and begin to play directly from the map **p1v1**) ### Singleplayer mods with one map or single maps 1. [Alternate Points](https://twhl.info/vault.php?map=4499) 2. [Battle](https://www.runthinkshootlive.com/posts/battle/) 3. [Bomb Squad](https://web.archive.org/web/20231009205618/http://snarkpit.net/index.php?s=maps&map=3471) 4. [Corruption](https://web.archive.org/web/20231009205616/http://www.snarkpit.net/index.php?s=maps&map=3154) 5. [Critical Mass](https://www.moddb.com/mods/half-life-critical-mass) [C3M1 Build 1 Demo](https://www.fileplanet.com/125746/120000/fileinfo/C3M1-Build-1) 6. [EPRST!](https://www.runthinkshootlive.com/posts/ep-rst/) 7. [Firing Range](https://www.runthinkshootlive.com/posts/firing-range/) 8. [Friendly Fire](https://www.runthinkshootlive.com/posts/friendly-fire/) 9. [Guitar Star Pre-Alpha](https://gamer-lab.com/rus/mods_goldsrc/Guitar_Star_(Prealpha)) 10. [J2000](https://www.runthinkshootlive.com/posts/j2000/) 11. [Killing House for OF](https://www.runthinkshootlive.com/posts/scientist-rescue-aka-cqb/) 12. [Klabautermann](https://www.runthinkshootlive.com/posts/klabautermann/) (though the map has some inner issues, it can be properly finished, just don't let the welder soldier die and don't press the fifth button until you mount a special gun in its' place) 13. [Little Escape](https://www.runthinkshootlive.com/posts/little-escape/) 14. [Marine Invasion: Freak-Lager](http://hl.gamebanana.com/gamefiles/2537) 15. [Madness 2](https://twhl.info/vault.php?map=1758) 16. **[Operation: Sandblast](https://unquenque.com/sandblast.html)** 17. [OpFor Postal](https://www.runthinkshootlive.com/posts/opfor-postal/) 18. [Super Nova Space Station](https://www.runthinkshootlive.com/posts/supernova-space-station/) 19. [The Red Area](https://www.runthinkshootlive.com/posts/the-red-area/) 20. [Underhalls](https://twhl.info/vault.php?map=4070) 21. [Without Doubts Beta](https://www.runthinkshootlive.com/posts/without-doubts/) 22. [X-Abaddon](https://www.moddb.com/mods/red-alert-half-life-x-pantion/addons/x-abaddon) 23. [You Die v2](https://www.runthinkshootlive.com/posts/you-die/) 24. [Zombies](https://www.runthinkshootlive.com/posts/zombies/) ## List of XashXT-based mods XashXT - SoHL-based extended toolkit for Xash3D mappers by *Unkle Mike*. To install mods from this category - first of all place mod folder beside **valve** folder. Currently, you can try to run this mods under PrimeXT. 1. **[Meanwile in Russia demo(MIR)](https://www.moddb.com/games/mir)** 2. Monorail Quest ## List of They Hunger-based mods To run this mods on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). ### The big singleplayers mods 1. **[They Hunger: Rebuild](https://www.moddb.com/mods/they-hungerrebuild)** - This mod uses new **func_vehicle** entity and our **hlsdk-portable** libraries. ### Single maps 1. [Awful changes](https://yadi.sk/d/OE6SDqbkoLy6o) by *MSteam* 2. [Dark Rock](https://yadi.sk/d/Lc4ahljfoigcQ) by *Adam 'Jeb_Radec' Brown* 3. [Hospital](https://yadi.sk/d/AAXjfvwLodkPG) by *Nod24* 4. [They Hunger: Escape](https://yadi.sk/d/f0lcjCycoT5t5) by *Gua* 5. [They Hunger: Lab of Horrors](https://yadi.sk/d/V_us_m78pFaMK) by *Jordan* 6. [They Hunger: Route666 Demo](https://yadi.sk/d/XKWCseAPohkG6) by *MadMapper* ## List of supported mods from mobile_hacks branch of HLSDK Portable by FWGS Currently, fresh Xash3D FWGS builds for Android, PS Vita and Nintendo Switch uses libraries from this branch, so you can play mods below out-of-the-box. 1. **[Afraid of Monsters](https://www.moddb.com/mods/afraid-of-monsters/downloads/afraid-of-monsters-v1)** 2. **[Big Lolly](https://www.moddb.com/mods/big-lolly)** 3. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)** 4. **[Half-Secret](https://www.moddb.com/mods/half-secret)** 5. **[Case Closed](https://www.moddb.com/mods/caseclosed)** 6. [Bloody Pizza - Vendetta](https://www.moddb.com/games/half-life/addons/bloody-pizza-vendetta) 7. [Borderlands](https://www.moddb.com/games/half-life/addons/borderlands-v04) 8. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction/downloads/half-life-induction-12)** 9. **[Redemption/Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)** 10. [Sewer beta](https://www.moddb.com/games/half-life/addons/sewer-beta) 11. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)** 12. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)** ## List of games and mods with custom gamedll For mods from this category - first of all place mod folder beside **valve** folder. 1. **[Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)** To run this mod on specific platforms you may be need to compile libraries from **redemption** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 2. **[Adrenaline Gamer](https://www.moddb.com/mods/adrenaline-gamer)** On **x86 Linux** you can use *OpenAG* client by *YaLTeR* To run this mod on specific platforms you may be need to compile libraries from **aghl** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 3. **[Afraid of Monsters](https://www.moddb.com/mods/afraid-of-monsters/downloads/afraid-of-monsters-v1)** To run this mod on specific platforms you may be need to compile libraries from **aom** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 4. **[Afraid of Monsters: Director's Cut](https://www.moddb.com/mods/afraid-of-monsters-dc)** To run this mod on specific platforms you may be need to compile libraries from **aomdc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 5. **[Azure Sheep](https://www.moddb.com/mods/azure-sheep)** To run this mod on specific platforms you may be need to compile libraries from **asheep** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 6. **[Big Lolly](https://www.moddb.com/mods/big-lolly)** To run this mod on specific platforms you may be need to compile libraries from **biglolly** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 7. **[Black OPS](https://www.moddb.com/mods/black-ops)** To run this mod on specific platforms you may be need to compile libraries from **blackops** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 8. [Bloody Pizza: Vendetta](https://www.runthinkshootlive.com/posts/bloody-pizza-vendetta/) To run this mod on specific platforms you may be need to compile libraries from **caseclosed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 9. [Borderlands](https://www.moddb.com/games/half-life/addons/borderlands-v04) To run this mod on specific platforms you may be need to compile libraries from **caseclosed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 10. **[Bubblemod](https://web.archive.org/web/20130717133158/http://www.bubblemod.org/dl_default.php)** This mod already has version for **x86 Linux**. To run this mod on specific platforms you may be need to compile libraries from **bubblemod** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 11. **[Case Closed](https://www.moddb.com/mods/caseclosed)** To run this mod on specific platforms you may be need to compile libraries from **caseclosed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 12. **[Cleaner's Adventures](https://www.moddb.com/mods/cleaners-adventures)** To run this mod on specific platforms you may be need to compile libraries from **CAd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 13. **[Cold Ice Remastered](https://www.moddb.com/mods/cold-ice-remastered)** This mod already supports x86 Linux. Support for other platforms may be available later. 14. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)** On x86 you may be need to compile client part. For other specific platforms you may be need to compile server part too. Source code: Client: https://github.com/Velaron/cs16-client/ Server: https://github.com/rehlds/ReGameDLL_CS 15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)** To run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)** To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)** Steam version already supports x86 Linux. To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)** This mod already supports x86 Linux. Support for other platforms may be available later. 19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)** To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 20. **[Gang Wars](https://www.moddb.com/mods/gangwars)** This mod already supports x86 Linux. Support for other platforms may be available later. 21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)** To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)** Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). Support for other platforms may be available later. 23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)** To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)** This mod already supports x86 Linux. Support for other platforms may be available later. 25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid* *Support temporary abandoned* To run this mod on specific platforms you may be need to compile libraries from **gravgun** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). Currently, MSVC is not supported, use MinGW if you need build for Windows. Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras). 26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)** To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)** This mod supports x86 Linux. Support for other platforms may be available later. 28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)** This mod already supports x86 Linux. To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful). 29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)** Steam version already supports x86 Linux. To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)** To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)** To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)** To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)** To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)** This mod already supports x86 Linux. Support for other platforms may be available later. 35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)** To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 36. **[Half-Secret](https://www.moddb.com/mods/half-secret)** To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)** This mod already supports x86 Linux. Support for other platforms may be available later. 38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)** To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 39. **[Paranoia](https://www.moddb.com/mods/paranoia)** *Support temporary abandoned* [Source code](https://github.com/FWGS/paranoia_toolkit). 40. **[Poke646](https://poke646.com/)** To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 41. **[Poke646: Vendetta](https://poke646.com/)** To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/) [Source code](https://github.com/SNMetamorph/PrimeXT) 43. **[Quake Remake](https://www.moddb.com/games/quake-remake)** *Support was fully abandoned due Quake Wrapper release.* [Actual source code](https://github.com/FWGS/quakeremake) 44. **[Rebellion](https://www.moddb.com/mods/rebellion1)** To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)** To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)** To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)** This mod already supports x86 Linux. Support for other platforms may be available later. 48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta) To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life) To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)** To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)** This mod already supports x86 Linux. Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available. 52. **[The Gate](https://www.moddb.com/mods/the-gate)** To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)** To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)** To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)** To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). 56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/) *Support was fully abandoned due PrimeXT development* [Source code](https://github.com/FWGS/XashXT-FWGS/) 57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)** This mod already supports x86 Linux. Support for other platforms may be available later. 58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)** To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). Don't forget to enable XENWARRIOR build option!!! 59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final) To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). ## List of Counter Strike-based mods 1. **[CSMoE](https://github.com/MoeMod/CSMoE/releases)** by *Chinese Xash3D FWGS Community* - uses custom Xash3D FWGS build. ================================================ FILE: Documentation/touch-controls.md ================================================ # Touch controls configuring ## Introduction Thanks to mittorn, we have the ability to fully customize the controls in Xash3D. The new config allows you to not only add and change control buttons, but also create custom menus. There is also a built-in visual editor available, which simplifies the customization process without the need for manual file editing. ## Editor mode usage 1. Launch Xash3D and start the game. 2. To enter the edit mode, click on the gear icon (command `touch_enableedit`). 3. In the edit mode, the grid is displayed. The number of cells can be changed with the command `touch_grid_count` (default is 50). The grid can be disabled with the command `touch_grid_enable 0`. *Touch controls layout editor* ![](images/editor.jpg) *Touch profiles window* ![](images/touch-profiles.jpg) *Touch buttons parameters window* ![](images/touch-buttons.jpg) ## Layout editor features * **Moving buttons**: click on a button, drag it to the desired location, and release. * **Resizing buttons**: place your first finger on the top left corner of the button, and use your second finger to resize it. * **Hiding/showing buttons**: select the button (it will turn red), then use the menu: * **Close**: closes the editing mode (`touch_disableedit`). * **Reset**: resets the button to its default values. * **Hide/Show**: hides or shows the button (`touch_hide ` / `touch_show `). ## Working with configuration files / console All changes made in the visual editor are automatically saved to the touch profile file. The files are located in the `touch_profiles` folder inside game directory. The file name depends on the selected profile. ### Common commands in configuration files ``` // Comment: lines starting with // are ignored. // Swipe zones for movement. Specify how far you need to swipe to speed up. // The default values ​​are set to be optimal for a small swipe to immediately walk quickly. touch_forwardzone "0.060000" ; touch_sidezone "0.060000" // Sensitivity settings (pitch - horizontal, yaw - vertical). touch_pitch ; touch_yaw // Grid settings (see above). touch_grid_count ; touch_grid_enable // Draws a border around the button. touch_set_stroke ( - red; - green; - blue; - alpha/transparency) // Show (1) or hide (0) client buttons. touch_setclientonly // Removes all buttons from the screen. touch_removeall // Shows all available buttons in the configuration file. touch_list // Saves the config after editing. You can specify a new file name. touch_config_file command.cfg ``` *The full list of buttons in the standard `touch.cfg` file, displayed in the console when using `touch_list`.* ![](images/example2.jpg) ## Adding new buttons To add a new button, use the console command: ``` touch_addbutton "digits" "touch/key_1.png" "toggle_digits" 0.340000 0.782222 0.400000 0.888889 255 255 255 100 0 ``` ### Parameters of `touch_addbutton` command | Parameter | Description | | --- | --- | | `"digits"` | Unique name of the button. | | `"touch/key_1.png"` | Path to the icon file (`.png` format). If the icon is not needed, leave `""`. | | `"toggle_digits"` | Command to execute after clicking (for example, `"buy"` for purchase). | | `0.340000` | X coordinate of the upper-left corner of the button. | | `0.782222` | Y coordinate of the upper-left corner of the button. | | `0.400000` | X coordinate of the lower-right corner of the button. | | `0.888889` | Y coordinate of the lower-right corner of the button. | | `255 255 255` | Button color in RGB format. | | `100` | Button transparency (0 - fully transparent, 255 - fully opaque). | | `0` | Flags (see next section). | ## List of flags Flags define the behavior of the button. Their values ​​are powers of two: | Flag | Value | Description | | --- | --- | --- | | `TOUCH_FL_HIDE` | 1 | Hides the button (not displayed in the game, but visible in the editor). | | `TOUCH_FL_NOEDIT` | 2 | Disables editing of the button in the editor. | | `TOUCH_FL_CLIENT` | 4 | The button is client-side (not saved in the main control file). | | `TOUCH_FL_MP` | 8 | The button is displayed only in multiplayer. | | `TOUCH_FL_SP` | 16 | The button is displayed only in singleplayer. | | `TOUCH_FL_DEF_SHOW` | 32 | The button is always displayed on startup. | | `TOUCH_FL_DEF_HIDE` | 64 | The button is always hidden on startup. | | `TOUCH_FL_DRAW_ADDITIVE` | 128 | The button colors are added together in blend mode. | | `TOUCH_FL_STROKE` | 256 | Enables outline stroke around the button. | Flags can be combined by adding their values ​​together. For example, `5 = 1 + 4` is the combination of `TOUCH_FL_HIDE` and `TOUCH_FL_CLIENT` flags, which is a hidden client button. *In the image below, the `spray`, `scores`, `messagemode` buttons are displayed simultaneously with the flag 8 and `loadquick`, `savequick` with the flag 16, and each is displayed in the corresponding game mode.* ![](images/example1.jpg) ## Useful commands * `touch_hide `: hides buttons by pattern. * `touch_setcommand`: changes the command bound to a button. * `touch_settexture`: quickly changes the button image. * `touch_setcolor`: sets the button color. * `touch_exportconfig`: exports the current configuration, including aspect ratio. ## Usage examples * `touch_hide menu*` hides all buttons with names starting with `menu`. * `touch_setcolor "attack" 255 160 0 128` changes the color of the primary fire button from opaque white to translucent orange, similar to the color of the HUD in Half-Life. *Result of executing this command* ![](images/example3.jpg) * Example of adding a new button with a custom icon (the `lastinv` command is used - quick change between weapons) ![](images/example4.jpg) *View of this button in layout editor* ![](images/example5.jpg) ## Tips * To prevent the `look` and `move` buttons from interfering with editing other elements, place them before the others in the configuration file. * And vice versa, to make a button appear on top of others, place it at the end of the configuration file. * You can assign commands to buttons that change other buttons, see the previous section "Usage examples". * After editing each parameter of each individual button in the Touch Buttons section, do not forget to press Save, otherwise the applied parameters will not be saved. * To create your own icon for the button, you can use any graphics editor (for Android, Photo Editor by iudesk is suitable). Saving conditions: * Image format - `.png` with transparency (e.g. alpha channel) * Aspect ratio / size - 1:1 / 256x256 * Path to the icons location - `touch/gfx` inside game directory. ### Additional links * [Handy palette for selecting color in RGB format](https://www.rapidtables.com/web/color/RGB_Color.html) ================================================ FILE: README.md ================================================ # Vulkan plus Ray Tracing (RTX) temporary fork of Xash3D FWGS engine [![GitHub Actions Status](https://github.com/w23/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/w23/xash3d-fwgs/actions/workflows/c-cpp.yml) ## TL;DR - ![image](https://github.com/w23/xash3d-fwgs/assets/321361/12200b56-df80-4d33-b433-71f5690fb4f5) - This fork adds Vulkan renderer to Xash3D-FWGS engine. - This is work-in-progress. It is in early stages and is not ready for unsupervised usage. - Vulkan renderer targets two different modes: - Traditional rasterizer. It is intended to produce pixel-perfect identical frames to existing GL renderer as possible. - Ray tracing. It implements real time path traced global illumination lighting with PBR materials. It will look noticeably different from original game. - It is intended to be merged back into upstream/master when it gets mature and stable enough. - Ray tracing requires 64-bit build. 32-bit drivers do not expose vulkan ray tracing extensions. - For more information, check out the [wiki](https://github.com/w23/xash3d-fwgs/wiki). - [Page on Mod DB](https://www.moddb.com/mods/half-life-rtx) (screenshots, etc). ## Current status - See [issues](https://github.com/w23/xash3d-fwgs/issues) and [project](https://github.com/users/w23/projects/2/views/12) - Traditional rasterizer mostly works: - Works on Windows and Linux with any Vulkan GPU (and at some point it worked on Raspberry Pi 4 even). - It is slower than OpenGL renderer (1. I suck at Vulkan. 2. No visibility culling is performed). - Some features are not implemented yet, like decals, dynamic lighting is different and way off, etc. - Ray tracer mostly works too, with dynamic GI and stuff. - It also requires material remaster (i.e. newer textures for PBR parameters) and missing RAD files for most of the game maps. - Works under both Windows and Linux. - Works on both AMD and Nvidia GPUs. - Works on Steam Deck with _interactive framerates_. - If you feel adventurous, you can follow [build instructions](https://github.com/w23/xash3d-fwgs/wiki/How-to-build-a-64bit). Note that they might be slightly out of date, kek. ## Follow development This project is 99% developed live on stream. I'm not a graphics programmer, and have no idea what I'm doing. I'm essentially learning Vulkan, game engine renderer development, linear algebra, and ray tracing techniques while getting hands dirty with this. This is all for your amusement. You can watch me making a fool of myself publicly here: - [Archive playlist on YouTube/floba23](https://www.youtube.com/playlist?list=PLP0z1CQXyu5CrDa522FklxbOC0SM_Manl) - [Twitch/provod](https://twitch.tv/provod) --- Regular upstream Xash3D README.md follows. --- # Xash3D FWGS Engine Xash3D FWGS icon [![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \ [![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://fwgsdiscord.mentality.rip/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \ [![Download Daily Build](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) Xash3D ([pronounced](https://ipa-reader.com/?text=ks%C9%91%CA%82) `[ksɑʂ]`) FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow. Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike. ## Donate [![Donate to FWGS button](https://img.shields.io/badge/Donate_to_FWGS-%3C3-magenta)](Documentation/donate.md) \ If you like Xash3D FWGS, consider supporting individual engine maintainers. By supporting us, you help to continue developing this game engine further. The sponsorship links are available in [documentation](Documentation/donate.md). ## Fork features * Steam Half-Life (HLSDK 2.5) support. * Crossplatform and modern compilers support: supports Windows, Linux, BSD & Android on x86 & ARM and [many more](Documentation/ports.md). * Better multiplayer: multiple master servers, headless dedicated server, voice chat, [GoldSrc protocol support](Documentation/goldsrc-protocol-support.md) and IPv6 support. * Multiple renderers support: OpenGL, GLESv1, GLESv2 and Software. * Advanced virtual filesystem: `.pk3` and `.pk3dir` support, compatibility with GoldSrc FS module, fast case-insensitivity emulation for crossplatform. * Mobility API: better game integration on mobile devices (vibration, touch controls). * Different input methods: touch and gamepad in addition to mouse & keyboard. * TrueType font rendering, as a part of mainui_cpp. * External VGUI support module. * PNG & KTX2 image format support. * Ogg Vorbis (`.ogg`) & Ogg Opus (`.opus`) audio formats support. * [A set of small improvements](Documentation/), without broken compatibility. ## Installation & Running 0) Get Xash3D FWGS binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. 1) Copy engine binaries to some directory. 2) Copy `valve` directory from [Half-Life](https://store.steampowered.com/app/70/HalfLife/) to directory with engine binaries. If your CPU is NOT x86 compatible or you're running 64-bit version of the engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-portable). This repository contains our fork of HLSDK and restored source code for Half-Life expansions and some mods. You still needed to copy `valve` directory as all game resources located there. 3) Run the main executable (`xash3d.exe` or AppImage). For additional info, run Xash3D with `-help` command line key. ### Android 0) Install the APK file. 1) Copy `valve` directory to a folder named `xash` in the Internal storage. 2) Run games from within the app. ## Contributing * Before sending an issue, check if someone already reported your issue. Make sure you're following "How To Ask Questions The Smart Way" guide by Eric Steven Raymond. Read more: http://www.catb.org/~esr/faqs/smart-questions.html. * Issues are accepted in both English and Russian. * Before sending a PR, check if you followed our contribution guide in CONTRIBUTING.md file. ## Build instructions We are using Waf build system. If you have some Waf-related questions, I recommend you to read [Waf Book](https://waf.io/book/). **NOTE: NEVER USE GitHub's ZIP ARCHIVES. GitHub doesn't include external dependencies we're using!** ### Prerequisites If your CPU is x86 compatible and you're on Windows or Linux, we are building 32-bit code by default. This was done to maintain compatibility with Steam releases of Half-Life and based on it's engine games. Even if Xash3D FWGS does support targetting 64-bit, you can't load games without recompiling them from source code! If your CPU is NOT x86 compatible or you decided build 64-bit version of engine, you may want to compile [Half-Life SDK](https://github.com/FWGS/hlsdk-portable). This repository contains our fork of HLSDK and restored source code for Half-Life expansions and some mods. #### Windows (Visual Studio) * Install Visual Studio. * Install latest [Python](https://python.org) **OR** run `cinst python.install` if you have Chocolatey. * Install latest [Git](https://git-scm.com/download/win) **OR** run `cinst git.install` if you have Chocolatey. * Download [SDL2](https://libsdl.org/download-2.0.php) development package for Visual Studio. * Clone this repository: `git clone --recursive https://github.com/FWGS/xash3d-fwgs`. * Make sure you have at least 12GB of free space to store all build-time dependencies: ~10GB for Visual Studio, 300 MB for Git, 100 MB for Python and other. #### GNU/Linux ##### Debian/Ubuntu * Only for 32-bit engine on 64-bit x86 operating system: * Enable i386 on your system: `$ sudo dpkg --add-architecture i386`. * Install `aptitude` ([why?](https://github.com/FWGS/xash3d-fwgs/issues/1828#issuecomment-2415131759)): `$ sudo apt update && sudo apt upgrade && sudo apt install aptitude` * Install development tools: `$ sudo aptitude --without-recommends install git build-essential gcc-multilib g++-multilib libsdl2-dev:i386 libfreetype-dev:i386 libopus-dev:i386 libbz2-dev:i386 libvorbis-dev:i386 libopusfile-dev:i386 libogg-dev:i386`. * Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig`. * For 64-bit engine on 64-bit x86 and other non-x86 systems: * Install development tools: `$ sudo apt install git build-essential python libsdl2-dev libfreetype6-dev libopus-dev libbz2-dev libvorbis-dev libopusfile-dev libogg-dev`. * Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`. ##### RedHat/Fedora * Only for 32-bit engine on 64-bit x86 operating system: * Install development tools: `$ sudo dnf install git gcc gcc-c++ glibc-devel.i686 SDL3-devel.i686 sdl2-compat-devel.i686 opus-devel.i686 freetype-devel.i686 bzip2-devel.i686 libvorbis-devel.i686 opusfile-devel.i686 libogg-devel.i686`. * Set PKG_CONFIG_PATH environment variable to point at 32-bit libraries: `$ export PKG_CONFIG_PATH=/usr/lib/pkgconfig`. * For 64-bit engine on 64-bit x86 and other non-x86 systems: * Install development tools: `$ sudo dnf install git gcc gcc-c++ SDL3-devel sdl2-compat-devel opus-devel freetype-devel bzip2-devel libvorbis-devel opusfile-devel libogg-devel`. * Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`. #### Android (Windows/Linux/macOS) * Install [Android Studio](https://developer.android.com/studio) (or the command line tools). * Install [Python](https://python.org) (at least 2.7, latest is better). * Install [Git](https://git-scm.com/download/win). * Install [Ninja](https://ninja-build.org/). * Install [CMake](https://cmake.org/) (for some dependencies). * Clone this repostory: `$ git clone --recursive https://github.com/FWGS/xash3d-fwgs`. ### Building #### Windows (Visual Studio) 0) Open command line. 1) Navigate to `xash3d-fwgs` directory. 2) (optional) Examine which build options are available: `waf --help`. 3) Configure build: `waf configure --sdl2=c:/path/to/SDL2`. 4) Compile: `waf build`. 5) Install: `waf install --destdir=c:/path/to/any/output/directory`. #### Linux If compiling 32-bit on amd64, make sure `PKG_CONFIG_PATH` from the previous step is set correctly, prior to running configure. 0) (optional) Examine which build options are available: `./waf --help`. 1) Configure build: `./waf configure` (you need to pass `-8` to compile 64-bit engine on 64-bit x86 processor). 2) Compile: `./waf build`. 3) Install: `./waf install --destdir=/path/to/any/output/directory`. #### Android (Windows/Linux/macOS) You can just open the `android` folder in Android Studio and build from here, or use `gradlew` to build from command line. ================================================ FILE: android/.gitignore ================================================ .gradle/ build/ .externalNativeBuild .cxx/ .idea/ local.properties .project .classpath .gradle .settings release/ *.hprof .vscode/ *.bak ================================================ FILE: android/app/build.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.time.LocalDateTime import java.time.Month import java.time.temporal.ChronoUnit plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) } android { namespace = "su.xash.engine" ndkVersion = "28.2.13676358" compileSdk = 35 defaultConfig { applicationId = "su.xash.engine" versionName = "0.21-" + getGitHash() versionCode = getBuildNum() minSdk = 21 targetSdk = 35 externalNativeBuild { val engineRoot = projectDir.parentFile.parent experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path experimentalProperties["ninja.configure"] = "run-python" experimentalProperties["ninja.arguments"] = setOf( File(engineRoot, "scripts/configure-ninja.py").path, engineRoot, "--variant=\${ndk.variantName}", "--abi=\${ndk.abi}", "--configuration-dir=\${ndk.buildRoot}", "--ndk-version=\${ndk.moduleNdkVersion}", "--min-sdk-version=\${ndk.minPlatform}", "--ndk-root=${android.ndkDirectory}", // shut up, fake options "-p:Configuration=\${ndk.variantName}", "-p:Platform=\${ndk.abi}" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_11 } } buildFeatures { viewBinding = true buildConfig = true } lint { abortOnError = false } /* androidResources { noCompress += "" } */ packaging { jniLibs { keepDebugSymbols.add("**/*.so") useLegacyPackaging = true } } sourceSets { getByName("main") { assets.srcDirs("../../3rdparty/extras/xash-extras") java.srcDir("../../3rdparty/SDL/android-project/app/src/main/java") } } buildTypes { debug { isMinifyEnabled = false isShrinkResources = false isDebuggable = true applicationIdSuffix = ".test" proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } release { isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } register("asan") { initWith(getByName("debug")) } register("continuous") { initWith(getByName("release")) applicationIdSuffix = ".test" } } } dependencies { implementation(libs.material) implementation(libs.appcompat) implementation(libs.navigation.runtime.ktx) implementation(libs.navigation.fragment.ktx) implementation(libs.navigation.ui.ktx) implementation(libs.preference.ktx) implementation(libs.swiperefreshlayout) implementation(libs.acra.http) } fun getBuildNum(): Int { val now = LocalDateTime.now() val releaseDate = LocalDateTime.of(2015, Month.APRIL, 1, 0, 0, 0) val qBuildNum = releaseDate.until(now, ChronoUnit.DAYS) val minuteOfDay = now.hour * 60 + now.minute return (qBuildNum * 10000 + minuteOfDay).toInt() } fun getGitHash(): String { val process = ProcessBuilder("git", "rev-parse", "--short", "HEAD").directory(project.rootDir) .redirectErrorStream(true).start() return process.inputStream.bufferedReader().readText().trim() } ================================================ FILE: android/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class su.xash.engine.XashActivity { java.lang.String loadAndroidID(); java.lang.String getAndroidID(); void saveAndroidID(java.lang.String); java.lang.String getCallingPackage(); java.lang.String[] getAssetsList(boolean, java.lang.String); android.content.res.AssetManager getAssets(boolean); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLInputConnection { void nativeCommitText(java.lang.String, int); void nativeGenerateScancodeForUnichar(char); } -keep,includedescriptorclasses class org.libsdl.app.SDLActivity { # for some reason these aren't compatible with allowoptimization modifier boolean supportsRelativeMouse(); void setWindowStyle(boolean); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity { java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it boolean onNativeSoftReturnKey(); void onNativeKeyboardFocusLost(); boolean isScreenKeyboardShown(); android.util.DisplayMetrics getDisplayDPI(); java.lang.String clipboardGetText(); boolean clipboardHasText(); void clipboardSetText(java.lang.String); int createCustomCursor(int[], int, int, int, int); void destroyCustomCursor(int); android.content.Context getContext(); boolean getManifestEnvironmentVariables(); android.view.Surface getNativeSurface(); void initTouch(); boolean isAndroidTV(); boolean isChromebook(); boolean isDeXMode(); boolean isTablet(); void manualBackButton(); int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]); void minimizeWindow(); int openURL(java.lang.String); void requestPermission(java.lang.String, int); int showToast(java.lang.String, int, int, int, int); boolean sendMessage(int, int); boolean setActivityTitle(java.lang.String); boolean setCustomCursor(int); void setOrientation(int, int, boolean, java.lang.String); boolean setRelativeMouseEnabled(boolean); boolean setSystemCursor(int); boolean shouldMinimizeOnFocusLoss(); boolean showTextInput(int, int, int, int); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager { boolean initialize(boolean, boolean); boolean openDevice(int); int sendOutputReport(int, byte[]); int sendFeatureReport(int, byte[]); boolean getFeatureReport(int, byte[]); void closeDevice(int); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager { int[] getAudioOutputDevices(); int[] getAudioInputDevices(); int[] audioOpen(int, int, int, int, int); void audioWriteFloatBuffer(float[]); void audioWriteShortBuffer(short[]); void audioWriteByteBuffer(byte[]); void audioClose(); int[] captureOpen(int, int, int, int, int); int captureReadFloatBuffer(float[], boolean); int captureReadShortBuffer(short[], boolean); int captureReadByteBuffer(byte[], boolean); void captureClose(); void audioSetThreadPriority(boolean, int); native int nativeSetupJNI(); native void removeAudioDevice(boolean, int); native void addAudioDevice(boolean, int); } -keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager { void pollInputDevices(); void pollHapticDevices(); void hapticRun(int, float, int); void hapticStop(int); } # Unexpected reference to missing service class: META-INF/services/javax.annotation.processing.Processor. -dontwarn javax.annotation.processing.Processor -dontwarn javax.annotation.processing.AbstractProcessor -dontwarn javax.annotation.processing.SupportedOptions ================================================ FILE: android/app/run-python ================================================ #!/bin/bash exec python $@ ================================================ FILE: android/app/run-python.bat ================================================ python %* ================================================ FILE: android/app/src/asan/res/values/strings.xml ================================================ Xash3D FWGS (Test) su.xash.engine.test.documents ================================================ FILE: android/app/src/asan/resources/lib/arm64-v8a/wrap.sh ================================================ #!/system/bin/sh HERE=$(cd "$(dirname "$0")" && pwd) cmd=$1 shift # This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- # bit app running on a 64-bit device, the 64-bit getprop will fail to load # because it will preload a 32-bit ASan runtime. # https://github.com/android/ndk/issues/1744 os_version=$(getprop ro.build.version.sdk) if [ "$os_version" -eq "27" ]; then cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" elif [ "$os_version" -eq "28" ]; then cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" else cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" fi export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi exec $cmd ================================================ FILE: android/app/src/asan/resources/lib/armeabi-v7a/wrap.sh ================================================ #!/system/bin/sh HERE=$(cd "$(dirname "$0")" && pwd) cmd=$1 shift # This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- # bit app running on a 64-bit device, the 64-bit getprop will fail to load # because it will preload a 32-bit ASan runtime. # https://github.com/android/ndk/issues/1744 os_version=$(getprop ro.build.version.sdk) if [ "$os_version" -eq "27" ]; then cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" elif [ "$os_version" -eq "28" ]; then cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" else cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" fi export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi exec $cmd ================================================ FILE: android/app/src/asan/resources/lib/x86_64/wrap.sh ================================================ #!/system/bin/sh HERE=$(cd "$(dirname "$0")" && pwd) cmd=$1 shift # This must be called *before* `LD_PRELOAD` is set. Otherwise, if this is a 32- # bit app running on a 64-bit device, the 64-bit getprop will fail to load # because it will preload a 32-bit ASan runtime. # https://github.com/android/ndk/issues/1744 os_version=$(getprop ro.build.version.sdk) if [ "$os_version" -eq "27" ]; then cmd="$cmd -Xrunjdwp:transport=dt_android_adb,suspend=n,server=y -Xcompiler-option --debuggable $@" elif [ "$os_version" -eq "28" ]; then cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y -Xcompiler-option --debuggable $@" else cmd="$cmd -XjdwpProvider:adbconnection -XjdwpOptions:suspend=n,server=y $@" fi export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls "$HERE"/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi exec $cmd ================================================ FILE: android/app/src/continuous/res/values/strings.xml ================================================ Xash3D FWGS (Test) su.xash.engine.test.documents ================================================ FILE: android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: android/app/src/main/java/su/xash/engine/DedicatedActivity.kt ================================================ package su.xash.engine class DedicatedActivity {} ================================================ FILE: android/app/src/main/java/su/xash/engine/DedicatedService.kt ================================================ package su.xash.engine import android.app.Service import android.content.Intent import android.os.IBinder class DedicatedService : Service() { override fun onBind(intent: Intent?): IBinder? { TODO("Not yet implemented") } } ================================================ FILE: android/app/src/main/java/su/xash/engine/MainActivity.kt ================================================ package su.xash.engine import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import su.xash.engine.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.toolbar) val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment navController = navHostFragment.navController appBarConfiguration = AppBarConfiguration(navController.graph) setupActionBarWithNavController(navController, appBarConfiguration) } override fun onSupportNavigateUp(): Boolean { return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() } } ================================================ FILE: android/app/src/main/java/su/xash/engine/MainApplication.kt ================================================ package su.xash.engine import android.app.Application import android.content.Context import android.os.StrictMode import org.acra.data.StringFormat import org.acra.ktx.initAcra class MainApplication : Application() { override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) if (!BuildConfig.DEBUG) { initAcra { buildConfigClass = BuildConfig::class.java reportFormat = StringFormat.JSON // httpSender { // uri = "http://bodis.pp.ua:5000/report" // } } } else { // enable strict mode to detect memory leaks etc. StrictMode.enableDefaults(); } } } ================================================ FILE: android/app/src/main/java/su/xash/engine/XashActivity.java ================================================ package su.xash.engine; import android.annotation.SuppressLint; import android.content.pm.ActivityInfo; import android.content.res.AssetManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.Settings.Secure; import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import org.libsdl.app.SDLActivity; import su.xash.engine.util.AndroidBug5497Workaround; public class XashActivity extends SDLActivity { private boolean mUseVolumeKeys; private String mPackageName; private static final String TAG = "XashActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { //getWindow().addFlags(WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES); getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } AndroidBug5497Workaround.assistActivity(this); } @Override public void onDestroy() { super.onDestroy(); // Now that we don't exit from native code, we need to exit here, resetting // application state (actually global variables that we don't cleanup on exit) // // When the issue with global variables will be resolved, remove that exit() call System.exit(0); } @Override protected String[] getLibraries() { return new String[]{"SDL2", "xash"}; } @SuppressLint("HardwareIds") private String getAndroidID() { return Secure.getString(getContentResolver(), Secure.ANDROID_ID); } @SuppressLint("ApplySharedPref") private void saveAndroidID(String id) { getSharedPreferences("xash_preferences", MODE_PRIVATE).edit().putString("xash_id", id).commit(); } private String loadAndroidID() { return getSharedPreferences("xash_preferences", MODE_PRIVATE).getString("xash_id", ""); } @Override public String getCallingPackage() { if (mPackageName != null) { return mPackageName; } return super.getCallingPackage(); } private AssetManager getAssets(boolean isEngine) { AssetManager am = null; if (isEngine) { am = getAssets(); } else { try { am = getPackageManager().getResourcesForApplication(getCallingPackage()).getAssets(); } catch (Exception e) { Log.e(TAG, "Unable to load mod assets!"); e.printStackTrace(); } } return am; } private String[] getAssetsList(boolean isEngine, String path) { AssetManager am = getAssets(isEngine); try { return am.list(path); } catch (Exception e) { e.printStackTrace(); } return new String[]{}; } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (SDLActivity.mBrokenLibraries) { return false; } int keyCode = event.getKeyCode(); if (!mUseVolumeKeys) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || keyCode == KeyEvent.KEYCODE_ZOOM_OUT) { return false; } } return getWindow().superDispatchKeyEvent(event); } // TODO: REMOVE LATER, temporary launchers support? @Override protected String[] getArguments() { String gamedir = getIntent().getStringExtra("gamedir"); if (gamedir == null) gamedir = "valve"; nativeSetenv("XASH3D_GAME", gamedir); String gamelibdir = getIntent().getStringExtra("gamelibdir"); if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir); String pakfile = getIntent().getStringExtra("pakfile"); if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile); String basedir = getIntent().getStringExtra("basedir"); if (basedir != null) { nativeSetenv("XASH3D_BASEDIR", basedir); } else { String rootPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/xash"; nativeSetenv("XASH3D_BASEDIR", rootPath); } mUseVolumeKeys = getIntent().getBooleanExtra("usevolume", false); mPackageName = getIntent().getStringExtra("package"); String[] env = getIntent().getStringArrayExtra("env"); if (env != null) { for (int i = 0; i < env.length; i += 2) nativeSetenv(env[i], env[i + 1]); } String argv = getIntent().getStringExtra("argv"); if (argv == null) argv = "-console -log"; return argv.split(" "); } } ================================================ FILE: android/app/src/main/java/su/xash/engine/adapters/GameAdapter.kt ================================================ package su.xash.engine.adapters import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.navigation.findNavController import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import su.xash.engine.R import su.xash.engine.databinding.CardGameBinding import su.xash.engine.model.Game import su.xash.engine.ui.library.LibraryViewModel class GameAdapter(private val libraryViewModel: LibraryViewModel) : ListAdapter(DiffCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameAdapter.GameViewHolder { val binding = CardGameBinding.inflate(LayoutInflater.from(parent.context), parent, false) return GameViewHolder(binding) } override fun onBindViewHolder(holder: GameAdapter.GameViewHolder, position: Int) { return holder.bind(getItem(position)) } private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { return oldItem.basedir.name == newItem.basedir.name } override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { return oldItem.basedir.name == newItem.basedir.name } } inner class GameViewHolder(val binding: CardGameBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(game: Game) { binding.apply { gameTitle.text = game.title if (game.icon != null) { gameIcon.setImageBitmap(game.icon) } else { gameIcon.visibility = View.GONE } if (game.cover != null) { gameCover.setImageBitmap(game.cover) } else { gameCover.visibility = View.GONE } settingsButton.setOnClickListener { libraryViewModel.setSelectedGame(game) it.findNavController() .navigate(R.id.action_libraryFragment_to_gameSettingsFragment) } root.setOnClickListener { libraryViewModel.startEngine(it.context, game) } launchButton.setOnClickListener { libraryViewModel.startEngine(it.context, game) } } } } } ================================================ FILE: android/app/src/main/java/su/xash/engine/model/BackgroundBitmap.kt ================================================ package su.xash.engine.model import android.graphics.Bitmap import android.graphics.Canvas import su.xash.engine.util.TGAReader import java.io.File import java.io.FileInputStream import java.util.Scanner object BackgroundBitmap { private const val BACKGROUND_ROWS = 3 private const val BACKGROUND_COLUMNS = 4 private const val BACKGROUND_WIDTH = 800 private const val BACKGROUND_HEIGHT = 600 fun createBackground(file: File): Bitmap { var bitmap = Bitmap.createBitmap(BACKGROUND_WIDTH, BACKGROUND_HEIGHT, Bitmap.Config.ARGB_8888) var canvas = Canvas(bitmap) var x: Int var y = 0 var width: Int var height = 0 val resourceFolder = File(file, "resource") var bgLayout = File(resourceFolder, "HD_BackgroundLayout.txt") if (!bgLayout.exists()) { bgLayout = File(resourceFolder, "BackgroundLayout.txt") } if (!bgLayout.exists()) { val dir = File(resourceFolder, "background") for (i in 0 until BACKGROUND_ROWS) { x = 0 for (j in 0 until BACKGROUND_COLUMNS) { val filename = "${BACKGROUND_WIDTH}_${i + 1}_${'a' + j}_loading.tga" val bmpFile = File(dir, filename) val bmpImage = loadTga(bmpFile) canvas.drawBitmap(bmpImage, x.toFloat(), y.toFloat(), null) x += bmpImage.width height = bmpImage.height } y += height } return bitmap } FileInputStream(bgLayout).use { inputStream -> Scanner(inputStream).use { scanner -> while (scanner.hasNext()) { when (val str = scanner.next()) { "resolution" -> { width = scanner.nextInt() height = scanner.nextInt() bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) canvas = Canvas(bitmap) } else -> { var bmpFile = file str.split("/").forEach { bmpFile = File(bmpFile, it) } //skip scanner.next() x = scanner.nextInt() y = scanner.nextInt() val bmp = loadTga(bmpFile) canvas.drawBitmap(bmp, x.toFloat(), y.toFloat(), null) } } } } } return bitmap } private fun loadTga(file: File): Bitmap { FileInputStream(file).use { val buffer = it.readBytes() val pixels = TGAReader.read(buffer, TGAReader.ARGB) val width = TGAReader.getWidth(buffer) val height = TGAReader.getHeight(buffer) return Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888) } } } ================================================ FILE: android/app/src/main/java/su/xash/engine/model/Game.kt ================================================ package su.xash.engine.model import android.content.Context import android.content.Intent import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import su.xash.engine.XashActivity import java.io.File import java.io.FileInputStream class Game(val ctx: Context, val basedir: File) { private var iconName = "game.ico" var title = "Unknown Game" var icon: Bitmap? = null var cover: Bitmap? = null private val pref = ctx.getSharedPreferences(basedir.name, Context.MODE_PRIVATE) init { val gameInfo = File(basedir, "gameinfo.txt") if (gameInfo.exists()) { parseGameInfo(gameInfo) } else { val libListGam = File(basedir, "liblist.gam") if (libListGam.exists()) parseGameInfo(libListGam) } val iconFile = File(basedir, iconName) if (iconFile.exists()) { icon = BitmapFactory.decodeFile(iconFile.path) } try { cover = BackgroundBitmap.createBackground(basedir) } catch (e: Exception) { e.printStackTrace() } } fun startEngine(ctx: Context) { ctx.startActivity(Intent(ctx, XashActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK putExtra("gamedir", basedir.name) putExtra("argv", pref.getString("arguments", "-console -log")) putExtra("usevolume", pref.getBoolean("use_volume_buttons", false)) putExtra("basedir", basedir.parent) //.putExtra("gamelibdir", getGameLibDir(context)) //.putExtra("package", getPackageName()) } }) } private fun parseGameInfo(file: File) { FileInputStream(file).use { inputStream -> inputStream.bufferedReader().use { reader -> reader.forEachLine { val tokens = it.split("\\s+".toRegex(), limit = 2) if (tokens.size >= 2) { val k = tokens[0] val v = tokens[1].trim('"') if (k == "title" || k == "game") title = v if (k == "icon") iconName = v } } } } } private fun getPackageName(): String? { // return if (mDbEntry != null) { // mDbEntry.getPackageName() // } else null return null } private fun getGameLibDir(ctx: Context): String? { val pkgName = getPackageName() if (pkgName != null) { val pkgInfo: PackageInfo = try { ctx.packageManager.getPackageInfo(pkgName, 0) } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() ctx.startActivity( Intent( Intent.ACTION_VIEW, Uri.parse("market://details?id=$pkgName") ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) ) return null } return pkgInfo.applicationInfo?.nativeLibraryDir } return ctx.applicationInfo.nativeLibraryDir } companion object { fun getGames(ctx: Context, file: File): List { val games = mutableListOf() if (checkIfGamedir(file)) { games.add(Game(ctx, file)) } else { file.listFiles()?.forEach { if (it.isDirectory) { if (checkIfGamedir(it)) { games.add(Game(ctx, it)) } } } } return games } fun checkIfGamedir(file: File): Boolean { if (File(file, "liblist.gam").exists()) return true if (File(file, "gameinfo.txt").exists()) return true return false } } } // Intent intent = new Intent("su.xash.engine.MOD"); // for (ResolveInfo info : context.getPackageManager() // .queryIntentActivities(intent, PackageManager.GET_META_DATA)) { // String packageName = info.activityInfo.applicationInfo.packageName; // String gameDir = info.activityInfo.applicationInfo.metaData.getString( // "su.xash.engine.gamedir"); // Log.d(TAG, "package = " + packageName + " gamedir = " + gameDir); // } //public void startEngine(Context context) { // context.startActivity(new Intent(context, XashActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP).putExtra("gamedir", getGameDir()).putExtra("argv", getArguments()).putExtra("usevolume", getVolumeState()).putExtra("gamelibdir", getGameLibDir(context)).putExtra("package", getPackageName())); //} ================================================ FILE: android/app/src/main/java/su/xash/engine/ui/library/LibraryFragment.kt ================================================ package su.xash.engine.ui.library import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment import android.provider.Settings import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.navigation.fragment.findNavController import com.google.android.material.dialog.MaterialAlertDialogBuilder import su.xash.engine.BuildConfig import su.xash.engine.R import su.xash.engine.adapters.GameAdapter import su.xash.engine.databinding.FragmentLibraryBinding class LibraryFragment : Fragment(), MenuProvider { private var _binding: FragmentLibraryBinding? = null private val binding get() = _binding!! private val libraryViewModel: LibraryViewModel by activityViewModels() private val startActivityForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (checkStoragePermissions()) { libraryViewModel.reloadGames(requireContext()) } } private val requiredPermissions = arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ) private val requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissions -> val granted = permissions.entries.all { it.value } if (granted) { libraryViewModel.reloadGames(requireContext()) } else { checkStoragePermissions() } } private fun checkStoragePermissions(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) MaterialAlertDialogBuilder(requireContext()).apply { setTitle(R.string.file_access_required) setMessage(R.string.file_access_message) setPositiveButton(android.R.string.ok) { _, _ -> startActivityForResult.launch( Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).setData( Uri.fromParts("package", BuildConfig.APPLICATION_ID, null) ) ) } setCancelable(false) show() return false } else { return true } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val permissionsNeeded = requiredPermissions.filter { ContextCompat.checkSelfPermission( requireContext(), it ) != PackageManager.PERMISSION_GRANTED }.toTypedArray() if (!permissionsNeeded.isEmpty()) { val showRationale = permissionsNeeded.any { ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), it) } MaterialAlertDialogBuilder(requireContext()).apply { setTitle(R.string.external_storage_required) setMessage(R.string.external_storage_message) setPositiveButton(android.R.string.ok) { _, _ -> if (showRationale) { requestPermissionLauncher.launch(permissionsNeeded) } else { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.fromParts("package", requireContext().packageName, null) } startActivity(intent) } } setCancelable(false) show() } return false } else { return true } } else { return true } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentLibraryBinding.inflate(inflater, container, false) val adapter = GameAdapter(libraryViewModel) binding.gamesList.adapter = adapter requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.swipeRefresh.setOnRefreshListener { libraryViewModel.reloadGames(requireContext()) } libraryViewModel.isReloading.observe(viewLifecycleOwner) { binding.swipeRefresh.isRefreshing = it } libraryViewModel.installedGames.observe(viewLifecycleOwner) { (binding.gamesList.adapter as GameAdapter).submitList(it) } if (checkStoragePermissions()) { libraryViewModel.reloadGames(requireContext()) } } override fun onDestroyView() { super.onDestroyView() _binding = null } override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.menu_library, menu) } override fun onMenuItemSelected(menuItem: MenuItem): Boolean { when (menuItem.itemId) { R.id.action_settings -> { findNavController().navigate(R.id.action_libraryFragment_to_appSettingsFragment) } } return false } override fun onResume() { super.onResume() if (checkStoragePermissions()) { libraryViewModel.reloadGames(requireContext()) } } } ================================================ FILE: android/app/src/main/java/su/xash/engine/ui/library/LibraryViewModel.kt ================================================ package su.xash.engine.ui.library import android.app.Application import android.content.Context import android.content.SharedPreferences import android.os.Environment import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import su.xash.engine.model.Game import java.io.File class LibraryViewModel(application: Application) : AndroidViewModel(application) { val installedGames: LiveData> get() = _installedGames private val _installedGames = MutableLiveData(emptyList()) val isReloading: LiveData get() = _isReloading private val _isReloading = MutableLiveData(false) val selectedItem: LiveData get() = _selectedItem private val _selectedItem = MutableLiveData() private val appPreferences: SharedPreferences = application.getSharedPreferences("app_preferences", Context.MODE_PRIVATE) fun reloadGames(ctx: Context) { if (isReloading.value == true) { return } _isReloading.value = true viewModelScope.launch { withContext(Dispatchers.IO) { val rootPath = appPreferences.getString("game_path", null) ?: (Environment.getExternalStorageDirectory().absolutePath + "/xash") val root = File(rootPath) _installedGames.postValue(Game.getGames(ctx, root)) _isReloading.postValue(false) } } } fun setSelectedGame(game: Game) { _selectedItem.value = game } fun startEngine(ctx: Context, game: Game) { game.startEngine(ctx) } } ================================================ FILE: android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsFragment.kt ================================================ package su.xash.engine.ui.settings import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import su.xash.engine.databinding.FragmentAppSettingsBinding class AppSettingsFragment : Fragment() { private var _binding: FragmentAppSettingsBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentAppSettingsBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) childFragmentManager.beginTransaction() .add(binding.settingsFragment.id, AppSettingsPreferenceFragment()).commit(); } override fun onDestroyView() { super.onDestroyView() _binding = null } } ================================================ FILE: android/app/src/main/java/su/xash/engine/ui/settings/AppSettingsPreferenceFragment.kt ================================================ package su.xash.engine.ui.settings import android.os.Bundle import androidx.preference.PreferenceFragmentCompat import su.xash.engine.R class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.sharedPreferencesName = "app_preferences"; setPreferencesFromResource(R.xml.app_preferences, rootKey); } } ================================================ FILE: android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsFragment.kt ================================================ package su.xash.engine.ui.settings import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import su.xash.engine.databinding.FragmentGameSettingsBinding import su.xash.engine.ui.library.LibraryViewModel class GameSettingsFragment : Fragment() { private var _binding: FragmentGameSettingsBinding? = null private val binding get() = _binding!! private val libraryViewModel: LibraryViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentGameSettingsBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val game = libraryViewModel.selectedItem.value!! binding.gameCard.apply { gameTitle.text = game.title if (game.icon != null) { gameIcon.setImageBitmap(game.icon) } else { gameIcon.visibility = View.GONE } if (game.cover != null) { gameCover.setImageBitmap(game.cover) } else { gameCover.visibility = View.GONE } buttonsContainer.visibility = View.GONE } childFragmentManager.beginTransaction() .add(binding.settingsFragment.id, GameSettingsPreferenceFragment(game)) .commit(); } override fun onDestroyView() { super.onDestroyView() _binding = null } } ================================================ FILE: android/app/src/main/java/su/xash/engine/ui/settings/GameSettingsPreferenceFragment.kt ================================================ package su.xash.engine.ui.settings import android.os.Bundle import androidx.preference.ListPreference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import su.xash.engine.R import su.xash.engine.model.Game class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.sharedPreferencesName = game.basedir.name; setPreferencesFromResource(R.xml.game_preferences, rootKey); val packageList = findPreference("package_name")!! packageList.entries = arrayOf(getString(R.string.app_name)) packageList.entryValues = arrayOf(requireContext().packageName) if (packageList.value == null) { packageList.setValueIndex(0); } val separatePackages = findPreference("separate_libraries")!! val clientPackage = findPreference("client_package")!! val serverPackage = findPreference("server_package")!! separatePackages.setOnPreferenceChangeListener { _, newValue -> if (newValue == true) { packageList.isVisible = false clientPackage.isVisible = true serverPackage.isVisible = true } else { packageList.isVisible = true clientPackage.isVisible = false serverPackage.isVisible = false } true } } } ================================================ FILE: android/app/src/main/java/su/xash/engine/util/AndroidBug5497Workaround.java ================================================ package su.xash.engine.util; import android.app.Activity; import android.graphics.Rect; import android.view.View; import android.widget.FrameLayout; public class AndroidBug5497Workaround { // For more information, see https://code.google.com/p/android/issues/detail?id=5497 // To use this class, simply invoke assistActivity() on an Activity that already has its content view set. public static void assistActivity(Activity activity) { new AndroidBug5497Workaround(activity); } private View mChildOfContent; private int usableHeightPrevious; private FrameLayout.LayoutParams frameLayoutParams; private AndroidBug5497Workaround(Activity activity) { FrameLayout content = activity.findViewById(android.R.id.content); mChildOfContent = content.getChildAt(0); mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(this::possiblyResizeChildOfContent); frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); } private void possiblyResizeChildOfContent() { int usableHeightNow = computeUsableHeight(); if (usableHeightNow != usableHeightPrevious) { int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); int heightDifference = usableHeightSansKeyboard - usableHeightNow; if (heightDifference > (usableHeightSansKeyboard / 4)) { // keyboard probably just became visible frameLayoutParams.height = usableHeightSansKeyboard - heightDifference; } else { // keyboard probably just became hidden frameLayoutParams.height = usableHeightSansKeyboard; } mChildOfContent.requestLayout(); usableHeightPrevious = usableHeightNow; } } private int computeUsableHeight() { Rect r = new Rect(); mChildOfContent.getWindowVisibleDisplayFrame(r); return (r.bottom - r.top); } } ================================================ FILE: android/app/src/main/java/su/xash/engine/util/TGAReader.java ================================================ /** * TGAReader.java *

* Copyright (c) 2014 Kenji Sasaki * Released under the MIT license. * https://github.com/npedotnet/TGAReader/blob/master/LICENSE *

* English document * https://github.com/npedotnet/TGAReader/blob/master/README.md *

* Japanese document * http://3dtech.jp/wiki/index.php?TGAReader */ package su.xash.engine.util; import java.io.IOException; public final class TGAReader { public static final Order ARGB = new Order(16, 8, 0, 24); public static final Order ABGR = new Order(0, 8, 16, 24); public static int getWidth(byte[] buffer) { return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8; } public static int getHeight(byte[] buffer) { return (buffer[14] & 0xFF) | (buffer[15] & 0xFF) << 8; } public static int[] read(byte[] buffer, Order order) throws IOException { // header // int idFieldLength = buffer[0] & 0xFF; // int colormapType = buffer[1] & 0xFF; int type = buffer[2] & 0xFF; int colormapOrigin = (buffer[3] & 0xFF) | (buffer[4] & 0xFF) << 8; int colormapLength = (buffer[5] & 0xFF) | (buffer[6] & 0xFF) << 8; int colormapDepth = buffer[7] & 0xFF; // int originX = (buffer[8] & 0xFF) | (buffer[9] & 0xFF) << 8; // unsupported // int originY = (buffer[10] & 0xFF) | (buffer[11] & 0xFF) << 8; // unsupported int width = getWidth(buffer); int height = getHeight(buffer); int depth = buffer[16] & 0xFF; int descriptor = buffer[17] & 0xFF; int[] pixels; // data switch (type) { case COLORMAP: { int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength; pixels = createPixelsFromColormap(width, height, colormapDepth, buffer, imageDataOffset, buffer, colormapOrigin, descriptor, order); } break; case RGB: pixels = createPixelsFromRGB(width, height, depth, buffer, 18, descriptor, order); break; case GRAYSCALE: pixels = createPixelsFromGrayscale(width, height, depth, buffer, 18, descriptor, order); break; case COLORMAP_RLE: { int imageDataOffset = 18 + (colormapDepth / 8) * colormapLength; byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, imageDataOffset); pixels = createPixelsFromColormap(width, height, colormapDepth, decodeBuffer, 0, buffer, colormapOrigin, descriptor, order); } break; case RGB_RLE: { byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18); pixels = createPixelsFromRGB(width, height, depth, decodeBuffer, 0, descriptor, order); } break; case GRAYSCALE_RLE: { byte[] decodeBuffer = decodeRLE(width, height, depth, buffer, 18); pixels = createPixelsFromGrayscale(width, height, depth, decodeBuffer, 0, descriptor, order); } break; default: throw new IOException("Unsupported image type: " + type); } return pixels; } private static final int COLORMAP = 1; private static final int RGB = 2; private static final int GRAYSCALE = 3; private static final int COLORMAP_RLE = 9; private static final int RGB_RLE = 10; private static final int GRAYSCALE_RLE = 11; private static final int RIGHT_ORIGIN = 0x10; private static final int UPPER_ORIGIN = 0x20; private static byte[] decodeRLE(int width, int height, int depth, byte[] buffer, int offset) { int elementCount = depth / 8; byte[] elements = new byte[elementCount]; int decodeBufferLength = elementCount * width * height; byte[] decodeBuffer = new byte[decodeBufferLength]; int decoded = 0; while (decoded < decodeBufferLength) { int packet = buffer[offset++] & 0xFF; if ((packet & 0x80) != 0) { // RLE for (int i = 0; i < elementCount; i++) { elements[i] = buffer[offset++]; } int count = (packet & 0x7F) + 1; for (int i = 0; i < count; i++) { for (int j = 0; j < elementCount; j++) { decodeBuffer[decoded++] = elements[j]; } } } else { // RAW int count = (packet + 1) * elementCount; for (int i = 0; i < count; i++) { decodeBuffer[decoded++] = buffer[offset++]; } } } return decodeBuffer; } private static int[] createPixelsFromColormap(int width, int height, int depth, byte[] bytes, int offset, byte[] palette, int colormapOrigin, int descriptor, Order order) throws IOException { int[] pixels; int rs = order.redShift; int gs = order.greenShift; int bs = order.blueShift; int as = order.alphaShift; switch (depth) { case 24: pixels = new int[width * height]; if ((descriptor & RIGHT_ORIGIN) != 0) { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 3 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * i + (width - j - 1)] = color; } } } else { // LowerRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 3 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * (height - i - 1) + (width - j - 1)] = color; } } } } else { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 3 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * i + j] = color; } } } else { // LowerLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 3 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * (height - i - 1) + j] = color; } } } } break; case 32: pixels = new int[width * height]; if ((descriptor & RIGHT_ORIGIN) != 0) { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 4 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = palette[index + 3] & 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * i + (width - j - 1)] = color; } } } else { // LowerRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 4 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = palette[index + 3] & 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * (height - i - 1) + (width - j - 1)] = color; } } } } else { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 4 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = palette[index + 3] & 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * i + j] = color; } } } else { // LowerLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int colormapIndex = bytes[offset + width * i + j] & 0xFF - colormapOrigin; int color = 0xFFFFFFFF; if (colormapIndex >= 0) { int index = 4 * colormapIndex + 18; int b = palette[index] & 0xFF; int g = palette[index + 1] & 0xFF; int r = palette[index + 2] & 0xFF; int a = palette[index + 3] & 0xFF; color = (r << rs) | (g << gs) | (b << bs) | (a << as); } pixels[width * (height - i - 1) + j] = color; } } } } break; default: throw new IOException("Unsupported depth:" + depth); } return pixels; } private static int[] createPixelsFromRGB(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException { int[] pixels; int rs = order.redShift; int gs = order.greenShift; int bs = order.blueShift; int as = order.alphaShift; switch (depth) { case 24: pixels = new int[width * height]; if ((descriptor & RIGHT_ORIGIN) != 0) { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 3 * width * i + 3 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = 0xFF; pixels[width * i + (width - j - 1)] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } else { // LowerRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 3 * width * i + 3 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = 0xFF; pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } } else { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 3 * width * i + 3 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = 0xFF; pixels[width * i + j] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } else { // LowerLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 3 * width * i + 3 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = 0xFF; pixels[width * (height - i - 1) + j] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } } break; case 32: pixels = new int[width * height]; if ((descriptor & RIGHT_ORIGIN) != 0) { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 4 * width * i + 4 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = bytes[index + 3] & 0xFF; pixels[width * i + (width - j - 1)] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } else { // LowerRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 4 * width * i + 4 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = bytes[index + 3] & 0xFF; pixels[width * (height - i - 1) + (width - j - 1)] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } } else { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 4 * width * i + 4 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = bytes[index + 3] & 0xFF; pixels[width * i + j] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } else { // LowerLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int index = offset + 4 * width * i + 4 * j; int b = bytes[index] & 0xFF; int g = bytes[index + 1] & 0xFF; int r = bytes[index + 2] & 0xFF; int a = bytes[index + 3] & 0xFF; pixels[width * (height - i - 1) + j] = (r << rs) | (g << gs) | (b << bs) | (a << as); } } } } break; default: throw new IOException("Unsupported depth:" + depth); } return pixels; } private static int[] createPixelsFromGrayscale(int width, int height, int depth, byte[] bytes, int offset, int descriptor, Order order) throws IOException { int[] pixels; int rs = order.redShift; int gs = order.greenShift; int bs = order.blueShift; int as = order.alphaShift; switch (depth) { case 8: pixels = new int[width * height]; if ((descriptor & RIGHT_ORIGIN) != 0) { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + width * i + j] & 0xFF; int a = 0xFF; pixels[width * i + (width - j - 1)] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } else { // LowerRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + width * i + j] & 0xFF; int a = 0xFF; pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } } else { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + width * i + j] & 0xFF; int a = 0xFF; pixels[width * i + j] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } else { // LowerLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + width * i + j] & 0xFF; int a = 0xFF; pixels[width * (height - i - 1) + j] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } } break; case 16: pixels = new int[width * height]; if ((descriptor & RIGHT_ORIGIN) != 0) { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; pixels[width * i + (width - j - 1)] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } else { // LowerRight for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; pixels[width * (height - i - 1) + (width - j - 1)] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } } else { if ((descriptor & UPPER_ORIGIN) != 0) { // UpperLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; pixels[width * i + j] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } else { // LowerLeft for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { int e = bytes[offset + 2 * width * i + 2 * j] & 0xFF; int a = bytes[offset + 2 * width * i + 2 * j + 1] & 0xFF; pixels[width * (height - i - 1) + j] = (e << rs) | (e << gs) | (e << bs) | (a << as); } } } } break; default: throw new IOException("Unsupported depth:" + depth); } return pixels; } private TGAReader() { } public static final class Order { Order(int redShift, int greenShift, int blueShift, int alphaShift) { this.redShift = redShift; this.greenShift = greenShift; this.blueShift = blueShift; this.alphaShift = alphaShift; } public int redShift; public int greenShift; public int blueShift; public int alphaShift; } } ================================================ FILE: android/app/src/main/res/drawable/ic_baseline_add_24.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable/ic_baseline_delete_24.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable/ic_baseline_folder_open_24.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable/ic_baseline_play_arrow_24.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable/ic_baseline_settings_24.xml ================================================ ================================================ FILE: android/app/src/main/res/drawable/ic_baseline_terminal_24.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/card_game.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/edit_text_preference.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/fragment_app_settings.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/fragment_game_settings.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/fragment_library.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/list_preference.xml ================================================ ================================================ FILE: android/app/src/main/res/layout/switch_preference.xml ================================================ ================================================ FILE: android/app/src/main/res/menu/menu_game_settings.xml ================================================

================================================ FILE: android/app/src/main/res/menu/menu_library.xml ================================================ ================================================ FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml ================================================ ================================================ FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_monochrome.xml ================================================ ================================================ FILE: android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: android/app/src/main/res/navigation/nav_graph.xml ================================================ ================================================ FILE: android/app/src/main/res/values/colors.xml ================================================ #F74843 #FB7E14 #000000 #151515 #292929 ================================================ FILE: android/app/src/main/res/values/ic_launcher_background.xml ================================================ #F74843 ================================================ FILE: android/app/src/main/res/values/strings.xml ================================================ Xash3D FWGS Library Dedicated Settings Game Settings Command-line arguments Use volume buttons in-game Libraries package Libraries from separate packages Client package Server package Use icons instead of backgrounds Game data location All-files access required All-files access is required for the app to function properly. Select current directory External storage access required External storage access is required for the app to function properly. ================================================ FILE: android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: android/app/src/main/res/values/themes.xml ================================================ ================================================ FILE: android/app/src/main/res/values-es/strings.xml ================================================ Xash3D FWGS Librería Dedicado Ajustes Ajustes del juego Argumentos de la línea de comandos Usar botones de volumen en el juego Paquete de librerías Librerías de paquetes separados Paquete de cliente Paquete de servidor Usar iconos en lugar de fondos Ubicación de los datos del juego Se requiere acceso a todos los archivos Se requiere acceso a todos los archivos para que la aplicación funcione correctamente. Seleccionar directorio actual Se requiere acceso al almacenamiento externo Se requiere acceso al almacenamiento externo para que la aplicación funcione correctamente. ================================================ FILE: android/app/src/main/res/values-pt-rBR/strings.xml ================================================ Biblioteca Dedicado Configurações Configurações do jogo Argumentos de linha de comando Usar botões de volume no jogo Pacote de bibliotecas Bibliotecas de pacotes separados Pacote do cliente Pacote do servidor Usar ícones em vez de fundos Local dos dados do jogo Acesso a todos os arquivos necessário É necessário acesso a todos os arquivos para que o aplicativo funcione corretamente. Selecionar diretório atual Acesso ao armazenamento externo necessário É necessário acesso ao armazenamento externo para que o aplicativo funcione corretamente. ================================================ FILE: android/app/src/main/res/values-ru/strings.xml ================================================ Библиотека Выделенный Настройки Настройки игры Аргументы командной строки Использовать кнопки громкости Пакет библиотек Библиотеки из отдельных пакетов Пакет клиента Пакет сервера Использовать иконки вместо фона Расположение игровых данных Требуется доступ ко всем файлам Для правильной работы приложения требуется доступ ко всем файлам. Выбрать текущую директорию Требуется доступ к внешнему хранилищу Для правильной работы приложения требуется доступ к внешнему хранилищу. ================================================ FILE: android/app/src/main/res/xml/app_preferences.xml ================================================ ================================================ FILE: android/app/src/main/res/xml/game_preferences.xml ================================================ ================================================ FILE: android/build.gradle.kts ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false } ================================================ FILE: android/gradle/libs.versions.toml ================================================ [versions] acraHttp = "5.12.0" agp = "8.11.1" appcompat = "1.7.1" kotlin = "2.2.0" material = "1.12.0" navigationRuntimeKtx = "2.9.1" preferenceKtx = "1.2.1" swiperefreshlayout = "1.1.0" [libraries] acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } material = { module = "com.google.android.material:material", version.ref = "material" } navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigationRuntimeKtx" } navigation-runtime-ktx = { module = "androidx.navigation:navigation-runtime-ktx", version.ref = "navigationRuntimeKtx" } navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationRuntimeKtx" } preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } ================================================ FILE: android/gradle/wrapper/gradle-wrapper.properties ================================================ #Tue Jul 01 19:51:06 EEST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: android/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true # Enable verbose output for CMake android.native.buildOutput=verbose ================================================ FILE: android/gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; esac # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: android/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windowz variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* goto execute :4NT_args @rem Get arguments from the 4NT Shell from JP Software set CMD_LINE_ARGS=%$ :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: android/settings.gradle.kts ================================================ pluginManagement { repositories { google { content { includeGroupByRegex("com\\.android.*") includeGroupByRegex("com\\.google.*") includeGroupByRegex("androidx.*") } } mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "Xash3D FWGS" include(":app") ================================================ FILE: common/backends.h ================================================ /* backends.h - backend macro definitions Copyright (C) 2016 Mittorn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef BACKENDS_H #define BACKENDS_H // video backends (XASH_VIDEO) #define VIDEO_NULL 0 #define VIDEO_SDL 1 #define VIDEO_FBDEV 3 #define VIDEO_DOS 4 // audio backends (XASH_SOUND) #define SOUND_NULL 0 #define SOUND_SDL 1 #define SOUND_ALSA 3 // input (XASH_INPUT) #define INPUT_NULL 0 #define INPUT_SDL 1 #define INPUT_EVDEV 3 // timer (XASH_TIMER) #define TIMER_NULL 0 // not used #define TIMER_SDL 1 #define TIMER_POSIX 2 #define TIMER_WIN32 3 #define TIMER_DOS 4 // messageboxes (XASH_MESSAGEBOX) #define MSGBOX_STDERR 0 #define MSGBOX_SDL 1 #define MSGBOX_WIN32 3 #define MSGBOX_NSWITCH 4 // library loading (XASH_LIB) #define LIB_NULL 0 #define LIB_POSIX 1 #define LIB_WIN32 2 #define LIB_STATIC 3 // movies (XASH_AVI) #define AVI_NULL 0 #define AVI_FFMPEG 1 #endif /* BACKENDS_H */ ================================================ FILE: common/beamdef.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef BEAMDEF_H #define BEAMDEF_H #define FBEAM_STARTENTITY 0x00000001 #define FBEAM_ENDENTITY 0x00000002 #define FBEAM_FADEIN 0x00000004 #define FBEAM_FADEOUT 0x00000008 #define FBEAM_SINENOISE 0x00000010 #define FBEAM_SOLID 0x00000020 #define FBEAM_SHADEIN 0x00000040 #define FBEAM_SHADEOUT 0x00000080 #define FBEAM_STARTVISIBLE 0x10000000 // Has this client actually seen this beam's start entity yet? #define FBEAM_ENDVISIBLE 0x20000000 // Has this client actually seen this beam's end entity yet? #define FBEAM_ISACTIVE 0x40000000 #define FBEAM_FOREVER 0x80000000 typedef struct beam_s BEAM; struct beam_s { BEAM *next; int type; int flags; vec3_t source; vec3_t target; vec3_t delta; float t; // 0 .. 1 over lifetime of beam float freq; float die; float width; float amplitude; float r, g, b; float brightness; float speed; float frameRate; float frame; int segments; int startEntity; int endEntity; int modelIndex; int frameCount; struct model_s *pFollowModel; struct particle_s *particles; }; #endif//BEAMDEF_H ================================================ FILE: common/bspfile.h ================================================ /* bspfile.h - BSP format included q1, hl1 support Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef BSPFILE_H #define BSPFILE_H /* ============================================================================== BRUSH MODELS .bsp contain level static geometry with including PVS and lightning info ============================================================================== */ // header #define Q1BSP_VERSION 29 // quake1 regular version (beta is 28) #define HLBSP_VERSION 30 // half-life regular version #define QBSP2_VERSION (('B' << 0) | ('S' << 8) | ('P' << 16) | ('2'<<24)) #define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH" #define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior // ver. 3 was occupied by experimental versions of P2:savior change fmt #define DELUXEMAP_VERSION 1 #define IDDELUXEMAPHEADER (('T'<<24)+('I'<<16)+('L'<<8)+'Q') // little-endian "QLIT" // worldcraft predefined angles #define ANGLE_UP -1 #define ANGLE_DOWN -2 // bmodel limits #define MAX_MAP_HULLS 4 // MAX_HULLS #define SURF_PLANEBACK BIT( 1 ) // plane should be negated #define SURF_DRAWSKY BIT( 2 ) // sky surface #define SURF_DRAWTURB_QUADS BIT( 3 ) // all subidivided polygons are quads #define SURF_DRAWTURB BIT( 4 ) // warp surface #define SURF_DRAWTILED BIT( 5 ) // face without lighmap #define SURF_CONVEYOR BIT( 6 ) // scrolled texture (was SURF_DRAWBACKGROUND) #define SURF_UNDERWATER BIT( 7 ) // caustics #define SURF_TRANSPARENT BIT( 8 ) // it's a transparent texture (was SURF_DONTWARP) // lightstyle management #define LM_STYLES 4 // MAXLIGHTMAPS #define LS_NORMAL 0x00 #define LS_UNUSED 0xFE #define LS_NONE 0xFF #define MAX_MAP_CLIPNODES_HLBSP 32767 #define MAX_MAP_CLIPNODES_BSP2 524288 // these limis not using by modelloader but only for displaying 'mapstats' correctly #define MAX_MAP_MODELS 2048 // embedded models #define MAX_MAP_ENTSTRING 0x200000 // 2 Mb should be enough #define MAX_MAP_PLANES 131072 // can be increased without problems #define MAX_MAP_NODES 262144 // can be increased without problems #define MAX_MAP_CLIPNODES MAX_MAP_CLIPNODES_BSP2 // can be increased without problems #define MAX_MAP_LEAFS 131072 // CRITICAL STUFF to run ad_sepulcher!!! #define MAX_MAP_VERTS 524288 // can be increased without problems #define MAX_MAP_FACES 262144 // can be increased without problems #define MAX_MAP_MARKSURFACES 524288 // can be increased without problems #define MAX_MAP_ENTITIES 8192 // network limit #define MAX_MAP_TEXINFO MAX_MAP_FACES // in theory each face may have personal texinfo #define MAX_MAP_EDGES 0x100000 // can be increased but not needs #define MAX_MAP_SURFEDGES 0x200000 // can be increased but not needs #define MAX_MAP_TEXTURES 2048 // can be increased but not needs #define MAX_MAP_MIPTEX 0x2000000 // 32 Mb internal textures data #define MAX_MAP_LIGHTING 0x2000000 // 32 Mb lightmap raw data (can contain deluxemaps) #define MAX_MAP_VISIBILITY 0x1000000 // 16 Mb visdata #define MAX_MAP_FACEINFO 8192 // can be increased but not needs // quake lump ordering #define LUMP_ENTITIES 0 #define LUMP_PLANES 1 #define LUMP_TEXTURES 2 // internal textures #define LUMP_VERTEXES 3 #define LUMP_VISIBILITY 4 #define LUMP_NODES 5 #define LUMP_TEXINFO 6 #define LUMP_FACES 7 #define LUMP_LIGHTING 8 #define LUMP_CLIPNODES 9 #define LUMP_LEAFS 10 #define LUMP_MARKSURFACES 11 #define LUMP_EDGES 12 #define LUMP_SURFEDGES 13 #define LUMP_MODELS 14 // internal submodels #define HEADER_LUMPS 15 // extra lump ordering #define LUMP_LIGHTVECS 0 // deluxemap data #define LUMP_FACEINFO 1 // landscape and lightmap resolution info #define LUMP_CUBEMAPS 2 // cubemap description #define LUMP_VERTNORMALS 3 // phong shaded vertex normals #define LUMP_LEAF_LIGHTING 4 // store vertex lighting for statics #define LUMP_WORLDLIGHTS 5 // list of all the virtual and real lights (used to relight models in-game) #define LUMP_COLLISION 6 // physics engine collision hull dump (userdata) #define LUMP_AINODEGRAPH 7 // node graph that stored into the bsp (userdata) #define LUMP_SHADOWMAP 8 // contains shadow map for direct light #define LUMP_VERTEX_LIGHT 9 // store vertex lighting for statics #define LUMP_UNUSED0 10 // one lump reserved for me #define LUMP_UNUSED1 11 // one lump reserved for me #define EXTRA_LUMPS 12 // count of the extra lumps // texture flags #define TEX_SPECIAL BIT( 0 ) // sky or slime, no lightmap or 256 subdivision #define TEX_WORLD_LUXELS BIT( 1 ) // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels) #define TEX_AXIAL_LUXELS BIT( 2 ) // force world luxels to axial positive scales #define TEX_EXTRA_LIGHTMAP BIT( 3 ) // bsp31 legacy - using 8 texels per luxel instead of 16 texels per luxel #define TEX_SCROLL BIT( 6 ) // Doom special FX #define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) // ambient sound types enum { AMBIENT_WATER = 0, // waterfall AMBIENT_SKY, // wind AMBIENT_SLIME, // never used in quake AMBIENT_LAVA, // never used in quake NUM_AMBIENTS, // automatic ambient sounds }; // // BSP File Structures // typedef struct { int fileofs; int filelen; } dlump_t; typedef struct { int version; dlump_t lumps[HEADER_LUMPS]; } dheader_t; typedef struct { int id; // must be little endian XASH int version; dlump_t lumps[EXTRA_LUMPS]; } dextrahdr_t; typedef struct { vec3_t mins; vec3_t maxs; vec3_t origin; // for sounds or lights int headnode[MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface; int numfaces; } dmodel_t; typedef struct { int nummiptex; int dataofs[4]; // [nummiptex] } dmiptexlump_t; typedef struct { vec3_t point; } dvertex_t; typedef struct { vec3_t normal; float dist; int type; // PLANE_X - PLANE_ANYZ ? } dplane_t; typedef struct { int planenum; short children[2]; // negative numbers are -(leafs + 1), not nodes short mins[3]; // for sphere culling short maxs[3]; word firstface; word numfaces; // counting both sides } dnode_t; typedef struct { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes float mins[3]; // for sphere culling float maxs[3]; int firstface; int numfaces; // counting both sides } dnode32_t; // leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas // all other leafs need visibility info typedef struct { int contents; int visofs; // -1 = no visibility info short mins[3]; // for frustum culling short maxs[3]; word firstmarksurface; word nummarksurfaces; // automatic ambient sounds byte ambient_level[NUM_AMBIENTS]; // ambient sound level (0 - 255) } dleaf_t; typedef struct { int contents; int visofs; // -1 = no visibility info float mins[3]; // for frustum culling float maxs[3]; int firstmarksurface; int nummarksurfaces; byte ambient_level[NUM_AMBIENTS]; } dleaf32_t; typedef struct { int planenum; short children[2]; // negative numbers are contents } dclipnode_t; typedef struct { int planenum; int children[2]; // negative numbers are contents } dclipnode32_t; typedef struct { float vecs[2][4]; // texmatrix [s/t][xyz offset] int miptex; short flags; short faceinfo; // -1 no face info otherwise dfaceinfo_t } dtexinfo_t; typedef struct { char landname[16]; // name of decsription in mapname_land.txt unsigned short texture_step; // default is 16, pixels\luxels ratio unsigned short max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) short groupid; // to determine equal landscapes from various groups, -1 - no group } dfaceinfo_t; typedef word dmarkface_t; // leaf marksurfaces indexes typedef int dmarkface32_t; // leaf marksurfaces indexes typedef int dsurfedge_t; // map surfedges // NOTE: that edge 0 is never used, because negative edge nums // are used for counterclockwise use of the edge in a face typedef struct { word v[2]; // vertex numbers } dedge_t; typedef struct { int v[2]; // vertex numbers } dedge32_t; typedef struct { word planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[LM_STYLES]; int lightofs; // start of [numstyles*surfsize] samples } dface_t; typedef struct { int planenum; int side; int firstedge; // we must support > 64k edges int numedges; int texinfo; // lighting info byte styles[LM_STYLES]; int lightofs; // start of [numstyles*surfsize] samples } dface32_t; #endif//BSPFILE_H ================================================ FILE: common/cl_entity.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef CL_ENTITY_H #define CL_ENTITY_H typedef struct efrag_s { struct mleaf_s *leaf; struct efrag_s *leafnext; struct cl_entity_s *entity; struct efrag_s *entnext; } efrag_t; typedef struct { byte mouthopen; // 0 = mouth closed, 255 = mouth agape byte sndcount; // counter for running average int sndavg; // running average } mouth_t; typedef struct { float prevanimtime; float sequencetime; byte prevseqblending[2]; vec3_t prevorigin; vec3_t prevangles; int prevsequence; float prevframe; byte prevcontroller[4]; byte prevblending[2]; } latchedvars_t; typedef struct { // Time stamp for this movement float animtime; vec3_t origin; vec3_t angles; } position_history_t; typedef struct cl_entity_s cl_entity_t; #define HISTORY_MAX 64 // Must be power of 2 #define HISTORY_MASK ( HISTORY_MAX - 1 ) #include "entity_state.h" #include "event_args.h" struct cl_entity_s { int index; // Index into cl_entities ( should match actual slot, but not necessarily ) qboolean player; // True if this entity is a "player" entity_state_t baseline; // The original state from which to delta during an uncompressed message entity_state_t prevstate; // The state information from the penultimate message received from the server entity_state_t curstate; // The state information from the last message received from server int current_position; // Last received history update index position_history_t ph[HISTORY_MAX]; // History of position and angle updates for this player mouth_t mouth; // For synchronizing mouth movements. latchedvars_t latched; // Variables used by studio model rendering routines // Information based on interplocation, extrapolation, prediction, or just copied from last msg received. // float lastmove; // Actual render position and angles vec3_t origin; vec3_t angles; // Attachment points vec3_t attachment[4]; // Other entity local information int trivial_accept; struct model_s *model; // cl.model_precache[ curstate.modelindes ]; all visible entities have a model struct efrag_s *efrag; // linked list of efrags struct mnode_s *topnode; // for bmodels, first world node that splits bmodel, or NULL if not split float syncbase; // for client-side animations -- used by obsolete alias animation system, remove? int visframe; // last frame this entity was found in an active leaf colorVec cvFloorColor; }; #endif//CL_ENTITY_H ================================================ FILE: common/com_image.h ================================================ #pragma once /* ======================================================================== internal image format typically expanded to rgba buffer NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == PF_RGB_888 ======================================================================== */ #define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24 || type == PF_LUMINANCE) #define ImageCompressed( type ) \ ( type == PF_DXT1 \ || type == PF_DXT3 \ || type == PF_DXT5 \ || type == PF_ATI2 \ || type == PF_BC4_SIGNED \ || type == PF_BC4_UNSIGNED \ || type == PF_BC5_SIGNED \ || type == PF_BC5_UNSIGNED \ || type == PF_BC6H_SIGNED \ || type == PF_BC6H_UNSIGNED \ || type == PF_BC7_UNORM \ || type == PF_BC7_SRGB \ || type == PF_KTX2_RAW ) typedef enum { PF_UNKNOWN = 0, PF_INDEXED_24, // inflated palette (768 bytes) PF_INDEXED_32, // deflated palette (1024 bytes) PF_RGBA_32, // normal rgba buffer PF_BGRA_32, // big endian RGBA (MacOS) PF_RGB_24, // uncompressed dds or another 24-bit image PF_BGR_24, // big-endian RGB (MacOS) PF_LUMINANCE, PF_DXT1, // s3tc DXT1/BC1 format PF_DXT3, // s3tc DXT3/BC2 format PF_DXT5, // s3tc DXT5/BC3 format PF_ATI2, // latc ATI2N/BC5 format PF_BC4_SIGNED, PF_BC4_UNSIGNED, PF_BC5_SIGNED, PF_BC5_UNSIGNED, PF_BC6H_SIGNED, // bptc BC6H signed FP16 format PF_BC6H_UNSIGNED, // bptc BC6H unsigned FP16 format PF_BC7_UNORM, // bptc BC7 format PF_BC7_SRGB, PF_KTX2_RAW, // Raw KTX2 data, used for yet unsupported KTX2 subformats PF_TOTALCOUNT, // must be last } pixformat_t; typedef struct bpc_desc_s { int format; // pixelformat char name[16]; // used for debug uint glFormat; // RGBA format int bpp; // channels (e.g. rgb = 3, rgba = 4) } bpc_desc_t; // imagelib global settings typedef enum { IL_USE_LERPING = BIT(0), // lerping images during resample IL_KEEP_8BIT = BIT(1), // don't expand paletted images IL_ALLOW_OVERWRITE = BIT(2), // allow to overwrite stored images IL_DONTFLIP_TGA = BIT(3), // Steam background completely ignore tga attribute 0x20 (stupid lammers!) IL_DDS_HARDWARE = BIT(4), // DXT compression is support IL_LOAD_DECAL = BIT(5), // special mode for load gradient decals IL_OVERVIEW = BIT(6), // overview required some unque operations IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals IL_KTX2_RAW = BIT(8), // renderer can consume raw KTX2 files (e.g. ref_vk) } ilFlags_t; // goes into rgbdata_t->encode #define DXT_ENCODE_DEFAULT 0 // don't use custom encoders #define DXT_ENCODE_COLOR_YCoCg 0x1A01 // make sure that value dosn't collide with anything #define DXT_ENCODE_ALPHA_1BIT 0x1A02 // normal 1-bit alpha #define DXT_ENCODE_ALPHA_8BIT 0x1A03 // normal 8-bit alpha #define DXT_ENCODE_ALPHA_SDF 0x1A04 // signed distance field #define DXT_ENCODE_NORMAL_AG_ORTHO 0x1A05 // orthographic projection #define DXT_ENCODE_NORMAL_AG_STEREO 0x1A06 // stereographic projection #define DXT_ENCODE_NORMAL_AG_PARABOLOID 0x1A07 // paraboloid projection #define DXT_ENCODE_NORMAL_AG_QUARTIC 0x1A08 // newton method #define DXT_ENCODE_NORMAL_AG_AZIMUTHAL 0x1A09 // Lambert Azimuthal Equal-Area // rgbdata output flags typedef enum { // rgbdata->flags IMAGE_CUBEMAP = BIT(0), // it's 6-sides cubemap buffer IMAGE_HAS_ALPHA = BIT(1), // image contain alpha-channel IMAGE_HAS_COLOR = BIT(2), // image contain RGB-channel IMAGE_COLORINDEX = BIT(3), // all colors in palette is gradients of last color (decals) IMAGE_HAS_LUMA = BIT(4), // image has luma pixels (q1-style maps) IMAGE_SKYBOX = BIT(5), // only used by FS_SaveImage - for write right suffixes IMAGE_QUAKESKY = BIT(6), // it's a quake sky double layered clouds (so keep it as 8 bit) IMAGE_DDS_FORMAT = BIT(7), // a hint for GL loader IMAGE_MULTILAYER = BIT(8), // to differentiate from 3D texture IMAGE_ONEBIT_ALPHA = BIT(9), // binary alpha IMAGE_QUAKEPAL = BIT(10), // image has quake1 palette // Image_Process manipulation flags IMAGE_FLIP_X = BIT(16), // flip the image by width IMAGE_FLIP_Y = BIT(17), // flip the image by height IMAGE_ROT_90 = BIT(18), // flip from upper left corner to down right corner IMAGE_ROT180 = IMAGE_FLIP_X|IMAGE_FLIP_Y, IMAGE_ROT270 = IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90, // reserved IMAGE_RESAMPLE = BIT(20), // resample image to specified dims // reserved // reserved IMAGE_FORCE_RGBA = BIT(23), // force image to RGBA buffer IMAGE_MAKE_LUMA = BIT(24), // create luma texture from indexed IMAGE_QUANTIZE = BIT(25), // make indexed image from 24 or 32- bit image IMAGE_LIGHTGAMMA = BIT(26), // apply gamma for image IMAGE_REMAP = BIT(27), // interpret width and height as top and bottom color } imgFlags_t; typedef struct rgbdata_s { word width; // image width word height; // image height word depth; // image depth uint type; // compression type uint flags; // misc image flags word encode; // DXT may have custom encoder, that will be decoded in GLSL-side byte numMips; // mipmap count byte *palette; // palette if present byte *buffer; // image buffer rgba_t fogParams; // some water textures in hl1 has info about fog color and alpha size_t size; // for bounds checking } rgbdata_t; ================================================ FILE: common/com_model.h ================================================ /* com_model.h - cient model structures Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef COM_MODEL_H #define COM_MODEL_H #include "xash3d_types.h" #include "bspfile.h" // we need some declarations from it /* ============================================================================== ENGINE MODEL FORMAT ============================================================================== */ #define STUDIO_RENDER 1 #define STUDIO_EVENTS 2 #define ZISCALE ((float)0x8000) #define MIPLEVELS 4 #define VERTEXSIZE 7 #define MAXLIGHTMAPS 4 // max light styles per face #define MAXDYNLIGHTS 8 // maximum dynamic lights per one pixel #define NUM_AMBIENTS 4 // automatic ambient sounds // model types typedef enum { mod_bad = -1, mod_brush, mod_sprite, mod_alias, mod_studio } modtype_t; typedef struct mplane_s { vec3_t normal; float dist; byte type; // for fast side tests byte signbits; // signx + (signy<<1) + (signz<<1) byte pad[2]; } mplane_t; typedef struct { vec3_t position; } mvertex_t; typedef struct mclipnode32_s { int planenum; int children[2]; // negative numbers are contents } mclipnode32_t; typedef struct mclipnode16_s { int planenum; short children[2]; // negative numbers are contents } mclipnode16_t; // size is matched but representation is not typedef struct medge32_s { unsigned int v[2]; } medge32_t; typedef struct medge16_s { unsigned short v[2]; unsigned int cachededgeoffset; } medge16_t; typedef struct texture_s { char name[16]; unsigned int width, height; int gl_texturenum; struct msurface_s *texturechain; // for gl_texsort drawing int anim_total; // total tenths in sequence ( 0 = no) int anim_min, anim_max; // time for this frame min <=time< max struct texture_s *anim_next; // in the animation sequence struct texture_s *alternate_anims; // bmodels in frame 1 use these unsigned short fb_texturenum; // auto-luma texturenum unsigned short dt_texturenum; // detail-texture binding unsigned int unused[3]; // reserved } texture_t; typedef struct { char landname[16]; // name of decsription in mapname_land.txt unsigned short texture_step; // default is 16, pixels\luxels ratio unsigned short max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) short groupid; // to determine equal landscapes from various groups, -1 - no group vec3_t mins, maxs; // terrain bounds (fill by user) intptr_t reserved[32]; // just for future expansions or mod-makers } mfaceinfo_t; typedef struct { mplane_t *edges; int numedges; vec3_t origin; vec_t radius; // for culling tests int contents; // sky or solid } mfacebevel_t; typedef struct { float vecs[2][4]; // [s/t] unit vectors in world space. // [i][3] is the s/t offset relative to the origin. // s or t = dot( 3Dpoint, vecs[i] ) + vecs[i][3] mfaceinfo_t *faceinfo; // pointer to landscape info and lightmap resolution (may be NULL) texture_t *texture; int flags; // sky or slime, no lightmap or 256 subdivision } mtexinfo_t; // a1ba: changed size to avoid undefined behavior. Check your allocations if you take this header! // For example: // before: malloc( sizeof( glpoly_t ) + ( numverts - 4 ) * VERTEXSIZE * sizeof( float )) // after (C): malloc( sizeof( glpoly_t ) + numverts * VERTEXSIZE * sizeof( float )) // after (C++): malloc( sizeof( glpoly_t ) + ( numverts - 1 ) * VERTEXSIZE * sizeof( float )) typedef struct glpoly2_s { struct glpoly2_s *next; struct glpoly2_s *chain; int numverts; int flags; // for SURF_UNDERWATER #ifdef __cplusplus float verts[1][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) #else float verts[][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) #endif } glpoly2_t; typedef struct mnode_s { // common with leaf int contents; // 0, to differentiate from leafs int visframe; // node needs to be traversed if current float minmaxs[6]; // for bounding box culling struct mnode_s *parent; // node specific mplane_t *plane; #if !XASH_64BIT union { struct mnode_s *children_[2]; struct { // the ordering is important int child_0_leaf : 1; int child_0_off : 23; int firstsurface_1 : 8; int child_1_leaf : 1; int child_1_off : 23; int numsurfaces_1 : 8; }; }; unsigned short firstsurface_0; unsigned short numsurfaces_0; #else // in 64-bit ABI this struct has 4 more bytes of padding, let's use it! struct mnode_s *children_[2]; unsigned short firstsurface_0; unsigned short numsurfaces_0; unsigned short firstsurface_1; unsigned short numsurfaces_1; #endif } mnode_t; typedef struct msurface_s msurface_t; typedef struct decal_s decal_t; // JAY: Compress this as much as possible struct decal_s { decal_t *pnext; // linked list for each surface msurface_t *psurface; // Surface id for persistence / unlinking float dx; // local texture coordinates float dy; // float scale; // Pixel scale short texture; // Decal texture short flags; // Decal flags FDECAL_* short entityIndex; // Entity this is attached to // Xash3D specific vec3_t position; // location of the decal center in world space. glpoly2_t *polys; // precomputed decal vertices intptr_t reserved[4]; // just for future expansions or mod-makers }; typedef struct mleaf_s { // common with node int contents; int visframe; // node needs to be traversed if current float minmaxs[6]; // for bounding box culling struct mnode_s *parent; // leaf specific byte *compressed_vis; struct efrag_s *efrags; msurface_t **firstmarksurface; int nummarksurfaces; int cluster; // helper to acess to uncompressed visdata byte ambient_sound_level[NUM_AMBIENTS]; } mleaf_t; // surface extradata typedef struct mextrasurf_s { vec3_t mins, maxs; vec3_t origin; // surface origin struct msurface_s *surf; // upcast to surface // extended light info int dlight_s, dlight_t; // gl lightmap coordinates for dynamic lightmaps short lightmapmins[2]; // lightmatrix short lightextents[2]; float lmvecs[2][4]; color24 *deluxemap; // note: this is the actual deluxemap data for this surface byte *shadowmap; // note: occlusion map for this surface // begin userdata struct msurface_s *lightmapchain; // lightmapped polys struct mextrasurf_s *detailchain; // for detail textures drawing mfacebevel_t *bevel; // for exact face traceline struct mextrasurf_s *lumachain; // draw fullbrights struct cl_entity_s *parent; // upcast to owner entity int mirrortexturenum; // gl texnum float mirrormatrix[4][4]; struct grasshdr_s *grass; // grass that linked by this surface unsigned short grasscount; // number of bushes per polygon (used to determine total VBO size) unsigned short numverts; // world->vertexes[] int firstvertex; // fisrt look up in tr.tbn_vectors[], then acess to world->vertexes[] intptr_t reserved[32]; // just for future expansions or mod-makers } mextrasurf_t; #ifdef SUPPORT_HL25_EXTENDED_STRUCTS // additional struct at the end of msurface_t for HL25 compatibility typedef struct mdisplaylist_s { unsigned int gl_displaylist; int rendermode; float scrolloffset; int renderDetailTexture; } mdisplaylist_t; #endif struct msurface_s { int visframe; // should be drawn when node is crossed mplane_t *plane; // pointer to shared plane int flags; // see SURF_ #defines int firstedge; // look up in model->surfedges[], negative numbers int numedges; // are backwards edges short texturemins[2]; short extents[2]; int light_s, light_t; // gl lightmap coordinates glpoly2_t *polys; // multiple if warped struct msurface_s *texturechain; mtexinfo_t *texinfo; // lighting info int dlightframe; // last frame the surface was checked by an animated light int dlightbits; // dynamically generated. Indicates if the surface illumination // is modified by an animated light. int lightmaptexturenum; byte styles[MAXLIGHTMAPS]; int cached_light[MAXLIGHTMAPS]; // values currently used in lightmap mextrasurf_t *info; // pointer to surface extradata (was cached_dlight) color24 *samples; // note: this is the actual lightmap data for this surface decal_t *pdecals; #ifdef SUPPORT_HL25_EXTENDED_STRUCTS mdisplaylist_t displaylist; #endif }; typedef struct hull_s { union { mclipnode16_t *clipnodes16; mclipnode32_t *clipnodes32; }; mplane_t *planes; int firstclipnode; int lastclipnode; vec3_t clip_mins; vec3_t clip_maxs; } hull_t; #ifndef CACHE_USER #define CACHE_USER typedef struct cache_user_s { void *data; // extradata } cache_user_t; #endif typedef struct model_s { char name[64]; // model name qboolean needload; // bmodels and sprites don't cache normally // shared modelinfo modtype_t type; // model type int numframes; // sprite's framecount poolhandle_t mempool; // private mempool (was synctype) int flags; // hl compatibility // // volume occupied by the model // vec3_t mins, maxs; // bounding box at angles '0 0 0' float radius; // brush model int firstmodelsurface; int nummodelsurfaces; int numsubmodels; dmodel_t *submodels; // or studio animations int numplanes; mplane_t *planes; int numleafs; // number of visible leafs, not counting 0 mleaf_t *leafs; int numvertexes; mvertex_t *vertexes; int numedges; union { medge16_t *edges16; medge32_t *edges32; }; int numnodes; mnode_t *nodes; int numtexinfo; mtexinfo_t *texinfo; int numsurfaces; msurface_t *surfaces; int numsurfedges; int *surfedges; int numclipnodes; union { mclipnode16_t *clipnodes16; mclipnode32_t *clipnodes32; }; int nummarksurfaces; msurface_t **marksurfaces; hull_t hulls[MAX_MAP_HULLS]; int numtextures; texture_t **textures; byte *visdata; color24 *lightdata; char *entities; // // additional model data // cache_user_t cache; // only access through Mod_Extradata } model_t; typedef struct alight_s { int ambientlight; // clip at 128 int shadelight; // clip at 192 - ambientlight vec3_t color; float *plightvec; } alight_t; typedef struct auxvert_s { float fv[3]; // viewspace x, y } auxvert_t; #define MAX_SCOREBOARDNAME 32 #define MAX_INFO_STRING 256 #include "custom.h" typedef struct player_info_s { int userid; // User id on server char userinfo[MAX_INFO_STRING]; // User info string char name[MAX_SCOREBOARDNAME]; // Name (extracted from userinfo) int spectator; // Spectator or not, unused (frags for quake demo playback) int ping; int packet_loss; // skin information char model[64]; int topcolor; int bottomcolor; // last frame rendered int renderframe; // Gait frame estimation int gaitsequence; float gaitframe; float gaityaw; vec3_t prevgaitorigin; customization_t customdata; // hashed cd key char hashedcdkey[16]; } player_info_t; // // sprite representation in memory // typedef enum { SPR_SINGLE = 0, SPR_GROUP, SPR_ANGLED } spriteframetype_t; typedef struct mspriteframe_s { int width; int height; float up, down, left, right; int gl_texturenum; } mspriteframe_t; typedef struct { int numframes; float *intervals; mspriteframe_t *frames[1]; } mspritegroup_t; typedef struct { spriteframetype_t type; mspriteframe_t *frameptr; } mspriteframedesc_t; typedef struct { short type; short texFormat; int maxwidth; int maxheight; int numframes; int radius; int facecull; int synctype; mspriteframedesc_t frames[1]; } msprite_t; /* ============================================================================== ALIAS MODELS Alias models are position independent, so the cache manager can move them. ============================================================================== */ #define MAXALIASVERTS 2048 #define MAXALIASFRAMES 256 #define MAXALIASTRIS 4096 #define MAX_SKINS 32 // This mirrors trivert_t in trilib.h, is present so Quake knows how to // load this data typedef struct { byte v[3]; byte lightnormalindex; } trivertex_t; typedef struct { int firstpose; int numposes; trivertex_t bboxmin; trivertex_t bboxmax; float interval; char name[16]; } maliasframedesc_t; typedef struct { int ident; int version; vec3_t scale; vec3_t scale_origin; float boundingradius; vec3_t eyeposition; int numskins; int skinwidth; int skinheight; int numverts; int numtris; int numframes; int synctype; int flags; float size; const trivertex_t **pposeverts; // only valid during loading, used to build GL mesh intptr_t reserved[7]; // VBO offsets int numposes; int poseverts; trivertex_t *posedata; // numposes * poseverts trivert_t int *commands; // gl command list with embedded s/t unsigned short gl_texturenum[MAX_SKINS][4]; unsigned short fb_texturenum[MAX_SKINS][4]; unsigned short gl_reserved0[MAX_SKINS][4]; // detail tex unsigned short gl_reserved1[MAX_SKINS][4]; // normalmap unsigned short gl_reserved2[MAX_SKINS][4]; // glossmap maliasframedesc_t frames[1]; // variable sized } aliashdr_t; // remapping info #define SUIT_HUE_START 192 #define SUIT_HUE_END 223 #define PLATE_HUE_START 160 #define PLATE_HUE_END 191 #define SHIRT_HUE_START 16 #define SHIRT_HUE_END 32 #define PANTS_HUE_START 96 #define PANTS_HUE_END 112 // 1/32 epsilon to keep floating point happy #define DIST_EPSILON (1.0f / 32.0f) #define FRAC_EPSILON (1.0f / 1024.0f) #define BACKFACE_EPSILON 0.01f #define MAX_BOX_LEAFS 256 #define ANIM_CYCLE 2 #define MOD_FRAMES 20 #define MAX_DEMOS 32 #define MAX_MOVIES 8 #define MAX_CDTRACKS 32 #define MAX_CLIENT_SPRITES 512 // SpriteTextures (0-256 hud, 256-512 client) #define MAX_REQUESTS 64 STATIC_CHECK_SIZEOF( mnode_t, 52, 72 ); STATIC_CHECK_SIZEOF( mextrasurf_t, 324, 496 ); STATIC_CHECK_SIZEOF( decal_t, 60, 88 ); STATIC_CHECK_SIZEOF( mfaceinfo_t, 176, 304 ); // model flags (stored in model_t->flags) #define MODEL_QBSP2 BIT( 28 ) // uses 32-bit types // access functions static inline mnode_t *node_child( const mnode_t *n, int side, const model_t *mod ) { #if !XASH_64BIT if( unlikely( mod->flags & MODEL_QBSP2 )) // MODEL_QBSP2 { if( side == 0 ) { if( n->child_0_leaf ) return (mnode_t *)(mod->leafs + n->child_0_off); else return (mnode_t *)(mod->nodes + n->child_0_off); } else { if( n->child_1_leaf ) return (mnode_t *)(mod->leafs + n->child_1_off); else return (mnode_t *)(mod->nodes + n->child_1_off); } } return n->children_[side]; #else return n->children_[side]; #endif } static inline void node_children( mnode_t *children[2], const mnode_t *n, const model_t *mod ) { children[0] = node_child( n, 0, mod ); children[1] = node_child( n, 1, mod ); } static inline int node_firstsurface( const mnode_t *n, const model_t *mod ) { if( mod->flags & MODEL_QBSP2 ) return n->firstsurface_0 + ( n->firstsurface_1 << 16 ); else return n->firstsurface_0; } static inline int node_numsurfaces( const mnode_t *n, const model_t *mod ) { if( mod->flags & MODEL_QBSP2 ) return n->numsurfaces_0 + ( n->numsurfaces_1 << 16 ); else return n->numsurfaces_0; } #endif//COM_MODEL_H ================================================ FILE: common/con_nprint.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef CON_NPRINT_H #define CON_NPRINT_H typedef struct con_nprint_s { int index; // Row # float time_to_live; // # of seconds before it dissappears float color[3]; // RGB colors ( 0.0 -> 1.0 scale ) } con_nprint_t; #endif//CON_NPRINT_H ================================================ FILE: common/const.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef CONST_H #define CONST_H // // Constants shared by the engine and dlls // This header file included by engine files and DLL files. // Most came from server.h // edict->flags #define FL_FLY (1U<<0) // Changes the SV_Movestep() behavior to not need to be on ground #define FL_SWIM (1U<<1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) #define FL_CONVEYOR (1U<<2) #define FL_CLIENT (1U<<3) #define FL_INWATER (1U<<4) #define FL_MONSTER (1U<<5) #define FL_GODMODE (1U<<6) #define FL_NOTARGET (1U<<7) #define FL_SKIPLOCALHOST (1U<<8) // Don't send entity to local host, it's predicting this entity itself #define FL_ONGROUND (1U<<9) // At rest / on the ground #define FL_PARTIALGROUND (1U<<10) // not all corners are valid #define FL_WATERJUMP (1U<<11) // player jumping out of water #define FL_FROZEN (1U<<12) // Player is frozen for 3rd person camera #define FL_FAKECLIENT (1U<<13) // JAC: fake client, simulated server side; don't send network messages to them #define FL_DUCKING (1U<<14) // Player flag -- Player is fully crouched #define FL_FLOAT (1U<<15) // Apply floating force to this entity when in water #define FL_GRAPHED (1U<<16) // worldgraph has this ent listed as something that blocks a connection // UNDONE: Do we need these? #define FL_IMMUNE_WATER (1U<<17) #define FL_IMMUNE_SLIME (1U<<18) #define FL_IMMUNE_LAVA (1U<<19) #define FL_PROXY (1U<<20) // This is a spectator proxy #define FL_ALWAYSTHINK (1U<<21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) #define FL_BASEVELOCITY (1U<<22) // Base velocity has been applied this frame (used to convert base velocity into momentum) #define FL_MONSTERCLIP (1U<<23) // Only collide in with monsters who have FL_MONSTERCLIP set #define FL_ONTRAIN (1U<<24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. #define FL_WORLDBRUSH (1U<<25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) #define FL_SPECTATOR (1U<<26) // This client is a spectator, don't run touch functions, etc. #define FL_LASERDOT (1U<<27) // Predicted laser spot from rocket launcher #define FL_CUSTOMENTITY (1U<<29) // This is a custom entity #define FL_KILLME (1U<<30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time #define FL_DORMANT (1U<<31) // Entity is dormant, no updates to client // Goes into globalvars_t.trace_flags #define FTRACE_SIMPLEBOX (1U<<0) // Traceline with a simple box #define FTRACE_IGNORE_GLASS (1U<<1) // traceline will be ignored entities with rendermode != kRenderNormal // walkmove modes #define WALKMOVE_NORMAL 0 // normal walkmove #define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type #define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers // edict->movetype values #define MOVETYPE_NONE 0 // never moves //#define MOVETYPE_ANGLENOCLIP 1 //#define MOVETYPE_ANGLECLIP 2 #define MOVETYPE_WALK 3 // Player only - moving on the ground #define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this #define MOVETYPE_FLY 5 // No gravity, but still collides with stuff #define MOVETYPE_TOSS 6 // gravity/collisions #define MOVETYPE_PUSH 7 // no clip to world, push and crush #define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity #define MOVETYPE_FLYMISSILE 9 // extra size to monsters #define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces #define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity #define MOVETYPE_FOLLOW 12 // track movement of aiment #define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) #define MOVETYPE_COMPOUND 14 // glue two entities together (simple movewith) // edict->solid values // NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves // SOLID only effects OTHER entities colliding with this one when they move - UGH! #define SOLID_NOT 0 // no interaction with other objects #define SOLID_TRIGGER 1 // touch on edge, but not blocking #define SOLID_BBOX 2 // touch on edge, block #define SOLID_SLIDEBOX 3 // touch on edge, but not an onground #define SOLID_BSP 4 // bsp clip, touch on edge, block #define SOLID_CUSTOM 5 // call external callbacks for tracing #define SOLID_PORTAL 6 // borrowed from FTE // edict->deadflag values #define DEAD_NO 0 // alive #define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground #define DEAD_DEAD 2 // dead. lying still. #define DEAD_RESPAWNABLE 3 #define DEAD_DISCARDBODY 4 #define DAMAGE_NO 0 #define DAMAGE_YES 1 #define DAMAGE_AIM 2 // entity effects #define EF_BRIGHTFIELD 1 // swirling cloud of particles #define EF_MUZZLEFLASH 2 // single frame ELIGHT on entity attachment 0 #define EF_BRIGHTLIGHT 4 // DLIGHT centered at entity origin #define EF_DIMLIGHT 8 // player flashlight #define EF_INVLIGHT 16 // get lighting from ceiling #define EF_NOINTERP 32 // don't interpolate the next frame #define EF_LIGHT 64 // rocket flare glow sprite #define EF_NODRAW 128 // don't draw entity #define EF_WATERSIDES (1U<<26) // Do not remove sides for func_water entity #define EF_FULLBRIGHT (1U<<27) // Just get fullbright #define EF_NOSHADOW (1U<<28) // ignore shadow for this entity #define EF_MERGE_VISIBILITY (1U<<29) // this entity allowed to merge vis (e.g. env_sky or portal camera) #define EF_REQUEST_PHS (1U<<30) // This entity requested phs bitvector instead of pvsbitvector in AddToFullPack calls // g-cont. one reserved bit here for me // entity flags #define EFLAG_SLERP 1 // do studio interpolation of this entity // // temp entity events // #define TE_BEAMPOINTS 0 // beam effect between two points // coord coord coord (start position) // coord coord coord (end position) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_BEAMENTPOINT 1 // beam effect between point and entity // short (start entity) // coord coord coord (end position) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_GUNSHOT 2 // particle effect plus ricochet sound // coord coord coord (position) #define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps // coord coord coord (position) // short (sprite index) // byte (scale in 0.1's) // byte (framerate) // byte (flags) // // The Explosion effect has some flags to control performance/aesthetic features: #define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion #define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) #define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights #define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound #define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles #define TE_EXPLFLAG_DRAWALPHA 16 // sprite will be drawn alpha #define TE_EXPLFLAG_ROTATE 32 // rotate the sprite randomly #define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound // coord coord coord (position) #define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps // coord coord coord (position) // short (sprite index) // byte (scale in 0.1's) // byte (framerate) #define TE_TRACER 6 // tracer effect from point to point // coord, coord, coord (start) // coord, coord, coord (end) #define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters // coord, coord, coord (start) // coord, coord, coord (end) // byte (life in 0.1's) // byte (width in 0.1's) // byte (amplitude in 0.01's) // short (sprite model index) #define TE_BEAMENTS 8 // short (start entity) // short (end entity) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite // coord coord coord (position) #define TE_LAVASPLASH 10 // Quake1 lava splash // coord coord coord (position) #define TE_TELEPORT 11 // Quake1 teleport splash // coord coord coord (position) #define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound // coord coord coord (position) // byte (starting color) // byte (num colors) #define TE_BSPDECAL 13 // Decal from the .BSP file // coord, coord, coord (x,y,z), decal position (center of texture in world) // short (texture index of precached decal texture name) // short (entity index) // [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) #define TE_IMPLOSION 14 // tracers moving toward a point // coord, coord, coord (position) // byte (radius) // byte (count) // byte (life in 0.1's) #define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions // coord, coord, coord (start) // coord, coord, coord (end) // short (sprite index) // byte (count) // byte (life in 0.1's) // byte (scale in 0.1's) // byte (velocity along vector in 10's) // byte (randomness of velocity in 10's) #define TE_BEAM 16 // obsolete #define TE_SPRITE 17 // additive sprite, plays 1 cycle // coord, coord, coord (position) // short (sprite index) // byte (scale in 0.1's) // byte (brightness) #define TE_BEAMSPRITE 18 // A beam with a sprite at the end // coord, coord, coord (start position) // coord, coord, coord (end position) // short (beam sprite index) // short (end sprite index) #define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime // coord coord coord (center position) // coord coord coord (axis and radius) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_BEAMDISK 20 // disk that expands to max radius over lifetime // coord coord coord (center position) // coord coord coord (axis and radius) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime // coord coord coord (center position) // coord coord coord (axis and radius) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving // short (entity:attachment to follow) // short (sprite index) // byte (life in 0.1's) // byte (line width in 0.1's) // byte,byte,byte (color) // byte (brightness) #define TE_GLOWSPRITE 23 // coord, coord, coord (pos) short (model index) byte (scale / 10) #define TE_BEAMRING 24 // connect a beam ring to two entities // short (start entity) // short (end entity) // short (sprite index) // byte (starting frame) // byte (frame rate in 0.1's) // byte (life in 0.1's) // byte (line width in 0.1's) // byte (noise amplitude in 0.01's) // byte,byte,byte (color) // byte (brightness) // byte (scroll speed in 0.1's) #define TE_STREAK_SPLASH 25 // oriented shower of tracers // coord coord coord (start position) // coord coord coord (direction vector) // byte (color) // short (count) // short (base speed) // short (random velocity) #define TE_BEAMHOSE 26 // obsolete #define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect // coord, coord, coord (pos) // byte (radius in 10's) // byte byte byte (color) // byte (life in 10's) // byte (decay rate in 10's) #define TE_ELIGHT 28 // point entity light, no world effect // short (entity:attachment to follow) // coord coord coord (initial position) // coord (radius) // byte byte byte (color) // byte (life in 0.1's) // coord (decay rate) #define TE_TEXTMESSAGE 29 // short 1.2.13 x (-1 = center) // short 1.2.13 y (-1 = center) // byte Effect 0 = fade in/fade out // 1 is flickery credits // 2 is write out (training room) // 4 bytes r,g,b,a color1 (text color) // 4 bytes r,g,b,a color2 (effect color) // ushort 8.8 fadein time // ushort 8.8 fadeout time // ushort 8.8 hold time // optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) // string text message (512 chars max sz string) #define TE_LINE 30 // coord, coord, coord startpos // coord, coord, coord endpos // short life in 0.1 s // 3 bytes r, g, b #define TE_BOX 31 // coord, coord, coord boxmins // coord, coord, coord boxmaxs // short life in 0.1 s // 3 bytes r, g, b #define TE_KILLBEAM 99 // kill all beams attached to entity // short (entity) #define TE_LARGEFUNNEL 100 // coord coord coord (funnel position) // short (sprite index) // short (flags) #define TE_BLOODSTREAM 101 // particle spray // coord coord coord (start position) // coord coord coord (spray vector) // byte (color) // byte (speed) #define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds // coord coord coord (start position) // coord coord coord (end position) #define TE_BLOOD 103 // particle spray // coord coord coord (start position) // coord coord coord (spray vector) // byte (color) // byte (speed) #define TE_DECAL 104 // Decal applied to a brush entity (not the world) // coord, coord, coord (x,y,z), decal position (center of texture in world) // byte (texture index of precached decal texture name) // short (entity index) #define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards // short (entity) // short (sprite index) // byte (density) #define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits // coord, coord, coord (position) // coord, coord, coord (velocity) // angle (initial yaw) // short (model index) // byte (bounce sound type) // byte (life in 0.1's) #define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set // coord, coord, coord (origin) // coord (velocity) // short (model index) // short (count) // byte (life in 0.1's) #define TE_BREAKMODEL 108 // box of models or sprites // coord, coord, coord (position) // coord, coord, coord (size) // coord, coord, coord (velocity) // byte (random velocity in 10's) // short (sprite or model index) // byte (count) // byte (life in 0.1 secs) // byte (flags) #define TE_GUNSHOTDECAL 109 // decal and ricochet sound // coord, coord, coord (position) // short (entity index???) // byte (decal???) #define TE_SPRITE_SPRAY 110 // spay of alpha sprites // coord, coord, coord (position) // coord, coord, coord (velocity) // short (sprite index) // byte (count) // byte (speed) // byte (noise) #define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. // coord, coord, coord (position) // byte (scale in 0.1's) #define TE_PLAYERDECAL 112 // ??? // byte (playerindex) // coord, coord, coord (position) // short (entity???) // byte (decal number???) // [optional] short (model index???) #define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards // coord, coord, coord (min start position) // coord, coord, coord (max start position) // coord (float height) // short (model index) // byte (count) // coord (speed) #define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards // coord, coord, coord (min start position) // coord, coord, coord (max start position) // coord (float height) // short (model index) // byte (count) // coord (speed) #define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) // coord, coord, coord (position) // short (sprite1 index) // short (sprite2 index) // byte (color) // byte (scale) #define TE_WORLDDECAL 116 // Decal applied to the world brush // coord, coord, coord (x,y,z), decal position (center of texture in world) // byte (texture index of precached decal texture name) #define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush // coord, coord, coord (x,y,z), decal position (center of texture in world) // byte (texture index of precached decal texture name - 256) #define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 // coord, coord, coord (x,y,z), decal position (center of texture in world) // byte (texture index of precached decal texture name - 256) // short (entity index) #define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) // coord, coord, coord (position) // coord, coord, coord (velocity) // short (modelindex) // byte (life) // byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). #define TE_SPRAY 120 // Throws a shower of sprites or models // coord, coord, coord (position) // coord, coord, coord (direction) // short (modelindex) // byte (count) // byte (speed) // byte (noise) // byte (rendermode) #define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) // byte (playernum) // short (sprite modelindex) // byte (count) // byte (variance) (0 = no variance in size) (10 = 10% variance in size) #define TE_PARTICLEBURST 122 // very similar to lavasplash. // coord (origin) // short (radius) // byte (particle color) // byte (duration * 10) (will be randomized a bit) #define TE_FIREFIELD 123 // makes a field of fire. // coord (origin) // short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) // short (modelindex) // byte (count) // byte (flags) // byte (duration (in seconds) * 10) (will be randomized a bit) // // to keep network traffic low, this message has associated flags that fit into a byte: #define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate #define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) #define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. #define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque #define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. #define TEFIRE_FLAG_ADDITIVE 32 // if set, sprite is rendered as additive #define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) // byte (entity index of player) // coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) // short (model index) // short (life * 10 ); #define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. // byte (entity index of player) #define TE_MULTIGUNSHOT 126 // much more compact shotgun message // This message is used to make a client approximate a 'spray' of gunfire. // Any weapon that fires more than one bullet per frame and fires in a bit of a spread is // a good candidate for MULTIGUNSHOT use. (shotguns) // // NOTE: This effect makes the client do traces for each bullet, these client traces ignore // entities that have studio models.Traces are 4096 long. // // coord (origin) // coord (origin) // coord (origin) // coord (direction) // coord (direction) // coord (direction) // coord (x noise * 100) // coord (y noise * 100) // byte (count) // byte (bullethole decal texture index) #define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. // coord (origin) // coord (origin) // coord (origin) // coord (velocity) // coord (velocity) // coord (velocity) // byte ( life * 10 ) // byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) // byte ( length * 10 ) #define MSG_BROADCAST 0 // unreliable to all #define MSG_ONE 1 // reliable to one (msg_entity) #define MSG_ALL 2 // reliable to all #define MSG_INIT 3 // write to the init string #define MSG_PVS 4 // Ents in PVS of org #define MSG_PAS 5 // Ents in PAS of org #define MSG_PVS_R 6 // Reliable to PVS #define MSG_PAS_R 7 // Reliable to PAS #define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) #define MSG_SPEC 9 // Sends to all spectator proxies // contents of a spot in the world #define CONTENTS_EMPTY -1 #define CONTENTS_SOLID -2 #define CONTENTS_WATER -3 #define CONTENTS_SLIME -4 #define CONTENTS_LAVA -5 #define CONTENTS_SKY -6 // These additional contents constants are defined in bspfile.h #define CONTENTS_ORIGIN -7 // removed at csg time #define CONTENTS_CLIP -8 // changed to contents_solid #define CONTENTS_CURRENT_0 -9 #define CONTENTS_CURRENT_90 -10 #define CONTENTS_CURRENT_180 -11 #define CONTENTS_CURRENT_270 -12 #define CONTENTS_CURRENT_UP -13 #define CONTENTS_CURRENT_DOWN -14 #define CONTENTS_TRANSLUCENT -15 #define CONTENTS_LADDER -16 #define CONTENT_FLYFIELD -17 #define CONTENT_GRAVITY_FLYFIELD -18 #define CONTENT_FOG -19 // channels #define CHAN_AUTO 0 #define CHAN_WEAPON 1 #define CHAN_VOICE 2 #define CHAN_ITEM 3 #define CHAN_BODY 4 #define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area #define CHAN_STATIC 6 // allocate channel from the static area #define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network #define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). // attenuation values #define ATTN_NONE 0 #define ATTN_NORM (float)0.8 #define ATTN_IDLE (float)2 #define ATTN_STATIC (float)1.25 // pitch values #define PITCH_NORM 100 // non-pitch shifted #define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high #define PITCH_HIGH 120 // volume values #define VOL_NORM 1.0 // plats #define PLAT_LOW_TRIGGER 1 // Trains #define SF_TRAIN_WAIT_RETRIGGER 1 #define SF_TRAIN_START_ON 4 // Train is initially moving #define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains // buttons #define IN_ATTACK (1U<<0) #define IN_JUMP (1U<<1) #define IN_DUCK (1U<<2) #define IN_FORWARD (1U<<3) #define IN_BACK (1U<<4) #define IN_USE (1U<<5) #define IN_CANCEL (1U<<6) #define IN_LEFT (1U<<7) #define IN_RIGHT (1U<<8) #define IN_MOVELEFT (1U<<9) #define IN_MOVERIGHT (1U<<10) #define IN_ATTACK2 (1U<<11) #define IN_RUN (1U<<12) #define IN_RELOAD (1U<<13) #define IN_ALT1 (1U<<14) #define IN_SCORE (1U<<15) // Used by client.dll for when scoreboard is held down // Break Model Defines #define BREAK_TYPEMASK 0x4F #define BREAK_GLASS 0x01 #define BREAK_METAL 0x02 #define BREAK_FLESH 0x04 #define BREAK_WOOD 0x08 #define BREAK_SMOKE 0x10 #define BREAK_TRANS 0x20 #define BREAK_CONCRETE 0x40 #define BREAK_2 0x80 // Colliding temp entity sounds #define BOUNCE_GLASS BREAK_GLASS #define BOUNCE_METAL BREAK_METAL #define BOUNCE_FLESH BREAK_FLESH #define BOUNCE_WOOD BREAK_WOOD #define BOUNCE_SHRAP 0x10 #define BOUNCE_SHELL 0x20 #define BOUNCE_CONCRETE BREAK_CONCRETE #define BOUNCE_SHOTSHELL 0x80 // Temp entity bounce sound types #define TE_BOUNCE_NULL 0 #define TE_BOUNCE_SHELL 1 #define TE_BOUNCE_SHOTSHELL 2 // Rendering constants enum { kRenderNormal, // src kRenderTransColor, // c*a+dest*(1-a) kRenderTransTexture, // src*a+dest*(1-a) kRenderGlow, // src*a+dest -- No Z buffer checks kRenderTransAlpha, // src*srca+dest*(1-srca) kRenderTransAdd, // src*a+dest }; enum { kRenderFxNone = 0, kRenderFxPulseSlow, kRenderFxPulseFast, kRenderFxPulseSlowWide, kRenderFxPulseFastWide, kRenderFxFadeSlow, kRenderFxFadeFast, kRenderFxSolidSlow, kRenderFxSolidFast, kRenderFxStrobeSlow, kRenderFxStrobeFast, kRenderFxStrobeFaster, kRenderFxFlickerSlow, kRenderFxFlickerFast, kRenderFxNoDissipation, kRenderFxDistort, // Distort/scale/translate flicker kRenderFxHologram, // kRenderFxDistort + distance fade kRenderFxDeadPlayer, // kRenderAmt is the player index kRenderFxExplode, // Scale up really big! kRenderFxGlowShell, // Glowing Shell kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) kRenderFxLightMultiplier, }; typedef int func_t; typedef int string_t; typedef unsigned short word; #include "xash3d_types.h" typedef struct { byte r, g, b; } color24; typedef struct { unsigned r, g, b, a; } colorVec; typedef struct link_s { struct link_s *prev, *next; } link_t; typedef struct edict_s edict_t; typedef struct { vec3_t normal; float dist; } plane_t; typedef struct { qboolean allsolid; // if true, plane is not valid qboolean startsolid; // if true, the initial point was in a solid area qboolean inopen, inwater; float fraction; // time completed, 1.0 = didn't hit anything vec3_t endpos; // final position plane_t plane; // surface normal at impact edict_t *ent; // entity the surface is on int hitgroup; // 0 == generic, non zero is specific body part } trace_t; #endif//CONST_H ================================================ FILE: common/cvardef.h ================================================ /* cvardef.h - quake cvar definition Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2024 Alibek Omarov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CVAR #define CVAR #include STDINT_H #include "xash3d_types.h" /* cvar_t variables are used to hold scalar or string variables that can be changed or displayed at the console or prog code as well as accessed directly in C code. 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. */ enum { // GoldSrc compatibility flags FCVAR_ARCHIVE = 1 << 0, // set to cause it to be saved to vars.rc FCVAR_USERINFO = 1 << 1, // added to userinfo when changed FCVAR_SERVER = 1 << 2, // added to serverinfo when changed, will notify clients by default FCVAR_EXTDLL = 1 << 3, // defined by server.dll FCVAR_CLIENTDLL = 1 << 4, // defined by client.dll FCVAR_PROTECTED = 1 << 5, // private server cvar FCVAR_SPONLY = 1 << 6, // can be set only in singleplayer FCVAR_PRINTABLEONLY = 1 << 7, // only allows printable characters FCVAR_UNLOGGED = 1 << 8, // disables notifying client about server cvar change FCVAR_NOEXTRAWHITESPACE = 1 << 9, // removes space characters from the beginning and the end of the string FCVAR_PRIVILEGED = 1 << 10, // only available in privileged mode FCVAR_FILTERABLE = 1 << 11, // treated as privileged if cl_filterstuffcmd is 1, otherwise ignored // Xash3D public flags // FCVAR_LATCH = 1 << 11, // deprecated Xash3D flag, conflicts with FCVAR_FILTERABLE. Use another FCVAR_LATCH! FCVAR_GLCONFIG = 1 << 12, // set to cause it to be saved to .cfg (see RefAPI) FCVAR_CHANGED = 1 << 13, // set each time the cvar changed FCVAR_GAMEUIDLL = 1 << 14, // defined by menu.dll FCVAR_CHEAT = 1 << 15, // cannot be changed if sv_cheats is 0 #if REF_DLL || ENGINE_DLL // Xash3D internal flags, MUST NOT be used outside of engine FCVAR_RENDERINFO = 1 << 16, // set to cause it to be saved to video.cfg FCVAR_READ_ONLY = 1 << 17, // display only, cannot be set by user at all FCVAR_EXTENDED = 1 << 18, // extended cvar structure FCVAR_ALLOCATED = 1 << 19, // allocated by the engine, must be freed with Mem_Free FCVAR_VIDRESTART = 1 << 20, // triggers video subsystem to recreate/modify window parameters FCVAR_TEMPORARY = 1 << 21, // only used to temporarly hold some value, can be unlinked FCVAR_MOVEVARS = 1 << 22, // access to movevars_t structure, synchornized between client and server FCVAR_USER_CREATED = 1 << 23, // created by a set command FCVAR_REFDLL = 1 << 29, // (Xash3D FWGS internal flag) defined by the renderer DLL #endif // REF_DLL || ENGINE_DLL FCVAR_LATCH = 1 << 30, // (Xash3D FWGS public flag, was FCVAR_FILTERABLE in Xash3D) save changes until server restart }; struct cvar_s { char *name; char *string; uint32_t flags; float value; struct cvar_s *next; }; typedef struct cvar_s cvar_t; STATIC_CHECK_SIZEOF( struct cvar_s, 20, 32 ); #if REF_DLL || ENGINE_DLL // Xash3D internal cvar format, MUST NOT be used outside of engine struct convar_s { char *name; char *string; uint32_t flags; float value; struct convar_s *next; char *desc; char *def_string; }; typedef struct convar_s convar_t; #if XASH_64BIT #define CVAR_SENTINEL (uintptr_t)0xDEADBEEFDEADBEEF #else #define CVAR_SENTINEL (uintptr_t)0xDEADBEEF #endif #define CVAR_CHECK_SENTINEL( cv ) ((uintptr_t)(cv)->next == CVAR_SENTINEL) #define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) \ convar_t cv = { (char*)cvname, (char*)cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, (char*)cvdesc, NULL } #define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) CVAR_DEFINE( cv, #cv, cvstr, cvflags, cvdesc ) #endif // REF_DLL || ENGINE_DLL #endif // CVAR ================================================ FILE: common/defaults.h ================================================ /* defaults.h - set up default configuration Copyright (C) 2016 Mittorn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef DEFAULTS_H #define DEFAULTS_H #include "backends.h" #include "build.h" /* =================================================================== SETUP BACKENDS DEFINITIONS =================================================================== */ #if !XASH_DEDICATED #if XASH_SDL // we are building using libSDL #ifndef XASH_VIDEO #define XASH_VIDEO VIDEO_SDL #endif // XASH_VIDEO #ifndef XASH_INPUT #define XASH_INPUT INPUT_SDL #endif // XASH_INPUT #ifndef XASH_SOUND #define XASH_SOUND SOUND_SDL #endif // XASH_SOUND #if XASH_SDL == 2 #ifndef XASH_TIMER #define XASH_TIMER TIMER_SDL #endif // XASH_TIMER #ifndef XASH_MESSAGEBOX #if !XASH_NSWITCH // SDL2 messageboxes not available #define XASH_MESSAGEBOX MSGBOX_SDL #endif #endif // XASH_MESSAGEBOX #endif #elif XASH_LINUX // we are building for Linux without SDL2, can draw only to framebuffer yet #ifndef XASH_VIDEO #define XASH_VIDEO VIDEO_FBDEV #endif // XASH_VIDEO #ifndef XASH_INPUT #define XASH_INPUT INPUT_EVDEV #endif // XASH_INPUT #ifndef XASH_SOUND #define XASH_SOUND SOUND_ALSA #endif // XASH_SOUND #define XASH_USE_EVDEV 1 #elif XASH_DOS4GW #ifndef XASH_VIDEO #define XASH_VIDEO VIDEO_DOS #endif #ifndef XASH_TIMER #define XASH_TIMER TIMER_DOS #endif // usually only 10-20 fds availiable #define XASH_REDUCE_FD #endif #endif // XASH_DEDICATED // // select messagebox implementation // #ifndef XASH_MESSAGEBOX #if XASH_WIN32 #define XASH_MESSAGEBOX MSGBOX_WIN32 #elif XASH_NSWITCH #define XASH_MESSAGEBOX MSGBOX_NSWITCH #else // !XASH_WIN32 #define XASH_MESSAGEBOX MSGBOX_STDERR #endif // !XASH_WIN32 #endif // XASH_MESSAGEBOX // // no timer - no xash // #ifndef XASH_TIMER #if XASH_WIN32 #define XASH_TIMER TIMER_WIN32 #else // !XASH_WIN32 #define XASH_TIMER TIMER_POSIX #endif // !XASH_WIN32 #endif // // determine movie playback backend // #ifndef XASH_AVI #if HAVE_FFMPEG #define XASH_AVI AVI_FFMPEG #else #define XASH_AVI AVI_NULL #endif #endif #ifdef XASH_STATIC_LIBS #define XASH_LIB LIB_STATIC #define XASH_INTERNAL_GAMELIBS #define XASH_ALLOW_SAVERESTORE_OFFSETS #elif XASH_WIN32 #define XASH_LIB LIB_WIN32 #elif XASH_POSIX #define XASH_LIB LIB_POSIX #endif // // fallback to NULL // #ifndef XASH_VIDEO #define XASH_VIDEO VIDEO_NULL #endif // XASH_VIDEO #ifndef XASH_SOUND #define XASH_SOUND SOUND_NULL #endif // XASH_SOUND #ifndef XASH_INPUT #define XASH_INPUT INPUT_NULL #endif // XASH_INPUT /* ========================================================================= Default build-depended cvar and constant values ========================================================================= */ // Platform overrides #if XASH_NSWITCH #define DEFAULT_TOUCH_ENABLE "1" #define DEFAULT_M_IGNORE "1" #define DEFAULT_MODE_WIDTH 1280 #define DEFAULT_MODE_HEIGHT 720 #define DEFAULT_ALLOWCONSOLE 1 #elif XASH_PSVITA #define DEFAULT_TOUCH_ENABLE "1" #define DEFAULT_M_IGNORE "1" #define DEFAULT_MODE_WIDTH 960 #define DEFAULT_MODE_HEIGHT 544 #define DEFAULT_ALLOWCONSOLE 1 #elif XASH_ANDROID #define DEFAULT_TOUCH_ENABLE "1" #elif XASH_MOBILE_PLATFORM #define DEFAULT_TOUCH_ENABLE "1" #define DEFAULT_M_IGNORE "1" #endif // !XASH_MOBILE_PLATFORM && !XASH_NSWITCH #if XASH_IOS // this means that libraries are provided with engine, but not in game data // You need add library loading code to library.c when adding new platform #define XASH_INTERNAL_GAMELIBS #endif // XASH_IOS // Defaults #ifndef DEFAULT_TOUCH_ENABLE #define DEFAULT_TOUCH_ENABLE "0" #endif // DEFAULT_TOUCH_ENABLE #ifndef DEFAULT_M_IGNORE #define DEFAULT_M_IGNORE "0" #endif // DEFAULT_M_IGNORE #ifndef DEFAULT_JOY_DEADZONE #define DEFAULT_JOY_DEADZONE "4096" #endif // DEFAULT_JOY_DEADZONE #ifndef DEFAULT_DEV #define DEFAULT_DEV 0 #endif // DEFAULT_DEV #ifndef DEFAULT_ALLOWCONSOLE #define DEFAULT_ALLOWCONSOLE 0 #endif // DEFAULT_ALLOWCONSOLE #ifndef DEFAULT_FULLSCREEN #define DEFAULT_FULLSCREEN "1" // must be a string #endif // DEFAULT_FULLSCREEN #ifndef DEFAULT_MAX_EDICTS #define DEFAULT_MAX_EDICTS 1200 // was 900 before HL25 #endif // DEFAULT_MAX_EDICTS #endif // DEFAULTS_H ================================================ FILE: common/demo_api.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef DEMO_API_H #define DEMO_API_H typedef struct demo_api_s { int (*IsRecording)( void ); int (*IsPlayingback)( void ); int (*IsTimeDemo)( void ); void (*WriteBuffer)( int size, unsigned char *buffer ); } demo_api_t; #endif//DEMO_API_H ================================================ FILE: common/dlight.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef DLIGHT_H #define DLIGHT_H typedef struct dlight_s { vec3_t origin; float radius; color24 color; float die; // stop lighting after this time float decay; // drop this each second float minlight; // don't add when contributing less int key; qboolean dark; // subtracts light instead of adding } dlight_t; #endif//DLIGHT_H ================================================ FILE: common/enginefeatures.h ================================================ /* enginefeatures.h - engine features that can be enabled by mod-maker request Copyright (C) 2012 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef FEATURES_H #define FEATURES_H // list of engine features that can be enabled through callback SV_CheckFeatures #define ENGINE_WRITE_LARGE_COORD (1<<0) // replace standard message WRITE_COORD with big message for support more than 8192 units in world #define ENGINE_QUAKE_COMPATIBLE (1<<1) // make engine compatible with quake (flags and effects) #define ENGINE_LOAD_DELUXEDATA (1<<2) // loading deluxemap for map (if present) #define ENGINE_PHYSICS_PUSHER_EXT (1<<3) // enable sets of improvements for MOVETYPE_PUSH physics #define ENGINE_LARGE_LIGHTMAPS (1<<4) // change lightmap sizes from 128x128 to 1024x1024 #define ENGINE_COMPENSATE_QUAKE_BUG (1<<5) // compensate stupid quake bug (inverse pitch) for mods where this bug is fixed #define ENGINE_IMPROVED_LINETRACE (1<<6) // new traceline that tracing through alphatextures #define ENGINE_COMPUTE_STUDIO_LERP (1<<7) // enable MOVETYPE_STEP lerping back in engine #define ENGINE_LINEAR_GAMMA_SPACE (1<<8) // disable influence of gamma/brightness cvars to textures/lightmaps, for mods with custom renderer #define ENGINE_DISABLE_HDTEXTURES (1U<<30) // disable support of HD-textures in case custom renderer have separate way to load them #define ENGINE_STEP_POSHISTORY_LERP (1U<<31) // enable MOVETYPE_STEP interpolation based on position history. Incompatible with ENGINE_COMPUTE_STUDIO_LERP! // adjust the mask when features will be added or removed #define ENGINE_FEATURES_MASK \ ( ENGINE_WRITE_LARGE_COORD \ | ENGINE_QUAKE_COMPATIBLE \ | ENGINE_LOAD_DELUXEDATA \ | ENGINE_PHYSICS_PUSHER_EXT \ | ENGINE_LARGE_LIGHTMAPS \ | ENGINE_COMPENSATE_QUAKE_BUG \ | ENGINE_IMPROVED_LINETRACE \ | ENGINE_COMPUTE_STUDIO_LERP \ | ENGINE_LINEAR_GAMMA_SPACE \ | ENGINE_STEP_POSHISTORY_LERP ) #endif//FEATURES_H ================================================ FILE: common/entity_state.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef ENTITY_STATE_H #define ENTITY_STATE_H // For entityType below #define ENTITY_NORMAL (1<<0) #define ENTITY_BEAM (1<<1) // Entity state is used for the baseline and for delta compression of a packet of // entities that is sent to a client. typedef struct entity_state_s entity_state_t; struct entity_state_s { // Fields which are filled in by routines outside of delta compression int entityType; // Index into cl_entities array for this entity. int number; float msg_time; // Message number last time the player/entity state was updated. int messagenum; // Fields which can be transitted and reconstructed over the network stream vec3_t origin; vec3_t angles; int modelindex; int sequence; float frame; int colormap; short skin; short solid; int effects; float scale; byte eflags; // Render information int rendermode; int renderamt; color24 rendercolor; int renderfx; int movetype; float animtime; float framerate; int body; byte controller[4]; byte blending[4]; vec3_t velocity; // Send bbox down to client for use during prediction. vec3_t mins; vec3_t maxs; int aiment; // If owned by a player, the index of that player ( for projectiles ). int owner; // Friction, for prediction. float friction; // Gravity multiplier float gravity; // PLAYER SPECIFIC int team; int playerclass; int health; qboolean spectator; int weaponmodel; int gaitsequence; // If standing on conveyor, e.g. vec3_t basevelocity; // Use the crouched hull, or the regular player hull. int usehull; // Latched buttons last time state updated. int oldbuttons; // -1 = in air, else pmove entity number int onground; int iStepLeft; // How fast we are falling float flFallVelocity; float fov; int weaponanim; // Parametric movement overrides vec3_t startpos; vec3_t endpos; float impacttime; float starttime; // For mods int iuser1; int iuser2; int iuser3; int iuser4; float fuser1; float fuser2; float fuser3; float fuser4; vec3_t vuser1; vec3_t vuser2; vec3_t vuser3; vec3_t vuser4; }; #include "pm_info.h" typedef struct clientdata_s { vec3_t origin; vec3_t velocity; int viewmodel; vec3_t punchangle; int flags; int waterlevel; int watertype; vec3_t view_ofs; float health; int bInDuck; int weapons; // remove? int flTimeStepSound; int flDuckTime; int flSwimTime; int waterjumptime; float maxspeed; float fov; int weaponanim; int m_iId; int ammo_shells; int ammo_nails; int ammo_cells; int ammo_rockets; float m_flNextAttack; int tfstate; int pushmsec; int deadflag; char physinfo[MAX_PHYSINFO_STRING]; // For mods int iuser1; int iuser2; int iuser3; int iuser4; float fuser1; float fuser2; float fuser3; float fuser4; vec3_t vuser1; vec3_t vuser2; vec3_t vuser3; vec3_t vuser4; } clientdata_t; #include "weaponinfo.h" #define MAX_LOCAL_WEAPONS 64 // max weapons that can be predicted on the client typedef struct local_state_s { entity_state_t playerstate; clientdata_t client; weapon_data_t weapondata[MAX_LOCAL_WEAPONS]; } local_state_t; #endif//ENTITY_STATE_H ================================================ FILE: common/entity_types.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef ENTITY_TYPES_H #define ENTITY_TYPES_H #define ET_NORMAL 0 #define ET_PLAYER 1 #define ET_TEMPENTITY 2 #define ET_BEAM 3 #define ET_FRAGMENTED 4 // BMODEL or SPRITE that was split across BSP nodes #endif//ENTITY_TYPES_H ================================================ FILE: common/event_api.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef EVENT_API_H #define EVENT_API_H #define EVENT_API_VERSION 1 typedef struct event_api_s { int version; void ( *EV_PlaySound )( int ent, float *origin, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); void ( *EV_StopSound )( int ent, int channel, const char *sample ); int ( *EV_FindModelIndex )( const char *pmodel ); int ( *EV_IsLocal )( int playernum ); int ( *EV_LocalPlayerDucking )( void ); void ( *EV_LocalPlayerViewheight )( float * ); void ( *EV_LocalPlayerBounds )( int hull, float *mins, float *maxs ); int ( *EV_IndexFromTrace)( struct pmtrace_s *pTrace ); struct physent_s *( *EV_GetPhysent )( int idx ); void ( *EV_SetUpPlayerPrediction )( int dopred, int bIncludeLocalClient ); void ( *EV_PushPMStates )( void ); void ( *EV_PopPMStates )( void ); void ( *EV_SetSolidPlayers )( int playernum ); void ( *EV_SetTraceHull )( int hull ); void ( *EV_PlayerTrace )( float *start, float *end, int traceFlags, int ignore_pe, struct pmtrace_s *tr ); void ( *EV_WeaponAnimation )( int sequence, int body ); unsigned short ( *EV_PrecacheEvent )( int type, const char* psz ); void ( *EV_PlaybackEvent )( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); const char *( *EV_TraceTexture )( int ground, float *vstart, float *vend ); void ( *EV_StopAllSounds )( int entnum, int entchannel ); void ( *EV_KillEvents )( int entnum, const char *eventname ); // Xash3D extension void ( *EV_PlayerTraceExt )( float *start, float *end, int traceFlags, int (*pfnIgnore)( struct physent_s *pe ), struct pmtrace_s *tr ); const char *(*EV_SoundForIndex)( int index ); struct msurface_s *( *EV_TraceSurface )( int ground, float *vstart, float *vend ); struct movevars_s *( *EV_GetMovevars )( void ); struct pmtrace_s *( *EV_VisTraceLine )( float *start, float *end, int flags ); struct physent_s *( *EV_GetVisent )( int idx ); int ( *EV_TestLine)( const vec3_t start, const vec3_t end, int flags ); void ( *EV_PushTraceBounds)( int hullnum, const float *mins, const float *maxs ); void ( *EV_PopTraceBounds)( void ); } event_api_t; #endif//EVENT_API_H ================================================ FILE: common/event_args.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef EVENT_ARGS_H #define EVENT_ARGS_H // Event was invoked with stated origin #define FEVENT_ORIGIN ( 1<<0 ) // Event was invoked with stated angles #define FEVENT_ANGLES ( 1<<1 ) typedef struct event_args_s { int flags; // Transmitted int entindex; float origin[3]; float angles[3]; float velocity[3]; int ducking; float fparam1; float fparam2; int iparam1; int iparam2; int bparam1; int bparam2; } event_args_t; #endif//EVENT_ARGS_H ================================================ FILE: common/event_flags.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef EVENT_FLAGS_H #define EVENT_FLAGS_H // Skip local host for event send. #define FEV_NOTHOST (1<<0) // Send the event reliably. You must specify the origin and angles and use // PLAYBACK_EVENT_FULL for this to work correctly on the server for anything // that depends on the event origin/angles. I.e., the origin/angles are not // taken from the invoking edict for reliable events. #define FEV_RELIABLE (1<<1) // Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC // sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). #define FEV_GLOBAL (1<<2) // If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate // #define FEV_UPDATE (1<<3) // Only send to entity specified as the invoker #define FEV_HOSTONLY (1<<4) // Only send if the event was created on the server. #define FEV_SERVER (1<<5) // Only issue event client side ( from shared code ) #define FEV_CLIENT (1<<6) #endif//EVENT_FLAGS_H ================================================ FILE: common/gameinfo.h ================================================ /* gameinfo.h - current game info Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef GAMEINFO_H #define GAMEINFO_H #define GFL_NOMODELS (1<<0) #define GFL_NOSKILLS (1<<1) #define GFL_RENDER_PICBUTTON_TEXT (1<<2) #define GFL_HD_BACKGROUND (1<<3) #define GFL_ANIMATED_TITLE (1<<4) /* ======================================================================== GAMEINFO stuff internal shared gameinfo structure (readonly for engine parts) ======================================================================== */ typedef struct GAMEINFO_s { // filesystem info char gamefolder[64]; // used for change game '-game x' char startmap[64]; // map to start singleplayer game char trainmap[64]; // map to start hazard course (if specified) char title[64]; // Game Main Title char version[14]; // game version (optional) short flags; // game flags // about mod info char game_url[256]; // link to a developer's site char update_url[256]; // link to updates page char type[64]; // single, toolkit, multiplayer etc char date[64]; char size[64]; // displayed mod size int gamemode; } GAMEINFO; /* ======================================================================== Extended GameInfo struct introduced in Xash3D FWGS GAMEINFO can't be reliably extended, as nor engine, nor menu can't be sure about struct size. By adding struct versioning, we can check the presense for extra fields. ======================================================================== */ #define GAMEINFO_VERSION 2 typedef enum gametype_e { GAME_NORMAL, GAME_SINGLEPLAYER_ONLY, GAME_MULTIPLAYER_ONLY, } gametype_t; typedef struct gameinfo2_s { int gi_version; // should be set to desired struct version, e.g. GAMEINFO_VERSION // filesystem info char gamefolder[64]; // used for change game char startmap[64]; // map to start singleplayer game from char trainmap[64]; // map to start hazardous course from (if specified) char demomap[64]; // map to start demo chapter from (if specified) char title[64]; // game title char iconpath[64]; // path to game icon char version[16]; // game version (optional) uint32_t flags; // gameinfo flags, extended to fit more flags // mod info char game_url[256]; // link to a developer's site char update_url[256]; // link to updates page char type[64]; // single, toolkit, multiplayer, etc char date[64]; // release date uint64_t size; // size in bytes gametype_t gamemode; } gameinfo2_t; #endif//GAMEINFO_H ================================================ FILE: common/hltv.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef HLTV_H #define HLTV_H #define TYPE_CLIENT 0 // client is a normal HL client (default) #define TYPE_PROXY 1 // client is another proxy #define TYPE_COMMENTATOR 3 // client is a commentator #define TYPE_DEMO 4 // client is a demo file // sub commands of svc_hltv: #define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands #define HLTV_STATUS 1 // send status infos about proxy #define HLTV_LISTEN 2 // tell client to listen to a multicast stream // sub commands of svc_director: #define DRC_CMD_NONE 0 // NULL director command #define DRC_CMD_START 1 // start director mode #define DRC_CMD_EVENT 2 // informs about director command #define DRC_CMD_MODE 3 // switches camera modes #define DRC_CMD_CAMERA 4 // sets camera registers #define DRC_CMD_TIMESCALE 5 // sets time scale #define DRC_CMD_MESSAGE 6 // send HUD centerprint #define DRC_CMD_SOUND 7 // plays a particular sound #define DRC_CMD_STATUS 8 // status info about broadcast #define DRC_CMD_BANNER 9 // banner file name for HLTV gui #define DRC_CMD_FADE 10 // send screen fade command #define DRC_CMD_SHAKE 11 // send screen shake command #define DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command #define DRC_CMD_LAST 12 // HLTV_EVENT event flags #define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) #define DRC_FLAG_SIDE (1<<4) // #define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene #define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo #define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) #define DRC_FLAG_INTRO (1<<8) // is a introduction scene #define DRC_FLAG_FINAL (1<<9) // is a final scene #define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data #define MAX_DIRECTOR_CMD_PARAMETERS 4 #define MAX_DIRECTOR_CMD_STRING 128 #endif//HLTV_H ================================================ FILE: common/ivoicetweak.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef IVOICETWEAK_H #define IVOICETWEAK_H // These provide access to the voice controls. typedef enum { MicrophoneVolume = 0, // values 0-1. OtherSpeakerScale // values 0-1. Scales how loud other players are. } VoiceTweakControl; typedef struct IVoiceTweak_s { // These turn voice tweak mode on and off. While in voice tweak mode, the user's voice is echoed back // without sending to the server. int (*StartVoiceTweakMode)( void ); // Returns 0 on error. void (*EndVoiceTweakMode)( void ); // Get/set control values. void (*SetControlFloat)( VoiceTweakControl iControl, float value ); float (*GetControlFloat)( VoiceTweakControl iControl ); } IVoiceTweak; #endif//IVOICETWEAK_H ================================================ FILE: common/kbutton.h ================================================ /* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef KBUTTON_H #define KBUTTON_H // a1ba: copied from WinQuake/client.h // // cl_input // typedef struct { int down[2]; // key nums holding it down int state; // low bit is down state } kbutton_t; STATIC_CHECK_SIZEOF( kbutton_t, 12, 12 ); #endif // KBUTTON_H ================================================ FILE: common/lightstyle.h ================================================ /* lightstyle.h - lighstyle description Copyright (C) 2011 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef LIGHTSTYLE_H #define LIGHTSTYLE_H typedef struct { char pattern[256]; float map[256]; int length; float value; qboolean interp; // allow to interpolate this lightstyle float time; // local time is gurantee what new style begins from the start, not mid or end of the sequence } lightstyle_t; #endif//LIGHTSTYLE_H ================================================ FILE: common/mathlib.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ // mathlib.h #include typedef float vec_t; typedef vec_t vec2_t[2]; typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; // x,y,z,w #ifndef M_PI #define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h #endif struct mplane_s; extern vec3_t vec3_origin; extern int nanmask; #define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) #ifndef VECTOR_H #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) #endif #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 VectorClear(a) {(a)[0]=0.0;(a)[1]=0.0;(a)[2]=0.0;} void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc); vec_t _DotProduct (vec3_t v1, vec3_t v2); void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); void _VectorCopy (vec3_t in, vec3_t out); int VectorCompare (const vec3_t v1, const vec3_t v2); float Length (const vec3_t v); void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross); float VectorNormalize (vec3_t v); // returns vector length void VectorInverse (vec3_t v); void VectorScale (const vec3_t in, vec_t scale, vec3_t out); void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); #define AngleIVectors AngleVectorsTranspose void AngleMatrix (const vec3_t angles, float (*matrix)[4] ); void AngleIMatrix (const vec3_t angles, float (*matrix)[4] ); void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out); void NormalizeAngles( vec3_t angles ); void InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac ); float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ); void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up); void VectorAngles( const vec3_t forward, vec3_t angles ); int InvertMatrix( const float * m, float *out ); int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); float anglemod(float a); #define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ (((p)->type < 3)? \ ( \ ((p)->dist <= (emins)[(p)->type])? \ 1 \ : \ ( \ ((p)->dist >= (emaxs)[(p)->type])?\ 2 \ : \ 3 \ ) \ ) \ : \ BoxOnPlaneSide( (emins), (emaxs), (p))) ================================================ FILE: common/net_api.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef NET_API_H #define NET_API_H #include "netadr.h" #define NETAPI_REQUEST_SERVERLIST ( 0 ) // Doesn't need a remote address #define NETAPI_REQUEST_PING ( 1 ) #define NETAPI_REQUEST_RULES ( 2 ) #define NETAPI_REQUEST_PLAYERS ( 3 ) #define NETAPI_REQUEST_DETAILS ( 4 ) // Set this flag for things like broadcast requests, etc. where the engine should not // kill the request hook after receiving the first response #define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 ) #define FNETAPI_LEGACY_PROTOCOL ( 1<<1 ) // xash3d-fwgs extension struct net_response_s; typedef void (*net_api_response_func_t) ( struct net_response_s *response ); #define NET_SUCCESS ( 0 ) #define NET_ERROR_TIMEOUT ( 1<<0 ) #define NET_ERROR_PROTO_UNSUPPORTED ( 1<<1 ) #define NET_ERROR_UNDEFINED ( 1<<2 ) #define NET_ERROR_FORBIDDEN ( 1<<3 ) // xash3d-fwgs extension typedef struct net_adrlist_s { struct net_adrlist_s *next; netadr_t remote_address; } net_adrlist_t; typedef struct net_response_s { // NET_SUCCESS or an error code int error; // Context ID int context; // Type int type; // Server that is responding to the request netadr_t remote_address; // Response RTT ping time double ping; // Key/Value pair string ( separated by backlash \ characters ) // WARNING: You must copy this buffer in the callback function, because it is freed // by the engine right after the call!!!! // ALSO: For NETAPI_REQUEST_SERVERLIST requests, this will be a pointer to a linked list of net_adrlist_t's void *response; } net_response_t; typedef struct net_status_s { // Connected to remote server? 1 == yes, 0 otherwise int connected; // Client's IP address netadr_t local_address; // Address of remote server netadr_t remote_address; // Packet Loss ( as a percentage ) int packet_loss; // Latency, in seconds ( multiply by 1000.0 to get milliseconds ) double latency; // Connection time, in seconds double connection_time; // Rate setting ( for incoming data ) double rate; } net_status_t; typedef struct net_api_s { // APIs void (*InitNetworking)( void ); void (*Status )( struct net_status_s *status ); void (*SendRequest)( int context, int request, int flags, double timeout, struct netadr_s *remote_address, net_api_response_func_t response ); void (*CancelRequest)( int context ); void (*CancelAllRequests)( void ); const char *(*AdrToString)( struct netadr_s *a ); int ( *CompareAdr)( struct netadr_s *a, struct netadr_s *b ); int ( *StringToAdr)( char *s, struct netadr_s *a ); const char *(*ValueForKey)( const char *s, const char *key ); void (*RemoveKey)( char *s, const char *key ); void (*SetValueForKey)( char *s, const char *key, const char *value, int maxsize ); } net_api_t; #endif//NET_APIH ================================================ FILE: common/netadr.h ================================================ /* Copyright (C) 1997-2001 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef NET_ADR_H #define NET_ADR_H #include STDINT_H // net.h -- quake's interface to the networking layer // a1ba: copied from Quake-2/qcommon/qcommon.h and modified to support IPv6 #define PORT_ANY -1 typedef enum netadrtype_e { NA_UNDEFINED = 0, NA_LOOPBACK, NA_BROADCAST, NA_IP, NA_IPX, NA_BROADCAST_IPX, NA_IP6, NA_MULTICAST_IP6 } netadrtype_t; /* Original Quake-2 structure: typedef struct { netadrtype_t type; byte ip[4]; byte ipx[10]; unsigned short port; } netadr_t; */ #pragma pack( push, 1 ) typedef struct netadr_s { // the reason we do this evil thing, is that when this struct contains IPv6 // address the `type` is 2-byte wide, but when it doesn't `type` must 4-byte // wide _and_ ip6_0 must be zeroed, to keep it binary compatible. #if XASH_LITTLE_ENDIAN uint16_t type; uint8_t ip6_0[2]; #elif XASH_BIG_ENDIAN uint8_t ip6_0[2]; uint16_t type; #else #error #endif union { // IPv6 struct uint8_t ip6_1[14]; struct { union { uint8_t ip[4]; uint32_t ip4; // for easier conversions }; uint8_t ipx[10]; }; }; uint16_t port; } netadr_t; #pragma pack( pop ) static inline netadrtype_t NET_NetadrType( const netadr_t *a ) { if( a->type == NA_IP6 || a->type == NA_MULTICAST_IP6 ) return (netadrtype_t)a->type; if( a->ip6_0[0] || a->ip6_0[1] ) return NA_UNDEFINED; return (netadrtype_t)a->type; } static inline void NET_NetadrSetType( netadr_t *a, netadrtype_t type ) { if( type == NA_IP6 || type == NA_MULTICAST_IP6 ) { a->type = type; return; } a->ip6_0[0] = a->ip6_0[1] = 0; a->type = type; } STATIC_CHECK_SIZEOF( netadr_t, 20, 20 ); #endif // NET_ADR_H ================================================ FILE: common/particledef.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef PARTICLEDEF_H #define PARTICLEDEF_H typedef enum { pt_static, pt_grav, pt_slowgrav, pt_fire, pt_explode, pt_explode2, pt_blob, pt_blob2, pt_vox_slowgrav, pt_vox_grav, pt_clientcustom // Must have callback function specified } ptype_t; typedef struct particle_s { vec3_t org; short color; short packedColor; struct particle_s *next; vec3_t vel; float ramp; float die; ptype_t type; void (*deathfunc)( struct particle_s *particle ); // for pt_clientcusttom, we'll call this function each frame void (*callback)( struct particle_s *particle, float frametime ); // For deathfunc, etc. unsigned char context; } particle_t; #endif//PARTICLEDEF_H ================================================ FILE: common/pmtrace.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef PM_TRACE_H #define PM_TRACE_H typedef struct { vec3_t normal; float dist; } pmplane_t; typedef struct pmtrace_s pmtrace_t; struct pmtrace_s { qboolean allsolid; // if true, plane is not valid qboolean startsolid; // if true, the initial point was in a solid area qboolean inopen, inwater; // End point is in empty space or in water float fraction; // time completed, 1.0 = didn't hit anything vec3_t endpos; // final position pmplane_t plane; // surface normal at impact int ent; // entity at impact vec3_t deltavelocity; // Change in player's velocity caused by impact. // Only run on server. int hitgroup; }; #endif//PM_TRACE_H ================================================ FILE: common/port.h ================================================ /* port.h -- Portability Layer for Windows types Copyright (C) 2015 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #pragma once #ifndef PORT_H #define PORT_H #include "build.h" #if !XASH_WIN32 #if XASH_APPLE #include #define OS_LIB_EXT "dylib" #define OPEN_COMMAND "open" #elif XASH_EMSCRIPTEN #define OS_LIB_EXT "wasm" #define OPEN_COMMAND "???" #else #define OS_LIB_EXT "so" #define OPEN_COMMAND "xdg-open" #endif #define OS_LIB_PREFIX "lib" #define VGUI_SUPPORT_DLL "libvgui_support." OS_LIB_EXT // Windows-specific #define __cdecl #define __stdcall #define _inline static inline #if XASH_POSIX #include #if XASH_NSWITCH #define SOLDER_LIBDL_COMPAT #include #elif XASH_PSVITA #define VRTLD_LIBDL_COMPAT #include #define O_BINARY 0 #else #include #define HAVE_DUP #define O_BINARY 0 #endif #define O_TEXT 0 #define _mkdir( x ) mkdir( x, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) #endif typedef void* HANDLE; typedef void* HINSTANCE; typedef struct tagPOINT { int x, y; } POINT; #else // WIN32 #define open _open #define read _read #define alloca _alloca #define HSPRITE WINAPI_HSPRITE #define WIN32_LEAN_AND_MEAN #include #include #undef HSPRITE #define OS_LIB_PREFIX "" #define OS_LIB_EXT "dll" #define VGUI_SUPPORT_DLL "../vgui_support." OS_LIB_EXT #define HAVE_DUP #endif //WIN32 #ifndef XASH_LOW_MEMORY #define XASH_LOW_MEMORY 0 #endif #include #include #include #endif // PORT_H ================================================ FILE: common/qfont.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef QFONT_H #define QFONT_H // Font stuff #define NUM_GLYPHS 256 typedef struct { short startoffset; short charwidth; } charinfo; typedef struct qfont_s { int width, height; int rowcount; int rowheight; charinfo fontinfo[NUM_GLYPHS]; byte data[4]; } qfont_t; #endif//QFONT_H ================================================ FILE: common/r_efx.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef R_EFX_H #define R_EFX_H // particle_t #if !defined( PARTICLEDEFH ) #include "particledef.h" #endif // BEAM #if !defined( BEAMDEFH ) #include "beamdef.h" #endif // dlight_t #if !defined ( DLIGHTH ) #include "dlight.h" #endif // cl_entity_t #if !defined( CL_ENTITYH ) #include "cl_entity.h" #endif /* // FOR REFERENCE, These are the built-in tracer colors. Note, color 4 is the one // that uses the tracerred/tracergreen/tracerblue and traceralpha cvar settings color24 gTracerColors[] = { { 255, 255, 255 }, // White { 255, 0, 0 }, // Red { 0, 255, 0 }, // Green { 0, 0, 255 }, // Blue { 0, 0, 0 }, // Tracer default, filled in from cvars, etc. { 255, 167, 17 }, // Yellow-orange sparks { 255, 130, 90 }, // Yellowish streaks (garg) { 55, 60, 144 }, // Blue egon streak { 255, 130, 90 }, // More Yellowish streaks (garg) { 255, 140, 90 }, // More Yellowish streaks (garg) { 200, 130, 90 }, // More red streaks (garg) { 255, 120, 70 }, // Darker red streaks (garg) }; */ #define TRACER_COLORINDEX_DEFAULT 4 // Temporary entity array #define TENTPRIORITY_LOW 0 #define TENTPRIORITY_HIGH 1 // TEMPENTITY flags #define FTENT_NONE 0x00000000 #define FTENT_SINEWAVE 0x00000001 #define FTENT_GRAVITY 0x00000002 #define FTENT_ROTATE 0x00000004 #define FTENT_SLOWGRAVITY 0x00000008 #define FTENT_SMOKETRAIL 0x00000010 #define FTENT_COLLIDEWORLD 0x00000020 #define FTENT_FLICKER 0x00000040 #define FTENT_FADEOUT 0x00000080 #define FTENT_SPRANIMATE 0x00000100 #define FTENT_HITSOUND 0x00000200 #define FTENT_SPIRAL 0x00000400 #define FTENT_SPRCYCLE 0x00000800 #define FTENT_COLLIDEALL 0x00001000 // will collide with world and slideboxes #define FTENT_PERSIST 0x00002000 // tent is not removed when unable to draw #define FTENT_COLLIDEKILL 0x00004000 // tent is removed upon collision with anything #define FTENT_PLYRATTACHMENT 0x00008000 // tent is attached to a player (owner) #define FTENT_SPRANIMATELOOP 0x00010000 // animating sprite doesn't die when last frame is displayed #define FTENT_SPARKSHOWER 0x00020000 #define FTENT_NOMODEL 0x00040000 // Doesn't have a model, never try to draw ( it just triggers other things ) #define FTENT_CLIENTCUSTOM 0x00080000 // Must specify callback. Callback function is responsible for killing tempent and updating fields ( unless other flags specify how to do things ) #define FTENT_SCALE 0x00100000 // An experiment struct pmtrace_s; typedef struct tempent_s { int flags; float die; float frameMax; float x; float y; float z; float fadeSpeed; float bounceFactor; int hitSound; void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ); void (*callback)( struct tempent_s *ent, float frametime, float currenttime ); struct tempent_s *next; int priority; short clientIndex; // if attached, this is the index of the client to stick to // if COLLIDEALL, this is the index of the client to ignore // TENTS with FTENT_PLYRATTACHMENT MUST set the clientindex! vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. cl_entity_t entity; // baseline.origin - velocity // baseline.renderamt - starting fadeout intensity // baseline.angles - angle velocity } TEMPENTITY; typedef struct efx_api_s efx_api_t; struct efx_api_s { particle_t *(*R_AllocParticle)( void (*callback)( struct particle_s *particle, float frametime )); void (*R_BlobExplosion)( const float *org ); void (*R_Blood)( const float *org, const float *dir, int pcolor, int speed ); void (*R_BloodSprite)( const float *org, int colorindex, int modelIndex, int modelIndex2, float size ); void (*R_BloodStream)( const float *org, const float *dir, int pcolor, int speed ); void (*R_BreakModel)( const float *pos, const float *size, const float *dir, float random, float life, int count, int modelIndex, char flags ); void (*R_Bubbles)( const float *mins, const float *maxs, float height, int modelIndex, int count, float speed ); void (*R_BubbleTrail)( const float *start, const float *end, float height, int modelIndex, int count, float speed ); void (*R_BulletImpactParticles)( const float *pos ); void (*R_EntityParticles)( struct cl_entity_s *ent ); void (*R_Explosion)( float *pos, int model, float scale, float framerate, int flags ); void (*R_FizzEffect)( struct cl_entity_s *pent, int modelIndex, int density ); void (*R_FireField)( float *org, int radius, int modelIndex, int count, int flags, float life ); void (*R_FlickerParticles)( const float *org ); void (*R_FunnelSprite)( const float *org, int modelIndex, int reverse ); void (*R_Implosion)( const float *end, float radius, int count, float life ); void (*R_LargeFunnel)( const float *org, int reverse ); void (*R_LavaSplash)( const float *org ); void (*R_MultiGunshot)( const float *org, const float *dir, const float *noise, int count, int decalCount, int *decalIndices ); void (*R_MuzzleFlash)( const float *pos1, int type ); void (*R_ParticleBox)( const float *mins, const float *maxs, unsigned char r, unsigned char g, unsigned char b, float life ); void (*R_ParticleBurst)( const float *pos, int size, int color, float life ); void (*R_ParticleExplosion)( const float *org ); void (*R_ParticleExplosion2)( const float *org, int colorStart, int colorLength ); void (*R_ParticleLine)( const float *start, const float *end, unsigned char r, unsigned char g, unsigned char b, float life ); void (*R_PlayerSprites)( int client, int modelIndex, int count, int size ); void (*R_Projectile)( const float *origin, const float *velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ) ); void (*R_RicochetSound)( const float *pos ); void (*R_RicochetSprite)( const float *pos, struct model_s *pmodel, float duration, float scale ); void (*R_RocketFlare)( const float *pos ); void (*R_RocketTrail)( float *start, float *end, int type ); void (*R_RunParticleEffect)( const float *org, const float *dir, int color, int count ); void (*R_ShowLine)( const float *start, const float *end ); void (*R_SparkEffect)( const float *pos, int count, int velocityMin, int velocityMax ); void (*R_SparkShower)( const float *pos ); void (*R_SparkStreaks)( const float *pos, int count, int velocityMin, int velocityMax ); void (*R_Spray)( const float *pos, const float *dir, int modelIndex, int count, int speed, int spread, int rendermode ); void (*R_Sprite_Explode)( TEMPENTITY *pTemp, float scale, int flags ); void (*R_Sprite_Smoke)( TEMPENTITY *pTemp, float scale ); void (*R_Sprite_Spray)( const float *pos, const float *dir, int modelIndex, int count, int speed, int iRand ); void (*R_Sprite_Trail)( int type, float *start, float *end, int modelIndex, int count, float life, float size, float amplitude, int renderamt, float speed ); void (*R_Sprite_WallPuff)( TEMPENTITY *pTemp, float scale ); void (*R_StreakSplash)( const float *pos, const float *dir, int color, int count, float speed, int velocityMin, int velocityMax ); void (*R_TracerEffect)( const float *start, const float *end ); void (*R_UserTracerParticle)( float *org, float *vel, float life, int colorIndex, float length, unsigned char deathcontext, void (*deathfunc)( struct particle_s *particle )); particle_t *(*R_TracerParticles)( float *org, float *vel, float life ); void (*R_TeleportSplash)( const float *org ); void (*R_TempSphereModel)( const float *pos, float speed, float life, int count, int modelIndex ); TEMPENTITY *(*R_TempModel)( const float *pos, const float *dir, const float *angles, float life, int modelIndex, int soundtype ); TEMPENTITY *(*R_DefaultSprite)( const float *pos, int spriteIndex, float framerate ); TEMPENTITY *(*R_TempSprite)( float *pos, const float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ); int (*Draw_DecalIndex)( int id ); int (*Draw_DecalIndexFromName)( const char *name ); void (*R_DecalShoot)( int textureIndex, int entity, int modelIndex, float *position, int flags ); void (*R_AttachTentToPlayer)( int client, int modelIndex, float zoffset, float life ); void (*R_KillAttachedTents)( int client ); BEAM *(*R_BeamCirclePoints)( int type, float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); BEAM *(*R_BeamEntPoint)( int startEnt, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); BEAM *(*R_BeamEnts)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); BEAM *(*R_BeamFollow)( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); void (*R_BeamKill)( int deadEntity ); BEAM *(*R_BeamLightning)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ); BEAM *(*R_BeamPoints)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); BEAM *(*R_BeamRing)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); dlight_t *(*CL_AllocDlight)( int key ); dlight_t *(*CL_AllocElight)( int key ); TEMPENTITY *(*CL_TempEntAlloc)( const float *org, struct model_s *model ); TEMPENTITY *(*CL_TempEntAllocNoModel)( const float *org ); TEMPENTITY *(*CL_TempEntAllocHigh)( const float *org, struct model_s *model ); TEMPENTITY *(*CL_TentEntAllocCustom)( const float *origin, struct model_s *model, int high, void (*callback)( struct tempent_s *ent, float frametime, float currenttime )); void (*R_GetPackedColor)( short *packed, short color ); short (*R_LookupColor)( unsigned char r, unsigned char g, unsigned char b ); void (*R_DecalRemoveAll)( int textureIndex ); // textureIndex points to the decal index in the array, not the actual texture index. void (*R_FireCustomDecal)( int textureIndex, int entity, int modelIndex, float *position, int flags, float scale ); }; #endif//R_EFX_H ================================================ FILE: common/r_studioint.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef R_STUDIOINT_H #define R_STUDIOINT_H #define STUDIO_INTERFACE_VERSION 1 typedef struct engine_studio_api_s { // Allocate number*size bytes and zero it void *( *Mem_Calloc )( int number, size_t size ); // Check to see if pointer is in the cache void *( *Cache_Check )( struct cache_user_s *c ); // Load file into cache ( can be swapped out on demand ) void ( *LoadCacheFile )( const char *path, struct cache_user_s *cu ); // Retrieve model pointer for the named model struct model_s *( *Mod_ForName )( const char *name, int crash_if_missing ); // Retrieve pointer to studio model data block from a model void *( *Mod_Extradata )( struct model_s *mod ); // Retrieve indexed model from client side model precache list struct model_s *( *GetModelByIndex )( int index ); // Get entity that is set for rendering struct cl_entity_s * ( *GetCurrentEntity )( void ); // Get referenced player_info_t struct player_info_s *( *PlayerInfo )( int index ); // Get most recently received player state data from network system struct entity_state_s *( *GetPlayerState )( int index ); // Get viewentity struct cl_entity_s * ( *GetViewEntity )( void ); // Get current frame count, and last two timestampes on client void ( *GetTimes )( int *framecount, double *current, double *old ); // Get a pointer to a cvar by name struct cvar_s *( *GetCvar )( const char *name ); // Get current render origin and view vectors ( up, right and vpn ) void ( *GetViewInfo )( float *origin, float *upv, float *rightv, float *vpnv ); // Get sprite model used for applying chrome effect struct model_s *( *GetChromeSprite )( void ); // Get model counters so we can incement instrumentation void ( *GetModelCounters )( int **s, int **a ); // Get software scaling coefficients void ( *GetAliasScale )( float *x, float *y ); // Get bone, light, alias, and rotation matrices float ****( *StudioGetBoneTransform )( void ); float ****( *StudioGetLightTransform )( void ); float ***( *StudioGetAliasTransform )( void ); float ***( *StudioGetRotationMatrix )( void ); // Set up body part, and get submodel pointers void ( *StudioSetupModel )( int bodypart, void **ppbodypart, void **ppsubmodel ); // Check if entity's bbox is in the view frustum int ( *StudioCheckBBox )( void ); // Apply lighting effects to model void ( *StudioDynamicLight )( struct cl_entity_s *ent, struct alight_s *plight ); void ( *StudioEntityLight )( struct alight_s *plight ); void ( *StudioSetupLighting )( struct alight_s *plighting ); // Draw mesh vertices void ( *StudioDrawPoints )( void ); // Draw hulls around bones void ( *StudioDrawHulls )( void ); // Draw bbox around studio models void ( *StudioDrawAbsBBox )( void ); // Draws bones void ( *StudioDrawBones )( void ); // Loads in appropriate texture for model void ( *StudioSetupSkin )( void *ptexturehdr, int index ); // Sets up for remapped colors void ( *StudioSetRemapColors )( int top, int bottom ); // Set's player model and returns model pointer struct model_s *( *SetupPlayerModel )( int index ); // Fires any events embedded in animation void ( *StudioClientEvents )( void ); // Retrieve/set forced render effects flags int ( *GetForceFaceFlags )( void ); void ( *SetForceFaceFlags )( int flags ); // Tell engine the value of the studio model header void ( *StudioSetHeader )( void *header ); // Tell engine which model_t * is being renderered void ( *SetRenderModel )( struct model_s *model ); // Final state setup and restore for rendering void ( *SetupRenderer )( int rendermode ); void ( *RestoreRenderer )( void ); // Set render origin for applying chrome effect void ( *SetChromeOrigin )( void ); // True if using D3D/OpenGL int ( *IsHardware )( void ); // Only called by hardware interface void ( *GL_StudioDrawShadow )( void ); void ( *GL_SetRenderMode )( int mode ); void ( *StudioSetRenderamt )( int iRenderamt ); void ( *StudioSetCullState )( int iCull ); void ( *StudioRenderShadow )( int iSprite, float *p1, float *p2, float *p3, float *p4 ); } engine_studio_api_t; typedef struct server_studio_api_s { // Allocate number*size bytes and zero it void *( *Mem_Calloc )( int number, size_t size ); // Check to see if pointer is in the cache void *( *Cache_Check )( struct cache_user_s *c ); // Load file into cache ( can be swapped out on demand ) void ( *LoadCacheFile )( const char *path, struct cache_user_s *cu ); // Retrieve pointer to studio model data block from a model void *( *Mod_Extradata )( struct model_s *mod ); } server_studio_api_t; // client blending typedef struct r_studio_interface_s { int version; int ( *StudioDrawModel )( int flags ); int ( *StudioDrawPlayer )( int flags, struct entity_state_s *pplayer ); } r_studio_interface_t; // server blending #define SV_BLENDING_INTERFACE_VERSION 1 typedef struct sv_blending_interface_s { int version; void ( *SV_StudioSetupBones )( struct model_s *pModel, float frame, int sequence, const vec3_t angles, const vec3_t origin, const byte *pcontroller, const byte *pblending, int iBone, const edict_t *pEdict ); } sv_blending_interface_t; #endif//R_STUDIOINT_H ================================================ FILE: common/ref_device.h ================================================ /* ref_device.h - common structures for retrieving GPU information for refs, menu and engine Copyright (C) 2021 a1batross This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef REF_DEVICE_H #define REF_DEVICE_H // modeled after Vulkan and currently useful only for it typedef enum ref_device_type_e { REF_DEVICE_TYPE_OTHER = 0, REF_DEVICE_TYPE_INTERGRATED_GPU, REF_DEVICE_TYPE_DISCRETE_GPU, REF_DEVICE_TYPE_VIRTUAL_GPU, REF_DEVICE_TYPE_CPU, REF_DEVICE_TYPE_LAST, } ref_device_type_t; #define REF_DEVICE_NAME_SIZE 256 // only add new fields to the end of the struct!!! typedef struct ref_device_s { int vendorID; int deviceID; ref_device_type_t deviceType; char deviceName[REF_DEVICE_NAME_SIZE]; } ref_device_t; #endif /* REF_DEVICE_H */ ================================================ FILE: common/ref_params.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef REF_PARAMS_H #define REF_PARAMS_H typedef struct ref_params_s { // output vec3_t vieworg; vec3_t viewangles; vec3_t forward; vec3_t right; vec3_t up; // Client frametime; float frametime; // Client time float time; // Misc int intermission; int paused; int spectator; int onground; int waterlevel; vec3_t simvel; vec3_t simorg; vec3_t viewheight; float idealpitch; vec3_t cl_viewangles; int health; vec3_t crosshairangle; float viewsize; vec3_t punchangle; int maxclients; int viewentity; int playernum; int max_entities; int demoplayback; int hardware; int smoothing; // Last issued usercmd struct usercmd_s *cmd; // Movevars struct movevars_s *movevars; int viewport[4]; // the viewport coordinates x, y, width, height int nextView; // the renderer calls ClientDLL_CalcRefdef() and Renderview // so long in cycles until this value is 0 (multiple views) int onlyClientDraw; // if !=0 nothing is drawn by the engine except clientDraw functions } ref_params_t; // same as ref_params but for overview mode typedef struct ref_overview_s { vec3_t origin; qboolean rotated; float xLeft; float xRight; float yTop; float yBottom; float zFar; float zNear; float flZoom; } ref_overview_t; // ref_viewpass_t->flags #define RF_DRAW_WORLD (1<<0) // pass should draw the world (otherwise it's player menu model) #define RF_DRAW_CUBEMAP (1<<1) // special 6x pass to render cubemap\skybox sides #define RF_DRAW_OVERVIEW (1<<2) // overview mode is active #define RF_ONLY_CLIENTDRAW (1<<3) // nothing is drawn by the engine except clientDraw functions // intermediate struct for viewpass (or just a single frame) typedef struct ref_viewpass_s { int viewport[4]; // size of new viewport vec3_t vieworigin; // view origin vec3_t viewangles; // view angles int viewentity; // entitynum (P2: Savior uses this) float fov_x, fov_y; // vertical & horizontal FOV int flags; // if !=0 nothing is drawn by the engine except clientDraw functions } ref_viewpass_t; #endif//REF_PARAMS_H ================================================ FILE: common/render_api.h ================================================ /* render_api.h - Xash3D extension for client interface Copyright (C) 2011 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef RENDER_API_H #define RENDER_API_H #include "lightstyle.h" #include "dlight.h" #define CL_RENDER_INTERFACE_VERSION 37 // Xash3D 1.0 #define MAX_STUDIO_DECALS 4096 // + unused space of BSP decals // render info parms #define PARM_TEX_WIDTH 1 // all parms with prefix 'TEX_' receive arg as texnum #define PARM_TEX_HEIGHT 2 // otherwise it's not used #define PARM_TEX_SRC_WIDTH 3 #define PARM_TEX_SRC_HEIGHT 4 #define PARM_TEX_SKYBOX 5 // second arg as skybox ordering num #define PARM_TEX_SKYTEXNUM 6 // skytexturenum for quake sky #define PARM_TEX_LIGHTMAP 7 // second arg as number 0 - 128 #define PARM_TEX_TARGET 8 #define PARM_TEX_TEXNUM 9 #define PARM_TEX_FLAGS 10 #define PARM_TEX_DEPTH 11 // 3D texture depth or 2D array num layers //reserved #define PARM_TEX_GLFORMAT 13 // get a texture GL-format #define PARM_TEX_ENCODE 14 // custom encoding for DXT image #define PARM_TEX_MIPCOUNT 15 // count of mipmaps (0 - autogenerated, 1 - disabled of mipmapping) #define PARM_BSP2_SUPPORTED 16 // tell custom renderer what engine is support BSP2 in this build #define PARM_SKY_SPHERE 17 // sky is quake sphere ? #define PARAM_GAMEPAUSED 18 // game is paused #define PARM_MAP_HAS_DELUXE 19 // map has deluxedata #define PARM_MAX_ENTITIES 20 #define PARM_WIDESCREEN 21 #define PARM_FULLSCREEN 22 #define PARM_SCREEN_WIDTH 23 #define PARM_SCREEN_HEIGHT 24 #define PARM_CLIENT_INGAME 25 #define PARM_FEATURES 26 // same as movevars->features #define PARM_ACTIVE_TMU 27 // for debug #define PARM_LIGHTSTYLEVALUE 28 // second arg is stylenum #define PARM_MAX_IMAGE_UNITS 29 #define PARM_CLIENT_ACTIVE 30 #define PARM_REBUILD_GAMMA 31 // if true lightmaps rebuilding for gamma change #define PARM_DEDICATED_SERVER 32 #define PARM_SURF_SAMPLESIZE 33 // lightmap resolution per face (second arg interpret as facenumber) #define PARM_GL_CONTEXT_TYPE 34 // opengl or opengles #define PARM_GLES_WRAPPER 35 // #define PARM_STENCIL_ACTIVE 36 #define PARM_WATER_ALPHA 37 #define PARM_TEX_MEMORY 38 // returns total memory of uploaded texture in bytes #define PARM_DELUXEDATA 39 // nasty hack, convert int to pointer #define PARM_SHADOWDATA 40 // nasty hack, convert int to pointer #define PARM_MODERNFLASHLIGHT 41 // new dynamic flashlight, initially for Vulkan render // skybox ordering enum { SKYBOX_RIGHT = 0, SKYBOX_BACK, SKYBOX_LEFT, SKYBOX_FORWARD, SKYBOX_UP, SKYBOX_DOWN, }; typedef enum { TF_COLORMAP = 0, // just for tabulate source TF_NEAREST = (1<<0), // disable texfilter TF_KEEP_SOURCE = (1<<1), // some images keep source TF_NOFLIP_TGA = (1<<2), // Steam background completely ignore tga attribute 0x20 TF_EXPAND_SOURCE = (1<<3), // Don't keep source as 8-bit expand to RGBA // reserved TF_RECTANGLE = (1<<5), // this is GL_TEXTURE_RECTANGLE TF_CUBEMAP = (1<<6), // it's cubemap texture TF_DEPTHMAP = (1<<7), // custom texture filter used TF_QUAKEPAL = (1<<8), // image has an quake1 palette TF_LUMINANCE = (1<<9), // force image to grayscale TF_SKYSIDE = (1<<10), // this is a part of skybox TF_CLAMP = (1<<11), // clamp texcoords to [0..1] range TF_NOMIPMAP = (1<<12), // don't build mips for this image TF_HAS_LUMA = (1<<13), // sets by GL_UploadTexture TF_MAKELUMA = (1<<14), // create luma from quake texture (only q1 textures contain luma-pixels) TF_NORMALMAP = (1<<15), // is a normalmap TF_HAS_ALPHA = (1<<16), // image has alpha (used only for GL_CreateTexture) TF_FORCE_COLOR = (1<<17), // force upload monochrome textures as RGB (detail textures) TF_UPDATE = (1<<18), // allow to update already loaded texture TF_BORDER = (1<<19), // zero clamp for projected textures TF_TEXTURE_3D = (1<<20), // this is GL_TEXTURE_3D TF_ATLAS_PAGE = (1<<21), // bit who indicate lightmap page or deluxemap page TF_ALPHACONTRAST = (1<<22), // special texture mode for A2C // reserved // reserved TF_IMG_UPLOADED = (1<<25), // this is set for first time when called glTexImage, otherwise it will be call glTexSubImage TF_ARB_FLOAT = (1<<26), // float textures TF_NOCOMPARE = (1<<27), // disable comparing for depth textures TF_ARB_16BIT = (1<<28), // keep image as 16-bit (not 24) TF_MULTISAMPLE = (1<<29), // multisampling texture TF_ALLOW_NEAREST = (1<<30), // allows toggling nearest filtering for TF_NOMIPMAP textures } texFlags_t; typedef enum { CONTEXT_TYPE_GL = 0, // compatibility profile CONTEXT_TYPE_GLES_1_X, CONTEXT_TYPE_GLES_2_X, CONTEXT_TYPE_GL_CORE } gl_context_type_t; typedef enum { GLES_WRAPPER_NONE = 0, // native GL GLES_WRAPPER_NANOGL, // used on GLES platforms GLES_WRAPPER_WES, // used on GLES platforms GLES_WRAPPER_GL4ES, // used on GLES platforms } gles_wrapper_t; // 30 bytes here typedef struct modelstate_s { short sequence; short frame; // 10 bits multiple by 4, should be enough byte blending[2]; byte controller[4]; byte poseparam[16]; byte body; byte skin; short scale; // model scale (multiplied by 16) } modelstate_t; typedef struct decallist_s { vec3_t position; char name[64]; short entityIndex; byte depth; byte flags; float scale; // this is the surface plane that we hit so that // we can move certain decals across // transitions if they hit similar geometry vec3_t impactPlaneNormal; modelstate_t studio_state; // studio decals only } decallist_t; enum movie_parms_e { AVI_PARM_LAST = 0, // marker for SetParm to end parse parsing arguments AVI_RENDER_TEXNUM, // (int) sets texture to draw into, if 0 will draw to screen AVI_RENDER_X, // (int) when set to screen, sets position where to draw AVI_RENDER_Y, AVI_RENDER_W, // (int) sets texture or screen width AVI_RENDER_H, // set to -1 to draw full screen AVI_REWIND, // no argument, rewind playback to the beginning AVI_ENTNUM, // (int) entity number, -1 for no spatialization AVI_VOLUME, // (int) volume from 0 to 255 AVI_ATTN, // (float) attenuation value AVI_PAUSE, // no argument, pauses playback AVI_RESUME, // no argument, resumes playback }; struct movie_state_s; struct ref_viewpass_s; typedef struct render_api_s { // Get renderer info (doesn't changes engine state at all) intptr_t (*RenderGetParm)( int parm, int arg ); // generic void (*GetDetailScaleForTexture)( int texture, float *xScale, float *yScale ); void (*GetExtraParmsForTexture)( int texture, byte *red, byte *green, byte *blue, byte *alpha ); lightstyle_t* (*GetLightStyle)( int number ); dlight_t* (*GetDynamicLight)( int number ); dlight_t* (*GetEntityLight)( int number ); byte (*LightToTexGamma)( byte color ); // software gamma support float (*GetFrameTime)( void ); // Set renderer info (tell engine about changes) void (*R_SetCurrentEntity)( struct cl_entity_s *ent ); // tell engine about both currententity and currentmodel void (*R_SetCurrentModel)( struct model_s *mod ); // change currentmodel but leave currententity unchanged int (*R_FatPVS)( const float *org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ); void (*R_StoreEfrags)( struct efrag_s **ppefrag, int framecount );// store efrags for static entities // Texture tools int (*GL_FindTexture)( const char *name ); const char* (*GL_TextureName)( unsigned int texnum ); const byte* (*GL_TextureData)( unsigned int texnum ); // may be NULL int (*GL_LoadTexture)( const char *name, const byte *buf, size_t size, int flags ); int (*GL_CreateTexture)( const char *name, int width, int height, const void *buffer, texFlags_t flags ); int (*GL_LoadTextureArray)( const char **names, int flags ); int (*GL_CreateTextureArray)( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ); void (*GL_FreeTexture)( unsigned int texnum ); // Decals manipulating (draw & remove) void (*DrawSingleDecal)( struct decal_s *pDecal, struct msurface_s *fa ); float *(*R_DecalSetupVerts)( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount ); void (*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only) // AVIkit support struct movie_state_s *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); qboolean (*AVI_GetVideoInfo)( struct movie_state_s *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int int (*AVI_GetVideoFrameNumber)( struct movie_state_s *Avi, float time ); byte *(*AVI_GetVideoFrame)( struct movie_state_s *Avi, int frame ); void (*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data ); void (*AVI_FreeVideo)( struct movie_state_s *Avi ); qboolean (*AVI_IsActive)( struct movie_state_s *Avi ); void (*AVI_StreamSound)( struct movie_state_s *Avi, int entnum, float fvol, float attn, float synctime ); qboolean (*AVI_Think)( struct movie_state_s *Avi ); qboolean (*AVI_SetParm)( struct movie_state_s *Avi, enum movie_parms_e parm, ... ); // glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client) void (*GL_Bind)( int tmu, unsigned int texnum ); void (*GL_SelectTexture)( int tmu ); void (*GL_LoadTextureMatrix)( const float *glmatrix ); void (*GL_TexMatrixIdentity)( void ); void (*GL_CleanUpTextureUnits)( int last ); // pass 0 for clear all the texture units void (*GL_TexGen)( unsigned int coord, unsigned int mode ); void (*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture void (*GL_TexCoordArrayMode)( unsigned int texmode ); void* (*GL_GetProcAddress)( const char *name ); void (*GL_UpdateTexSize)( int texnum, int width, int height, int depth ); // recalc statistics void (*GL_Reserved0)( void ); // for potential interface expansion without broken compatibility void (*GL_Reserved1)( void ); // Misc renderer functions void (*GL_DrawParticles)( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime ); void (*EnvShot)( const float *vieworg, const char *name, qboolean skyshot, int shotsize ); // store skybox into gfx\env folder int (*SPR_LoadExt)( const char *szPicName, unsigned int texFlags ); // extended version of SPR_Load colorVec (*LightVec)( const float *start, const float *end, float *lightspot, float *lightvec ); struct mstudiotex_s *( *StudioGetTexture )( struct cl_entity_s *e ); const struct ref_overview_s *( *GetOverviewParms )( void ); const char *( *GetFileByIndex )( int fileindex ); int (*pfnSaveFile)( const char *filename, const void *data, int len ); void (*R_Reserved0)( void ); // static allocations void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ) ALLOC_CHECK( 1 ); void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); // engine utils (not related with render API but placed here) char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); unsigned int (*pfnFileBufferCRC32)( const void *buffer, const int length ); int (*COM_CompareFileTime)( const char *filename1, const char *filename2, int *iCompare ); void (*Host_Error)( const char *error, ... ); // cause Host Error void* ( *pfnGetModel )( int modelindex ); float (*pfnTime)( void ); // Sys_DoubleTime void (*Cvar_Set)( const char *name, const char *value ); void (*S_FadeMusicVolume)( float fadePercent ); // fade background track (0-100 percents) // a1ba: changed long to int void (*SetRandomSeed)( int lSeed ); // set custom seed for RANDOM_FLOAT\RANDOM_LONG for predictable random // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 37 } render_api_t; // render callbacks typedef struct render_interface_s { int version; // passed through R_RenderFrame (0 - use engine renderer, 1 - use custom client renderer) int (*GL_RenderFrame)( const struct ref_viewpass_s *rvp ); // build all the lightmaps on new level or when gamma is changed void (*GL_BuildLightmaps)( void ); // setup map bounds for ortho-projection when we in dev_overview mode void (*GL_OrthoBounds)( const float *mins, const float *maxs ); // prepare studio decals for save int (*R_CreateStudioDecalList)( decallist_t *pList, int count ); // clear decals by engine request (e.g. for demo recording or vid_restart) void (*R_ClearStudioDecals)( void ); // grab r_speeds message qboolean (*R_SpeedsMessage)( char *out, size_t size ); // alloc or destroy model custom data void (*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer ); // alloc or destroy entity custom data void (*R_ProcessEntData)( qboolean allocate ); // get visdata for current frame from custom renderer byte* (*Mod_GetCurrentVis)( void ); // tell the renderer what new map is started void (*R_NewMap)( void ); // clear the render entities before each frame void (*R_ClearScene)( void ); // shuffle previous & next states for lerping void (*CL_UpdateLatchedVars)( struct cl_entity_s *e, qboolean reset ); } render_interface_t; #endif//RENDER_API_H ================================================ FILE: common/screenfade.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef SCREENFADE_H #define SCREENFADE_H typedef struct screenfade_s { float fadeSpeed; // How fast to fade (tics / second) (+ fade in, - fade out) float fadeEnd; // When the fading hits maximum float fadeTotalEnd; // Total End Time of the fade (used for FFADE_OUT) float fadeReset; // When to reset to not fading (for fadeout and hold) byte fader, fadeg, fadeb, fadealpha; // Fade color int fadeFlags; // Fading flags } screenfade_t; #endif//SCREENFADE_H ================================================ FILE: common/studio_event.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #pragma once #ifndef STUDIO_EVENT_H #define STUDIO_EVENT_H #define MAXEVENTSTRING 64 typedef struct mstudioevent_s { // the frame at which this animation event occurs int32_t frame; // the script event type int32_t event; // was "type" int32_t unused; // options // could be path to sound WAVE files char options[MAXEVENTSTRING]; } mstudioevent_t; #endif // STUDIO_EVENT_H ================================================ FILE: common/synctype.h ================================================ /* synctype.h -- shared synctype_t definition Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 2023 Alibek Omarov 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 SYNCTYPE_H #define SYNCTYPE_H typedef enum {ST_SYNC=0, ST_RAND } synctype_t; #endif ================================================ FILE: common/triangleapi.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef TRIANGLEAPI_H #define TRIANGLEAPI_H typedef enum { TRI_FRONT = 0, TRI_NONE = 1, } TRICULLSTYLE; #define TRI_API_VERSION 1 #define TRI_TRIANGLES 0 #define TRI_TRIANGLE_FAN 1 #define TRI_QUADS 2 #define TRI_POLYGON 3 #define TRI_LINES 4 #define TRI_TRIANGLE_STRIP 5 #define TRI_QUAD_STRIP 6 #define TRI_POINTS 7 // Xash3D added typedef struct triangleapi_s { int version; void (*RenderMode)( int mode ); void (*Begin)( int primitiveCode ); void (*End)( void ); void (*Color4f)( float r, float g, float b, float a ); void (*Color4ub)( unsigned char r, unsigned char g, unsigned char b, unsigned char a ); void (*TexCoord2f)( float u, float v ); void (*Vertex3fv)( const float *worldPnt ); void (*Vertex3f)( float x, float y, float z ); void (*Brightness)( float brightness ); void (*CullFace)( TRICULLSTYLE style ); int (*SpriteTexture)( struct model_s *pSpriteModel, int frame ); int (*WorldToScreen)( const float *world, float *screen ); // Returns 1 if it's z clipped void (*Fog)( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b. void (*ScreenToWorld)( const float *screen, float *world ); void (*GetMatrix)( const int pname, float *matrix ); int (*BoxInPVS)( float *mins, float *maxs ); void (*LightAtPoint)( float *pos, float *value ); void (*Color4fRendermode)( float r, float g, float b, float a, int rendermode ); void (*FogParams)( float flDensity, int iFogSkybox ); } triangleapi_t; #endif//TRIANGLEAPI_H ================================================ FILE: common/usercmd.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef USERCMD_H #define USERCMD_H typedef struct usercmd_s { short lerp_msec; // Interpolation time on client byte msec; // Duration in ms of command vec3_t viewangles; // Command view angles // intended velocities float forwardmove; // Forward velocity float sidemove; // Sideways velocity float upmove; // Upward velocity byte lightlevel; // Light level at spot where we are standing. unsigned short buttons; // Attack and move buttons byte impulse; // Impulse command issued byte weaponselect; // Current weapon id // Experimental player impact stuff. int impact_index; vec3_t impact_position; } usercmd_t; #endif//USERCMD_H ================================================ FILE: common/wadfile.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef WADFILE_H #define WADFILE_H /* ======================================================================== .WAD archive format (WhereAllData - WAD) List of compressed files, that can be identify only by TYP_* header: dwadinfo_t[dwadinfo_t] file_1: byte[dwadinfo_t[num]->disksize] file_2: byte[dwadinfo_t[num]->disksize] file_3: byte[dwadinfo_t[num]->disksize] ... file_n: byte[dwadinfo_t[num]->disksize] infotable dlumpinfo_t[dwadinfo_t->numlumps] ======================================================================== */ #define IDWAD2HEADER (('2'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD2" quake wads #define IDWAD3HEADER (('3'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD3" half-life wads #define WAD3_NAMELEN 16 // dlumpinfo_t->attribs #define ATTR_NONE 0 // allow to read-write #define ATTR_READONLY BIT( 0 ) // don't overwrite this lump in anyway #define ATTR_COMPRESSED BIT( 1 ) // not used for now, just reserved #define ATTR_HIDDEN BIT( 2 ) // not used for now, just reserved #define ATTR_SYSTEM BIT( 3 ) // not used for now, just reserved // dlumpinfo_t->type #define TYP_ANY -1 // any type can be accepted #define TYP_NONE 0 // unknown lump type #define TYP_LABEL 1 // legacy from Doom1. Empty lump - label (like P_START, P_END etc) #define TYP_PALETTE 64 // quake or half-life palette (768 bytes) #define TYP_DDSTEX 65 // contain DDS texture #define TYP_GFXPIC 66 // menu or hud image (not contain mip-levels) #define TYP_MIPTEX 67 // quake1 and half-life in-game textures with four miplevels #define TYP_SCRIPT 68 // contain script files #define TYP_COLORMAP2 69 // old stuff. build palette from LBM file (not used) #define TYP_QFONT 70 // half-life font (qfont_t) /* ======================================================================== .LMP image format (Half-Life gfx.wad lumps) ======================================================================== */ typedef struct lmp_s { unsigned int width; unsigned int height; } lmp_t; /* ======================================================================== .MIP image format (half-Life textures) ======================================================================== */ typedef struct mip_s { char name[16]; unsigned int width; unsigned int height; unsigned int offsets[4]; // four mip maps stored } mip_t; /* ======================================================================== .WAD header format (half-Life textures) ======================================================================== */ typedef struct { int ident; // should be WAD3 int numlumps; // num files int infotableofs; // LUT offset } dwadinfo_t; /* ======================================================================== .WAD struct (half-Life textures) ======================================================================== */ typedef struct { int filepos; // file offset in WAD int disksize; // compressed or uncompressed int size; // uncompressed signed char type; // TYP_* signed char attribs; // file attribs signed char pad0; signed char pad1; char name[WAD3_NAMELEN]; // must be null terminated } dlumpinfo_t; #endif//WADFILE_H ================================================ FILE: common/weaponinfo.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef WEAPONINFO_H #define WEAPONINFO_H // Info about weapons player might have in his/her possession typedef struct weapon_data_s { int m_iId; int m_iClip; float m_flNextPrimaryAttack; float m_flNextSecondaryAttack; float m_flTimeWeaponIdle; int m_fInReload; int m_fInSpecialReload; float m_flNextReload; float m_flPumpTime; float m_fReloadTime; float m_fAimedDamage; float m_fNextAimBonus; int m_fInZoom; int m_iWeaponState; int iuser1; int iuser2; int iuser3; int iuser4; float fuser1; float fuser2; float fuser3; float fuser4; } weapon_data_t; #endif//WEAPONINFO_H ================================================ FILE: common/wrect.h ================================================ /* wrect.h - rectangle definition Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef WRECT_H #define WRECT_H typedef struct wrect_s { int left, right, top, bottom; } wrect_t; #endif//WRECT_H ================================================ FILE: common/xash3d_types.h ================================================ // basic typedefs #ifndef XASH_TYPES_H #define XASH_TYPES_H #include "build.h" #if XASH_IRIX #include #endif #if XASH_WIN32 #include // off_t #endif // _WIN32 #include // off_t #ifdef STDINT_H #include STDINT_H #else // !STDINT_H #include #endif // !STDINT_H #include typedef uint8_t byte; typedef float vec_t; typedef vec_t vec2_t[2]; #ifndef vec3_t // SDK renames it to Vector typedef vec_t vec3_t[3]; #endif typedef vec_t vec4_t[4]; typedef vec_t quat_t[4]; typedef byte rgba_t[4]; // unsigned byte colorpack typedef byte rgb_t[3]; // unsigned byte colorpack typedef vec_t matrix3x4[3][4]; typedef vec_t matrix4x4[4][4]; typedef uint32_t poolhandle_t; #undef true #undef false // true and false are keywords in C++ and C23 #if !__cplusplus && __STDC_VERSION__ < 202311L enum { false, true }; #endif typedef int qboolean; #define MAX_STRING 256 // generic string #define MAX_VA_STRING 1024 // compatibility macro #define MAX_SYSPATH 1024 // system filepath #define MAX_MODS 512 // environment games that engine can keep visible #define BIT( n ) ( 1U << ( n )) #define BIT64( n ) ( 1ULL << ( n )) #define SetBits( iBitVector, bits ) ((iBitVector) = (iBitVector) | (bits)) #define ClearBits( iBitVector, bits ) ((iBitVector) = (iBitVector) & ~(bits)) #define FBitSet( iBitVector, bit ) ((iBitVector) & (bit)) #ifndef __cplusplus #ifdef NULL #undef NULL #endif #define NULL ((void *)0) #endif // color strings #define IsColorString( p ) ( p && *( p ) == '^' && *(( p ) + 1) && *(( p ) + 1) >= '0' && *(( p ) + 1 ) <= '9' ) #define ColorIndex( c ) ((( c ) - '0' ) & 7 ) #undef EXPORT #if defined( __GNUC__ ) #if defined( __i386__ ) #define EXPORT __attribute__(( visibility( "default" ), force_align_arg_pointer )) #define GAME_EXPORT __attribute__(( force_align_arg_pointer )) #else #define EXPORT __attribute__(( visibility ( "default" ))) #define GAME_EXPORT #endif #define MALLOC __attribute__(( malloc )) // added in GCC 11 #if __GNUC__ >= 11 // might want to set noclone due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=116893 // but it's easier to not force mismatched-dealloc to error yet #define MALLOC_LIKE( x, y ) __attribute__(( malloc( x, y ))) #else #define MALLOC_LIKE( x, y ) MALLOC #endif #define NORETURN __attribute__(( noreturn )) #define NONNULL __attribute__(( nonnull )) #define RETURNS_NONNULL __attribute__(( returns_nonnull )) #if __clang__ || __MCST__ // clang has bugged returns_nonnull for functions pointers, it's ignored and generates a warning about objective-c? O_o // lcc doesn't support it at all #define PFN_RETURNS_NONNULL #else #define PFN_RETURNS_NONNULL RETURNS_NONNULL #endif #define FORMAT_CHECK( x ) __attribute__(( format( printf, x, x + 1 ))) #define ALLOC_CHECK( x ) __attribute__(( alloc_size( x ))) #define NO_ASAN __attribute__(( no_sanitize( "address" ))) #define WARN_UNUSED_RESULT __attribute__(( warn_unused_result )) #define RENAME_SYMBOL( x ) asm( x ) #else #if defined( _MSC_VER ) #define EXPORT __declspec( dllexport ) #define NO_ASAN __declspec( no_sanitize_address ) #else #define EXPORT #define NO_ASAN #endif #define GAME_EXPORT #define NORETURN #define NONNULL #define RETURNS_NONNULL #define PFN_RETURNS_NONNULL #define FORMAT_CHECK( x ) #define ALLOC_CHECK( x ) #define RENAME_SYMBOL( x ) #define MALLOC #define MALLOC_LIKE( x, y ) #define WARN_UNUSED_RESULT #endif #if defined( __has_feature ) #if __has_feature( address_sanitizer ) #define USE_ASAN 1 #endif // __has_feature #endif // defined( __has_feature ) #if !defined( USE_ASAN ) && defined( __SANITIZE_ADDRESS__ ) #define USE_ASAN 1 #endif #if __GNUC__ >= 3 #define unlikely( x ) __builtin_expect( x, 0 ) #define likely( x ) __builtin_expect( x, 1 ) #elif defined( __has_builtin ) #if __has_builtin( __builtin_expect ) // this must be after defined() check #define unlikely( x ) __builtin_expect( x, 0 ) #define likely( x ) __builtin_expect( x, 1 ) #endif #endif #if !defined( unlikely ) || !defined( likely ) #define unlikely( x ) ( x ) #define likely( x ) ( x ) #endif #if __STDC_VERSION__ >= 202311L || __cplusplus >= 201103L // C23 or C++ static_assert is a keyword #define STATIC_ASSERT_( ignore, x, y ) static_assert( x, y ) #define STATIC_ASSERT static_assert #elif __STDC_VERSION__ >= 201112L // in C11 it's _Static_assert #define STATIC_ASSERT_( ignore, x, y ) _Static_assert( x, y ) #define STATIC_ASSERT _Static_assert #else #define STATIC_ASSERT_( id, x, y ) extern int id[( x ) ? 1 : -1] // need these to correctly expand the line macro #define STATIC_ASSERT_3( line, x, y ) STATIC_ASSERT_( static_assert_ ## line, x, y ) #define STATIC_ASSERT_2( line, x, y ) STATIC_ASSERT_3( line, x, y ) #define STATIC_ASSERT( x, y ) STATIC_ASSERT_2( __LINE__, x, y ) #endif // at least, statically check size of some public structures #if XASH_64BIT #define STATIC_CHECK_SIZEOF( type, size32, size64 ) \ STATIC_ASSERT( sizeof( type ) == size64, #type " unexpected size" ) #else #define STATIC_CHECK_SIZEOF( type, size32, size64 ) \ STATIC_ASSERT( sizeof( type ) == size32, #type " unexpected size" ) #endif #if !defined( __cplusplus ) && __STDC_VERSION__ >= 199101L // not C++ and C99 or newer #define XASH_RESTRICT restrict #elif _MSC_VER || __GNUC__ || __clang__ // compiler-specific extensions #define XASH_RESTRICT __restrict #else #define XASH_RESTRICT // nothing #endif #ifdef XASH_BIG_ENDIAN #define LittleLong(x) (((int)(((x)&255)<<24)) + ((int)((((x)>>8)&255)<<16)) + ((int)(((x)>>16)&255)<<8) + (((x) >> 24)&255)) #define LittleLongSW(x) (x = LittleLong(x) ) #define LittleShort(x) ((short)( (((short)(x) >> 8) & 255) + (((short)(x) & 255) << 8))) #define LittleShortSW(x) (x = LittleShort(x) ) _inline float LittleFloat( float f ) { union { float f; unsigned char b[4]; } dat1, dat2; dat1.f = f; dat2.b[0] = dat1.b[3]; dat2.b[1] = dat1.b[2]; dat2.b[2] = dat1.b[1]; dat2.b[3] = dat1.b[0]; return dat2.f; } #else #define LittleLong(x) (x) #define LittleLongSW(x) #define LittleShort(x) (x) #define LittleShortSW(x) #define LittleFloat(x) (x) #endif typedef unsigned int dword; typedef unsigned int uint; typedef char string[MAX_STRING]; typedef off_t fs_offset_t; #if XASH_WIN32 typedef int fs_size_t; // return type of _read, _write funcs #else /* !XASH_WIN32 */ typedef ssize_t fs_size_t; #endif /* !XASH_WIN32 */ typedef void *(*pfnCreateInterface_t)( const char *, int * ); // config strings are a general means of communication from // the server to all connected clients. // each config string can be at most CS_SIZE characters. #if XASH_LOW_MEMORY == 0 #define MAX_QPATH 64 // max length of a game pathname #elif XASH_LOW_MEMORY == 2 #define MAX_QPATH 32 // should be enough for singleplayer #elif XASH_LOW_MEMORY == 1 #define MAX_QPATH 48 #endif #define MAX_OSPATH 260 // max length of a filesystem pathname #define CS_SIZE 64 // size of one config string #define CS_TIME 16 // size of time string #endif // XASH_TYPES_H ================================================ FILE: engine/alias.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #ifndef ALIAS_H #define ALIAS_H #include "build.h" #include STDINT_H #include "synctype.h" /* ============================================================================== ALIAS MODELS Alias models are position independent, so the cache manager can move them. ============================================================================== */ #define IDALIASHEADER (('O'<<24)+('P'<<16)+('D'<<8)+'I') // little-endian "IDPO" #define ALIAS_VERSION 6 // client-side model flags #define ALIAS_ROCKET 0x0001 // leave a trail #define ALIAS_GRENADE 0x0002 // leave a trail #define ALIAS_GIB 0x0004 // leave a trail #define ALIAS_ROTATE 0x0008 // rotate (bonus items) #define ALIAS_TRACER 0x0010 // green split trail #define ALIAS_ZOMGIB 0x0020 // small blood trail #define ALIAS_TRACER2 0x0040 // orange split trail + rotate #define ALIAS_TRACER3 0x0080 // purple trail typedef enum { ALIAS_SINGLE = 0, ALIAS_GROUP } aliasframetype_t; typedef enum { ALIAS_SKIN_SINGLE = 0, ALIAS_SKIN_GROUP } aliasskintype_t; typedef struct { int32_t ident; int32_t version; vec3_t scale; vec3_t scale_origin; float boundingradius; vec3_t eyeposition; int32_t numskins; int32_t skinwidth; int32_t skinheight; int32_t numverts; int32_t numtris; int32_t numframes; uint32_t synctype; // was synctype_t int32_t flags; float size; } daliashdr_t; STATIC_CHECK_SIZEOF( daliashdr_t, 84, 84 ); typedef struct { int32_t onseam; int32_t s; int32_t t; } stvert_t; STATIC_CHECK_SIZEOF( stvert_t, 12, 12 ); typedef struct dtriangle_s { int32_t facesfront; int32_t vertindex[3]; } dtriangle_t; STATIC_CHECK_SIZEOF( dtriangle_t, 16, 16 ); #define DT_FACES_FRONT 0x0010 #define ALIAS_ONSEAM 0x0020 typedef struct { trivertex_t bboxmin; // lightnormal isn't used trivertex_t bboxmax; // lightnormal isn't used char name[16]; // frame name from grabbing } daliasframe_t; STATIC_CHECK_SIZEOF( daliasframe_t, 24, 24 ); typedef struct { int32_t numframes; trivertex_t bboxmin; // lightnormal isn't used trivertex_t bboxmax; // lightnormal isn't used } daliasgroup_t; STATIC_CHECK_SIZEOF( daliasgroup_t, 12, 12 ); typedef struct { int32_t numskins; } daliasskingroup_t; STATIC_CHECK_SIZEOF( daliasskingroup_t, 4, 4 ); typedef struct { float interval; } daliasinterval_t; STATIC_CHECK_SIZEOF( daliasinterval_t, 4, 4 ); typedef struct { float interval; } daliasskininterval_t; STATIC_CHECK_SIZEOF( daliasskininterval_t, 4, 4 ); typedef struct { uint32_t type; // was aliasframetype_t } daliasframetype_t; STATIC_CHECK_SIZEOF( daliasframetype_t, 4, 4 ); typedef struct { uint32_t type; // was aliasskintype_t } daliasskintype_t; STATIC_CHECK_SIZEOF( daliasskintype_t, 4, 4 ); #endif//ALIAS_H ================================================ FILE: engine/anorms.h ================================================ /* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ {-0.525731, 0.000000, 0.850651}, {-0.442863, 0.238856, 0.864188}, {-0.295242, 0.000000, 0.955423}, {-0.309017, 0.500000, 0.809017}, {-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000}, {0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, {0.147621, 0.716567, 0.681718}, {0.000000, 0.525731, 0.850651}, {0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651}, {0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, {0.162460, 0.262866, 0.951056}, {-0.681718, 0.147621, 0.716567}, {-0.809017, 0.309017, 0.500000}, {-0.587785, 0.425325, 0.688191}, {-0.850651, 0.525731, 0.000000}, {-0.864188, 0.442863, 0.238856}, {-0.716567, 0.681718, 0.147621}, {-0.688191, 0.587785, 0.425325}, {-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863}, {-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, {-0.500000, 0.809017, -0.309017}, {-0.525731, 0.850651, 0.000000}, {0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863}, {0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.955423, 0.295242}, {-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863}, {0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, {0.238856, 0.864188, -0.442863}, {0.262866, 0.951056, -0.162460}, {0.500000, 0.809017, -0.309017}, {0.850651, 0.525731, 0.000000}, {0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, {0.525731, 0.850651, 0.000000}, {0.425325, 0.688191, 0.587785}, {0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325}, {0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, {0.587785, 0.425325, 0.688191}, {0.955423, 0.295242, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866}, {0.850651, -0.525731, 0.000000}, {0.955423, -0.295242, 0.000000}, {0.864188, -0.442863, 0.238856}, {0.951056, -0.162460, 0.262866}, {0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567}, {0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, {0.809017, 0.309017, -0.500000}, {0.951056, 0.162460, -0.262866}, {0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567}, {0.681718, -0.147621, -0.716567}, {0.850651, 0.000000, -0.525731}, {0.809017, -0.309017, -0.500000}, {0.864188, -0.442863, -0.238856}, {0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718}, {0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, {0.442863, 0.238856, -0.864188}, {0.587785, 0.425325, -0.688191}, {0.688191, 0.587785, -0.425325}, {-0.147621, 0.716567, -0.681718}, {-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, {-0.525731, 0.000000, -0.850651}, {-0.442863, 0.238856, -0.864188}, {-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056}, {0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, {0.162460, 0.262866, -0.951056}, {-0.442863, -0.238856, -0.864188}, {-0.309017, -0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056}, {0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, {0.147621, -0.716567, -0.681718}, {0.000000, -0.525731, -0.850651}, {0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188}, {0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, {0.500000, -0.809017, -0.309017}, {0.425325, -0.688191, -0.587785}, {0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325}, {0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, {0.000000, -1.000000, 0.000000}, {0.262866, -0.951056, -0.162460}, {0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242}, {0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, {0.500000, -0.809017, 0.309017}, {0.716567, -0.681718, 0.147621}, {0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863}, {-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, {-0.850651, -0.525731, 0.000000}, {-0.716567, -0.681718, -0.147621}, {-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000}, {-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, {-0.262866, -0.951056, 0.162460}, {-0.864188, -0.442863, 0.238856}, {-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325}, {-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, {-0.587785, -0.425325, 0.688191}, {-0.309017, -0.500000, 0.809017}, {-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785}, {-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, {0.162460, -0.262866, 0.951056}, {0.309017, -0.500000, 0.809017}, {0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651}, {0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, {0.688191, -0.587785, 0.425325}, {-0.955423, 0.295242, 0.000000}, {-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000}, {-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, {-0.951056, -0.162460, 0.262866}, {-0.864188, 0.442863, -0.238856}, {-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000}, {-0.864188, -0.442863, -0.238856}, {-0.951056, -0.162460, -0.262866}, {-0.809017, -0.309017, -0.500000}, {-0.681718, 0.147621, -0.716567}, {-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731}, {-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, {-0.425325, 0.688191, -0.587785}, {-0.425325, -0.688191, -0.587785}, {-0.587785, -0.425325, -0.688191}, {-0.688191, -0.587785, -0.425325}, ================================================ FILE: engine/cdll_exp.h ================================================ /* cdll_exp.h - exports for client Copyright (C) 2013 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef CDLL_EXP_H #define CDLL_EXP_H struct tempent_s; struct usercmd_s; struct physent_s; struct playermove_s; struct mstudioevent_s; struct engine_studio_api_s; struct r_studio_interface_s; // NOTE: ordering is important! typedef struct cldll_func_s { int (*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion ); void (*pfnInit)( void ); int (*pfnVidInit)( void ); int (*pfnRedraw)( float flTime, int intermission ); int (*pfnUpdateClientData)( client_data_t *cdata, float flTime ); void (*pfnReset)( void ); void (*pfnPlayerMove)( struct playermove_s *ppmove, int server ); void (*pfnPlayerMoveInit)( struct playermove_s *ppmove ); char (*pfnPlayerMoveTexture)( char *name ); void (*IN_ActivateMouse)( void ); void (*IN_DeactivateMouse)( void ); void (*IN_MouseEvent)( int mstate ); void (*IN_ClearStates)( void ); void (*IN_Accumulate)( void ); void (*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active ); int (*CL_IsThirdPerson)( void ); void (*CL_CameraOffset)( float *ofs ); // unused void *(*KB_Find)( const char *name ); void (*CAM_Think)( void ); // camera stuff void (*pfnCalcRefdef)( ref_params_t *pparams ); int (*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname ); void (*pfnCreateEntities)( void ); void (*pfnDrawNormalTriangles)( void ); void (*pfnDrawTransparentTriangles)( void ); void (*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity ); void (*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed ); void (*pfnShutdown)( void ); void (*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client ); void (*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src ); void (*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd ); void (*pfnDemo_ReadBuffer)( int size, byte *buffer ); int (*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size ); int (*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs ); void (*pfnFrame)( double time ); int (*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding ); void (*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); cl_entity_t *(*pfnGetUserEntity)( int index ); void (*pfnVoiceStatus)( int entindex, qboolean bTalking ); void (*pfnDirectorMessage)( int iSize, void *pbuf ); int (*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); void (*pfnChatInputPosition)( int *x, int *y ); // Xash3D extension int (*pfnGetRenderInterface)( int version, render_api_t *renderfuncs, render_interface_t *callback ); void (*pfnClipMoveToEntity)( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); // Xash3D FWGS extension int (*pfnTouchEvent)( int type, int fingerID, float x, float y, float dx, float dy ); void (*pfnMoveEvent)( float forwardmove, float sidemove ); void (*pfnLookEvent)( float relyaw, float relpitch ); } cldll_func_t; #endif//CDLL_EXP_H ================================================ FILE: engine/cdll_int.h ================================================ /*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ // // cdll_int.h // // 4-23-98 // JOHN: client dll interface declarations // #ifndef CDLL_INT_H #define CDLL_INT_H #ifdef __cplusplus extern "C" { #endif #include "const.h" #include #define MAX_ALIAS_NAME 32 typedef struct cmdalias_s { struct cmdalias_s *next; char name[MAX_ALIAS_NAME]; char *value; } cmdalias_t; // this file is included by both the engine and the client-dll, // so make sure engine declarations aren't done twice typedef int HSPRITE; // handle to a graphic typedef int (*pfnUserMsgHook)( const char *pszName, int iSize, void *pbuf ); #include "wrect.h" #define SCRINFO_SCREENFLASH 1 #define SCRINFO_STRETCHED 2 typedef struct SCREENINFO_s { int iSize; int iWidth; int iHeight; int iFlags; int iCharHeight; short charWidths[256]; } SCREENINFO; typedef struct client_data_s { // fields that cannot be modified (ie. have no effect if changed) vec3_t origin; // fields that can be changed by the cldll vec3_t viewangles; int iWeaponBits; float fov; // field of view } client_data_t; typedef struct client_sprite_s { char szName[64]; char szSprite[64]; int hspr; int iRes; wrect_t rc; } client_sprite_t; typedef struct client_textmessage_s { int effect; byte r1, g1, b1, a1; // 2 colors for effects byte r2, g2, b2, a2; float x; float y; float fadein; float fadeout; float holdtime; float fxtime; const char *pName; const char *pMessage; } client_textmessage_t; typedef struct hud_player_info_s { char *name; short ping; byte thisplayer; // TRUE if this is the calling player // stuff that's unused at the moment, but should be done byte spectator; byte packetloss; char *model; short topcolor; short bottomcolor; uint64_t m_nSteamID; } hud_player_info_t; struct screenfade_s; struct tagPOINT; struct event_args_s; typedef struct cl_enginefuncs_s { // sprite handlers HSPRITE (*pfnSPR_Load)( const char *szPicName ); int (*pfnSPR_Frames)( HSPRITE hPic ); int (*pfnSPR_Height)( HSPRITE hPic, int frame ); int (*pfnSPR_Width)( HSPRITE hPic, int frame ); void (*pfnSPR_Set)( HSPRITE hPic, int r, int g, int b ); void (*pfnSPR_Draw)( int frame, int x, int y, const wrect_t *prc ); void (*pfnSPR_DrawHoles)( int frame, int x, int y, const wrect_t *prc ); void (*pfnSPR_DrawAdditive)( int frame, int x, int y, const wrect_t *prc ); void (*pfnSPR_EnableScissor)( int x, int y, int width, int height ); void (*pfnSPR_DisableScissor)( void ); client_sprite_t *(*pfnSPR_GetList)( char *psz, int *piCount ); // screen handlers void (*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a ); int (*pfnGetScreenInfo)( SCREENINFO *pscrinfo ); void (*pfnSetCrosshair)( HSPRITE hspr, wrect_t rc, int r, int g, int b ); // cvar handlers struct cvar_s *(*pfnRegisterVariable)( const char *szName, const char *szValue, int flags ); float (*pfnGetCvarFloat)( const char *szName ); const char* (*pfnGetCvarString)( const char *szName ); // command handlers int (*pfnAddCommand)( const char *cmd_name, void (*function)(void) ); int (*pfnHookUserMsg)( const char *szMsgName, pfnUserMsgHook pfn ); int (*pfnServerCmd)( const char *szCmdString ); int (*pfnClientCmd)( const char *szCmdString ); void (*pfnGetPlayerInfo)( int ent_num, hud_player_info_t *pinfo ); // sound handlers void (*pfnPlaySoundByName)( const char *szSound, float volume ); void (*pfnPlaySoundByIndex)( int iSound, float volume ); // vector helpers void (*pfnAngleVectors)( const float *vecAngles, float *forward, float *right, float *up ); // text message system client_textmessage_t *(*pfnTextMessageGet)( const char *pName ); int (*pfnDrawCharacter)( int x, int y, int number, int r, int g, int b ); int (*pfnDrawConsoleString)( int x, int y, char *string ); void (*pfnDrawSetTextColor)( float r, float g, float b ); void (*pfnDrawConsoleStringLen)( const char *string, int *length, int *height ); void (*pfnConsolePrint)( const char *string ); void (*pfnCenterPrint)( const char *string ); // Added for user input processing int (*GetWindowCenterX)( void ); int (*GetWindowCenterY)( void ); void (*GetViewAngles)( float * ); void (*SetViewAngles)( float * ); int (*GetMaxClients)( void ); void (*Cvar_SetValue)( const char *cvar, float value ); int (*Cmd_Argc)( void ); const char *(*Cmd_Argv)( int arg ); void (*Con_Printf)( const char *fmt, ... ); void (*Con_DPrintf)( const char *fmt, ... ); void (*Con_NPrintf)( int pos, const char *fmt, ... ); void (*Con_NXPrintf)( struct con_nprint_s *info, const char *fmt, ... ); const char* (*PhysInfo_ValueForKey)( const char *key ); const char* (*ServerInfo_ValueForKey)( const char *key ); float (*GetClientMaxspeed)( void ); int (*CheckParm)( char *parm, char **ppnext ); void (*Key_Event)( int key, int down ); void (*GetMousePosition)( int *mx, int *my ); int (*IsNoClipping)( void ); struct cl_entity_s *(*GetLocalPlayer)( void ); struct cl_entity_s *(*GetViewModel)( void ); struct cl_entity_s *(*GetEntityByIndex)( int idx ); float (*GetClientTime)( void ); void (*V_CalcShake)( void ); void (*V_ApplyShake)( float *origin, float *angles, float factor ); int (*PM_PointContents)( const float *point, int *truecontents ); int (*PM_WaterEntity)( const float *p ); struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehull, int ignore_pe ); struct model_s *(*CL_LoadModel)( const char *modelname, int *index ); int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); const struct model_s* (*GetSpritePointer)( HSPRITE hSprite ); void (*pfnPlaySoundByNameAtLocation)( char *szSound, float volume, float *origin ); unsigned short (*pfnPrecacheEvent)( int type, const char* psz ); void (*pfnPlaybackEvent)( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); void (*pfnWeaponAnim)( int iAnim, int body ); float (*pfnRandomFloat)( float flLow, float flHigh ); int (*pfnRandomLong)( int lLow, int lHigh ); void (*pfnHookEvent)( const char *name, void ( *pfnEvent )( struct event_args_s *args )); int (*Con_IsVisible) ( void ); const char *(*pfnGetGameDirectory)( void ); struct cvar_s *(*pfnGetCvarPointer)( const char *szName ); const char *(*Key_LookupBinding)( const char *pBinding ); const char *(*pfnGetLevelName)( void ); void (*pfnGetScreenFade)( struct screenfade_s *fade ); void (*pfnSetScreenFade)( struct screenfade_s *fade ); void* (*VGui_GetPanel)( void ); void (*VGui_ViewportPaintBackground)( int extents[4] ); byte* (*COM_LoadFile)( const char *path, int usehunk, int *pLength ); char* (*COM_ParseFile)( char *data, char *token ); void (*COM_FreeFile)( void *buffer ); struct triangleapi_s *pTriAPI; struct efx_api_s *pEfxAPI; struct event_api_s *pEventAPI; struct demo_api_s *pDemoAPI; struct net_api_s *pNetAPI; struct IVoiceTweak_s *pVoiceTweak; // returns 1 if the client is a spectator only (connected to a proxy), 0 otherwise or 2 if in dev_overview mode int (*IsSpectateOnly)( void ); struct model_s *(*LoadMapSprite)( const char *filename ); // file search functions void (*COM_AddAppDirectoryToSearchPath)( const char *pszBaseDir, const char *appName ); int (*COM_ExpandFilename)( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); // User info // playerNum is in the range (1, MaxClients) // returns NULL if player doesn't exit // returns "" if no value is set const char *( *PlayerInfo_ValueForKey )( int playerNum, const char *key ); void (*PlayerInfo_SetValueForKey )( const char *key, const char *value ); // Gets a unique ID for the specified player. This is the same even if you see the player on a different server. // iPlayer is an entity index, so client 0 would use iPlayer=1. // Returns false if there is no player on the server in the specified slot. qboolean (*GetPlayerUniqueID)(int iPlayer, char playerID[16]); // TrackerID access int (*GetTrackerIDForPlayer)(int playerSlot); int (*GetPlayerForTrackerID)(int trackerID); // Same as pfnServerCmd, but the message goes in the unreliable stream so it can't clog the net stream // (but it might not get there). int ( *pfnServerCmdUnreliable )( char *szCmdString ); void (*pfnGetMousePos)( struct tagPOINT *ppt ); void (*pfnSetMousePos)( int x, int y ); void (*pfnSetMouseEnable)( qboolean fEnable ); // undocumented interface starts here struct cvar_s* (*pfnGetFirstCvarPtr)( void ); void* (*pfnGetFirstCmdFunctionHandle)( void ); void* (*pfnGetNextCmdFunctionHandle)( void *cmdhandle ); const char* (*pfnGetCmdFunctionName)( void *cmdhandle ); float (*pfnGetClientOldTime)( void ); float (*pfnGetGravity)( void ); struct model_s* (*pfnGetModelByIndex)( int index ); void (*pfnSetFilterMode)( int mode ); // same as gl_texsort in original Quake void (*pfnSetFilterColor)( float red, float green, float blue ); void (*pfnSetFilterBrightness)( float brightness ); void *(*pfnSequenceGet)( const char *fileName, const char *entryName ); void (*pfnSPR_DrawGeneric)( int frame, int x, int y, const wrect_t *prc, int blendsrc, int blenddst, int width, int height ); void *(*pfnSequencePickSentence)( const char *groupName, int pickMethod, int *entryPicked ); int (*pfnDrawString)( int x, int y, const char *str, int r, int g, int b ); int (*pfnDrawStringReverse)( int x, int y, const char *str, int r, int g, int b ); const char *(*LocalPlayerInfo_ValueForKey)( const char* key ); int (*pfnVGUI2DrawCharacter)( int x, int y, int ch, unsigned int font ); int (*pfnVGUI2DrawCharacterAdditive)( int x, int y, int ch, int r, int g, int b, unsigned int font ); unsigned int (*pfnGetApproxWavePlayLen)( const char *filename ); void* (*GetCareerGameUI)( void ); // g-cont. !!!! potential crash-point! void (*Cvar_Set)( const char *name, const char *value ); int (*pfnIsPlayingCareerMatch)( void ); void (*pfnPlaySoundVoiceByName)( char *szSound, float volume, int pitch ); void (*pfnPrimeMusicStream)( char *filename, int looping ); double (*pfnSys_FloatTime)( void ); // decay funcs void (*pfnProcessTutorMessageDecayBuffer)( int *buffer, int buflen ); void (*pfnConstructTutorMessageDecayBuffer)( int *buffer, int buflen ); void (*pfnResetTutorMessageDecayData)( void ); void (*pfnPlaySoundByNameAtPitch)( char *szSound, float volume, int pitch ); void (*pfnFillRGBABlend)( int x, int y, int width, int height, int r, int g, int b, int a ); int (*pfnGetAppID)( void ); cmdalias_t *(*pfnGetAliases)( void ); void (*pfnVguiWrap2_GetMouseDelta)( int *x, int *y ); // added in 2019 update, not documented yet int (*pfnFilteredClientCmd)( const char *cmd ); } cl_enginefunc_t; #define CLDLL_INTERFACE_VERSION 7 #ifdef __cplusplus } #endif #endif//CDLL_INT_H ================================================ FILE: engine/client/avi/avi.h ================================================ /* avi.h -- common avi support header Copyright (C) 2018 a1batross, Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef AVI_H #define AVI_H // // avikit.c // typedef struct movie_state_s movie_state_t; int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ); byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ); qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ); qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ); void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ); movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ); int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ); void AVI_CloseVideo( movie_state_t *Avi ); qboolean AVI_IsActive( movie_state_t *Avi ); void AVI_FreeVideo( movie_state_t *Avi ); movie_state_t *AVI_GetState( int num ); qboolean AVI_Initailize( void ); void AVI_Shutdown( void ); qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ); qboolean AVI_Think( movie_state_t *Avi ); #endif // AVI_H ================================================ FILE: engine/client/avi/avi_ffmpeg.c ================================================ /* avi_ffmpreg.c - playing AVI files (ffmpeg backend) Copyright (C) FTEQW developers (for plugins/avplug/avdecode.c) Copyright (C) Sam Lantinga (for tests/testffmpeg.c) Copyright (C) 2023-2024 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "defaults.h" #include "common.h" #include "client.h" static qboolean avi_initialized; static poolhandle_t avi_mempool; #if XASH_AVI == AVI_FFMPEG #define XASH_FFMPEG_DLOPEN 1 #include "avi_ffmpeg.h" struct movie_state_s { // ffmpeg contexts AVFormatContext *fmt_ctx; AVCodecContext *video_ctx; AVCodecContext *audio_ctx; struct SwsContext *sws_ctx; struct SwrContext *swr_ctx; AVPacket *pkt; AVFrame *aframe; AVFrame *vframe; AVFrame *vframe_copy; int64_t first_time; int64_t last_time; // video stream byte *dst; double duration; int video_stream; int xres; int yres; int dst_linesize; enum AVPixelFormat pix_fmt; // rendering video parameters int x, y, w, h; // passed to R_DrawStretchRaw int texture; // passed to R_UploadStretchRaw // audio stream int audio_stream; int channels; int rate; enum AVSampleFormat s_fmt; byte *cached_audio; size_t cached_audio_buf_len; // absolute size of cached_audio array size_t cached_audio_len; // how many data in bytes we have in cached_audio array size_t cached_audio_pos; // how far we've read into cached_audio array // rendering audio parameters float attn; int16_t entnum; // MAX_ENTITY_BITS is 13 byte volume; byte active : 1; byte quiet : 1; byte paused : 1; }; qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) { qboolean ret = true; va_list va; va_start( va, parm ); while( parm != AVI_PARM_LAST ) { float fval; int val; switch( parm ) { case AVI_RENDER_TEXNUM: Avi->texture = va_arg( va, int ); break; case AVI_RENDER_X: Avi->x = va_arg( va, int ); break; case AVI_RENDER_Y: Avi->y = va_arg( va, int ); break; case AVI_RENDER_W: Avi->w = va_arg( va, int ); break; case AVI_RENDER_H: Avi->h = va_arg( va, int ); break; case AVI_REWIND: if( Avi->audio_ctx ) pavcodec_flush_buffers( Avi->audio_ctx ); pavcodec_flush_buffers( Avi->video_ctx ); Avi->cached_audio_len = Avi->cached_audio_pos = 0; Avi->last_time = -1; Avi->first_time = 0; pav_seek_frame( Avi->fmt_ctx, -1, 0, AVSEEK_FLAG_FRAME | AVSEEK_FLAG_BACKWARD ); break; case AVI_ENTNUM: val = va_arg( va, int ); Avi->entnum = bound( 0, val, MAX_EDICTS ); break; case AVI_VOLUME: val = va_arg( va, int ); Avi->volume = bound( 0, val, 255 ); break; case AVI_ATTN: fval = va_arg( va, double ); Avi->attn = Q_max( 0.0f, fval ); break; case AVI_PAUSE: Avi->paused = true; break; case AVI_RESUME: Avi->paused = false; break; default: ret = false; } parm = va_arg( va, enum movie_parms_e ); } va_end( va ); return ret; } static void AVI_SpewError( qboolean quiet, const char *fmt, ... ) FORMAT_CHECK( 2 ); static void AVI_SpewError( qboolean quiet, const char *fmt, ... ) { char buf[MAX_VA_STRING]; va_list va; if( quiet ) return; va_start( va, fmt ); Q_vsnprintf( buf, sizeof( buf ), fmt, va ); va_end( va ); Con_Printf( S_ERROR "%s", buf ); } static void AVI_SpewAvError( qboolean quiet, const char *func, int numerr ) { if( !quiet ) { char errstr[AV_ERROR_MAX_STRING_SIZE]; pav_strerror( numerr, errstr, sizeof( errstr )); Con_Printf( S_ERROR "%s: %s (%d)\n", func, errstr, numerr ); } } static int AVI_OpenCodecContext( AVCodecContext **dst_dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type, qboolean quiet ) { const AVCodec *dec; AVCodecContext *dec_ctx; AVStream *st; int idx, ret; if(( ret = pav_find_best_stream( fmt_ctx, type, -1, -1, NULL, 0 )) < 0 ) { AVI_SpewAvError( quiet, "av_find_best_stream", ret ); return ret; } idx = ret; st = fmt_ctx->streams[idx]; if( !( dec = pavcodec_find_decoder( st->codecpar->codec_id ))) { AVI_SpewError( quiet, S_ERROR "Failed to find %s codec\n", pav_get_media_type_string( type )); return AVERROR( EINVAL ); } if( !( dec_ctx = pavcodec_alloc_context3( dec ))) { AVI_SpewError( quiet, S_ERROR "Failed to allocate %s codec context", dec->name ); return AVERROR( ENOMEM ); } if(( ret = pavcodec_parameters_to_context( dec_ctx, st->codecpar )) < 0 ) { AVI_SpewAvError( quiet, "avcodec_parameters_to_context", ret ); pavcodec_free_context( &dec_ctx ); return ret; } dec_ctx->pkt_timebase = st->time_base; if(( ret = pavcodec_open2( dec_ctx, dec, NULL )) < 0 ) { AVI_SpewAvError( quiet, "avcodec_open2", ret ); pavcodec_free_context( &dec_ctx ); return ret; } *dst_dec_ctx = dec_ctx; return idx; // always positive } int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) { return 0; } int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) { return 0; } qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) { if( !Avi->active ) return false; if( xres ) *xres = Avi->xres; if( yres ) *yres = Avi->yres; if( duration ) *duration = Avi->duration; return true; } qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ) { return Avi ? Avi->active && Avi->audio_ctx : false; } // just let it compile, bruh! byte *AVI_GetVideoFrame( movie_state_t *Avi, int target ) { return Avi->dst; } static void AVI_StreamAudio( movie_state_t *Avi ) { int buffer_samples, file_samples, file_bytes; rawchan_t *ch = NULL; // keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_StartStreaming wasn't enabled qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !s_listener.streaming : false; if( !dma.initialized || disable_stream || s_listener.paused || !Avi->cached_audio ) return; ch = S_FindRawChannel( Avi->entnum, true ); if( !ch ) return; ch->master_vol = Avi->volume; ch->dist_mult = (Avi->attn / SND_CLIP_DISTANCE); if( ch->s_rawend < soundtime ) ch->s_rawend = soundtime; while( ch->s_rawend < soundtime + ch->max_samples ) { size_t copy; buffer_samples = ch->max_samples - (ch->s_rawend - soundtime); file_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED); if( file_samples <= 1 ) return; // no more samples need file_bytes = file_samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; if( file_bytes > ch->max_samples ) { file_bytes = ch->max_samples; file_samples = file_bytes / ( pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels ); } copy = Q_min( file_bytes, Q_max( Avi->cached_audio_len - Avi->cached_audio_pos, 0 )); if( !copy ) break; if( file_bytes > copy ) { file_bytes = copy; file_samples = file_bytes / ( pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels ); } ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, file_samples, Avi->rate, pav_get_bytes_per_sample( Avi->s_fmt ), Avi->channels, Avi->cached_audio + Avi->cached_audio_pos ); Avi->cached_audio_pos += copy; } } static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame ) { int samples = frame->nb_samples; size_t len = samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; int outsamples; uint8_t *ptr; // allocate data if( !Avi->cached_audio ) { Avi->cached_audio_buf_len = len; Avi->cached_audio_pos = 0; Avi->cached_audio_len = 0; Avi->cached_audio = Mem_Malloc( avi_mempool, len ); } else { if( Avi->cached_audio_pos ) { // Con_Printf( "%s: erasing old data of size %d\n", __func__, Avi->cached_audio_pos ); Avi->cached_audio_len -= Avi->cached_audio_pos; memmove( Avi->cached_audio, Avi->cached_audio + Avi->cached_audio_pos, Avi->cached_audio_len ); Avi->cached_audio_pos = 0; } if( len + Avi->cached_audio_len > Avi->cached_audio_buf_len ) { // Con_Printf( "%s: resizing old buffer of size %d to size %d\n", __func__, Avi->cached_audio_buf_len, len + Avi->cached_audio_buf_len ); Avi->cached_audio_buf_len = len + Avi->cached_audio_len; Avi->cached_audio = Mem_Realloc( avi_mempool, Avi->cached_audio, Avi->cached_audio_buf_len ); } } ptr = Avi->cached_audio + Avi->cached_audio_len; outsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples ); Avi->cached_audio_len += outsamples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; // Con_Printf( "%s: got audio chunk of size %d samples\n", __func__, outsamples ); } qboolean AVI_Think( movie_state_t *Avi ) { qboolean decoded = false; qboolean flushing = false; qboolean redraw = false; const double timebase = (double)Avi->video_ctx->pkt_timebase.den / Avi->video_ctx->pkt_timebase.num; int64_t curtime = round( Platform_DoubleTime() * timebase ); if( !Avi->first_time ) // always remember at which timestamp we started playing Avi->first_time = curtime; if( Avi->paused ) { // FIXME: there might be a better way to do this Avi->last_time = curtime; return true; } // Con_NPrintf( 1, "cached_audio_buf_len = %zu", Avi->cached_audio_buf_len ); while( 1 ) // try to get multiple decoded frames to keep up when we're running at low fps { int res; AVI_StreamAudio( Avi ); // always flush audio buffers // recalc time so we always play last possible frame curtime = round( Platform_DoubleTime() * timebase ); if( Avi->last_time > curtime ) break; if(( res = pav_read_frame( Avi->fmt_ctx, Avi->pkt )) >= 0 ) { if( Avi->pkt->stream_index == Avi->audio_stream ) { res = pavcodec_send_packet( Avi->audio_ctx, Avi->pkt ); if( res < 0 ) AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (audio)", res ); } else if( Avi->pkt->stream_index == Avi->video_stream ) { res = pavcodec_send_packet( Avi->video_ctx, Avi->pkt ); if( res < 0 ) AVI_SpewAvError( Avi->quiet, "avcodec_send_packet (video)", res ); } pav_packet_unref( Avi->pkt ); } else { if( res != AVERROR_EOF ) AVI_SpewAvError( Avi->quiet, "av_read_frame", res ); if( Avi->audio_ctx ) pavcodec_flush_buffers( Avi->audio_ctx ); pavcodec_flush_buffers( Avi->video_ctx ); flushing = true; break; } if( Avi->audio_ctx ) { while( pavcodec_receive_frame( Avi->audio_ctx, Avi->aframe ) == 0 ) { AVI_HandleAudio( Avi, Avi->aframe ); decoded = true; } } while( pavcodec_receive_frame( Avi->video_ctx, Avi->vframe ) == 0 ) { Avi->last_time = Avi->first_time + Avi->vframe->best_effort_timestamp; decoded = true; if( FBitSet( Avi->vframe->flags, AV_FRAME_FLAG_CORRUPT|AV_FRAME_FLAG_DISCARD )) continue; if( Avi->vframe->decode_error_flags != 0 ) continue; pav_frame_unref( Avi->vframe_copy ); if( pav_frame_ref( Avi->vframe_copy, Avi->vframe ) == 0 ) redraw = true; } } if( redraw ) { psws_scale( Avi->sws_ctx, (void*)Avi->vframe_copy->data, Avi->vframe_copy->linesize, 0, Avi->video_ctx->height, &Avi->dst, &Avi->dst_linesize ); pav_frame_unref( Avi->vframe_copy ); } if( Avi->texture == 0 ) { int w = Avi->w >= 0 ? Avi->w : refState.width; int h = Avi->h >= 0 ? Avi->h : refState.height; ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw ); } else if( redraw && Avi->texture > 0 ) ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst ); if( flushing && !decoded ) return false; // probably hit an EOF return true; } void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) { byte *dst[4]; int dst_linesize[4]; int ret; if( Avi->active ) AVI_CloseVideo( Avi ); if( !filename || !avi_initialized ) return; Avi->active = false; Avi->quiet = quiet; Avi->video_ctx = Avi->audio_ctx = NULL; Avi->fmt_ctx = NULL; if(( ret = pavformat_open_input( &Avi->fmt_ctx, filename, NULL, NULL )) < 0 ) { AVI_SpewAvError( quiet, "avformat_open_input", ret ); return; } if(( ret = pavformat_find_stream_info( Avi->fmt_ctx, NULL )) < 0 ) { AVI_SpewAvError( quiet, "avformat_find_stream_info", ret ); return; } if( !( Avi->pkt = pav_packet_alloc( ))) { AVI_SpewAvError( quiet, "av_packet_alloc", 0 ); return; } if( !( Avi->vframe = pav_frame_alloc( ))) { AVI_SpewAvError( quiet, "av_frame_alloc (video)", 0 ); return; } if( !( Avi->vframe_copy = pav_frame_alloc( ))) { AVI_SpewAvError( quiet, "av_frame_alloc (video)", 0 ); return; } Avi->video_stream = AVI_OpenCodecContext( &Avi->video_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_VIDEO, quiet ); if( Avi->video_stream < 0 ) return; Avi->xres = Avi->video_ctx->width; Avi->yres = Avi->video_ctx->height; Avi->pix_fmt = Avi->video_ctx->pix_fmt; Avi->duration = Avi->fmt_ctx->duration / (double)AV_TIME_BASE; Avi->entnum = S_RAW_SOUND_SOUNDTRACK; Avi->attn = ATTN_NONE; Avi->volume = 255; if( !( Avi->sws_ctx = psws_getContext( Avi->xres, Avi->yres, Avi->pix_fmt, Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, SWS_POINT, NULL, NULL, NULL ))) { AVI_SpewAvError( quiet, "sws_getContext", 0 ); return; } if(( ret = pav_image_alloc( dst, dst_linesize, Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, 1 )) < 0 ) { AVI_SpewAvError( quiet, "av_image_alloc", ret ); return; } Avi->dst = dst[0]; Avi->dst_linesize = dst_linesize[0]; if( load_audio ) { if( !( Avi->aframe = pav_frame_alloc( ))) { AVI_SpewAvError( quiet, "av_frame_alloc (audio)", 0 ); return; } Avi->audio_stream = AVI_OpenCodecContext( &Avi->audio_ctx, Avi->fmt_ctx, AVMEDIA_TYPE_AUDIO, quiet ); // audio stream was requested but it wasn't found if( Avi->audio_stream < 0 ) return; Avi->channels = Q_min( Avi->audio_ctx->ch_layout.nb_channels, 2 ); if( Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8 || Avi->audio_ctx->sample_fmt == AV_SAMPLE_FMT_U8P ) Avi->s_fmt = AV_SAMPLE_FMT_U8; else Avi->s_fmt = AV_SAMPLE_FMT_S16; Avi->rate = Avi->audio_ctx->sample_rate; if(( ret = pswr_alloc_set_opts2( &Avi->swr_ctx, &Avi->audio_ctx->ch_layout, Avi->s_fmt, Avi->rate, &Avi->audio_ctx->ch_layout, Avi->audio_ctx->sample_fmt, Avi->audio_ctx->sample_rate, 0, 0 )) < 0 ) { AVI_SpewAvError( quiet, "swr_alloc_set_opts2", ret ); return; } if(( ret = pswr_init( Avi->swr_ctx )) < 0 ) { AVI_SpewAvError( quiet, "swr_init", ret ); return; } } Avi->active = true; } void AVI_CloseVideo( movie_state_t *Avi ) { if( Avi->active ) { if( Avi->cached_audio ) Mem_Free( Avi->cached_audio ); pswr_free( &Avi->swr_ctx ); pavcodec_free_context( &Avi->audio_ctx ); pav_frame_free( &Avi->aframe ); pav_free( Avi->dst ); psws_freeContext( Avi->sws_ctx ); pavcodec_free_context( &Avi->video_ctx ); pav_frame_free( &Avi->vframe ); pav_frame_free( &Avi->vframe_copy ); pav_packet_free( &Avi->pkt ); pavformat_close_input( &Avi->fmt_ctx ); } memset( Avi, 0, sizeof( *Avi )); } #if XASH_FFMPEG_DLOPEN #define F( x ) #x, (void **)&p##x static const dllfunc_t libavutil_funcs[] = { { F( avutil_version ) }, { F( av_frame_alloc ) }, { F( av_frame_free ) }, { F( av_frame_ref ) }, { F( av_frame_unref ) }, { F( av_strerror ) }, { F( av_free ) }, { F( av_get_bytes_per_sample ) }, { F( av_get_media_type_string ) }, { F( av_image_alloc ) }, }; static const dllfunc_t libavformat_funcs[] = { { F( avformat_version ) }, { F( av_find_best_stream ) }, { F( av_read_frame ) }, { F( av_seek_frame ) }, { F( avformat_close_input ) }, { F( avformat_find_stream_info ) }, { F( avformat_open_input ) }, }; static const dllfunc_t libavcodec_funcs[] = { { F( avcodec_version ) }, { F( av_packet_alloc ) }, { F( av_packet_free ) }, { F( av_packet_unref ) }, { F( avcodec_alloc_context3 ) }, { F( avcodec_find_decoder ) }, { F( avcodec_flush_buffers ) }, { F( avcodec_free_context ) }, { F( avcodec_open2 ) }, { F( avcodec_parameters_to_context ) }, { F( avcodec_receive_frame ) }, { F( avcodec_send_packet ) }, }; static const dllfunc_t libswresample_funcs[] = { { F( swresample_version ) }, { F( swr_alloc_set_opts2 ) }, { F( swr_convert ) }, { F( swr_free ) }, { F( swr_init ) }, }; static const dllfunc_t libswscale_funcs[] = { { F( swscale_version ) }, { F( sws_freeContext ) }, { F( sws_getContext ) }, { F( sws_scale ) }, }; #undef F #define SS( x ) #x #define S( x ) SS( x ) static dll_info_t libavutil_info = { #if XASH_WIN32 .name = "avutil-" S( SUPPORTED_AVU_VERSION_MAJOR ) ".dll", #else .name = "libavutil.so." S( SUPPORTED_AVU_VERSION_MAJOR ), #endif .fcts = libavutil_funcs, .num_fcts = ARRAYSIZE( libavutil_funcs ), }; static dll_info_t libavformat_info = { #if XASH_WIN32 .name = "avformat-" S( SUPPORTED_AVF_VERSION_MAJOR ) ".dll", #else .name = "libavformat.so." S( SUPPORTED_AVF_VERSION_MAJOR ), #endif .fcts = libavformat_funcs, .num_fcts = ARRAYSIZE( libavformat_funcs ), }; static dll_info_t libavcodec_info = { #if XASH_WIN32 .name = "avcodec-" S( SUPPORTED_AVC_VERSION_MAJOR ) ".dll", #else .name = "libavcodec.so." S( SUPPORTED_AVC_VERSION_MAJOR ), #endif .fcts = libavcodec_funcs, .num_fcts = ARRAYSIZE( libavcodec_funcs ), }; static dll_info_t libswresample_info = { #if XASH_WIN32 .name = "swresample-" S( SUPPORTED_SWR_VERSION_MAJOR ) ".dll", #else .name = "libswresample.so." S( SUPPORTED_SWR_VERSION_MAJOR ), #endif .fcts = libswresample_funcs, .num_fcts = ARRAYSIZE( libswresample_funcs ), }; static dll_info_t libswscale_info = { #if XASH_WIN32 .name = "swscale-" S( SUPPORTED_SWS_VERSION_MAJOR ) ".dll", #else .name = "libswscale.so." S( SUPPORTED_SWS_VERSION_MAJOR ), #endif .fcts = libswscale_funcs, .num_fcts = ARRAYSIZE( libswscale_funcs ), }; static qboolean AVI_LoadFFmpeg( void ) { if( !Sys_LoadLibrary( &libavutil_info )) return false; if( !Sys_LoadLibrary( &libavformat_info )) return false; if( !Sys_LoadLibrary( &libavcodec_info )) return false; if( !Sys_LoadLibrary( &libswresample_info )) return false; if( !Sys_LoadLibrary( &libswscale_info )) return false; return true; } static void AVI_UnloadFFmpeg( void ) { Sys_FreeLibrary( &libavutil_info ); Sys_FreeLibrary( &libavformat_info ); Sys_FreeLibrary( &libavcodec_info ); Sys_FreeLibrary( &libswresample_info ); Sys_FreeLibrary( &libswscale_info ); } #else static qboolean AVI_LoadFFmpeg( void ) { return true; } static void AVI_UnloadFFmpeg( void ) { } #endif static qboolean AVI_ValidateFFmpegVersion( void ) { uint ver; // print version we're compiled with and which version we're running with ver = pavutil_version(); Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVUTIL_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); if( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVU_VERSION_MAJOR ) { Con_Printf( S_ERROR "AVI: Unsupported libavutil version.\n" ); return false; } ver = pavformat_version(); Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVFORMAT_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); if( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVF_VERSION_MAJOR ) { Con_Printf( S_ERROR "AVI: Unsupported libavformat version.\n" ); return false; } ver = pavcodec_version(); Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBAVCODEC_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); if( AV_VERSION_MAJOR( ver ) != SUPPORTED_AVC_VERSION_MAJOR ) { Con_Printf( S_ERROR "AVI: Unsupported libavcodec version.\n" ); return false; } ver = pswscale_version(); Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWSCALE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); if( AV_VERSION_MAJOR( ver ) != SUPPORTED_SWS_VERSION_MAJOR ) { Con_Printf( S_ERROR "AVI: Unsupported libswscale version.\n" ); return false; } ver = pswresample_version(); Con_Reportf( "AVI: %s (runtime %d.%d.%d)\n", LIBSWRESAMPLE_IDENT, AV_VERSION_MAJOR( ver ), AV_VERSION_MINOR( ver ), AV_VERSION_MICRO( ver )); if( AV_VERSION_MAJOR( ver ) != SUPPORTED_SWR_VERSION_MAJOR ) { Con_Printf( S_ERROR "AVI: Unsupported libswresample version.\n" ); return false; } return true; } #else struct movie_state_s { qboolean active; }; int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) { return 0; } byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame ) { return NULL; } qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration ) { return false; } qboolean AVI_HaveAudioTrack( const movie_state_t *Avi ) { return false; } void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) { ; } int AVI_TimeToSoundPosition( movie_state_t *Avi, int time ) { return 0; } void AVI_CloseVideo( movie_state_t *Avi ) { ; } qboolean AVI_Think( movie_state_t *Avi ) { return false; } qboolean AVI_SetParm( movie_state_t *Avi, enum movie_parms_e parm, ... ) { return false; } static qboolean AVI_ValidateFFmpegVersion( void ) { return false; } static qboolean AVI_LoadFFmpeg( void ) { return false; } static void AVI_UnloadFFmpeg( void ) { } #endif // XASH_AVI == AVI_NULL static movie_state_t avi[2]; movie_state_t *AVI_GetState( int num ) { return &avi[num]; } qboolean AVI_IsActive( movie_state_t *Avi ) { return Avi ? Avi->active : false; } qboolean AVI_Initailize( void ) { if( XASH_AVI == AVI_NULL ) { Con_Printf( "AVI: Not supported\n" ); return false; } if( Sys_CheckParm( "-noavi" )) { Con_Printf( "AVI: Disabled\n" ); return false; } if( !AVI_LoadFFmpeg( )) return false; if( !AVI_ValidateFFmpegVersion( )) return false; avi_initialized = true; avi_mempool = Mem_AllocPool( "AVI Zone" ); return false; } void AVI_Shutdown( void ) { Mem_FreePool( &avi_mempool ); avi_initialized = false; AVI_UnloadFFmpeg(); } movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) { movie_state_t *Avi; string path; const char *fullpath; // fast reject if( !avi_initialized ) return NULL; // open cinematic Q_snprintf( path, sizeof( path ), "media/%s", filename ); COM_DefaultExtension( path, ".avi", sizeof( path )); fullpath = FS_GetDiskPath( path, false ); if( FS_FileExists( path, false ) && !fullpath ) { Con_Printf( "Couldn't load %s from packfile. Please extract it\n", path ); return NULL; } Avi = Mem_Calloc( avi_mempool, sizeof( movie_state_t )); AVI_OpenVideo( Avi, fullpath, load_audio, false ); if( !AVI_IsActive( Avi )) { AVI_FreeVideo( Avi ); // something bad happens return NULL; } // all done return Avi; } void AVI_FreeVideo( movie_state_t *Avi ) { if( !Avi ) return; AVI_CloseVideo( Avi ); if( Mem_IsAllocatedExt( avi_mempool, Avi )) Mem_Free( Avi ); } ================================================ FILE: engine/client/avi/avi_ffmpeg.h ================================================ /* avi_ffmpreg.c - playing AVI files (ffmpeg backend) Copyright (C) FTEQW developers (for plugins/avplug/avdecode.c) Copyright (C) Sam Lantinga (for tests/testffmpeg.c) Copyright (C) 2023-2024 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #ifndef AVI_FFMPEG_H #define AVI_FFMPEG_H #if XASH_AVI == AVI_FFMPEG #include #include #include #include #include #if XASH_FFMPEG_DLOPEN // the following symbols were taken from ffmpeg public headers // on each major ffmpeg uprgade, they must be validated // ffmpeg guarantees API and ABI compatibility between major versions // so complain if this gets compiled against unsupported yet version // the same check will be done in runtime to ensure compatibility #define SUPPORTED_AVU_VERSION_MAJOR 59 #define SUPPORTED_AVF_VERSION_MAJOR 61 #define SUPPORTED_AVC_VERSION_MAJOR 61 #define SUPPORTED_SWR_VERSION_MAJOR 5 #define SUPPORTED_SWS_VERSION_MAJOR 8 #if SUPPORTED_AVU_VERSION_MAJOR != LIBAVUTIL_VERSION_MAJOR #error #endif #if SUPPORTED_AVF_VERSION_MAJOR != LIBAVFORMAT_VERSION_MAJOR #error #endif #if SUPPORTED_AVC_VERSION_MAJOR != LIBAVCODEC_VERSION_MAJOR #error #endif #if SUPPORTED_SWR_VERSION_MAJOR != LIBSWRESAMPLE_VERSION_MAJOR #error #endif #if SUPPORTED_SWS_VERSION_MAJOR != LIBSWSCALE_VERSION_MAJOR #error #endif // libavutil unsigned (*pavutil_version)( void ); AVFrame *(*pav_frame_alloc)( void ); void (*pav_frame_free)( AVFrame **frame ); int (*pav_frame_ref)( AVFrame *dst, const AVFrame *src ); void (*pav_frame_unref)( AVFrame *frame ); int (*pav_strerror)( int errnum, char *errbuf, size_t errbuf_size ); void (*pav_free)( void *ptr ); int (*pav_get_bytes_per_sample)( enum AVSampleFormat sample_fmt ); const char *(*pav_get_media_type_string)( enum AVMediaType media_type ); int (*pav_image_alloc)( uint8_t *pointers[4], int linesizes[4], int w, int h, enum AVPixelFormat pix_fmt, int align ); // libavformat unsigned (*pavformat_version)( void ); int (*pav_find_best_stream)( AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, const struct AVCodec **decoder_ret, int flags ); int (*pav_read_frame)( AVFormatContext *s, AVPacket *pkt ); int (*pav_seek_frame)( AVFormatContext *s, int stream_index, int64_t timestamp, int flags ); void (*pavformat_close_input)(AVFormatContext **s); int (*pavformat_find_stream_info)(AVFormatContext *ic, AVDictionary **options); int (*pavformat_open_input)(AVFormatContext **ps, const char *url, const AVInputFormat *fmt, AVDictionary **options); // libavcodec unsigned (*pavcodec_version)( void ); AVPacket *(*pav_packet_alloc)( void ); void (*pav_packet_free)( AVPacket **pkt ); void (*pav_packet_unref)( AVPacket *pkt ); AVCodecContext *(*pavcodec_alloc_context3)( const AVCodec *codec ); const AVCodec *(*pavcodec_find_decoder)( enum AVCodecID id ); void (*pavcodec_flush_buffers)( AVCodecContext *avctx ); void (*pavcodec_free_context)( AVCodecContext **avctx ); int (*pavcodec_open2)( AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options ); int (*pavcodec_parameters_to_context)( AVCodecContext *codec, const struct AVCodecParameters *par ); int (*pavcodec_receive_frame)( AVCodecContext *avctx, AVFrame *frame ); int (*pavcodec_send_packet)( AVCodecContext *avctx, const AVPacket *avpkt ); // libswresample unsigned (*pswresample_version)( void ); int (*pswr_alloc_set_opts2)( struct SwrContext **ps, const AVChannelLayout *out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate, const AVChannelLayout *in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate, int log_offset, void *log_ctx ); int (*pswr_convert)( struct SwrContext *s, uint8_t *const *out, int out_count, const uint8_t *const *in, int in_count ); void (*pswr_free)( struct SwrContext **s ); int (*pswr_init)( struct SwrContext *s ); // libswscale unsigned (*pswscale_version)( void ); void (*psws_freeContext)( struct SwsContext *swsContext ); struct SwsContext *(*psws_getContext)( int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param ); int (*psws_scale)( struct SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[] ); #else // !XASH_FFMPEG_DLOPEN #define SUPPORTED_AVU_VERSION_MAJOR LIBAVUTIL_VERSION_MAJOR #define SUPPORTED_AVF_VERSION_MAJOR LIBAVFORMAT_VERSION_MAJOR #define SUPPORTED_AVC_VERSION_MAJOR LIBAVCODEC_VERSION_MAJOR #define SUPPORTED_SWR_VERSION_MAJOR LIBSWRESAMPLE_VERSION_MAJOR #define SUPPORTED_SWS_VERSION_MAJOR LIBSWSCALE_VERSION_MAJOR // libavutil #define pavutil_version avutil_version #define pav_frame_alloc av_frame_alloc #define pav_frame_ref av_frame_ref #define pav_frame_unref av_Frame_unref #define pav_strerror av_strerror #define pav_free av_free #define pav_get_bytes_per_sample av_get_bytes_per_sample #define pav_get_media_type_string av_get_media_type_string #define pav_image_alloc av_image_alloc // libavformat #define pavformat_version avformat_version #define pav_find_best_stream av_find_best_stream #define pav_read_frame av_read_frame #define pav_seek_frame av_seek_frame #define pavformat_close_input avformat_close_input #define pavformat_find_stream_info avformat_find_stream_info #define pavformat_open_input avformat_open_input // libavcodec #define pavcodec_version avcodec_version #define pav_packet_alloc av_packet_alloc #define pav_packet_free av_packet_free #define pav_packet_unref av_packet_unref #define pavcodec_alloc_context3 avcodec_alloc_context3 #define pavcodec_find_decoder avcodec_find_decoder #define pavcodec_flush_buffers avcodec_flush_buffers #define pavcodec_free_context avcodec_free_context #define pavcodec_open2 avcodec_open2 #define pavcodec_parameters_to_context avcodec_parameters_to_context #define pavcodec_receive_frame avcodec_receive_frame #define pavcodec_send_packet avcodec_send_packet // libswresample #define pswresample_version swresample_version #define pswr_alloc_set_opts2 swr_alloc_set_opts2 #define pswr_convert swr_convert #define pswr_free swr_free #define pswr_init swr_init // libswscale #define pswscale_version swscale_version #define psws_freeContext sws_freeContext #define psws_getContext sws_getContext #define psws_scale sws_scale #endif // !XASH_FFMPEG_DLOPEN #endif // XASH_AVI == AVI_FFMPEG #endif // AVI_FFMPEG_H ================================================ FILE: engine/client/cl_cmds.c ================================================ /* cl_cmds.c - client console commnds Copyright (C) 2007 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" /* ==================== CL_PlayVideo_f movie ==================== */ void CL_PlayVideo_f( void ) { string path; if( Cmd_Argc() != 2 && Cmd_Argc() != 3 ) { Con_Printf( S_USAGE "movie [full]\n" ); return; } if( cls.state == ca_active ) { Con_Printf( "Can't play movie while connected to a server.\nPlease disconnect first.\n" ); return; } switch( Cmd_Argc( )) { case 2: // simple user version Q_snprintf( path, sizeof( path ), "media/%s", Cmd_Argv( 1 )); COM_DefaultExtension( path, ".avi", sizeof( path )); SCR_PlayCinematic( path ); break; case 3: // sequenced cinematics used this SCR_PlayCinematic( Cmd_Argv( 1 )); break; } } /* =============== CL_PlayCDTrack_f Emulate audio-cd system =============== */ void CL_PlayCDTrack_f( void ) { const char *command; const char *pszTrack; static int track = 0; static qboolean paused = false; static qboolean looped = false; static qboolean enabled = true; if( Cmd_Argc() < 2 ) return; command = Cmd_Argv( 1 ); pszTrack = Cmd_Argv( 2 ); if( !enabled && Q_stricmp( command, "on" )) return; // CD-player is disabled if( !Q_stricmp( command, "play" )) { if( Q_isdigit( pszTrack )) { track = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS ); S_StartBackgroundTrack( clgame.cdtracks[track-1], NULL, 0, false ); } else S_StartBackgroundTrack( pszTrack, NULL, 0, true ); paused = false; looped = false; } else if( !Q_stricmp( command, "playfile" )) { S_StartBackgroundTrack( pszTrack, NULL, 0, true ); paused = false; looped = false; } else if( !Q_stricmp( command, "loop" )) { if( Q_isdigit( pszTrack )) { track = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS ); S_StartBackgroundTrack( clgame.cdtracks[track-1], clgame.cdtracks[track-1], 0, false ); } else S_StartBackgroundTrack( pszTrack, pszTrack, 0, true ); paused = false; looped = true; } else if( !Q_stricmp( command, "loopfile" )) { S_StartBackgroundTrack( pszTrack, pszTrack, 0, true ); paused = false; looped = true; } else if( !Q_stricmp( command, "pause" )) { S_StreamSetPause( true ); paused = true; } else if( !Q_stricmp( command, "resume" )) { S_StreamSetPause( false ); paused = false; } else if( !Q_stricmp( command, "stop" )) { S_StopBackgroundTrack(); paused = false; looped = false; track = 0; } else if( !Q_stricmp( command, "on" )) { enabled = true; } else if( !Q_stricmp( command, "off" )) { enabled = false; } else if( !Q_stricmp( command, "info" )) { int i, maxTrack; for( maxTrack = i = 0; i < MAX_CDTRACKS; i++ ) if( COM_CheckStringEmpty( clgame.cdtracks[i] ) ) maxTrack++; Con_Printf( "%u tracks\n", maxTrack ); if( track ) { if( paused ) Con_Printf( "Paused %s track %u\n", looped ? "looping" : "playing", track ); else Con_Printf( "Currently %s track %u\n", looped ? "looping" : "playing", track ); } Con_Printf( "Volume is %f\n", s_musicvolume.value ); return; } else Con_Printf( "%s: unknown command %s\n", Cmd_Argv( 0 ), command ); } /* ============================================================================== SCREEN SHOTS ============================================================================== */ /* ================== CL_LevelShot_f splash logo while map is loading ================== */ void CL_LevelShot_f( void ) { size_t ft1, ft2; string filename; if( cls.scrshot_request != scrshot_plaque ) return; cls.scrshot_request = scrshot_inactive; // check for exist if( cls.demoplayback && ( cls.demonum != -1 )) { Q_snprintf( cls.shotname, sizeof( cls.shotname ), "levelshots/%s_%s.bmp", cls.demoname, refState.wideScreen ? "16x9" : "4x3" ); Q_snprintf( filename, sizeof( filename ), "%s.dem", cls.demoname ); // make sure what levelshot is newer than demo ft1 = FS_FileTime( filename, false ); ft2 = FS_FileTime( cls.shotname, true ); } else { Q_snprintf( cls.shotname, sizeof( cls.shotname ), "levelshots/%s_%s.bmp", clgame.mapname, refState.wideScreen ? "16x9" : "4x3" ); // make sure what levelshot is newer than bsp ft1 = FS_FileTime( cl.worldmodel->name, false ); ft2 = FS_FileTime( cls.shotname, true ); } // missing levelshot or level never than levelshot if( ft2 == -1 || ft1 > ft2 ) cls.scrshot_action = scrshot_plaque; // build new frame for levelshot else cls.scrshot_action = scrshot_inactive; // disable - not needs } static scrshot_t CL_GetScreenshotTypeFromString( const char *string ) { if( !Q_stricmp( string, "snapshot" )) return scrshot_snapshot; if( !Q_stricmp( string, "screenshot" )) return scrshot_normal; if( !Q_stricmp( string, "saveshot" )) return scrshot_savegame; if( !Q_stricmp( string, "envshot" )) return scrshot_envshot; if( !Q_stricmp( string, "skyshot" )) return scrshot_skyshot; return scrshot_inactive; } void CL_GenericShot_f( void ) { const char *argv0 = Cmd_Argv( 0 ); scrshot_t type; type = CL_GetScreenshotTypeFromString( argv0 ); if( type == scrshot_normal || type == scrshot_snapshot ) { if( CL_IsDevOverviewMode() == 1 ) type = scrshot_mapshot; } else { if( Cmd_Argc() < 2 ) { Con_Printf( S_USAGE "%s \n", argv0 ); return; } } switch( type ) { case scrshot_envshot: case scrshot_skyshot: Q_snprintf( cls.shotname, sizeof( cls.shotname ), "gfx/env/%s", Cmd_Argv( 1 )); break; case scrshot_savegame: Q_snprintf( cls.shotname, sizeof( cls.shotname ), DEFAULT_SAVE_DIRECTORY "%s.bmp", Cmd_Argv( 1 )); break; case scrshot_mapshot: Q_snprintf( cls.shotname, sizeof( cls.shotname ), "overviews/%s.bmp", clgame.mapname ); break; case scrshot_normal: case scrshot_snapshot: { string checkname; int i; // allow overriding screenshot by users request if( Cmd_Argc() > 1 ) { Q_strncpy( cls.shotname, Cmd_Argv( 1 ), sizeof( cls.shotname )); break; } if( type == scrshot_snapshot ) FS_AllowDirectPaths( true ); for( i = 0; i < 9999; i++ ) { int ret; if( type == scrshot_snapshot ) ret = Q_snprintf( checkname, sizeof( checkname ), "../%s_%04d.png", clgame.mapname, i ); else ret = Q_snprintf( checkname, sizeof( checkname ), "scrshots/%s_shot%04d.png", clgame.mapname, i ); if( ret <= 0 ) { Con_Printf( S_ERROR "unable to write %s\n", argv0 ); FS_AllowDirectPaths( false ); return; } if( !FS_FileExists( checkname, true )) break; } FS_AllowDirectPaths( false ); Q_strncpy( cls.shotname, checkname, sizeof( cls.shotname )); break; } case scrshot_inactive: case scrshot_plaque: default: return; // shouldn't happen } cls.scrshot_action = type; // build new frame for saveshot cls.envshot_vieworg = NULL; cls.envshot_viewsize = 0; } /* ============== CL_DeleteDemo_f ============== */ void CL_DeleteDemo_f( void ) { if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "killdemo \n" ); return; } if( cls.demorecording && !Q_stricmp( cls.demoname, Cmd_Argv( 1 ))) { Con_Printf( "Can't delete %s - recording\n", Cmd_Argv( 1 )); return; } // delete demo FS_Delete( va( "%s.dem", Cmd_Argv( 1 ))); } /* ================= CL_SetSky_f Set a specified skybox (only for local clients) ================= */ void CL_SetSky_f( void ) { if( Cmd_Argc() < 2 ) { Con_Printf( S_USAGE "skyname \n" ); return; } R_SetupSky( Cmd_Argv( 1 )); } /* ============= SCR_Viewpos_f viewpos (level-designer helper) ============= */ void SCR_Viewpos_f( void ) { Con_Printf( "org ( %g %g %g )\n", refState.vieworg[0], refState.vieworg[1], refState.vieworg[2] ); Con_Printf( "ang ( %g %g %g )\n", refState.viewangles[0], refState.viewangles[1], refState.viewangles[2] ); } /* ============= CL_WavePlayLen_f ============= */ void CL_WavePlayLen_f( void ) { const char *name; uint msecs; if( Cmd_Argc() != 2 ) { Con_Printf( "waveplaylen : returns approximate number of milliseconds a wave file will take to play.\n" ); return; } name = Cmd_Argv( 1 ); msecs = Sound_GetApproxWavePlayLen( name ); if( msecs == 0 ) { Con_Printf( "Unable to read %s, file may be missing or incorrectly formatted.\n", name ); return; } Con_Printf( "Play time is approximately %dms\n", msecs ); } ================================================ FILE: engine/client/cl_custom.c ================================================ /* cl_custom.c - downloading custom resources Copyright (C) 2018 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "net_encode.h" qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) { char filepath[MAX_QPATH]; switch( pResource->type ) { case t_sound: case t_model: // built-in resources not needs to be downloaded if( pResource->szFileName[0] == '*' ) return true; break; } // resource was missed on server if( pResource->nDownloadSize == -1 ) { ClearBits( pResource->ucFlags, RES_FATALIFMISSING ); return true; } if( pResource->type == t_sound ) Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", pResource->szFileName ); else Q_strncpy( filepath, pResource->szFileName, sizeof( filepath )); if( !COM_IsSafeFileToDownload( filepath )) { Con_Reportf( "refusing to download %s\n", filepath ); return true; } if( !cl_allow_download.value ) { Con_Reportf( "Download refused, cl_allowdownload is 0\n" ); return true; } if( cls.state == ca_active && !cl_download_ingame.value ) { Con_Reportf( "In-game download refused...\n" ); return true; } // don't request downloads from local client it's silly if( Host_IsLocalClient() || FS_FileExists( filepath, false )) return true; if( cls.demoplayback ) { Con_Reportf( S_WARN "file %s missing during demo playback.\n", filepath ); return true; } host.downloadcount++; if( cl.http_download ) { HTTP_AddDownload( filepath, pResource->nDownloadSize, true, pResource ); } else { MSG_BeginClientCmd( msg, clc_stringcmd ); MSG_WriteStringf( msg, "dlfile %s", filepath ); } return false; } void CL_AddToResourceList( resource_t *pResource, resource_t *pList ) { if( pResource->pPrev != NULL || pResource->pNext != NULL ) { Con_Reportf( S_ERROR "Resource already linked\n" ); return; } if( pList->pPrev == NULL || pList->pNext == NULL ) Host_Error( "Resource list corrupted.\n" ); pResource->pPrev = pList->pPrev; pResource->pNext = pList; pList->pPrev->pNext = pResource; pList->pPrev = pResource; } void CL_RemoveFromResourceList( resource_t *pResource ) { if( pResource->pPrev == NULL || pResource->pNext == NULL ) Host_Error( "mislinked resource in %s\n", __func__ ); if( pResource->pNext == pResource || pResource->pPrev == pResource ) Host_Error( "attempt to free last entry in list.\n" ); pResource->pPrev->pNext = pResource->pNext; pResource->pNext->pPrev = pResource->pPrev; pResource->pPrev = NULL; pResource->pNext = NULL; } void CL_MoveToOnHandList( resource_t *pResource ) { if( !pResource ) { Con_Reportf( "Null resource passed to %s\n", __func__ ); return; } CL_RemoveFromResourceList( pResource ); CL_AddToResourceList( pResource, &cl.resourcesonhand ); } static void CL_ClearResourceList( resource_t *pList ) { resource_t *p, *n; for( p = pList->pNext; p != pList && p; p = n ) { n = p->pNext; CL_RemoveFromResourceList( p ); Mem_Free( p ); } pList->pPrev = pList; pList->pNext = pList; } void CL_ClearResourceLists( void ) { CL_ClearResourceList( &cl.resourcesneeded ); CL_ClearResourceList( &cl.resourcesonhand ); } ================================================ FILE: engine/client/cl_debug.c ================================================ /* cl_debug.c - server message debugging Copyright (C) 2018 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "net_encode.h" #include "particledef.h" #include "cl_tent.h" #include "shake.h" #include "hltv.h" #include "input.h" #define MSG_COUNT 32 // last 32 messages parsed #define MSG_MASK (MSG_COUNT - 1) typedef struct { int command; int starting_offset; int frame_number; } oldcmd_t; typedef struct { oldcmd_t oldcmd[MSG_COUNT]; int currentcmd; qboolean parsing; } msg_debug_t; static msg_debug_t cls_message_debug; const char *CL_MsgInfo( int cmd ) { static string sz; Q_strncpy( sz, "???", sizeof( sz )); if( cmd >= 0 && cmd <= svc_lastmsg ) { // get engine message name const char *svc_string = NULL; switch( cls.legacymode ) { case PROTO_CURRENT: svc_string = svc_strings[cmd]; break; case PROTO_LEGACY: svc_string = svc_legacy_strings[cmd]; break; case PROTO_QUAKE: svc_string = svc_quake_strings[cmd]; break; case PROTO_GOLDSRC: svc_string = svc_goldsrc_strings[cmd]; break; } // fall back to current protocol strings if( !svc_string ) svc_string = svc_strings[cmd]; Q_strncpy( sz, svc_string, sizeof( sz )); } else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) { int i; for( i = 0; i < MAX_USER_MESSAGES; i++ ) { if( clgame.msg[i].number == cmd ) { Q_strncpy( sz, clgame.msg[i].name, sizeof( sz )); break; } } } return sz; } /* ===================== CL_Parse_Debug enable message debugging ===================== */ void CL_Parse_Debug( qboolean enable ) { cls_message_debug.parsing = enable; } /* ===================== CL_Parse_RecordCommand record new message params into debug buffer ===================== */ void CL_Parse_RecordCommand( int cmd, int startoffset ) { int slot; if( cmd == svc_nop ) return; slot = ( cls_message_debug.currentcmd++ & MSG_MASK ); cls_message_debug.oldcmd[slot].command = cmd; cls_message_debug.oldcmd[slot].starting_offset = startoffset; cls_message_debug.oldcmd[slot].frame_number = host.framecount; } /* ===================== CL_ResetFrame ===================== */ void CL_ResetFrame( frame_t *frame ) { memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t )); frame->receivedtime = host.realtime; frame->valid = true; frame->choked = false; frame->latency = 0.0; frame->time = cl.mtime[0]; } /* ===================== CL_WriteErrorMessage write net_message into buffer.dat for debugging ===================== */ static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg ) { const char *buffer_file = "buffer.dat"; file_t *fp; fp = FS_Open( buffer_file, "wb", false ); if( !fp ) return; FS_Write( fp, &cls.starting_count, sizeof( int )); FS_Write( fp, ¤t_count, sizeof( int )); FS_Write( fp, &cls.legacymode, sizeof( cls.legacymode )); FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg )); FS_Close( fp ); Con_Printf( "Wrote erroneous message to %s\n", buffer_file ); } /* ===================== CL_WriteMessageHistory list last 32 messages for debugging net troubleshooting ===================== */ void CL_WriteMessageHistory( void ) { oldcmd_t *old; sizebuf_t *msg = &net_message; int i, thecmd; if( !cls.initialized || cls.state == ca_disconnected ) return; if( !cls_message_debug.parsing ) return; Con_Printf( "Last %i messages parsed.\n", MSG_COUNT ); // finish here thecmd = cls_message_debug.currentcmd - 1; thecmd -= ( MSG_COUNT - 1 ); // back up to here for( i = 0; i < MSG_COUNT - 1; i++ ) { thecmd &= MSG_MASK; old = &cls_message_debug.oldcmd[thecmd]; Con_Printf( "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); thecmd++; } old = &cls_message_debug.oldcmd[thecmd]; Con_Printf( S_RED "BAD: " S_DEFAULT "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); CL_WriteErrorMessage( old->starting_offset, msg ); cls_message_debug.parsing = false; } void CL_ReplayBufferDat_f( void ) { file_t *f = FS_Open( Cmd_Argv( 1 ), "rb", true ); sizebuf_t msg; char buffer[NET_MAX_MESSAGE]; int starting_count, current_count, protocol; fs_offset_t len; if( !f ) return; FS_Read( f, &starting_count, sizeof( starting_count )); FS_Read( f, ¤t_count, sizeof( current_count )); FS_Read( f, &protocol, sizeof( protocol )); cls.legacymode = protocol; len = FS_Read( f, buffer, sizeof( buffer )); FS_Close( f ); MSG_Init( &msg, __func__, buffer, len ); Delta_Shutdown(); Delta_Init(); clgame.maxEntities = MAX_EDICTS; clgame.entities = Mem_Calloc( clgame.mempool, sizeof( *clgame.entities ) * clgame.maxEntities ); // ad-hoc implement #if 0 { const int message_pos = 12; // put real number here MSG_SeekToBit( &msg, ( message_pos - 12 + 1 ) << 3, SEEK_SET ); CL_ParseYourMom( &msg, protocol ); } #endif Sys_Quit( __func__ ); } ================================================ FILE: engine/client/cl_demo.c ================================================ /* cl_demo.c - demo record & playback Copyright (C) 2007 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "net_encode.h" #define dem_unknown 0 // unknown command #define dem_norewind 1 // startup message #define dem_read 2 // it's a normal network packet #define dem_jumptime 3 // move the demostart time value forward by this amount #define dem_userdata 4 // userdata from the client.dll #define dem_usercmd 5 // read usercmd_t #define dem_stop 6 // end of time #define dem_lastcmd dem_stop #define DEMO_STARTUP 0 // this lump contains startup info needed to spawn into the server #define DEMO_NORMAL 1 // this lump contains playback info of messages, etc., needed during playback. // Demo flags #define FDEMO_TITLE 0x01 // Show title #define FDEMO_PLAY 0x04 // Playing cd track #define FDEMO_FADE_IN_SLOW 0x08 // Fade in (slow) #define FDEMO_FADE_IN_FAST 0x10 // Fade in (fast) #define FDEMO_FADE_OUT_SLOW 0x20 // Fade out (slow) #define FDEMO_FADE_OUT_FAST 0x40 // Fade out (fast) #define IDEMOHEADER (('M'<<24)+('E'<<16)+('D'<<8)+'I') // little-endian "IDEM" #define DEMO_PROTOCOL 3 #define PROTOCOL_GOLDSRC_VERSION_DEMO (PROTOCOL_GOLDSRC_VERSION | (BIT( 7 ))) // should be 48, only to differentiate it from PROTOCOL_LEGACY_VERSION const char *demo_cmd[dem_lastcmd+1] = { "dem_unknown", "dem_norewind", "dem_read", "dem_jumptime", "dem_userdata", "dem_usercmd", "dem_stop", }; #pragma pack( push, 1 ) typedef struct { int id; // should be IDEM int dem_protocol; // should be DEMO_PROTOCOL int net_protocol; // should be PROTOCOL_VERSION double host_fps; // fps for demo playing char mapname[64]; // name of map char comment[64]; // comment for demo char gamedir[64]; // name of game directory (FS_Gamedir()) int directory_offset; // offset of Entry Directory. } demoheader_t; #pragma pack( pop ) typedef struct { int entrytype; // DEMO_STARTUP or DEMO_NORMAL float playback_time; // time of track int playback_frames; // # of frames in track int offset; // file offset of track data int length; // length of track int flags; // FX-flags char description[64]; // entry description } demoentry_t; typedef struct { demoentry_t *entries; // track entry info int32_t numentries; // number of tracks } demodirectory_t; // add angles typedef struct { float starttime; vec3_t viewangles; } demoangle_t; // private demo states struct { demoheader_t header; demoentry_t *entry; demodirectory_t directory; int framecount; float starttime; float realstarttime; float timestamp; float lasttime; int entryIndex; // interpolation stuff demoangle_t cmds[ANGLE_BACKUP]; int angle_position; } demo; static qboolean CL_NextDemo( void ); static int CL_GetDemoNetProtocol( connprotocol_t proto ) { switch( proto ) { case PROTO_CURRENT: return PROTOCOL_VERSION; case PROTO_LEGACY: return PROTOCOL_LEGACY_VERSION; case PROTO_QUAKE: return PROTOCOL_VERSION_QUAKE; case PROTO_GOLDSRC: return PROTOCOL_GOLDSRC_VERSION_DEMO; } return PROTOCOL_VERSION; } static connprotocol_t CL_GetProtocolFromDemo( int net_protocol ) { switch( net_protocol ) { case PROTOCOL_VERSION: return PROTO_CURRENT; case PROTOCOL_LEGACY_VERSION: return PROTO_LEGACY; case PROTOCOL_VERSION_QUAKE: return PROTO_QUAKE; case PROTOCOL_GOLDSRC_VERSION_DEMO: return PROTO_GOLDSRC; } return PROTO_CURRENT; } /* ==================== CL_StartupDemoHeader spooling demo header in case we record a demo on this level ==================== */ void CL_StartupDemoHeader( void ) { CL_CloseDemoHeader(); cls.demoheader = FS_Open( "demoheader.tmp", "w+bm", true ); if( !cls.demoheader ) { Con_DPrintf( S_ERROR "couldn't open temporary header file.\n" ); return; } Con_Printf( "Spooling demo header.\n" ); } /* ==================== CL_CloseDemoHeader close demoheader file on engine shutdown ==================== */ void CL_CloseDemoHeader( void ) { if( !cls.demoheader ) return; FS_Close( cls.demoheader ); } /* ==================== CL_GetDemoRecordClock write time while demo is recording ==================== */ static float CL_GetDemoRecordClock( void ) { return cl.mtime[0]; } /* ==================== CL_GetDemoPlaybackClock overwrite host.realtime ==================== */ static float CL_GetDemoPlaybackClock( void ) { return host.realtime + host.frametime; } /* ==================== CL_GetDemoFramerate overwrite host.frametime ==================== */ double CL_GetDemoFramerate( void ) { if( cls.timedemo ) return 0.0; return bound( MIN_FPS, demo.header.host_fps, MAX_FPS_HARD ); } /* ================= CL_DemoAborted ================= */ static void CL_DemoAborted( void ) { if( cls.demofile ) FS_Close( cls.demofile ); cls.demoplayback = false; cls.changedemo = false; cls.timedemo = false; demo.framecount = 0; cls.demofile = NULL; cls.demonum = -1; Cvar_DirectSet( &v_dark, "0" ); } /* ==================== CL_WriteDemoCmdHeader Writes the demo command header and time-delta ==================== */ static void CL_WriteDemoCmdHeader( byte cmd, file_t *file ) { float dt; Assert( cmd >= 1 && cmd <= dem_lastcmd ); if( !file ) return; // command FS_Write( file, &cmd, sizeof( byte )); // time offset dt = (float)(CL_GetDemoRecordClock() - demo.starttime); FS_Write( file, &dt, sizeof( float )); } /* ==================== CL_WriteDemoJumpTime Update level time on a next level ==================== */ void CL_WriteDemoJumpTime( void ) { if( cls.demowaiting || !cls.demofile ) return; demo.starttime = CL_GetDemoRecordClock(); // setup the demo starttime // demo playback should read this as an incoming message. // write the client's realtime value out so we can synchronize the reads. CL_WriteDemoCmdHeader( dem_jumptime, cls.demofile ); } /* ==================== CL_WriteDemoUserCmd Writes the current user cmd ==================== */ void CL_WriteDemoUserCmd( int cmdnumber ) { sizebuf_t buf; word bytes; byte data[1024]; if( !cls.demorecording || !cls.demofile ) return; CL_WriteDemoCmdHeader( dem_usercmd, cls.demofile ); FS_Write( cls.demofile, &cls.netchan.outgoing_sequence, sizeof( int )); FS_Write( cls.demofile, &cmdnumber, sizeof( int )); // write usercmd_t MSG_Init( &buf, "UserCmd", data, sizeof( data )); CL_WriteUsercmd( PROTO_CURRENT, &buf, -1, cmdnumber ); // always no delta, always in current protocol bytes = MSG_GetNumBytesWritten( &buf ); FS_Write( cls.demofile, &bytes, sizeof( word )); FS_Write( cls.demofile, data, bytes ); } /* ==================== CL_WriteDemoSequence Save state of cls.netchan sequences so that we can play the demo correctly. ==================== */ static void CL_WriteDemoSequence( file_t *file ) { Assert( file != NULL ); FS_Write( file, &cls.netchan.incoming_sequence, sizeof( int )); FS_Write( file, &cls.netchan.incoming_acknowledged, sizeof( int )); FS_Write( file, &cls.netchan.incoming_reliable_acknowledged, sizeof( int )); FS_Write( file, &cls.netchan.incoming_reliable_sequence, sizeof( int )); FS_Write( file, &cls.netchan.outgoing_sequence, sizeof( int )); FS_Write( file, &cls.netchan.reliable_sequence, sizeof( int )); FS_Write( file, &cls.netchan.last_reliable_sequence, sizeof( int )); } /* ==================== CL_WriteDemoMessage Dumps the current net message, prefixed by the length ==================== */ void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg ) { file_t *file = startup ? cls.demoheader : cls.demofile; int swlen; byte c; if( !file ) return; swlen = MSG_GetNumBytesWritten( msg ) - start; if( swlen <= 0 ) return; if( !startup ) demo.framecount++; // demo playback should read this as an incoming message. c = (cls.state != ca_active) ? dem_norewind : dem_read; CL_WriteDemoCmdHeader( c, file ); CL_WriteDemoSequence( file ); // write the length out. FS_Write( file, &swlen, sizeof( int )); // output the buffer. Skip the network packet stuff. FS_Write( file, MSG_GetData( msg ) + start, swlen ); } /* ==================== CL_WriteDemoUserMessage Dumps the user message (demoaction) ==================== */ void GAME_EXPORT CL_WriteDemoUserMessage( int size, byte *buffer ) { if( !cls.demorecording || cls.demowaiting ) return; if( !cls.demofile || !buffer || size <= 0 ) return; CL_WriteDemoCmdHeader( dem_userdata, cls.demofile ); // write the length out. FS_Write( cls.demofile, &size, sizeof( int )); // output the buffer. FS_Write( cls.demofile, buffer, size ); } /* ==================== CL_WriteDemoHeader Write demo header ==================== */ static void CL_WriteDemoHeader( const char *name ) { double maxfps; int copysize; int savepos; int curpos; Con_Printf( "recording to %s.\n", name ); cls.demofile = FS_Open( name, "wb", false ); cls.demotime = 0.0; if( !cls.demofile ) { Con_Printf( S_ERROR "couldn't open %s.\n", name ); return; } cls.demorecording = true; cls.demowaiting = true; // don't start saving messages until a non-delta compressed message is received maxfps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT; memset( &demo.header, 0, sizeof( demo.header )); demo.header.id = IDEMOHEADER; demo.header.dem_protocol = DEMO_PROTOCOL; demo.header.net_protocol = CL_GetDemoNetProtocol( cls.legacymode ); demo.header.host_fps = host_maxfps.value ? bound( MIN_FPS, host_maxfps.value, maxfps ) : maxfps; Q_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname )); Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment )); Q_strncpy( demo.header.gamedir, FS_Gamedir(), sizeof( demo.header.gamedir )); // write header FS_Write( cls.demofile, &demo.header, sizeof( demo.header )); demo.directory.numentries = 2; demo.directory.entries = Mem_Calloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries ); // DIRECTORY ENTRY # 0 demo.entry = &demo.directory.entries[0]; // only one here. demo.entry->entrytype = DEMO_STARTUP; demo.entry->playback_time = 0.0f; // startup takes 0 time. demo.entry->offset = FS_Tell( cls.demofile ); // position for this chunk. // finish off the startup info. CL_WriteDemoCmdHeader( dem_stop, cls.demoheader ); FS_Flush( cls.demoheader ); // now copy the stuff we cached from the server. copysize = savepos = FS_Tell( cls.demoheader ); FS_Seek( cls.demoheader, 0, SEEK_SET ); FS_FileCopy( cls.demofile, cls.demoheader, copysize ); // jump back to end, in case we record another demo for this session. FS_Seek( cls.demoheader, savepos, SEEK_SET ); demo.starttime = CL_GetDemoRecordClock(); // setup the demo starttime demo.realstarttime = demo.starttime; demo.framecount = 0; cls.td_startframe = host.framecount; cls.td_lastframe = -1; // get a new message this frame // now move on to entry # 1, the first data chunk. curpos = FS_Tell( cls.demofile ); demo.entry->length = curpos - demo.entry->offset; // now we are writing the first real lump. demo.entry = &demo.directory.entries[1]; // first real data lump demo.entry->entrytype = DEMO_NORMAL; demo.entry->playback_time = 0.0f; // startup takes 0 time. demo.entry->offset = FS_Tell( cls.demofile ); // demo playback should read this as an incoming message. // write the client's realtime value out so we can synchronize the reads. CL_WriteDemoCmdHeader( dem_jumptime, cls.demofile ); if( clgame.hInstance ) clgame.dllFuncs.pfnReset(); Cbuf_InsertText( "fullupdate\n" ); Cbuf_Execute(); } /* ================= CL_StopRecord finish recording demo ================= */ static void CL_StopRecord( void ) { int i, curpos; float stoptime; int frames; if( !cls.demorecording ) return; // demo playback should read this as an incoming message. CL_WriteDemoCmdHeader( dem_stop, cls.demofile ); stoptime = CL_GetDemoRecordClock(); if( clgame.hInstance ) clgame.dllFuncs.pfnReset(); curpos = FS_Tell( cls.demofile ); demo.entry->length = curpos - demo.entry->offset; demo.entry->playback_time = stoptime - demo.realstarttime; demo.entry->playback_frames = demo.framecount; // Now write out the directory and free it and touch up the demo header. FS_Write( cls.demofile, &demo.directory.numentries, sizeof( int )); for( i = 0; i < demo.directory.numentries; i++ ) FS_Write( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t )); Mem_Free( demo.directory.entries ); demo.directory.numentries = 0; demo.header.directory_offset = curpos; FS_Seek( cls.demofile, 0, SEEK_SET ); FS_Write( cls.demofile, &demo.header, sizeof( demo.header )); FS_Close( cls.demofile ); cls.demofile = NULL; cls.demorecording = false; cls.demoname[0] = '\0'; cls.td_lastframe = host.framecount; gameui.globals->demoname[0] = '\0'; demo.header.host_fps = 0.0; frames = cls.td_lastframe - cls.td_startframe; Con_Printf( "Completed demo\nRecording time: %02d:%02d, frames %i\n", (int)(cls.demotime / 60.0f), (int)fmod(cls.demotime, 60.0f), frames ); cls.demotime = 0.0; } /* ================= CL_DrawDemoRecording ================= */ void CL_DrawDemoRecording( void ) { char string[64]; rgba_t color = { 255, 255, 255, 255 }; int pos; int len; if(!( host_developer.value && cls.demorecording )) return; pos = FS_Tell( cls.demofile ); Q_snprintf( string, sizeof( string ), "^1RECORDING:^7 %s: %s time: %02d:%02d", cls.demoname, Q_memprint( pos ), (int)(cls.demotime / 60.0f ), (int)fmod( cls.demotime, 60.0f )); Con_DrawStringLen( string, &len, NULL ); Con_DrawString(( refState.width - len ) >> 1, refState.height >> 4, string, color ); } /* ======================================================================= CLIENT SIDE DEMO PLAYBACK ======================================================================= */ /* ================= CL_ReadDemoCmdHeader read the demo command ================= */ static qboolean CL_ReadDemoCmdHeader( byte *cmd, float *dt ) { // read the command // HACKHACK: skip NOPs do { FS_Read( cls.demofile, cmd, sizeof( byte )); } while( *cmd == dem_unknown ); if( *cmd > dem_lastcmd ) { Con_Printf( S_ERROR "Demo cmd %d > %d, file offset = %d\n", *cmd, dem_lastcmd, (int)FS_Tell( cls.demofile )); CL_DemoCompleted(); return false; } // read the timestamp FS_Read( cls.demofile, dt, sizeof( float )); return true; } /* ================= CL_ReadDemoUserCmd read the demo usercmd for predicting and smooth movement during playback the demo ================= */ static void CL_ReadDemoUserCmd( qboolean discard ) { byte data[1024]; int cmdnumber; int outgoing_sequence; runcmd_t *pcmd; word bytes; FS_Read( cls.demofile, &outgoing_sequence, sizeof( int )); FS_Read( cls.demofile, &cmdnumber, sizeof( int )); FS_Read( cls.demofile, &bytes, sizeof( short )); if( bytes >= sizeof( data )) { Con_Printf( S_ERROR "%s: too large dem_usercmd (size %u seq %i)\n", __func__, bytes, outgoing_sequence ); CL_DemoAborted(); return; } FS_Read( cls.demofile, data, bytes ); if( !discard ) { const usercmd_t nullcmd = { 0 }; sizebuf_t buf; demoangle_t *a; MSG_Init( &buf, "UserCmd", data, sizeof( data )); // a1ba: I have no proper explanation why cmdnumber++; pcmd = &cl.commands[cmdnumber & CL_UPDATE_MASK]; pcmd->processedfuncs = false; pcmd->senttime = 0.0f; pcmd->receivedtime = 0.1f; pcmd->frame_lerp = 0.1f; pcmd->heldback = false; pcmd->sendsize = 1; // always delta'ing from null MSG_ReadDeltaUsercmd( &buf, &nullcmd, &pcmd->cmd ); // make sure what interp info contain angles from different frames // or lerping will stop working if( demo.lasttime != demo.timestamp ) { // select entry into circular buffer demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK; a = &demo.cmds[demo.angle_position]; // record update a->starttime = demo.timestamp; VectorCopy( pcmd->cmd.viewangles, a->viewangles ); demo.lasttime = demo.timestamp; } // NOTE: we need to have the current outgoing sequence correct // so we can do prediction correctly during playback cls.netchan.outgoing_sequence = outgoing_sequence; // save last usercmd cl.cmd = pcmd->cmd; } } /* ================= CL_ReadDemoSequence read netchan sequences ================= */ static void CL_ReadDemoSequence( qboolean discard ) { int incoming_sequence; int incoming_acknowledged; int incoming_reliable_acknowledged; int incoming_reliable_sequence; int outgoing_sequence; int reliable_sequence; int last_reliable_sequence; FS_Read( cls.demofile, &incoming_sequence, sizeof( int )); FS_Read( cls.demofile, &incoming_acknowledged, sizeof( int )); FS_Read( cls.demofile, &incoming_reliable_acknowledged, sizeof( int )); FS_Read( cls.demofile, &incoming_reliable_sequence, sizeof( int )); FS_Read( cls.demofile, &outgoing_sequence, sizeof( int )); FS_Read( cls.demofile, &reliable_sequence, sizeof( int )); FS_Read( cls.demofile, &last_reliable_sequence, sizeof( int )); if( discard ) return; cls.netchan.incoming_sequence = incoming_sequence; cls.netchan.incoming_acknowledged = incoming_acknowledged; cls.netchan.incoming_reliable_acknowledged = incoming_reliable_acknowledged; cls.netchan.incoming_reliable_sequence = incoming_reliable_sequence; cls.netchan.outgoing_sequence = outgoing_sequence; cls.netchan.reliable_sequence = reliable_sequence; cls.netchan.last_reliable_sequence = last_reliable_sequence; } /* ================= CL_DemoStartPlayback ================= */ static void CL_DemoStartPlayback( int mode ) { if( cls.changedemo ) { int maxclients = cl.maxclients; S_StopAllSounds( true ); SCR_BeginLoadingPlaque( false ); CL_ClearState( ); CL_InitEdicts( maxclients ); // re-arrange edicts } else { // NOTE: at this point demo is still valid CL_Disconnect(); SV_Shutdown( "Server was killed due to demo playback start\n" ); Con_FastClose(); UI_SetActiveMenu( false ); } cls.demoplayback = mode; cls.state = ca_connected; cl.background = (cls.demonum != -1) ? true : false; cls.spectator = false; cls.signon = 0; demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message CL_SetupNetchanForProtocol( cls.legacymode ); memset( demo.cmds, 0, sizeof( demo.cmds )); demo.angle_position = 1; demo.framecount = 0; cls.lastoutgoingcommand = -1; cls.nextcmdtime = host.realtime; cl.last_command_ack = -1; } /* ================= CL_DemoCompleted ================= */ void CL_DemoCompleted( void ) { if( cls.demonum != -1 ) cls.changedemo = true; CL_StopPlayback(); if( !CL_NextDemo() && !cls.changedemo ) UI_SetActiveMenu( true ); Cvar_DirectSet( &v_dark, "0" ); } /* ================= CL_DemoMoveToNextSection returns true on success, false on failure g-cont. probably captain obvious mode is ON ================= */ static qboolean CL_DemoMoveToNextSection( void ) { if( ++demo.entryIndex >= demo.directory.numentries ) { // done CL_DemoCompleted(); return false; } // switch to next section, we got a dem_stop demo.entry = &demo.directory.entries[demo.entryIndex]; // ready to continue reading, reset clock. FS_Seek( cls.demofile, demo.entry->offset, SEEK_SET ); // time is now relative to this chunk's clock. demo.starttime = CL_GetDemoPlaybackClock(); demo.framecount = 0; return true; } static qboolean CL_ReadRawNetworkData( byte *buffer, size_t *length ) { int msglen = 0; Assert( buffer != NULL ); Assert( length != NULL ); *length = 0; // assume we fail FS_Read( cls.demofile, &msglen, sizeof( int )); if( msglen < 0 ) { Con_Reportf( S_ERROR "Demo message length < 0\n" ); CL_DemoCompleted(); return false; } if( msglen > MAX_INIT_MSG ) { Con_Reportf( S_ERROR "Demo message %i > %i\n", msglen, MAX_INIT_MSG ); CL_DemoCompleted(); return false; } if( msglen > 0 ) { if( FS_Read( cls.demofile, buffer, msglen ) != msglen ) { Con_Reportf( S_ERROR "Error reading demo message data\n" ); CL_DemoCompleted(); return false; } } cls.netchan.last_received = host.realtime; cls.netchan.total_received += msglen; *length = msglen; if( cls.state != ca_active ) Cbuf_Execute(); return true; } /* ================= CL_DemoReadMessageQuake reads demo data and write it to client ================= */ static qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length ) { vec3_t viewangles; int msglen = 0; demoangle_t *a; *length = 0; // assume we fail // decide if it is time to grab the next message if( cls.signon == SIGNONS ) // allways grab until fully connected { if( cls.timedemo ) { if( host.framecount == cls.td_lastframe ) return false; // already read this frame's message cls.td_lastframe = host.framecount; // if this is the second frame, grab the real td_starttime // so the bogus time on the first frame doesn't count if( host.framecount == cls.td_startframe + 1 ) cls.td_starttime = host.realtime; } else if( cl.time <= cl.mtime[0] ) { // don't need another message yet return false; } } // get the next message FS_Read( cls.demofile, &msglen, sizeof( int )); FS_Read( cls.demofile, &viewangles[0], sizeof( float )); FS_Read( cls.demofile, &viewangles[1], sizeof( float )); FS_Read( cls.demofile, &viewangles[2], sizeof( float )); cls.netchan.incoming_sequence++; demo.timestamp = cl.mtime[0]; cl.skip_interp = false; // make sure what interp info contain angles from different frames // or lerping will stop working if( demo.lasttime != demo.timestamp ) { // select entry into circular buffer demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK; a = &demo.cmds[demo.angle_position]; // record update a->starttime = demo.timestamp; VectorCopy( viewangles, a->viewangles ); demo.lasttime = demo.timestamp; } if( msglen < 0 ) { Con_Reportf( S_ERROR "Demo message length < 0\n" ); CL_DemoCompleted(); return false; } if( msglen > MAX_INIT_MSG ) { Con_Reportf( S_ERROR "Demo message %i > %i\n", msglen, MAX_INIT_MSG ); CL_DemoCompleted(); return false; } if( msglen > 0 ) { if( FS_Read( cls.demofile, buffer, msglen ) != msglen ) { Con_Reportf( S_ERROR "Error reading demo message data\n" ); CL_DemoCompleted(); return false; } } cls.netchan.last_received = host.realtime; cls.netchan.total_received += msglen; *length = msglen; if( cls.state != ca_active ) Cbuf_Execute(); return true; } /* ================= CL_DemoReadMessage reads demo data and write it to client ================= */ qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) { size_t curpos = 0, lastpos = 0; float fElapsedTime = 0.0f; qboolean swallowmessages = true; static int tdlastdemoframe = 0; byte *userbuf = NULL; size_t size = 0; byte cmd; if( !cls.demofile ) { CL_DemoCompleted(); return false; } if(( !cl.background && ( cl.paused || cls.key_dest != key_game )) || cls.key_dest == key_console ) { demo.starttime += host.frametime; return false; // paused } if( cls.demoplayback == DEMO_QUAKE1 ) return CL_DemoReadMessageQuake( buffer, length ); do { qboolean bSkipMessage = false; if( !cls.demofile ) break; curpos = FS_Tell( cls.demofile ); if( !CL_ReadDemoCmdHeader( &cmd, &demo.timestamp )) return false; fElapsedTime = CL_GetDemoPlaybackClock() - demo.starttime; if( !cls.timedemo ) bSkipMessage = ((demo.timestamp - cl_serverframetime()) >= fElapsedTime) ? true : false; if( cls.changelevel ) demo.framecount = 1; // changelevel issues if( demo.framecount <= 2 && ( fElapsedTime - demo.timestamp ) > host.frametime ) demo.starttime = CL_GetDemoPlaybackClock(); // not ready for a message yet, put it back on the file. if( cmd != dem_norewind && cmd != dem_stop && bSkipMessage ) { // never skip first message if( demo.framecount != 0 ) { FS_Seek( cls.demofile, curpos, SEEK_SET ); return false; // not time yet. } } // we already have the usercmd_t for this frame // don't read next usercmd_t so predicting will work properly if( cmd == dem_usercmd && lastpos != 0 && demo.framecount != 0 ) { FS_Seek( cls.demofile, lastpos, SEEK_SET ); return false; // not time yet. } // COMMAND HANDLERS switch( cmd ) { case dem_jumptime: demo.starttime = CL_GetDemoPlaybackClock(); return false; // time is changed, skip frame case dem_stop: CL_DemoMoveToNextSection(); return false; // header is ended, skip frame case dem_userdata: FS_Read( cls.demofile, &size, sizeof( int )); userbuf = Mem_Malloc( cls.mempool, size ); FS_Read( cls.demofile, userbuf, size ); if( clgame.hInstance ) clgame.dllFuncs.pfnDemo_ReadBuffer( size, userbuf ); Mem_Free( userbuf ); userbuf = NULL; break; case dem_usercmd: CL_ReadDemoUserCmd( false ); lastpos = FS_Tell( cls.demofile ); break; default: swallowmessages = false; break; } } while( swallowmessages ); // If we are playing back a timedemo, and we've already passed on a // frame update for this host_frame tag, then we'll just skip this message. if( cls.timedemo && ( tdlastdemoframe == host.framecount )) { FS_Seek( cls.demofile, FS_Tell ( cls.demofile ) - 5, SEEK_SET ); return false; } tdlastdemoframe = host.framecount; if( !cls.demofile ) return false; // if not on "LOADING" section, check a few things if( demo.entryIndex ) { // We are now on the second frame of a new section, // if so, reset start time (unless in a timedemo) if( demo.framecount == 1 && !cls.timedemo ) { // cheat by moving the relative start time forward. demo.starttime = CL_GetDemoPlaybackClock(); } } demo.framecount++; CL_ReadDemoSequence( false ); return CL_ReadRawNetworkData( buffer, length ); } static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next ) { int i, i0, i1, imod; float at; if( cls.timedemo ) return; imod = demo.angle_position - 1; i0 = (imod + 1) & ANGLE_MASK; i1 = (imod + 0) & ANGLE_MASK; if( demo.cmds[i0].starttime >= t ) { for( i = 0; i < ANGLE_BACKUP - 2; i++ ) { at = demo.cmds[imod & ANGLE_MASK].starttime; if( at == 0.0f ) break; if( at < t ) { i0 = (imod + 1) & ANGLE_MASK; i1 = (imod + 0) & ANGLE_MASK; break; } imod--; } } *next = &demo.cmds[i0]; *prev = &demo.cmds[i1]; // avoid division by zero (probably this should never happens) if((*prev)->starttime == (*next)->starttime ) { *prev = *next; *frac = 0.0f; return; } // time spans the two entries *frac = ( t - (*prev)->starttime ) / ((*next)->starttime - (*prev)->starttime ); *frac = bound( 0.0f, *frac, 1.0f ); } /* ============== CL_DemoInterpolateAngles We can predict or inpolate player movement with standed client code but viewangles interpolate here ============== */ void CL_DemoInterpolateAngles( void ) { demoangle_t *prev = NULL, *next = NULL; float frac = 0.0f; float curtime; if( cls.demoplayback == DEMO_QUAKE1 ) { // manually select next & prev states next = &demo.cmds[(demo.angle_position - 0) & ANGLE_MASK]; prev = &demo.cmds[(demo.angle_position - 1) & ANGLE_MASK]; if( cl.skip_interp ) *prev = *next; // camera was teleported frac = cl.lerpFrac; } else { curtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime; if( curtime > demo.timestamp ) curtime = demo.timestamp; // don't run too far CL_DemoFindInterpolatedViewAngles( curtime, &frac, &prev, &next ); } if( prev && next ) { vec4_t q, q1, q2; AngleQuaternion( next->viewangles, q1, false ); AngleQuaternion( prev->viewangles, q2, false ); QuaternionSlerp( q2, q1, frac, q ); QuaternionAngle( q, cl.viewangles ); } else VectorCopy( cl.cmd.viewangles, cl.viewangles ); } /* ============== CL_FinishTimeDemo show stats ============== */ static void CL_FinishTimeDemo( void ) { qboolean temp = host.allow_console; int frames; double time; cls.timedemo = false; // the first frame didn't count frames = (host.framecount - cls.td_startframe) - 1; time = host.realtime - cls.td_starttime; if( !time ) time = 1.0; host.allow_console = true; Con_Printf( "timedemo result: %i frames %5.3f seconds %5.3f fps\n", frames, time, frames / time ); host.allow_console = temp; if( Sys_CheckParm( "-timedemo" )) CL_Quit_f(); } /* ============== CL_StopPlayback Called when a demo file runs out, or the user starts a game ============== */ void CL_StopPlayback( void ) { if( !cls.demoplayback ) return; // release demofile FS_Close( cls.demofile ); cls.demoplayback = false; demo.framecount = 0; cls.demofile = NULL; cls.olddemonum = Q_max( -1, cls.demonum - 1 ); if( demo.directory.entries != NULL ) Mem_Free( demo.directory.entries ); cls.td_lastframe = host.framecount; demo.directory.numentries = 0; demo.directory.entries = NULL; demo.header.host_fps = 0.0; demo.entry = NULL; cls.demoname[0] = '\0'; // clear demoname too gameui.globals->demoname[0] = '\0'; if( cls.timedemo ) CL_FinishTimeDemo(); if( cls.changedemo ) { S_StopAllSounds( true ); S_StopBackgroundTrack(); } else { // let game known about demo state Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); cls.state = ca_disconnected; memset( &cls.serveradr, 0, sizeof( cls.serveradr ) ); cls.set_lastdemo = false; S_StopBackgroundTrack(); cls.connect_time = 0; cls.demonum = -1; cls.signon = 0; // and finally clear the state CL_ClearState (); } } /* ================== CL_GetDemoComment ================== */ int GAME_EXPORT CL_GetDemoComment( const char *demoname, char *comment ) { file_t *demfile; demoheader_t demohdr; demodirectory_t directory; demoentry_t entry; float playtime = 0.0f; int i; if( !comment ) return false; demfile = FS_Open( demoname, "rb", false ); if( !demfile ) { comment[0] = '\0'; return false; } // read in the m_DemoHeader FS_Read( demfile, &demohdr, sizeof( demoheader_t )); if( demohdr.id != IDEMOHEADER ) { FS_Close( demfile ); Q_strncpy( comment, "", MAX_STRING ); return false; } if(( demohdr.net_protocol != PROTOCOL_VERSION && demohdr.net_protocol != PROTOCOL_LEGACY_VERSION ) || demohdr.dem_protocol != DEMO_PROTOCOL ) { FS_Close( demfile ); Q_strncpy( comment, "", MAX_STRING ); return false; } // now read in the directory structure. FS_Seek( demfile, demohdr.directory_offset, SEEK_SET ); FS_Read( demfile, &directory.numentries, sizeof( int )); if( directory.numentries < 1 || directory.numentries > 1024 ) { FS_Close( demfile ); Q_strncpy( comment, "", MAX_STRING ); return false; } for( i = 0; i < directory.numentries; i++ ) { FS_Read( demfile, &entry, sizeof( demoentry_t )); playtime += entry.playback_time; } // split comment to sections Q_strncpy( comment, demohdr.mapname, CS_SIZE ); Q_strncpy( comment + CS_SIZE, demohdr.comment, CS_SIZE ); Q_snprintf( comment + CS_SIZE * 2, CS_TIME, "%g sec", playtime ); // all done FS_Close( demfile ); return true; } /* ================== CL_NextDemo Called when a demo finishes ================== */ static qboolean CL_NextDemo( void ) { char str[MAX_QPATH]; if( cls.demonum == -1 ) return false; // don't play demos S_StopAllSounds( true ); if( !cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS ) { cls.demonum = 0; if( !cls.demos[cls.demonum][0] ) { Con_Printf( "no demos listed with startdemos\n" ); cls.demonum = -1; return false; } } Q_snprintf( str, MAX_STRING, "playdemo %s\n", cls.demos[cls.demonum] ); Cbuf_InsertText( str ); cls.demonum++; return true; } /* ================== CL_CheckStartupDemos queue demos loop after movie playing ================== */ void CL_CheckStartupDemos( void ) { if( !cls.demos_pending ) return; // no demos in loop if( cls.movienum != -1 ) return; // wait until movies finished if( GameState->nextstate != STATE_RUNFRAME || cls.demoplayback ) { // commandline override cls.demos_pending = false; cls.demonum = -1; return; } // run demos loop in background mode Cvar_DirectSet( &v_dark, "1" ); cls.demos_pending = false; cls.demonum = 0; CL_NextDemo (); } /* ================== CL_DemoGetName ================== */ static void CL_DemoGetName( int lastnum, char *filename, size_t size ) { if( lastnum < 0 || lastnum > 9999 ) { // bound Q_strncpy( filename, "demo9999", size ); return; } Q_snprintf( filename, size, "demo%04d", lastnum ); } /* ==================== CL_Record_f record Begins recording a demo from the current position ==================== */ void CL_Record_f( void ) { string demoname, demopath; const char *name; int n; if( Cmd_Argc() == 1 ) { name = "new"; } else if( Cmd_Argc() == 2 ) { name = Cmd_Argv( 1 ); } else { Con_Printf( S_USAGE "record \n" ); return; } if( cls.demorecording ) { Con_Printf( "Already recording.\n"); return; } if( cls.demoplayback ) { Con_Printf( "Can't record during demo playback.\n"); return; } if( !cls.demoheader || cls.state != ca_active ) { Con_Printf( "You must be in a level to record.\n"); return; } if( !Q_stricmp( name, "new" )) { // scan for a free filename for( n = 0; n < 10000; n++ ) { CL_DemoGetName( n, demoname, sizeof( demoname )); Q_snprintf( demopath, sizeof( demopath ), "%s.dem", demoname ); if( !FS_FileExists( demopath, true )) break; } if( n == 10000 ) { Con_Printf( S_ERROR "no free slots for demo recording\n" ); return; } } else Q_strncpy( demoname, name, sizeof( demoname )); // open the demo file Q_snprintf( demopath, sizeof( demopath ), "%s.dem", demoname ); // make sure that old demo is removed if( FS_FileExists( demopath, false )) FS_Delete( demopath ); Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); Q_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname )); CL_WriteDemoHeader( demopath ); } static qboolean CL_ParseDemoHeader( const char *callee, const char *filename, file_t *f, demoheader_t *hdr, int32_t *numentries ) { if( FS_Read( f, hdr, sizeof( *hdr )) != sizeof( *hdr ) || hdr->id != IDEMOHEADER ) { Con_Printf( S_ERROR "%s: %s is not in supported format or not a demo file\n", callee, filename ); return false; } // force null terminate strings hdr->mapname[sizeof( hdr->mapname ) - 1] = 0; hdr->comment[sizeof( hdr->comment ) - 1] = 0; hdr->gamedir[sizeof( hdr->gamedir ) - 1] = 0; if( hdr->dem_protocol != DEMO_PROTOCOL ) { Con_Printf( S_ERROR "%s: demo protocol outdated (%i should be %i)\n", callee, hdr->net_protocol, DEMO_PROTOCOL ); return false; } if( hdr->net_protocol != PROTOCOL_VERSION && hdr->net_protocol != PROTOCOL_LEGACY_VERSION && hdr->net_protocol != PROTOCOL_GOLDSRC_VERSION_DEMO ) { Con_Printf( S_ERROR "%s: net protocol outdated (%i should be %i or %i)\n", callee, hdr->net_protocol, PROTOCOL_VERSION, PROTOCOL_LEGACY_VERSION ); return false; } if( FS_Seek( f, hdr->directory_offset, SEEK_SET ) < 0 || FS_Read( f, numentries, sizeof( *numentries )) != sizeof( *numentries )) { Con_Printf( S_ERROR "%s: can't find directory offset in %s, demo file corrupted\n", callee, filename ); return false; } if(( *numentries < 1 ) || ( *numentries > 1024 )) { Con_Printf( S_ERROR "%s: demo have bogus # of directory entries: %i\n", callee, *numentries ); return false; } return true; } /* ==================== CL_PlayDemo_f playdemo ==================== */ void CL_PlayDemo_f( void ) { char filename[MAX_QPATH]; char demoname[MAX_QPATH]; int i, ident; if( Cmd_Argc() < 2 ) { Con_Printf( S_USAGE "%s \n", Cmd_Argv( 0 )); return; } if( cls.demoplayback ) CL_StopPlayback(); if( cls.demorecording ) { Con_Printf( "Can't playback during demo record.\n"); return; } Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname )); COM_StripExtension( demoname ); Q_snprintf( filename, sizeof( filename ), "%s.dem", demoname ); // hidden parameter if( Cmd_Argc() > 2 ) cls.set_lastdemo = Q_atoi( Cmd_Argv( 2 )); // member last demo if( cls.set_lastdemo ) Cvar_Set( "lastdemo", demoname ); if( !FS_FileExists( filename, true )) { Con_Printf( S_ERROR "couldn't open %s\n", filename ); CL_DemoAborted(); return; } cls.demofile = FS_Open( filename, "rb", true ); Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); Q_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname )); FS_Read( cls.demofile, &ident, sizeof( int )); FS_Seek( cls.demofile, 0, SEEK_SET ); // rewind back to start cls.forcetrack = 0; // check for quake demos if( ident != IDEMOHEADER ) { int c, neg = false; demo.header.host_fps = host_maxfps.value; while(( c = FS_Getc( cls.demofile )) != '\n' ) { if( c == '-' ) neg = true; else cls.forcetrack = cls.forcetrack * 10 + (c - '0'); } if( neg ) cls.forcetrack = -cls.forcetrack; CL_DemoStartPlayback( DEMO_QUAKE1 ); cls.legacymode = PROTO_QUAKE; return; // quake demo is started } // read in the demo header if( !CL_ParseDemoHeader( Cmd_Argv( 0 ), filename, cls.demofile, &demo.header, &demo.directory.numentries )) { CL_DemoAborted(); return; } // allocate demo entries demo.directory.entries = Mem_Malloc( cls.mempool, sizeof( *demo.directory.entries ) * demo.directory.numentries ); for( i = 0; i < demo.directory.numentries; i++ ) { demoentry_t *entry = &demo.directory.entries[i]; if( FS_Read( cls.demofile, entry, sizeof( *entry )) != sizeof( *entry )) { Con_Printf( S_ERROR "%s: demo entry %i of %s corrupted", Cmd_Argv( 0 ), i, filename ); CL_DemoAborted(); return; } entry->description[sizeof( entry->description ) - 1] = 0; } demo.entryIndex = 0; demo.entry = &demo.directory.entries[demo.entryIndex]; FS_Seek( cls.demofile, demo.entry->offset, SEEK_SET ); CL_DemoStartPlayback( DEMO_XASH3D ); // must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol cls.legacymode = CL_GetProtocolFromDemo( demo.header.net_protocol ); // g-cont. is this need? Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); // begin a playback demo } /* ==================== CL_TimeDemo_f timedemo ==================== */ void CL_TimeDemo_f( void ) { CL_PlayDemo_f (); // cls.td_starttime will be grabbed at the second frame of the demo, so // all the loading time doesn't get counted cls.timedemo = true; cls.td_starttime = host.realtime; cls.td_startframe = host.framecount; cls.td_lastframe = -1; // get a new message this frame } /* ================== CL_StartDemos_f ================== */ void CL_StartDemos_f( void ) { int i, c; if( cls.key_dest != key_menu ) { Con_Printf( "'startdemos' is not valid from the console\n" ); return; } c = Cmd_Argc() - 1; if( c > MAX_DEMOS ) { Con_DPrintf( S_WARN "%s: max %i demos in demoloop\n", __func__, MAX_DEMOS ); c = MAX_DEMOS; } Con_Printf( "%i demo%s in loop\n", c, (c > 1) ? "s" : "" ); for( i = 1; i < c + 1; i++ ) Q_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] )); cls.demos_pending = true; } /* ================== CL_Demos_f Return to looping demos ================== */ void CL_Demos_f( void ) { if( cls.key_dest != key_menu ) { Con_Printf( "'demos' is not valid from the console\n" ); return; } // demos loop are not running if( cls.olddemonum == -1 ) return; cls.demonum = cls.olddemonum; // run demos loop in background mode if( !SV_Active() && !cls.demoplayback ) CL_NextDemo (); } /* ==================== CL_Stop_f stop any client activity ==================== */ void CL_Stop_f( void ) { // stop all CL_StopRecord(); CL_StopPlayback(); SCR_StopCinematic(); // stop background track that was runned from the console if( !SV_Active( )) { S_StopBackgroundTrack(); } } void CL_ListDemo_f( void ) { demoheader_t hdr; int32_t num_entries; file_t *f; char filename[MAX_QPATH]; char demoname[MAX_QPATH]; int i; if( Cmd_Argc() < 2 ) { Con_Printf( S_USAGE "%s \n", Cmd_Argv( 0 )); return; } Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname )); COM_StripExtension( demoname ); Q_snprintf( filename, sizeof( filename ), "%s.dem", demoname ); f = FS_Open( filename, "rb", true ); if( !f ) { Con_Printf( S_ERROR "couldn't open %s\n", filename ); return; } if( !CL_ParseDemoHeader( Cmd_Argv( 0 ), filename, f, &hdr, &num_entries )) { FS_Close( f ); return; } Con_Printf( "Demo contents for %s:\n" "\tProtocol: %i net/%i demo\n" "\tFPS: %g\n" "\tMap: %s\n" "\tComment: %s\n" "\tGame: %s\n", filename, hdr.net_protocol, hdr.dem_protocol, hdr.host_fps, hdr.mapname, hdr.comment, hdr.gamedir ); for( i = 0; i < num_entries; i++ ) { demoentry_t entry; Con_Printf( "Demo entry #%i:\n", i ); if( FS_Read( f, &entry, sizeof( entry )) != sizeof( entry )) { Con_Printf( S_ERROR "can't read demo entry\n" ); FS_Close( f ); return; } entry.description[sizeof( entry.description ) - 1] = 0; if( entry.entrytype == DEMO_STARTUP ) { // startup entries don't have anything useful Con_Printf( "\tEntry type: " S_YELLOW "startup" S_DEFAULT "\n" ); } else { Con_Printf( "\tEntry type: " S_GREEN "normal" S_DEFAULT " (%i)\n" "\tEntry playback time/frames: %.2f seconds/%i frames\n" "\tEntry flags: 0x%x\n" "\tEntry description: %s\n", entry.entrytype, entry.playback_time, entry.playback_frames, entry.flags, entry.description ); } } FS_Close( f ); } ================================================ FILE: engine/client/cl_efrag.c ================================================ /* gl_refrag.c - store entity fragments Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "entity_types.h" #include "studio.h" #include "world.h" // BOX_ON_PLANE_SIDE #include "client.h" #include "xash3d_mathlib.h" /* =============================================================================== ENTITY FRAGMENT FUNCTIONS =============================================================================== */ #define NUM_EFRAGS_ALLOC 64 // alloc 64 efrags (1-2kb each alloc) static efrag_t **lastlink; static mnode_t *r_pefragtopnode; static vec3_t r_emins, r_emaxs; static cl_entity_t *r_addent; static int cl_efrags_num; static efrag_t *cl_efrags; static efrag_t *CL_AllocEfrags( int num ) { int i; efrag_t *efrags; if( !cl.worldmodel ) { Host_Error( "%s: called with NULL world\n", __func__ ); return NULL; } if( num == 0 ) return NULL; // set world to be the owner, so it will get automatically cleaned up efrags = Mem_Calloc( cl.worldmodel->mempool, sizeof( *efrags ) * num ); // initialize linked list for( i = 0; i < num - 1; i++ ) efrags[i].entnext = &efrags[i + 1]; cl_efrags_num += num; return efrags; } /* ============== CL_ClearEfrags ============== */ void CL_ClearEfrags( void ) { cl_efrags_num = 0; cl_efrags = NULL; } /* =================== R_SplitEntityOnNode =================== */ static void R_SplitEntityOnNode( mnode_t *node ) { efrag_t *ef; mleaf_t *leaf; int sides; if( node->contents == CONTENTS_SOLID ) return; // add an efrag if the node is a leaf if( node->contents < 0 ) { if( !r_pefragtopnode ) r_pefragtopnode = node; leaf = (mleaf_t *)node; // grab an efrag off the free list ef = cl_efrags; if( !ef ) ef = CL_AllocEfrags( NUM_EFRAGS_ALLOC ); cl_efrags = ef->entnext; ef->entity = r_addent; // add the entity link *lastlink = ef; lastlink = &ef->entnext; ef->entnext = NULL; // set the leaf links ef->leaf = leaf; ef->leafnext = leaf->efrags; leaf->efrags = ef; return; } // NODE_MIXED sides = BOX_ON_PLANE_SIDE( r_emins, r_emaxs, node->plane ); if( sides == 3 ) { // split on this plane // if this is the first splitter of this bmodel, remember it if( !r_pefragtopnode ) r_pefragtopnode = node; } // recurse down the contacted sides if( sides & 1 ) R_SplitEntityOnNode( node_child( node, 0, cl.worldmodel )); if( sides & 2 ) R_SplitEntityOnNode( node_child( node, 1, cl.worldmodel )); } /* =========== R_AddEfrags =========== */ void R_AddEfrags( cl_entity_t *ent ) { matrix3x4 transform; vec3_t outmins, outmaxs; int i; if( !ent->model ) return; r_addent = ent; lastlink = &ent->efrag; r_pefragtopnode = NULL; // handle entity rotation for right bbox expanding Matrix3x4_CreateFromEntity( transform, ent->angles, vec3_origin, 1.0f ); Matrix3x4_TransformAABB( transform, ent->model->mins, ent->model->maxs, outmins, outmaxs ); for( i = 0; i < 3; i++ ) { r_emins[i] = ent->origin[i] + outmins[i]; r_emaxs[i] = ent->origin[i] + outmaxs[i]; } R_SplitEntityOnNode( cl.worldmodel->nodes ); ent->topnode = r_pefragtopnode; } /* ================ R_StoreEfrags ================ */ void R_StoreEfrags( efrag_t **ppefrag, int framecount ) { efrag_t *pefrag; cl_entity_t *pent; model_t *clmodel; while(( pefrag = *ppefrag ) != NULL ) { pent = pefrag->entity; clmodel = pent->model; // how this could happen? if( unlikely( clmodel->type < mod_brush || clmodel->type > mod_studio )) continue; if( pent->visframe != framecount ) { if( CL_AddVisibleEntity( pent, ET_FRAGMENTED )) { // mark that we've recorded this entity for this frame pent->curstate.messagenum = cl.parsecount; pent->visframe = framecount; } } ppefrag = &pefrag->leafnext; } } ================================================ FILE: engine/client/cl_efx.c ================================================ #include "common.h" #include "client.h" #include "customentity.h" #include "r_efx.h" #include "cl_tent.h" #include "pm_local.h" #define PART_SIZE Q_max( 0.5f, cl_draw_particles.value ) /* ============================================================== PARTICLES MANAGEMENT ============================================================== */ // particle ramps static int ramp1[8] = { 0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61 }; static int ramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 }; static int ramp3[6] = { 0x6d, 0x6b, 6, 5, 4, 3 }; static int gSparkRamp[9] = { 0xfe, 0xfd, 0xfc, 0x6f, 0x6e, 0x6d, 0x6c, 0x67, 0x60 }; static CVAR_DEFINE_AUTO( tracerspeed, "6000", 0, "tracer speed" ); static CVAR_DEFINE_AUTO( tracerlength, "0.8", 0, "tracer length factor" ); static CVAR_DEFINE_AUTO( traceroffset, "30", 0, "tracer starting offset" ); static particle_t *cl_active_particles; static particle_t *cl_active_tracers; static particle_t *cl_free_particles; static particle_t *cl_particles = NULL; // particle pool static vec3_t cl_avelocities[NUMVERTEXNORMALS]; static float cl_lasttimewarn = 0.0f; // expand debugging BBOX particle hulls by this many units. #define BOX_GAP 0.0f /* ================ R_LookupColor find nearest color in particle palette ================ */ short GAME_EXPORT R_LookupColor( byte r, byte g, byte b ) { int i, best; float diff, bestdiff; float rf, gf, bf; bestdiff = 999999; best = -1; for( i = 0; i < 256; i++ ) { rf = r - clgame.palette[i].r; gf = g - clgame.palette[i].g; bf = b - clgame.palette[i].b; // convert color to monochrome diff = rf * (rf * 0.2f) + gf * (gf * 0.5f) + bf * (bf * 0.3f); if ( diff < bestdiff ) { bestdiff = diff; best = i; } } return best; } /* ================ R_GetPackedColor in hardware mode does nothing ================ */ void GAME_EXPORT R_GetPackedColor( short *packed, short color ) { if( packed ) *packed = 0; } /* ================ CL_InitParticles ================ */ void CL_InitParticles( void ) { int i; cl_particles = Mem_Calloc( cls.mempool, sizeof( particle_t ) * GI->max_particles ); CL_ClearParticles (); // this is used for EF_BRIGHTFIELD for( i = 0; i < NUMVERTEXNORMALS; i++ ) { cl_avelocities[i][0] = COM_RandomFloat( 0.0f, 2.55f ); cl_avelocities[i][1] = COM_RandomFloat( 0.0f, 2.55f ); cl_avelocities[i][2] = COM_RandomFloat( 0.0f, 2.55f ); } Cvar_RegisterVariable( &tracerspeed ); Cvar_RegisterVariable( &tracerlength ); Cvar_RegisterVariable( &traceroffset ); } /* ================ CL_ClearParticles ================ */ void CL_ClearParticles( void ) { int i; if( !cl_particles ) return; cl_free_particles = cl_particles; cl_active_particles = NULL; cl_active_tracers = NULL; for( i = 0; i < GI->max_particles - 1; i++ ) cl_particles[i].next = &cl_particles[i+1]; cl_particles[GI->max_particles-1].next = NULL; } /* ================ CL_FreeParticles ================ */ void CL_FreeParticles( void ) { if( cl_particles ) Mem_Free( cl_particles ); cl_particles = NULL; } /* ================ CL_AllocParticleFast unconditionally give new particle pointer from cl_free_particles ================ */ particle_t *CL_AllocParticleFast( void ) { particle_t *p = NULL; if( cl_free_particles ) { p = cl_free_particles; cl_free_particles = p->next; } return p; } /* ================ R_AllocParticle can return NULL if particles is out ================ */ particle_t * GAME_EXPORT R_AllocParticle( void (*callback)( particle_t*, float )) { particle_t *p; if( !cl_draw_particles.value ) return NULL; // never alloc particles when we not in game if( cl_clientframetime() == 0.0 ) return NULL; if( !cl_free_particles ) { if( cl_lasttimewarn < host.realtime ) { // don't spam about overflow Con_DPrintf( S_ERROR "Overflow %d particles\n", GI->max_particles ); cl_lasttimewarn = host.realtime + 1.0f; } return NULL; } p = cl_free_particles; cl_free_particles = p->next; p->next = cl_active_particles; cl_active_particles = p; // clear old particle p->type = pt_static; VectorClear( p->vel ); VectorClear( p->org ); p->packedColor = 0; p->die = cl.time; p->color = 0; p->ramp = 0; if( callback ) { p->type = pt_clientcustom; p->callback = callback; } return p; } /* ================ R_AllocTracer can return NULL if particles is out ================ */ static particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life ) { particle_t *p; if( !cl_draw_tracers.value ) return NULL; // never alloc particles when we not in game if( cl_clientframetime() == 0.0 ) return NULL; if( !cl_free_particles ) { if( cl_lasttimewarn < host.realtime ) { // don't spam about overflow Con_DPrintf( S_ERROR "Overflow %d tracers\n", GI->max_particles ); cl_lasttimewarn = host.realtime + 1.0f; } return NULL; } p = cl_free_particles; cl_free_particles = p->next; p->next = cl_active_tracers; cl_active_tracers = p; // clear old particle p->type = pt_static; VectorCopy( org, p->org ); VectorCopy( vel, p->vel ); p->die = cl.time + life; p->ramp = tracerlength.value; p->color = TRACER_COLORINDEX_DEFAULT; // select custom color p->packedColor = 255; // alpha return p; } /* ============================================================== VIEWBEAMS MANAGEMENT ============================================================== */ static BEAM *cl_active_beams; static BEAM *cl_free_beams; static BEAM *cl_viewbeams = NULL; // beams pool /* ============================================================== BEAM ALLOCATE & PROCESSING ============================================================== */ /* ============== R_BeamSetAttributes set beam attributes ============== */ static void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame ) { pbeam->frame = (float)startFrame; pbeam->frameRate = framerate; pbeam->r = r; pbeam->g = g; pbeam->b = b; } /* ============== R_BeamAlloc ============== */ static BEAM *R_BeamAlloc( void ) { BEAM *pBeam; if( !cl_free_beams ) return NULL; pBeam = cl_free_beams; cl_free_beams = pBeam->next; memset( pBeam, 0, sizeof( *pBeam )); pBeam->next = cl_active_beams; cl_active_beams = pBeam; pBeam->die = cl.time; return pBeam; } /* ============== R_BeamFree ============== */ static void R_BeamFree( BEAM *pBeam ) { // free particles that have died off. R_FreeDeadParticles( &pBeam->particles ); // now link into free list; pBeam->next = cl_free_beams; cl_free_beams = pBeam; } /* ================ CL_InitViewBeams ================ */ void CL_InitViewBeams( void ) { cl_viewbeams = Mem_Calloc( cls.mempool, sizeof( BEAM ) * GI->max_beams ); CL_ClearViewBeams(); } /* ================ CL_ClearViewBeams ================ */ void CL_ClearViewBeams( void ) { int i; if( !cl_viewbeams ) return; // clear beams cl_free_beams = cl_viewbeams; cl_active_beams = NULL; for( i = 0; i < GI->max_beams - 1; i++ ) cl_viewbeams[i].next = &cl_viewbeams[i+1]; cl_viewbeams[GI->max_beams - 1].next = NULL; } /* ================ CL_FreeViewBeams ================ */ void CL_FreeViewBeams( void ) { if( cl_viewbeams ) Mem_Free( cl_viewbeams ); cl_viewbeams = NULL; } /* ============== R_BeamGetEntity extract entity number from index handle user entities ============== */ cl_entity_t *R_BeamGetEntity( int index ) { if( index < 0 ) return clgame.dllFuncs.pfnGetUserEntity( BEAMENT_ENTITY( -index )); return CL_GetEntityByIndex( BEAMENT_ENTITY( index )); } /* ============== CL_KillDeadBeams ============== */ void CL_KillDeadBeams( cl_entity_t *pDeadEntity ) { BEAM *pbeam; BEAM *pnewlist; BEAM *pnext; particle_t *pHead; // build a new list to replace cl_active_beams. pbeam = cl_active_beams; // old list. pnewlist = NULL; // new list. while( pbeam ) { cl_entity_t *beament; pnext = pbeam->next; // link into new list. if( R_BeamGetEntity( pbeam->startEntity ) != pDeadEntity ) { pbeam->next = pnewlist; pnewlist = pbeam; pbeam = pnext; continue; } pbeam->flags &= ~(FBEAM_STARTENTITY | FBEAM_ENDENTITY); if( pbeam->type != TE_BEAMFOLLOW ) { // remove beam pbeam->die = cl.time - 0.1f; // kill off particles pHead = pbeam->particles; while( pHead ) { pHead->die = cl.time - 0.1f; pHead = pHead->next; } // free the beam R_BeamFree( pbeam ); } else { // stay active pbeam->next = pnewlist; pnewlist = pbeam; } pbeam = pnext; } // We now have a new list with the bogus stuff released. cl_active_beams = pnewlist; } /* =============== CL_ReadLineFile_f Optimized version of pointfile - use beams instead of particles =============== */ void CL_ReadLineFile_f( void ) { byte *afile; char *pfile; vec3_t p1, p2; int count, modelIndex; char filename[MAX_QPATH]; model_t *model; string token; Q_snprintf( filename, sizeof( filename ), "maps/%s.lin", clgame.mapname ); afile = FS_LoadFile( filename, NULL, false ); if( !afile ) { Con_Printf( S_ERROR "couldn't open %s\n", filename ); return; } Con_Printf( "Reading %s...\n", filename ); count = 0; pfile = (char *)afile; model = CL_LoadModel( DEFAULT_LASERBEAM_PATH, &modelIndex ); while( 1 ) { pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; p1[0] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; p1[1] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; p1[2] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; if( token[0] != '-' ) { Con_Printf( S_ERROR "%s is corrupted\n", filename ); break; } pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; p2[0] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; p2[1] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; p2[2] = Q_atof( token ); count++; if( !R_BeamPoints( p1, p2, modelIndex, 0, 2, 0, 255, 0, 0, 0, 255.0f, 0.0f, 0.0f )) { if( !model || model->type != mod_sprite ) Con_Printf( S_ERROR "failed to load \"%s\"!\n", DEFAULT_LASERBEAM_PATH ); else Con_Printf( S_ERROR "not enough free beams!\n" ); break; } } Mem_Free( afile ); if( count ) Con_Printf( "%i lines read\n", count ); else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); } /* ============== R_BeamSprite Create a beam with sprite at the end Valve legacy ============== */ static void CL_BeamSprite( vec3_t start, vec3_t end, int beamIndex, int spriteIndex ) { R_BeamPoints( start, end, beamIndex, 0.01f, 0.4f, 0, COM_RandomFloat( 0.5f, 0.655f ), 5.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f ); R_TempSprite( end, vec3_origin, 0.1f, spriteIndex, kRenderTransAdd, kRenderFxNone, 0.35f, 0.01f, 0.0f ); } /* ============== R_BeamSetup generic function. all beams must be passed through this ============== */ static void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) { model_t *sprite = CL_ModelHandle( modelIndex ); if( !sprite ) return; pbeam->type = BEAM_POINTS; pbeam->modelIndex = modelIndex; pbeam->frame = 0; pbeam->frameRate = 0; pbeam->frameCount = sprite->numframes; VectorCopy( start, pbeam->source ); VectorCopy( end, pbeam->target ); VectorSubtract( end, start, pbeam->delta ); pbeam->freq = speed * cl.time; pbeam->die = life + cl.time; pbeam->amplitude = amplitude; pbeam->brightness = brightness; pbeam->width = width; pbeam->speed = speed; if( amplitude >= 0.50f ) pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels pbeam->pFollowModel = NULL; pbeam->flags = 0; } /* ============== CL_BeamAttemptToDie Check for expired beams ============== */ static qboolean CL_BeamAttemptToDie( BEAM *pBeam ) { Assert( pBeam != NULL ); // premanent beams never die automatically if( FBitSet( pBeam->flags, FBEAM_FOREVER )) return false; if( pBeam->type == TE_BEAMFOLLOW && pBeam->particles ) { // wait for all trails are dead return false; } // other beams if( pBeam->die > cl.time ) return false; return true; } /* ============== R_BeamKill Remove beam attached to specified entity and all particle trails (if this is a beamfollow) ============== */ void GAME_EXPORT R_BeamKill( int deadEntity ) { BEAM *beam; for( beam = cl_active_beams; beam; beam = beam->next ) { if( FBitSet( beam->flags, FBEAM_STARTENTITY ) && beam->startEntity == deadEntity ) { if( beam->type != TE_BEAMFOLLOW ) beam->die = cl.time; ClearBits( beam->flags, FBEAM_STARTENTITY ); } if( FBitSet( beam->flags, FBEAM_ENDENTITY ) && beam->endEntity == deadEntity ) { beam->die = cl.time; ClearBits( beam->flags, FBEAM_ENDENTITY ); } } } /* ============== CL_ParseViewBeam handle beam messages ============== */ void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) { vec3_t start, end; int modelIndex, startFrame; float frameRate, life, width; int startEnt, endEnt; float noise, speed; float r, g, b, a; switch( beamType ) { case TE_BEAMPOINTS: case TE_BEAMENTPOINT: case TE_BEAMENTS: if( beamType == TE_BEAMENTS ) { startEnt = MSG_ReadShort( msg ); endEnt = MSG_ReadShort( msg ); } else { if( beamType == TE_BEAMENTPOINT ) { startEnt = MSG_ReadShort( msg ); } else { start[0] = MSG_ReadCoord( msg ); start[1] = MSG_ReadCoord( msg ); start[2] = MSG_ReadCoord( msg ); } end[0] = MSG_ReadCoord( msg ); end[1] = MSG_ReadCoord( msg ); end[2] = MSG_ReadCoord( msg ); } modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); frameRate = (float)MSG_ReadByte( msg ) * 0.1f; life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ) * 0.1f; noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; speed = (float)MSG_ReadByte( msg ) * 0.1f; if( beamType == TE_BEAMENTS ) R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); else if( beamType == TE_BEAMENTPOINT ) R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); else R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_LIGHTNING: start[0] = MSG_ReadCoord( msg ); start[1] = MSG_ReadCoord( msg ); start[2] = MSG_ReadCoord( msg ); end[0] = MSG_ReadCoord( msg ); end[1] = MSG_ReadCoord( msg ); end[2] = MSG_ReadCoord( msg ); life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ) * 0.1f; noise = (float)MSG_ReadByte( msg ) * 0.01f; modelIndex = MSG_ReadShort( msg ); R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6f, 3.5f ); break; case TE_BEAM: break; case TE_BEAMSPRITE: start[0] = MSG_ReadCoord( msg ); start[1] = MSG_ReadCoord( msg ); start[2] = MSG_ReadCoord( msg ); end[0] = MSG_ReadCoord( msg ); end[1] = MSG_ReadCoord( msg ); end[2] = MSG_ReadCoord( msg ); modelIndex = MSG_ReadShort( msg ); // beam model startFrame = MSG_ReadShort( msg ); // sprite model CL_BeamSprite( start, end, modelIndex, startFrame ); break; case TE_BEAMTORUS: case TE_BEAMDISK: case TE_BEAMCYLINDER: start[0] = MSG_ReadCoord( msg ); start[1] = MSG_ReadCoord( msg ); start[2] = MSG_ReadCoord( msg ); end[0] = MSG_ReadCoord( msg ); end[1] = MSG_ReadCoord( msg ); end[2] = MSG_ReadCoord( msg ); modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); frameRate = (float)MSG_ReadByte( msg ) * 0.1f; life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ); noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; speed = (float)MSG_ReadByte( msg ) * 0.1f; R_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_BEAMFOLLOW: startEnt = MSG_ReadShort( msg ); modelIndex = MSG_ReadShort( msg ); life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ); r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; R_BeamFollow( startEnt, modelIndex, life, width, r, g, b, a ); break; case TE_BEAMRING: startEnt = MSG_ReadShort( msg ); endEnt = MSG_ReadShort( msg ); modelIndex = MSG_ReadShort( msg ); startFrame = MSG_ReadByte( msg ); frameRate = (float)MSG_ReadByte( msg ) * 0.1f; life = (float)MSG_ReadByte( msg ) * 0.1f; width = (float)MSG_ReadByte( msg ) * 0.1f; noise = (float)MSG_ReadByte( msg ) * 0.01f; r = (float)MSG_ReadByte( msg ) / 255.0f; g = (float)MSG_ReadByte( msg ) / 255.0f; b = (float)MSG_ReadByte( msg ) / 255.0f; a = (float)MSG_ReadByte( msg ) / 255.0f; speed = (float)MSG_ReadByte( msg ) * 0.1f; R_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); break; case TE_BEAMHOSE: break; case TE_KILLBEAM: startEnt = MSG_ReadShort( msg ); R_BeamKill( startEnt ); break; } } /* ============== R_BeamEnts Create beam between two ents ============== */ BEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) { cl_entity_t *start, *end; BEAM *pbeam; model_t *mod; mod = CL_ModelHandle( modelIndex ); // need a valid model. if( !mod || mod->type != mod_sprite ) return NULL; start = R_BeamGetEntity( startEnt ); end = R_BeamGetEntity( endEnt ); if( !start || !end ) return NULL; // don't start temporary beams out of the PVS if( life != 0 && ( !start->model || !end->model )) return NULL; pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed ); if( !pbeam ) return NULL; pbeam->type = TE_BEAMPOINTS; SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); pbeam->startEntity = startEnt; pbeam->endEntity = endEnt; R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); return pbeam; } /* ============== R_BeamPoints Create beam between two points ============== */ BEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) { BEAM *pbeam; if( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true )) return NULL; pbeam = R_BeamAlloc(); if( !pbeam ) return NULL; pbeam->die = cl.time; if( modelIndex < 0 ) return NULL; R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed ); if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); return pbeam; } /* ============== R_BeamCirclePoints Create beam cicrle ============== */ BEAM * GAME_EXPORT R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) { BEAM *pbeam = R_BeamLightning( start, end, modelIndex, life, width, amplitude, brightness, speed ); if( !pbeam ) return NULL; pbeam->type = type; if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); return pbeam; } /* ============== R_BeamEntPoint Create beam between entity and point ============== */ BEAM *GAME_EXPORT R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) { BEAM *pbeam; cl_entity_t *start; start = R_BeamGetEntity( startEnt ); if( !start ) return NULL; if( life == 0 && !start->model ) return NULL; pbeam = R_BeamAlloc(); if ( !pbeam ) return NULL; pbeam->die = cl.time; if( modelIndex < 0 ) return NULL; R_BeamSetup( pbeam, vec3_origin, end, modelIndex, life, width, amplitude, brightness, speed ); pbeam->type = TE_BEAMPOINTS; SetBits( pbeam->flags, FBEAM_STARTENTITY ); if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); pbeam->startEntity = startEnt; pbeam->endEntity = 0; R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); return pbeam; } /* ============== R_BeamRing Create beam between two ents ============== */ BEAM * GAME_EXPORT R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) { BEAM *pbeam; cl_entity_t *start, *end; start = R_BeamGetEntity( startEnt ); end = R_BeamGetEntity( endEnt ); if( !start || !end ) return NULL; if( life != 0 && ( !start->model || !end->model )) return NULL; pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed ); if( !pbeam ) return NULL; pbeam->type = TE_BEAMRING; SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); pbeam->startEntity = startEnt; pbeam->endEntity = endEnt; R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); return pbeam; } /* ============== R_BeamFollow Create beam following with entity ============== */ BEAM *GAME_EXPORT R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ) { BEAM *pbeam = R_BeamAlloc(); if( !pbeam ) return NULL; pbeam->die = cl.time; if( modelIndex < 0 ) return NULL; R_BeamSetup( pbeam, vec3_origin, vec3_origin, modelIndex, life, width, life, brightness, 1.0f ); pbeam->type = TE_BEAMFOLLOW; SetBits( pbeam->flags, FBEAM_STARTENTITY ); pbeam->startEntity = startEnt; R_BeamSetAttributes( pbeam, r, g, b, 1.0f, 0 ); return pbeam; } /* ============== R_BeamLightning template for new beams ============== */ BEAM *GAME_EXPORT R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) { BEAM *pbeam = R_BeamAlloc(); if( !pbeam ) return NULL; pbeam->die = cl.time; if( modelIndex < 0 ) return NULL; R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed ); return pbeam; } /* =============== R_EntityParticles set EF_BRIGHTFIELD effect =============== */ void GAME_EXPORT R_EntityParticles( cl_entity_t *ent ) { float angle; float sr, sp, sy, cr, cp, cy; vec3_t forward; particle_t *p; int i; for( i = 0; i < NUMVERTEXNORMALS; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; angle = cl.time * cl_avelocities[i][0]; SinCos( angle, &sy, &cy ); angle = cl.time * cl_avelocities[i][1]; SinCos( angle, &sp, &cp ); angle = cl.time * cl_avelocities[i][2]; SinCos( angle, &sr, &cr ); VectorSet( forward, cp * cy, cp * sy, -sp ); p->die = cl.time + 0.001f; p->color = 111; // yellow VectorMAMAM( 1.0f, ent->origin, 64.0f, m_bytenormals[i], 16.0f, forward, p->org ); } } /* =============== R_ParticleExplosion =============== */ void GAME_EXPORT R_ParticleExplosion( const vec3_t org ) { particle_t *p; int i, j; for( i = 0; i < 1024; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 5.0f; p->ramp = COM_RandomLong( 0, 3 ); p->color = ramp1[0]; for( j = 0; j < 3; j++ ) { p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); } if( i & 1 ) p->type = pt_explode; else p->type = pt_explode2; } } /* =============== R_ParticleExplosion2 =============== */ void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength ) { int i, j; int colorMod = 0, packedColor; particle_t *p; packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles for( i = 0; i < 512; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 0.3f; p->color = colorStart + ( colorMod % colorLength ); p->packedColor = packedColor; colorMod++; p->type = pt_blob; for( j = 0; j < 3; j++ ) { p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); } } } /* =============== R_BlobExplosion =============== */ void GAME_EXPORT R_BlobExplosion( const vec3_t org ) { particle_t *p; int i, j, packedColor; packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles for( i = 0; i < 1024; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + COM_RandomFloat( 1.0f, 1.4f ); p->packedColor = packedColor; if( i & 1 ) { p->type = pt_blob; p->color = COM_RandomLong( 66, 71 ); } else { p->type = pt_blob2; p->color = COM_RandomLong( 150, 155 ); } for( j = 0; j < 3; j++ ) { p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); } } } /* =============== ParticleEffect PARTICLE_EFFECT on server =============== */ void GAME_EXPORT R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count ) { particle_t *p; int i; if( count == 1024 ) { // rocket explosion R_ParticleExplosion( org ); return; } for( i = 0; i < count; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->color = (color & ~7) + COM_RandomLong( 0, 7 ); p->die = cl.time + COM_RandomFloat( 0.1f, 0.4f ); p->type = pt_slowgrav; VectorAddScalar( org, COM_RandomFloat( -8.0f, 8.0f ), p->org ); VectorScale( dir, 15.0f, p->vel ); } } /* =============== R_Blood particle spray =============== */ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed ) { vec3_t pos, dir, vec; float pspeed = speed * 3.0f; int i, j; particle_t *p; VectorNormalize2( ndir, dir ); for( i = 0; i < (speed / 2); i++ ) { VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos ); VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec ); for( j = 0; j < 7; j++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 1.5f; p->color = pcolor + COM_RandomLong( 0, 9 ); p->type = pt_vox_grav; VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org ); VectorScale( vec, pspeed, p->vel ); } } } /* =============== R_BloodStream particle spray 2 =============== */ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor, int speed ) { particle_t *p; int i, j; float arc; int accel = speed; // must be integer due to bug in GoldSrc vec3_t dir; VectorNormalize2( ndir, dir ); for( arc = 0.05f, i = 0; i < 100; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 2.0f; p->type = pt_vox_grav; p->color = pcolor + COM_RandomLong( 0, 9 ); VectorCopy( org, p->org ); VectorCopy( dir, p->vel ); p->vel[2] -= arc; arc -= 0.005f; VectorScale( p->vel, accel, p->vel ); accel -= 0.00001f; // so last few will drip } for( arc = 0.075f, i = 0; i < ( speed / 5 ); i++ ) { float num; p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 3.0f; p->color = pcolor + COM_RandomLong( 0, 9 ); p->type = pt_vox_slowgrav; VectorCopy( org, p->org ); VectorCopy( dir, p->vel ); p->vel[2] -= arc; arc -= 0.005f; num = COM_RandomFloat( 0.0f, 1.0f ); accel = speed * num; num *= 1.7f; VectorScale( p->vel, num, p->vel ); VectorScale( p->vel, accel, p->vel ); for( j = 0; j < 2; j++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 3.0f; p->color = pcolor + COM_RandomLong( 0, 9 ); p->type = pt_vox_slowgrav; p->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f ); p->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f ); p->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f ); VectorCopy( dir, p->vel ); p->vel[2] -= arc; VectorScale( p->vel, num, p->vel ); VectorScale( p->vel, accel, p->vel ); } } } /* =============== R_LavaSplash =============== */ void GAME_EXPORT R_LavaSplash( const vec3_t org ) { particle_t *p; float vel; vec3_t dir; int i, j, k; for( i = -16; i < 16; i++ ) { for( j = -16; j <16; j++ ) { for( k = 0; k < 1; k++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + COM_RandomFloat( 2.0f, 2.62f ); p->color = COM_RandomLong( 224, 231 ); p->type = pt_slowgrav; dir[0] = j * 8.0f + COM_RandomFloat( 0.0f, 7.0f ); dir[1] = i * 8.0f + COM_RandomFloat( 0.0f, 7.0f ); dir[2] = 256.0f; p->org[0] = org[0] + dir[0]; p->org[1] = org[1] + dir[1]; p->org[2] = org[2] + COM_RandomFloat( 0.0f, 63.0f ); VectorNormalize( dir ); vel = COM_RandomFloat( 50.0f, 113.0f ); VectorScale( dir, vel, p->vel ); } } } } /* =============== R_ParticleBurst =============== */ void GAME_EXPORT R_ParticleBurst( const vec3_t org, int size, int color, float life ) { particle_t *p; vec3_t dir, dest; int i, j; float dist; for( i = 0; i < 32; i++ ) { for( j = 0; j < 32; j++ ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + life + COM_RandomFloat( -0.5f, 0.5f ); p->color = color + COM_RandomLong( 0, 10 ); p->ramp = 1.0f; VectorCopy( org, p->org ); VectorAddScalar( org, COM_RandomFloat( -size, size ), dest ); VectorSubtract( dest, p->org, dir ); dist = VectorNormalizeLength( dir ); VectorScale( dir, ( dist / life ), p->vel ); } } } /* =============== R_LargeFunnel =============== */ void GAME_EXPORT R_LargeFunnel( const vec3_t org, int reverse ) { particle_t *p; float vel, dist; vec3_t dir, dest; int i, j; for( i = -8; i < 8; i++ ) { for( j = -8; j < 8; j++ ) { p = R_AllocParticle( NULL ); if( !p ) return; dest[0] = (i * 32.0f) + org[0]; dest[1] = (j * 32.0f) + org[1]; dest[2] = org[2] + COM_RandomFloat( 100.0f, 800.0f ); if( reverse ) { VectorCopy( org, p->org ); VectorSubtract( dest, p->org, dir ); } else { VectorCopy( dest, p->org ); VectorSubtract( org, p->org, dir ); } vel = dest[2] / 8.0f; if( vel < 64.0f ) vel = 64.0f; dist = VectorNormalizeLength( dir ); vel += COM_RandomFloat( 64.0f, 128.0f ); VectorScale( dir, vel, p->vel ); p->die = cl.time + (dist / vel ); p->color = 244; // green color } } } /* =============== R_TeleportSplash =============== */ void GAME_EXPORT R_TeleportSplash( const vec3_t org ) { particle_t *p; vec3_t dir; float vel; int i, j, k; for( i = -16; i < 16; i += 4 ) { for( j = -16; j < 16; j += 4 ) { for( k = -24; k < 32; k += 4 ) { p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + COM_RandomFloat( 0.2f, 0.34f ); p->color = COM_RandomLong( 7, 14 ); p->type = pt_slowgrav; dir[0] = j * 8.0f; dir[1] = i * 8.0f; dir[2] = k * 8.0f; p->org[0] = org[0] + i + COM_RandomFloat( 0.0f, 3.0f ); p->org[1] = org[1] + j + COM_RandomFloat( 0.0f, 3.0f ); p->org[2] = org[2] + k + COM_RandomFloat( 0.0f, 3.0f ); VectorNormalize( dir ); vel = COM_RandomFloat( 50.0f, 113.0f ); VectorScale( dir, vel, p->vel ); } } } } /* =============== R_RocketTrail =============== */ void GAME_EXPORT R_RocketTrail( vec3_t start, vec3_t end, int type ) { vec3_t vec, right, up; float len, dec; particle_t *p; VectorSubtract( end, start, vec ); len = VectorNormalizeLength( vec ); if( type == 7 ) { dec = 1.0f; VectorVectors( vec, right, up ); } else if( type < 128 ) { dec = 3.0f; } else { // initialize if type will be 7 here VectorVectors( vec, right, up ); dec = 1.0f; type -= 128; } VectorScale( vec, dec, vec ); while( len > 0 ) { p = R_AllocParticle( NULL ); if( !p ) return; len -= dec; p->die = cl.time + 2.0f; switch( type ) { case 0: case 1: p->ramp = COM_RandomLong( 0 + type * 2, 3 + type * 2 ); p->color = ramp3[(int)p->ramp]; p->type = pt_fire; VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); break; case 2: p->color = COM_RandomLong( 67, 74 ); p->type = pt_grav; VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); break; case 3: case 5: { static int tracercount; p->die = cl.time + 0.5f; p->color = ( tracercount & 4 ) * 2; if( type == 3 ) p->color += 52; else p->color += 230; VectorCopy( start, p->org ); tracercount++; p->vel[0] = 30.0f * vec[1]; p->vel[1] = 30.0f * vec[0]; p->vel[tracercount & 1] = -p->vel[tracercount & 1]; break; } case 4: p->color = COM_RandomLong( 67, 70 ); p->type = pt_grav; VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); len -= 3.0f; break; case 6: p->type = pt_fire; p->ramp = COM_RandomLong( 0, 3 ); p->color = ramp3[(int)p->ramp]; VectorCopy( start, p->org ); break; case 7: { float x = COM_RandomLong( 0, 65535 ); float y = COM_RandomLong( 8, 16 ); float s, c; SinCos( x, &s, &c ); s *= y; c *= y; VectorMAMAM( 1.0f, start, s, right, c, up, p->org ); VectorSubtract( start, p->org, p->vel ); VectorScale( p->vel, 2.0f, p->vel ); x = COM_RandomFloat( 96.0f, 111.0f ); VectorMA( p->vel, x, vec, p->vel ); p->ramp = COM_RandomLong( 0, 3 ); p->color = ramp3[(int)p->ramp]; p->type = pt_explode2; break; } default: VectorCopy( start, p->org ); break; } VectorAdd( start, vec, start ); } } /* =============== PM_ParticleLine draw line from particles ================ */ static void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel ) { float len, curdist; vec3_t diff, pos; // determine distance VectorSubtract( end, start, diff ); len = VectorNormalizeLength( diff ); curdist = 0; while( curdist <= len ) { VectorMA( start, curdist, diff, pos ); CL_Particle( pos, pcolor, life, 0, zvel ); curdist += 2.0f; } } /* ================ PM_DrawRectangle ================ */ static void PM_DrawRectangle( const vec3_t tl, const vec3_t bl, const vec3_t tr, const vec3_t br, int pcolor, float life ) { PM_ParticleLine( tl, bl, pcolor, life, 0 ); PM_ParticleLine( bl, br, pcolor, life, 0 ); PM_ParticleLine( br, tr, pcolor, life, 0 ); PM_ParticleLine( tr, tl, pcolor, life, 0 ); } /* ================ PM_DrawBBox ================ */ static void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life ) { vec3_t p[8], tmp; float gap = BOX_GAP; int i; for( i = 0; i < 8; i++ ) { tmp[0] = (i & 1) ? mins[0] - gap : maxs[0] + gap; tmp[1] = (i & 2) ? mins[1] - gap : maxs[1] + gap ; tmp[2] = (i & 4) ? mins[2] - gap : maxs[2] + gap ; VectorAdd( tmp, origin, tmp ); VectorCopy( tmp, p[i] ); } for( i = 0; i < 6; i++ ) { PM_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], pcolor, life ); } } /* ================ R_ParticleLine ================ */ void GAME_EXPORT R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life ) { int pcolor; pcolor = R_LookupColor( r, g, b ); PM_ParticleLine( start, end, pcolor, life, 0 ); } /* ================ R_ParticleBox ================ */ void GAME_EXPORT R_ParticleBox( const vec3_t absmin, const vec3_t absmax, byte r, byte g, byte b, float life ) { vec3_t mins, maxs; vec3_t origin; int pcolor; pcolor = R_LookupColor( r, g, b ); VectorAverage( absmax, absmin, origin ); VectorSubtract( absmax, origin, maxs ); VectorSubtract( absmin, origin, mins ); PM_DrawBBox( mins, maxs, origin, pcolor, life ); } /* ================ R_ShowLine ================ */ void GAME_EXPORT R_ShowLine( const vec3_t start, const vec3_t end ) { vec3_t dir, org; float len; particle_t *p; VectorSubtract( end, start, dir ); len = VectorNormalizeLength( dir ); VectorScale( dir, 5.0f, dir ); VectorCopy( start, org ); while( len > 0 ) { len -= 5.0f; p = R_AllocParticle( NULL ); if( !p ) return; p->die = cl.time + 30; p->color = 75; VectorCopy( org, p->org ); VectorAdd( org, dir, org ); } } /* =============== R_BulletImpactParticles =============== */ void GAME_EXPORT R_BulletImpactParticles( const vec3_t pos ) { int i, quantity; int color; float dist; vec3_t dir; particle_t *p; VectorSubtract( pos, refState.vieworg, dir ); dist = VectorLength( dir ); if( dist > 1000.0f ) dist = 1000.0f; quantity = (1000.0f - dist) / 100.0f; if( quantity == 0 ) quantity = 1; color = 3 - ((30 * quantity) / 100 ); R_SparkStreaks( pos, 2, -200, 200 ); for( i = 0; i < quantity * 4; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; VectorCopy( pos, p->org); p->vel[0] = COM_RandomFloat( -1.0f, 1.0f ); p->vel[1] = COM_RandomFloat( -1.0f, 1.0f ); p->vel[2] = COM_RandomFloat( -1.0f, 1.0f ); VectorScale( p->vel, COM_RandomFloat( 50.0f, 100.0f ), p->vel ); p->die = cl.time + 0.5; p->color = 3 - color; p->type = pt_grav; } } /* =============== R_FlickerParticles =============== */ void GAME_EXPORT R_FlickerParticles( const vec3_t org ) { particle_t *p; int i; for( i = 0; i < 15; i++ ) { p = R_AllocParticle( NULL ); if( !p ) return; VectorCopy( org, p->org ); p->vel[0] = COM_RandomFloat( -32.0f, 32.0f ); p->vel[1] = COM_RandomFloat( -32.0f, 32.0f ); p->vel[2] = COM_RandomFloat( 80.0f, 143.0f ); p->die = cl.time + 2.0f; p->type = pt_blob2; p->color = 254; } } /* =============== R_StreakSplash create a splash of streaks =============== */ void GAME_EXPORT R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velocityMin, int velocityMax ) { vec3_t vel, vel2; particle_t *p; int i; VectorScale( dir, speed, vel ); for( i = 0; i < count; i++ ) { VectorAddScalar( vel, COM_RandomFloat( velocityMin, velocityMax ), vel2 ); p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f )); if( !p ) return; p->type = pt_grav; p->color = color; p->ramp = 1.0f; } } /* =============== CL_Particle pmove debugging particle =============== */ void CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel ) { particle_t *p; p = R_AllocParticle( NULL ); if( !p ) return; if( org ) VectorCopy( org, p->org ); p->die = cl.time + life; p->vel[2] += zvel; // ??? p->color = color; } /* =============== R_TracerEffect =============== */ void GAME_EXPORT R_TracerEffect( const vec3_t start, const vec3_t end ) { vec3_t pos, vel, dir; float len, speed; float offset; speed = Q_max( tracerspeed.value, 3.0f ); VectorSubtract( end, start, dir ); len = VectorLength( dir ); if( len == 0.0f ) return; VectorScale( dir, 1.0f / len, dir ); // normalize offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value; VectorScale( dir, offset, vel ); VectorAdd( start, vel, pos ); VectorScale( dir, speed, vel ); R_AllocTracer( pos, vel, len / speed ); } /* =============== R_UserTracerParticle =============== */ void GAME_EXPORT R_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext, void (*deathfunc)( particle_t *p )) { particle_t *p; if( colorIndex < 0 ) return; if(( p = R_AllocTracer( org, vel, life )) != NULL ) { p->context = deathcontext; p->deathfunc = deathfunc; p->color = colorIndex; p->ramp = length; } } /* =============== R_TracerParticles allow more customization =============== */ particle_t *R_TracerParticles( float *org, float *vel, float life ) { return R_AllocTracer( org, vel, life ); } /* =============== R_SparkStreaks create a streak tracers =============== */ void GAME_EXPORT R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax ) { particle_t *p; vec3_t vel; int i; for( i = 0; icolor = 5; p->type = pt_grav; p->ramp = 0.5f; } } /* =============== R_Implosion make implosion tracers =============== */ void GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float life ) { float dist = ( radius / 100.0f ); vec3_t start, temp, vel; float factor; particle_t *p; int i; if( life <= 0.0f ) life = 0.1f; // to avoid divide by zero factor = -1.0 / life; for ( i = 0; i < count; i++ ) { temp[0] = dist * COM_RandomFloat( -100.0f, 100.0f ); temp[1] = dist * COM_RandomFloat( -100.0f, 100.0f ); temp[2] = dist * COM_RandomFloat( 0.0f, 100.0f ); VectorScale( temp, factor, vel ); VectorAdd( temp, end, start ); if(( p = R_AllocTracer( start, vel, life )) == NULL ) return; p->type = pt_explode; } } /* ============== R_FreeDeadParticles Free particles that time has expired ============== */ void R_FreeDeadParticles( particle_t **ppparticles ) { particle_t *p, *kill; // kill all the ones hanging direcly off the base pointer while( 1 ) { kill = *ppparticles; if( kill && kill->die < cl.time ) { if( kill->deathfunc ) kill->deathfunc( kill ); kill->deathfunc = NULL; *ppparticles = kill->next; kill->next = cl_free_particles; cl_free_particles = kill; continue; } break; } // kill off all the others for( p = *ppparticles; p; p = p->next ) { while( 1 ) { kill = p->next; if( kill && kill->die < cl.time ) { if( kill->deathfunc ) kill->deathfunc( kill ); kill->deathfunc = NULL; p->next = kill->next; kill->next = cl_free_particles; cl_free_particles = kill; continue; } break; } } } /* =============== CL_ReadPointFile_f =============== */ void CL_ReadPointFile_f( void ) { byte *afile; char *pfile; vec3_t org; int count; particle_t *p; char filename[64]; string token; Q_snprintf( filename, sizeof( filename ), "maps/%s.pts", clgame.mapname ); afile = FS_LoadFile( filename, NULL, false ); if( !afile ) { Con_Printf( S_ERROR "couldn't open %s\n", filename ); return; } Con_Printf( "Reading %s...\n", filename ); count = 0; pfile = (char *)afile; while( 1 ) { pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; org[0] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; org[1] = Q_atof( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); if( !pfile ) break; org[2] = Q_atof( token ); count++; if( !cl_free_particles ) { Con_Printf( S_ERROR "not enough free particles!\n" ); break; } // NOTE: can't use R_AllocParticle because this command // may be executed from the console, while frametime is 0 p = cl_free_particles; cl_free_particles = p->next; p->next = cl_active_particles; cl_active_particles = p; p->ramp = 0; p->type = pt_static; p->die = cl.time + 99999; p->color = (-count) & 15; VectorCopy( org, p->org ); VectorClear( p->vel ); } Mem_Free( afile ); if( count ) Con_Printf( "%i points read\n", count ); else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); } static void CL_FreeDeadBeams( void ) { BEAM *pBeam, *pNext, *pPrev = NULL; // draw temporary entity beams for( pBeam = cl_active_beams; pBeam; pBeam = pNext ) { // need to store the next one since we may delete this one pNext = pBeam->next; // retire old beams if( CL_BeamAttemptToDie( pBeam )) { // reset links if( pPrev ) pPrev->next = pNext; else cl_active_beams = pNext; // free the beam R_BeamFree( pBeam ); pBeam = NULL; continue; } pPrev = pBeam; } } void CL_DrawEFX( float time, qboolean fTrans ) { CL_FreeDeadBeams(); if( cl_draw_beams.value ) ref.dllFuncs.CL_DrawBeams( fTrans, cl_active_beams ); if( fTrans ) { R_FreeDeadParticles( &cl_active_particles ); if( cl_draw_particles.value ) ref.dllFuncs.CL_DrawParticles( time, cl_active_particles, PART_SIZE ); R_FreeDeadParticles( &cl_active_tracers ); if( cl_draw_tracers.value ) ref.dllFuncs.CL_DrawTracers( time, cl_active_tracers ); } } void CL_ThinkParticle( double frametime, particle_t *p ) { float time3 = 15.0f * frametime; float time2 = 10.0f * frametime; float time1 = 5.0f * frametime; float dvel = 4.0f * frametime; float grav = frametime * clgame.movevars.gravity * 0.05f; if( p->type != pt_clientcustom ) { // update position. VectorMA( p->org, frametime, p->vel, p->org ); } switch( p->type ) { case pt_static: break; case pt_fire: p->ramp += time1; if( p->ramp >= 6.0f ) p->die = -1.0f; else p->color = ramp3[(int)p->ramp]; p->vel[2] += grav; break; case pt_explode: p->ramp += time2; if( p->ramp >= 8.0f ) p->die = -1.0f; else p->color = ramp1[(int)p->ramp]; VectorMA( p->vel, dvel, p->vel, p->vel ); p->vel[2] -= grav; break; case pt_explode2: p->ramp += time3; if( p->ramp >= 8.0f ) p->die = -1.0f; else p->color = ramp2[(int)p->ramp]; VectorMA( p->vel,-frametime, p->vel, p->vel ); p->vel[2] -= grav; break; case pt_blob: if( p->packedColor == 255 ) { // normal blob explosion VectorMA( p->vel, dvel, p->vel, p->vel ); p->vel[2] -= grav; break; } // intentionally fallthrough case pt_blob2: if( p->packedColor == 255 ) { // normal blob explosion p->vel[0] -= p->vel[0] * dvel; p->vel[1] -= p->vel[1] * dvel; p->vel[2] -= grav; } else { p->ramp += time2; if( p->ramp >= 9.0f ) p->ramp = 0.0f; p->color = gSparkRamp[(int)p->ramp]; VectorMA( p->vel, -frametime * 0.5f, p->vel, p->vel ); p->type = COM_RandomLong( 0, 3 ) ? pt_blob : pt_blob2; p->vel[2] -= grav * 5.0f; } break; case pt_grav: p->vel[2] -= grav * 20.0f; break; case pt_slowgrav: p->vel[2] -= grav; break; case pt_vox_grav: p->vel[2] -= grav * 8.0f; break; case pt_vox_slowgrav: p->vel[2] -= grav * 4.0f; break; case pt_clientcustom: if( p->callback ) p->callback( p, frametime ); break; } } ================================================ FILE: engine/client/cl_events.c ================================================ /* cl_events.c - client-side event system implementation Copyright (C) 2011 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "event_flags.h" #include "net_encode.h" #include "con_nprint.h" /* =============== CL_ResetEvent =============== */ void CL_ResetEvent( event_info_t *ei ) { ei->index = 0; memset( &ei->args, 0, sizeof( ei->args )); ei->fire_time = 0.0; ei->flags = 0; } /* ============= CL_CalcPlayerVelocity compute velocity for a given client ============= */ static void CL_CalcPlayerVelocity( int idx, vec3_t velocity ) { clientdata_t *pcd; vec3_t delta; double dt; VectorClear( velocity ); if( idx <= 0 || idx > cl.maxclients ) return; if( idx == cl.playernum + 1 ) { pcd = &cl.frames[cl.parsecountmod].clientdata; VectorCopy( pcd->velocity, velocity ); } else { dt = clgame.entities[idx].curstate.animtime - clgame.entities[idx].prevstate.animtime; if( dt != 0.0 ) { VectorSubtract( clgame.entities[idx].curstate.velocity, clgame.entities[idx].prevstate.velocity, delta ); VectorScale( delta, 1.0f / dt, velocity ); } else { VectorCopy( clgame.entities[idx].curstate.velocity, velocity ); } } } /* ============= CL_DescribeEvent ============= */ static void CL_DescribeEvent( event_info_t *ei, int slot ) { int idx = (slot & 63) * 2; con_nprint_t info; string origin_str = { 0 }; //, angles_str = { 0 }; if( !cl_showevents.value ) return; info.time_to_live = 1.0f; info.index = idx; // mark reliable as green and unreliable as red if( FBitSet( ei->flags, FEV_RELIABLE )) VectorSet( info.color, 0.5f, 1.0f, 0.5f ); else VectorSet( info.color, 1.0f, 0.5f, 0.5f ); if( !VectorIsNull( ei->args.origin )) { Q_snprintf( origin_str, sizeof( origin_str ), "(%.2f,%.2f,%.2f)", ei->args.origin[0], ei->args.origin[1], ei->args.origin[2]); } /*if( !VectorIsNull( ei->args.angles )) { Q_snprintf( angles_str, sizeof( angles_str ), "ang %.2f %.2f %.2f", ei->args.angles[0], ei->args.angles[1], ei->args.angles[2]); }*/ Con_NXPrintf( &info, "%i %.2f %c %s %s", slot, cl.time, (FBitSet( ei->flags, FEV_CLIENT ) ? 'c' : FBitSet( ei->flags, FEV_SERVER ) ? 's' : '?'), cl.event_precache[ei->index], origin_str); info.index++; Con_NXPrintf( &info, "b(%i,%i) i(%i,%i) f(%.2f,%.2f)", ei->args.bparam1, ei->args.bparam2, ei->args.iparam1, ei->args.iparam2, ei->args.fparam1, ei->args.fparam2); } /* ============= CL_SetEventIndex ============= */ void CL_SetEventIndex( const char *szEvName, int ev_index ) { cl_user_event_t *ev; int i; if( !szEvName || !*szEvName ) return; // ignore blank names // search event by name to link with for( i = 0; i < MAX_EVENTS; i++ ) { ev = clgame.events[i]; if( !ev ) break; if( !Q_stricmp( ev->name, szEvName )) { ev->index = ev_index; return; } } } /* ============= CL_EventIndex ============= */ word CL_EventIndex( const char *name ) { word i; if( !COM_CheckString( name )) return 0; for( i = 1; i < MAX_EVENTS && cl.event_precache[i][0]; i++ ) { if( !Q_stricmp( cl.event_precache[i], name )) return i; } return 0; } /* ============= CL_RegisterEvent ============= */ void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func ) { cl_user_event_t *ev; if( lastnum == MAX_EVENTS ) return; // clear existing or allocate new one if( !clgame.events[lastnum] ) clgame.events[lastnum] = Mem_Calloc( cls.mempool, sizeof( cl_user_event_t )); else memset( clgame.events[lastnum], 0, sizeof( cl_user_event_t )); ev = clgame.events[lastnum]; // NOTE: ev->index will be set later Q_strncpy( ev->name, szEvName, sizeof( ev->name )); ev->func = func; } /* ============= CL_FireEvent ============= */ static qboolean CL_FireEvent( event_info_t *ei, int slot ) { cl_user_event_t *ev; const char *name; int i, idx; if( !ei || !ei->index ) return false; // get the func pointer for( i = 0; i < MAX_EVENTS; i++ ) { ev = clgame.events[i]; if( !ev ) { idx = bound( 1, ei->index, ( MAX_EVENTS - 1 )); Con_Reportf( S_ERROR "%s: %s not precached\n", __func__, cl.event_precache[idx] ); break; } if( ev->index == ei->index ) { name = cl.event_precache[ei->index]; if( cl_trace_events.value ) { Con_Printf( "^3EVENT %s AT %.2f %.2f %.2f\n" // event name "\t%.2f %.2f %i %i %s %s\n", // bool params name, ei->args.origin[0], ei->args.origin[1], ei->args.origin[2], ei->args.fparam1, ei->args.fparam2, ei->args.iparam1, ei->args.iparam2, ei->args.bparam1 ? "TRUE" : "FALSE", ei->args.bparam2 ? "TRUE" : "FALSE" ); } if( ev->func ) { CL_DescribeEvent( ei, slot ); ev->func( &ei->args ); return true; } Con_Reportf( S_ERROR "%s: %s not hooked\n", __func__, name ); break; } } return false; } /* ============= CL_FireEvents called right before draw frame ============= */ void CL_FireEvents( void ) { event_state_t *es; event_info_t *ei; int i; es = &cl.events; for( i = 0; i < MAX_EVENT_QUEUE; i++ ) { ei = &es->ei[i]; if( ei->index == 0 ) continue; // delayed event! if( ei->fire_time && ( ei->fire_time > cl.time )) continue; CL_FireEvent( ei, i ); // zero out the remaining fields CL_ResetEvent( ei ); } } /* ============= CL_FindEvent find first empty event ============= */ static event_info_t *CL_FindEmptyEvent( void ) { int i; event_state_t *es; event_info_t *ei; es = &cl.events; // look for first slot where index is != 0 for( i = 0; i < MAX_EVENT_QUEUE; i++ ) { ei = &es->ei[i]; if( ei->index != 0 ) continue; return ei; } // no slots available return NULL; } /* ============= CL_FindEvent replace only unreliable events ============= */ static event_info_t *CL_FindUnreliableEvent( void ) { event_state_t *es; event_info_t *ei; int i; es = &cl.events; for ( i = 0; i < MAX_EVENT_QUEUE; i++ ) { ei = &es->ei[i]; if( ei->index != 0 ) { // it's reliable, so skip it if( FBitSet( ei->flags, FEV_RELIABLE )) continue; } return ei; } // this should never happen return NULL; } /* ============= CL_QueueEvent ============= */ static void CL_QueueEvent( int flags, int index, float delay, event_args_t *args ) { event_info_t *ei; // find a normal slot ei = CL_FindEmptyEvent(); if( !ei ) { if( FBitSet( flags, FEV_RELIABLE )) { ei = CL_FindUnreliableEvent(); } if( !ei ) return; } ei->index = index; ei->packet_index = 0; ei->fire_time = delay ? (cl.time + delay) : 0.0f; ei->flags = flags; ei->args = *args; } /* ============= CL_ParseReliableEvent ============= */ void CL_ParseReliableEvent( sizebuf_t *msg, connprotocol_t proto ) { int event_index; event_args_t nullargs, args; float delay = 0.0f; memset( &nullargs, 0, sizeof( nullargs )); event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); // reliable events not use delta-compression just null-compression if( proto == PROTO_GOLDSRC ) { Delta_ReadGSFields( msg, DT_EVENT_T, &nullargs, &args, 0.0f ); if( MSG_ReadOneBit( msg )) delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); } else { if( MSG_ReadOneBit( msg )) delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); MSG_ReadDeltaEvent( msg, &nullargs, &args ); } if( args.entindex > 0 && args.entindex <= cl.maxclients ) { args.angles[PITCH] *= 3.0f; if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) args.angles[PITCH] = -args.angles[PITCH]; } CL_QueueEvent( FEV_RELIABLE|FEV_SERVER, event_index, delay, &args ); } /* ============= CL_ParseEvent ============= */ void CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto ) { int event_index; int i, num_events; int packet_index; const event_args_t nullargs = { 0 }; event_args_t args = { 0 }; entity_state_t *state; float delay; int entity_bits; num_events = MSG_ReadUBitLong( msg, 5 ); if( proto == PROTO_GOLDSRC ) entity_bits = MAX_GOLDSRC_ENTITY_BITS; else if( proto == PROTO_LEGACY ) entity_bits = MAX_LEGACY_ENTITY_BITS; else entity_bits = MAX_ENTITY_BITS; // parse events queue for( i = 0 ; i < num_events; i++ ) { event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); if( MSG_ReadOneBit( msg )) { packet_index = MSG_ReadUBitLong( msg, entity_bits ); if( MSG_ReadOneBit( msg )) { if( proto == PROTO_GOLDSRC ) Delta_ReadGSFields( msg, DT_EVENT_T, &nullargs, &args, 0.0f ); else MSG_ReadDeltaEvent( msg, &nullargs, &args ); } } else packet_index = -1; if( MSG_ReadOneBit( msg )) delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); else delay = 0.0f; if( packet_index != -1 ) { frame_t *frame = &cl.frames[cl.parsecountmod]; if( packet_index < frame->num_entities ) { state = &cls.packet_entities[(frame->first_entity+packet_index)%cls.num_client_entities]; args.entindex = state->number; if( VectorIsNull( args.origin )) VectorCopy( state->origin, args.origin ); if( VectorIsNull( args.angles )) VectorCopy( state->angles, args.angles ); COM_NormalizeAngles( args.angles ); if( state->number > 0 && state->number <= cl.maxclients ) { args.angles[PITCH] *= 3.0f; if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) args.angles[PITCH] = -args.angles[PITCH]; CL_CalcPlayerVelocity( state->number, args.velocity ); args.ducking = ( state->usehull == 1 ); } } else { if( args.entindex != 0 ) { if( args.entindex > 0 && args.entindex <= cl.maxclients ) { args.angles[PITCH] /= 3.0f; if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) args.angles[PITCH] = -args.angles[PITCH]; } } } // Place event on queue CL_QueueEvent( FEV_SERVER, event_index, delay, &args ); } } } /* ============= CL_PlaybackEvent ============= */ void GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) { event_args_t args; if( FBitSet( flags, FEV_SERVER )) return; // first check event for out of bounds if( eventindex < 1 || eventindex >= MAX_EVENTS ) { Con_DPrintf( S_ERROR "%s: invalid eventindex %i\n", __func__, eventindex ); return; } // check event for precached if( !CL_EventIndex( cl.event_precache[eventindex] )) { Con_DPrintf( S_ERROR "%s: event %i was not precached\n", __func__, eventindex ); return; } SetBits( flags, FEV_CLIENT ); // it's a client event ClearBits( flags, FEV_NOTHOST|FEV_HOSTONLY|FEV_GLOBAL ); if( delay < 0.0f ) delay = 0.0f; // fixup negative delays memset( &args, 0, sizeof( args )); VectorCopy( origin, args.origin ); VectorCopy( angles, args.angles ); VectorCopy( cl.simvel, args.velocity ); args.entindex = cl.playernum + 1; args.ducking = ( cl.local.usehull == 1 ); args.fparam1 = fparam1; args.fparam2 = fparam2; args.iparam1 = iparam1; args.iparam2 = iparam2; args.bparam1 = bparam1; args.bparam2 = bparam2; CL_QueueEvent( flags, eventindex, delay, &args ); } ================================================ FILE: engine/client/cl_font.c ================================================ /* cl_font.c - bare bones engine font manager Copyright (C) 2023 Alibek Omarov This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "filesystem.h" #include "client.h" #include "qfont.h" qboolean CL_FixedFont( cl_font_t *font ) { return font && font->valid && font->type == FONT_FIXED; } static int CL_LoadFontTexture( const char *fontname, uint texFlags, int *width ) { int font_width; int tex; if( !g_fsapi.FileExists( fontname, false )) return 0; tex = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, texFlags ); if( !tex ) return 0; font_width = REF_GET_PARM( PARM_TEX_WIDTH, tex ); if( !font_width ) { ref.dllFuncs.GL_FreeTexture( tex ); return 0; } *width = font_width; return tex; } static int CL_FontRenderMode( convar_t *fontrender ) { switch((int)fontrender->value ) { case 0: return kRenderTransAdd; case 1: return kRenderTransAlpha; case 2: return kRenderTransTexture; default: Cvar_DirectSet( fontrender, fontrender->def_string ); } return kRenderTransTexture; } void CL_SetFontRendermode( cl_font_t *font ) { ref.dllFuncs.GL_SetRenderMode( CL_FontRenderMode( font->rendermode )); } qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags ) { int font_width, i; if( !rendermode ) return false; if( font->valid ) return true; // already loaded font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width ); if( !font->hFontTexture ) return false; font->type = FONT_FIXED; font->valid = true; font->scale = scale; font->rendermode = rendermode; font->charHeight = Q_rint( font_width / 16 * scale ); for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ ) { font->fontRc[i].left = ( i * font_width / 16 ) % font_width; font->fontRc[i].right = font->fontRc[i].left + font_width / 16; font->fontRc[i].top = ( i / 16 ) * ( font_width / 16 ); font->fontRc[i].bottom = font->fontRc[i].top + font_width / 16; font->charWidths[i] = Q_rint( font_width / 16 * scale ); } return true; } qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags ) { fs_offset_t length; qfont_t src; byte *pfile; int font_width, i; if( !rendermode ) return false; if( font->valid ) return true; pfile = g_fsapi.LoadFile( fontname, &length, false ); if( !pfile ) return false; if( length < sizeof( src )) { Mem_Free( pfile ); return false; } memcpy( &src, pfile, sizeof( src )); Mem_Free( pfile ); font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width ); if( !font->hFontTexture ) return false; font->type = FONT_VARIABLE; font->valid = true; font->scale = scale ? scale : 1.0f; font->rendermode = rendermode; font->charHeight = Q_rint( src.rowheight * scale ); for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ ) { const charinfo *ci = &src.fontinfo[i]; font->fontRc[i].left = (word)ci->startoffset % font_width; font->fontRc[i].right = font->fontRc[i].left + ci->charwidth; font->fontRc[i].top = (word)ci->startoffset / font_width; font->fontRc[i].bottom = font->fontRc[i].top + src.rowheight; font->charWidths[i] = Q_rint( src.fontinfo[i].charwidth * scale ); } return true; } void CL_FreeFont( cl_font_t *font ) { if( !font || !font->valid ) return; ref.dllFuncs.GL_FreeTexture( font->hFontTexture ); memset( font, 0, sizeof( *font )); } static int CL_CalcTabStop( const cl_font_t *font, int x ) { int space = font->charWidths[' ']; int tab = space * 6; // 6 spaces int stop = tab - x % tab; if( stop < space ) return tab * 2 - x % tab; // select next return stop; } int CL_DrawCharacter( float x, float y, int number, const rgba_t color, cl_font_t *font, int flags ) { wrect_t *rc; float w, h; float s1, t1, s2, t2, half = 0.5f; int texw, texh; if( !font || !font->valid || y < -font->charHeight ) return 0; // check if printable if( number <= 32 ) { if( number == ' ' ) return font->charWidths[' ']; else if( number == '\t' ) return CL_CalcTabStop( font, x ); return 0; } if( FBitSet( flags, FONT_DRAW_UTF8 )) number = Con_UtfProcessChar( number & 255 ); else number &= 255; if( !number || !font->charWidths[number]) return 0; R_GetTextureParms( &texw, &texh, font->hFontTexture ); if( !texw || !texh ) return font->charWidths[number]; rc = &font->fontRc[number]; if( font->scale <= 1.f || !REF_GET_PARM( PARM_TEX_FILTERING, font->hFontTexture )) half = 0; s1 = ((float)rc->left + half ) / texw; t1 = ((float)rc->top + half ) / texh; s2 = ((float)rc->right - half ) / texw; t2 = ((float)rc->bottom - half ) / texh; w = ( rc->right - rc->left ) * font->scale; h = ( rc->bottom - rc->top ) * font->scale; if( FBitSet( flags, FONT_DRAW_HUD )) SPR_AdjustSize( &x, &y, &w, &h ); if( !FBitSet( flags, FONT_DRAW_NORENDERMODE )) CL_SetFontRendermode( font ); // don't apply color to fixed fonts it's already colored if( font->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, font->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8 ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] ); else ref.dllFuncs.Color4ub( 255, 255, 255, color[3] ); ref.dllFuncs.R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, font->hFontTexture ); return font->charWidths[number]; } int CL_DrawString( float x, float y, const char *s, const rgba_t color, cl_font_t *font, int flags ) { rgba_t current_color; int draw_len = 0; if( !font || !font->valid ) return 0; if( FBitSet( flags, FONT_DRAW_UTF8 )) Con_UtfProcessChar( 0 ); // clear utf state if( !FBitSet( flags, FONT_DRAW_NORENDERMODE )) CL_SetFontRendermode( font ); Vector4Copy( color, current_color ); while( *s ) { if( *s == '\n' ) { s++; if( !*s ) break; // some client functions ignore newlines if( !FBitSet( flags, FONT_DRAW_NOLF )) { draw_len = 0; y += font->charHeight; } if( FBitSet( flags, FONT_DRAW_RESETCOLORONLF )) Vector4Copy( color, current_color ); continue; } if( IsColorString( s )) { // don't copy alpha if( !FBitSet( flags, FONT_DRAW_FORCECOL )) VectorCopy( g_color_table[ColorIndex(*( s + 1 ))], current_color ); s += 2; continue; } // skip setting rendermode, it was changed for this string already draw_len += CL_DrawCharacter( x + draw_len, y, (byte)*s, current_color, font, flags | FONT_DRAW_NORENDERMODE ); s++; } return draw_len; } int CL_DrawStringf( cl_font_t *font, float x, float y, const rgba_t color, int flags, const char *fmt, ... ) { va_list va; char buf[MAX_VA_STRING]; va_start( va, fmt ); Q_vsnprintf( buf, sizeof( buf ), fmt, va ); va_end( va ); return CL_DrawString( x, y, buf, color, font, flags ); } void CL_DrawCharacterLen( cl_font_t *font, int number, int *width, int *height ) { if( !font || !font->valid ) return; if( width ) { if( number == '\t' ) *width = CL_CalcTabStop( font, 0 ); // at least return max tabstop else *width = font->charWidths[number & 255]; } if( height ) *height = font->charHeight; } void CL_DrawStringLen( cl_font_t *font, const char *s, int *width, int *height, int flags ) { int draw_len = 0; if( !font || !font->valid ) return; if( height ) *height = font->charHeight; if( width ) *width = 0; if( !COM_CheckString( s )) return; if( FBitSet( flags, FONT_DRAW_UTF8 )) Con_UtfProcessChar( 0 ); // reset utf state while( *s ) { int number; if( *s == '\n' ) { // BUG: no check for end string here // but high chances somebody's relying on this s++; draw_len = 0; if( !FBitSet( flags, FONT_DRAW_NOLF )) { if( height ) *height += font->charHeight; } continue; } else if( *s == '\t' ) { draw_len += CL_CalcTabStop( font, 0 ); // at least return max tabstop s++; continue; } if( IsColorString( s )) { s += 2; continue; } if( FBitSet( flags, FONT_DRAW_UTF8 )) number = Con_UtfProcessChar( (byte)*s ); else number = (byte)*s; if( number ) { draw_len += font->charWidths[number]; if( width ) { if( draw_len > *width ) *width = draw_len; } } s++; } } ================================================ FILE: engine/client/cl_frame.c ================================================ /* cl_frame.c - client world snapshot Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "net_encode.h" #include "entity_types.h" #include "pm_local.h" #include "cl_tent.h" #include "studio.h" #include "dlight.h" #include "sound.h" #include "input.h" // #define STUDIO_INTERPOLATION_FIX /* ========================================================================= FRAME INTERPOLATION ========================================================================= */ /* ================== CL_UpdatePositions Store another position into interpolation circular buffer ================== */ static void CL_UpdatePositions( cl_entity_t *ent ) { position_history_t *ph, *prev; prev = &ent->ph[ent->current_position]; ent->current_position = (ent->current_position + 1) & HISTORY_MASK; ph = &ent->ph[ent->current_position]; VectorCopy( ent->curstate.origin, ph->origin ); VectorCopy( ent->curstate.angles, ph->angles ); ph->animtime = ent->curstate.animtime; // a1ba: for some reason, this sometimes still may happen // at this time, I'm not sure whether this bug happens in delta readwrite code // or server just decides to go backwards and really sends these values if( ph->animtime < prev->animtime ) { // try to deduce real animtime by looking up the difference between // server messages (cl.mtime is never modified ny the interpolation code) float diff = Q_max( 0, ent->curstate.msg_time - ent->prevstate.msg_time ); ph->animtime = prev->animtime + diff; } } /* ================== CL_ResetPositions Interpolation init or reset after teleporting ================== */ static void CL_ResetPositions( cl_entity_t *ent ) { position_history_t store; if( !ent ) return; store = ent->ph[ent->current_position]; ent->current_position = 1; memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX ); ent->ph[1] = ent->ph[0] = store; } /* ================== CL_EntityTeleported check for instant movement in case we don't want interpolate this ================== */ static qboolean CL_EntityTeleported( cl_entity_t *ent ) { float len, maxlen; vec3_t delta; VectorSubtract( ent->curstate.origin, ent->prevstate.origin, delta ); // compute potential max movement in units per frame and compare with entity movement maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS )); len = VectorLength( delta ); return (len > maxlen); } /* ================== CL_CompareTimestamps round-off floating errors ================== */ static qboolean CL_CompareTimestamps( float t1, float t2 ) { int iTime1 = t1 * 1000; int iTime2 = t2 * 1000; return (( iTime1 - iTime2 ) <= 1 ); } /* ================== CL_EntityIgnoreLerp some ents will be ignore lerping ================== */ static qboolean CL_EntityIgnoreLerp( cl_entity_t *e ) { if( cl_nointerp.value > 0.f ) return true; if( e->model && e->model->type == mod_alias ) return false; return (e->curstate.movetype == MOVETYPE_NONE) ? true : false; } /* ================== CL_EntityCustomLerp ================== */ static qboolean CL_EntityCustomLerp( cl_entity_t *e ) { switch( e->curstate.movetype ) { case MOVETYPE_NONE: case MOVETYPE_STEP: case MOVETYPE_WALK: case MOVETYPE_FLY: case MOVETYPE_COMPOUND: return false; // ABSOLUTELY STUPID HACK TO ALLOW MONSTERS // INTERPOLATION IN GRAVGUNMOD COOP // MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT case MOVETYPE_TOSS: if( cls.legacymode == PROTO_LEGACY && e->model && e->model->type == mod_studio ) return false; } return true; } /* ================== CL_ParametricMove check for parametrical moved entities ================== */ static qboolean CL_ParametricMove( cl_entity_t *ent ) { float frac, dt, t; vec3_t delta; if( ent->curstate.starttime == 0.0f || ent->curstate.impacttime == 0.0f ) return false; VectorSubtract( ent->curstate.endpos, ent->curstate.startpos, delta ); dt = ent->curstate.impacttime - ent->curstate.starttime; if( dt != 0.0f ) { if( ent->lastmove > cl.time ) t = ent->lastmove; else t = cl.time; frac = ( t - ent->curstate.starttime ) / dt; frac = bound( 0.0f, frac, 1.0f ); VectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin ); ent->lastmove = t; } VectorNormalize( delta ); if( VectorLength( delta ) > 0.0f ) VectorAngles( delta, ent->curstate.angles ); // re-aim projectile return true; } /* ==================== CL_UpdateLatchedVars ==================== */ static void CL_UpdateLatchedVars( cl_entity_t *ent ) { if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio )) return; // below fields used only for alias and studio interpolation VectorCopy( ent->prevstate.origin, ent->latched.prevorigin ); VectorCopy( ent->prevstate.angles, ent->latched.prevangles ); if( ent->model->type == mod_alias ) ent->latched.prevframe = ent->prevstate.frame; ent->latched.prevanimtime = ent->prevstate.animtime; if( ent->curstate.sequence != ent->prevstate.sequence ) { memcpy( ent->latched.prevseqblending, ent->prevstate.blending, sizeof( ent->latched.prevseqblending )); ent->latched.prevsequence = ent->prevstate.sequence; ent->latched.sequencetime = ent->curstate.animtime; } memcpy( ent->latched.prevcontroller, ent->prevstate.controller, sizeof( ent->latched.prevcontroller )); memcpy( ent->latched.prevblending, ent->prevstate.blending, sizeof( ent->latched.prevblending )); // update custom latched vars if( clgame.drawFuncs.CL_UpdateLatchedVars != NULL ) clgame.drawFuncs.CL_UpdateLatchedVars( ent, false ); } /* ==================== CL_GetStudioEstimatedFrame ==================== */ static float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) { studiohdr_t *pstudiohdr; mstudioseqdesc_t *pseqdesc; int sequence; if( ent->model != NULL && ent->model->type == mod_studio ) { pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model ); if( pstudiohdr && pstudiohdr->numseq > 0 ) { sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 ); pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time ); } } return 0; } /* ==================== CL_ResetLatchedVars ==================== */ void CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset ) { if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio )) return; // below fields used only for alias and studio interpolation if( full_reset ) { // don't modify for sprites to avoid broke sprite interp memcpy( ent->latched.prevblending, ent->curstate.blending, sizeof( ent->latched.prevblending )); ent->latched.sequencetime = ent->curstate.animtime; memcpy( ent->latched.prevcontroller, ent->curstate.controller, sizeof( ent->latched.prevcontroller )); if( ent->model->type == mod_studio ) ent->latched.prevframe = CL_GetStudioEstimatedFrame( ent ); else if( ent->model->type == mod_alias ) ent->latched.prevframe = ent->curstate.frame; ent->prevstate = ent->curstate; } ent->latched.prevanimtime = ent->curstate.animtime = cl.mtime[0]; VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); VectorCopy( ent->curstate.angles, ent->latched.prevangles ); ent->latched.prevsequence = ent->curstate.sequence; // update custom latched vars if( clgame.drawFuncs.CL_UpdateLatchedVars != NULL ) clgame.drawFuncs.CL_UpdateLatchedVars( ent, true ); } /* ================== CL_ProcessEntityUpdate apply changes since new frame received ================== */ static void CL_ProcessEntityUpdate( cl_entity_t *ent ) { qboolean parametric; ent->model = CL_ModelHandle( ent->curstate.modelindex ); ent->index = ent->curstate.number; if( FBitSet( ent->curstate.entityType, ENTITY_NORMAL )) COM_NormalizeAngles( ent->curstate.angles ); parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f; // allow interpolation on bmodels too if( ent->model && ent->model->type == mod_brush ) ent->curstate.animtime = ent->curstate.msg_time; if( CL_EntityCustomLerp( ent ) && !parametric ) ent->curstate.animtime = ent->curstate.msg_time; if( !CL_CompareTimestamps( ent->curstate.animtime, ent->prevstate.animtime ) || CL_EntityIgnoreLerp( ent )) { CL_UpdateLatchedVars( ent ); CL_UpdatePositions( ent ); } // g-cont. it should be done for all the players? if( ent->player && !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) ent->curstate.angles[PITCH] /= -3.0f; VectorCopy( ent->curstate.origin, ent->origin ); VectorCopy( ent->curstate.angles, ent->angles ); // initialize attachments for now VectorCopy( ent->origin, ent->attachment[0] ); VectorCopy( ent->origin, ent->attachment[1] ); VectorCopy( ent->origin, ent->attachment[2] ); VectorCopy( ent->origin, ent->attachment[3] ); } /* ================== CL_FindInterpolationUpdates find two timestamps ================== */ static qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, double targettime, position_history_t **ph0, position_history_t **ph1 ) { qboolean extrapolate = true; uint i, i0, i1, imod; imod = ent->current_position; i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end) i1 = (imod - 1) & HISTORY_MASK; // oldpos (lerp start) for( i = 1; i < HISTORY_MAX - 1; i++ ) { double at = ent->ph[( imod - i ) & HISTORY_MASK].animtime; if( at == 0.0f ) break; if( targettime > at ) { // found it i0 = (( imod - i ) + 1 ) & HISTORY_MASK; i1 = (( imod - i ) + 0 ) & HISTORY_MASK; extrapolate = false; break; } } *ph0 = &ent->ph[i0]; *ph1 = &ent->ph[i1]; return extrapolate; } /* ================== CL_PureOrigin non-local players interpolation ================== */ static void CL_PureOrigin( cl_entity_t *ent, double t, vec3_t outorigin, vec3_t outangles ) { double t1, t0, frac; position_history_t *ph0, *ph1; vec3_t delta; // NOTE: ph0 is next, ph1 is a prev CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 ); t0 = ph0->animtime; t1 = ph1->animtime; if( t0 != 0.0 ) { vec4_t q, q1, q2; VectorSubtract( ph0->origin, ph1->origin, delta ); if( !Q_equal( t0, t1 )) frac = ( t - t1 ) / ( t0 - t1 ); else frac = 1.0; frac = bound( 0.0, frac, 1.2 ); VectorMA( ph1->origin, frac, delta, outorigin ); AngleQuaternion( ph0->angles, q1, false ); AngleQuaternion( ph1->angles, q2, false ); QuaternionSlerp( q2, q1, frac, q ); QuaternionAngle( q, outangles ); } else { // no backup found VectorCopy( ph1->origin, outorigin ); VectorCopy( ph1->angles, outangles ); } } /* ================== CL_InterpolateModel non-players interpolation ================== */ static int CL_InterpolateModel( cl_entity_t *e ) { position_history_t *ph0 = NULL, *ph1 = NULL; vec3_t origin, angles, delta; double t, t1, t2, frac; vec4_t q, q1, q2; VectorCopy( e->curstate.origin, e->origin ); VectorCopy( e->curstate.angles, e->angles ); if( cls.timedemo || !e->model ) return 1; if( cls.demoplayback == DEMO_QUAKE1 ) { // quake lerping is easy VectorLerp( e->prevstate.origin, cl.lerpFrac, e->curstate.origin, e->origin ); AngleQuaternion( e->prevstate.angles, q1, false ); AngleQuaternion( e->curstate.angles, q2, false ); QuaternionSlerp( q1, q2, cl.lerpFrac, q ); QuaternionAngle( q, e->angles ); return 1; } if( cl.maxclients <= 1 ) return 1; if( e->model->type == mod_brush && !cl_bmodelinterp.value ) return 1; if( cl.local.moving && cl.local.onground == e->index ) return 1; t = cl.time - cl_interp.value; CL_FindInterpolationUpdates( e, t, &ph0, &ph1 ); t1 = ph1->animtime; t2 = ph0->animtime; if( t - t1 < 0.0f ) return 0; if( t1 == 0.0f ) { VectorCopy( ph0->origin, e->origin ); VectorCopy( ph0->angles, e->angles ); return 0; } if( Q_equal( t2, t1 )) { VectorCopy( ph0->origin, e->origin ); VectorCopy( ph0->angles, e->angles ); return 1; } VectorSubtract( ph0->origin, ph1->origin, delta ); frac = (t - t1) / (t2 - t1); if( frac < 0.0f ) return 0; if( frac > 1.0f ) frac = 1.0f; VectorMA( ph1->origin, frac, delta, origin ); AngleQuaternion( ph0->angles, q1, false ); AngleQuaternion( ph1->angles, q2, false ); QuaternionSlerp( q2, q1, frac, q ); QuaternionAngle( q, angles ); VectorCopy( origin, e->origin ); VectorCopy( angles, e->angles ); return 1; } /* ============= CL_ComputePlayerOrigin interpolate non-local clients ============= */ void CL_ComputePlayerOrigin( cl_entity_t *ent ) { double targettime; vec4_t q, q1, q2; vec3_t origin; vec3_t angles; if( !ent->player ) return; if( cl_nointerp.value > 0.f ) { VectorCopy( ent->curstate.angles, ent->angles ); VectorCopy( ent->curstate.origin, ent->origin ); return; } if( cls.demoplayback == DEMO_QUAKE1 ) { // quake lerping is easy VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, ent->origin ); AngleQuaternion( ent->prevstate.angles, q1, false ); AngleQuaternion( ent->curstate.angles, q2, false ); QuaternionSlerp( q1, q2, cl.lerpFrac, q ); QuaternionAngle( q, ent->angles ); return; } targettime = cl.time - cl_interp.value; CL_PureOrigin( ent, targettime, origin, angles ); VectorCopy( angles, ent->angles ); VectorCopy( origin, ent->origin ); } /* ================= CL_ProcessPlayerState process player states after the new packet has received ================= */ static void CL_ProcessPlayerState( int playerindex, entity_state_t *state ) { entity_state_t *ps; ps = &cl.frames[cl.parsecountmod].playerstate[playerindex]; ps->number = state->number; ps->messagenum = cl.parsecount; ps->msg_time = cl.mtime[0]; clgame.dllFuncs.pfnProcessPlayerState( ps, state ); } /* ================= CL_ResetLatchedState reset latched state if this frame entity was teleported or just EF_NOINTERP was set ================= */ static void CL_ResetLatchedState( int pnum, frame_t *frame, cl_entity_t *ent ) { if( CHECKVISBIT( frame->flags, pnum )) { VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); VectorCopy( ent->curstate.angles, ent->latched.prevangles ); CL_ResetLatchedVars( ent, true ); CL_ResetPositions( ent ); // parametric interpolation will starts at this point if( ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f ) ent->lastmove = cl.time; } } /* ================= CL_ProcessPacket process player states after the new packet has received ================= */ void CL_ProcessPacket( frame_t *frame ) { entity_state_t *state; cl_entity_t *ent; int pnum; for( pnum = 0; pnum < frame->num_entities; pnum++ ) { // request the entity state from circular buffer state = &cls.packet_entities[(frame->first_entity+pnum) % cls.num_client_entities]; state->messagenum = cl.parsecount; state->msg_time = cl.mtime[0]; // mark all the players ent = &clgame.entities[state->number]; ent->player = CL_IsPlayerIndex( state->number ); if( state->number == ( cl.playernum + 1 )) clgame.dllFuncs.pfnTxferLocalOverrides( state, &frame->clientdata ); // shuffle states ent->prevstate = ent->curstate; ent->curstate = *state; CL_ProcessEntityUpdate( ent ); CL_ResetLatchedState( pnum, frame, ent ); if( !ent->player ) continue; CL_ProcessPlayerState(( state->number - 1 ), state ); if( state->number == ( cl.playernum + 1 )) CL_CheckPredictionError(); } } /* ========================================================================= FRAME PARSING ========================================================================= */ static qboolean CL_ParseEntityNumFromPacket( sizebuf_t *msg, int *newnum, connprotocol_t proto ) { if( proto == PROTO_LEGACY ) { *newnum = MSG_ReadWord( msg ); if( *newnum == 0 ) return false; } else { *newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); if( *newnum == LAST_EDICT ) return false; } return true; } /* ================= CL_FlushEntityPacket Read and ignore whole entity packet. ================= */ static void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto ) { int newnum; entity_state_t from, to; memset( &from, 0, sizeof( from )); cl.frames[cl.parsecountmod].valid = false; cl.validsequence = 0; // can't render a frame // read it all, but ignore it while( 1 ) { if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto )) break; // done if( MSG_CheckOverflow( msg )) Host_Error( "%s: overflow\n", __func__ ); MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ) ? DELTA_PLAYER : DELTA_ENTITY, cl.mtime[0] ); } } qboolean CL_ValidateDeltaPacket( uint oldpacket, frame_t *oldframe ) { int subtracted = ( cls.netchan.incoming_sequence - oldpacket ) & 0xFF; if( subtracted == 0 ) { Con_NPrintf( 2, "^3Warning:^1 update too old\n^7\n" ); return false; } if( subtracted >= CL_UPDATE_MASK ) { // we can't use this, it is too old Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); return false; } if(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - NUM_PACKET_ENTITIES )) { Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); return false; } return true; } int CL_UpdateOldEntNum( int oldindex, frame_t *oldframe, entity_state_t **oldent ) { if( !oldframe ) { *oldent = NULL; return MAX_ENTNUMBER; } if( oldindex >= oldframe->num_entities ) return MAX_ENTNUMBER; *oldent = &cls.packet_entities[(oldframe->first_entity + oldindex) % cls.num_client_entities]; return (*oldent)->number; } /* ================= CL_DeltaEntity processing delta update ================= */ static void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean has_update ) { cl_entity_t *ent; entity_state_t *state; qboolean newent = (old) ? false : true; int pack = frame->num_entities; int delta_type = DELTA_ENTITY; qboolean alive = true; // alloc next slot to store update state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities]; if( CL_IsPlayerIndex( newnum )) delta_type = DELTA_PLAYER; if(( newnum < 0 ) || ( newnum >= clgame.maxEntities )) { Con_DPrintf( S_ERROR "%s: invalid newnum: %d\n", __func__, newnum ); if( has_update ) MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] ); return; } ent = CL_EDICT_NUM( newnum ); ent->index = newnum; // enumerate entity index if( newent ) old = &ent->baseline; if( has_update ) alive = MSG_ReadDeltaEntity( msg, old, state, newnum, delta_type, cl.mtime[0] ); else *state = *old; if( !alive ) { CL_KillDeadBeams( ent ); // release dead beams #if 0 // this is for reference if( state->number == -1 ) Con_DPrintf( "Entity %i was removed from server\n", newnum ); else Con_Dprintf( "Entity %i was removed from delta-message\n", newnum ); #endif return; } if( newent ) { // interpolation must be reset SETVISBIT( frame->flags, pack ); // release beams from previous entity // a1ba: check that this entity number was never used on client // as beams can be transferred before this entity was sent to client // (for example, beam was sent over during beam entity spawn // but referenced start point entity hasn't been sent over due to PVS) if( ent->curstate.messagenum != 0 ) CL_KillDeadBeams( ent ); } // add entity to packet cls.next_client_entities++; frame->num_entities++; } /* ================== CL_ParsePacketEntities An svc_packetentities has just been parsed, deal with the rest of the data stream. ================== */ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto ) { frame_t *newframe, *oldframe; int oldindex, newnum, oldnum; int playerbytes = 0; int bufStart; entity_state_t *oldent; qboolean player; int count; // save first uncompressed packet as timestamp if( cls.changelevel && !delta && cls.demorecording ) CL_WriteDemoJumpTime(); // sentinel count. save it for debug checking if( proto == PROTO_LEGACY ) count = MSG_ReadWord( msg ); else count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1; newframe = &cl.frames[cl.parsecountmod]; // allocate parse entities memset( newframe->flags, 0, sizeof( newframe->flags )); newframe->first_entity = cls.next_client_entities; newframe->num_entities = 0; newframe->valid = true; // assume valid if( delta ) { uint oldpacket = MSG_ReadByte( msg ); oldframe = &cl.frames[oldpacket & CL_UPDATE_MASK]; if( !CL_ValidateDeltaPacket( oldpacket, oldframe )) { CL_FlushEntityPacket( msg, proto ); return playerbytes; } } else { // this is a full update that we can start delta compressing from now oldframe = NULL; cls.demowaiting = false; // we can start recording now } // mark current delta state cl.validsequence = cls.netchan.incoming_sequence; oldent = NULL; oldindex = 0; oldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent ); while( 1 ) { if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto )) break; // done if( MSG_CheckOverflow( msg )) Host_Error( "%s: overflow\n", __func__ ); player = CL_IsPlayerIndex( newnum ); while( oldnum < newnum ) { // one or more entities from the old packet are unchanged CL_DeltaEntity( msg, newframe, oldnum, oldent, false ); oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); } if( oldnum == newnum ) { // delta from previous state bufStart = MSG_GetNumBytesRead( msg ); CL_DeltaEntity( msg, newframe, newnum, oldent, true ); if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); continue; } if( oldnum > newnum ) { // delta from baseline ? bufStart = MSG_GetNumBytesRead( msg ); CL_DeltaEntity( msg, newframe, newnum, NULL, true ); if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; continue; } } // any remaining entities in the old frame are copied over while( oldnum != MAX_ENTNUMBER ) { // one or more entities from the old packet are unchanged CL_DeltaEntity( msg, newframe, oldnum, oldent, false ); oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); } if( newframe->num_entities != count && newframe->num_entities != 0 ) Con_Reportf( S_WARN "%s%s: (%i should be %i)\n", __func__, delta ? "Delta" : "", newframe->num_entities, count ); if( !newframe->valid ) return playerbytes; // frame is not valid but message was parsed // now process packet. CL_ProcessPacket( newframe ); // add new entities into physic lists CL_SetSolidEntities(); // first update is the final signon stage where we actually receive an entity (i.e., the world at least) if( cls.signon == ( SIGNONS - 1 )) { // we are done with signon sequence. cls.signon = SIGNONS; // Clear loading plaque. CL_SignonReply( proto ); } return playerbytes; } /* ========================================================================== INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS ========================================================================== */ /* ============= CL_AddVisibleEntity all the visible entities should pass this filter ============= */ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) { qboolean draw_player = true; if( !ent || !ent->model ) return false; // don't add the player in firstperson mode if( RP_LOCALCLIENT( ent )) { cl.local.apply_effects = true; if( !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) { // we don't draw player in default renderer in firstperson mode // but let the client.dll know about player entity anyway // for use in custom renderers draw_player = false; } } // check for adding this entity if( !clgame.dllFuncs.pfnAddEntity( entityType, ent, ent->model->name )) { // local player was reject by game code, so ignore any effects if( RP_LOCALCLIENT( ent )) cl.local.apply_effects = false; return false; } if( !draw_player ) return false; if( entityType == ET_BEAM ) { ref.dllFuncs.CL_AddCustomBeam( ent ); return true; } else if( !ref.dllFuncs.R_AddEntity( ent, entityType )) { return false; } // because pTemp->entity.curstate.effects // is already occupied by FTENT_FLICKER if( entityType != ET_TEMPENTITY && !RP_LOCALCLIENT( ent ) ) { // apply client-side effects CL_AddEntityEffects( ent ); // alias & studiomodel efefcts CL_AddModelEffects( ent ); } return true; } /* ============= CL_LinkCustomEntity Add server beam to draw list ============= */ static void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state ) { ent->curstate.movetype = state->modelindex; // !!! if( ent->model->type != mod_sprite ) Con_Reportf( S_WARN "bad model on beam ( %s )\n", ent->model->name ); ent->latched.prevsequence = ent->curstate.sequence; VectorCopy( ent->origin, ent->latched.prevorigin ); VectorCopy( ent->angles, ent->latched.prevangles ); ent->prevstate = ent->curstate; CL_AddVisibleEntity( ent, ET_BEAM ); } /* ============= CL_LinkPlayers Create visible entities in the correct position for all current players ============= */ static void CL_LinkPlayers( frame_t *frame ) { entity_state_t *state; cl_entity_t *ent; int i; ent = CL_GetLocalPlayer(); // apply muzzleflash to weaponmodel if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH )) SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH ); // check all the clients but add only visible for( i = 0, state = frame->playerstate; i < MAX_CLIENTS; i++, state++ ) { if( state->messagenum != cl.parsecount ) continue; // not present this frame if( !state->modelindex || FBitSet( state->effects, EF_NODRAW )) continue; ent = &clgame.entities[i + 1]; // fixup the player indexes... if( ent->index != ( i + 1 )) ent->index = (i + 1); if( i == cl.playernum ) { if( cls.demoplayback != DEMO_QUAKE1 ) { VectorCopy( state->origin, ent->origin ); VectorCopy( state->origin, ent->prevstate.origin ); VectorCopy( state->origin, ent->curstate.origin ); } VectorCopy( ent->curstate.angles, ent->angles ); } if( FBitSet( ent->curstate.effects, EF_NOINTERP )) CL_ResetLatchedVars( ent, false ); if( CL_EntityTeleported( ent )) { VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); VectorCopy( ent->curstate.angles, ent->latched.prevangles ); CL_ResetPositions( ent ); } if ( i == cl.playernum ) { // using interpolation only for local player angles CL_ComputePlayerOrigin( ent ); if( cls.demoplayback == DEMO_QUAKE1 ) VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, cl.simorg ); VectorCopy( cl.simorg, ent->origin ); } else { VectorCopy( ent->curstate.origin, ent->origin ); VectorCopy( ent->curstate.angles, ent->angles ); // interpolate non-local clients CL_ComputePlayerOrigin( ent ); } VectorCopy( ent->origin, ent->attachment[0] ); VectorCopy( ent->origin, ent->attachment[1] ); VectorCopy( ent->origin, ent->attachment[2] ); VectorCopy( ent->origin, ent->attachment[3] ); CL_AddVisibleEntity( ent, ET_PLAYER ); } // apply local player effects if entity is not added if( cl.local.apply_effects ) CL_AddEntityEffects( CL_GetLocalPlayer( )); } /* =============== CL_LinkPacketEntities =============== */ static void CL_LinkPacketEntities( frame_t *frame ) { cl_entity_t *ent; entity_state_t *state; qboolean parametric; qboolean interpolate; int i; for( i = 0; i < frame->num_entities; i++ ) { state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities]; // clients are should be done in CL_LinkPlayers if( state->number >= 1 && state->number <= cl.maxclients ) continue; // if set to invisible, skip if( !state->modelindex || FBitSet( state->effects, EF_NODRAW )) continue; ent = CL_GetEntityByIndex( state->number ); if( !ent ) { Con_Reportf( S_ERROR "%s: bad entity %i\n", __func__, state->number ); continue; } // animtime must keep an actual ent->curstate.animtime = state->animtime; ent->curstate.frame = state->frame; interpolate = false; if( !ent->model ) continue; if( ent->curstate.rendermode == kRenderNormal ) { // auto 'solid' faces if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && Host_IsQuakeCompatible( )) { ent->curstate.rendermode = kRenderTransAlpha; ent->curstate.renderamt = 255; } } parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f ); if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND ) { if( ent->curstate.animtime == ent->prevstate.animtime && !VectorCompare( ent->curstate.origin, ent->prevstate.origin )) ent->lastmove = cl.time + 0.2; if( FBitSet( ent->curstate.eflags, EFLAG_SLERP )) { if( ent->curstate.animtime != 0.0f && ( ent->model->type == mod_alias || ent->model->type == mod_studio )) { #ifdef STUDIO_INTERPOLATION_FIX if( ent->lastmove >= cl.time ) VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) interpolate = true; else ent->curstate.movetype = MOVETYPE_STEP; #else if( ent->lastmove >= cl.time ) { float at = ent->curstate.animtime; CL_ResetLatchedVars( ent, true ); if( cl_fixmodelinterpolationartifacts.value ) ent->latched.prevanimtime = ent->curstate.animtime = at; VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); VectorCopy( ent->curstate.angles, ent->latched.prevangles ); if( !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) { // disable step interpolation in client.dll ent->curstate.movetype = MOVETYPE_NONE; } } else { if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) { interpolate = true; } else { // restore step interpolation in client.dll ent->curstate.movetype = MOVETYPE_STEP; } } #endif } } } if( ent->model->type == mod_brush ) { CL_InterpolateModel( ent ); } else { if( parametric ) { CL_ParametricMove( ent ); VectorCopy( ent->curstate.origin, ent->origin ); VectorCopy( ent->curstate.angles, ent->angles ); } else if( CL_EntityCustomLerp( ent )) { if ( !CL_InterpolateModel( ent )) continue; } // a1ba: in GoldSrc this is done for cstrike and czero // but let modders use this as an engine feature else if( FBitSet( host.features, ENGINE_STEP_POSHISTORY_LERP ) && ent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address )) { if( !CL_InterpolateModel( ent )) continue; } #if 0 // ABSOLUTELY STUPID HACK TO ALLOW MONSTERS // INTERPOLATION IN GRAVGUNMOD COOP // MUST BE REMOVED ONCE WE REMOVE 48 PROTO SUPPORT else if( cls.legacymode == PROTO_LEGACY && ent->model->type == mod_studio && ent->curstate.movetype == MOVETYPE_TOSS ) { if( !CL_InterpolateModel( ent )) continue; } #endif else { // no interpolation right now VectorCopy( ent->curstate.origin, ent->origin ); VectorCopy( ent->curstate.angles, ent->angles ); } if( ent->model->type == mod_studio ) { if( interpolate && FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) ref.dllFuncs.R_StudioLerpMovement( ent, cl.time, ent->origin, ent->angles ); } } if( !FBitSet( state->entityType, ENTITY_NORMAL )) { CL_LinkCustomEntity( ent, state ); continue; } if( ent->model->type != mod_brush ) { // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug if( !ent->curstate.rendercolor.r && !ent->curstate.rendercolor.g && !ent->curstate.rendercolor.b ) ent->curstate.rendercolor.r = ent->curstate.rendercolor.g = ent->curstate.rendercolor.b = 255; } // XASH SPECIFIC if( ent->curstate.rendermode == kRenderNormal && ent->curstate.renderfx == kRenderFxNone ) ent->curstate.renderamt = 255.0f; if( ent->curstate.aiment != 0 && ent->curstate.movetype != MOVETYPE_COMPOUND ) ent->curstate.movetype = MOVETYPE_FOLLOW; if( FBitSet( ent->curstate.effects, EF_NOINTERP )) CL_ResetLatchedVars( ent, false ); if( CL_EntityTeleported( ent )) { VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); VectorCopy( ent->curstate.angles, ent->latched.prevangles ); CL_ResetPositions( ent ); } VectorCopy( ent->origin, ent->attachment[0] ); VectorCopy( ent->origin, ent->attachment[1] ); VectorCopy( ent->origin, ent->attachment[2] ); VectorCopy( ent->origin, ent->attachment[3] ); CL_AddVisibleEntity( ent, ET_NORMAL ); } } /* =============== CL_MoveThirdpersonCamera think thirdperson =============== */ void CL_MoveThirdpersonCamera( void ) { if( cls.state == ca_disconnected || cls.state == ca_cinematic ) return; // think thirdperson camera clgame.dllFuncs.CAM_Think (); } /* =============== CL_EmitEntities add visible entities to refresh list process frame interpolation etc =============== */ void CL_EmitEntities( void ) { if( cl.paused ) return; // don't waste time // not in server yet, no entities to redraw if( cls.state != ca_active || !cl.validsequence ) return; // make sure we have at least one valid update if( !cl.frames[cl.parsecountmod].valid ) return; // animate lightestyles ref.dllFuncs.CL_RunLightStyles( CL_GetLightStyle( 0 )); // decay dynamic lights CL_DecayLights (); // compute last interpolation amount CL_UpdateFrameLerp (); // set client ideal pitch when mlook is disabled CL_SetIdealPitch (); ref.dllFuncs.R_ClearScene (); // link all the visible clients first CL_LinkPlayers ( &cl.frames[cl.parsecountmod] ); // link all the entities that actually have update CL_LinkPacketEntities ( &cl.frames[cl.parsecountmod] ); // link custom user temp entities clgame.dllFuncs.pfnCreateEntities(); // evaluate temp entities CL_TempEntUpdate (); // fire events (client and server) CL_FireEvents (); // handle spectator camera movement CL_MoveSpectatorCamera(); // perfomance test CL_TestLights(); } /* ========================================================================== SOUND ENGINE IMPLEMENTATION ========================================================================== */ qboolean CL_GetEntitySpatialization( channel_t *ch ) { cl_entity_t *ent; qboolean valid_origin; if( ch->entnum == 0 ) { ch->staticsound = true; return true; // static sound } if(( ch->entnum - 1 ) == cl.playernum ) { VectorCopy( refState.vieworg, ch->origin ); return true; } valid_origin = VectorIsNull( ch->origin ) ? false : true; ent = CL_GetEntityByIndex( ch->entnum ); // entity is not present on the client but has valid origin if( !ent || !ent->model || ent->curstate.messagenum != cl.parsecount ) return valid_origin; // setup origin if( ent->model->type == mod_brush ) { VectorAverage( ent->model->mins, ent->model->maxs, ch->origin ); VectorAdd( ent->origin, ch->origin, ch->origin ); } else { VectorCopy( ent->origin, ch->origin ); } return true; } qboolean CL_GetMovieSpatialization( rawchan_t *ch ) { cl_entity_t *ent; qboolean valid_origin; valid_origin = VectorIsNull( ch->origin ) ? false : true; ent = CL_GetEntityByIndex( ch->entnum ); // entity is not present on the client but has valid origin if( !ent || !ent->index || ent->curstate.messagenum == 0 ) return valid_origin; // setup origin if( ent->model->type == mod_brush ) { VectorAverage( ent->model->mins, ent->model->maxs, ch->origin ); VectorAdd( ent->origin, ch->origin, ch->origin ); } else { VectorCopy( ent->origin, ch->origin ); } return true; } ================================================ FILE: engine/client/cl_game.c ================================================ /* cl_game.c - client dll interaction Copyright (C) 2008 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #if XASH_SDL == 2 #include // SDL_GetWindowPosition #elif XASH_SDL == 3 #include // SDL_GetWindowPosition #endif // XASH_SDL #include "common.h" #include "client.h" #include "const.h" #include "triangleapi.h" #include "r_efx.h" #include "demo_api.h" #include "ivoicetweak.h" #include "pm_local.h" #include "cl_tent.h" #include "input.h" #include "shake.h" #include "sprite.h" #include "library.h" #include "vgui_draw.h" #include "sound.h" // SND_STOP_LOOPING #include "platform/platform.h" #define MAX_LINELENGTH 80 #define MAX_TEXTCHANNELS 8 // must be power of two (GoldSrc uses 4 channels) #define TEXT_MSGNAME "TextMessage%i" static char cl_textbuffer[MAX_TEXTCHANNELS][2048]; static client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS]; static const dllfunc_t cdll_exports[] = { { "Initialize", (void **)&clgame.dllFuncs.pfnInitialize }, { "HUD_VidInit", (void **)&clgame.dllFuncs.pfnVidInit }, { "HUD_Init", (void **)&clgame.dllFuncs.pfnInit }, { "HUD_Shutdown", (void **)&clgame.dllFuncs.pfnShutdown }, { "HUD_Redraw", (void **)&clgame.dllFuncs.pfnRedraw }, { "HUD_UpdateClientData", (void **)&clgame.dllFuncs.pfnUpdateClientData }, { "HUD_Reset", (void **)&clgame.dllFuncs.pfnReset }, { "HUD_PlayerMove", (void **)&clgame.dllFuncs.pfnPlayerMove }, { "HUD_PlayerMoveInit", (void **)&clgame.dllFuncs.pfnPlayerMoveInit }, { "HUD_PlayerMoveTexture", (void **)&clgame.dllFuncs.pfnPlayerMoveTexture }, { "HUD_ConnectionlessPacket", (void **)&clgame.dllFuncs.pfnConnectionlessPacket }, { "HUD_GetHullBounds", (void **)&clgame.dllFuncs.pfnGetHullBounds }, { "HUD_Frame", (void **)&clgame.dllFuncs.pfnFrame }, { "HUD_PostRunCmd", (void **)&clgame.dllFuncs.pfnPostRunCmd }, { "HUD_Key_Event", (void **)&clgame.dllFuncs.pfnKey_Event }, { "HUD_AddEntity", (void **)&clgame.dllFuncs.pfnAddEntity }, { "HUD_CreateEntities", (void **)&clgame.dllFuncs.pfnCreateEntities }, { "HUD_StudioEvent", (void **)&clgame.dllFuncs.pfnStudioEvent }, { "HUD_TxferLocalOverrides", (void **)&clgame.dllFuncs.pfnTxferLocalOverrides }, { "HUD_ProcessPlayerState", (void **)&clgame.dllFuncs.pfnProcessPlayerState }, { "HUD_TxferPredictionData", (void **)&clgame.dllFuncs.pfnTxferPredictionData }, { "HUD_TempEntUpdate", (void **)&clgame.dllFuncs.pfnTempEntUpdate }, { "HUD_DrawNormalTriangles", (void **)&clgame.dllFuncs.pfnDrawNormalTriangles }, { "HUD_DrawTransparentTriangles", (void **)&clgame.dllFuncs.pfnDrawTransparentTriangles }, { "HUD_GetUserEntity", (void **)&clgame.dllFuncs.pfnGetUserEntity }, { "Demo_ReadBuffer", (void **)&clgame.dllFuncs.pfnDemo_ReadBuffer }, { "CAM_Think", (void **)&clgame.dllFuncs.CAM_Think }, { "CL_IsThirdPerson", (void **)&clgame.dllFuncs.CL_IsThirdPerson }, { "CL_CameraOffset", (void **)&clgame.dllFuncs.CL_CameraOffset }, // unused callback. Now camera code is completely moved to the user area { "CL_CreateMove", (void **)&clgame.dllFuncs.CL_CreateMove }, { "IN_ActivateMouse", (void **)&clgame.dllFuncs.IN_ActivateMouse }, { "IN_DeactivateMouse", (void **)&clgame.dllFuncs.IN_DeactivateMouse }, { "IN_MouseEvent", (void **)&clgame.dllFuncs.IN_MouseEvent }, { "IN_Accumulate", (void **)&clgame.dllFuncs.IN_Accumulate }, { "IN_ClearStates", (void **)&clgame.dllFuncs.IN_ClearStates }, { "V_CalcRefdef", (void **)&clgame.dllFuncs.pfnCalcRefdef }, { "KB_Find", (void **)&clgame.dllFuncs.KB_Find }, }; // optional exports static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher { { "HUD_GetStudioModelInterface", (void **)&clgame.dllFuncs.pfnGetStudioModelInterface }, { "HUD_DirectorMessage", (void **)&clgame.dllFuncs.pfnDirectorMessage }, { "HUD_VoiceStatus", (void **)&clgame.dllFuncs.pfnVoiceStatus }, { "HUD_ChatInputPosition", (void **)&clgame.dllFuncs.pfnChatInputPosition }, { "HUD_GetRenderInterface", (void **)&clgame.dllFuncs.pfnGetRenderInterface }, // Xash3D ext { "HUD_ClipMoveToEntity", (void **)&clgame.dllFuncs.pfnClipMoveToEntity }, // Xash3D ext { "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext { "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext { "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext }; static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ); /* ==================== CL_CreatePlaylist Create a default valve playlist ==================== */ static void CL_CreatePlaylist( const char *filename ) { file_t *f; f = FS_Open( filename, "w", false ); if( !f ) return; // make standard cdaudio playlist FS_Print( f, "blank\n" ); // #1 FS_Print( f, "Half-Life01.mp3\n" ); // #2 FS_Print( f, "Prospero01.mp3\n" ); // #3 FS_Print( f, "Half-Life12.mp3\n" ); // #4 FS_Print( f, "Half-Life07.mp3\n" ); // #5 FS_Print( f, "Half-Life10.mp3\n" ); // #6 FS_Print( f, "Suspense01.mp3\n" ); // #7 FS_Print( f, "Suspense03.mp3\n" ); // #8 FS_Print( f, "Half-Life09.mp3\n" ); // #9 FS_Print( f, "Half-Life02.mp3\n" ); // #10 FS_Print( f, "Half-Life13.mp3\n" ); // #11 FS_Print( f, "Half-Life04.mp3\n" ); // #12 FS_Print( f, "Half-Life15.mp3\n" ); // #13 FS_Print( f, "Half-Life14.mp3\n" ); // #14 FS_Print( f, "Half-Life16.mp3\n" ); // #15 FS_Print( f, "Suspense02.mp3\n" ); // #16 FS_Print( f, "Half-Life03.mp3\n" ); // #17 FS_Print( f, "Half-Life08.mp3\n" ); // #18 FS_Print( f, "Prospero02.mp3\n" ); // #19 FS_Print( f, "Half-Life05.mp3\n" ); // #20 FS_Print( f, "Prospero04.mp3\n" ); // #21 FS_Print( f, "Half-Life11.mp3\n" ); // #22 FS_Print( f, "Half-Life06.mp3\n" ); // #23 FS_Print( f, "Prospero03.mp3\n" ); // #24 FS_Print( f, "Half-Life17.mp3\n" ); // #25 FS_Print( f, "Prospero05.mp3\n" ); // #26 FS_Print( f, "Suspense05.mp3\n" ); // #27 FS_Print( f, "Suspense07.mp3\n" ); // #28 FS_Close( f ); } /* ==================== CL_InitCDAudio Initialize CD playlist ==================== */ static void CL_InitCDAudio( const char *filename ) { byte *afile; char *pfile; string token; int c = 0; if( !FS_FileExists( filename, false )) { // create a default playlist CL_CreatePlaylist( filename ); } afile = FS_LoadFile( filename, NULL, false ); if( !afile ) return; pfile = (char *)afile; // format: trackname\n [num] while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) { if( !Q_stricmp( token, "blank" )) clgame.cdtracks[c][0] = '\0'; else { Q_snprintf( clgame.cdtracks[c], sizeof( clgame.cdtracks[c] ), "media/%s", token ); } if( ++c > MAX_CDTRACKS - 1 ) { Con_Reportf( S_WARN "%s: too many tracks %i in %s\n", __func__, MAX_CDTRACKS, filename ); break; } } Mem_Free( afile ); } /* ============= CL_AdjustXPos adjust text by x pos ============= */ static int CL_AdjustXPos( float x, int width, int totalWidth ) { int xPos; if( x == -1 ) { xPos = ( clgame.scrInfo.iWidth - width ) * 0.5f; } else { if ( x < 0 ) xPos = (1.0f + x) * clgame.scrInfo.iWidth - totalWidth; // Alight right else // align left xPos = x * clgame.scrInfo.iWidth; } if( xPos + width > clgame.scrInfo.iWidth ) xPos = clgame.scrInfo.iWidth - width; else if( xPos < 0 ) xPos = 0; return xPos; } /* ============= CL_AdjustYPos adjust text by y pos ============= */ static int CL_AdjustYPos( float y, int height ) { int yPos; if( y == -1 ) // centered? { yPos = ( clgame.scrInfo.iHeight - height ) * 0.5f; } else { // Alight bottom? if( y < 0 ) yPos = (1.0f + y) * clgame.scrInfo.iHeight - height; // Alight bottom else // align top yPos = y * clgame.scrInfo.iHeight; } if( yPos + height > clgame.scrInfo.iHeight ) yPos = clgame.scrInfo.iHeight - height; else if( yPos < 0 ) yPos = 0; return yPos; } /* ============= CL_CenterPrint print centerscreen message ============= */ void CL_CenterPrint( const char *text, float y ) { cl_font_t *font = Con_GetCurFont(); if( !COM_CheckString( text ) || !font || !font->valid ) return; clgame.centerPrint.totalWidth = 0; clgame.centerPrint.time = cl.mtime[0]; // allow pause for centerprint Q_strncpy( clgame.centerPrint.message, text, sizeof( clgame.centerPrint.message )); CL_DrawStringLen( font, clgame.centerPrint.message, &clgame.centerPrint.totalWidth, &clgame.centerPrint.totalHeight, FONT_DRAW_HUD | FONT_DRAW_UTF8 ); if( font->charHeight ) clgame.centerPrint.lines = clgame.centerPrint.totalHeight / font->charHeight; else clgame.centerPrint.lines = 1; clgame.centerPrint.y = CL_AdjustYPos( y, clgame.centerPrint.totalHeight ); } /* ==================== SPR_AdjustSize draw hudsprite routine ==================== */ void SPR_AdjustSize( float *x, float *y, float *w, float *h ) { float xscale, yscale; if( refState.width == clgame.scrInfo.iWidth && refState.height == clgame.scrInfo.iHeight ) return; // scale for screen sizes xscale = refState.width / (float)clgame.scrInfo.iWidth; yscale = refState.height / (float)clgame.scrInfo.iHeight; *x *= xscale; *y *= yscale; *w *= xscale; *h *= yscale; } static void SPR_AdjustTexCoords( int texnum, float width, float height, float *s1, float *t1, float *s2, float *t2 ) { const qboolean filtering = REF_GET_PARM( PARM_TEX_FILTERING, texnum ); const int xremainder = refState.width % clgame.scrInfo.iWidth; const int yremainder = refState.height % clgame.scrInfo.iHeight; if(( filtering || xremainder ) && refState.width != clgame.scrInfo.iWidth ) { // align to texel if scaling *s1 += 0.5f; *s2 -= 0.5f; } if(( filtering || yremainder ) && refState.height != clgame.scrInfo.iHeight ) { // align to texel if scaling *t1 += 0.5f; *t2 -= 0.5f; } *s1 /= width; *t1 /= height; *s2 /= width; *t2 /= height; } /* ==================== SPR_DrawGeneric draw hudsprite routine ==================== */ static void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc ) { float s1, s2, t1, t2; int texnum; if( width == -1 && height == -1 ) { int w, h; // assume we get sizes from image ref.dllFuncs.R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite ); width = w; height = h; } texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame ); if( prc ) { wrect_t rc = *prc; // Sigh! some stupid modmakers set wrong rectangles in hud.txt if( rc.left <= 0 || rc.left >= width ) rc.left = 0; if( rc.top <= 0 || rc.top >= height ) rc.top = 0; if( rc.right <= 0 || rc.right > width ) rc.right = width; if( rc.bottom <= 0 || rc.bottom > height ) rc.bottom = height; s1 = rc.left; t1 = rc.top; s2 = rc.right; t2 = rc.bottom; // calc user-defined rectangle SPR_AdjustTexCoords( texnum, width, height, &s1, &t1, &s2, &t2 ); width = rc.right - rc.left; height = rc.bottom - rc.top; } else { s1 = t1 = 0.0f; s2 = t2 = 1.0f; } // pass scissor test if supposed if( !CL_Scissor( &clgame.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) return; // scale for screen sizes SPR_AdjustSize( &x, &y, &width, &height ); ref.dllFuncs.Color4ub( clgame.ds.spriteColor[0], clgame.ds.spriteColor[1], clgame.ds.spriteColor[2], clgame.ds.spriteColor[3] ); ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, texnum ); } /* ============= CL_DrawCenterPrint called each frame ============= */ void CL_DrawCenterPrint( void ) { cl_font_t *font = Con_GetCurFont(); char *pText; int i, j, x, y; int width, lineLength; byte *colorDefault, line[MAX_LINELENGTH]; int charWidth, charHeight; if( !clgame.centerPrint.time ) return; if(( cl.time - clgame.centerPrint.time ) >= scr_centertime.value ) { // time expired clgame.centerPrint.time = 0.0f; return; } y = clgame.centerPrint.y; // start y colorDefault = g_color_table[7]; pText = clgame.centerPrint.message; CL_DrawCharacterLen( font, 0, NULL, &charHeight ); CL_SetFontRendermode( font ); for( i = 0; i < clgame.centerPrint.lines; i++ ) { lineLength = 0; width = 0; while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH ) { int number = Con_UtfProcessChar(( byte ) * pText ); pText++; if( number == 0 ) continue; line[lineLength] = number; CL_DrawCharacterLen( font, number, &charWidth, NULL ); width += charWidth; lineLength++; } if( lineLength == MAX_LINELENGTH ) lineLength--; pText++; // Skip LineFeed line[lineLength] = 0; x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth ); for( j = 0; j < lineLength; j++ ) { if( x >= 0 && y >= 0 && x <= refState.width ) x += CL_DrawCharacter( x, y, line[j], colorDefault, font, FONT_DRAW_HUD | FONT_DRAW_NORENDERMODE ); } y += charHeight; } } static int V_FadeAlpha( screenfade_t *sf ) { int alpha; if( cl.time > sf->fadeReset && cl.time > sf->fadeEnd ) { if( !FBitSet( sf->fadeFlags, FFADE_STAYOUT )) return 0; } if( FBitSet( sf->fadeFlags, FFADE_STAYOUT )) { alpha = sf->fadealpha; if( FBitSet( sf->fadeFlags, FFADE_OUT ) && sf->fadeTotalEnd > cl.time ) { alpha += sf->fadeSpeed * ( sf->fadeTotalEnd - cl.time ); } else { sf->fadeEnd = cl.time + 0.1; } } else { alpha = sf->fadeSpeed * ( sf->fadeEnd - cl.time ); if( FBitSet( sf->fadeFlags, FFADE_OUT )) { alpha += sf->fadealpha; } } alpha = bound( 0, alpha, sf->fadealpha ); return alpha; } /* ============= CL_DrawScreenFade fill screen with specfied color can be modulated ============= */ static void CL_DrawScreenFade( void ) { screenfade_t *sf = &clgame.fade; int alpha; alpha = V_FadeAlpha( sf ); if( !alpha ) return; if( !FBitSet( sf->fadeFlags, FFADE_MODULATE )) { ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); ref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, alpha ); } else if( Host_IsQuakeCompatible( )) { // Quake Wrapper and Quake Remake use FFADE_MODULATE for item pickups // so hack the check here ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); ref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, alpha ); } else { ref.dllFuncs.GL_SetRenderMode( kRenderScreenFadeModulate ); ref.dllFuncs.Color4ub( (uint16_t)( sf->fader * alpha + ( 255 - alpha ) * 255 ) >> 8, (uint16_t)( sf->fadeg * alpha + ( 255 - alpha ) * 255 ) >> 8, (uint16_t)( sf->fadeb * alpha + ( 255 - alpha ) * 255 ) >> 8, 255 ); } ref.dllFuncs.R_DrawStretchPic( 0, 0, refState.width, refState.height, 0, 0, 1, 1, R_GetBuiltinTexture( REF_WHITE_TEXTURE )); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); } /* ==================== CL_InitTitles parse all messages that declared in titles.txt and hold them into permament memory pool ==================== */ static void CL_InitTitles( const char *filename ) { fs_offset_t fileSize; byte *pMemFile; int i; // initialize text messages (game_text) for( i = 0; i < MAX_TEXTCHANNELS; i++ ) { char name[MAX_VA_STRING]; Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i ); cl_textmessage[i].pName = copystringpool( clgame.mempool, name ); cl_textmessage[i].pMessage = cl_textbuffer[i]; } // clear out any old data that's sitting around. if( clgame.titles ) Mem_Free( clgame.titles ); clgame.titles = NULL; clgame.numTitles = 0; pMemFile = FS_LoadFile( filename, &fileSize, false ); if( !pMemFile ) return; CL_TextMessageParse( pMemFile, fileSize ); Mem_Free( pMemFile ); } /* ==================== CL_HudMessage Template to show hud messages ==================== */ void CL_HudMessage( const char *pMessage ) { if( !COM_CheckString( pMessage )) return; CL_DispatchUserMessage( "HudText", Q_strlen( pMessage ), (void *)pMessage ); } /* ==================== CL_ParseTextMessage Parse TE_TEXTMESSAGE ==================== */ void CL_ParseTextMessage( sizebuf_t *msg ) { static int msgindex = 0; client_textmessage_t *text; int channel; // read channel ( 0 - auto) channel = MSG_ReadByte( msg ); if( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 )) { channel = msgindex; msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1); } // grab message channel text = &cl_textmessage[channel]; text->x = (float)(MSG_ReadShort( msg ) / 8192.0f); text->y = (float)(MSG_ReadShort( msg ) / 8192.0f); text->effect = MSG_ReadByte( msg ); text->r1 = MSG_ReadByte( msg ); text->g1 = MSG_ReadByte( msg ); text->b1 = MSG_ReadByte( msg ); text->a1 = MSG_ReadByte( msg ); text->r2 = MSG_ReadByte( msg ); text->g2 = MSG_ReadByte( msg ); text->b2 = MSG_ReadByte( msg ); text->a2 = MSG_ReadByte( msg ); text->fadein = (float)(MSG_ReadWord( msg ) / 256.0f ); text->fadeout = (float)(MSG_ReadWord( msg ) / 256.0f ); text->holdtime = (float)(MSG_ReadWord( msg ) / 256.0f ); if( text->effect == 2 ) text->fxtime = (float)(MSG_ReadWord( msg ) / 256.0f ); else text->fxtime = 0.0f; // to prevent grab too long messages Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 ); CL_HudMessage( text->pName ); } /* ================ CL_ParseFinaleCutscene show display finale or cutscene message ================ */ void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ) { static int msgindex = 0; client_textmessage_t *text; int channel; cl.intermission = level; channel = msgindex; msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1); // grab message channel text = &cl_textmessage[channel]; // NOTE: svc_finale and svc_cutscene has a // predefined settings like Quake-style text->x = -1.0f; text->y = 0.15f; text->effect = 2; // scan out effect text->r1 = 245; text->g1 = 245; text->b1 = 245; text->a1 = 0; // unused text->r2 = 0; text->g2 = 0; text->b2 = 0; text->a2 = 0; text->fadein = 0.15f; text->fadeout = 0.0f; text->holdtime = 99999.0f; text->fxtime = 0.0f; // to prevent grab too long messages Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 ); if( *text->pMessage == '\0' ) return; // no real text CL_HudMessage( text->pName ); } /* ==================== CL_GetMaxlients Render callback for studio models ==================== */ int GAME_EXPORT CL_GetMaxClients( void ) { return cl.maxclients; } /* ==================== CL_SoundFromIndex return soundname from index ==================== */ static const char *CL_SoundFromIndex( int index ) { sfx_t *sfx = NULL; int hSound; // make sure what we in-bounds index = bound( 0, index, MAX_SOUNDS ); hSound = cl.sound_index[index]; if( !hSound ) { Con_DPrintf( S_ERROR "%s: invalid sound index %i\n", __func__, index ); return NULL; } sfx = S_GetSfxByHandle( hSound ); if( !sfx ) { Con_DPrintf( S_ERROR "%s: bad sfx for index %i\n", __func__, index ); return NULL; } return sfx->name; } /* ================ CL_EnableScissor enable scissor test ================ */ void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height ) { scissor->x = x; scissor->y = y; scissor->width = width; scissor->height = height; scissor->test = true; } /* ================ CL_DisableScissor disable scissor test ================ */ void CL_DisableScissor( scissor_state_t *scissor ) { scissor->test = false; } /* ================ CL_Scissor perform common scissor test ================ */ qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) { float dudx, dvdy; if( !scissor->test ) return true; // clip sub rect to sprite if( *width == 0 || *height == 0 ) return false; if( *x + *width <= scissor->x ) return false; if( *x >= scissor->x + scissor->width ) return false; if( *y + *height <= scissor->y ) return false; if( *y >= scissor->y + scissor->height ) return false; dudx = (*u1 - *u0) / *width; dvdy = (*v1 - *v0) / *height; if( *x < scissor->x ) { *u0 += (scissor->x - *x) * dudx; *width -= scissor->x - *x; *x = scissor->x; } if( *x + *width > scissor->x + scissor->width ) { *u1 -= (*x + *width - (scissor->x + scissor->width)) * dudx; *width = scissor->x + scissor->width - *x; } if( *y < scissor->y ) { *v0 += (scissor->y - *y) * dvdy; *height -= scissor->y - *y; *y = scissor->y; } if( *y + *height > scissor->y + scissor->height ) { *v1 -= (*y + *height - (scissor->y + scissor->height)) * dvdy; *height = scissor->y + scissor->height - *y; } return true; } /* ========= SPR_EnableScissor ========= */ static void GAME_EXPORT SPR_EnableScissor( int x, int y, int width, int height ) { // check bounds x = bound( 0, x, clgame.scrInfo.iWidth ); y = bound( 0, y, clgame.scrInfo.iHeight ); width = bound( 0, width, clgame.scrInfo.iWidth - x ); height = bound( 0, height, clgame.scrInfo.iHeight - y ); CL_EnableScissor( &clgame.ds.scissor, x, y, width, height ); } /* ========= SPR_DisableScissor ========= */ static void GAME_EXPORT SPR_DisableScissor( void ) { CL_DisableScissor( &clgame.ds.scissor ); } /* ==================== CL_DrawCrosshair Render crosshair ==================== */ static void CL_DrawCrosshair( void ) { int x, y, width, height; float xscale, yscale; if( !clgame.ds.pCrosshair || !cl_crosshair.value ) return; // any camera on or client is died if( cl.local.health <= 0 || cl.viewentity != ( cl.playernum + 1 )) return; // get crosshair dimension width = clgame.ds.rcCrosshair.right - clgame.ds.rcCrosshair.left; height = clgame.ds.rcCrosshair.bottom - clgame.ds.rcCrosshair.top; x = clgame.viewport[0] + ( clgame.viewport[2] >> 1 ); y = clgame.viewport[1] + ( clgame.viewport[3] >> 1 ); // g-cont - cl.crosshairangle is the autoaim angle. // if we're not using autoaim, just draw in the middle of the screen if( !VectorIsNull( cl.crosshairangle )) { vec3_t angles; vec3_t forward; vec3_t point, screen; VectorAdd( refState.viewangles, cl.crosshairangle, angles ); AngleVectors( angles, forward, NULL, NULL ); VectorAdd( refState.vieworg, forward, point ); ref.dllFuncs.WorldToScreen( point, screen ); x += ( clgame.viewport[2] >> 1 ) * screen[0] + 0.5f; y += ( clgame.viewport[3] >> 1 ) * screen[1] + 0.5f; } // back to logical sizes xscale = (float)clgame.scrInfo.iWidth / refState.width; yscale = (float)clgame.scrInfo.iHeight / refState.height; x *= xscale; y *= yscale; // move at center the screen x -= 0.5f * width; y -= 0.5f * height; clgame.ds.pSprite = clgame.ds.pCrosshair; Vector4Copy( clgame.ds.rgbaCrosshair, clgame.ds.spriteColor ); pfnSPR_DrawHoles( 0, x, y, &clgame.ds.rcCrosshair ); } /* ============= CL_DrawLoading draw loading progress bar ============= */ static void CL_DrawLoadingOrPaused( int tex ) { float x, y, width, height; int iWidth, iHeight; R_GetTextureParms( &iWidth, &iHeight, tex ); x = ( clgame.scrInfo.iWidth - iWidth ) / 2.0f; y = ( clgame.scrInfo.iHeight - iHeight ) / 2.0f; width = iWidth; height = iHeight; SPR_AdjustSize( &x, &y, &width, &height ); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, tex ); } void CL_DrawHUD( int state ) { if( state == CL_ACTIVE && !cl.video_prepped ) state = CL_LOADING; if( state == CL_ACTIVE && cl.paused ) state = CL_PAUSED; switch( state ) { case CL_ACTIVE: if( !cl.intermission ) CL_DrawScreenFade (); CL_DrawCrosshair (); CL_DrawCenterPrint (); clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission ); if( cl.intermission ) CL_DrawScreenFade (); break; case CL_PAUSED: CL_DrawScreenFade (); CL_DrawCrosshair (); CL_DrawCenterPrint (); clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission ); if( showpause.value ) { if( !cls.pauseIcon ) cls.pauseIcon = SCR_LoadPauseIcon(); CL_DrawLoadingOrPaused( Q_max( 0, cls.pauseIcon )); } break; case CL_LOADING: CL_DrawLoadingOrPaused( cls.loadingBar ); break; case CL_CHANGELEVEL: if( cls.draw_changelevel ) { CL_DrawLoadingOrPaused( cls.loadingBar ); cls.draw_changelevel = false; } break; } } static void CL_ClearUserMessage( char *pszName, int svc_num ) { int i; for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) if( ( clgame.msg[i].number == svc_num ) && Q_stricmp( clgame.msg[i].name, pszName ) ) clgame.msg[i].number = 0; } void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) { int i; if( !pszName || !*pszName ) Host_Error( "%s: bad message name\n", __func__ ); if( svc_num <= svc_lastmsg ) Host_Error( "%s: tried to hook a system message \"%s\"\n", __func__, svc_strings[svc_num] ); // see if already hooked for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) { // NOTE: no check for DispatchFunc, check only name if( !Q_stricmp( clgame.msg[i].name, pszName )) { clgame.msg[i].number = svc_num; clgame.msg[i].size = iSize; CL_ClearUserMessage( pszName, svc_num ); return; } } if( i == MAX_USER_MESSAGES ) { Host_Error( "%s: MAX_USER_MESSAGES hit!\n", __func__ ); return; } // register new message without DispatchFunc, so we should parse it properly Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name )); clgame.msg[i].number = svc_num; clgame.msg[i].size = iSize; CL_ClearUserMessage( pszName, svc_num ); } void CL_ClearWorld( void ) { if( clgame.entities ) // check if we have entities, legacy protocol support kinda breaks this logic { cl_entity_t *worldmodel = clgame.entities; worldmodel->curstate.modelindex = 1; // world model worldmodel->curstate.solid = SOLID_BSP; worldmodel->curstate.movetype = MOVETYPE_PUSH; worldmodel->model = cl.worldmodel; worldmodel->index = 0; } world.max_recursion = 0; clgame.ds.cullMode = TRI_FRONT; clgame.numStatics = 0; } void CL_InitEdicts( int maxclients ) { Assert( clgame.entities == NULL ); if( !clgame.mempool ) return; // Host_Error without client #if XASH_LOW_MEMORY != 2 CL_UPDATE_BACKUP = ( maxclients <= 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP; #endif cls.num_client_entities = CL_UPDATE_BACKUP * NUM_PACKET_ENTITIES; cls.packet_entities = Mem_Realloc( clgame.mempool, cls.packet_entities, sizeof( entity_state_t ) * cls.num_client_entities ); clgame.entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities ); clgame.static_entities = NULL; // will be initialized later clgame.numStatics = 0; if(( clgame.maxRemapInfos - 1 ) != clgame.maxEntities ) { CL_ClearAllRemaps (); // purge old remap info clgame.maxRemapInfos = clgame.maxEntities + 1; clgame.remap_info = (remap_info_t **)Mem_Calloc( clgame.mempool, sizeof( remap_info_t* ) * clgame.maxRemapInfos ); } ref.dllFuncs.R_ProcessEntData( true, clgame.entities, clgame.maxEntities ); } void CL_FreeEdicts( void ) { ref.dllFuncs.R_ProcessEntData( false, NULL, 0 ); if( clgame.entities ) Mem_Free( clgame.entities ); clgame.entities = NULL; if( clgame.static_entities ) Mem_Free( clgame.static_entities ); clgame.static_entities = NULL; if( cls.packet_entities ) Z_Free( cls.packet_entities ); cls.packet_entities = NULL; cls.num_client_entities = 0; cls.next_client_entities = 0; clgame.numStatics = 0; } void CL_ClearEdicts( void ) { if( clgame.entities != NULL ) return; // in case we stopped with error clgame.maxEntities = 2; CL_InitEdicts( cl.maxclients ); } /* ================== CL_ClearSpriteTextures free studio cache on change level ================== */ void CL_ClearSpriteTextures( void ) { int i; for( i = 1; i < MAX_CLIENT_SPRITES; i++ ) clgame.sprites[i].needload = NL_UNREFERENCED; } // it's a Valve default value for LoadMapSprite (probably must be power of two) #define MAPSPRITE_SIZE 128 /* ==================== Mod_LoadMapSprite Loading a bitmap image as sprite with multiple frames as pieces of input image ==================== */ static void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) { rgbdata_t *pix, temp = { 0 }; char texname[128]; int i, w, h; int xl, yl; int numframes; msprite_t *psprite; char poolname[MAX_VA_STRING]; if( loaded ) *loaded = false; Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); Image_SetForceFlags( IL_OVERVIEW ); pix = FS_LoadImage( texname, buffer, size ); Image_ClearForceFlags(); if( !pix ) return; // bad image or something else mod->type = mod_sprite; if( pix->width % MAPSPRITE_SIZE ) w = pix->width - ( pix->width % MAPSPRITE_SIZE ); else w = pix->width; if( pix->height % MAPSPRITE_SIZE ) h = pix->height - ( pix->height % MAPSPRITE_SIZE ); else h = pix->height; if( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE; if( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE; // resample image if needed Image_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, 0.0f ); w = h = MAPSPRITE_SIZE; // check range if( w > pix->width ) w = pix->width; if( h > pix->height ) h = pix->height; // determine how many frames we needs numframes = (pix->width * pix->height) / (w * h); Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); mod->mempool = Mem_AllocPool( poolname ); psprite = Mem_Calloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); mod->cache.data = psprite; // make link to extradata psprite->type = SPR_FWD_PARALLEL_ORIENTED; psprite->texFormat = SPR_ALPHTEST; psprite->numframes = mod->numframes = numframes; psprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1))); mod->mins[0] = mod->mins[1] = -w / 2; mod->maxs[0] = mod->maxs[1] = w / 2; mod->mins[2] = -h / 2; mod->maxs[2] = h / 2; // create a temporary pic temp.width = w; temp.height = h; temp.type = pix->type; temp.flags = pix->flags; temp.size = w * h * PFDesc[temp.type].bpp; temp.buffer = Mem_Malloc( mod->mempool, temp.size ); temp.palette = NULL; // chop the image and upload into video memory for( i = xl = yl = 0; i < numframes; i++ ) { mspriteframe_t *pspriteframe; int xh = xl + w, yh = yl + h, x, y, j; int linedelta = ( pix->width - w ) * 4; byte *src = pix->buffer + ( yl * pix->width + xl ) * 4; byte *dst = temp.buffer; // cut block from source for( y = yl; y < yh; y++ ) { for( x = xl; x < xh; x++ ) for( j = 0; j < 4; j++ ) *dst++ = *src++; src += linedelta; } // build uinque frame name Q_snprintf( texname, sizeof( texname ), "#MAP/%s_%i%i.spr", mod->name, i / 10, i % 10 ); psprite->frames[i].frameptr = Mem_Calloc( mod->mempool, sizeof( mspriteframe_t )); pspriteframe = psprite->frames[i].frameptr; pspriteframe->width = w; pspriteframe->height = h; pspriteframe->up = ( h >> 1 ); pspriteframe->left = -( w >> 1 ); pspriteframe->down = ( h >> 1 ) - h; pspriteframe->right = w + -( w >> 1 ); pspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE ); xl += w; if( xl >= pix->width ) { xl = 0; yl += h; } } FS_FreeImage( pix ); Mem_Free( temp.buffer ); if( loaded ) *loaded = true; } /* ============= CL_LoadHudSprite upload sprite frames ============= */ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags ) { byte *buf; fs_offset_t size; qboolean loaded; Assert( m_pSprite != NULL ); Q_strncpy( m_pSprite->name, szSpriteName, sizeof( m_pSprite->name )); // it's hud sprite, make difference names to prevent free shared textures if( type == SPR_CLIENT || type == SPR_HUDSPRITE ) SetBits( m_pSprite->flags, MODEL_CLIENT ); m_pSprite->numtexinfo = texFlags; // store texFlags for renderer into numtexinfo if( !FS_FileExists( szSpriteName, false ) ) { if( cls.state != ca_active && cl.maxclients > 1 ) { // trying to download sprite from server CL_AddClientResource( szSpriteName, t_model ); m_pSprite->needload = NL_NEEDS_LOADED; return true; } else { Con_Reportf( S_ERROR "Could not load HUD sprite %s\n", szSpriteName ); Mod_FreeModel( m_pSprite ); return false; } } buf = FS_LoadFile( szSpriteName, &size, false ); if( buf == NULL ) return false; if( type == SPR_MAPSPRITE ) Mod_LoadMapSprite( m_pSprite, buf, size, &loaded ); else { Mod_LoadSpriteModel( m_pSprite, buf, &loaded ); ref.dllFuncs.Mod_ProcessRenderData( m_pSprite, true, buf ); } Mem_Free( buf ); if( !loaded ) { Mod_FreeModel( m_pSprite ); return false; } m_pSprite->needload = NL_PRESENT; return true; } /* ============= CL_LoadSpriteModel some sprite models is exist only at client: HUD sprites, tent sprites or overview images ============= */ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFlags ) { char name[MAX_QPATH]; model_t *mod; int i, start; if( !COM_CheckString( filename )) { Con_Reportf( S_ERROR "%s: bad name!\n", __func__ ); return NULL; } Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); for( i = 0, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) { if( !Q_stricmp( mod->name, name )) { if( mod->needload == NL_NEEDS_LOADED ) { if( CL_LoadHudSprite( name, mod, type, texFlags )) return mod; } // prolonge registration mod->needload = NL_PRESENT; return mod; } } // find a free model slot spot // use low indices only for HUD sprites // for GoldSrc bug compatibility start = type == SPR_HUDSPRITE ? 0 : MAX_CLIENT_SPRITES / 2; for( i = 0, mod = &clgame.sprites[start]; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) { if( !mod->name[0] ) break; // this is a valid spot } if( i == MAX_CLIENT_SPRITES / 2 ) { Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES / 2 ); return NULL; } // load new map sprite if( CL_LoadHudSprite( name, mod, type, texFlags )) return mod; return NULL; } /* ============= CL_LoadClientSprite load sprites for temp ents ============= */ model_t *CL_LoadClientSprite( const char *filename ) { return CL_LoadSpriteModel( filename, SPR_CLIENT, 0 ); } /* =============================================================================== CGame Builtin Functions =============================================================================== */ /* ========= pfnSPR_LoadExt ========= */ HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) { model_t *spr; if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL ) return 0; return (spr - clgame.sprites) + 1; // return index } /* ========= pfnSPR_Load function exported for support GoldSrc Monitor utility ========= */ HSPRITE EXPORT pfnSPR_Load( const char *szPicName ); HSPRITE EXPORT pfnSPR_Load( const char *szPicName ) { model_t *spr; if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL ) return 0; return (spr - clgame.sprites) + 1; // return index } /* ============= CL_GetSpritePointer ============= */ static const model_t *CL_GetSpritePointer( HSPRITE hSprite ) { model_t *mod; int index = hSprite - 1; if( index < 0 || index >= MAX_CLIENT_SPRITES ) return NULL; // bad image mod = &clgame.sprites[index]; if( mod->needload == NL_NEEDS_LOADED ) { int type = FBitSet( mod->flags, MODEL_CLIENT ) ? SPR_HUDSPRITE : SPR_MAPSPRITE; if( CL_LoadHudSprite( mod->name, mod, type, mod->numtexinfo )) return mod; } if( mod->mempool ) { mod->needload = NL_PRESENT; return mod; } return NULL; } /* ========= pfnSPR_Frames function exported for support GoldSrc Monitor utility ========= */ int EXPORT pfnSPR_Frames( HSPRITE hPic ); int EXPORT pfnSPR_Frames( HSPRITE hPic ) { int numFrames = 0; ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic )); return numFrames; } /* ========= pfnSPR_Height ========= */ static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame ) { int sprHeight = 0; ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic )); return sprHeight; } /* ========= pfnSPR_Width ========= */ static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame ) { int sprWidth = 0; ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic )); return sprWidth; } /* ========= pfnSPR_Set ========= */ static void GAME_EXPORT pfnSPR_Set( HSPRITE hPic, int r, int g, int b ) { const model_t *sprite = CL_GetSpritePointer( hPic ); // a1ba: do not alter the state if invalid HSPRITE was passed if( !sprite ) return; clgame.ds.pSprite = sprite; clgame.ds.spriteColor[0] = bound( 0, r, 255 ); clgame.ds.spriteColor[1] = bound( 0, g, 255 ); clgame.ds.spriteColor[2] = bound( 0, b, 255 ); clgame.ds.spriteColor[3] = 255; } /* ========= pfnSPR_Draw ========= */ static void GAME_EXPORT pfnSPR_Draw( int frame, int x, int y, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha ); SPR_DrawGeneric( frame, x, y, -1, -1, prc ); } /* ========= pfnSPR_DrawHoles ========= */ static void GAME_EXPORT pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ) { #if 1 // REFTODO ref.dllFuncs.GL_SetRenderMode( kRenderTransColor ); #else pglEnable( GL_ALPHA_TEST ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglEnable( GL_BLEND ); #endif SPR_DrawGeneric( frame, x, y, -1, -1, prc ); #if 1 ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); #else pglDisable( GL_ALPHA_TEST ); pglDisable( GL_BLEND ); #endif } /* ========= pfnSPR_DrawAdditive ========= */ static void GAME_EXPORT pfnSPR_DrawAdditive( int frame, int x, int y, const wrect_t *prc ) { #if 1 // REFTODO ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); #else pglEnable( GL_BLEND ); pglBlendFunc( GL_ONE, GL_ONE ); #endif SPR_DrawGeneric( frame, x, y, -1, -1, prc ); #if 1 // REFTODO ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); #else pglDisable( GL_BLEND ); #endif } /* ========= SPR_GetList for parsing half-life scripts - hud.txt etc ========= */ static client_sprite_t *SPR_GetList( char *psz, int *piCount ) { cached_spritelist_t *pEntry = &clgame.sprlist[0]; int slot, index, numSprites = 0; byte *afile; char *pfile; string token; if( piCount ) *piCount = 0; // see if already in list // NOTE: client.dll is cache hud.txt but reparse weapon lists again and again // obviously there a memory leak by-design. Cache the sprite lists to prevent it for( slot = 0; slot < MAX_CLIENT_SPRITES && pEntry->szListName[0]; slot++ ) { pEntry = &clgame.sprlist[slot]; if( !Q_stricmp( pEntry->szListName, psz )) { if( piCount ) *piCount = pEntry->count; return pEntry->pList; } } if( slot == MAX_CLIENT_SPRITES ) { Con_Printf( S_ERROR "%s: overflow cache!\n", __func__ ); return NULL; } if( !clgame.itemspath[0] ) // typically it's sprites\*.txt COM_ExtractFilePath( psz, clgame.itemspath ); afile = FS_LoadFile( psz, NULL, false ); if( !afile ) return NULL; pfile = (char *)afile; pfile = COM_ParseFile( pfile, token, sizeof( token )); numSprites = Q_atoi( token ); Q_strncpy( pEntry->szListName, psz, sizeof( pEntry->szListName )); // name, res, pic, x, y, w, h pEntry->pList = Mem_Calloc( cls.mempool, sizeof( client_sprite_t ) * numSprites ); for( index = 0; index < numSprites; index++ ) { if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL ) break; Q_strncpy( pEntry->pList[index].szName, token, sizeof( pEntry->pList[0].szName )); // read resolution pfile = COM_ParseFile( pfile, token, sizeof( token )); pEntry->pList[index].iRes = Q_atoi( token ); // read spritename pfile = COM_ParseFile( pfile, token, sizeof( token )); Q_strncpy( pEntry->pList[index].szSprite, token, sizeof( pEntry->pList[0].szSprite )); // parse rectangle pfile = COM_ParseFile( pfile, token, sizeof( token )); pEntry->pList[index].rc.left = Q_atoi( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); pEntry->pList[index].rc.top = Q_atoi( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); pEntry->pList[index].rc.right = pEntry->pList[index].rc.left + Q_atoi( token ); pfile = COM_ParseFile( pfile, token, sizeof( token )); pEntry->pList[index].rc.bottom = pEntry->pList[index].rc.top + Q_atoi( token ); pEntry->count++; } if( index < numSprites ) Con_DPrintf( S_WARN "unexpected end of %s (%i should be %i)\n", psz, numSprites, index ); if( piCount ) *piCount = pEntry->count; Mem_Free( afile ); return pEntry->pList; } /* ============= CL_FillRGBA ============= */ static void GAME_EXPORT CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a ) { float x_ = x, y_ = y, w_ = w, h_ = h; r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); SPR_AdjustSize( &x_, &y_, &w_, &h_ ); ref.dllFuncs.FillRGBA( kRenderTransAdd, x_, y_, w_, h_, r, g, b, a ); } /* ============= pfnGetScreenInfo get actual screen info ============= */ int GAME_EXPORT CL_GetScreenInfo( SCREENINFO *pscrinfo ) { qboolean apply_scale_factor = false; // we don't want floating point inaccuracies float scale_factor = hud_scale.value; if( FBitSet( hud_fontscale.flags, FCVAR_CHANGED )) { CL_FreeFont( &cls.creditsFont ); SCR_LoadCreditsFont(); ClearBits( hud_fontscale.flags, FCVAR_CHANGED ); } // setup screen info clgame.scrInfo.iSize = sizeof( clgame.scrInfo ); clgame.scrInfo.iFlags = SCRINFO_SCREENFLASH; if( hud_scale.value >= 320.0f && hud_scale.value >= hud_scale_minimal_width.value ) { scale_factor = refState.width / hud_scale.value; apply_scale_factor = scale_factor > 1.0f; } else if( scale_factor && scale_factor != 1.0f ) { float scaled_width = (float)refState.width / scale_factor; if( scaled_width >= hud_scale_minimal_width.value ) apply_scale_factor = true; } if( apply_scale_factor ) { clgame.scrInfo.iWidth = (float)refState.width / scale_factor; clgame.scrInfo.iHeight = (float)refState.height / scale_factor; SetBits( clgame.scrInfo.iFlags, SCRINFO_STRETCHED ); } else { clgame.scrInfo.iWidth = refState.width; clgame.scrInfo.iHeight = refState.height; ClearBits( clgame.scrInfo.iFlags, SCRINFO_STRETCHED ); } if( !pscrinfo ) return 0; if( pscrinfo->iSize != clgame.scrInfo.iSize ) clgame.scrInfo.iSize = pscrinfo->iSize; // copy screeninfo out memcpy( pscrinfo, &clgame.scrInfo, clgame.scrInfo.iSize ); return 1; } /* ============= pfnSetCrosshair setup crosshair ============= */ static void GAME_EXPORT pfnSetCrosshair( HSPRITE hspr, wrect_t rc, int r, int g, int b ) { clgame.ds.rgbaCrosshair[0] = (byte)r; clgame.ds.rgbaCrosshair[1] = (byte)g; clgame.ds.rgbaCrosshair[2] = (byte)b; clgame.ds.rgbaCrosshair[3] = (byte)0xFF; clgame.ds.pCrosshair = CL_GetSpritePointer( hspr ); clgame.ds.rcCrosshair = rc; } /* ============= pfnCvar_RegisterVariable ============= */ static cvar_t *GAME_EXPORT pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags ) { // a1ba: try to mitigate outdated client.dll vulnerabilities if( !Q_stricmp( szName, "motdfile" ) || !Q_stricmp( szName, "sensitivity" )) flags |= FCVAR_PRIVILEGED; return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_CLIENTDLL )); } static int GAME_EXPORT Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ) { int flags = CMD_CLIENTDLL; // a1ba: try to mitigate outdated client.dll vulnerabilities if( !Q_stricmp( cmd_name, "motd_write" )) flags |= CMD_PRIVILEGED; return Cmd_AddCommandEx( cmd_name, function, "client command", flags, __func__ ); } /* ============= pfnHookUserMsg ============= */ static int GAME_EXPORT pfnHookUserMsg( const char *pszName, pfnUserMsgHook pfn ) { int i; // ignore blank names or invalid callbacks if( !pszName || !*pszName || !pfn ) return 0; for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) { // see if already hooked if( !Q_stricmp( clgame.msg[i].name, pszName )) return 1; } if( i == MAX_USER_MESSAGES ) { Host_Error( "%s: MAX_USER_MESSAGES hit!\n", __func__ ); return 0; } // hook new message Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name )); clgame.msg[i].func = pfn; return 1; } /* ============= pfnServerCmd ============= */ static int GAME_EXPORT pfnServerCmd( const char *szCmdString ) { if( !COM_CheckString( szCmdString )) return 0; // just like the client typed "cmd xxxxx" at the console MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); MSG_WriteString( &cls.netchan.message, szCmdString ); return 1; } /* ============= pfnClientCmd ============= */ static int GAME_EXPORT pfnClientCmd( const char *szCmdString ) { if( !COM_CheckString( szCmdString )) return 0; if( cls.initialized ) { Cbuf_AddText( szCmdString ); Cbuf_AddText( "\n" ); } else { // will exec later Q_strncat( host.deferred_cmd, szCmdString, sizeof( host.deferred_cmd )); Q_strncat( host.deferred_cmd, "\n", sizeof( host.deferred_cmd )); } return 1; } /* ============= pfnFilteredClientCmd ============= */ static int GAME_EXPORT pfnFilteredClientCmd( const char *szCmdString ) { if( !COM_CheckString( szCmdString )) return 0; // a1ba: // there should be stufftext validator, that checks // hardcoded commands and disallows them before passing to // filtered buffer, returning 0 // I've replaced it by hooking potentially exploitable // commands and variables(motd_write, motdfile, etc) in client interfaces Cbuf_AddFilteredText( szCmdString ); Cbuf_AddFilteredText( "\n" ); return 1; } /* ============= pfnGetPlayerInfo ============= */ static void GAME_EXPORT pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo ) { player_info_t *player; ent_num -= 1; // player list if offset by 1 from ents if( ent_num >= cl.maxclients || ent_num < 0 || !cl.players[ent_num].name[0] ) { pinfo->name = NULL; pinfo->thisplayer = false; return; } player = &cl.players[ent_num]; pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false; pinfo->name = player->name; pinfo->model = player->model; pinfo->spectator = player->spectator; pinfo->ping = player->ping; pinfo->packetloss = player->packet_loss; pinfo->topcolor = player->topcolor; pinfo->bottomcolor = player->bottomcolor; } /* ============= pfnPlaySoundByName ============= */ static void GAME_EXPORT pfnPlaySoundByName( const char *szSound, float volume ) { int hSound = S_RegisterSound( szSound ); S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING ); } /* ============= pfnPlaySoundByIndex ============= */ static void GAME_EXPORT pfnPlaySoundByIndex( int iSound, float volume ) { int hSound; // make sure what we in-bounds iSound = bound( 0, iSound, MAX_SOUNDS ); hSound = cl.sound_index[iSound]; if( !hSound ) return; S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING ); } /* ============= pfnTextMessageGet returns specified message from titles.txt ============= */ client_textmessage_t *CL_TextMessageGet( const char *pName ) { int i; // first check internal messages for( i = 0; i < MAX_TEXTCHANNELS; i++ ) { char name[MAX_VA_STRING]; Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i ); if( !Q_strcmp( pName, name )) return cl_textmessage + i; } // find desired message for( i = 0; i < clgame.numTitles; i++ ) { if( !Q_stricmp( pName, clgame.titles[i].pName )) return clgame.titles + i; } return NULL; // found nothing } /* ============= pfnDrawCharacter returns drawed chachter width (in real screen pixels) ============= */ static int GAME_EXPORT pfnDrawCharacter( int x, int y, int number, int r, int g, int b ) { rgba_t color = { r, g, b, 255 }; int flags = FONT_DRAW_HUD; if( hud_utf8.value ) flags |= FONT_DRAW_UTF8; return CL_DrawCharacter( x, y, number, color, &cls.creditsFont, flags ); } /* ============= pfnDrawConsoleString drawing string like a console string ============= */ int GAME_EXPORT pfnDrawConsoleString( int x, int y, char *string ) { cl_font_t *font = Con_GetFont( con_fontsize.value ); rgba_t color; Vector4Copy( clgame.ds.textColor, color ); Vector4Set( clgame.ds.textColor, 255, 255, 255, 255 ); return x + CL_DrawString( x, y, string, color, font, FONT_DRAW_UTF8 | FONT_DRAW_HUD ); } /* ============= pfnDrawSetTextColor set color for anything ============= */ void GAME_EXPORT pfnDrawSetTextColor( float r, float g, float b ) { // bound color and convert to byte clgame.ds.textColor[0] = (byte)bound( 0, r * 255, 255 ); clgame.ds.textColor[1] = (byte)bound( 0, g * 255, 255 ); clgame.ds.textColor[2] = (byte)bound( 0, b * 255, 255 ); clgame.ds.textColor[3] = (byte)0xFF; } /* ============= pfnDrawConsoleStringLen compute string length in screen pixels ============= */ void GAME_EXPORT pfnDrawConsoleStringLen( const char *pText, int *length, int *height ) { cl_font_t *font = Con_GetFont( con_fontsize.value ); if( height ) *height = font->charHeight; CL_DrawStringLen( font, pText, length, NULL, FONT_DRAW_UTF8 | FONT_DRAW_HUD ); } /* ============= pfnConsolePrint prints directly into console (can skip notify) ============= */ static void GAME_EXPORT pfnConsolePrint( const char *string ) { if( !COM_CheckString( string )) return; // WON GoldSrc behavior if( string[0] != 1 ) Con_Printf( "%s", string ); else Con_NPrintf( 0, "%s", string + 1 ); } /* ============= pfnCenterPrint holds and fade message at center of screen like trigger_multiple message in q1 ============= */ static void GAME_EXPORT pfnCenterPrint( const char *string ) { CL_CenterPrint( string, 0.25f ); } /* ========= GetWindowCenterX ========= */ static int GAME_EXPORT pfnGetWindowCenterX( void ) { int x = 0; #if XASH_WIN32 if( m_ignore.value ) { POINT pos; GetCursorPos( &pos ); return pos.x; } #endif #if XASH_SDL >= 2 SDL_GetWindowPosition( host.hWnd, &x, NULL ); #endif return host.window_center_x + x; } /* ========= GetWindowCenterY ========= */ static int GAME_EXPORT pfnGetWindowCenterY( void ) { int y = 0; #if XASH_WIN32 if( m_ignore.value ) { POINT pos; GetCursorPos( &pos ); return pos.y; } #endif #if XASH_SDL >= 2 SDL_GetWindowPosition( host.hWnd, NULL, &y ); #endif return host.window_center_y + y; } /* ============= pfnGetViewAngles return interpolated angles from previous frame ============= */ static void GAME_EXPORT pfnGetViewAngles( float *angles ) { if( angles ) VectorCopy( cl.viewangles, angles ); } /* ============= pfnSetViewAngles return interpolated angles from previous frame ============= */ static void GAME_EXPORT pfnSetViewAngles( float *angles ) { if( angles ) VectorCopy( angles, cl.viewangles ); } /* ============= pfnPhysInfo_ValueForKey ============= */ static const char* GAME_EXPORT pfnPhysInfo_ValueForKey( const char *key ) { return Info_ValueForKey( cls.physinfo, key ); } /* ============= pfnServerInfo_ValueForKey ============= */ static const char* GAME_EXPORT pfnServerInfo_ValueForKey( const char *key ) { return Info_ValueForKey( cl.serverinfo, key ); } /* ============= pfnGetClientMaxspeed value that come from server ============= */ static float GAME_EXPORT pfnGetClientMaxspeed( void ) { return cl.local.maxspeed; } /* ============= pfnIsNoClipping ============= */ static int GAME_EXPORT pfnIsNoClipping( void ) { return ( cl.frames[cl.parsecountmod].playerstate[cl.playernum].movetype == MOVETYPE_NOCLIP ); } /* ============= pfnGetViewModel ============= */ static cl_entity_t* GAME_EXPORT CL_GetViewModel( void ) { return &clgame.viewent; } /* ============= pfnGetClientTime ============= */ static float GAME_EXPORT pfnGetClientTime( void ) { return cl.time; } /* ============= pfnCalcShake ============= */ static void GAME_EXPORT pfnCalcShake( void ) { screen_shake_t *const shake = &clgame.shake; float frametime, fraction, freq; int i; if( cl.time > shake->time || shake->amplitude <= 0 || shake->frequency <= 0 || shake->duration <= 0 ) { // reset shake if( shake->time != 0 ) { shake->time = 0; shake->applied_angle = 0; VectorClear( shake->applied_offset ); } return; } frametime = cl_clientframetime(); if( cl.time > shake->next_shake ) { // get next shake time based on frequency over duration shake->next_shake = (float)cl.time + shake->frequency / shake->duration; // randomize each shake for( i = 0; i < 3; i++ ) shake->offset[i] = COM_RandomFloat( -shake->amplitude, shake->amplitude ); shake->angle = COM_RandomFloat( -shake->amplitude * 0.25f, shake->amplitude * 0.25f ); } // get initial fraction and frequency values over the duration fraction = ((float)cl.time - shake->time ) / shake->duration; freq = fraction != 0.0f ? ( shake->frequency / fraction ) * shake->frequency : 0.0f; // quickly approach zero but apply time over sine wave fraction *= fraction * sin( cl.time * freq ); // apply shake offset for( i = 0; i < 3; i++ ) shake->applied_offset[i] = shake->offset[i] * fraction; // apply roll angle shake->applied_angle = shake->angle * fraction; // decrease amplitude, but slower on longer shakes or higher frequency shake->amplitude -= shake->amplitude * ( frametime / ( shake->frequency * shake->duration )); } /* ============= pfnApplyShake ============= */ static void GAME_EXPORT pfnApplyShake( float *origin, float *angles, float factor ) { if( origin ) VectorMA( origin, factor, clgame.shake.applied_offset, origin ); if( angles ) angles[ROLL] += clgame.shake.applied_angle * factor; } /* ============= pfnIsSpectateOnly ============= */ static int GAME_EXPORT pfnIsSpectateOnly( void ) { return (cls.spectator != 0); } /* ============= pfnPointContents ============= */ int GAME_EXPORT PM_CL_PointContents( const float *p, int *truecontents ) { return PM_PointContentsPmove( clgame.pmove, p, truecontents ); } pmtrace_t *PM_CL_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) { return PM_TraceLine( clgame.pmove, start, end, flags, usehull, ignore_pe ); } static void GAME_EXPORT pfnPlaySoundByNameAtLocation( char *szSound, float volume, float *origin ) { int hSound = S_RegisterSound( szSound ); S_StartSound( origin, cl.viewentity, CHAN_AUTO, hSound, volume, ATTN_NORM, PITCH_NORM, 0 ); } /* ============= pfnPrecacheEvent ============= */ static word GAME_EXPORT pfnPrecacheEvent( int type, const char* psz ) { return CL_EventIndex( psz ); } /* ============= pfnHookEvent ============= */ static void GAME_EXPORT pfnHookEvent( const char *filename, pfnEventHook pfn ) { char name[64]; cl_user_event_t *ev; int i; // ignore blank names if( !filename || !*filename ) return; Q_strncpy( name, filename, sizeof( name )); COM_FixSlashes( name ); // find an empty slot for( i = 0; i < MAX_EVENTS; i++ ) { ev = clgame.events[i]; if( !ev ) break; if( !Q_stricmp( name, ev->name ) && ev->func != NULL ) { Con_Reportf( S_WARN "%s: %s already hooked!\n", __func__, name ); return; } } CL_RegisterEvent( i, name, pfn ); } /* ============= pfnKillEvent ============= */ static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname ) { int i; event_state_t *es; event_info_t *ei; word eventIndex = CL_EventIndex( eventname ); if( eventIndex >= MAX_EVENTS ) return; if( entnum < 0 || entnum >= clgame.maxEntities ) return; es = &cl.events; // find all events with specified index and kill it for( i = 0; i < MAX_EVENT_QUEUE; i++ ) { ei = &es->ei[i]; if( ei->index == eventIndex && ei->entity_index == entnum ) { CL_ResetEvent( ei ); break; } } } /* ============= pfnPlaySound ============= */ static void GAME_EXPORT pfnPlaySound( int ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch ) { S_StartSound( org, ent, chan, S_RegisterSound( samp ), vol, attn, pitch, flags ); } /* ============= CL_FindModelIndex ============= */ static int GAME_EXPORT CL_FindModelIndex( const char *m ) { char filepath[MAX_QPATH]; int i; if( !COM_CheckString( m )) return 0; Q_strncpy( filepath, m, sizeof( filepath )); COM_FixSlashes( filepath ); for( i = 0; i < cl.nummodels; i++ ) { if( !cl.models[i+1] ) continue; if( !Q_stricmp( cl.models[i+1]->name, filepath )) return i+1; } return 0; } /* ============= pfnIsLocal ============= */ static int GAME_EXPORT pfnIsLocal( int playernum ) { if( playernum == cl.playernum ) return true; return false; } /* ============= pfnLocalPlayerDucking ============= */ static int GAME_EXPORT pfnLocalPlayerDucking( void ) { return (cl.local.usehull == 1) ? true : false; } /* ============= pfnLocalPlayerViewheight ============= */ static void GAME_EXPORT pfnLocalPlayerViewheight( float *view_ofs ) { if( view_ofs ) VectorCopy( cl.viewheight, view_ofs ); } /* ============= pfnLocalPlayerBounds ============= */ static void GAME_EXPORT pfnLocalPlayerBounds( int hull, float *mins, float *maxs ) { if( hull >= 0 && hull < 4 ) { if( mins ) VectorCopy( host.player_mins[hull], mins ); if( maxs ) VectorCopy( host.player_maxs[hull], maxs ); } } /* ============= pfnIndexFromTrace ============= */ static int GAME_EXPORT pfnIndexFromTrace( struct pmtrace_s *pTrace ) { #if 0 // Velaron: breaks compatibility with mods that call the function after CL_PopPMStates if( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent ) { // return cl.entities number return clgame.pmove->physents[pTrace->ent].info; } return -1; #endif return clgame.pmove->physents[pTrace->ent].info; } /* ============= pfnGetPhysent ============= */ physent_t *pfnGetPhysent( int idx ) { if( idx >= 0 && idx < clgame.pmove->numphysent ) { // return physent return &clgame.pmove->physents[idx]; } return NULL; } /* ============= pfnGetVisent ============= */ static physent_t *pfnGetVisent( int idx ) { if( idx >= 0 && idx < clgame.pmove->numvisent ) { // return physent return &clgame.pmove->visents[idx]; } return NULL; } static int GAME_EXPORT CL_TestLine( const vec3_t start, const vec3_t end, int flags ) { return PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags ); } /* ============= CL_PushTraceBounds ============= */ static void GAME_EXPORT CL_PushTraceBounds( int hullnum, const float *mins, const float *maxs ) { if( !host.trace_bounds_pushed ) { memcpy( host.player_mins_backup, host.player_mins, sizeof( host.player_mins_backup )); memcpy( host.player_maxs_backup, host.player_maxs, sizeof( host.player_maxs_backup )); host.trace_bounds_pushed = true; } hullnum = bound( 0, hullnum, 3 ); VectorCopy( mins, host.player_mins[hullnum] ); VectorCopy( maxs, host.player_maxs[hullnum] ); } /* ============= CL_PopTraceBounds ============= */ static void GAME_EXPORT CL_PopTraceBounds( void ) { if( !host.trace_bounds_pushed ) { Con_Reportf( S_ERROR "%s called without push!\n", __func__ ); return; } host.trace_bounds_pushed = false; memcpy( host.player_mins, host.player_mins_backup, sizeof( host.player_mins )); memcpy( host.player_maxs, host.player_maxs_backup, sizeof( host.player_maxs )); } /* ============= pfnSetTraceHull ============= */ static void GAME_EXPORT CL_SetTraceHull( int hull ) { clgame.pmove->usehull = bound( 0, hull, 3 ); } /* ============= pfnPlayerTrace ============= */ static void GAME_EXPORT CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr ) { if( !tr ) return; *tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); } /* ============= pfnPlayerTraceExt ============= */ static void GAME_EXPORT CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr ) { if( !tr ) return; *tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pfnIgnore ); } /* ============= CL_TraceTexture ============= */ const char * GAME_EXPORT PM_CL_TraceTexture( int ground, float *vstart, float *vend ) { return PM_TraceTexture( clgame.pmove, ground, vstart, vend ); } /* ============= pfnTraceSurface ============= */ struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ) { return PM_TraceSurfacePmove( clgame.pmove, ground, vstart, vend ); } /* ============= pfnGetMovevars ============= */ static movevars_t *pfnGetMoveVars( void ) { return &clgame.movevars; } /* ============= pfnStopAllSounds ============= */ static void GAME_EXPORT pfnStopAllSounds( int ent, int entchannel ) { S_StopSound( ent, entchannel, NULL ); } /* ============= CL_LoadModel ============= */ model_t *CL_LoadModel( const char *modelname, int *index ) { int i; if( index ) *index = -1; if(( i = CL_FindModelIndex( modelname )) == 0 ) return NULL; if( index ) *index = i; return CL_ModelHandle( i ); } static int GAME_EXPORT CL_AddEntity( int entityType, cl_entity_t *pEnt ) { if( !pEnt ) return false; // clear effects for all temp entities if( !pEnt->index ) pEnt->curstate.effects = 0; // let the render reject entity without model return CL_AddVisibleEntity( pEnt, entityType ); } /* ============= pfnGetGameDirectory ============= */ static const char *pfnGetGameDirectory( void ) { static char szGetGameDir[MAX_SYSPATH]; Q_strncpy( szGetGameDir, GI->gamefolder, sizeof( szGetGameDir )); return szGetGameDir; } /* ============= pfnGetLevelName ============= */ static const char *pfnGetLevelName( void ) { static char mapname[64]; // a1ba: don't return maps/.bsp if no map is loaded yet // in GoldSrc this is handled by cl.levelname field but we don't have it // so emulate this behavior here if( cls.state >= ca_connected && COM_CheckStringEmpty( clgame.mapname )) Q_snprintf( mapname, sizeof( mapname ), "maps/%s.bsp", clgame.mapname ); else mapname[0] = '\0'; // not in game return mapname; } /* ============= pfnGetScreenFade ============= */ static void GAME_EXPORT pfnGetScreenFade( struct screenfade_s *fade ) { if( fade ) *fade = clgame.fade; } /* ============= pfnSetScreenFade ============= */ static void GAME_EXPORT pfnSetScreenFade( struct screenfade_s *fade ) { if( fade ) clgame.fade = *fade; } /* ============= pfnLoadMapSprite ============= */ static model_t *pfnLoadMapSprite( const char *filename ) { model_t *mod; mod = Mod_FindName( filename, false ); if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 )) return mod; return NULL; } /* ============= COM_AddAppDirectoryToSearchPath ============= */ static void GAME_EXPORT COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName ) { FS_AddGameHierarchy( pszBaseDir, FS_NOWRITE_PATH ); } /* =========== COM_ExpandFilename Finds the file in the search path, copies over the name with the full path name. This doesn't search in the pak file. =========== */ static int GAME_EXPORT COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ) { char result[MAX_SYSPATH]; if( !COM_CheckString( fileName ) || !nameOutBuffer || nameOutBufferSize <= 0 ) return 0; // filename examples: // media\sierra.avi - D:\Xash3D\valve\media\sierra.avi // models\barney.mdl - D:\Xash3D\bshift\models\barney.mdl if( g_fsapi.GetFullDiskPath( result, sizeof( result ), fileName, false )) { // check for enough room if( Q_strlen( result ) > nameOutBufferSize ) return 0; Q_strncpy( nameOutBuffer, result, nameOutBufferSize ); return 1; } return 0; } /* ============= PlayerInfo_ValueForKey ============= */ static const char *PlayerInfo_ValueForKey( int playerNum, const char *key ) { // find the player if(( playerNum > cl.maxclients ) || ( playerNum < 1 )) return NULL; if( !cl.players[playerNum-1].name[0] ) return NULL; return Info_ValueForKey( cl.players[playerNum-1].userinfo, key ); } /* ============= PlayerInfo_SetValueForKey ============= */ static void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value ) { convar_t *var; if( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value )) return; // no changes ? var = Cvar_FindVar( key ); if( var && FBitSet( var->flags, FCVAR_USERINFO )) { Cvar_DirectSet( var, value ); } else if( Info_SetValueForStarKey( cls.userinfo, key, value, sizeof( cls.userinfo ))) { // time to update server copy of userinfo CL_UpdateInfo( key, value ); } } /* ============= pfnGetPlayerUniqueID ============= */ static qboolean GAME_EXPORT pfnGetPlayerUniqueID( int iPlayer, char playerID[16] ) { if( iPlayer < 1 || iPlayer > cl.maxclients ) return false; // make sure there is a player here.. if( !cl.players[iPlayer-1].userinfo[0] || !cl.players[iPlayer-1].name[0] ) return false; memcpy( playerID, cl.players[iPlayer-1].hashedcdkey, 16 ); return true; } /* ============= pfnGetTrackerIDForPlayer obsolete, unused ============= */ static int GAME_EXPORT pfnGetTrackerIDForPlayer( int playerSlot ) { return 0; } /* ============= pfnGetPlayerForTrackerID obsolete, unused ============= */ static int GAME_EXPORT pfnGetPlayerForTrackerID( int trackerID ) { return 0; } /* ============= pfnServerCmdUnreliable ============= */ static int GAME_EXPORT pfnServerCmdUnreliable( char *szCmdString ) { if( !COM_CheckString( szCmdString )) return 0; MSG_BeginClientCmd( &cls.datagram, clc_stringcmd ); MSG_WriteString( &cls.datagram, szCmdString ); return 1; } /* ============= pfnGetMousePos ============= */ static void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt ) { if( !ppt ) return; Platform_GetMousePos( &ppt->x, &ppt->y ); } /* ============= pfnSetMouseEnable legacy of dinput code ============= */ static void GAME_EXPORT pfnSetMouseEnable( qboolean fEnable ) { } /* ============= pfnGetServerTime ============= */ static float GAME_EXPORT pfnGetClientOldTime( void ) { return cl.oldtime; } /* ============= pfnGetGravity ============= */ static float GAME_EXPORT pfnGetGravity( void ) { return clgame.movevars.gravity; } /* ============= pfnEnableTexSort TODO: implement ============= */ static void GAME_EXPORT pfnEnableTexSort( int enable ) { } /* ============= pfnSetLightmapColor TODO: implement ============= */ static void GAME_EXPORT pfnSetLightmapColor( float red, float green, float blue ) { } /* ============= pfnSetLightmapScale TODO: implement ============= */ static void GAME_EXPORT pfnSetLightmapScale( float scale ) { } /* ============= pfnSPR_DrawGeneric ============= */ static void GAME_EXPORT pfnSPR_DrawGeneric( int frame, int x, int y, const wrect_t *prc, int blendsrc, int blenddst, int width, int height ) { #if 0 // REFTODO: pglEnable( GL_BLEND ); pglBlendFunc( blendsrc, blenddst ); // g-cont. are params is valid? #endif SPR_DrawGeneric( frame, x, y, width, height, prc ); } /* ============= LocalPlayerInfo_ValueForKey ============= */ static const char *GAME_EXPORT LocalPlayerInfo_ValueForKey( const char* key ) { return Info_ValueForKey( cls.userinfo, key ); } /* ============= pfnVGUI2DrawCharacter ============= */ static int GAME_EXPORT pfnVGUI2DrawCharacter( int x, int y, int number, unsigned int font ) { return pfnDrawCharacter( x, y, number, 255, 255, 255 ); } /* ============= pfnVGUI2DrawCharacterAdditive ============= */ static int GAME_EXPORT pfnVGUI2DrawCharacterAdditive( int x, int y, int ch, int r, int g, int b, unsigned int font ) { return pfnDrawCharacter( x, y, ch, r, g, b ); } /* ============= pfnDrawString ============= */ static int GAME_EXPORT pfnDrawString( int x, int y, const char *str, int r, int g, int b ) { rgba_t color = { r, g, b, 255 }; int flags = FONT_DRAW_HUD | FONT_DRAW_NOLF; if( hud_utf8.value ) SetBits( flags, FONT_DRAW_UTF8 ); return CL_DrawString( x, y, str, color, &cls.creditsFont, flags ); } /* ============= pfnDrawStringReverse ============= */ static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int r, int g, int b ) { rgba_t color = { r, g, b, 255 }; int flags = FONT_DRAW_HUD | FONT_DRAW_NOLF; int width; if( hud_utf8.value ) SetBits( flags, FONT_DRAW_UTF8 ); CL_DrawStringLen( &cls.creditsFont, str, &width, NULL, flags ); x -= width; return CL_DrawString( x, y, str, color, &cls.creditsFont, flags ); } /* ============= GetCareerGameInterface ============= */ static void *GAME_EXPORT GetCareerGameInterface( void ) { Msg( "^1Career GameInterface called!\n" ); return NULL; } /* ============= pfnPlaySoundVoiceByName ============= */ static void GAME_EXPORT pfnPlaySoundVoiceByName( char *filename, float volume, int pitch ) { int hSound = S_RegisterSound( filename ); S_StartSound( NULL, cl.viewentity, CHAN_NETWORKVOICE_END + 1, hSound, volume, 1.0, pitch, SND_STOP_LOOPING ); } /* ============= pfnMP3_InitStream ============= */ static void GAME_EXPORT pfnMP3_InitStream( char *filename, int looping ) { if( !filename ) { S_StopBackgroundTrack(); return; } if( looping ) { S_StartBackgroundTrack( filename, filename, 0, false ); } else { S_StartBackgroundTrack( filename, NULL, 0, false ); } } /* ============= pfnPlaySoundByNameAtPitch ============= */ static void GAME_EXPORT pfnPlaySoundByNameAtPitch( char *filename, float volume, int pitch ) { int hSound = S_RegisterSound( filename ); S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, 1.0, pitch, SND_STOP_LOOPING ); } /* ============= pfnFillRGBABlend ============= */ static void GAME_EXPORT CL_FillRGBABlend( int x, int y, int w, int h, int r, int g, int b, int a ) { float x_ = x, y_ = y, w_ = w, h_ = h; r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); SPR_AdjustSize( &x_, &y_, &w_, &h_ ); ref.dllFuncs.FillRGBA( kRenderTransTexture, x_, y_, w_, h_, r, g, b, a ); } /* ============= pfnGetAppID ============= */ static int GAME_EXPORT pfnGetAppID( void ) { return 70; // Half-Life AppID } /* ============= pfnVguiWrap2_GetMouseDelta TODO: implement ============= */ static void GAME_EXPORT pfnVguiWrap2_GetMouseDelta( int *x, int *y ) { } /* ============= pfnParseFile handle colon separately ============= */ static char *pfnParseFile( char *data, char *token ) { return COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL, NULL ); } /* ================= TriAPI implementation ================= */ /* ================= TriRenderMode ================= */ void TriRenderMode( int mode ) { clgame.ds.renderMode = mode; ref.dllFuncs.TriRenderMode( mode ); } /* ================= TriColor4f ================= */ void TriColor4f( float r, float g, float b, float a ) { if( clgame.ds.renderMode == kRenderTransAlpha ) ref.dllFuncs.Color4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f ); else ref.dllFuncs.Color4f( r * a, g * a, b * a, 1.0 ); clgame.ds.triRGBA[0] = r; clgame.ds.triRGBA[1] = g; clgame.ds.triRGBA[2] = b; clgame.ds.triRGBA[3] = a; } /* ============= TriColor4ub ============= */ void TriColor4ub( byte r, byte g, byte b, byte a ) { clgame.ds.triRGBA[0] = r * (1.0f / 255.0f); clgame.ds.triRGBA[1] = g * (1.0f / 255.0f); clgame.ds.triRGBA[2] = b * (1.0f / 255.0f); clgame.ds.triRGBA[3] = a * (1.0f / 255.0f); ref.dllFuncs.Color4f( clgame.ds.triRGBA[0], clgame.ds.triRGBA[1], clgame.ds.triRGBA[2], 1.0f ); } /* ============= TriBrightness ============= */ void TriBrightness( float brightness ) { float r, g, b; r = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness; g = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness; b = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness; ref.dllFuncs.Color4f( r, g, b, 1.0f ); } /* ============= TriCullFace ============= */ void TriCullFace( TRICULLSTYLE style ) { clgame.ds.cullMode = style; ref.dllFuncs.CullFace( style ); } /* ============= TriWorldToScreen convert world coordinates (x,y,z) into screen (x, y) ============= */ int TriWorldToScreen( const float *world, float *screen ) { return ref.dllFuncs.WorldToScreen( world, screen ); } /* ============= TriBoxInPVS check box in pvs (absmin, absmax) ============= */ int TriBoxInPVS( float *mins, float *maxs ) { return Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( )); } /* ============= TriLightAtPoint NOTE: dlights are ignored ============= */ void TriLightAtPoint( float *pos, float *value ) { colorVec vLightColor; if( !pos || !value ) return; vLightColor = ref.dllFuncs.R_LightPoint( pos ); value[0] = vLightColor.r; value[1] = vLightColor.g; value[2] = vLightColor.b; } /* ============= TriColor4fRendermode Heavy legacy of Quake... ============= */ void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ) { if( rendermode == kRenderTransAlpha ) { clgame.ds.triRGBA[3] = a / 255.0f; ref.dllFuncs.Color4f( r, g, b, a ); } else ref.dllFuncs.Color4f( r * a, g * a, b * a, 1.0f ); } /* ============= TriSpriteTexture bind current texture ============= */ int TriSpriteTexture( model_t *pSpriteModel, int frame ) { int gl_texturenum; if(( gl_texturenum = ref.dllFuncs.R_GetSpriteTexture( pSpriteModel, frame )) <= 0 ) return 0; ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum ); return 1; } /* ================= DemoApi implementation ================= */ /* ================= Demo_IsTimeDemo ================= */ static int GAME_EXPORT Demo_IsTimeDemo( void ) { return cls.timedemo; } /* ================= NetworkApi implementation ================= */ /* ================= NetAPI_InitNetworking ================= */ static void GAME_EXPORT NetAPI_InitNetworking( void ) { NET_Config( true, false ); // allow remote } /* ================= NetAPI_InitNetworking ================= */ static void GAME_EXPORT NetAPI_Status( net_status_t *status ) { qboolean connected = false; int packet_loss = 0; Assert( status != NULL ); if( cls.state > ca_disconnected && cls.state != ca_cinematic ) connected = true; if( cls.state == ca_active ) packet_loss = bound( 0, (int)cls.packet_loss, 100 ); status->connected = connected; status->connection_time = (connected) ? (host.realtime - cls.netchan.connect_time) : 0.0; status->latency = (connected) ? cl.frames[cl.parsecountmod].latency : 0.0; status->remote_address = cls.netchan.remote_address; status->packet_loss = packet_loss; NET_GetLocalAddress( &status->local_address, NULL ); // NetAPI doesn't know about IPv6 status->rate = rate.value; } /* ================= NetAPI_SendRequest ================= */ static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response ) { net_request_t *nr = NULL; int i; if( !response ) { Con_DPrintf( S_ERROR "%s: no callbcak specified for request with context %i!\n", __func__, context ); return; } if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX ) return; // IPX no longer support if( request == NETAPI_REQUEST_SERVERLIST ) return; // no support for server list requests // find a free request for( i = 0; i < MAX_REQUESTS; i++ ) { nr = &clgame.net_requests[i]; if( !nr->pfnFunc ) break; } if( i == MAX_REQUESTS ) { double max_timeout = 0; // no free requests? use oldest for( i = 0, nr = NULL; i < MAX_REQUESTS; i++ ) { if(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout ) { max_timeout = host.realtime - clgame.net_requests[i].timesend; nr = &clgame.net_requests[i]; } } } Assert( nr != NULL ); // clear slot memset( nr, 0, sizeof( *nr )); // create a new request nr->timesend = host.realtime; nr->timeout = nr->timesend + timeout; nr->pfnFunc = response; nr->resp.context = context; nr->resp.type = request; nr->resp.remote_address = *remote_address; nr->flags = flags; // local servers request Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_NETINFO" %i %i %i", FBitSet( flags, FNETAPI_LEGACY_PROTOCOL ) ? PROTOCOL_LEGACY_VERSION : PROTOCOL_VERSION, context, request ); } /* ================= NetAPI_CancelRequest ================= */ static void GAME_EXPORT NetAPI_CancelRequest( int context ) { net_request_t *nr; int i; ; // find a specified request for( i = 0; i < MAX_REQUESTS; i++ ) { nr = &clgame.net_requests[i]; if( clgame.net_requests[i].resp.context == context ) { if( nr->pfnFunc ) { SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); nr->resp.ping = host.realtime - nr->timesend; nr->pfnFunc( &nr->resp ); } memset( &clgame.net_requests[i], 0, sizeof( net_request_t )); break; } } } /* ================= NetAPI_CancelAllRequests ================= */ void GAME_EXPORT NetAPI_CancelAllRequests( void ) { net_request_t *nr; int i; // tell the user about cancel for( i = 0; i < MAX_REQUESTS; i++ ) { nr = &clgame.net_requests[i]; if( !nr->pfnFunc ) continue; // not used SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); nr->resp.ping = host.realtime - nr->timesend; nr->pfnFunc( &nr->resp ); } memset( clgame.net_requests, 0, sizeof( clgame.net_requests )); } /* ================= NetAPI_AdrToString ================= */ static const char *NetAPI_AdrToString( netadr_t *a ) { return NET_AdrToString( *a ); } /* ================= NetAPI_CompareAdr ================= */ static int GAME_EXPORT NetAPI_CompareAdr( netadr_t *a, netadr_t *b ) { return NET_CompareAdr( *a, *b ); } /* ================= NetAPI_RemoveKey ================= */ static void GAME_EXPORT NetAPI_RemoveKey( char *s, const char *key ) { Info_RemoveKey( s, key ); } /* ================= NetAPI_SetValueForKey ================= */ static void GAME_EXPORT NetAPI_SetValueForKey( char *s, const char *key, const char *value, int maxsize ) { if( key[0] == '*' ) return; Info_SetValueForStarKey( s, key, value, maxsize ); } /* ================= IVoiceTweak implementation TODO: implement ================= */ /* ================= Voice_StartVoiceTweakMode ================= */ static int GAME_EXPORT Voice_StartVoiceTweakMode( void ) { return 0; } /* ================= Voice_EndVoiceTweakMode ================= */ static void GAME_EXPORT Voice_EndVoiceTweakMode( void ) { } /* ================= Voice_SetControlFloat ================= */ static void GAME_EXPORT Voice_SetControlFloat( VoiceTweakControl iControl, float value ) { } /* ================= Voice_GetControlFloat ================= */ static float GAME_EXPORT Voice_GetControlFloat( VoiceTweakControl iControl ) { return 1.0f; } static void GAME_EXPORT VGui_ViewportPaintBackground( int extents[4] ) { // stub } // shared between client and server triangleapi_t gTriApi; static efx_api_t gEfxApi = { R_AllocParticle, R_BlobExplosion, R_Blood, R_BloodSprite, R_BloodStream, R_BreakModel, R_Bubbles, R_BubbleTrail, R_BulletImpactParticles, R_EntityParticles, R_Explosion, R_FizzEffect, R_FireField, R_FlickerParticles, R_FunnelSprite, R_Implosion, R_LargeFunnel, R_LavaSplash, R_MultiGunshot, R_MuzzleFlash, R_ParticleBox, R_ParticleBurst, R_ParticleExplosion, R_ParticleExplosion2, R_ParticleLine, R_PlayerSprites, R_Projectile, R_RicochetSound, R_RicochetSprite, R_RocketFlare, R_RocketTrail, R_RunParticleEffect, R_ShowLine, R_SparkEffect, R_SparkShower, R_SparkStreaks, R_Spray, R_Sprite_Explode, R_Sprite_Smoke, R_Sprite_Spray, R_Sprite_Trail, R_Sprite_WallPuff, R_StreakSplash, R_TracerEffect, R_UserTracerParticle, R_TracerParticles, R_TeleportSplash, R_TempSphereModel, R_TempModel, R_DefaultSprite, R_TempSprite, CL_DecalIndex, CL_DecalIndexFromName, CL_DecalShoot, R_AttachTentToPlayer, R_KillAttachedTents, R_BeamCirclePoints, R_BeamEntPoint, R_BeamEnts, R_BeamFollow, R_BeamKill, R_BeamLightning, R_BeamPoints, R_BeamRing, CL_AllocDlight, CL_AllocElight, CL_TempEntAlloc, CL_TempEntAllocNoModel, CL_TempEntAllocHigh, CL_TempEntAllocCustom, R_GetPackedColor, R_LookupColor, CL_DecalRemoveAll, CL_FireCustomDecal, }; static event_api_t gEventApi = { EVENT_API_VERSION, pfnPlaySound, S_StopSound, CL_FindModelIndex, pfnIsLocal, pfnLocalPlayerDucking, pfnLocalPlayerViewheight, pfnLocalPlayerBounds, pfnIndexFromTrace, pfnGetPhysent, CL_SetUpPlayerPrediction, CL_PushPMStates, CL_PopPMStates, CL_SetSolidPlayers, CL_SetTraceHull, CL_PlayerTrace, CL_WeaponAnim, pfnPrecacheEvent, CL_PlaybackEvent, PM_CL_TraceTexture, pfnStopAllSounds, pfnKillEvents, CL_PlayerTraceExt, // Xash3D added CL_SoundFromIndex, pfnTraceSurface, pfnGetMoveVars, CL_VisTraceLine, pfnGetVisent, CL_TestLine, CL_PushTraceBounds, CL_PopTraceBounds, }; static demo_api_t gDemoApi = { (void *)CL_IsRecordDemo, (void *)CL_IsPlaybackDemo, Demo_IsTimeDemo, CL_WriteDemoUserMessage, }; net_api_t gNetApi = { NetAPI_InitNetworking, NetAPI_Status, NetAPI_SendRequest, NetAPI_CancelRequest, NetAPI_CancelAllRequests, NetAPI_AdrToString, NetAPI_CompareAdr, (void *)NET_StringToAdr, Info_ValueForKey, NetAPI_RemoveKey, NetAPI_SetValueForKey, }; static IVoiceTweak gVoiceApi = { Voice_StartVoiceTweakMode, Voice_EndVoiceTweakMode, Voice_SetControlFloat, Voice_GetControlFloat, }; // engine callbacks static cl_enginefunc_t gEngfuncs = { pfnSPR_Load, pfnSPR_Frames, pfnSPR_Height, pfnSPR_Width, pfnSPR_Set, pfnSPR_Draw, pfnSPR_DrawHoles, pfnSPR_DrawAdditive, SPR_EnableScissor, SPR_DisableScissor, SPR_GetList, CL_FillRGBA, CL_GetScreenInfo, pfnSetCrosshair, pfnCvar_RegisterClientVariable, Cvar_VariableValue, Cvar_VariableString, Cmd_AddClientCommand, pfnHookUserMsg, pfnServerCmd, pfnClientCmd, pfnGetPlayerInfo, pfnPlaySoundByName, pfnPlaySoundByIndex, AngleVectors, CL_TextMessageGet, pfnDrawCharacter, pfnDrawConsoleString, pfnDrawSetTextColor, pfnDrawConsoleStringLen, pfnConsolePrint, pfnCenterPrint, pfnGetWindowCenterX, pfnGetWindowCenterY, pfnGetViewAngles, pfnSetViewAngles, CL_GetMaxClients, Cvar_SetValue, Cmd_Argc, Cmd_Argv, Con_Printf, Con_DPrintf, Con_NPrintf, Con_NXPrintf, pfnPhysInfo_ValueForKey, pfnServerInfo_ValueForKey, pfnGetClientMaxspeed, COM_CheckParm, Key_Event, Platform_GetMousePos, pfnIsNoClipping, CL_GetLocalPlayer, CL_GetViewModel, CL_GetEntityByIndex, pfnGetClientTime, pfnCalcShake, pfnApplyShake, PM_CL_PointContents, CL_WaterEntity, PM_CL_TraceLine, CL_LoadModel, CL_AddEntity, CL_GetSpritePointer, pfnPlaySoundByNameAtLocation, pfnPrecacheEvent, CL_PlaybackEvent, CL_WeaponAnim, COM_RandomFloat, COM_RandomLong, pfnHookEvent, Con_Visible, pfnGetGameDirectory, pfnCVarGetPointer, Key_LookupBinding, pfnGetLevelName, pfnGetScreenFade, pfnSetScreenFade, VGui_GetPanel, VGui_ViewportPaintBackground, COM_LoadFile, pfnParseFile, COM_FreeFile, &gTriApi, &gEfxApi, &gEventApi, &gDemoApi, &gNetApi, &gVoiceApi, pfnIsSpectateOnly, pfnLoadMapSprite, COM_AddAppDirectoryToSearchPath, COM_ExpandFilename, PlayerInfo_ValueForKey, PlayerInfo_SetValueForKey, pfnGetPlayerUniqueID, pfnGetTrackerIDForPlayer, pfnGetPlayerForTrackerID, pfnServerCmdUnreliable, pfnGetMousePos, Platform_SetMousePos, pfnSetMouseEnable, Cvar_GetList, (void*)Cmd_GetFirstFunctionHandle, (void*)Cmd_GetNextFunctionHandle, (void*)Cmd_GetName, pfnGetClientOldTime, pfnGetGravity, CL_ModelHandle, pfnEnableTexSort, pfnSetLightmapColor, pfnSetLightmapScale, pfnSequenceGet, pfnSPR_DrawGeneric, pfnSequencePickSentence, pfnDrawString, pfnDrawStringReverse, LocalPlayerInfo_ValueForKey, pfnVGUI2DrawCharacter, pfnVGUI2DrawCharacterAdditive, Sound_GetApproxWavePlayLen, GetCareerGameInterface, Cvar_Set, pfnIsCareerMatch, pfnPlaySoundVoiceByName, pfnMP3_InitStream, Sys_DoubleTime, pfnProcessTutorMessageDecayBuffer, pfnConstructTutorMessageDecayBuffer, pfnResetTutorMessageDecayData, pfnPlaySoundByNameAtPitch, CL_FillRGBABlend, pfnGetAppID, Cmd_AliasGetList, pfnVguiWrap2_GetMouseDelta, pfnFilteredClientCmd }; void CL_UnloadProgs( void ) { if( !clgame.hInstance ) return; CL_FreeEdicts(); CL_FreeTempEnts(); CL_FreeViewBeams(); CL_FreeParticles(); CL_ClearAllRemaps(); Mod_ClearUserData(); // NOTE: HLFX 0.5 has strange bug: hanging on exit if no map was loaded if( Q_stricmp( GI->gamefolder, "hlfx" ) || GI->version != 0.5f ) clgame.dllFuncs.pfnShutdown(); if( GI->internal_vgui_support ) VGui_Shutdown(); Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY ); Cvar_Unlink( FCVAR_CLIENTDLL ); Cmd_Unlink( CMD_CLIENTDLL ); COM_FreeLibrary( clgame.hInstance ); Mem_FreePool( &cls.mempool ); Mem_FreePool( &clgame.mempool ); memset( &clgame, 0, sizeof( clgame )); } qboolean CL_LoadProgs( const char *name ) { static playermove_t gpMove; CL_EXPORT_FUNCS GetClientAPI; // single export qboolean valid_single_export = false; qboolean missed_exports = false; int i; if( clgame.hInstance ) CL_UnloadProgs(); // initialize PlayerMove clgame.pmove = &gpMove; cls.mempool = Mem_AllocPool( "Client Static Pool" ); clgame.mempool = Mem_AllocPool( "Client Edicts Zone" ); clgame.entities = NULL; // a1ba: we need to check if client.dll has direct dependency on SDL2 // and if so, disable relative mouse mode #if XASH_WIN32 && !XASH_64BIT clgame.client_dll_uses_sdl = COM_CheckLibraryDirectDependency( name, OS_LIB_PREFIX "SDL2." OS_LIB_EXT, false ); Con_Printf( S_NOTE "%s uses %s for mouse input\n", name, clgame.client_dll_uses_sdl ? "SDL2" : "Windows API" ); #endif // NOTE: important stuff! // vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS during LoadLibrary if( !GI->internal_vgui_support && VGui_LoadProgs( NULL )) VGui_Startup( refState.width, refState.height ); else GI->internal_vgui_support = true; // we failed to load vgui_support, but let's probe client.dll for support anyway clgame.hInstance = COM_LoadLibrary( name, false, false ); if( !clgame.hInstance ) return false; // delayed vgui initialization for internal support if( GI->internal_vgui_support && VGui_LoadProgs( clgame.hInstance )) VGui_Startup( refState.width, refState.height ); // clear exports ClearExports( cdll_exports, ARRAYSIZE( cdll_exports )); // trying to get single export if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "GetClientAPI" )) != NULL ) { Con_Reportf( "%s: found single callback export\n", __func__ ); // trying to fill interface now GetClientAPI( &clgame.dllFuncs ); } else if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "F" )) != NULL ) { Con_Reportf( "%s: found single callback export (secured client dlls)\n", __func__ ); // trying to fill interface now CL_GetSecuredClientAPI( GetClientAPI ); } if( GetClientAPI != NULL ) // check critical functions again valid_single_export = ValidateExports( cdll_exports, ARRAYSIZE( cdll_exports )); for( i = 0; i < ARRAYSIZE( cdll_exports ); i++ ) { if( *(cdll_exports[i].func) != NULL ) continue; // already gott through 'F' or 'GetClientAPI' // functions are cleared before all the extensions are evaluated if(( *(cdll_exports[i].func) = (void *)COM_GetProcAddress( clgame.hInstance, cdll_exports[i].name )) == NULL ) { Con_Reportf( S_ERROR "%s: failed to get address of %s proc\n", __func__, cdll_exports[i].name ); // print all not found exports at once, for debug missed_exports = true; } } if( missed_exports ) { COM_FreeLibrary( clgame.hInstance ); clgame.hInstance = NULL; return false; } // it may be loaded through 'GetClientAPI' so we don't need to clear them if( !valid_single_export ) ClearExports( cdll_new_exports, ARRAYSIZE( cdll_new_exports )); for( i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ ) { if( *(cdll_new_exports[i].func) != NULL ) continue; // already gott through 'F' or 'GetClientAPI' // functions are cleared before all the extensions are evaluated // NOTE: new exports can be missed without stop the engine if(( *(cdll_new_exports[i].func) = (void *)COM_GetProcAddress( clgame.hInstance, cdll_new_exports[i].name )) == NULL ) Con_Reportf( S_WARN "%s: failed to get address of %s proc\n", __func__, cdll_new_exports[i].name ); } if( !clgame.dllFuncs.pfnInitialize( &gEngfuncs, CLDLL_INTERFACE_VERSION )) { COM_FreeLibrary( clgame.hInstance ); Con_Reportf( "%s: can't init client API\n", __func__ ); clgame.hInstance = NULL; return false; } Cvar_FullSet( "host_clientloaded", "1", FCVAR_READ_ONLY ); clgame.maxRemapInfos = 0; // will be alloc on first call CL_InitEdicts(); clgame.maxEntities = 2; // world + localclient (have valid entities not in game) CL_InitCDAudio( "media/cdaudio.txt" ); CL_InitTitles( "titles.txt" ); CL_InitParticles (); CL_InitViewBeams (); CL_InitTempEnts (); if( !R_InitRenderAPI()) // Xash3D extension Con_Reportf( S_WARN "%s: couldn't get render API\n", __func__ ); if( !Mobile_Init() ) // Xash3D FWGS extension: mobile interface Con_Reportf( S_WARN "%s: couldn't get mobility API\n", __func__ ); CL_InitEdicts( cl.maxclients ); // initailize local player and world CL_InitClientMove(); // initialize pm_shared // initialize game clgame.dllFuncs.pfnInit(); ref.dllFuncs.CL_InitStudioAPI(); return true; } ================================================ FILE: engine/client/cl_gameui.c ================================================ /* cl_menu.c - menu dlls interaction Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "const.h" #include "library.h" #include "input.h" #include "server.h" // !!svgame.hInstance #include "vid_common.h" #include "ref_common.h" static void UI_UpdateUserinfo( void ); gameui_static_t gameui; static void UI_ToggleAllowConsole_f( void ) { host.allow_console = host.allow_console_init = true; if( gameui.globals ) gameui.globals->developer = true; } void UI_UpdateMenu( float realtime ) { if( !gameui.hInstance ) return; // if some deferred cmds is waiting if( UI_IsVisible() && COM_CheckString( host.deferred_cmd )) { Cbuf_AddText( host.deferred_cmd ); host.deferred_cmd[0] = '\0'; Cbuf_Execute(); return; } // don't show menu while level is loaded if( GameState->nextstate != STATE_RUNFRAME && !GameState->loadGame ) return; // menu time (not paused, not clamped) gameui.globals->time = host.realtime; gameui.globals->frametime = host.realframetime; gameui.globals->demoplayback = cls.demoplayback; gameui.globals->demorecording = cls.demorecording; gameui.globals->developer = host.allow_console; gameui.dllFuncs.pfnRedraw( realtime ); UI_UpdateUserinfo(); } void UI_KeyEvent( int key, qboolean down ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnKeyEvent( key, down ); } void UI_MouseMove( int x, int y ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnMouseMove( x, y ); } void UI_SetActiveMenu( qboolean fActive ) { movie_state_t *cin_state; if( !gameui.hInstance ) { if( !fActive ) Key_SetKeyDest( key_game ); return; } gameui.drawLogo = fActive; gameui.dllFuncs.pfnSetActiveMenu( fActive ); if( !fActive ) { // close logo when menu is shutdown cin_state = AVI_GetState( CIN_LOGO ); AVI_CloseVideo( cin_state ); } } void UI_AddServerToList( netadr_t adr, const char *info ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnAddServerToList( adr, info ); } void UI_GetCursorPos( int *pos_x, int *pos_y ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnGetCursorPos( pos_x, pos_y ); } void UI_SetCursorPos( int pos_x, int pos_y ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnSetCursorPos( pos_x, pos_y ); } void UI_ShowCursor( qboolean show ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnShowCursor( show ); } qboolean UI_CreditsActive( void ) { if( !gameui.hInstance ) return 0; return gameui.dllFuncs.pfnCreditsActive(); } void UI_CharEvent( int key ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnCharEvent( key ); } qboolean UI_MouseInRect( void ) { if( !gameui.hInstance ) return 1; return gameui.dllFuncs.pfnMouseInRect(); } qboolean UI_IsVisible( void ) { if( !gameui.hInstance ) return 0; return gameui.dllFuncs.pfnIsVisible(); } /* ======================= UI_AddTouchButtonToList send button parameters to menu ======================= */ void UI_AddTouchButtonToList( const char *name, const char *texture, const char *command, unsigned char *color, int flags ) { if( gameui.dllFuncs2.pfnAddTouchButtonToList ) { gameui.dllFuncs2.pfnAddTouchButtonToList( name, texture, command, color, flags ); } } /* ================= UI_ResetPing notify gameui dll about latency reset ================= */ void UI_ResetPing( void ) { if( gameui.dllFuncs2.pfnResetPing ) { gameui.dllFuncs2.pfnResetPing( ); } } /* ================= UI_ShowConnectionWarning show connection warning dialog implemented by gameui dll ================= */ void UI_ShowConnectionWarning( void ) { if( cls.state != ca_connected ) return; if( Host_IsLocalClient() ) return; if( ++cl.lostpackets == 8 ) { CL_Disconnect(); if( gameui.dllFuncs2.pfnShowConnectionWarning ) { gameui.dllFuncs2.pfnShowConnectionWarning(); } Con_DPrintf( S_WARN "Too many lost packets! Showing Network options menu\n" ); } } /* ================= UI_ShowConnectionWarning show update dialog ================= */ void UI_ShowUpdateDialog( qboolean preferStore ) { if( gameui.dllFuncs2.pfnShowUpdateDialog ) { gameui.dllFuncs2.pfnShowUpdateDialog( preferStore ); } Con_Printf( S_WARN "This version is not supported anymore. To continue, install latest engine version\n" ); } /* ================= UI_ShowConnectionWarning show message box ================= */ qboolean UI_ShowMessageBox( const char *text ) { if( gameui.dllFuncs2.pfnShowMessageBox ) { gameui.dllFuncs2.pfnShowMessageBox( text ); return true; } return false; } void UI_ConnectionProgress_Disconnect( void ) { if( gameui.dllFuncs2.pfnConnectionProgress_Disconnect ) { gameui.dllFuncs2.pfnConnectionProgress_Disconnect( ); } } void UI_ConnectionProgress_Download( const char *pszFileName, const char *pszServerName, const char *pszServerPath, int iCurrent, int iTotal, const char *comment ) { if( !gameui.dllFuncs2.pfnConnectionProgress_Download ) return; if( pszServerPath ) { char serverpath[MAX_SYSPATH]; Q_snprintf( serverpath, sizeof( serverpath ), "%s%s", pszServerName, pszServerPath ); gameui.dllFuncs2.pfnConnectionProgress_Download( pszFileName, serverpath, iCurrent, iTotal, comment ); } else { gameui.dllFuncs2.pfnConnectionProgress_Download( pszFileName, pszServerName, iCurrent, iTotal, comment ); } } void UI_ConnectionProgress_DownloadEnd( void ) { if( gameui.dllFuncs2.pfnConnectionProgress_DownloadEnd ) { gameui.dllFuncs2.pfnConnectionProgress_DownloadEnd( ); } } void UI_ConnectionProgress_Precache( void ) { if( gameui.dllFuncs2.pfnConnectionProgress_Precache ) { gameui.dllFuncs2.pfnConnectionProgress_Precache( ); } } void UI_ConnectionProgress_Connect( const char *server ) // NULL for local server { if( gameui.dllFuncs2.pfnConnectionProgress_Connect ) { gameui.dllFuncs2.pfnConnectionProgress_Connect( server ); } } void UI_ConnectionProgress_ChangeLevel( void ) { if( gameui.dllFuncs2.pfnConnectionProgress_ChangeLevel ) { gameui.dllFuncs2.pfnConnectionProgress_ChangeLevel( ); } } void UI_ConnectionProgress_ParseServerInfo( const char *server ) { if( gameui.dllFuncs2.pfnConnectionProgress_ParseServerInfo ) { gameui.dllFuncs2.pfnConnectionProgress_ParseServerInfo( server ); } } static void GAME_EXPORT UI_DrawLogo( const char *filename, float x, float y, float width, float height ) { movie_state_t *cin_state; if( !gameui.drawLogo ) return; cin_state = AVI_GetState( CIN_LOGO ); if( !AVI_IsActive( cin_state )) { string path; const char *fullpath; // run cinematic if not Q_snprintf( path, sizeof( path ), "media/%s", filename ); COM_DefaultExtension( path, ".avi", sizeof( path )); fullpath = FS_GetDiskPath( path, false ); if( FS_FileExists( path, false ) && !fullpath ) { Con_Printf( S_ERROR "Couldn't load %s from packfile. Please extract it\n", path ); gameui.drawLogo = false; return; } AVI_OpenVideo( cin_state, fullpath, false, true ); if( !( AVI_GetVideoInfo( cin_state, &gameui.logo_xres, &gameui.logo_yres, &gameui.logo_length ))) { AVI_CloseVideo( cin_state ); gameui.drawLogo = false; return; } } if( width <= 0 || height <= 0 ) { // precache call, don't draw return; } AVI_SetParm( cin_state, AVI_RENDER_TEXNUM, 0, AVI_RENDER_X, (int)x, AVI_RENDER_Y, (int)y, AVI_RENDER_W, (int)width, AVI_RENDER_H, (int)height, AVI_PARM_LAST ); // read the next frame if( !AVI_Think( cin_state )) AVI_SetParm( cin_state, AVI_REWIND, AVI_PARM_LAST ); } static int GAME_EXPORT UI_GetLogoWidth( void ) { return gameui.logo_xres; } static int GAME_EXPORT UI_GetLogoHeight( void ) { return gameui.logo_yres; } static float GAME_EXPORT UI_GetLogoLength( void ) { return gameui.logo_length; } static void UI_UpdateUserinfo( void ) { player_info_t *player; if( !host.userinfo_changed ) return; player = &gameui.playerinfo; Q_strncpy( player->userinfo, cls.userinfo, sizeof( player->userinfo )); Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name )); Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model )); player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" )); player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" )); host.userinfo_changed = false; // we got it } void Host_Credits( void ) { if( !gameui.hInstance ) return; gameui.dllFuncs.pfnFinalCredits(); } static void UI_ConvertGameInfo( gameinfo2_t *out, const gameinfo_t *in ) { out->gi_version = GAMEINFO_VERSION; Q_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder )); Q_strncpy( out->startmap, in->startmap, sizeof( out->startmap )); Q_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap )); Q_strncpy( out->demomap, in->demomap, sizeof( out->demomap )); Q_strncpy( out->title, in->title, sizeof( out->title )); Q_snprintf( out->version, sizeof( out->version ), "%g", in->version ); Q_strncpy( out->iconpath, in->iconpath, sizeof( out->iconpath )); Q_strncpy( out->game_url, in->game_url, sizeof( out->game_url )); Q_strncpy( out->update_url, in->update_url, sizeof( out->update_url )); out->size = in->size; Q_strncpy( out->type, in->type, sizeof( out->type )); Q_strncpy( out->date, in->date, sizeof( out->date )); out->gamemode = in->gamemode; if( in->nomodels ) SetBits( out->flags, GFL_NOMODELS ); if( in->noskills ) SetBits( out->flags, GFL_NOSKILLS ); if( in->render_picbutton_text ) SetBits( out->flags, GFL_RENDER_PICBUTTON_TEXT ); if( in->hd_background ) SetBits( out->flags, GFL_HD_BACKGROUND ); if( in->animated_title ) SetBits( out->flags, GFL_ANIMATED_TITLE ); } static void UI_ToOldGameInfo( GAMEINFO *out, const gameinfo2_t *in ) { Q_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder )); Q_strncpy( out->startmap, in->startmap, sizeof( out->startmap )); Q_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap )); Q_strncpy( out->title, in->title, sizeof( out->title )); Q_strncpy( out->version, in->version, sizeof( out->version )); out->flags = in->flags & 0xFFFF; Q_strncpy( out->game_url, in->game_url, sizeof( out->game_url )); Q_strncpy( out->update_url, in->update_url, sizeof( out->update_url )); Q_strncpy( out->size, Q_memprint( in->size ), sizeof( out->size )); Q_strncpy( out->type, in->type, sizeof( out->type )); Q_strncpy( out->date, in->date, sizeof( out->date )); out->gamemode = in->gamemode; } static void UI_GetModsInfo( void ) { int i; gameui.modsInfo = Mem_Calloc( gameui.mempool, sizeof( *gameui.modsInfo ) * FI->numgames ); for( i = 0; i < FI->numgames; i++ ) UI_ConvertGameInfo( &gameui.modsInfo[i], FI->games[i] ); } /* ==================== PIC_DrawGeneric draw hudsprite routine ==================== */ static void PIC_DrawGeneric( float x, float y, float width, float height, const wrect_t *prc ) { float s1, s2, t1, t2; int w, h; // assume we get sizes from image R_GetTextureParms( &w, &h, gameui.ds.gl_texturenum ); if( prc ) { // calc user-defined rectangle s1 = prc->left / (float)w; t1 = prc->top / (float)h; s2 = prc->right / (float)w; t2 = prc->bottom / (float)h; if( width == -1 && height == -1 ) { width = prc->right - prc->left; height = prc->bottom - prc->top; } } else { s1 = t1 = 0.0f; s2 = t2 = 1.0f; } if( width == -1 && height == -1 ) { width = w; height = h; } // pass scissor test if supposed if( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) return; ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, gameui.ds.gl_texturenum ); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); } /* =============================================================================== MainUI Builtin Functions =============================================================================== */ /* ========= pfnPIC_Load ========= */ static HIMAGE GAME_EXPORT pfnPIC_Load( const char *szPicName, const byte *image_buf, int image_size, int flags ) { HIMAGE tx; if( !COM_CheckString( szPicName )) { Con_Reportf( S_ERROR "%s: refusing to load image with empty name\n", __func__ ); return 0; } // add default parms to image SetBits( flags, TF_IMAGE ); Image_SetForceFlags( IL_LOAD_DECAL ); // allow decal images for menu tx = ref.dllFuncs.GL_LoadTexture( szPicName, image_buf, image_size, flags ); Image_ClearForceFlags(); return tx; } /* ========= pfnPIC_Width ========= */ static int GAME_EXPORT pfnPIC_Width( HIMAGE hPic ) { int picWidth; R_GetTextureParms( &picWidth, NULL, hPic ); return picWidth; } /* ========= pfnPIC_Height ========= */ static int GAME_EXPORT pfnPIC_Height( HIMAGE hPic ) { int picHeight; R_GetTextureParms( NULL, &picHeight, hPic ); return picHeight; } /* ========= pfnPIC_Set ========= */ static void GAME_EXPORT pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a ) { gameui.ds.gl_texturenum = hPic; r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); ref.dllFuncs.Color4ub( r, g, b, a ); } /* ========= pfnPIC_Draw ========= */ static void GAME_EXPORT pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderNormal ); PIC_DrawGeneric( x, y, width, height, prc ); } /* ========= pfnPIC_DrawTrans ========= */ static void GAME_EXPORT pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); PIC_DrawGeneric( x, y, width, height, prc ); } /* ========= pfnPIC_DrawHoles ========= */ static void GAME_EXPORT pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransAlpha ); PIC_DrawGeneric( x, y, width, height, prc ); } /* ========= pfnPIC_DrawAdditive ========= */ static void GAME_EXPORT pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc ) { ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd ); PIC_DrawGeneric( x, y, width, height, prc ); } /* ========= pfnPIC_EnableScissor ========= */ static void GAME_EXPORT pfnPIC_EnableScissor( int x, int y, int width, int height ) { // check bounds x = bound( 0, x, gameui.globals->scrWidth ); y = bound( 0, y, gameui.globals->scrHeight ); width = bound( 0, width, gameui.globals->scrWidth - x ); height = bound( 0, height, gameui.globals->scrHeight - y ); CL_EnableScissor( &gameui.ds.scissor, x, y, width, height ); } /* ========= pfnPIC_DisableScissor ========= */ static void GAME_EXPORT pfnPIC_DisableScissor( void ) { CL_DisableScissor( &gameui.ds.scissor ); } /* ============= pfnFillRGBA ============= */ static void GAME_EXPORT pfnFillRGBA( int x, int y, int width, int height, int r, int g, int b, int a ) { r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, width, height, r, g, b, a ); } /* ============= pfnCvar_RegisterVariable ============= */ static cvar_t *GAME_EXPORT pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ) { return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( szName, flags|FCVAR_GAMEUIDLL )); } static int GAME_EXPORT Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function ) { return Cmd_AddCommandEx( cmd_name, function, "gameui command", CMD_GAMEUIDLL, __func__ ); } /* ============= pfnClientCmd ============= */ static void GAME_EXPORT pfnClientCmd( int exec_now, const char *szCmdString ) { if( !szCmdString || !szCmdString[0] ) return; Cbuf_AddText( szCmdString ); Cbuf_AddText( "\n" ); // client command executes immediately if( exec_now ) Cbuf_Execute(); } /* ============= pfnPlaySound ============= */ static void GAME_EXPORT pfnPlaySound( const char *szSound ) { if( !COM_CheckString( szSound )) return; S_StartLocalSound( szSound, VOL_NORM, false ); } /* ============= pfnDrawCharacter quakefont draw character ============= */ static void GAME_EXPORT pfnDrawCharacter( int ix, int iy, int iwidth, int iheight, int ch, int ulRGBA, HIMAGE hFont ) { rgba_t color; float row, col, size; float s1, t1, s2, t2; float x = ix, y = iy; float width = iwidth; float height = iheight; ch &= 255; if( ch == ' ' ) return; if( y < -height ) return; color[3] = (ulRGBA & 0xFF000000) >> 24; color[0] = (ulRGBA & 0xFF0000) >> 16; color[1] = (ulRGBA & 0xFF00) >> 8; color[2] = (ulRGBA & 0xFF) >> 0; ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] ); col = (ch & 15) * 0.0625f + (0.5f / 256.0f); row = (ch >> 4) * 0.0625f + (0.5f / 256.0f); size = 0.0625f - (1.0f / 256.0f); s1 = col; t1 = row; s2 = s1 + size; t2 = t1 + size; // pass scissor test if supposed if( !CL_Scissor( &gameui.ds.scissor, &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) return; ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, hFont ); ref.dllFuncs.Color4ub( 255, 255, 255, 255 ); } /* ============= UI_DrawConsoleString drawing string like a console string ============= */ static int GAME_EXPORT UI_DrawConsoleString( int x, int y, const char *string ) { int drawLen; if( !string || !*string ) return 0; // silent ignore drawLen = Con_DrawString( x, y, string, gameui.ds.textColor ); MakeRGBA( gameui.ds.textColor, 255, 255, 255, 255 ); return (x + drawLen); // exclude color prexfixes } /* ============= pfnDrawSetTextColor set color for anything ============= */ static void GAME_EXPORT UI_DrawSetTextColor( int r, int g, int b, int alpha ) { // bound color and convert to byte gameui.ds.textColor[0] = r; gameui.ds.textColor[1] = g; gameui.ds.textColor[2] = b; gameui.ds.textColor[3] = alpha; } /* ==================== pfnGetPlayerModel for drawing playermodel previews ==================== */ static cl_entity_t* GAME_EXPORT pfnGetPlayerModel( void ) { return &gameui.playermodel; } /* ==================== pfnSetPlayerModel for drawing playermodel previews ==================== */ static void GAME_EXPORT pfnSetPlayerModel( cl_entity_t *ent, const char *path ) { ent->model = Mod_ForName( path, false, false ); ent->curstate.modelindex = MAX_MODELS; // unreachable index } /* ==================== pfnClearScene for drawing playermodel previews ==================== */ static void GAME_EXPORT pfnClearScene( void ) { ref.dllFuncs.R_PushScene(); ref.dllFuncs.R_ClearScene(); } /* ==================== pfnRenderScene for drawing playermodel previews ==================== */ static void GAME_EXPORT pfnRenderScene( const ref_viewpass_t *rvp ) { ref_viewpass_t copy; // to avoid division by zero if( !rvp || rvp->fov_x <= 0.0f || rvp->fov_y <= 0.0f ) return; copy = *rvp; // don't allow special modes from menu copy.flags = 0; ref.dllFuncs.R_Set2DMode( false ); GL_RenderFrame( © ); ref.dllFuncs.R_Set2DMode( true ); ref.dllFuncs.R_PopScene(); } /* ==================== pfnAddEntity adding player model into visible list ==================== */ static int GAME_EXPORT pfnAddEntity( int entityType, cl_entity_t *ent ) { if( !ref.dllFuncs.R_AddEntity( ent, entityType )) return false; return true; } /* ==================== pfnClientJoin send client connect ==================== */ static void GAME_EXPORT pfnClientJoin( const netadr_t adr ) { Cbuf_AddTextf( "connect %s\n", NET_AdrToString( adr )); } /* ==================== pfnKeyGetOverstrikeMode get global key overstrike state ==================== */ static int GAME_EXPORT pfnKeyGetOverstrikeMode( void ) { return host.key_overstrike; } /* ==================== pfnKeySetOverstrikeMode set global key overstrike mode ==================== */ static void GAME_EXPORT pfnKeySetOverstrikeMode( int fActive ) { host.key_overstrike = fActive; } /* ==================== pfnKeyGetState returns kbutton struct if found ==================== */ static void *pfnKeyGetState( const char *name ) { if( clgame.dllFuncs.KB_Find ) return clgame.dllFuncs.KB_Find( name ); return NULL; } /* ========= pfnMemAlloc ========= */ static void *pfnMemAlloc( size_t cb, const char *filename, const int fileline ) { return _Mem_Alloc( gameui.mempool, cb, true, filename, fileline ); } /* ========= pfnMemFree ========= */ static void GAME_EXPORT pfnMemFree( void *mem, const char *filename, const int fileline ) { _Mem_Free( mem, filename, fileline ); } /* ========= pfnGetGameInfo ========= */ static int GAME_EXPORT pfnGetOldGameInfo( GAMEINFO *pgameinfo ) { if( !pgameinfo ) return 0; UI_ToOldGameInfo( pgameinfo, &gameui.gameInfo ); return 1; } /* ========= pfnGetGamesList ========= */ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *numGames ) { if( numGames ) *numGames = FI->numgames; if( !gameui.oldModsInfo ) { int i; if( !gameui.modsInfo ) UI_GetModsInfo(); // first allocate array of pointers gameui.oldModsInfo = Mem_Calloc( gameui.mempool, sizeof( *gameui.oldModsInfo ) * FI->numgames ); for( i = 0; i < FI->numgames; i++ ) { gameui.oldModsInfo[i] = Mem_Calloc( gameui.mempool, sizeof( *gameui.oldModsInfo[i] )); UI_ToOldGameInfo( gameui.oldModsInfo[i], &gameui.modsInfo[i] ); } } return gameui.oldModsInfo; } /* ========= pfnGetFilesList release prev search on a next call ========= */ static char ** GAME_EXPORT pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly ) { static search_t *t = NULL; if( t ) Mem_Free( t ); // release prev search t = FS_Search( pattern, true, gamedironly ); if( !t ) { if( numFiles ) *numFiles = 0; return NULL; } if( numFiles ) *numFiles = t->numfilenames; return t->filenames; } /* ========= pfnGetClipboardData pointer must be released in call place ========= */ static char *pfnGetClipboardData( void ) { return Sys_GetClipboardData(); } /* ========= pfnCheckGameDll ========= */ static int GAME_EXPORT pfnCheckGameDll( void ) { #ifdef XASH_INTERNAL_GAMELIBS return true; #else string dllpath; if( svgame.hInstance ) return true; COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath )); if( FS_FileExists( dllpath, false )) return true; return false; #endif } /* ========= pfnChangeInstance ========= */ static void GAME_EXPORT pfnChangeInstance( const char *newInstance, const char *szFinalMessage ) { Con_Reportf( S_ERROR "%s menu call is deprecated!\n", __func__ ); } /* ========= pfnHostEndGame ========= */ static void GAME_EXPORT pfnHostEndGame( const char *szFinalMessage ) { if( !szFinalMessage ) szFinalMessage = ""; Host_EndGame( false, "%s", szFinalMessage ); } /* ========= pfnStartBackgroundTrack ========= */ static void GAME_EXPORT pfnStartBackgroundTrack( const char *introTrack, const char *mainTrack ) { S_StartBackgroundTrack( introTrack, mainTrack, 0, false ); } static void GAME_EXPORT GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) { ref.dllFuncs.GL_ProcessTexture( texnum, gamma, topColor, bottomColor ); } /* ================= UI_ShellExecute ================= */ static void GAME_EXPORT UI_ShellExecute( const char *path, const char *parms, int shouldExit ) { Platform_ShellExecute( path, parms ); if( shouldExit ) Sys_Quit( __func__ ); } /* ============== pfnParseFile legacy wrapper ============== */ static char *pfnParseFile( char *buf, char *token ) { return COM_ParseFile( buf, token, INT_MAX ); } /* ============= pfnFileExists legacy wrapper ============= */ static int pfnFileExists( const char *path, int gamedironly ) { return FS_FileExists( path, gamedironly ); } /* ============= pfnDelete legacy wrapper ============= */ static int pfnDelete( const char *path ) { return FS_Delete( path ); } static void GAME_EXPORT pfnCon_DefaultColor( int r, int g, int b ) { Con_DefaultColor( r, g, b, true ); } static void GAME_EXPORT pfnSetCursor( void *hCursor ) { uintptr_t cursor; if( !gameui.use_extended_api ) return; // ignore original Xash menus cursor = (uintptr_t)hCursor; if( cursor < dc_user || cursor > dc_last ) return; Platform_SetCursorType( cursor ); } static void GAME_EXPORT pfnGetGameDir( char *out ) { if( !out ) return; Q_strncpy( out, GI->gamefolder, sizeof( GI->gamefolder )); } // engine callbacks static const ui_enginefuncs_t gEngfuncs = { pfnPIC_Load, GL_FreeImage, pfnPIC_Width, pfnPIC_Height, pfnPIC_Set, pfnPIC_Draw, pfnPIC_DrawHoles, pfnPIC_DrawTrans, pfnPIC_DrawAdditive, pfnPIC_EnableScissor, pfnPIC_DisableScissor, pfnFillRGBA, pfnCvar_RegisterGameUIVariable, Cvar_VariableValue, Cvar_VariableString, Cvar_Set, Cvar_SetValue, Cmd_AddGameUICommand, pfnClientCmd, Cmd_RemoveCommand, Cmd_Argc, Cmd_Argv, Cmd_Args, Con_Printf, Con_DPrintf, UI_NPrintf, UI_NXPrintf, pfnPlaySound, UI_DrawLogo, UI_GetLogoWidth, UI_GetLogoHeight, UI_GetLogoLength, pfnDrawCharacter, UI_DrawConsoleString, UI_DrawSetTextColor, Con_DrawStringLen, pfnCon_DefaultColor, pfnGetPlayerModel, pfnSetPlayerModel, pfnClearScene, pfnRenderScene, pfnAddEntity, Host_Error, pfnFileExists, pfnGetGameDir, Cmd_CheckMapsList, CL_Active, pfnClientJoin, COM_LoadFileForMe, pfnParseFile, COM_FreeFile, Key_ClearStates, Key_SetKeyDest, Key_KeynumToString, Key_GetBinding, Key_SetBinding, Key_IsDown, pfnKeyGetOverstrikeMode, pfnKeySetOverstrikeMode, pfnKeyGetState, pfnMemAlloc, pfnMemFree, pfnGetOldGameInfo, pfnGetGamesList, pfnGetFilesList, SV_GetSaveComment, CL_GetDemoComment, pfnCheckGameDll, pfnGetClipboardData, UI_ShellExecute, Host_WriteServerConfig, pfnChangeInstance, pfnStartBackgroundTrack, pfnHostEndGame, COM_RandomFloat, COM_RandomLong, pfnSetCursor, pfnIsMapValid, GL_ProcessTexture, pfnCompareFileTime, VID_GetModeString, (void*)COM_SaveFile, pfnDelete }; static void pfnEnableTextInput( int enable ) { Key_EnableTextInput( enable, false ); } static int pfnGetRenderers( unsigned int num, char *short_name, size_t size1, char *long_name, size_t size2 ) { if( num >= ref.num_renderers ) return 0; if( short_name && size1 ) Q_strncpy( short_name, ref.short_names[num], size1 ); if( long_name && size2 ) Q_strncpy( long_name, ref.long_names[num], size2 ); return 1; } static char *pfnParseFileSafe( char *data, char *buf, const int size, unsigned int flags, int *len ) { return COM_ParseFileSafe( data, buf, size, flags, len, NULL ); } static gameinfo2_t *pfnGetGameInfo( int gi_version ) { if( gi_version != gameui.gameInfo.gi_version ) return NULL; return &gameui.gameInfo; } static gameinfo2_t *pfnGetModInfo( int gi_version, int i ) { if( i < 0 || i >= FI->numgames ) return NULL; if( !gameui.modsInfo ) UI_GetModsInfo(); if( gi_version != gameui.modsInfo[i].gi_version ) return NULL; return &gameui.modsInfo[i]; } static int pfnIsCvarReadOnly( const char *name ) { convar_t *cv = Cvar_FindVar( name ); if( !cv ) return -1; return FBitSet( cv->flags, FCVAR_READ_ONLY ) ? 1 : 0; } static ui_extendedfuncs_t gExtendedfuncs = { pfnEnableTextInput, Con_UtfProcessChar, Con_UtfMoveLeft, Con_UtfMoveRight, pfnGetRenderers, Sys_DoubleTime, pfnParseFileSafe, NET_AdrToString, NET_CompareAdrSort, Sys_GetNativeObject, &gNetApi, pfnGetGameInfo, pfnGetModInfo, pfnIsCvarReadOnly, R_GetRenderDevice }; void UI_UnloadProgs( void ) { if( !gameui.hInstance ) return; // deinitialize game gameui.dllFuncs.pfnShutdown(); Cmd_RemoveCommand( "ui_allowconsole" ); Cvar_FullSet( "host_gameuiloaded", "0", FCVAR_READ_ONLY ); Cvar_Unlink( FCVAR_GAMEUIDLL ); Cmd_Unlink( CMD_GAMEUIDLL ); COM_FreeLibrary( gameui.hInstance ); Mem_FreePool( &gameui.mempool ); memset( &gameui, 0, sizeof( gameui )); } qboolean UI_LoadProgs( void ) { static ui_enginefuncs_t gpEngfuncs; static ui_extendedfuncs_t gpExtendedfuncs; static ui_globalvars_t gpGlobals; UIEXTENEDEDAPI GetExtAPI; UITEXTAPI GiveTextApi; MENUAPI GetMenuAPI; string dllpath; int i; if( gameui.hInstance ) UI_UnloadProgs(); // setup globals gameui.globals = &gpGlobals; COM_GetCommonLibraryPath( LIBRARY_GAMEUI, dllpath, sizeof( dllpath )); if(!( gameui.hInstance = COM_LoadLibrary( dllpath, false, false ))) { string path = OS_LIB_PREFIX "menu." OS_LIB_EXT; FS_AllowDirectPaths( true ); // no use to load it from engine directory, as library loader // that implements internal gamelibs already knows how to load it #ifndef XASH_INTERNAL_GAMELIBS if(!( gameui.hInstance = COM_LoadLibrary( path, false, true ))) #endif { FS_AllowDirectPaths( false ); return false; } } FS_AllowDirectPaths( false ); if(( GetMenuAPI = (MENUAPI)COM_GetProcAddress( gameui.hInstance, "GetMenuAPI" )) == NULL ) { COM_FreeLibrary( gameui.hInstance ); Con_Reportf( "%s: can't init menu API\n", __func__ ); gameui.hInstance = NULL; return false; } gameui.use_extended_api = false; // make local copy of engfuncs to prevent overwrite it with user dll gpEngfuncs = gEngfuncs; gameui.mempool = Mem_AllocPool( "Menu Pool" ); if( !GetMenuAPI( &gameui.dllFuncs, &gpEngfuncs, gameui.globals )) { COM_FreeLibrary( gameui.hInstance ); Con_Reportf( "%s: can't init menu API\n", __func__ ); Mem_FreePool( &gameui.mempool ); gameui.hInstance = NULL; return false; } // make local copy of engfuncs to prevent overwrite it with user dll gpExtendedfuncs = gExtendedfuncs; memset( &gameui.dllFuncs2, 0, sizeof( gameui.dllFuncs2 )); // try to initialize new extended API if( ( GetExtAPI = (UIEXTENEDEDAPI)COM_GetProcAddress( gameui.hInstance, "GetExtAPI" ) ) ) { Con_Reportf( "%s: extended Menu API found\n", __func__ ); if( GetExtAPI( MENU_EXTENDED_API_VERSION, &gameui.dllFuncs2, &gpExtendedfuncs ) ) { Con_Reportf( "%s: extended Menu API initialized\n", __func__ ); gameui.use_extended_api = true; } } else // otherwise, fallback to old and deprecated extensions { if( ( GiveTextApi = (UITEXTAPI)COM_GetProcAddress( gameui.hInstance, "GiveTextAPI" ) ) ) { Con_Reportf( "%s: extended text API found\n", __func__ ); Con_Reportf( S_WARN "Text API is deprecated! If you are mod developer, consider moving to Extended Menu API!\n" ); if( GiveTextApi( &gpExtendedfuncs ) ) // they are binary compatible, so we can just pass extended funcs API to menu { Con_Reportf( "%s: extended text API initialized\n", __func__ ); gameui.use_extended_api = true; } } gameui.dllFuncs2.pfnAddTouchButtonToList = (ADDTOUCHBUTTONTOLIST)COM_GetProcAddress( gameui.hInstance, "AddTouchButtonToList" ); if( gameui.dllFuncs2.pfnAddTouchButtonToList ) { Con_Reportf( "%s: AddTouchButtonToList call found\n", __func__ ); Con_Reportf( S_WARN "AddTouchButtonToList is deprecated! If you are mod developer, consider moving to Extended Menu API!\n" ); } } Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); Cmd_AddRestrictedCommand( "ui_allowconsole", UI_ToggleAllowConsole_f, "unlocks developer console" ); UI_ConvertGameInfo( &gameui.gameInfo, FI->GameInfo ); // current gameinfo // setup globals gameui.globals->developer = host.allow_console; // initialize game gameui.dllFuncs.pfnInit(); return true; } ================================================ FILE: engine/client/cl_main.c ================================================ /* cl_main.c - client main loop Copyright (C) 2009 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "net_encode.h" #include "cl_tent.h" #include "input.h" #include "kbutton.h" #include "vgui_draw.h" #include "library.h" #include "vid_common.h" #include "pm_local.h" #include "multi_emulator.h" #define MAX_CMD_BUFFER 8000 #define CL_CONNECTION_TIMEOUT 15.0f #define CL_CONNECTION_RETRIES 10 #define CL_TEST_RETRIES 5 CVAR_DEFINE_AUTO( showpause, "1", 0, "show pause logo when paused" ); CVAR_DEFINE_AUTO( mp_decals, "300", FCVAR_ARCHIVE, "decals limit in multiplayer" ); static CVAR_DEFINE_AUTO( dev_overview, "0", 0, "draw level in overview-mode" ); static CVAR_DEFINE_AUTO( cl_resend, "6.0", 0, "time to resend connect" ); CVAR_DEFINE( cl_allow_download, "cl_allowdownload", "1", FCVAR_ARCHIVE, "allow to downloading resources from the server" ); static CVAR_DEFINE( cl_allow_upload, "cl_allowupload", "1", FCVAR_ARCHIVE, "allow to uploading resources to the server" ); CVAR_DEFINE_AUTO( cl_download_ingame, "1", FCVAR_ARCHIVE, "allow to downloading resources while client is active" ); static CVAR_DEFINE_AUTO( cl_logofile, "lambda", FCVAR_ARCHIVE, "player logo name" ); static CVAR_DEFINE_AUTO( cl_logocolor, "orange", FCVAR_ARCHIVE, "player logo color" ); static CVAR_DEFINE_AUTO( cl_logoext, "bmp", FCVAR_ARCHIVE, "temporary cvar to tell engine which logo must be packed" ); CVAR_DEFINE_AUTO( cl_logomaxdim, "96", FCVAR_ARCHIVE, "maximum decal dimension" ); static CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" ); CVAR_DEFINE( cl_draw_particles, "r_drawparticles", "1", FCVAR_CHEAT, "render particles" ); CVAR_DEFINE( cl_draw_tracers, "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" ); CVAR_DEFINE( cl_draw_beams, "r_drawbeams", "1", FCVAR_CHEAT, "render beams" ); static CVAR_DEFINE_AUTO( rcon_address, "", FCVAR_PRIVILEGED, "remote control address" ); CVAR_DEFINE_AUTO( cl_timeout, "60", 0, "connect timeout (in-seconds)" ); CVAR_DEFINE_AUTO( cl_nopred, "0", FCVAR_ARCHIVE|FCVAR_USERINFO, "disable client movement prediction" ); static CVAR_DEFINE_AUTO( cl_nodelta, "0", 0, "disable delta-compression for server messages" ); CVAR_DEFINE( cl_crosshair, "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); static CVAR_DEFINE_AUTO( cl_cmdbackup, "10", FCVAR_ARCHIVE, "how many additional history commands are sent" ); CVAR_DEFINE_AUTO( cl_showerror, "0", FCVAR_ARCHIVE, "show prediction error" ); CVAR_DEFINE_AUTO( cl_bmodelinterp, "1", FCVAR_ARCHIVE, "enable bmodel interpolation" ); static CVAR_DEFINE_AUTO( cl_lightstyle_lerping, "0", FCVAR_ARCHIVE, "enables animated light lerping (perfomance option)" ); CVAR_DEFINE_AUTO( cl_idealpitchscale, "0.8", 0, "how much to look up/down slopes and stairs when not using freelook" ); CVAR_DEFINE_AUTO( cl_nosmooth, "0", FCVAR_ARCHIVE, "disable smooth up stair climbing" ); CVAR_DEFINE_AUTO( cl_smoothtime, "0.1", FCVAR_ARCHIVE, "time to smooth up" ); CVAR_DEFINE_AUTO( cl_clockreset, "0.1", FCVAR_ARCHIVE, "frametime delta maximum value before reset" ); static CVAR_DEFINE_AUTO( cl_fixtimerate, "7.5", FCVAR_ARCHIVE, "time in msec to client clock adjusting" ); CVAR_DEFINE_AUTO( hud_fontscale, "1.0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud font texture" ); CVAR_DEFINE_AUTO( hud_fontrender, "0", FCVAR_ARCHIVE, "hud font render mode (0: additive, 1: holes, 2: trans)" ); CVAR_DEFINE_AUTO( hud_scale, "0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud at current resolution" ); CVAR_DEFINE_AUTO( hud_scale_minimal_width, "640", FCVAR_ARCHIVE|FCVAR_LATCH, "if hud_scale results in a HUD virtual screen smaller than this value, it won't be applied" ); CVAR_DEFINE_AUTO( cl_solid_players, "1", 0, "Make all players not solid (can't traceline them)" ); CVAR_DEFINE_AUTO( cl_updaterate, "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); CVAR_DEFINE_AUTO( cl_showevents, "0", FCVAR_ARCHIVE, "show events playback" ); CVAR_DEFINE_AUTO( cl_cmdrate, "30", FCVAR_ARCHIVE, "Max number of command packets sent to server per second" ); CVAR_DEFINE( cl_interp, "ex_interp", "0.1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "Interpolate object positions starting this many seconds in past" ); CVAR_DEFINE_AUTO( cl_nointerp, "0", 0, "disable interpolation of entities and players" ); static CVAR_DEFINE_AUTO( cl_dlmax, "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed outcoming fragment size" ); static CVAR_DEFINE_AUTO( cl_upmax, "508", FCVAR_ARCHIVE, "max allowed incoming fragment size" ); CVAR_DEFINE_AUTO( cl_lw, "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable client weapon predicting" ); CVAR_DEFINE_AUTO( cl_charset, "utf-8", FCVAR_ARCHIVE, "1-byte charset to use (iconv style)" ); CVAR_DEFINE_AUTO( cl_trace_consistency, "0", FCVAR_ARCHIVE, "enable consistency info tracing (good for developers)" ); CVAR_DEFINE_AUTO( cl_trace_stufftext, "0", FCVAR_ARCHIVE, "enable stufftext (server-to-client console commands) tracing (good for developers)" ); CVAR_DEFINE_AUTO( cl_trace_messages, "0", FCVAR_ARCHIVE|FCVAR_CHEAT, "enable message names tracing (good for developers)" ); CVAR_DEFINE_AUTO( cl_trace_events, "0", FCVAR_ARCHIVE|FCVAR_CHEAT, "enable events tracing (good for developers)" ); static CVAR_DEFINE_AUTO( cl_nat, "0", 0, "show servers running under NAT" ); CVAR_DEFINE_AUTO( hud_utf8, "0", FCVAR_ARCHIVE, "Use utf-8 encoding for hud text" ); CVAR_DEFINE_AUTO( ui_renderworld, "0", FCVAR_ARCHIVE, "render world when UI is visible" ); static CVAR_DEFINE_AUTO( cl_maxframetime, "0", 0, "set deadline timer for client rendering to catch freezes" ); CVAR_DEFINE_AUTO( cl_fixmodelinterpolationartifacts, "1", 0, "try to fix up models interpolation on a moving platforms (monsters on trains for example)" ); // // userinfo // static char username[32]; static CVAR_DEFINE_AUTO( name, username, FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_PRINTABLEONLY|FCVAR_FILTERABLE, "player name" ); static CVAR_DEFINE_AUTO( model, "", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player model ('player' is a singleplayer model)" ); static CVAR_DEFINE_AUTO( topcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player top color" ); static CVAR_DEFINE_AUTO( bottomcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player bottom color" ); CVAR_DEFINE_AUTO( rate, "25000", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" ); static CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE, "you wouldn't steal a car" ); static CVAR_DEFINE_AUTO( cl_advertise_engine_in_name, "1", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "add [Xash3D] to the nickname when connecting to GoldSrc servers" ); client_t cl; client_static_t cls; clgame_static_t clgame; static void CL_SendMasterServerScanRequest( void ); //====================================================================== int GAME_EXPORT CL_Active( void ) { return ( cls.state == ca_active ); } qboolean CL_Initialized( void ) { return cls.initialized; } //====================================================================== qboolean CL_IsInGame( void ) { if( host.type == HOST_DEDICATED ) return true; // always active for dedicated servers if( cl.background || cl.maxclients > 1 ) return true; // always active for multiplayer or background map return ( cls.key_dest == key_game ); // active if not menu or console } qboolean CL_IsInConsole( void ) { return ( cls.key_dest == key_console ); } qboolean CL_IsIntermission( void ) { return cl.intermission; } qboolean CL_IsPlaybackDemo( void ) { return cls.demoplayback; } qboolean CL_IsRecordDemo( void ) { return cls.demorecording; } qboolean CL_DisableVisibility( void ) { return cls.envshot_disable_vis; } char *CL_Userinfo( void ) { return cls.userinfo; } int CL_IsDevOverviewMode( void ) { if( dev_overview.value > 0.0f ) { if( host_developer.value || cls.spectator ) return (int)dev_overview.value; } return 0; } connprotocol_t CL_Protocol( void ) { return cls.legacymode; } /* =============== CL_CheckClientState finalize connection process and begin new frame with new cls.state =============== */ static void CL_CheckClientState( void ) { // first update is the pre-final signon stage if(( cls.state == ca_connected || cls.state == ca_validate ) && ( cls.signon == SIGNONS )) { cls.state = ca_active; cls.changelevel = false; // changelevel is done cls.changedemo = false; // changedemo is done cl.first_frame = true; // first rendering frame SCR_MakeLevelShot(); // make levelshot if needs Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar Netchan_ReportFlow( &cls.netchan ); Con_DPrintf( "client connected at %.2f sec\n", Sys_DoubleTime() - cls.timestart ); } } static int CL_GetGoldSrcFragmentSize( void *unused, fragsize_t mode ) { switch( mode ) { case FRAGSIZE_SPLIT: return 1200; // MAX_RELIABLE_PAYLOAD case FRAGSIZE_UNRELIABLE: return 1400; // MAX_ROUTABLE_PACKET default: if( cls.state == ca_active ) return bound( 16, cl_dlmax.value, 1024 ); return 128; } } static int CL_GetFragmentSize( void *unused, fragsize_t mode ) { switch( mode ) { case FRAGSIZE_SPLIT: return 0; case FRAGSIZE_UNRELIABLE: return NET_MAX_MESSAGE; default: if( Netchan_IsLocal( &cls.netchan )) return FRAGMENT_LOCAL_SIZE; return cl_upmax.value; } } /* ===================== CL_SignonReply An svc_signonnum has been received, perform a client side setup ===================== */ void CL_SignonReply( connprotocol_t proto ) { // g-cont. my favorite message :-) Con_Reportf( "%s: %i\n", __func__, cls.signon ); switch( cls.signon ) { case 1: CL_ServerCommand( true, proto == PROTO_GOLDSRC ? "sendents" : "begin" ); if( host_developer.value >= DEV_EXTENDED ) Mem_PrintStats(); break; case 2: SCR_EndLoadingPlaque(); if( cl.proxy_redirect && !cls.spectator ) CL_Disconnect(); cl.proxy_redirect = false; break; } } /* =============== CL_LerpPoint Determines the fraction between the last two messages that the objects should be put at. =============== */ static float CL_LerpPoint( void ) { double f = cl_serverframetime(); double frac; if( f == 0.0 || cls.timedemo ) { double fgap = cl_clientframetime(); cl.time = cl.mtime[0]; // maybe don't need for Xash demos if( cls.demoplayback ) cl.oldtime = cl.mtime[0] - fgap; return 1.0f; } if( cl_interp.value <= 0.001 ) return 1.0f; frac = ( cl.time - cl.mtime[0] ) / cl_interp.value; return frac; } /* =============== CL_DriftInterpolationAmount Drift interpolation value (this is used for server unlag system) =============== */ static int CL_DriftInterpolationAmount( int goal ) { float fgoal, maxmove, diff; int msec; fgoal = (float)goal / 1000.0f; if( fgoal != cl.local.interp_amount ) { maxmove = host.frametime * 0.05; diff = fgoal - cl.local.interp_amount; diff = bound( -maxmove, diff, maxmove ); cl.local.interp_amount += diff; } msec = cl.local.interp_amount * 1000.0f; msec = bound( 0, msec, 100 ); return msec; } /* =============== CL_ComputeClientInterpolationAmount Validate interpolation cvars, calc interpolation window =============== */ static void CL_ComputeClientInterpolationAmount( usercmd_t *cmd ) { const float epsilon = 0.001f; // to avoid float invalid comparision float min_interp; float max_interp = MAX_EX_INTERP; float interpolation_time; if( cl_updaterate.value < MIN_UPDATERATE ) { Con_Printf( "cl_updaterate minimum is %f, resetting to default (20)\n", MIN_UPDATERATE ); Cvar_Reset( "cl_updaterate" ); } if( cl_updaterate.value > MAX_UPDATERATE ) { Con_Printf( "cl_updaterate clamped at maximum (%f)\n", MAX_UPDATERATE ); Cvar_SetValue( "cl_updaterate", MAX_UPDATERATE ); } if( cls.spectator ) max_interp = 0.2f; min_interp = 1.0f / cl_updaterate.value; interpolation_time = cl_interp.value * 1000.0; if( (cl_interp.value + epsilon) < min_interp ) { Con_Printf( "ex_interp forced up to %.1f msec\n", min_interp * 1000.f ); Cvar_SetValue( "ex_interp", min_interp ); } else if( (cl_interp.value - epsilon) > max_interp ) { Con_Printf( "ex_interp forced down to %.1f msec\n", max_interp * 1000.f ); Cvar_SetValue( "ex_interp", max_interp ); } interpolation_time = bound( min_interp, interpolation_time, max_interp ); cmd->lerp_msec = CL_DriftInterpolationAmount( interpolation_time * 1000 ); } /* ================= CL_ComputePacketLoss ================= */ static void CL_ComputePacketLoss( void ) { int i, lost = 0; if( host.realtime < cls.packet_loss_recalc_time ) return; cls.packet_loss_recalc_time = host.realtime + 1.0; for( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ ) { if( cl.frames[i & CL_UPDATE_MASK].receivedtime == -1.0 ) lost++; } cls.packet_loss = lost * 100.0f / (float)CL_UPDATE_BACKUP; } /* ================= CL_UpdateFrameLerp ================= */ void CL_UpdateFrameLerp( void ) { if( cls.state != ca_active || !cl.validsequence ) return; // compute last interpolation amount cl.lerpFrac = CL_LerpPoint(); cl.commands[(cls.netchan.outgoing_sequence - 1) & CL_UPDATE_MASK].frame_lerp = cl.lerpFrac; } static void CL_FindInterpolatedAddAngle( float t, float *frac, pred_viewangle_t **prev, pred_viewangle_t **next ) { int i, i0, i1, imod; float at; imod = cl.angle_position - 1; i0 = (imod + 1) & ANGLE_MASK; i1 = (imod + 0) & ANGLE_MASK; if( cl.predicted_angle[i0].starttime >= t ) { for( i = 0; i < ANGLE_BACKUP - 2; i++ ) { at = cl.predicted_angle[imod & ANGLE_MASK].starttime; if( at == 0.0f ) break; if( at < t ) { i0 = (imod + 1) & ANGLE_MASK; i1 = (imod + 0) & ANGLE_MASK; break; } imod--; } } *next = &cl.predicted_angle[i0]; *prev = &cl.predicted_angle[i1]; // avoid division by zero (probably this should never happens) if((*prev)->starttime == (*next)->starttime ) { *prev = *next; *frac = 0.0f; return; } // time spans the two entries *frac = ( t - (*prev)->starttime ) / ((*next)->starttime - (*prev)->starttime ); *frac = bound( 0.0f, *frac, 1.0f ); } static void CL_ApplyAddAngle( void ) { pred_viewangle_t *prev = NULL, *next = NULL; float addangletotal = 0.0f; float amove, frac = 0.0f; CL_FindInterpolatedAddAngle( cl.time, &frac, &prev, &next ); if( prev && next ) addangletotal = prev->total + frac * ( next->total - prev->total ); else addangletotal = cl.prevaddangletotal; amove = addangletotal - cl.prevaddangletotal; // update input angles cl.viewangles[YAW] += amove; // remember last total cl.prevaddangletotal = addangletotal; } /* ======================================================================= CLIENT MOVEMENT COMMUNICATION ======================================================================= */ /* =============== CL_ProcessShowTexturesCmds navigate around texture atlas =============== */ static qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd ) { static int oldbuttons; int changed; int released; if( !r_showtextures.value || CL_IsDevOverviewMode( )) return false; changed = (oldbuttons ^ cmd->buttons); released = changed & (~cmd->buttons); if( released & ( IN_RIGHT|IN_MOVERIGHT )) Cvar_SetValue( "r_showtextures", r_showtextures.value + 1 ); if( released & ( IN_LEFT|IN_MOVELEFT )) Cvar_SetValue( "r_showtextures", Q_max( 1, r_showtextures.value - 1 )); oldbuttons = cmd->buttons; return true; } /* =============== CL_ProcessOverviewCmds Transform user movement into overview adjust =============== */ static qboolean CL_ProcessOverviewCmds( usercmd_t *cmd ) { ref_overview_t *ov = &clgame.overView; int sign = 1; float size = world.size[!ov->rotated] / world.size[ov->rotated]; float step = (2.0f / size) * host.realframetime; float step2 = step * 100.0f * (2.0f / ov->flZoom); if( !CL_IsDevOverviewMode() || r_showtextures.value ) return false; if( ov->flZoom < 0.0f ) sign = -1; if( cmd->upmove > 0.0f ) ov->zNear += step; else if( cmd->upmove < 0.0f ) ov->zNear -= step; if( cmd->buttons & IN_JUMP ) ov->zFar += step; else if( cmd->buttons & IN_DUCK ) ov->zFar -= step; if( cmd->buttons & IN_FORWARD ) ov->origin[ov->rotated] -= sign * step2; else if( cmd->buttons & IN_BACK ) ov->origin[ov->rotated] += sign * step2; if( ov->rotated ) { if( cmd->buttons & ( IN_RIGHT|IN_MOVERIGHT )) ov->origin[0] -= sign * step2; else if( cmd->buttons & ( IN_LEFT|IN_MOVELEFT )) ov->origin[0] += sign * step2; } else { if( cmd->buttons & ( IN_RIGHT|IN_MOVERIGHT )) ov->origin[1] += sign * step2; else if( cmd->buttons & ( IN_LEFT|IN_MOVELEFT )) ov->origin[1] -= sign * step2; } if( cmd->buttons & IN_ATTACK ) ov->flZoom += step; else if( cmd->buttons & IN_ATTACK2 ) ov->flZoom -= step; if( ov->flZoom == 0.0f ) ov->flZoom = 0.0001f; // to prevent disivion by zero return true; } /* ================= CL_UpdateClientData tell the client.dll about player origin, angles, fov, etc ================= */ static void CL_UpdateClientData( void ) { client_data_t cdat; if( cls.state != ca_active ) return; memset( &cdat, 0, sizeof( cdat ) ); VectorCopy( cl.viewangles, cdat.viewangles ); VectorCopy( clgame.entities[cl.viewentity].origin, cdat.origin ); cdat.iWeaponBits = cl.local.weapons; cdat.fov = cl.local.scr_fov; if( clgame.dllFuncs.pfnUpdateClientData( &cdat, cl.time )) { // grab changes if successful VectorCopy( cdat.viewangles, cl.viewangles ); cl.local.scr_fov = cdat.fov; } } /* ================= CL_CreateCmd ================= */ static void CL_CreateCmd( void ) { usercmd_t nullcmd = { 0 }, *cmd; runcmd_t *pcmd; qboolean active; double accurate_ms; vec3_t angles; int input_override; int i, ms; if( cls.state <= ca_connected || cls.state == ca_cinematic ) return; // store viewangles in case it's will be freeze VectorCopy( cl.viewangles, angles ); input_override = 0; // fix rounding error and framerate depending player move accurate_ms = host.frametime * 1000; ms = (int)accurate_ms; cl.frametime_remainder += accurate_ms - ms; // accumulate rounding error each frame // add a ms if error accumulates enough if( cl.frametime_remainder >= 1.0 ) { int ms2 = (int)cl.frametime_remainder; ms += ms2; cl.frametime_remainder -= ms2; } // ms can't be negative, rely on error accumulation only if FPS > 1000 ms = Q_min( ms, 255 ); CL_SetSolidEntities(); CL_PushPMStates(); CL_SetSolidPlayers( cl.playernum ); // message we are constructing. i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; pcmd = &cl.commands[i]; if( !cls.demoplayback ) { pcmd->processedfuncs = false; pcmd->senttime = host.realtime; memset( &pcmd->cmd, 0, sizeof( pcmd->cmd )); pcmd->receivedtime = -1.0; pcmd->heldback = false; pcmd->sendsize = 0; cmd = &pcmd->cmd; } else { cmd = &nullcmd; } active = (( cls.signon == SIGNONS ) && !cl.paused && !cls.demoplayback ); Platform_PreCreateMove(); clgame.dllFuncs.CL_CreateMove( host.frametime, cmd, active ); IN_EngineAppendMove( host.frametime, cmd, active ); CL_PopPMStates(); if( !cls.demoplayback ) { CL_ComputeClientInterpolationAmount( &pcmd->cmd ); pcmd->cmd.lightlevel = cl.local.light_level; pcmd->cmd.msec = ms; } input_override |= CL_ProcessOverviewCmds( &pcmd->cmd ); input_override |= CL_ProcessShowTexturesCmds( &pcmd->cmd ); if(( cl.background && !cls.demoplayback ) || input_override || cls.changelevel ) { VectorCopy( angles, pcmd->cmd.viewangles ); VectorCopy( angles, cl.viewangles ); if( !cl.background ) pcmd->cmd.msec = 0; } // demo always have commands so don't overwrite them if( !cls.demoplayback ) cl.cmd = pcmd->cmd; // predict all unacknowledged movements CL_PredictMovement( false ); } void CL_WriteUsercmd( connprotocol_t proto, sizebuf_t *msg, int from, int to ) { const usercmd_t nullcmd = { 0 }; const usercmd_t *f; usercmd_t *t; Assert( from == -1 || ( from >= 0 && from < MULTIPLAYER_BACKUP )); Assert( to >= 0 && to < MULTIPLAYER_BACKUP ); f = from == -1 ? &nullcmd : &cl.commands[from].cmd; t = &cl.commands[to].cmd; // write it into the buffer if( proto == PROTO_GOLDSRC ) { MSG_StartBitWriting( msg ); Delta_WriteGSFields( msg, DT_USERCMD_T, f, t, 0.0f ); MSG_EndBitWriting( msg ); } else MSG_WriteDeltaUsercmd( msg, f, t ); } /* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds =================== */ static void CL_WritePacket( void ) { sizebuf_t buf; byte data[MAX_CMD_BUFFER] = { 0 }; runcmd_t *pcmd; int numbackup, maxbackup, maxcmds; const connprotocol_t proto = cls.legacymode; // FIXME: on Xash protocol we don't send move commands until ca_active // to prevent outgoing_command outrun incoming_acknowledged // which is fatal for some buggy mods like TFC // // ... but GoldSrc don't have (real) ca_validate state, so we consider // ca_validate the same as ca_active, otherwise we don't pass validation // of server-side mods like ReAuthCheck const connstate_t min_state = proto == PROTO_GOLDSRC ? ca_validate : ca_active; // don't send anything if playing back a demo if( cls.demoplayback || cls.state < ca_connected || cls.state == ca_cinematic ) return; if( cls.state < min_state ) { Netchan_TransmitBits( &cls.netchan, 0, "" ); return; } // cls.state can only be ca_validate or ca_active from here CL_ComputePacketLoss( ); MSG_Init( &buf, "ClientData", data, sizeof( data )); switch( proto ) { case PROTO_GOLDSRC: maxbackup = MAX_GOLDSRC_BACKUP_CMDS; maxcmds = MAX_GOLDSRC_TOTAL_CMDS; break; case PROTO_LEGACY: maxbackup = MAX_LEGACY_BACKUP_CMDS; maxcmds = MAX_LEGACY_TOTAL_CMDS; break; default: maxbackup = MAX_BACKUP_COMMANDS; maxcmds = MAX_TOTAL_CMDS; break; } numbackup = bound( 0, cl_cmdbackup.value, maxbackup ); // allow extended usercmd limit if( proto == PROTO_GOLDSRC && cls.build_num >= 5971 ) maxcmds = MAX_GOLDSRC_EXTENDED_TOTAL_CMDS - numbackup; // clamp cmdrate if( cl_cmdrate.value < 10.0f ) Cvar_DirectSet( &cl_cmdrate, "10" ); else if( cl_cmdrate.value > 100.0f ) Cvar_DirectSet( &cl_cmdrate, "100" ); // are we hltv spectator? if( cls.spectator && cl.delta_sequence == cl.validsequence && ( !cls.demorecording || !cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime ) return; // can send this command? pcmd = &cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK]; if( cl.maxclients == 1 || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal.value ) || ( host.realtime >= cls.nextcmdtime && Netchan_CanPacket( &cls.netchan, true ))) pcmd->heldback = false; else pcmd->heldback = true; // immediately add it to the demo, regardless if we send the message or not if( cls.demorecording ) CL_WriteDemoUserCmd( cls.netchan.outgoing_sequence & CL_UPDATE_MASK ); if( !pcmd->heldback ) { int newcmds, numcmds; int from, i, key; int packet_loss = bound( 0, (int)cls.packet_loss, 100 ); cls.nextcmdtime = host.realtime + ( 1.0f / cl_cmdrate.value ); if( cls.lastoutgoingcommand < 0 ) cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; newcmds = cls.netchan.outgoing_sequence - cls.lastoutgoingcommand; newcmds = bound( 0, newcmds, maxcmds ); numcmds = newcmds + numbackup; // goldsrc starts writing clc_move earlier but it doesn't make sense if it's not going to be sent MSG_BeginClientCmd( &buf, clc_move ); if( proto == PROTO_GOLDSRC ) MSG_WriteByte( &buf, 0 ); // command length key = MSG_GetRealBytesWritten( &buf ); MSG_WriteByte( &buf, 0 ); if( proto == PROTO_GOLDSRC && voice_loopback.value ) SetBits( packet_loss, 7 ); // set 7-th bit to tell server that we want voice loopback MSG_WriteByte( &buf, packet_loss ); MSG_WriteByte( &buf, numbackup ); MSG_WriteByte( &buf, newcmds ); for( from = -1, i = numcmds - 1; i >= 0; i-- ) { int to = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; CL_WriteUsercmd( proto, &buf, from, to ); from = to; } // finalize message if( proto == PROTO_GOLDSRC ) { int size = MSG_GetRealBytesWritten( &buf ) - key - 1; buf.pData[key - 1] = Q_min( size, 255 ); buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence ); COM_Munge( &buf.pData[key + 1], Q_min( size, 255 ), cls.netchan.outgoing_sequence ); } else if( !Host_IsLocalClient( )) { int size = MSG_GetRealBytesWritten( &buf ) - key - 1; buf.pData[key] = CRC32_BlockSequence( &buf.pData[key + 1], size, cls.netchan.outgoing_sequence ); } // check if we're timing out if( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged >= CL_UPDATE_MASK && host.realtime - cls.netchan.last_received >= CL_CONNECTION_TIMEOUT ) { Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); Con_NPrintf( 2, "^1Auto-disconnect in %.1f seconds^7", cl_timeout.value - ( host.realtime - cls.netchan.last_received )); cl.validsequence = 0; } if( cl_nodelta.value ) cl.validsequence = 0; if( cl.validsequence && ( !cls.demorecording || !cls.demowaiting )) { cl.delta_sequence = cl.validsequence; MSG_BeginClientCmd( &buf, clc_delta ); MSG_WriteByte( &buf, cl.validsequence & 0xff ); } else cl.delta_sequence = -1; // command finished, remember last sent sequence id cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; pcmd->sendsize = MSG_GetNumBytesWritten( &buf ); CL_AddVoiceToDatagram(); // now add unreliable, if there is enough space if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf )) MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram )); MSG_Clear( &cls.datagram ); Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); } else { cls.netchan.outgoing_sequence++; } // update download/upload slider. Netchan_UpdateProgress( &cls.netchan ); } /* ================= CL_SendCommand Called every frame to builds and sends a command packet to the server. ================= */ static void CL_SendCommand( void ) { // we create commands even if a demo is playing, CL_CreateCmd(); // clc_move, userinfo etc CL_WritePacket(); } /* ================== CL_BeginUpload_f ================== */ static void CL_BeginUpload_f( void ) { const char *name; resource_t custResource; byte *buf = NULL; int size = 0; byte md5[16]; name = Cmd_Argv( 1 ); if( !COM_CheckString( name )) return; if( !cl_allow_upload.value ) return; if( Q_strlen( name ) != 36 || Q_strnicmp( name, "!MD5", 4 )) { Con_Printf( "Ingoring upload of non-customization\n" ); return; } memset( &custResource, 0, sizeof( custResource )); COM_HexConvert( name + 4, 32, md5 ); if( HPAK_ResourceForHash( hpk_custom_file.string, md5, &custResource )) { if( memcmp( md5, custResource.rgucMD5_hash, 16 )) { Con_Reportf( "Bogus data retrieved from %s, attempting to delete entry\n", hpk_custom_file.string ); HPAK_RemoveLump( hpk_custom_file.string, &custResource ); return; } if( HPAK_GetDataPointer( hpk_custom_file.string, &custResource, &buf, &size )) { byte md5[16]; MD5Context_t ctx; memset( &ctx, 0, sizeof( ctx )); MD5Init( &ctx ); MD5Update( &ctx, buf, size ); MD5Final( md5, &ctx ); if( memcmp( custResource.rgucMD5_hash, md5, 16 )) { Con_Reportf( "HPAK_AddLump called with bogus lump, md5 mismatch\n" ); Con_Reportf( "Purported: %s\n", MD5_Print( custResource.rgucMD5_hash ) ); Con_Reportf( "Actual : %s\n", MD5_Print( md5 ) ); Con_Reportf( "Removing conflicting lump\n" ); HPAK_RemoveLump( hpk_custom_file.string, &custResource ); return; } } } if( buf && size > 0 ) { Netchan_CreateFileFragmentsFromBuffer( &cls.netchan, name, buf, size ); Netchan_FragSend( &cls.netchan ); Mem_Free( buf ); } } /* ================== CL_Quit_f ================== */ void CL_Quit_f( void ) { CL_Disconnect(); Sys_Quit( "command" ); } /* ================ CL_Drop Called after an Host_Error was thrown ================ */ void CL_Drop( void ) { if( !cls.initialized ) return; CL_Disconnect(); } static void CL_GetCDKey( char *protinfo, size_t protinfosize ) { byte hash[16] = { 0 }; MD5Context_t ctx = { 0 }; char key[64]; int keylength; keylength = Q_snprintf( key, sizeof( key ), "%u", COM_RandomLong( 0, 0x7ffffffe )); MD5Init( &ctx ); MD5Update( &ctx, key, keylength ); MD5Final( hash, &ctx ); Q_strnlwr( MD5_Print( hash ), key, sizeof( key )); Info_SetValueForKey( protinfo, "cdkey", key, protinfosize ); } static void CL_WriteSteamTicket( sizebuf_t *send ) { const char *s; uint32_t crc; char buf[768] = { 0 }; // setti and steamemu return 768 int i = sizeof( buf ); if( !Q_strcmp( cl_ticket_generator.string, "null" )) { MSG_WriteBytes( send, buf, 512 ); // specifically 512 bytes of zeros return; } //if( !Q_strcmp( cl_ticket_generator.string, "steam" ) //{ // i = SteamBroker_InitiateGameConnection( buf, sizeof( buf )); // MSG_WriteBytes( send, buf, i ); // return; //} s = ID_GetMD5(); CRC32_Init( &crc ); CRC32_ProcessBuffer( &crc, s, Q_strlen( s )); crc = CRC32_Final( crc ); i = GenerateRevEmu2013( buf, s, crc ); MSG_WriteBytes( send, buf, i ); // RevEmu2013: pTicket[1] = revHash (low), pTicket[5] = 0x01100001 (high) *(uint32_t*)cls.steamid = LittleLong( ((uint32_t*)buf)[1] ); *(uint32_t*)(cls.steamid + 4) = LittleLong( ((uint32_t*)buf)[5] ); } /* ======================= CL_SendConnectPacket We have gotten a challenge from the server, so try and connect. ====================== */ static void CL_SendConnectPacket( connprotocol_t proto, int challenge ) { char protinfo[MAX_INFO_STRING]; const char *key = ID_GetMD5(); netadr_t adr = { 0 }; int input_devices; netadrtype_t adrtype; protinfo[0] = 0; if( !NET_StringToAdr( cls.servername, &adr )) { Con_Printf( "%s: bad server address\n", __func__ ); cls.connect_time = 0; return; } adrtype = NET_NetadrType( &adr ); if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER ); input_devices = IN_CollectInputDevices(); IN_LockInputDevices( adrtype != NA_LOOPBACK ? true : false ); // GoldSrc doesn't need sv_cheats set to 0, it's handled by svc_goldsrc_sendextrainfo // it also doesn't need useragent string if( adrtype != NA_LOOPBACK && proto != PROTO_GOLDSRC ) { Cvar_SetCheatState(); Cvar_FullSet( "sv_cheats", "0", FCVAR_READ_ONLY | FCVAR_SERVER ); Info_SetValueForKeyf( protinfo, "d", sizeof( protinfo ), "%d", input_devices ); Info_SetValueForKey( protinfo, "v", XASH_VERSION, sizeof( protinfo ) ); Info_SetValueForKeyf( protinfo, "b", sizeof( protinfo ), "%d", Q_buildnum( )); Info_SetValueForKey( protinfo, "o", Q_buildos(), sizeof( protinfo ) ); Info_SetValueForKey( protinfo, "a", Q_buildarch(), sizeof( protinfo ) ); } if( proto == PROTO_GOLDSRC ) { const char *name; sizebuf_t send; byte send_buf[2048]; Info_SetValueForKey( protinfo, "prot", "3", sizeof( protinfo )); // steam auth type Info_SetValueForKeyf( protinfo, "unique", sizeof( protinfo ), "%i", 0xffffffff ); Info_SetValueForKey( protinfo, "raw", "steam", sizeof( protinfo )); CL_GetCDKey( protinfo, sizeof( protinfo )); // remove keys set for legacy protocol Info_RemoveKey( cls.userinfo, "cl_maxpacket" ); Info_RemoveKey( cls.userinfo, "cl_maxpayload" ); name = Info_ValueForKey( cls.userinfo, "name" ); if( cl_advertise_engine_in_name.value && Q_strnicmp( name, "[Xash3D]", 8 )) Info_SetValueForKeyf( cls.userinfo, "name", sizeof( cls.userinfo ), "[Xash3D]%s", name ); MSG_Init( &send, "GoldSrcConnect", send_buf, sizeof( send_buf )); MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET ); MSG_WriteStringf( &send, C2S_CONNECT" %i %i \"%s\" \"%s\"\n", PROTOCOL_GOLDSRC_VERSION, challenge, protinfo, cls.userinfo ); MSG_SeekToBit( &send, -8, SEEK_CUR ); // rewrite null terminator CL_WriteSteamTicket( &send ); if( MSG_CheckOverflow( &send )) Con_Printf( S_ERROR "%s: %s overflow!\n", __func__, MSG_GetName( &send ) ); NET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr ); Con_Printf( "Trying to connect with GoldSrc 48 protocol\n" ); } else if( proto == PROTO_LEGACY ) { const char *dlmax; int qport = Cvar_VariableInteger( "net_qport" ); // reset nickname from cvar value Info_SetValueForKey( cls.userinfo, "name", name.string, sizeof( cls.userinfo )); // set related userinfo keys dlmax = ( cl_dlmax.value >= 100 && cl_dlmax.value < 40000 ) ? cl_dlmax.string : "1400"; Info_SetValueForKey( cls.userinfo, "cl_maxpacket", dlmax, sizeof( cls.userinfo )); if( !COM_CheckStringEmpty( Info_ValueForKey( cls.userinfo, "cl_maxpayload" ))) Info_SetValueForKey( cls.userinfo, "cl_maxpayload", "1000", sizeof( cls.userinfo ) ); Info_SetValueForKey( protinfo, "i", key, sizeof( protinfo )); Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT" %i %i %i \"%s\" %d \"%s\"\n", PROTOCOL_LEGACY_VERSION, qport, challenge, cls.userinfo, NET_LEGACY_EXT_SPLIT, protinfo ); Con_Printf( "Trying to connect with legacy protocol\n" ); } else { const char *qport = Cvar_VariableString( "net_qport" ); int extensions = NET_EXT_SPLITSIZE; // reset nickname from cvar value Info_SetValueForKey( cls.userinfo, "name", name.string, sizeof( cls.userinfo )); if( cl_dlmax.value > FRAGMENT_MAX_SIZE || cl_dlmax.value < FRAGMENT_MIN_SIZE ) Cvar_DirectSetValue( &cl_dlmax, FRAGMENT_DEFAULT_SIZE ); // remove keys set for legacy protocol Info_RemoveKey( cls.userinfo, "cl_maxpacket" ); Info_RemoveKey( cls.userinfo, "cl_maxpayload" ); Info_SetValueForKey( protinfo, "uuid", key, sizeof( protinfo )); Info_SetValueForKey( protinfo, "qport", qport, sizeof( protinfo )); Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", extensions); Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT" %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, challenge, protinfo, cls.userinfo ); Con_Printf( "Trying to connect with modern protocol\n" ); } cls.timestart = Sys_DoubleTime(); } /* ================= CL_GetTestFragmentSize Returns bandwidth test fragment size ================= */ static int CL_GetTestFragmentSize( void ) { // const int fragmentSizes[CL_TEST_RETRIES] = { 64000, 32000, 10666, 5200, 1400 }; // it turns out, even if we pass the bandwidth test, it doesn't mean we can use such large fragments // as a temporary solution, use smaller fragment sizes const int fragmentSizes[CL_TEST_RETRIES] = { 1400, 1200, 1000, 800, 508 }; if( cls.connect_retry >= 0 && cls.connect_retry < CL_TEST_RETRIES ) return bound( FRAGMENT_MIN_SIZE, fragmentSizes[cls.connect_retry], FRAGMENT_MAX_SIZE ); else return FRAGMENT_MIN_SIZE; } static void CL_SendGetChallenge( netadr_t to ) { // always send GoldSrc-styled getchallenge message // Xash servers will ignore it but for GoldSrc it will help // in auto-detection Netchan_OutOfBandPrint( NS_CLIENT, to, C2S_GETCHALLENGE" steam\n" ); } /* ================= CL_CheckForResend Resend a connect message if the last one has timed out ================= */ static void CL_CheckForResend( void ) { netadr_t adr; net_gai_state_t res; float resendTime; qboolean bandwidthTest; if( cls.internetservers_wait ) CL_SendMasterServerScanRequest(); // if the local server is running and we aren't then connect if( cls.state == ca_disconnected && SV_Active( )) { cls.signon = 0; cls.state = ca_connecting; Q_strncpy( cls.servername, "localhost", sizeof( cls.servername )); NET_NetadrSetType( &cls.serveradr, NA_LOOPBACK ); cls.legacymode = PROTO_CURRENT; // we don't need a challenge on the localhost CL_SendConnectPacket( PROTO_CURRENT, 0 ); return; } // resend if we haven't gotten a reply yet if( cls.demoplayback || cls.state != ca_connecting ) return; if( cl_resend.value < CL_MIN_RESEND_TIME ) Cvar_DirectSetValue( &cl_resend, CL_MIN_RESEND_TIME ); else if( cl_resend.value > CL_MAX_RESEND_TIME ) Cvar_DirectSetValue( &cl_resend, CL_MAX_RESEND_TIME ); bandwidthTest = cls.legacymode == PROTO_CURRENT && cl_test_bandwidth.value && cls.connect_retry <= CL_TEST_RETRIES; resendTime = bandwidthTest ? 1.0f : cl_resend.value; if(( host.realtime - cls.connect_time ) < resendTime ) return; res = NET_StringToAdrNB( cls.servername, &adr, false ); if( res == NET_EAI_NONAME ) { CL_Disconnect(); return; } if( res == NET_EAI_AGAIN ) { cls.connect_time = MAX_HEARTBEAT; return; } // only retry so many times before failure. if( cls.connect_retry >= CL_CONNECTION_RETRIES ) { Con_DPrintf( S_ERROR "%s: couldn't connect\n", __func__ ); CL_Disconnect(); return; } if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER ); if( cls.connect_retry == CL_TEST_RETRIES ) { // too many fails use default connection method Con_Printf( "Bandwidth test failed, fallback to default connecting method\n" ); Con_Printf( "Connecting to %s... (retry #%i)\n", cls.servername, cls.connect_retry + 1 ); CL_SendGetChallenge( adr ); Cvar_DirectSetValue( &cl_dlmax, FRAGMENT_MIN_SIZE ); cls.connect_time = host.realtime; cls.connect_retry++; return; } cls.serveradr = adr; cls.max_fragment_size = CL_GetTestFragmentSize(); cls.connect_time = host.realtime; // for retransmit requests cls.connect_retry++; if( bandwidthTest ) { Con_Printf( "Connecting to %s... (retry #%i, fragment size %i)\n", cls.servername, cls.connect_retry, cls.max_fragment_size ); Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_BANDWIDTHTEST" %i %i\n", PROTOCOL_VERSION, cls.max_fragment_size ); } else { Con_Printf( "Connecting to %s... (retry #%i)\n", cls.servername, cls.connect_retry ); CL_SendGetChallenge( adr ); } } static resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index ) { resource_t *r = &cl.resourcelist[cl.num_resources]; if( cl.num_resources >= MAX_RESOURCES ) Host_Error( "Too many resources on client\n" ); cl.num_resources++; Q_strncpy( r->szFileName, name, sizeof( r->szFileName )); r->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0; r->nDownloadSize = size; r->nIndex = index; r->type = type; return r; } static void CL_CreateResourceList( void ) { char szFileName[MAX_OSPATH]; byte rgucMD5_hash[16] = { 0 }; resource_t *pNewResource; int nSize; file_t *fp; HPAK_FlushHostQueue(); cl.num_resources = 0; memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash )); // sanitize cvar value if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" )) Cvar_DirectSet( &cl_logoext, "bmp" ); Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string ); if( cls.legacymode == PROTO_GOLDSRC ) { CL_ConvertImageToWAD3( szFileName ); Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName )); } fp = FS_Open( szFileName, "rb", true ); if( !fp ) return; MD5_HashFile( rgucMD5_hash, szFileName, NULL ); nSize = FS_FileLength( fp ); if( nSize != 0 ) { pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 ); if( pNewResource ) { SetBits( pNewResource->ucFlags, RES_CUSTOM ); memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 ); HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp ); } } FS_Close( fp ); } static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto ) { if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" )) { *proto = PROTO_CURRENT; return true; } if( !Q_stricmp( s, "legacy" ) || !Q_strcmp( s, "48" )) { *proto = PROTO_LEGACY; return true; } if( !Q_stricmp( s, "goldsrc" ) || !Q_stricmp( s, "gs" )) { *proto = PROTO_GOLDSRC; return true; } // quake protocol only used for demos Con_Printf( "Unknown protocol. Supported are: 49 (current), 48 (legacy), gs (goldsrc)\n" ); return false; } /* ================ CL_Connect_f ================ */ static void CL_Connect_f( void ) { string server; connprotocol_t proto = PROTO_CURRENT; // hint to connect by using legacy protocol if( Cmd_Argc() == 3 && !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ) && Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "connect [protocol]\n" ); return; } Q_strncpy( server, Cmd_Argv( 1 ), sizeof( server )); // if running a local server, kill it and reissue if( SV_Active( )) SV_Shutdown( "Server was killed due to connection to remote server\n" ); NET_Config( true, !cl_nat.value ); // allow remote Con_Printf( "server %s\n", server ); CL_Disconnect(); // TESTTEST: a see console during connection UI_SetActiveMenu( false ); Key_SetKeyDest( key_console ); cls.state = ca_connecting; cls.legacymode = proto; Q_strncpy( cls.servername, server, sizeof( cls.servername )); cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately cls.max_fragment_size = FRAGMENT_MAX_SIZE; // guess a we can establish connection with maximum fragment size cls.connect_retry = 0; cls.spectator = false; cls.signon = 0; } /* ===================== CL_Rcon_f Send the rest of the command line over as an unconnected command. ===================== */ static void CL_Rcon_f( void ) { char message[1024]; sizebuf_t msg; netadr_t to; int i; if( !COM_CheckString( rcon_password.string )) { Con_Printf( "You must set 'rcon_password' before issuing an rcon command.\n" ); return; } NET_Config( true, false ); // allow remote if( cls.state >= ca_connected ) { to = cls.netchan.remote_address; } else { if( !COM_CheckString( rcon_address.string )) { Con_Printf( "You must either be connected or set the 'rcon_address' cvar to issue rcon commands\n" ); return; } NET_StringToAdr( rcon_address.string, &to ); if( to.port == 0 ) to.port = MSG_BigShort( PORT_SERVER ); } MSG_Init( &msg, "RconMessage", message, sizeof( message )); MSG_WriteLong( &msg, -1 ); MSG_WriteStringf( &msg, C2S_RCON" %s ", rcon_password.string ); MSG_SeekToBit( &msg, -8, SEEK_CUR ); for( i = 1; i < Cmd_Argc(); i++ ) { string command; Cmd_Escape( command, Cmd_Argv( i ), sizeof( command )); MSG_WriteString( &msg, command ); MSG_SeekToBit( &msg, -8, SEEK_CUR ); MSG_WriteChar( &msg, ' ' ); } MSG_WriteByte( &msg, 0 ); NET_SendPacket( NS_CLIENT, MSG_GetNumBytesWritten( &msg ), MSG_GetData( &msg ), to ); } /* ===================== CL_ClearState ===================== */ void CL_ClearState( void ) { int i; CL_ClearResourceLists(); for( i = 0; i < MAX_CLIENTS; i++ ) COM_ClearCustomizationList( &cl.players[i].customdata, false ); S_StopAllSounds ( true ); CL_ClearEffects (); CL_FreeEdicts (); PM_ClearPhysEnts( clgame.pmove ); NetAPI_CancelAllRequests(); // wipe the entire cl structure memset( &cl, 0, sizeof( cl )); MSG_Clear( &cls.netchan.message ); memset( &clgame.fade, 0, sizeof( clgame.fade )); memset( &clgame.shake, 0, sizeof( clgame.shake )); clgame.mapname[0] = '\0'; Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); cl.maxclients = 1; // allow to drawing player in menu cl.mtime[0] = cl.mtime[1] = 1.0f; // because level starts from 1.0f second cls.signon = 0; cl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded; cl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand; CL_CreateResourceList(); CL_ClearSpriteTextures(); // now all hud sprites are invalid cl.local.interp_amount = 0.1f; cl.local.scr_fov = 90.0f; Cvar_SetValue( "scr_download", -1.0f ); Cvar_SetValue( "scr_loading", 0.0f ); host.allow_console = host.allow_console_init; HTTP_ClearCustomServers(); } /* ===================== CL_SendDisconnectMessage Sends a disconnect message to the server ===================== */ static void CL_SendDisconnectMessage( connprotocol_t proto ) { sizebuf_t buf; byte data[32]; if( cls.state == ca_disconnected ) return; MSG_Init( &buf, "LastMessage", data, sizeof( data )); MSG_BeginClientCmd( &buf, clc_stringcmd ); if( proto == PROTO_GOLDSRC ) MSG_WriteString( &buf, "dropclient\n" ); else MSG_WriteString( &buf, "disconnect" ); if( NET_NetadrType( &cls.netchan.remote_address ) == NA_UNDEFINED ) NET_NetadrSetType( &cls.netchan.remote_address, NA_LOOPBACK ); // make sure message will be delivered Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); } int CL_GetSplitSize( void ) { int splitsize = (int)cl_dlmax.value; if( !FBitSet( cls.extensions, NET_EXT_SPLITSIZE )) return 1400; if(( splitsize < FRAGMENT_MIN_SIZE ) || ( splitsize > FRAGMENT_MAX_SIZE )) { Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); return FRAGMENT_DEFAULT_SIZE; } return (int)cl_dlmax.value; } void CL_SetupNetchanForProtocol( connprotocol_t proto ) { int (*pfnBlockSize)( void *, fragsize_t ) = CL_GetFragmentSize; uint flags = 0; switch( proto ) { case PROTO_GOLDSRC: SetBits( flags, NETCHAN_USE_MUNGE | NETCHAN_USE_BZIP2 | NETCHAN_GOLDSRC ); pfnBlockSize = CL_GetGoldSrcFragmentSize; break; case PROTO_LEGACY: if( FBitSet( Q_atoi( Cmd_Argv( 1 )), NET_LEGACY_EXT_SPLIT )) { SetBits( flags, NETCHAN_USE_LEGACY_SPLIT ); Con_Reportf( "^2NET_EXT_SPLIT enabled^7 (packet sizes is %d/%d)\n", (int)cl_dlmax.value, 65536 ); } break; default: if( !Host_IsLocalClient( )) SetBits( flags, NETCHAN_USE_LZSS ); cls.extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" )); if( FBitSet( cls.extensions, NET_EXT_SPLITSIZE )) Con_Reportf( "^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d)\n", (int)cl_dlmax.value ); break; } Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, pfnBlockSize, flags ); } /* ===================== CL_Reconnect build a request to reconnect client ===================== */ static void CL_Reconnect( qboolean setup_netchan ) { if( setup_netchan ) { CL_SetupNetchanForProtocol( cls.legacymode ); } else { // clear channel and stuff Netchan_Clear( &cls.netchan ); MSG_Clear( &cls.netchan.message ); } cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connected; cls.signon = 0; CL_ServerCommand( true, "new" ); cl.validsequence = 0; // haven't gotten a valid frame update yet cl.delta_sequence = -1; // we'll request a full delta from the baseline cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet cls.nextcmdtime = host.realtime; // we can send a cmd right away cl.last_command_ack = -1; CL_StartupDemoHeader (); } /* ===================== CL_Disconnect Goes from a connected state to full screen console state Sends a disconnect message to the server This is also called on Host_Error, so it shouldn't cause any errors ===================== */ void CL_Disconnect( void ) { if( cls.state == ca_disconnected ) return; cls.connect_time = 0; cls.changedemo = false; cls.max_fragment_size = FRAGMENT_MAX_SIZE; // reset fragment size Voice_Disconnect(); CL_Stop_f(); // send a disconnect message to the server CL_SendDisconnectMessage( cls.legacymode ); CL_ClearState (); S_StopBackgroundTrack (); SCR_EndLoadingPlaque (); // get rid of loading plaque // clear the network channel, too. Netchan_Clear( &cls.netchan ); IN_LockInputDevices( false ); // unlock input devices cls.state = ca_disconnected; memset( &cls.serveradr, 0, sizeof( cls.serveradr ) ); cls.set_lastdemo = false; cls.connect_retry = 0; cls.signon = 0; cls.legacymode = PROTO_CURRENT; // back to menu in non-developer mode if( host_developer.value || cls.key_dest == key_menu ) return; UI_SetActiveMenu( true ); } void CL_Disconnect_f( void ) { if( Host_IsLocalClient( )) Host_EndGame( true, "disconnected from server\n" ); else CL_Disconnect(); } void CL_Crashed( void ) { // already freed if( host.status == HOST_CRASHED ) return; if( host.type != HOST_NORMAL ) return; if( !cls.initialized ) return; host.status = HOST_CRASHED; CL_Stop_f(); // stop any demos // send a disconnect message to the server CL_SendDisconnectMessage( cls.legacymode ); Host_WriteOpenGLConfig(); Host_WriteConfig(); // write config } /* ================= CL_LocalServers_f ================= */ static void CL_LocalServers_f( void ) { netadr_t adr = { 0 }; Con_Printf( "Scanning for servers on the local network area...\n" ); NET_Config( true, true ); // allow remote // send a broadcast packet NET_NetadrSetType( &adr, NA_BROADCAST ); adr.port = MSG_BigShort( PORT_SERVER ); Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION ); NET_NetadrSetType( &adr, NA_MULTICAST_IP6 ); Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION ); } /* ================= CL_BuildMasterServerScanRequest ================= */ static size_t NONNULL CL_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t *key, qboolean nat, const char *filter ) { size_t remaining; char *info, temp[32]; if( unlikely( size < sizeof( MS_SCAN_REQUEST ))) return 0; Q_strncpy( buf, MS_SCAN_REQUEST, size ); info = buf + sizeof( MS_SCAN_REQUEST ) - 1; remaining = size - sizeof( MS_SCAN_REQUEST ); Q_strncpy( info, filter, remaining ); *key = COM_RandomLong( 0, 0x7FFFFFFF ); #ifndef XASH_ALL_SERVERS Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); #endif // let master know about client version Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); Info_SetValueForKey( info, "nat", nat ? "1" : "0", remaining ); Info_SetValueForKey( info, "commit", g_buildcommit, remaining ); Info_SetValueForKey( info, "branch", g_buildbranch, remaining ); Info_SetValueForKey( info, "os", Q_buildos(), remaining ); Info_SetValueForKey( info, "arch", Q_buildarch(), remaining ); Q_snprintf( temp, sizeof( temp ), "%d", Q_buildnum() ); Info_SetValueForKey( info, "buildnum", temp, remaining ); Q_snprintf( temp, sizeof( temp ), "%x", *key ); Info_SetValueForKey( info, "key", temp, remaining ); return sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ); } /* ================= CL_SendMasterServerScanRequest ================= */ static void CL_SendMasterServerScanRequest( void ) { cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, cls.internetservers_query_len, cls.internetservers_query ); cls.internetservers_pending = true; } /* ================= CL_InternetServers_f ================= */ static void CL_InternetServers_f( void ) { qboolean nat = cl_nat.value != 0.0f; uint32_t key; if( Cmd_Argc( ) > 2 || ( Cmd_Argc( ) == 2 && !Info_IsValid( Cmd_Argv( 1 )))) { Con_Printf( S_USAGE "internetservers [filter]\n" ); return; } cls.internetservers_query_len = CL_BuildMasterServerScanRequest( cls.internetservers_query, sizeof( cls.internetservers_query ), &cls.internetservers_key, nat, Cmd_Argv( 1 )); Con_Printf( "Scanning for servers on the internet area...\n" ); NET_Config( true, true ); // allow remote CL_SendMasterServerScanRequest(); } static void CL_QueryServer_f( void ) { netadr_t adr; connprotocol_t proto; if( Cmd_Argc( ) != 3 ) { Con_Printf( S_USAGE "ui_queryserver \n" ); return; } NET_Config( true, false ); if( !NET_StringToAdr( Cmd_Argv( 1 ), &adr )) { Con_Printf( S_ERROR "%s: can't parse %s", __func__, Cmd_Argv( 1 )); return; } if( adr.port == 0 ) adr.port = PORT_SERVER; if( !CL_StringToProtocol( Cmd_Argv( 2 ), &proto )) return; switch( proto ) { case PROTO_GOLDSRC: Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator! break; case PROTO_LEGACY: Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_LEGACY_VERSION ); break; case PROTO_CURRENT: Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION ); break; } } /* ================= CL_Reconnect_f The server is changing levels ================= */ static void CL_Reconnect_f( void ) { if( cls.state == ca_disconnected ) return; S_StopAllSounds ( true ); if( cls.state == ca_connected ) { CL_Reconnect( false ); return; } if( COM_CheckString( cls.servername )) { connprotocol_t proto = cls.legacymode; if( cls.state >= ca_connected ) CL_Disconnect(); cls.connect_time = MAX_HEARTBEAT; // fire immediately cls.demonum = cls.movienum = -1; // not in the demo loop now cls.state = ca_connecting; cls.signon = 0; cls.legacymode = proto; // don't change protocol Con_Printf( "reconnecting...\n" ); } } /* ================= CL_FixupColorStringsForInfoString all the keys and values must be ends with ^7 ================= */ static void CL_FixupColorStringsForInfoString( const char *in, char *out, size_t len ) { qboolean hasPrefix = false; qboolean endOfKeyVal = false; int color = 7; int count = 0; if( *in == '\\' ) { *out++ = *in++; count++; } while( *in && count < len ) { if( IsColorString( in )) color = ColorIndex( *(in+1)); // color the not reset while end of key (or value) was found! if( *in == '\\' && color != 7 ) { if( IsColorString( out - 2 )) { *(out - 1) = '7'; } else { *out++ = '^'; *out++ = '7'; count += 2; } color = 7; } *out++ = *in++; count++; } // check the remaining value if( color != 7 ) { // if the ends with another color rewrite it if( IsColorString( out - 2 )) { *(out - 1) = '7'; } else { *out++ = '^'; *out++ = '7'; count += 2; } } *out = '\0'; } /* ================= CL_ParseStatusMessage Handle a reply from a info ================= */ static void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) { static char infostring[512+8]; char *s = MSG_ReadString( msg ); int i; const char *magic = ": wrong version\n", *p; size_t len = Q_strlen( s ), magiclen = Q_strlen( magic ); if( len >= magiclen && !Q_strcmp( s + len - magiclen, magic )) { Netchan_OutOfBandPrint( NS_CLIENT, from, A2A_INFO" %i", PROTOCOL_LEGACY_VERSION ); return; } if( !Info_IsValid( s )) { Con_Printf( "^1Server^7: %s, invalid infostring\n", NET_AdrToString( from )); return; } CL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring )); if( !COM_CheckString( Info_ValueForKey( infostring, "gamedir" ))) { Con_Printf( "^1Server^7: %s, Info: %s\n", NET_AdrToString( from ), infostring ); return; // unsupported proto } Info_RemoveKey( infostring, "gs" ); // don't let servers pretend they're something else p = Info_ValueForKey( infostring, "p" ); if( !COM_CheckStringEmpty( p )) { Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring )); Info_SetValueForKey( infostring, "p", "48", sizeof( infostring )); Con_Printf( "^3Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); } else if( !Q_strcmp( p, "48" )) { Info_SetValueForKey( infostring, "legacy", "1", sizeof( infostring )); Con_Printf( "^3Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); } else { // more info about servers Con_Printf( "^2Server^7: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); } UI_AddServerToList( from, infostring ); } static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg ) { static char s[512+8]; int p, numcl, maxcl, password, remaining; string host, map, gamedir, version; connprotocol_t proto; char *replace; // set to beginning but skip header MSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET ); p = MSG_ReadByte( msg ); Q_strncpy( host, MSG_ReadString( msg ), sizeof( host )); Q_strncpy( map, MSG_ReadString( msg ), sizeof( map )); Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir )); MSG_ReadString( msg ); // game description MSG_ReadShort( msg ); // app id numcl = MSG_ReadByte( msg ); maxcl = MSG_ReadByte( msg ); MSG_ReadByte( msg ); // bots count MSG_ReadByte( msg ); // dedicated MSG_ReadByte( msg ); // operating system password = MSG_ReadByte( msg ); Q_strncpy( version, MSG_ReadString( msg ), sizeof( version )); if( MSG_CheckOverflow( msg )) { Con_Printf( "%s: malfored info packet from %s\n", __func__, NET_AdrToString( from )); return; } // time to figure out protocol if( p == PROTOCOL_VERSION ) proto = PROTO_CURRENT; else if( p == PROTOCOL_LEGACY_VERSION ) { if( Q_stristr( version, "Stdio" )) proto = PROTO_GOLDSRC; else proto = PROTO_LEGACY; } else { Con_Printf( "%s: unsupported protocol %d from %s\n", __func__, p, NET_AdrToString( from )); return; } // now construct infostring for mainui Info_SetValueForKeyf( s, "p", sizeof( s ), "%i", proto == PROTO_CURRENT ? PROTOCOL_VERSION : PROTOCOL_LEGACY_VERSION ); Info_SetValueForKey( s, "gs", proto == PROTO_GOLDSRC ? "1" : "0", sizeof( s )); Info_SetValueForKey( s, "map", map, sizeof( s )); Info_SetValueForKey( s, "dm", "0", sizeof( s )); // obsolete keys Info_SetValueForKey( s, "team", "0", sizeof( s )); Info_SetValueForKey( s, "coop", "0", sizeof( s )); Info_SetValueForKeyf( s, "numcl", sizeof( s ), "%i", numcl ); Info_SetValueForKeyf( s, "maxcl", sizeof( s ), "%i", maxcl ); Info_SetValueForKey( s, "gamedir", gamedir, sizeof( s )); Info_SetValueForKey( s, "password", password ? "1" : "0", sizeof( s )); // write host last so we can try to cut off too long hostnames // TODO: value size limit for infostrings remaining = sizeof( s ) - Q_strlen( s ) - sizeof( "\\host\\" ) - 1; if( remaining < 0 ) { // should never happen? Con_Printf( S_ERROR "%s: infostring overflow!\n", __func__ ); return; } while(( replace = Q_strpbrk( host, "\\\"" ))) { *replace = ' '; // find a better replacement? } Info_SetValueForKey( s, "host", host, sizeof( s )); UI_AddServerToList( from, s ); } /* ================= CL_ParseNETInfoMessage Handle a reply from a netinfo ================= */ static void CL_ParseNETInfoMessage( netadr_t from, const char *s ) { net_request_t *nr = NULL; static char infostring[MAX_PRINT_MSG]; int i, context, type; int errorBits = 0; const char *val; size_t slen; context = Q_atoi( Cmd_Argv( 1 )); type = Q_atoi( Cmd_Argv( 2 )); // find request with specified context and type for( i = 0; i < MAX_REQUESTS; i++ ) { if( clgame.net_requests[i].resp.context == context && clgame.net_requests[i].resp.type == type ) { nr = &clgame.net_requests[i]; break; } } // not found, ignore if( nr == NULL ) return; // find the payload s = Q_strchr( s, ' ' ); // skip netinfo if( !s ) return; s = Q_strchr( s + 1, ' ' ); // skip challenge if( !s ) return; s = Q_strchr( s + 1, ' ' ); // skip type if( s ) s++; // skip final whitespace else if( type != NETAPI_REQUEST_PING ) // ping have no payload, and that's ok return; if( s ) { if( s[0] == '\\' ) { // check for errors val = Info_ValueForKey( s, "neterror" ); if( !Q_stricmp( val, "protocol" )) SetBits( errorBits, NET_ERROR_PROTO_UNSUPPORTED ); else if( !Q_stricmp( val, "undefined" )) SetBits( errorBits, NET_ERROR_UNDEFINED ); else if( !Q_stricmp( val, "forbidden" )) SetBits( errorBits, NET_ERROR_FORBIDDEN ); CL_FixupColorStringsForInfoString( s, infostring, sizeof( infostring )); } else { Q_strncpy( infostring, s, sizeof( infostring )); } } else { infostring[0] = 0; } // setup the answer nr->resp.response = infostring; nr->resp.remote_address = from; nr->resp.error = NET_SUCCESS; nr->resp.ping = host.realtime - nr->timesend; if( nr->timeout <= host.realtime ) SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); SetBits( nr->resp.error, errorBits ); // misc error bits nr->pfnFunc( &nr->resp ); if( !FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE )) memset( nr, 0, sizeof( *nr )); // done } /* ================= CL_ProcessNetRequests check for timeouts ================= */ static void CL_ProcessNetRequests( void ) { net_request_t *nr; int i; // find a request with specified context for( i = 0; i < MAX_REQUESTS; i++ ) { nr = &clgame.net_requests[i]; if( !nr->pfnFunc ) continue; // not used if( nr->timeout <= host.realtime ) { // setup the answer SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); nr->resp.ping = host.realtime - nr->timesend; nr->pfnFunc( &nr->resp ); memset( nr, 0, sizeof( *nr )); // done } } } //=================================================================== /* =============== CL_SetupOverviewParams Get initial overview values =============== */ void CL_SetupOverviewParams( void ) { ref_overview_t *ov = &clgame.overView; float mapAspect, screenAspect, aspect; ov->rotated = ( world.size[1] <= world.size[0] ) ? true : false; // calculate nearest aspect mapAspect = world.size[!ov->rotated] / world.size[ov->rotated]; screenAspect = (float)refState.width / (float)refState.height; aspect = Q_max( mapAspect, screenAspect ); ov->zNear = world.maxs[2]; ov->zFar = world.mins[2]; ov->flZoom = ( 8192.0f / world.size[ov->rotated] ) / aspect; VectorAverage( world.mins, world.maxs, ov->origin ); memset( &cls.spectator_state, 0, sizeof( cls.spectator_state )); if( cls.spectator ) { cls.spectator_state.playerstate.friction = 1; cls.spectator_state.playerstate.gravity = 1; cls.spectator_state.playerstate.number = cl.playernum + 1; cls.spectator_state.playerstate.usehull = 1; cls.spectator_state.playerstate.movetype = MOVETYPE_NOCLIP; cls.spectator_state.client.maxspeed = clgame.movevars.spectatormaxspeed; } } /* ================= CL_IsFromConnectingServer Used for connectionless packets, when netchan may not be ready. ================= */ static qboolean CL_IsFromConnectingServer( netadr_t from ) { return NET_IsLocalAddress( from ) || NET_CompareAdr( cls.serveradr, from ); } static void CL_HandleTestPacket( netadr_t from, sizebuf_t *msg ) { byte recv_buf[NET_MAX_FRAGMENT]; dword crcValue; int realsize; dword crcValue2 = 0; // this message only used during connection // it doesn't make sense after client_connect if( cls.state != ca_connecting ) return; if( !CL_IsFromConnectingServer( from )) return; crcValue = MSG_ReadLong( msg ); realsize = MSG_GetMaxBytes( msg ) - MSG_GetNumBytesRead( msg ); if( cls.max_fragment_size != MSG_GetMaxBytes( msg )) { if( cls.connect_retry >= CL_TEST_RETRIES ) { // too many fails use default connection method Con_Printf( "hi-speed connection is failed, use default method\n" ); CL_SendGetChallenge( from ); Cvar_SetValue( "cl_dlmax", FRAGMENT_DEFAULT_SIZE ); cls.connect_time = host.realtime; return; } // if we waiting more than cl_timeout or packet was trashed cls.connect_time = MAX_HEARTBEAT; return; // just wait for a next responce } // reading test buffer MSG_ReadBytes( msg, recv_buf, realsize ); // procssing the CRC CRC32_ProcessBuffer( &crcValue2, recv_buf, realsize ); if( crcValue == crcValue2 ) { // packet was sucessfully delivered, adjust the fragment size and get challenge Con_DPrintf( "CRC %x is matched, get challenge, fragment size %d\n", crcValue, cls.max_fragment_size ); CL_SendGetChallenge( from ); Cvar_SetValue( "cl_dlmax", cls.max_fragment_size ); cls.connect_time = host.realtime; } else { if( cls.connect_retry >= CL_TEST_RETRIES ) { // too many fails use default connection method Con_Printf( "hi-speed connection is failed, use default method\n" ); CL_SendGetChallenge( from ); Cvar_SetValue( "cl_dlmax", FRAGMENT_MIN_SIZE ); cls.connect_time = host.realtime; return; } Msg( "got testpacket, CRC mismatched 0x%08x should be 0x%08x, trying next fragment size %d\n", crcValue2, crcValue, cls.max_fragment_size >> 1 ); // trying the next size of packet cls.connect_time = MAX_HEARTBEAT; } } static void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from ) { if( !CL_IsFromConnectingServer( from )) return; if( cls.state == ca_connected ) { Con_DPrintf( S_ERROR "dup connect received. ignored\n"); return; } if( proto == PROTO_GOLDSRC ) { if( Q_strcmp( c, S2C_GOLDSRC_CONNECTION )) { Con_DPrintf( S_ERROR "GoldSrc client connect expected but wasn't received, ignored\n"); return; } if( Cmd_Argc() > 4 ) cls.build_num = Q_atoi( Cmd_Argv( 4 )); } else if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION )) { Con_DPrintf( S_ERROR "GoldSrc client connect received but wasn't expected, ignored\n"); return; } CL_Reconnect( true ); UI_SetActiveMenu( cl.background ); } static void CL_Print( const char *c, const char *args, netadr_t from, sizebuf_t *msg ) { const char *s; s = c[0] == A2C_GOLDSRC_PRINT ? args + 1 : MSG_ReadString( msg ); if( !COM_CheckStringEmpty( s )) return; Con_Printf( "Remote message from %s:\n", NET_AdrToString( from )); Con_Printf( "%s%c", s, s[Q_strlen( s ) - 1] != '\n' ? '\n' : '\0' ); } static void CL_Challenge( const char *c, netadr_t from ) { if( cls.state != ca_connecting ) return; if( !CL_IsFromConnectingServer( from )) return; // try to autodetect protocol by challenge response if( !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE )) cls.legacymode = PROTO_GOLDSRC; // challenge from the server we are connecting to CL_SendConnectPacket( cls.legacymode, Q_atoi( Cmd_Argv( 1 ))); } static void CL_ErrorMsg( const char *c, const char *args, netadr_t from, sizebuf_t *msg ) { char formatted_msg[MAX_VA_STRING]; if( !CL_IsFromConnectingServer( from )) return; if( msg != NULL && !Q_strcmp( c, S2C_ERRORMSG )) { const char *s = MSG_ReadString( msg ); Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", s ); } else if( c[0] == S2C_GOLDSRC_REJECT ) { Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", args + 1 ); } else if( c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD ) { if( !Q_strnicmp( &c[1], "BADPASSWORD", 11 )) Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", args + 12 ); else Q_snprintf( formatted_msg, sizeof( formatted_msg ), "^3Server message^7\n%s", args + 1 ); } // in case we're in console or it's classic mainui which doesn't support messageboxes if( !UI_IsVisible() || !UI_ShowMessageBox( formatted_msg )) Msg( "%s\n", formatted_msg ); // don't disconnect, errormsg is a FWGS extension and // always followed by disconnect message } static void CL_Reject( const char *c, const char *args, netadr_t from ) { // this message only used during connection // it doesn't make sense after client_connect if( cls.state != ca_connecting ) return; if( !CL_IsFromConnectingServer( from )) return; CL_ErrorMsg( c, args, from, NULL ); // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us CL_Disconnect_f(); } static void CL_ServerList( netadr_t from, sizebuf_t *msg ) { if( !NET_IsMasterAdr( from )) { Con_Printf( S_WARN "unexpected server list packet from %s\n", NET_AdrToString( from )); return; } // check the extra header if( MSG_ReadByte( msg ) == 0x7f ) { uint32_t key = MSG_ReadDword( msg ); if( cls.internetservers_key != key ) { Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from )); return; } MSG_ReadByte( msg ); // reserved byte } else { Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from )); return; } // serverlist got from masterserver while( MSG_GetNumBitsLeft( msg ) > 8 ) { uint8_t addr[16]; netadr_t servadr = { 0 }; if( NET_NetadrType( &from ) == NA_IP6 ) // IPv6 master server only sends IPv6 addresses { MSG_ReadBytes( msg, addr, sizeof( addr )); NET_IP6BytesToNetadr( &servadr, addr ); NET_NetadrSetType( &servadr, NA_IP6 ); } else { MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP NET_NetadrSetType( &servadr, NA_IP ); } servadr.port = MSG_ReadShort( msg ); // 2 bytes for Port // list is ends here if( !servadr.port ) break; NET_Config( true, false ); // allow remote Netchan_OutOfBandPrint( NS_CLIENT, servadr, A2A_INFO" %i", PROTOCOL_VERSION ); } if( cls.internetservers_pending ) { UI_ResetPing(); cls.internetservers_pending = false; } } /* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) { char *args; const char *c; MSG_Clear( msg ); MSG_ReadLong( msg ); // skip the -1 args = MSG_ReadStringLine( msg ); Cmd_TokenizeString( args ); c = Cmd_Argv( 0 ); Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c ); // server connection if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION )) { CL_ClientConnect( cls.legacymode, c, from ); } else if( !Q_strcmp( c, A2A_INFO )) { CL_ParseStatusMessage( from, msg ); // server responding to a status broadcast } else if( c[0] == S2A_GOLDSRC_INFO ) { CL_ParseGoldSrcStatusMessage( from, msg ); } else if( !Q_strcmp( c, A2A_NETINFO )) { CL_ParseNETInfoMessage( from, args ); // server responding to a status broadcast } else if( c[0] == A2C_GOLDSRC_PRINT || !Q_strcmp( c, A2C_PRINT )) { CL_Print( c, args, from, msg ); } else if( !Q_strcmp( c, S2C_BANDWIDTHTEST )) { CL_HandleTestPacket( from, msg ); } else if( !Q_strcmp( c, A2A_PING )) { Netchan_OutOfBandPrint( NS_CLIENT, from, A2A_ACK ); } else if( !Q_strcmp( c, A2A_GOLDSRC_PING )) { Netchan_OutOfBandPrint( NS_CLIENT, from, A2A_GOLDSRC_ACK ); } else if( !Q_strcmp( c, A2A_ACK ) || !Q_strcmp( c, A2A_GOLDSRC_ACK )) { // no-op } else if( !Q_strcmp( c, S2C_CHALLENGE ) || !Q_strcmp( c, S2C_GOLDSRC_CHALLENGE )) { CL_Challenge( c, from ); } else if( !Q_strcmp( c, S2C_REJECT ) || c[0] == S2C_GOLDSRC_REJECT || c[0] == S2C_GOLDSRC_REJECT_BADPASSWORD ) { CL_Reject( c, args, from ); } else if( !Q_strcmp( c, S2C_ERRORMSG )) { CL_ErrorMsg( c, args, from, msg ); } else if( !Q_strcmp( c, M2A_SERVERSLIST )) { CL_ServerList( from, msg ); } else { char buf[MAX_SYSPATH]; int len = sizeof( buf ); if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) { // user out of band message (must be handled in SV_ConnectionlessPacket) if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, (byte *)buf ); } else { Con_DPrintf( S_ERROR "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); } } } /* ==================== CL_GetMessage Handles recording and playback of demos, on top of NET_ code ==================== */ static qboolean CL_GetMessage( byte *data, size_t *length ) { if( cls.demoplayback ) return CL_DemoReadMessage( data, length ); return NET_GetPacket( NS_CLIENT, &net_from, data, length ); } static void CL_ParseNetMessage( sizebuf_t *msg, void (*parsefn)( sizebuf_t * )) { cls.starting_count = MSG_GetNumBytesRead( msg ); // updates each frame CL_Parse_Debug( true ); // begin parsing parsefn( msg ); cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - cls.starting_count; CL_Parse_Debug( false ); // done // we don't know if it is ok to save a demo message until // after we have parsed the frame if( !cls.demoplayback ) { if( cls.state != ca_active ) CL_WriteDemoMessage( true, cls.starting_count, msg ); if( cls.demorecording && !cls.demowaiting ) CL_WriteDemoMessage( false, cls.starting_count, msg ); } } /* ================= CL_ReadNetMessage ================= */ static void CL_ReadNetMessage( void ) { size_t curSize; void (*parsefn)( sizebuf_t *msg ); switch( cls.legacymode ) { case PROTO_LEGACY: parsefn = CL_ParseLegacyServerMessage; break; case PROTO_QUAKE: parsefn = CL_ParseQuakeMessage; break; case PROTO_GOLDSRC: parsefn = CL_ParseGoldSrcServerMessage; break; default: parsefn = CL_ParseServerMessage; break; } while( CL_GetMessage( net_message_buffer, &curSize )) { const int split_header = LittleLong( 0xFFFFFFFE ); if( cls.legacymode == PROTO_LEGACY && !memcmp( &split_header, net_message_buffer, sizeof( split_header ))) { // Will rewrite existing packet by merged if( !NetSplit_GetLong( &cls.netchan.netsplit, &net_from, net_message_buffer, &curSize ) ) continue; } MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); // check for connectionless packet (0xffffffff) first if( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) { CL_ConnectionlessPacket( net_from, &net_message ); continue; } // can't be a valid sequenced packet if( cls.state < ca_connected ) continue; if( !cls.demoplayback ) { if( MSG_GetMaxBytes( &net_message ) < 8 ) { Con_Printf( S_WARN "%s: %s:runt packet\n", __func__, NET_AdrToString( net_from )); continue; } // packet from server if( !NET_CompareAdr( net_from, cls.netchan.remote_address )) { Con_DPrintf( S_ERROR "%s: %s:sequenced packet without connection\n", __func__, NET_AdrToString( net_from )); continue; } if( !Netchan_Process( &cls.netchan, &net_message )) continue; // wasn't accepted for some reason } if( cls.state == ca_active ) { cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; } else { CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); } CL_ParseNetMessage( &net_message, parsefn ); } // build list of all solid entities per next frame (exclude clients) CL_SetSolidEntities(); // check for fragmentation/reassembly related packets. if( cls.state != ca_disconnected && Netchan_IncomingReady( &cls.netchan )) { // process the incoming buffer(s) if( Netchan_CopyNormalFragments( &cls.netchan, &net_message, &curSize )) { MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); CL_ParseNetMessage( &net_message, parsefn ); } if( Netchan_CopyFileFragments( &cls.netchan, &net_message )) { // remove from resource request stuff. CL_ProcessFile( true, cls.netchan.incomingfilename ); } } Netchan_UpdateProgress( &cls.netchan ); // check requests for time-expire CL_ProcessNetRequests(); } /* ================= CL_ReadPackets Updates the local time and reads/handles messages on client net connection. ================= */ static void CL_ReadPackets( void ) { // decide the simulation time cl.oldtime = cl.time; if( !cl.paused ) cl.time += host.frametime; // demo time if( cls.demorecording && !cls.demowaiting ) cls.demotime += host.frametime; CL_ReadNetMessage(); CL_ApplyAddAngle(); #if 0 // keep cheat cvars are unchanged if( cl.maxclients > 1 && cls.state == ca_active && !host_developer.value ) Cvar_SetCheatState(); #endif // hot precache and downloading resources if( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime ) { double checktime = Host_IsLocalGame() ? 0.1 : 1.0; if( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded ) { // check resource for downloading and precache CL_EstimateNeededResources(); CL_BatchResourceRequest( false ); cls.dl.doneregistering = false; cls.dl.custom = true; } cl.lastresourcecheck = host.realtime + checktime; } // singleplayer never has connection timeout if( NET_IsLocalAddress( cls.netchan.remote_address )) return; // if in the debugger last frame, don't timeout if( host.frametime > 5.0f ) cls.netchan.last_received = Sys_DoubleTime(); // check timeout if( cls.state >= ca_connected && cls.state != ca_cinematic && !cls.demoplayback ) { if( host.realtime - cls.netchan.last_received > cl_timeout.value ) { Con_Printf( "\nServer connection timed out.\n" ); CL_Disconnect(); return; } } } /* ==================== CL_CleanFileName Replace the displayed name for some resources ==================== */ static const char *CL_CleanFileName( const char *filename ) { if( COM_CheckString( filename ) && filename[0] == '!' ) return "customization"; return filename; } /* ==================== CL_RegisterCustomization register custom resource for player ==================== */ static void CL_RegisterCustomization( resource_t *resource ) { qboolean bFound = false; customization_t *pList; for( pList = cl.players[resource->playernum].customdata.pNext; pList; pList = pList->pNext ) { if( !memcmp( pList->resource.rgucMD5_hash, resource->rgucMD5_hash, 16 )) { bFound = true; break; } } if( !bFound ) { player_info_t *player = &cl.players[resource->playernum]; if( !COM_CreateCustomization( &player->customdata, resource, resource->playernum, FCUST_FROMHPAK, NULL, NULL )) Con_Printf( "Unable to create custom decal for player %i\n", resource->playernum ); } else { Con_DPrintf( "Duplicate resource received and ignored.\n" ); } } /* ==================== CL_ProcessFile A file has been received via the fragmentation/reassembly layer, put it in the right spot and see if we have finished downloading files. ==================== */ void CL_ProcessFile( qboolean successfully_received, const char *filename ) { int sound_len = sizeof( DEFAULT_SOUNDPATH ) - 1; byte rgucMD5_hash[16]; resource_t *p; if( COM_CheckString( filename ) && successfully_received ) { if( filename[0] != '!' ) Con_Printf( "processing %s\n", filename ); if( !Q_strnicmp( filename, DEFAULT_DOWNLOADED_DIRECTORY, sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1 )) { // skip "downloaded/" part to avoid mismatch with needed resources list filename += sizeof( DEFAULT_DOWNLOADED_DIRECTORY ) - 1; } } else if( !successfully_received ) { Con_Printf( S_ERROR "server failed to transmit file '%s'\n", CL_CleanFileName( filename )); } if( cls.legacymode == PROTO_LEGACY ) { if( host.downloadcount > 0 ) host.downloadcount--; if( !host.downloadcount ) { MSG_WriteByte( &cls.netchan.message, clc_stringcmd ); MSG_WriteString( &cls.netchan.message, "continueloading" ); } return; } for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) { if( !Q_strnicmp( filename, "!MD5", 4 )) { COM_HexConvert( filename + 4, 32, rgucMD5_hash ); if( !memcmp( p->rgucMD5_hash, rgucMD5_hash, 16 )) break; } else if( p->type == t_sound ) { const char *pfilename = filename; if( !Q_strnicmp( filename, DEFAULT_SOUNDPATH, sound_len )) pfilename += sound_len; if( !Q_stricmp( p->szFileName, pfilename )) break; } else { if( !Q_stricmp( p->szFileName, filename )) break; } } if( p != &cl.resourcesneeded ) { if( successfully_received ) ClearBits( p->ucFlags, RES_WASMISSING ); if( filename[0] == '!' ) { if( cls.netchan.tempbuffer ) { if( p->nDownloadSize == cls.netchan.tempbuffersize ) { if( p->ucFlags & RES_CUSTOM ) { HPAK_AddLump( true, hpk_custom_file.string, p, cls.netchan.tempbuffer, NULL ); CL_RegisterCustomization( p ); } } else { Con_Printf( "Downloaded %i bytes for purported %i byte file, ignoring download\n", cls.netchan.tempbuffersize, p->nDownloadSize ); } if( cls.netchan.tempbuffer ) Mem_Free( cls.netchan.tempbuffer ); } cls.netchan.tempbuffersize = 0; cls.netchan.tempbuffer = NULL; } // moving to 'onhandle' list even if file was missed CL_MoveToOnHandList( p ); } if( cls.state != ca_disconnected ) { host.downloadcount = 0; for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) host.downloadcount++; if( cl.resourcesneeded.pNext == &cl.resourcesneeded ) { byte msg_buf[MAX_INIT_MSG]; sizebuf_t msg; MSG_Init( &msg, "Resource Registration", msg_buf, sizeof( msg_buf )); if( CL_PrecacheResources( )) CL_RegisterResources( &msg, cls.legacymode ); if( MSG_GetNumBytesWritten( &msg ) > 0 ) { Netchan_CreateFragments( &cls.netchan, &msg ); Netchan_FragSend( &cls.netchan ); } } if( cls.netchan.tempbuffer ) { Con_Printf( "Received a decal %s, but didn't find it in resources needed list!\n", filename ); Mem_Free( cls.netchan.tempbuffer ); } cls.netchan.tempbuffer = NULL; cls.netchan.tempbuffersize = 0; } } /* ==================== CL_ServerCommand send command to a server ==================== */ void CL_ServerCommand( qboolean reliable, const char *fmt, ... ) { char string[MAX_SYSPATH]; va_list argptr; if( cls.state < ca_connecting ) return; va_start( argptr, fmt ); Q_vsnprintf( string, sizeof( string ), fmt, argptr ); va_end( argptr ); if( reliable ) { MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); MSG_WriteString( &cls.netchan.message, string ); } else { MSG_BeginClientCmd( &cls.datagram, clc_stringcmd ); MSG_WriteString( &cls.datagram, string ); } } /* =============== CL_UpdateInfo tell server about changed userinfo =============== */ void CL_UpdateInfo( const char *key, const char *value ) { switch( cls.legacymode ) { case PROTO_LEGACY: if( cls.state != ca_active ) break; MSG_BeginClientCmd( &cls.netchan.message, clc_legacy_userinfo ); MSG_WriteString( &cls.netchan.message, cls.userinfo ); break; case PROTO_GOLDSRC: if( cl_advertise_engine_in_name.value && !Q_stricmp( key, "name" ) && Q_strnicmp( value, "[Xash3D]", 8 )) { CL_ServerCommand( true, "setinfo \"%s\" \"[Xash3D]%s\"\n", key, value ); break; } // intentional fallthrough default: CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", key, value ); break; } } //============================================================================= /* ============== CL_SetInfo_f ============== */ static void CL_SetInfo_f( void ) { convar_t *var; if( Cmd_Argc() == 1 ) { Con_Printf( "User info settings:\n" ); Info_Print( cls.userinfo ); Con_Printf( "Total %zu symbols\n", Q_strlen( cls.userinfo )); return; } if( Cmd_Argc() != 3 ) { Con_Printf( S_USAGE "setinfo [ ]\n" ); return; } // NOTE: some userinfo comed from cvars, e.g. cl_lw but we can call "setinfo cl_lw 1" // without real cvar changing. So we need to lookup for cvar first to make sure what // our key is not linked with console variable var = Cvar_FindVar( Cmd_Argv( 1 )); // make sure what cvar is existed and really part of userinfo if( var && FBitSet( var->flags, FCVAR_USERINFO )) { Cvar_DirectSet( var, Cmd_Argv( 2 )); } else if( Info_SetValueForKey( cls.userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), sizeof( cls.userinfo ))) { // send update only on successfully changed userinfo Cmd_ForwardToServer (); } } /* ============== CL_Physinfo_f ============== */ static void CL_Physinfo_f( void ) { Con_Printf( "Phys info settings:\n" ); Info_Print( cls.physinfo ); Con_Printf( "Total %zu symbols\n", Q_strlen( cls.physinfo )); } static qboolean CL_ShouldRescanFilesystem( void ) { resource_t *res; qboolean retval = false; for( res = cl.resourcesonhand.pNext; res && res != &cl.resourcesonhand; res = res->pNext ) { if( res->type == t_generic ) { const char *ext = COM_FileExtension( res->szFileName ); if( !g_fsapi.IsArchiveExtensionSupported( ext, IAES_ONLY_REAL_ARCHIVES )) continue; if( FBitSet( res->ucExtraFlags, RES_EXTRA_ARCHIVE_CHECKED )) continue; SetBits( res->ucExtraFlags, RES_EXTRA_ARCHIVE_CHECKED ); retval = true; } } return retval; } qboolean CL_PrecacheResources( void ) { resource_t *pRes; // if we downloaded new WAD files or any other archives they must be added to searchpath if( CL_ShouldRescanFilesystem( )) FS_Rescan_f(); // NOTE: world need to be loaded as first model for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) { if( FBitSet( pRes->ucFlags, RES_PRECACHED )) continue; if( pRes->type != t_model || pRes->nIndex != WORLD_INDEX ) continue; cl.models[pRes->nIndex] = Mod_LoadWorld( pRes->szFileName, true ); SetBits( pRes->ucFlags, RES_PRECACHED ); cl.nummodels = 1; break; } // then we set up all the world submodels for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) { if( FBitSet( pRes->ucFlags, RES_PRECACHED )) continue; if( pRes->type == t_model && pRes->szFileName[0] == '*' ) { cl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, false ); cl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 ); SetBits( pRes->ucFlags, RES_PRECACHED ); if( cl.models[pRes->nIndex] == NULL ) { Con_Printf( S_ERROR "submodel %s not found\n", pRes->szFileName ); if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) { CL_Disconnect_f(); return false; } } } } if( cls.state != ca_active ) S_BeginRegistration(); // precache all the remaining resources where order is doesn't matter for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) { if( FBitSet( pRes->ucFlags, RES_PRECACHED )) continue; switch( pRes->type ) { case t_sound: if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.sound_precache ) && pRes->nIndex < ARRAYSIZE( cl.sound_index )) { if( FBitSet( pRes->ucFlags, RES_WASMISSING )) { Con_Printf( S_ERROR "Could not load sound " DEFAULT_SOUNDPATH "%s\n", pRes->szFileName ); cl.sound_precache[pRes->nIndex][0] = 0; cl.sound_index[pRes->nIndex] = 0; } else { Q_strncpy( cl.sound_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.sound_precache[0] )); cl.sound_index[pRes->nIndex] = S_RegisterSound( pRes->szFileName ); if( !cl.sound_index[pRes->nIndex] ) { if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) { S_EndRegistration(); CL_Disconnect_f(); return false; } } } } else { // client sounds S_RegisterSound( pRes->szFileName ); } break; case t_skin: break; case t_model: if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.models )) { cl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 ); if( pRes->szFileName[0] != '*' ) { if( pRes->nIndex != -1 ) { cl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, true ); if( cl.models[pRes->nIndex] == NULL ) { if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) { S_EndRegistration(); CL_Disconnect_f(); return false; } } } else { CL_LoadClientSprite( pRes->szFileName ); } } } break; case t_decal: if( !FBitSet( pRes->ucFlags, RES_CUSTOM ) && pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( host.draw_decals )) Q_strncpy( host.draw_decals[pRes->nIndex], pRes->szFileName, sizeof( host.draw_decals[0] )); break; case t_generic: if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.files_precache )) { Q_strncpy( cl.files_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.files_precache[0] )); cl.numfiles = Q_max( cl.numfiles, pRes->nIndex + 1 ); } break; case t_eventscript: if( pRes->nIndex >= 0 && pRes->nIndex < ARRAYSIZE( cl.event_precache )) { Q_strncpy( cl.event_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.event_precache[0] )); CL_SetEventIndex( cl.event_precache[pRes->nIndex], pRes->nIndex ); } break; default: break; } SetBits( pRes->ucFlags, RES_PRECACHED ); } // make sure modelcount is in-range cl.nummodels = bound( 0, cl.nummodels, MAX_MODELS ); cl.numfiles = bound( 0, cl.numfiles, MAX_CUSTOM ); if( cls.state != ca_active ) S_EndRegistration(); return true; } /* ================== CL_FullServerinfo_f Sent by server when serverinfo changes ================== */ static void CL_FullServerinfo_f( void ) { if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "fullserverinfo \n" ); return; } Q_strncpy( cl.serverinfo, Cmd_Argv( 1 ), sizeof( cl.serverinfo )); } /* ================= CL_Escape_f Escape to menu from game ================= */ static void CL_Escape_f( void ) { if( cls.key_dest == key_menu ) return; // the final credits is running if( UI_CreditsActive( )) return; if( cls.state == ca_cinematic ) SCR_NextMovie(); // jump to next movie else UI_SetActiveMenu( true ); } static void CL_ListMessages_f( void ) { int i; Con_Printf( "num size name\n" ); for( i = 0; i < MAX_USER_MESSAGES; i++ ) { if( !COM_CheckStringEmpty( clgame.msg[i].name )) break; Con_Printf( "%3d\t%3d\t%s\n", clgame.msg[i].number, clgame.msg[i].size, clgame.msg[i].name ); } Con_Printf( "Total %i messages\n", i ); } /* ================= CL_InitLocal ================= */ static void CL_InitLocal( void ) { cls.state = ca_disconnected; cls.signon = 0; memset( &cls.serveradr, 0, sizeof( cls.serveradr ) ); cl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded; cl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand; Cvar_RegisterVariable( &cl_ticket_generator ); Cvar_RegisterVariable( &cl_advertise_engine_in_name ); Cvar_RegisterVariable( &showpause ); Cvar_RegisterVariable( &mp_decals ); Cvar_RegisterVariable( &dev_overview ); Cvar_RegisterVariable( &cl_resend ); Cvar_RegisterVariable( &cl_allow_upload ); Cvar_RegisterVariable( &cl_allow_download ); Cvar_RegisterVariable( &cl_download_ingame ); Cvar_RegisterVariable( &cl_logofile ); Cvar_RegisterVariable( &cl_logocolor ); Cvar_RegisterVariable( &cl_logoext ); Cvar_RegisterVariable( &cl_logomaxdim ); Cvar_RegisterVariable( &cl_test_bandwidth ); Voice_RegisterCvars(); VGui_RegisterCvars(); // register our variables Cvar_RegisterVariable( &cl_crosshair ); Cvar_RegisterVariable( &cl_nodelta ); Cvar_RegisterVariable( &cl_idealpitchscale ); Cvar_RegisterVariable( &cl_solid_players ); Cvar_RegisterVariable( &cl_interp ); Cvar_RegisterVariable( &cl_timeout ); Cvar_RegisterVariable( &cl_charset ); Cvar_RegisterVariable( &hud_utf8 ); Cvar_RegisterVariable( &rcon_address ); Cvar_RegisterVariable( &cl_trace_consistency ); Cvar_RegisterVariable( &cl_trace_stufftext ); Cvar_RegisterVariable( &cl_trace_messages ); Cvar_RegisterVariable( &cl_trace_events ); // userinfo Cvar_RegisterVariable( &cl_nopred ); Q_strncpy( username, Sys_GetCurrentUser(), sizeof( username )); // initialize before registering variable Cvar_RegisterVariable( &name ); Cvar_Get( "ui_username", username, FCVAR_READ_ONLY|FCVAR_PRIVILEGED, "default user name" ); Cvar_RegisterVariable( &model ); Cvar_RegisterVariable( &cl_updaterate ); Cvar_RegisterVariable( &cl_dlmax ); Cvar_RegisterVariable( &cl_upmax ); Cvar_RegisterVariable( &cl_nat ); Cvar_RegisterVariable( &rate ); Cvar_RegisterVariable( &topcolor ); Cvar_RegisterVariable( &bottomcolor ); Cvar_RegisterVariable( &cl_lw ); Cvar_Get( "cl_lc", "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable lag compensation" ); Cvar_Get( "password", "", FCVAR_USERINFO, "server password" ); Cvar_Get( "team", "", FCVAR_USERINFO, "player team" ); Cvar_Get( "skin", "", FCVAR_USERINFO, "player skin" ); Cvar_RegisterVariable( &cl_nosmooth ); Cvar_RegisterVariable( &cl_nointerp ); Cvar_RegisterVariable( &cl_smoothtime ); Cvar_RegisterVariable( &cl_cmdbackup ); Cvar_RegisterVariable( &cl_cmdrate ); Cvar_RegisterVariable( &cl_draw_particles ); Cvar_RegisterVariable( &cl_draw_tracers ); Cvar_RegisterVariable( &cl_draw_beams ); Cvar_RegisterVariable( &cl_lightstyle_lerping ); Cvar_RegisterVariable( &cl_showerror ); Cvar_RegisterVariable( &cl_bmodelinterp ); Cvar_RegisterVariable( &cl_clockreset ); Cvar_RegisterVariable( &cl_fixtimerate ); Cvar_RegisterVariable( &hud_fontscale ); Cvar_RegisterVariable( &hud_fontrender ); Cvar_RegisterVariable( &hud_scale ); Cvar_RegisterVariable( &hud_scale_minimal_width ); Cvar_Get( "cl_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); Cvar_RegisterVariable( &cl_showevents ); Cvar_Get( "lastdemo", "", FCVAR_ARCHIVE, "last played demo" ); Cvar_RegisterVariable( &ui_renderworld ); Cvar_RegisterVariable( &cl_maxframetime ); Cvar_RegisterVariable( &cl_fixmodelinterpolationartifacts ); // server commands Cmd_AddCommand ("noclip", NULL, "enable or disable no clipping mode" ); Cmd_AddCommand ("notarget", NULL, "notarget mode (monsters do not see you)" ); Cmd_AddCommand ("fullupdate", NULL, "re-init HUD on start demo recording" ); Cmd_AddCommand ("give", NULL, "give specified item or weapon" ); Cmd_AddCommand ("drop", NULL, "drop current/specified item or weapon" ); Cmd_AddCommand ("gametitle", NULL, "show game logo" ); Cmd_AddRestrictedCommand ("kill", NULL, "die instantly" ); Cmd_AddCommand ("god", NULL, "enable godmode" ); Cmd_AddCommand ("fov", NULL, "set client field of view" ); Cmd_AddRestrictedCommand ("ent_list", NULL, "list entities on server" ); Cmd_AddRestrictedCommand ("ent_fire", NULL, "fire entity command (be careful)" ); Cmd_AddRestrictedCommand ("ent_info", NULL, "dump entity information" ); Cmd_AddRestrictedCommand ("ent_create", NULL, "create entity with specified values (be careful)" ); Cmd_AddRestrictedCommand ("ent_getvars", NULL, "put parameters of specified entities to client's' ent_last_* cvars" ); // register our commands Cmd_AddCommand ("pause", NULL, "pause the game (if the server allows pausing)" ); Cmd_AddRestrictedCommand( "localservers", CL_LocalServers_f, "collect info about local servers" ); Cmd_AddRestrictedCommand( "internetservers", CL_InternetServers_f, "collect info about internet servers" ); Cmd_AddRestrictedCommand( "ui_queryserver", CL_QueryServer_f, "query server info from console" ); Cmd_AddCommand ("cd", CL_PlayCDTrack_f, "Play cd-track (not real cd-player of course)" ); Cmd_AddCommand ("mp3", CL_PlayCDTrack_f, "Play mp3-track (based on virtual cd-player)" ); Cmd_AddCommand ("waveplaylen", CL_WavePlayLen_f, "Get approximate length of wave file"); Cmd_AddRestrictedCommand ("setinfo", CL_SetInfo_f, "examine or change the userinfo string (alias of userinfo)" ); Cmd_AddRestrictedCommand ("userinfo", CL_SetInfo_f, "examine or change the userinfo string (alias of setinfo)" ); Cmd_AddCommand ("physinfo", CL_Physinfo_f, "print current client physinfo" ); Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server" ); Cmd_AddRestrictedCommand( "record", CL_Record_f, "record a demo" ); Cmd_AddCommand ("playdemo", CL_PlayDemo_f, "play a demo" ); Cmd_AddCommand ("timedemo", CL_TimeDemo_f, "demo benchmark" ); Cmd_AddRestrictedCommand( "killdemo", CL_DeleteDemo_f, "delete a specified demo file" ); Cmd_AddCommand ("startdemos", CL_StartDemos_f, "start playing back the selected demos sequentially" ); Cmd_AddCommand ("demos", CL_Demos_f, "restart looping demos defined by the last startdemos command" ); Cmd_AddCommand ("movie", CL_PlayVideo_f, "play a movie" ); Cmd_AddCommand ("stop", CL_Stop_f, "stop playing or recording a demo" ); Cmd_AddCommand( "listdemo", CL_ListDemo_f, "list demo entries" ); Cmd_AddCommand ("info", NULL, "collect info about local servers with specified protocol" ); Cmd_AddCommand ("escape", CL_Escape_f, "escape from game to menu" ); Cmd_AddCommand ("togglemenu", CL_Escape_f, "toggle between game and menu" ); Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "show leaks on a map (if present of course)" ); Cmd_AddCommand ("linefile", CL_ReadLineFile_f, "show leaks on a map (if present of course)" ); Cmd_AddCommand ("fullserverinfo", CL_FullServerinfo_f, "sent by server when serverinfo changes" ); Cmd_AddCommand ("upload", CL_BeginUpload_f, "uploading file to the server" ); Cmd_AddRestrictedCommand( "replaybufferdat", CL_ReplayBufferDat_f, "development and debugging tool" ); Cmd_AddRestrictedCommand ("quit", CL_Quit_f, "quit from game" ); Cmd_AddRestrictedCommand ("exit", CL_Quit_f, "quit from game" ); Cmd_AddCommand ("screenshot", CL_GenericShot_f, "takes a screenshot of the next rendered frame" ); Cmd_AddCommand ("snapshot", CL_GenericShot_f, "takes a snapshot of the next rendered frame" ); Cmd_AddCommand ("envshot", CL_GenericShot_f, "takes a six-sides cubemap shot with specified name" ); Cmd_AddCommand ("skyshot", CL_GenericShot_f, "takes a six-sides envmap (skybox) shot with specified name" ); Cmd_AddCommand ("levelshot", CL_LevelShot_f, "same as \"screenshot\", used for create plaque images" ); Cmd_AddCommand ("saveshot", CL_GenericShot_f, "used for create save previews with LoadGame menu" ); Cmd_AddCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); Cmd_AddCommand ("reconnect", CL_Reconnect_f, "reconnect to current level" ); Cmd_AddCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" ); Cmd_AddCommand ("precache", CL_LegacyPrecache_f, "legacy server compatibility" ); Cmd_AddCommand( "richpresence_gamemode", Cmd_Null_f, "compatibility command, does nothing" ); Cmd_AddCommand( "richpresence_update", Cmd_Null_f, "compatibility command, does nothing" ); Cmd_AddCommand( "cl_list_messages", CL_ListMessages_f, "list registered user messages" ); } //============================================================================ /* ================== CL_AdjustClock slowly adjuct client clock to smooth lag effect ================== */ static void CL_AdjustClock( void ) { if( cl.timedelta == 0.0f || !cl_fixtimerate.value ) return; if( cl_fixtimerate.value < 0.0f ) Cvar_SetValue( "cl_fixtimerate", 7.5f ); if( fabs( cl.timedelta ) >= 0.001f ) { double msec, adjust; double sign; msec = ( cl.timedelta * 1000.0 ); sign = ( msec < 0 ) ? 1.0 : -1.0; msec = Q_min( cl_fixtimerate.value, fabs( msec )); adjust = sign * ( msec / 1000.0 ); if( fabs( adjust ) < fabs( cl.timedelta )) { cl.timedelta += adjust; cl.time += adjust; } if( cl.oldtime > cl.time ) cl.oldtime = cl.time; } } /* ================== Host_ClientBegin ================== */ void Host_ClientBegin( void ) { // exec console commands Cbuf_Execute (); // if client is not active, do nothing if( !cls.initialized ) return; // finalize connection process if needs CL_CheckClientState(); // tell the client.dll about client data CL_UpdateClientData(); // if running the server locally, make intentions now if( SV_Active( )) CL_SendCommand (); } /* ================== Host_ClientFrame ================== */ void Host_ClientFrame( void ) { // if client is not active, do nothing if( !cls.initialized ) return; if( cls.key_dest == key_game && cls.state == ca_active && !Con_Visible() ) Platform_SetTimer( cl_maxframetime.value ); // if running the server remotely, send intentions now after // the incoming messages have been read if( !SV_Active( )) CL_SendCommand (); clgame.dllFuncs.pfnFrame( host.frametime ); // remember last received framenum CL_SetLastUpdate (); // read updates from server CL_ReadPackets (); // do prediction again in case we got // a new portion updates from server CL_RedoPrediction (); // update voice Voice_Idle( host.frametime ); // emit visible entities CL_EmitEntities (); // in case we lost connection CL_CheckForResend (); // procssing resources on handle while( CL_RequestMissingResources( )); // handle thirdperson camera CL_MoveThirdpersonCamera(); // handle spectator movement CL_MoveSpectatorCamera(); // catch changes video settings VID_CheckChanges(); // update the screen SCR_UpdateScreen (); // update audio SND_UpdateSound (); // play avi-files SCR_RunCinematic (); // adjust client time CL_AdjustClock (); } //============================================================================ /* ==================== CL_Init ==================== */ void CL_Init( void ) { string libpath; if( host.type == HOST_DEDICATED ) return; // nothing running on the client CL_InitLocal(); VID_Init(); // init video S_Init(); // init sound Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device) // unreliable buffer. unsed for unreliable commands and voice stream MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); // IN_TouchInit(); COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath )); if( !CL_LoadProgs( libpath )) Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( )); ID_Init(); cls.build_num = 0; cls.initialized = true; cl.maxclients = 1; // allow to drawing player in menu cls.olddemonum = -1; cls.demonum = -1; } /* =============== CL_Shutdown =============== */ void CL_Shutdown( void ) { Con_Printf( "%s()\n", __func__ ); if( host.status != HOST_CRASHED && cls.initialized ) { Host_WriteOpenGLConfig (); Host_WriteVideoConfig (); Touch_WriteConfig(); } // IN_TouchShutdown (); Joy_Shutdown (); CL_CloseDemoHeader (); IN_Shutdown (); Mobile_Shutdown (); SCR_Shutdown (); CL_UnloadProgs (); cls.initialized = false; // for client-side VGUI support we use other order if( FI && FI->GameInfo && !FI->GameInfo->internal_vgui_support ) VGui_Shutdown(); if( g_fsapi.Delete ) g_fsapi.Delete( "demoheader.tmp" ); // remove tmp file SCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them S_Shutdown (); R_Shutdown (); Con_Shutdown (); } ================================================ FILE: engine/client/cl_mobile.c ================================================ /* cl_mobile.c - common mobile interface Copyright (C) 2015 a1batross This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "mobility_int.h" #include "library.h" #include "input.h" #include "platform/platform.h" static CVAR_DEFINE_AUTO( vibration_length, "1.0", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "vibration length" ); static CVAR_DEFINE_AUTO( vibration_enable, "1", FCVAR_ARCHIVE | FCVAR_PRIVILEGED, "enable vibration" ); static cl_font_t g_scaled_font; static float g_font_scale; static void pfnVibrate( float life, char flags ) { if( !vibration_enable.value || life < 0.0f ) return; // here goes platform-specific backends Platform_Vibrate( life * vibration_length.value, flags ); } static void Vibrate_f( void ) { if( Cmd_Argc() != 2 ) { Msg( S_USAGE "vibrate
\n"); return; } touch.swidth = Q_atoi( Cmd_Argv( 1 ) ); MakeRGBA( touch.scolor, Q_atoi( Cmd_Argv( 2 ) ), Q_atoi( Cmd_Argv( 3 ) ), Q_atoi( Cmd_Argv( 4 ) ), Q_atoi( Cmd_Argv( 5 ) ) ); } static touch_button_t *Touch_FindNextNoPattern( touch_button_t *buttons, const char *name, qboolean privileged ) { touch_button_t *b; for( b = buttons; b; b = b->next ) { if( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED )) continue; if( !Q_strncmp( b->name, name, sizeof( b->name ))) return b; } return NULL; } static touch_button_t *Touch_FindButtonNoPattern( touchbuttonlist_t *list, const char *name, qboolean privileged ) { return Touch_FindNextNoPattern( list->first, name, privileged ); } static touch_button_t *Touch_FindNext( touch_button_t *buttons, const char *name, qboolean privileged ) { touch_button_t *b; qboolean has_pattern = Q_strchr( name, '*' ) != NULL; if( !has_pattern ) return Touch_FindNextNoPattern( buttons, name, privileged ); for( b = buttons; b; b = b->next ) { if( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED )) continue; if( Q_stricmpext( name, b->name )) return b; } return NULL; } static touch_button_t *Touch_FindFirst( touchbuttonlist_t *list, const char *name, qboolean privileged ) { return Touch_FindNext( list->first, name, privileged ); } void Touch_SetClientOnly( byte state ) { // TODO: fix clash with vgui cursors if( touch.clientonly == state ) return; touch.clientonly = state; touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; touch.forward = touch.side = 0; if( state ) { Platform_SetCursorType( dc_arrow ); IN_DeactivateMouse(); } else { Platform_SetCursorType( dc_none ); IN_ActivateMouse(); } } static void Touch_SetClientOnly_f( void ) { if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "touch_setclientonly \n"); return; } Touch_SetClientOnly( Q_atoi( Cmd_Argv( 1 ))); } static void Touch_RemoveButtonFromList( touchbuttonlist_t *list, const char *name, qboolean privileged ) { touch_button_t *button; IN_TouchEditClear(); while(( button = Touch_FindFirst( &touch.list_user, name, privileged ))) { if( button->prev ) button->prev->next = button->next; else list->first = button->next; if( button->next ) button->next->prev = button->prev; else list->last = button->prev; Mem_Free( button ); } } void Touch_RemoveButton( const char *name, qboolean privileged ) { Touch_RemoveButtonFromList( &touch.list_user, name, privileged ); } static void IN_TouchRemoveButton_f( void ) { if( Cmd_Argc() != 2 ) { Con_Printf( S_USAGE "touch_removebutton