Compare commits

...

239 Commits

Author SHA1 Message Date
Alibek Omarov
7e06417db6 ref: use vector initializer macros 2026-05-20 13:13:07 -04:00
Alibek Omarov
1901c8a91d engine: use vector initializer macros 2026-05-20 13:11:07 -04:00
Alibek Omarov
2511c07a66 ref: use designated initializers where possible 2026-05-20 12:44:47 -04:00
Alibek Omarov
20b2332ca1 filesystem: use designated initializers where possible 2026-05-20 12:44:36 -04:00
Alibek Omarov
a244c4d453 engine: use designated initializers where possible 2026-05-20 12:44:26 -04:00
Alibek Omarov
0b14349cdb public: add Vec2, Vec3 and Vec4 macros used for initializers 2026-05-20 12:43:40 -04:00
Alibek Omarov
70272ca903 engine: platform: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
4fbdf5e8ee engine: common: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
7e939b5cd7 engine: client: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
264f69cd91 engine: server: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
40af9f992e ref: gl: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
d8e3a78aba ref: soft: refactor variable declarations 2026-05-20 20:15:01 +05:00
Alibek Omarov
28ac39fd84 ref: common: refactor variable declarations 2026-05-19 21:14:25 +05:00
Alibek Omarov
a742fa385d filesystem: refactor variable declarations 2026-05-19 21:10:29 +05:00
Alibek Omarov
e47c675386 public: refactor variable declarations 2026-05-19 21:05:09 +05:00
Владислав Сухов
de585f80fb 3rdparty: mainui: update 2026-05-19 19:38:12 +05:00
Владислав Сухов
af58ef4a2a engine: menu_int: add flags for mipmaps and alpha channel support in image handling 2026-05-19 19:38:12 +05:00
Владислав Сухов
dbe3f42485 engine: update player logo color to RGB format 2026-05-19 19:38:12 +05:00
Alibek Omarov
0053edbe7b scripts: gha: drop signing step from build_android.sh, as it's done on gradle level now 2026-05-18 13:01:22 +05:00
Alibek Omarov
61f3369957 filesystem: refactor large filesystem.c into smaller files 2026-05-18 12:50:08 +05:00
Alibek Omarov
334bebb1fb engine: use memcpy when reading sprite model to avoid unaligned access 2026-05-17 12:34:21 +05:00
Alibek Omarov
463ed05340 android: add auto-updater to the launcher 2026-05-17 12:12:40 +05:00
Alibek Omarov
92bad81d9a engine: client: fix off-by-one 2026-05-17 11:48:20 +05:00
Alibek Omarov
4c667d9d8c engine: client: bound current_position with HISTORY_MASK, in case of something passes invalid values 2026-05-17 11:45:22 +05:00
Alibek Omarov
cb04e9cbd5 engine: server: also use constant time string comparison for sv_password 2026-05-17 11:26:53 +05:00
Alibek Omarov
c2c781f039 engine: server: harden rcon, use constant time comparison, do not output untrusted data to log 2026-05-17 11:23:55 +05:00
Alibek Omarov
dbe1a2742e public: add Q_strcmp_constant_time 2026-05-17 11:23:08 +05:00
Alibek Omarov
fe2019ee06 engine: common: add more cases to COM_IsSafeToDownload 2026-05-17 11:20:00 +05:00
Alibek Omarov
e5c091c303 engine: client: fix off-by-one error in CL_ParseResource 2026-05-17 11:17:42 +05:00
Alibek Omarov
9d050e7ed3 engine: limit decompressed buffer alloc size to 64MiB 2026-05-17 11:17:23 +05:00
Alibek Omarov
be35b3503e engine: limit maximum amount of pending fragbufs 2026-05-17 10:54:51 +05:00
Alibek Omarov
5fad4ae6ca engine: client: mark rcon and connect commands privileged 2026-05-17 10:51:44 +05:00
Alibek Omarov
2290ae0239 public: disable Amiga's double-slash check in CheckNastyPath 2026-05-17 10:42:22 +05:00
Alibek Omarov
9082a47348 engine: validate paths before downloading 2026-05-17 10:41:59 +05:00
Alibek Omarov
5d7ed9d8a7 public: add CheckNastyPath from Darkplaces's VFS so it can be used in engine for path traversal checks 2026-05-16 21:28:51 +05:00
Alibek Omarov
8120669e5b engine: restore bounds check in Delta_FindStructByIndex, as it gets passed arbitrary numbers too 2026-05-16 21:27:51 +05:00
Alibek Omarov
fb6c4a224c engine: check for overflow in split packet reassembly 2026-05-16 21:13:47 +05:00
Alibek Omarov
166763a887 engine: common: limit amount of delta encoded fields by 64 in GoldSrc protocol (because this is the maximum it allows anyway) 2026-05-16 21:12:49 +05:00
Alibek Omarov
e56cf4e1da engine: client: validate player index and max players 2026-05-16 21:00:50 +05:00
Alibek Omarov
fc4b982ecc engine: client: correctly skip message that would overflow temp entity buffer 2026-05-16 20:59:57 +05:00
Alibek Omarov
d305292340 engine: zone: in CheckAlloc walk through over both allocation chains, so that we don't read out of bounds if passed pointer is not allocated by zone allocator 2026-05-16 12:56:49 +05:00
Alibek Omarov
9ee6be87d7 engine: host: try to avoid recursive Host_Error 2026-05-16 12:37:08 +05:00
Alibek Omarov
4e4a54ab68 engine: despite byte-swapping code is no-op on little endian, it still fires and can read out of bounds on some models, leading to crash 2026-05-16 12:21:46 +05:00
Alibek Omarov
516554b704 public: update miniz to latest version 2026-05-15 12:36:56 +05:00
Alibek Omarov
7be955c493 gitignore: do not even try adding slop generators garbage 2026-05-15 11:39:45 +05:00
Владислав Сухов
c6fb702fb1 engine: add MenuFactory native object 2026-05-15 08:48:58 +05:00
Alibek Omarov
cae1d32c66 3rdparty: gl-wes-v2: update 2026-05-13 11:14:32 +05:00
Alibek Omarov
5e70995989 ref: gl: more maybe unused stuff 2026-05-13 11:13:46 +05:00
Alibek Omarov
50f7f3bb87 engine: server: mark str64 as maybe unused 2026-05-13 11:12:19 +05:00
Alibek Omarov
7c3da8c381 3rdparty: gl-wes-v2: update 2026-05-13 11:11:41 +05:00
Alibek Omarov
e38d3511b5 3rdparty: nanogl: update 2026-05-13 11:11:32 +05:00
Alibek Omarov
d4fcebb377 3rdparty: MultiEmulator: update 2026-05-13 11:11:22 +05:00
Alibek Omarov
996f0e906b engine: client: drop double free check from dbghulls 2026-05-13 10:53:21 +05:00
Alibek Omarov
0f490a2f16 engine: client: fix warnings, cleanup unused or maybe-uninitialized variables 2026-05-13 10:48:37 +05:00
Alibek Omarov
cef6cb58bb ref: gl: fix maybe-uninitialized 2026-05-13 10:47:15 +05:00
Alibek Omarov
47aad1b3a3 ref: soft: cleanup unused variables 2026-05-13 10:46:59 +05:00
Alibek Omarov
ff8efebba2 engine: fix more warnings 2026-05-13 10:46:44 +05:00
Alibek Omarov
be6571a6a2 3rdparty: library_suffix: update 2026-05-13 10:45:50 +05:00
Alibek Omarov
1d67585df4 3rdparty: mainui: update 2026-05-13 10:45:39 +05:00
Alibek Omarov
10d441f825 public: fix warnings, add guards around some of the macros, remove unused variable 2026-05-13 10:30:35 +05:00
Alibek Omarov
2dc132f817 engine: common: fix up warnings, clean up unused variables 2026-05-13 10:13:52 +05:00
Alibek Omarov
e9bdbf2f33 ref: gl: mark maybe unused globals 2026-05-13 10:11:47 +05:00
Alibek Omarov
b630a84447 common: xash3d_types: add macro MAYBE_UNUSED 2026-05-13 10:11:21 +05:00
Alibek Omarov
dccd076b92 engine: platform: sdl2: cleanup unused variables 2026-05-13 10:09:49 +05:00
Alibek Omarov
cb7ea5729a engine: server: cleanup unused variables 2026-05-13 10:09:37 +05:00
Alibek Omarov
9bad17505e filesystem: cleanup unused variables 2026-05-13 10:09:19 +05:00
Alibek Omarov
bf990d1040 ref: gl: cleanup unused variables 2026-05-13 10:09:09 +05:00
Alibek Omarov
63e112bf4c engine: client: soundlib: add more broken null files 2026-05-13 09:32:50 +05:00
Alibek Omarov
0aac2e4d32 engine: zone: add small allocation optimization, reducing the size of <255 bytes allocations at the cost of less debug info
Keep it opt-in through a flag at pool alloc time.
Opt-in console related allocations, they are usually small enough strings.
2026-05-13 09:32:50 +05:00
Alibek Omarov
33b3cdb0f5 engine: client: soundlib: catch broken null.wav and replace it with silence 2026-05-10 03:02:24 +05:00
Alibek Omarov
9c4edaed21 engine: client: sound: switch to single-pass mixing, now channels gets upscaled directly to DMA speed, fixing the issue with s_lerping 2026-05-10 02:48:23 +05:00
Alibek Omarov
d4205867dd public: simplify COM_Strip and COM_DefaultExtension by reusing extension extraction function. Add moar tests for them 2026-05-10 00:56:44 +05:00
Alibek Omarov
3c07680263 android: pack crash log to ZIP file, put system and intent info alongside it. Properly handle if multple email clients are installed on device (tested with gmail, outlook and k-9). 2026-05-10 00:32:29 +05:00
Alibek Omarov
e5d6f66f61 filesystem: strip exec flag and add nowrite flag to archives
pk3dir now disallows executables, which shouldn't be a problem for anybody.
Revised FS_Path command output.
2026-05-09 19:22:10 +05:00
Alibek Omarov
a106924c7f filesystem: avoid scanning game directory as executable in FS_FindLibrary on Android, generic refactoring 2026-05-09 16:57:21 +05:00
Alibek Omarov
11ee883c84 android: show build info for mod's downloaded libraries 2026-05-09 16:57:21 +05:00
Alibek Omarov
881f0c5ca7 engine: show the missing library warning message on Android, but only if APK doesn't try to override it, skip client for now 2026-05-09 16:57:21 +05:00
Alibek Omarov
ac0c8a2210 android: implement game library downloading 2026-05-09 16:57:21 +05:00
Alibek Omarov
dba090e0da android: bundle Let's Encrypt's ISRG Root X1 certificate for EOL devices 2026-05-09 16:57:21 +05:00
Alibek Omarov
262f32b002 engine: platform: android: implement library loading from VFS paths on Android 2026-05-09 16:57:21 +05:00
Zane van Iperen
843f6297ed engine: net_ws: fix NET_QueuePacket indentation after previous change 2026-05-09 13:15:17 +05:00
Zane van Iperen
d1510345f1 engine: net_ws: round-robin IPv4/IPv6 reads
Previously, it would always return on the first one that had activity.
In practice, this meant that if IPv4 starts getting slammed, IPv6 reads
wouldn't happen.
2026-05-09 13:15:17 +05:00
Alibek Omarov
36307bb386 android: handle crash logs created by the engine, keep them for 30 days for current version 2026-05-07 17:58:25 +05:00
Alibek Omarov
52953a7647 engine: platform: posix: drop crash info into crash.log on Android 2026-05-07 15:48:34 +05:00
Alibek Omarov
ebeff144f9 Merge remote-tracking branch 'origin/master' 2026-05-07 13:50:55 +05:00
Alibek Omarov
c8cc953d48 engine: platform: posix: make crash message a global because the stack could be limited when we hit crash handler 2026-05-07 13:45:39 +05:00
Alibek Omarov
d6bf06e997 engine: common: check XASH3D_BASEDIR when opening log file, fixes engine.log creation on Android 2026-05-07 12:23:56 +05:00
Alibek Omarov
568b7abc5f scripts: waifulib: xcompile: set max-page-size to 64k on PSVita to workaround latest toolchain issue 2026-05-07 12:07:25 +05:00
Alibek Omarov
1b7f50d3c6 scripts: waifulib: xcompile: fix build error with newer NDKs caused by incompatibility of legacy STL and Clang 2026-05-07 12:05:26 +05:00
Alibek Omarov
c8e1c2673f engine: client: sound: revert singleplayer check for local sounds 2026-05-07 11:57:09 +05:00
Alibek Omarov
c2df265de7 engine: operate on a local copy of dspriteframe_t to avoid unaligned access on ARMv7 2026-05-07 11:56:08 +05:00
Alibek Omarov
e9e885534a filesystem: dir: hand off file existence check to the OS if path starts with .. or ../ 2026-05-07 10:32:00 +05:00
Alibek Omarov
95cc6c4445 filesystem: small refactoring, remove double nullptr checks 2026-05-07 10:32:00 +05:00
Alibek Omarov
086cb6aab8 engine: move {gamedir}/downloaded directory to {gamedir}_downloads for compatibility
Some mods like Sven-Coop 4.8 stupidly open game files directly and need this to
load downloaded soundcache.
2026-05-07 10:32:00 +05:00
Alibek Omarov
e6a44b70e0 engine: client: always load client.dll last to crash on nullptr in mods that fetch cvar pointers early, add comment for anyone who would modify this file 2026-05-05 01:15:16 +05:00
Alibek Omarov
fdb59e990e engine: rename Host_IsLocalGame to Host_IsSinglePlayerGame to avoid confusion 2026-05-05 01:04:57 +05:00
Alibek Omarov
eb79bc6b40 engine: use Host_IsLocalClient where it should be, as it checks for local games, not specifically singleplayer games 2026-05-05 01:03:03 +05:00
Alibek Omarov
e907e9fc95 engine: client: fix check for local game when deciding to enable netchan splitsize extension 2026-05-05 00:38:33 +05:00
SNMetamorph
fc356c88b3 engine: client: minor fixes in steam broker client side code 2026-05-02 10:50:21 +05:00
SNMetamorph
bc672e5aca Documentation: extensions: updated description of steam broker protocol 2026-05-02 10:50:21 +05:00
SNMetamorph
2635fd9739 engine: common: make use NET_MakeSocketNonBlocking instead of direct system API functions 2026-05-02 10:50:21 +05:00
SNMetamorph
653fe51fd4 engine: client: refactor data serializing in steam broker client side logic code 2026-05-02 10:50:21 +05:00
SNMetamorph
980d9fcaab engine: client: improve logging in steam broker client side logic code 2026-05-02 10:50:21 +05:00
SNMetamorph
72ef5cffca engine: client: total refactoring of steam broker client side logic code 2026-05-02 10:50:21 +05:00
SNMetamorph
a707f5933d engine: common: expose network utilities functions needed for broker 2026-05-02 10:50:21 +05:00
Alibek Omarov
b9b0ff5769 3rdparty: mainui: update 2026-05-01 20:10:06 +05:00
Marc-Antoine Lortie
982a216df4 Fix hlsdk-xash3d references where applicable. 2026-05-01 19:02:13 +05:00
Alibek Omarov
7d87d65a3f engine: server: lift down Host_Error on NULL name in PrecacheModel 2026-04-28 20:05:09 +05:00
Alibek Omarov
6e4e048ef8 ci: thanks for your service, cirrus. F. 2026-04-28 11:25:25 +05:00
Alibek Omarov
5b6b372cfe ref: gl: drop broken minlight/scale cvars for litwater. Keep them for mods. Fix post-merge issues 2026-04-28 11:21:56 +05:00
Alibek Omarov
2f31cf315c ref: gl: add litwater support 2026-04-28 11:12:44 +05:00
Alibek Omarov
910cce56ca Documentation: extensions: document lightmapped water extension 2026-04-28 11:12:11 +05:00
Alibek Omarov
68dba8b4e0 engine: add litwater support 2026-04-28 11:12:11 +05:00
Alibek Omarov
dbfeacfecc 3rdparty: vgui_support: update 2026-04-28 09:50:50 +05:00
Alibek Omarov
0bdd4648bc utils: mdldec: explicitly cast chars to bytes in ctype functions 2026-04-28 09:50:33 +05:00
Alibek Omarov
ec8059173d public: explicitly cast chars to bytes in ctype functions 2026-04-28 09:50:33 +05:00
Alibek Omarov
f91e5a7647 engine: explicitly cast chars to bytes in ctype functions 2026-04-28 09:50:33 +05:00
Alibek Omarov
7942d9b1bc engine: mod_bmodel: refactor unified bmodel loader
* Use offsets into dbspmodel_t struct, get rid of srcmodel global variable.
* Allocate dbspmodel_t when it's required
* Make srclumps const, move Blue-Shift hack into lump loader's flag.
2026-04-26 06:29:04 +05:00
Alibek Omarov
7abf0c819f 3rdparty: update xash-extras and mainui 2026-04-26 05:52:30 +05:00
Alibek Omarov
85eb82a370 Revert "engine: client: show difference between demo time and real time in timedemo result"
This reverts commit 90e25cc623.
2026-04-26 04:26:50 +05:00
Alibek Omarov
90e25cc623 engine: client: show difference between demo time and real time in timedemo result 2026-04-26 04:15:15 +05:00
Alibek Omarov
fca4152d5a ref: gl: fix typo in gl_drawrangeelements 2026-04-26 04:13:50 +05:00
Alibek Omarov
2ccef63754 engine: ref_api: change RefGetParm return type to intptr_t, bump RefAPI version 2026-04-26 04:12:52 +05:00
Alibek Omarov
87615784b9 engine: client: fix server list response parsing on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
d3b01d313b ref: gl: do not limit texture units by broken report of shaders extension 2026-04-26 04:12:52 +05:00
Alibek Omarov
a3897f402a 3rdparty: mainui: update 2026-04-26 04:12:52 +05:00
Alibek Omarov
21d84ee692 ref: gl: add -glnoerr flag that enables no error context, for possible performance gains 2026-04-26 04:12:52 +05:00
Alibek Omarov
e30ce9c150 engine: fix palette and builtin textures on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
22391eba1b engine: handle swapping sequence files in LoadCacheFile for games with external StudioModelRenderer 2026-04-26 04:12:52 +05:00
Alibek Omarov
800725453a engine: soundlib: pass XASH_BIG_ENDIAN to ov_read in vorbis loader 2026-04-26 04:12:52 +05:00
Alibek Omarov
22ecec095e engine: platform: sdl: request AUDIO_S16SYS to make sound output in native endian 2026-04-26 04:12:52 +05:00
Alibek Omarov
8545ed5ccb engine: mod_studio: implement loading sequence files on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
a4fdf19ad8 engine: client: Fix font loading on BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
75e4a21d7a engine: mod_studio: more studio model swapping 2026-04-26 04:12:52 +05:00
Alibek Omarov
1099344a7d engine: soundlib: swap WAV data 2026-04-26 04:12:52 +05:00
Alibek Omarov
ab1eaed0a5 engine: platform: posix: remove Sys_Crash and Sys_CrashDetailsLibbacktrace from the output by matching function name only once 2026-04-26 04:12:52 +05:00
Alibek Omarov
76a01d0855 engine: platform: posix: remove extra newline from libbacktrace output 2026-04-26 04:12:52 +05:00
Alibek Omarov
b00679ee30 engine: make Sys_LogFileNo return -1 when log file isn't opened 2026-04-26 04:12:52 +05:00
Alibek Omarov
b3bb2fc16f engine: imagelib: add IL_HOST_ENDIAN flag to mark on-disk data that's already in host endianness 2026-04-26 04:12:52 +05:00
Alibek Omarov
86a2ea29e4 wscript: define libbacktrace as libbacktrace_custom, fix DL_ITERATE_PHDR not defined due to failing fallback check
* Changed to libbacktrace_custom to not collide with libbacktrace built as part of compiler's runtime
2026-04-26 04:12:52 +05:00
Alibek Omarov
d4f8543556 engine: platform: posix: better libbacktrace error output 2026-04-26 04:12:52 +05:00
Alibek Omarov
e850728016 engine: soundlib: fixes for BE 2026-04-26 04:12:52 +05:00
Alibek Omarov
11877f4c96 engine: imagelib: fixes for BE 2026-04-26 04:12:52 +05:00
Denis Drakhnia
2ba5030b62 engine: server: fix no response to goldsrc info query 2026-04-24 21:56:22 +05:00
Alibek Omarov
06b14800e1 engine: mod_bmodel: fix bspx swap definition 2026-04-24 08:00:49 +05:00
Alibek Omarov
bb7c85fd22 wscript: turn on progress bar by default 2026-04-24 07:31:36 +05:00
Alibek Omarov
37ce62fa6a waf: update to latest wafsrc + progress-bar-enhacnement 2026-04-24 07:31:11 +05:00
Alibek Omarov
e1b29e2da3 engine: temporarily disable swapping extended header 2026-04-24 06:58:53 +05:00
Alibek Omarov
b60afd73e9 public: swaplib: add comments 2026-04-24 06:58:53 +05:00
Alibek Omarov
84447a5292 public: crclib: add fix for md5 from q3 2026-04-24 06:58:53 +05:00
Alibek Omarov
fe4ea4aabf engine: server: fix pfnMessageEnd on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
878822290c public: crclib: fix CRC32_BlockSequence on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
04ca5d7cb0 engine: common: use FloatAsUint and UintAsFloat to write and read floats to/from sizebuf_t 2026-04-24 06:58:53 +05:00
Alibek Omarov
d46ed40f33 engine: pre-swap studio model before loading on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
82e031205c engine: pre-swap sprite before loading on BE 2026-04-24 06:58:53 +05:00
Alibek Omarov
6f5f04a29d engine: server: fix testpacket on BE server 2026-04-24 06:58:53 +05:00
Alibek Omarov
f9ed578bf1 engine: move CRC32_MapFIle to mod_bmodel, as it's a common function. Use le_struct_swap 2026-04-24 06:58:53 +05:00
Alibek Omarov
86c84c7de7 engine: common: add struct swapping for BSP models
With support for Quake BSP, HL BSP, extended HL BSP and BSPX.
2026-04-24 06:58:53 +05:00
Alibek Omarov
489c1c9dd5 public: add swaplib 2026-04-24 06:58:53 +05:00
Alibek Omarov
1df6a040ec engine: common: fix WriteBits/ReadBits 2026-04-24 06:58:53 +05:00
Alibek Omarov
82c2ca53fb engine: common: fix WriteUBitLong/ReadUBitLong 2026-04-24 06:58:53 +05:00
Alibek Omarov
3ddd1e2448 engine: common: fix LZSS on big endian 2026-04-24 06:58:53 +05:00
Alibek Omarov
371d619685 engine: platform: posix: remove dead ucontext.h include 2026-04-24 06:58:53 +05:00
Владислав Сухов
480f29e96e engine: common: mod_studio: fix model traversal and crash in Mod_StudioComputeBounds
Mod_StudioComputeBounds incorrectly assumed that all models across all body parts were laid out contiguously in memory. This caused out-of-bounds reads or reading garbage data when loading complex models, leading to crashes.
2026-04-24 01:13:46 +05:00
Zane van Iperen
95548053eb engine: common: set cmd_args to "" when tokenising, if we've no arguments
Fixes metamod-fwgs[1] breaking when CMD_ARGS() returns NULL.

[1]: eedede40e8/metamod/src/commands_meta.cpp (L63)
2026-04-22 21:27:27 +05:00
Владислав Сухов
0daf2c2b4d engine: client: add cvar to enable/disable gamepad gyro 2026-04-22 21:26:34 +05:00
Alibek Omarov
e2d5bea327 ref: cleanup unused functions 2026-04-21 23:10:32 +05:00
Alibek Omarov
306a5812a9 engine: server: fix svc_setpause usage in spawn command, fix minor protocol issue when spawnnig particle from pmove 2026-04-20 01:33:54 +05:00
Alibek Omarov
a3dba64513 public: move atlas packing code from renderer, add tests 2026-04-20 01:33:54 +05:00
Alibek Omarov
491ba9bc7d 3rdparty: mainui: update 2026-04-19 23:12:55 +05:00
Alibek Omarov
1c462df8a2 common: q_client: include stdint.h and xash3d_types.h 2026-04-19 23:12:44 +05:00
Alibek Omarov
ed975976f8 engine: client: add feature to reupload logo if any of logo related cvars were changed 2026-04-19 22:46:51 +05:00
Alibek Omarov
bbf8ab378d engine: server: add rate limiting for updating client's resources data too often 2026-04-19 22:30:04 +05:00
Alibek Omarov
5383e36af5 engine: net_chan: fix off-by-one error when trying to decompress packet 2026-04-19 20:50:42 +05:00
Alibek Omarov
897fb98b93 engine: net_chan: fix Netchan_AddBufferToList leaking pointer 2026-04-19 20:43:32 +05:00
Alibek Omarov
96e88adfcd engine: client: try to make R_Blood more accurate 2026-04-19 17:05:26 +05:00
Alibek Omarov
4dde4047f1 engine: make particle texture white, shift 1 pixel down and to the right 2026-04-19 17:04:40 +05:00
Alibek Omarov
905161529f engine: client: ref_common: reorganize R_CreateBuiltinTextures, but also fix a typo without assigned alpha to particle texture 2026-04-19 01:32:40 +05:00
Alibek Omarov
044523434d ref: add R_EntityDynamicLight that calculates lighting for studio and alias models, and also located in libref_common 2026-04-19 01:17:28 +05:00
Alibek Omarov
da4c4bb76a ref: remove unused sprite_radius global variable 2026-04-19 00:32:45 +05:00
Alibek Omarov
95b3271d68 engine: client: move input stuff to input subfolder 2026-04-18 22:18:50 +05:00
Alibek Omarov
3361a5d760 engine: client: split OSK into it's own file 2026-04-18 22:18:50 +05:00
Alibek Omarov
b725d2ece0 engine: client: moving beds 2026-04-18 22:18:50 +05:00
Alibek Omarov
66bee8b468 engine: client: fix trimmed autocomplete output for keys 2026-04-18 22:18:50 +05:00
Alibek Omarov
5e664dc3e9 engine: client: check for upper bound limit in Key_StringToKeynum 2026-04-18 22:18:50 +05:00
Alibek Omarov
bfdfeb3dc0 engine: client: fix Joy_GetHatValueForAxis threshold for forward axis, fix bindings limit 2026-04-18 22:18:50 +05:00
Alibek Omarov
49248f5e14 engine: client: touch fixes, fix division-by-zero in Touch_AspectRatio, GRID_COUNT, fix Touch_RemoveButtonFromList 2026-04-18 22:18:50 +05:00
Владислав Сухов
d03ea4c10a engine: client: in_gyro: fix inverted yaw axis 2026-04-18 00:41:46 +05:00
ksagameng
780dbd6183 scripts: gha: fix ios ci builds not including game libs 2026-04-17 19:41:30 +05:00
GLoOoccK
50da79f1e0 Update cl_parse_gs.c 2026-04-17 19:40:58 +05:00
Alibek Omarov
62201900cf ref: null: remove unused function 2026-04-16 23:25:32 +05:00
Alibek Omarov
ae233ac491 Merge branch 'refapi-15' 2026-04-16 23:19:06 +05:00
Владислав Сухов
dd1cb04cb7 engine: client: sdl: add support for built-in device gyroscope using SDL2 Sensor API (Android/iOS) 2026-04-16 00:15:35 +05:00
Alibek Omarov
74869c8f5d ref: make R_GatherPlayerLight shared between renderers 2026-04-14 22:36:15 +05:00
Alibek Omarov
dd712a0df9 engine: update Mod_PointInLeaf signature for RefAPI 2026-04-14 22:36:15 +05:00
Alibek Omarov
bfdb62d31e engine: ref_api: complete the RefAPI 15 changelog 2026-04-14 22:36:15 +05:00
Alibek Omarov
ae7b9eb28d engine: move creation of default textures to the engnie 2026-04-14 22:36:15 +05:00
Alibek Omarov
c63e13c8f4 engine: move handling of AVI textures into the engine 2026-04-14 22:36:15 +05:00
Alibek Omarov
d3e1407675 engine: remove unused R_Flush and VGUI_UploadTextureBlock from RefAPI 2026-04-14 22:36:15 +05:00
Alibek Omarov
e2e4728c7c engine: remove R_BeamCull from RefAPI 2026-04-14 22:36:15 +05:00
Alibek Omarov
dd3583d11a engine: move details textures parser to the engine 2026-04-14 21:18:57 +05:00
Alibek Omarov
e6924844da engine: move FillRenderAPI and FillTriAPI to renderers to reduce amount of exports 2026-04-14 21:18:56 +05:00
Alibek Omarov
a49db03561 engine: move StudioAPI negotiation to the engine 2026-04-14 21:18:03 +05:00
Alibek Omarov
84b7b5803b ref: soft: remove mistakingly put const on RI.rvp 2026-04-13 23:13:49 +05:00
Alibek Omarov
c224d39ba7 ref: soft: replace RI.onlyClientDraw with RF_ONLY_CLIENTDRAW flag in viewpass 2026-04-13 23:13:49 +05:00
Alibek Omarov
9b24b08df1 ref: gl: remove onlyClientDraw, drawOrtho, RI.params with viewpass flag check. RF_DRAW_OVERVIEW and RF_DRAW_CUBEMAP are cannot be set at the same time, so drawOrtho check replaced by RF_DRAW_CUBEMAP only 2026-04-13 23:13:49 +05:00
Alibek Omarov
a37b0e58f1 ref: soft: get rid of RI.drawOrtho, it's a debugging/development tool, not suited for software renderer use 2026-04-13 23:13:49 +05:00
Alibek Omarov
afca422e35 ref: soft: read ref_viewpass_t flags instead of cached RI.drawWorld 2026-04-13 23:13:49 +05:00
Alibek Omarov
3440ddf443 ref: gl: read ref_viewpass_t flags instead of cached RI.drawWorld 2026-04-13 23:13:49 +05:00
Alibek Omarov
6730ed9555 ref: soft: remove unused isSkyVisible 2026-04-13 23:13:49 +05:00
Alibek Omarov
fdd3a44fa0 ref: gl: remove unused isSkyVisible 2026-04-13 23:13:49 +05:00
Alibek Omarov
241c926076 ref: soft: replace RI.{vieworg,viewangles,pvsorigin} by ref_viewpass_t fields 2026-04-13 23:13:49 +05:00
Alibek Omarov
148a9a5a8c ref: gl: replace RI.{vieworg,viewangles,pvsorigin} by ref_viewpass_t fields 2026-04-13 23:13:49 +05:00
Alibek Omarov
513984d7fd ref: soft: save original viewpass and access viewport and fov through it 2026-04-13 23:13:49 +05:00
Alibek Omarov
1009795d5c ref: gl: save original viewpass and access viewport and fov through it 2026-04-13 23:13:49 +05:00
Alibek Omarov
e3d2da74db engine: move underwater fov effect handling to the engine 2026-04-13 23:13:49 +05:00
Alibek Omarov
ff3a57ae8a ref: gl: drop legacy mirror viewpass code 2026-04-13 23:13:49 +05:00
Alibek Omarov
271a6e2210 ref: soft: drop broken code for all non-viewer viewpasses 2026-04-13 23:13:49 +05:00
Alibek Omarov
0d40357b93 ref: common: wscript: fix psvita build 2026-04-13 13:01:18 +05:00
Alibek Omarov
a9dcdd034c ref: soft: fix build 2026-04-13 13:01:18 +05:00
Alibek Omarov
7324ff43fc ref: add GL_ResampleTexture to libref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
d85e7fc2b1 ref: move remaining light functions to libref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
d91ecfa3df ref: gl: refactor R_LightVec 2026-04-13 13:01:18 +05:00
Alibek Omarov
b7a3a02aad ref: common: add R_PushDlightsForBmodel 2026-04-13 13:01:18 +05:00
Alibek Omarov
5aca78951c ref: move R_PushDlights and R_MarkLights to ref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
81abae65b5 ref: move RunLightstyles to ref_common 2026-04-13 13:01:18 +05:00
Alibek Omarov
f5f6d9bbe4 engine: move sprite texture loading back to engine, only leave rendering for renderer 2026-04-13 13:01:18 +05:00
Alibek Omarov
ef73b55413 ref: common: move malloc and GL_InitRandomTable 2026-04-13 13:01:18 +05:00
Alibek Omarov
8334695522 ref: common: draft new static library, containing shared code for ref_gl and ref_soft 2026-04-13 13:01:18 +05:00
Alibek Omarov
d7f416d659 engine: draft new RefAPI 14: remove reserved functions from render api, remove CL_AddCustomBeam 2026-04-13 13:01:18 +05:00
a1batross
5f6577df2e engine: client: don't allow con_fontscale lower than 1
Prevents possible division by zero
2026-04-12 14:50:58 +05:00
Sergey Degtyar
e87f307ac1 engine: image: fix blue halo around sprays 2026-04-10 15:00:02 +05:00
Владислав Сухов
7e015a268b engine: client: in_joy: implement per-axis gyroscope deadzone support 2026-04-10 14:59:30 +05:00
TheEVolk
a679c53111 engine: client: add cvar sv_voice_singleplayer flag FCVAR_SERVER 2026-04-10 14:58:46 +05:00
TheEVolk
9e8d248044 engine: client: voice api 2026-04-10 14:58:46 +05:00
Alibek Omarov
055d646225 ref: gl: merge refactoring changes from litwater branch 2026-04-09 11:43:47 +05:00
ksagameng
a2514b6916 platform: ios: Remove FTP server as latest apple clang broke it 2026-04-09 10:25:18 +05:00
ksagameng
0ac5e78040 scripts: ios: Modify script to work with dylib loading changes 2026-04-09 10:25:18 +05:00
ksagameng
a82221e3df engine: platform: No more lib_ios, use FS_FindLibaray to load dylibs instead 2026-04-09 10:25:18 +05:00
330 changed files with 24873 additions and 30178 deletions

View File

@@ -1,32 +0,0 @@
task:
name: freebsd-14-amd64
freebsd_instance:
image_family: freebsd-14-3
setup_script:
- pkg update
- pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
- git submodule update --init --recursive
test_script:
- ./scripts/cirrus/build_freebsd.sh
task:
name: freebsd-15-amd64
freebsd_instance:
image_family: freebsd-15-0-amd64-zfs
setup_script:
- pkg update
- pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
- git submodule update --init --recursive
test_script:
- ./scripts/cirrus/build_freebsd.sh
#task:
# name: freebsd-16-amd64
# freebsd_instance:
# image_family: freebsd-16-0-snap
# setup_script:
# - pkg update
# - pkg install -y pkgconf git sdl2 python freetype2 libvorbis opusfile bzip2 libbacktrace libogg libvorbis
# - git submodule update --init --recursive
# test_script:
# - ./scripts/cirrus/build_freebsd.sh

13
.gitignore vendored
View File

@@ -361,3 +361,16 @@ cmake-build-*
3rdparty/SDL
3rdparty/hlsdk-portable
!scripts/build-ninja.py
# slop
CLAUDE.md
AGENTS.md
GEMINI.md
.claude/
.gemini/
.cursor*
.windsurf*
.aider*
.continue/
.codeium/
.github/copilot-instructions.md

View File

@@ -20,10 +20,10 @@ int main (void) {
}'''
FRAGMENT_GETPAGESIZE='''#include <unistd.h>
int main(void) { return getpagesize() }'''
int main(void) { return getpagesize(); }'''
FRAGMENT_GETEXECNAME='''#include <stdlib.h>
int main(void) { return getexecname() != 0 }'''
int main(void) { return getexecname() != 0; }'''
FRAGMENT_STRNLEN='''#include <string.h>
int main(int argc, char **argv) { return (int)strnlen(argv[0], 10); }'''
@@ -120,8 +120,8 @@ def configure(conf):
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_DL_ITERATE_PHDR % 'link.h', ' dl_iterate_phdr function in link.h', 'HAVE_DL_ITERATE_PHDR_LINK_H', 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_SYS_LINK_H', 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'),
@@ -137,6 +137,9 @@ def configure(conf):
msg='Checking for in parallel'
)
if conf.is_defined('HAVE_DL_ITERATE_PHDR_LINK_H') or conf.is_defined('HAVE_DL_ITERATE_PHDR_SYS_LINK_H'):
conf.define('HAVE_DL_ITERATE_PHDR', 1)
conf.env[DEFKEYS].sort()
conf.write_config_header()
@@ -181,7 +184,8 @@ def build(bld):
task = bld.stlib(
source = ['libbacktrace/' + i for i in sources],
target = 'backtrace',
# while we're using upstream libbacktrace, sometimes compiler picks up it's own version that isn't built with required options
target = 'backtrace_custom',
features = 'frandomseed' if bld.env.HAVE_FRANDOM_SEED else '',
use = 'EXTRAFLAGS lzma z zstd',
includes = '. libbacktrace/',

View File

@@ -22,7 +22,7 @@ The other yet unsupported configuration is the big endian.
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).
Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-portable/). 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.

View File

@@ -0,0 +1,19 @@
## Lightmapped water
Xash3D FWGS supports lightmapped water, as an extension. It adds three new cvars and new worldspawn key values.
### For level designers:
If you're a level designer and intend to make your level to have lightmapped water, you can put these keyvalues to worldspawn entity description (always first entity in entities list):
| Key | Value | Description |
| ---------------------- | ------- | ----------- |
| `_litwater` | integer | Set to any non-zero value to enable lightmapped water. Overrides `gl_litwater_force` cvar value. |
| `_litwater_minlight` | integer | Minimal lightmap value water surface will receive. Helps to avoid too dark areas when water isn't properly lit. If not set, defaults to zero. |
| `_litwater_scale` | float | Scales up lightmap value for water surfaces. If not set, defaults to 1.0. |
### For players:
Some of the maps already have computed lightmap for water surfaces and sometimes water has been properly lit but the support hasn't been declared by the level designer.
As a player, you can enable it in `Video options` menu or through console with `gl_litwater_force` cvar. There are also `gl_litwater_minlight` and `gl_litwater_scale` cvars that function similar to keys above. The default values has been set to `192` and `1.25` respectively to slightly avoid issues with maps that wasn't intended to have lightmapped water.

View File

@@ -16,6 +16,7 @@ Only these objects are guaranteed to be available on all targets.
|-------------|-----------|
| `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`. |
| `MenuFactory` | Returns a `CreateInterface` function pointer (`pfnCreateInterface_t`) for the currently loaded menu library. |
#### Android-specific objects

View File

@@ -1,8 +1,139 @@
# Steam broker protocol
`sb_connect <ip:port> <server's steam id> <secure> <challenge>` - used for obtaining auth ticket during connection to GoldSrc server
# Steam API Broker Protocol Specification
`sb_disconnect <ip:port> <challenge>` - used for signaling broker about disconnecting from GoldSrc server
## Overview
`sb_gamedir <gamedir>` - used for signaling broker about game startup and for announcing started mod, so broker could choose proper AppID for Steam API initialization
Due to proprietary nature of Steamworks SDK, it cannot be run on same amount
of platforms supported by Xash3D FWGS, neither we can link library directly due to
GNU GPLv3 license.
`sb_terminate` - used for signaling broker about game shutdown, ideally broker should be terminated too (and later restarted automatically and waiting for sb_gamedir message)
However, here comes the broker, by running it (in trusted
network, preferrably) on a machine that has Steam client installed, the
engine can communicate with it, acquiring needed information to log-in into
Steam protected multiplayer servers.
## Key Characteristics
Broker uses a simple TCP-based binary protocol with the following properties:
- Single active session per connection: once a session is activated, the broker will reject all other TCP connections until the session is terminated.
- All messages follow a consistent structure with a header, length, and payload: this is called a "frame".
- Stateful interactions: commands affect the session state, and certain commands are only valid in specific states.
- Numeric parameters are encoded in little-endian format for compatibility with majority of platforms.
---
## Frame Format
All communication uses a fixed frame structure:
| Field | Size | Type | Description |
|-------|------|------|-------------|
| Header | 4 bytes | ASCII | Frame start signature |
| Length | 2 bytes | uint16_t (LE) | Size of payload in bytes |
| Payload | N bytes | Binary | Command string or response data |
**Notes**:
- Header must be exactly `SBRK` (`53 42 52 4B` in hexadecimal form)
- Maximum frame size: soft limit is 4096 bytes, hard limit is 65535 bytes (due to uint16_t usage)
---
## Session Lifecycle
```
┌──────────────┐
│ IDLE │ ← Initial state, accepts new TCP connections
└──────────────┘
│ sb_gamedir
┌──────────────┐
│ ACTIVE │ ← Session established, rejecting all of other TCP connections
└──────────────┘
│ sb_connect
┌──────────────┐
│ ACTIVE │ ← Client getting auth ticket to connect to game server
└──────────────┘
│ sb_disconnect
┌──────────────┐
│ ... │ ← Client announcing disconnect from game server, could connect somewhere again
└──────────────┘
┌──────────────┐
│ ACTIVE │ ← Client shutting down game, terminating broker to reset Steam client state
└──────────────┘
│ sb_terminate
┌──────────────┐
│ RESTARTING │
└──────────────┘
```
---
## Commands
All commands listed below are meant to be sent from client to broker as payload of a frame. The broker will parse the command string and respond accordingly.
### Session activation
Activates the session and signaling game startup.
**Format**: `sb_gamedir <gamedir>`
**Response**: None
---
### Authentication ticket request
Requests authentication ticket for a game server.
**Precondition**: Session must be active
**Format**: `sb_connect <ip:port> <server_steamid> <secure> <challenge>`
**Parameters**:
| Name | Description |
|------|------------------|
| `<ip:port>` | Game server address (e.g., `127.0.0.1:27015`) |
| `<server_steamid>` | Server SteamID |
| `<secure>` | 0 = insecure, 1 = secure |
| `<challenge>` | Random value for reply prevention |
**Response Format**:
| Field | Size | Type | Description |
|-------|------|------|-------------|
| Header | 11 bytes | string | `"sb_connect\n"` |
| Challenge | 4 bytes | int32_t (LE) | Echo of challenge from request |
| SteamID | 8 bytes | uint64_t (LE) | Client SteamID from configuration |
| Size | 4 bytes | uint32_t (LE) | Length of ticket in bytes |
| Ticket | N bytes | - | Authentication ticket data |
---
### Server disconnection announcement
Notifies broker of disconnection from game server.
**Precondition**: Session must be active
**Format**: `sb_disconnect <ip:port> <challenge>`
**Response**: None
---
### Session termination
Signaling game shutdown to broker and resetting Steam client state.
**Format**: `sb_terminate`
**Response**: None
---

View File

@@ -4,7 +4,7 @@
|---- |------- |--------------- |----------------------
|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.
|Blue Shift |The latest steam release |Uses vgui2 library which xash3d does not support. |Recreated source code here: https://github.com/FWGS/hlsdk-portable/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.
@@ -15,6 +15,6 @@
|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.
|Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-portable/tree/rebellion.
|Sven-Coop |5.0+ |Uses custom GoldSrc engine. |
|Time Shadows |Beta 0.1 |Uses Direct3D renderer. |

View File

@@ -134,7 +134,7 @@ To build you should clone [SDL](https://github.com/libsdl-org/SDL) from `SDL2` b
0) (optional) Examine which build options are available: `./waf --help`.
1) Configure build: `./waf configure --ios --enable-bundled-deps --sdl2 (path/to/SDL2.framework)`, set `--ios-simulator` instead of `--ios` if you want to build for simulator.
2) Compile `./waf build`.
3) Navigate to `build` and copy your compiled SDL2.framework there, then add your client dylibs to `build/ios/cl_dlls` and any other dylibs to `build/ios/dlls` (You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
3) Navigate to `build` and copy your compiled SDL2.framework there, then add your game dylibs to `build/ios/libs/(gamedir)/(dlls/cl_dlls)`(You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
4) Run `scripts/ios/createipa.sh` to create an installable ipa
### Running tests

3
android/.gitignore vendored
View File

@@ -11,4 +11,5 @@ local.properties
release/
*.hprof
.vscode/
*.bak
*.bak
.kotlin/

View File

@@ -20,6 +20,8 @@ extensions.configure<ApplicationExtension> {
minSdk = 21
targetSdk = 35
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
externalNativeBuild {
val engineRoot = projectDir.parentFile.parent
@@ -58,6 +60,15 @@ extensions.configure<ApplicationExtension> {
buildConfig = true
}
signingConfigs {
create("androidDebugKey") {
storeFile = File(projectDir.parentFile, "debug.keystore")
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
}
}
lint {
abortOnError = false
}
@@ -91,6 +102,7 @@ extensions.configure<ApplicationExtension> {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "false")
}
release {
@@ -99,6 +111,7 @@ extensions.configure<ApplicationExtension> {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "false")
}
register("asan") {
@@ -108,6 +121,8 @@ extensions.configure<ApplicationExtension> {
register("continuous") {
initWith(getByName("release"))
applicationIdSuffix = ".test"
buildConfigField("boolean", "ENABLE_AUTO_UPDATE", "true")
signingConfig = signingConfigs.getByName("androidDebugKey")
}
}
}

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Xash3D FWGS (Test)</string>
<string name="authority" translatable="false">su.xash.engine.test.documents</string>
</resources>

View File

@@ -48,6 +48,8 @@
<uses-permission android:name="android.permission.INTERNET" />
<!-- Dedicated server -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Self-update: install downloaded APK -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".MainApplication"
@@ -57,6 +59,7 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
<activity
@@ -68,6 +71,23 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths" />
</provider>
<receiver
android:name=".model.InstallStatusReceiver"
android:exported="false">
<intent-filter>
<action android:name="su.xash.engine.INSTALL_RESULT" />
</intent-filter>
</receiver>
<activity
android:name=".XashActivity"
android:alwaysRetainTaskState="true"
@@ -85,5 +105,9 @@
<intent>
<action android:name="su.xash.engine.MOD" />
</intent>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries>
</manifest>

View File

@@ -1,13 +1,30 @@
package su.xash.engine
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
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 com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import su.xash.engine.databinding.ActivityMainBinding
import su.xash.engine.model.AppUpdater
import su.xash.engine.util.CrashReports
import su.xash.engine.util.monospaceTextView
import su.xash.engine.util.showDownloadProgressDialog
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
@@ -27,9 +44,142 @@ class MainActivity : AppCompatActivity() {
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
CrashReports.prune(this)
showPendingCrashReport()
checkForEngineUpdate()
}
private fun checkForEngineUpdate() {
val prefs = getSharedPreferences(UPDATE_PREFS, Context.MODE_PRIVATE)
val now = System.currentTimeMillis()
if (now - prefs.getLong(KEY_LAST_CHECK, 0L) < CHECK_INTERVAL_MS)
return
val updater = AppUpdater(this)
lifecycleScope.launch {
val info = updater.checkForUpdate()
prefs.edit().putLong(KEY_LAST_CHECK, now).apply()
if (info == null)
return@launch
if (prefs.getInt(KEY_DISMISSED_BUILDNUM, -1) >= info.buildNum)
return@launch
val changelog = updater.fetchChangelog(BuildConfig.GIT_HASH, info.tagName)
showEngineUpdateDialog(updater, info.buildNum, changelog, prefs)
}
}
private fun showEngineUpdateDialog(
updater: AppUpdater,
remoteBuildNum: Int,
changelog: List<AppUpdater.CommitInfo>?,
prefs: android.content.SharedPreferences,
) {
val builder = MaterialAlertDialogBuilder(this)
.setTitle(R.string.engine_update_available)
.setMessage(getString(R.string.engine_update_message, remoteBuildNum))
.setPositiveButton(R.string.engine_update_download) { _, _ ->
showEngineDownloadDialog(updater)
}
.setNegativeButton(R.string.engine_update_later) { _, _ ->
prefs.edit().putInt(KEY_DISMISSED_BUILDNUM, remoteBuildNum).apply()
}
if (!changelog.isNullOrEmpty()) {
val text = buildString {
append(getString(R.string.engine_update_changelog_header))
val shown = changelog.take(CHANGELOG_MAX_LINES)
for (c in shown)
append("\n").append(c.subject)
val extra = changelog.size - shown.size
if (extra > 0)
append("\n").append(getString(R.string.engine_update_changelog_more, extra))
}
builder.setView(monospaceTextView(this, text))
}
builder.show()
}
private fun showEngineDownloadDialog(updater: AppUpdater) {
if (!updater.canInstall()) {
promptForInstallPermission()
return
}
showDownloadProgressDialog(
ctx = this,
titleRes = R.string.engine_update_downloading,
cancelable = true,
scope = lifecycleScope,
download = { onProgress -> updater.downloadAndInstall(onProgress) },
)
}
private fun promptForInstallPermission() {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.engine_update_permission_needed)
.setMessage(R.string.engine_update_permission_message)
.setPositiveButton(R.string.engine_update_open_settings) { _, _ ->
val packageIntent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES,
"package:$packageName".toUri())
try {
startActivity(packageIntent)
} catch (_: ActivityNotFoundException) {
try {
startActivity(Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES))
} catch (_: ActivityNotFoundException) {
// no settings screen — nothing more we can do
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
private fun showPendingCrashReport() {
val pending = CrashReports.pendingStacktrace(this)
if (!pending.exists() || pending.length() == 0L)
return
val historyDir = CrashReports.historyDir(this).apply { mkdirs() }
val ts = SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(Date())
val entryDir = File(historyDir, "crash-$ts").apply { mkdirs() }
moveOrCopy(pending, File(entryDir, CrashReports.STACKTRACE_NAME))
moveOrCopy(CrashReports.pendingSysinfo(this), File(entryDir, CrashReports.SYSINFO_NAME))
moveOrCopy(CrashReports.pendingIntent(this), File(entryDir, CrashReports.INTENT_NAME))
val entry = CrashReports.Entry(entryDir)
AlertDialog.Builder(this)
.setTitle(R.string.crash_dialog_title)
.setView(monospaceTextView(this, entry.summary()))
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(this, entry) }
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(this, entry) }
.setNegativeButton(R.string.crash_dismiss, null)
.show()
}
private fun moveOrCopy(src: File, dst: File) {
if (!src.exists())
return
if (src.renameTo(dst))
return
dst.writeText(src.readText())
src.delete()
}
companion object {
private const val CHANGELOG_MAX_LINES = 15
private const val UPDATE_PREFS = "app_updater"
private const val KEY_LAST_CHECK = "last_check_ms"
private const val KEY_DISMISSED_BUILDNUM = "dismissed_buildnum"
private const val CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000L
}
}

View File

@@ -1,6 +1,7 @@
package su.xash.engine;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.os.Build;
@@ -14,7 +15,9 @@ import android.view.WindowManager;
import org.libsdl.app.SDLActivity;
import su.xash.engine.util.AndroidBug5497Workaround;
import su.xash.engine.util.CrashReports;
import java.io.File;
import java.util.Arrays;
import java.util.List;
@@ -120,9 +123,54 @@ public class XashActivity extends SDLActivity {
return getWindow().superDispatchKeyEvent(event);
}
private static void appendStringExtra(StringBuilder sb, Intent intent, String key) {
String value = intent.getStringExtra(key);
if (value != null)
sb.append(" ").append(key).append(" = ").append(value).append('\n');
}
// record intent info, so that it could be consumed later for crash reporting
private void recordLaunchInfo() {
// do not overwrite current launch info with pending crash log, shouldn't happen but might
File pendingCrash = new File(getFilesDir(), "crashes/" + CrashReports.STACKTRACE_NAME);
if (pendingCrash.exists() && pendingCrash.length() > 0)
return;
// write Android version, fingerprint, supported abis, etc
CrashReports.writeSystemInfo(this);
// now create intent info and pass it to crash reporting
Intent intent = getIntent();
if (intent == null)
return;
StringBuilder sb = new StringBuilder();
sb.append("Action: ").append(intent.getAction()).append('\n');
sb.append("Data: ").append(intent.getDataString()).append('\n');
sb.append("Calling package: ").append(getCallingPackage()).append('\n');
sb.append("Extras:\n");
// only write intent extras that we care about
appendStringExtra(sb, intent, "gamedir");
appendStringExtra(sb, intent, "gamelibdir");
appendStringExtra(sb, intent, "pakfile");
appendStringExtra(sb, intent, "basedir");
appendStringExtra(sb, intent, "package");
appendStringExtra(sb, intent, "argv");
sb.append(" usevolume = ").append(intent.getBooleanExtra("usevolume", false)).append('\n');
String[] env = intent.getStringArrayExtra("env");
if (env != null)
sb.append(" env = ").append(Arrays.toString(env)).append('\n');
CrashReports.writeIntentInfo(this, sb.toString());
}
// TODO: REMOVE LATER, temporary launchers support?
@Override
protected String[] getArguments() {
File crashDir = new File(getFilesDir(), "crashes");
crashDir.mkdirs();
nativeSetenv("XASH3D_CRASH_DIR", crashDir.getAbsolutePath());
recordLaunchInfo();
String gamedir = getIntent().getStringExtra("gamedir");
if (gamedir == null) gamedir = "valve";
nativeSetenv("XASH3D_GAME", gamedir);
@@ -130,6 +178,14 @@ public class XashActivity extends SDLActivity {
String gamelibdir = getIntent().getStringExtra("gamelibdir");
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", gamelibdir);
String rodir = System.getenv("XASH3D_RODIR");
if (rodir == null) {
// FIXME: we are using rodir as a supplier for downloaded game libraries
rodir = getFilesDir().getAbsolutePath() + "/gamelibs";
nativeSetenv("XASH3D_RODIR", rodir);
}
Log.i(TAG, "XASH3D_RODIR = " + rodir);
String pakfile = getIntent().getStringExtra("pakfile");
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);

View File

@@ -0,0 +1,205 @@
package su.xash.engine.model
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.util.Log
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import su.xash.engine.BuildConfig
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import kotlin.coroutines.coroutineContext
class AppUpdater(private val context: Context) {
data class UpdateInfo(val buildNum: Int, val tagName: String)
data class CommitInfo(val sha: String, val subject: String)
fun canInstall(): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
context.packageManager.canRequestPackageInstalls()
suspend fun checkForUpdate(): UpdateInfo? {
if (!BuildConfig.ENABLE_AUTO_UPDATE) return null
return withContext(Dispatchers.IO) {
var connection: HttpURLConnection? = null
try {
connection = URL(RELEASE_API_URL).openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.readTimeout = 5000
connection.setRequestProperty("Accept", "application/vnd.github+json")
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
Log.w(TAG, "Release API check failed: HTTP ${connection.responseCode}")
return@withContext null
}
val release = JSONObject(connection.inputStream.bufferedReader().readText())
val body = release.optString("body", "")
val tagName = release.optString("tag_name").ifEmpty { TAG_CONTINUOUS }
// buildnum is days-since-2015-04-01, same metric as VERSION_CODE / 10000
val remote = BUILDNUM_REGEX.find(body)?.groupValues?.get(1)?.toIntOrNull()
val localDays = BuildConfig.VERSION_CODE / 10000
Log.i(TAG, "Remote buildnum: $remote (tag=$tagName), local: $localDays")
if (remote != null && remote - localDays >= STALENESS_DAYS)
UpdateInfo(remote, tagName)
else
null
} catch (e: CancellationException) {
throw e
} catch (e: IOException) {
Log.w(TAG, "Update check failed: ${e.message}")
null
} catch (e: JSONException) {
Log.w(TAG, "Update check parse failed: ${e.message}")
null
} finally {
connection?.disconnect()
}
}
}
suspend fun fetchChangelog(fromRef: String, toRef: String): List<CommitInfo>? {
if (fromRef.isEmpty() || toRef.isEmpty())
return null
return withContext(Dispatchers.IO) {
var connection: HttpURLConnection? = null
try {
connection = URL("$COMPARE_API_BASE/$fromRef...$toRef").openConnection() as HttpURLConnection
connection.connectTimeout = 5000
connection.readTimeout = 5000
connection.setRequestProperty("Accept", "application/vnd.github+json")
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
Log.w(TAG, "Compare API failed: HTTP ${connection.responseCode}")
return@withContext null
}
val json = JSONObject(connection.inputStream.bufferedReader().readText())
val commits = json.optJSONArray("commits") ?: return@withContext null
val result = ArrayList<CommitInfo>(commits.length())
for (i in 0 until commits.length()) {
val c = commits.getJSONObject(i)
val sha = c.optString("sha").ifEmpty { continue }
val msg = c.optJSONObject("commit")?.optString("message") ?: continue
val subject = msg.substringBefore('\n').trim()
if (subject.isNotEmpty())
result.add(CommitInfo(sha, subject))
}
// GitHub returns oldest first
result.reverse()
result
} catch (e: CancellationException) {
throw e
} catch (e: IOException) {
Log.w(TAG, "Changelog fetch failed: ${e.message}")
null
} catch (e: JSONException) {
Log.w(TAG, "Changelog parse failed: ${e.message}")
null
} finally {
connection?.disconnect()
}
}
}
suspend fun downloadAndInstall(onProgress: (Long, Long) -> Unit): Result<Unit> {
return withContext(Dispatchers.IO) {
val tempFile = File(context.cacheDir, "xash3d-fwgs-update.apk")
var connection: HttpURLConnection? = null
try {
connection = URL(APK_URL).openConnection() as HttpURLConnection
connection.connectTimeout = 10000
connection.readTimeout = 30000
connection.instanceFollowRedirects = true
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK)
return@withContext Result.failure(IOException("HTTP ${connection.responseCode}"))
val total = connection.contentLengthLong
var downloaded = 0L
var lastEmit = 0L
connection.inputStream.use { input ->
FileOutputStream(tempFile).use { output ->
val buffer = ByteArray(65536)
while (true) {
coroutineContext.ensureActive()
val read = input.read(buffer)
if (read < 0)
break
output.write(buffer, 0, read)
downloaded += read
val now = System.currentTimeMillis()
if (now - lastEmit >= PROGRESS_INTERVAL_MS) {
lastEmit = now
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
}
}
}
}
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
Log.i(TAG, "Downloaded APK: ${tempFile.length()} bytes -> ${tempFile.absolutePath}")
triggerInstall(tempFile)
Result.success(Unit)
} catch (e: CancellationException) {
tempFile.delete()
throw e
} catch (e: IOException) {
tempFile.delete()
Result.failure(e)
} finally {
connection?.disconnect()
}
}
}
private fun triggerInstall(apk: File) {
val installer = context.packageManager.packageInstaller
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val sessionId = installer.createSession(params)
installer.openSession(sessionId).use { session ->
session.openWrite("base.apk", 0, apk.length()).use { out ->
apk.inputStream().use { it.copyTo(out) }
session.fsync(out)
}
val statusIntent = Intent(INSTALL_ACTION).setPackage(context.packageName)
val piFlags = PendingIntent.FLAG_UPDATE_CURRENT or
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0
val pi = PendingIntent.getBroadcast(context, sessionId, statusIntent, piFlags)
session.commit(pi.intentSender)
}
}
companion object {
private const val TAG = "AppUpdater"
private const val STALENESS_DAYS = 3
private const val PROGRESS_INTERVAL_MS = 100L
private const val TAG_CONTINUOUS = "continuous"
private const val INSTALL_ACTION = "su.xash.engine.INSTALL_RESULT"
private const val APK_URL =
"https://github.com/FWGS/xash3d-fwgs/releases/download/continuous/xash3d-fwgs-android.apk"
private const val RELEASE_API_URL =
"https://api.github.com/repos/FWGS/xash3d-fwgs/releases/tags/continuous"
private const val COMPARE_API_BASE =
"https://api.github.com/repos/FWGS/xash3d-fwgs/compare"
private val BUILDNUM_REGEX = Regex("""buildnum\s+(\d+)""")
}
}

View File

@@ -8,12 +8,15 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import su.xash.engine.R
import su.xash.engine.XashActivity
import su.xash.engine.util.showDownloadProgressDialog
import java.io.File
import java.io.FileInputStream
import java.util.Arrays
import android.util.Log
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
private var iconName = "game.ico"
@@ -52,7 +55,7 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
fun startEngine(ctx: Context) {
val packageNames = getPackageNamesForGameDir(basedir.name)
var externalGame = false
var commandLineArgs = "";
var commandLineArgs = ""
if (basedir.name != defaultGameDir)
commandLineArgs += "-game ${basedir.name} "
@@ -68,17 +71,18 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
}
}
commandLineArgs += pref.getString("arguments", "-console -log")
commandLineArgs += pref.getString("arguments", "-console -log") ?: ""
var packageName: String? = null
var gameLibDir: String? = null
if (externalGame && packageNames != null) {
var packageName: String? = null
var gameLibDir: String? = null
for (pn in packageNames) {
gameLibDir = try {
getGameLibDir(ctx, pn)
} catch(e: PackageManager.NameNotFoundException) {
} catch (e: PackageManager.NameNotFoundException) {
null
} catch(e: Exception) {
} catch (e: Exception) {
e.printStackTrace()
null
}
@@ -99,12 +103,65 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
ctx.startActivity(intent)
}
show()
return
}
return
}
launchEngine(ctx, commandLineArgs, packageName = packageName, gameLibDir = gameLibDir)
return
}
if (packageNames == null) {
// Unknown game — try to use downloaded libraries from hlsdk-mega-build
val downloader = GameLibDownloader(ctx)
val args = commandLineArgs
if (downloader.isDownloaded(basedir.name)) {
downloader.logExistingLibs(basedir.name)
launchEngine(ctx, args)
return
}
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch {
when (val r = downloader.lookupBuild(basedir.name)) {
is GameLibDownloader.Lookup.Available -> showDownloadDialog(ctx, downloader, args)
is GameLibDownloader.Lookup.NotInManifest -> launchEngine(ctx, args)
is GameLibDownloader.Lookup.Error -> showManifestErrorDialog(ctx, args, r.cause)
}
}
return
}
launchEngine(ctx, commandLineArgs)
}
private fun showDownloadDialog(ctx: Context, downloader: GameLibDownloader, commandLineArgs: String) {
showDownloadProgressDialog(
ctx = ctx,
titleRes = R.string.downloading_game_libs,
cancelable = true,
scope = CoroutineScope(Dispatchers.Main + SupervisorJob()),
download = { onProgress -> downloader.download(basedir.name, onProgress) },
onSuccess = { launchEngine(ctx, commandLineArgs) },
)
}
private fun showManifestErrorDialog(ctx: Context, commandLineArgs: String, cause: Throwable) {
MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.manifest_error_title)
.setMessage(ctx.getString(R.string.manifest_error_message, cause.message ?: cause.javaClass.simpleName))
.setPositiveButton(R.string.launch_anyway) { _, _ -> launchEngine(ctx, commandLineArgs) }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun launchEngine(
ctx: Context,
commandLineArgs: String,
packageName: String? = null,
gameLibDir: String? = null
) {
ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
@@ -113,13 +170,8 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
putExtra("basedir", basedir.parent)
if (gameLibDir != null) {
putExtra("gamelibdir", gameLibDir)
}
if (packageName != null) {
putExtra("package", packageName)
}
if (gameLibDir != null) putExtra("gamelibdir", gameLibDir)
if (packageName != null) putExtra("package", packageName)
})
}
@@ -152,9 +204,6 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
return arrayOf("su.xash.engine")
// return if (mDbEntry != null) {
// mDbEntry.getPackageName()
// } else null
return null
}

View File

@@ -0,0 +1,377 @@
package su.xash.engine.model
import android.content.Context
import android.os.Build
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import java.security.MessageDigest
import java.util.zip.ZipInputStream
import kotlin.coroutines.coroutineContext
class GameLibDownloader(private val context: Context) {
private val prefs get() = context.getSharedPreferences("gamelib_metadata", Context.MODE_PRIVATE)
// converts Android's ABI to library-suffix's arch names
fun getArch(): String? {
for (abi in Build.SUPPORTED_ABIS) {
when (abi) {
"arm64-v8a" -> return "arm64"
"armeabi-v7a" -> return "armv7l"
"x86_64" -> return "amd64"
"x86" -> return "i386"
}
}
return null
}
fun getLibsBaseDir(): File = File(context.filesDir, "gamelibs")
fun isDownloaded(gamedir: String): Boolean {
val dir = File(getLibsBaseDir(), gamedir)
if (dir.isDirectory && dir.list()?.isNotEmpty() == true)
return true
// zips extract to the case used in the manifest (e.g. "CAd"); the user's
// folder name might differ in case, so do a case-insensitive lookup too.
val base = getLibsBaseDir()
val match = base.listFiles()?.firstOrNull {
it.isDirectory && it.name.equals(gamedir, ignoreCase = true)
}
return match != null && (match.list()?.isNotEmpty() == true)
}
data class ManifestEntry(
val modKey: String,
val platformArch: String,
val filename: String,
val sha256: String,
val sourceJson: String?
)
data class SourceInfo(
val url: String?,
val branch: String?,
val commit: String?
)
private fun manifestCacheFile(): File = File(context.cacheDir, "hlsdk-manifest.json")
private suspend fun fetchManifest(): JSONObject? = fetchManifestOrError().first
private suspend fun fetchManifestOrError(): Pair<JSONObject?, Throwable?> {
val cacheFile = manifestCacheFile()
var connection: HttpURLConnection? = null
try {
connection = URL(MANIFEST_URL).openConnection() as HttpURLConnection
connection.connectTimeout = 10000
connection.readTimeout = 15000
connection.instanceFollowRedirects = true
connection.connect()
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
val err = IOException("HTTP ${connection.responseCode} fetching manifest")
Log.w(TAG, "Manifest fetch failed: ${err.message}")
val cached = readCachedManifest(cacheFile)
return if (cached != null) cached to null else null to err
}
val text = connection.inputStream.bufferedReader().use { it.readText() }
val json = JSONObject(text)
val versionError = checkManifestVersion(json)
if (versionError != null) {
Log.w(TAG, "Manifest rejected: ${versionError.message}")
return null to versionError
}
cacheFile.writeText(text)
return json to null
} catch (e: Exception) {
Log.w(TAG, "Manifest fetch failed: ${e.message}")
val cached = readCachedManifest(cacheFile)
return if (cached != null) cached to null else null to e
} finally {
connection?.disconnect()
}
}
private fun readCachedManifest(cacheFile: File): JSONObject? {
if (!cacheFile.exists())
return null
val json = try {
JSONObject(cacheFile.readText())
} catch (_: Exception) {
return null
}
return if (checkManifestVersion(json) == null) json else null
}
private fun checkManifestVersion(json: JSONObject): IOException? {
if (!json.has("version"))
return IOException("Manifest missing 'version' field")
val v = json.opt("version")
if (v !is Int || v != MANIFEST_VERSION)
return IOException("Unsupported manifest version: $v (expected $MANIFEST_VERSION)")
return null
}
private fun lookupEntry(manifest: JSONObject, gamedir: String): ManifestEntry? {
val arch = getArch() ?: return null
val platformArch = "android-$arch"
val mods = manifest.optJSONObject("mods") ?: return null
// Find mod key case-insensitively
var modKey: String? = null
val keys = mods.keys()
while (keys.hasNext()) {
val k = keys.next()
if (k.equals(gamedir, ignoreCase = true)) {
modKey = k
break
}
}
if (modKey == null)
return null
val builds = mods.optJSONObject(modKey)?.optJSONObject("builds") ?: return null
val build = builds.optJSONObject(platformArch) ?: return null
val filename = build.optString("filename", "")
val sha256 = build.optString("sha256", "")
if (filename.isEmpty() || sha256.isEmpty())
return null
val sourceJson = build.optJSONObject("source")?.toString()
return ManifestEntry(modKey, platformArch, filename, sha256.lowercase(), sourceJson)
}
fun getSourceInfo(gamedir: String): SourceInfo? {
val raw = prefs.getString("${gamedir}_source", null) ?: return null
return try {
val o = JSONObject(raw)
SourceInfo(
url = o.optString("url").ifEmpty { null },
branch = o.optString("branch").ifEmpty { null },
commit = o.optString("commit").ifEmpty { null },
)
} catch (_: Exception) {
null
}
}
fun getDownloadTime(gamedir: String): Long =
prefs.getLong("${gamedir}_download_time", 0L)
sealed class Lookup {
data class Available(val entry: ManifestEntry) : Lookup()
object NotInManifest : Lookup()
data class Error(val cause: Throwable) : Lookup()
}
suspend fun lookupBuild(gamedir: String): Lookup {
return withContext(Dispatchers.IO) {
val (manifest, error) = fetchManifestOrError()
if (manifest == null)
return@withContext Lookup.Error(error ?: IOException("Failed to fetch manifest"))
val entry = lookupEntry(manifest, gamedir)
?: return@withContext Lookup.NotInManifest
Lookup.Available(entry)
}
}
suspend fun isUpdateAvailable(gamedir: String): Boolean {
return withContext(Dispatchers.IO) {
val manifest = fetchManifest() ?: return@withContext false
val entry = lookupEntry(manifest, gamedir) ?: return@withContext false
val storedSha = prefs.getString("${gamedir}_sha256", null)
storedSha == null || !storedSha.equals(entry.sha256, ignoreCase = true)
}
}
private fun urlForFilename(filename: String): String = "$RELEASE_BASE_URL/$filename"
private suspend fun tryDownload(
url: String,
dest: File,
onProgress: (Long, Long) -> Unit
): Exception? {
var connection: HttpURLConnection? = null
try {
connection = URL(url).openConnection() as HttpURLConnection
connection.connectTimeout = 10000
connection.readTimeout = 30000
connection.instanceFollowRedirects = true
connection.connect()
val code = connection.responseCode
if (code !in 200..299)
return IOException("HTTP $code: $url")
val total = connection.contentLengthLong
var downloaded = 0L
var lastEmit = 0L
connection.inputStream.use { input ->
FileOutputStream(dest).use { output ->
val buffer = ByteArray(65536)
while (true) {
coroutineContext.ensureActive()
val read = input.read(buffer)
if (read < 0)
break
output.write(buffer, 0, read)
downloaded += read
val now = System.currentTimeMillis()
if (now - lastEmit >= PROGRESS_INTERVAL_MS) {
lastEmit = now
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
}
}
}
}
withContext(Dispatchers.Main) { onProgress(downloaded, total) }
return null
} catch (e: Exception) {
return e
} finally {
connection?.disconnect()
}
}
suspend fun download(gamedir: String, onProgress: (Long, Long) -> Unit): Result<Unit> {
return withContext(Dispatchers.IO) {
val manifest = fetchManifest()
?: return@withContext Result.failure(IOException("Failed to fetch manifest"))
val entry = lookupEntry(manifest, gamedir)
?: return@withContext Result.failure(IOException("No build for '$gamedir' on ${getArch() ?: "this arch"}"))
val url = urlForFilename(entry.filename)
val tempFile = File(context.cacheDir, entry.filename)
try {
coroutineContext.ensureActive()
Log.i(TAG, "Downloading $url -> ${tempFile.absolutePath}")
val error = tryDownload(url, tempFile, onProgress)
if (error != null)
return@withContext Result.failure(error)
val actualSha = computeSHA256(tempFile)
Log.i(TAG, "Downloaded ${tempFile.length()} bytes, SHA-256: $actualSha (expected ${entry.sha256})")
if (!actualSha.equals(entry.sha256, ignoreCase = true))
return@withContext Result.failure(IOException("SHA-256 mismatch: expected ${entry.sha256}, got $actualSha"))
val destDir = getLibsBaseDir()
destDir.mkdirs()
val destCanon = destDir.canonicalPath
// Remove any prior install (possibly under a differently-cased dir) so we
// don't leave behind .so files from an older build.
destDir.listFiles()?.forEach {
if (it.isDirectory && it.name.equals(entry.modKey, ignoreCase = true))
it.deleteRecursively()
}
Log.i(TAG, "Extracting to ${destDir.absolutePath}")
val extractedFiles = mutableListOf<String>()
ZipInputStream(tempFile.inputStream()).use { zip ->
var zipEntry = zip.nextEntry
while (zipEntry != null) {
coroutineContext.ensureActive()
if (!zipEntry.isDirectory) {
val outFile = File(destDir, zipEntry.name)
if (!outFile.canonicalPath.startsWith(destCanon + File.separator))
throw SecurityException("Zip path traversal: ${zipEntry.name}")
outFile.parentFile?.mkdirs()
FileOutputStream(outFile).use { zip.copyTo(it) }
// ZipInputStream doesn't restore Unix permissions; shared libraries
// need the execute bit so dlopen can mmap them with PROT_EXEC
if (outFile.name.endsWith(".so"))
outFile.setExecutable(true, false)
extractedFiles.add(" ${outFile.absolutePath} (${outFile.length()} bytes, executable: ${outFile.canExecute()})")
}
zip.closeEntry()
zipEntry = zip.nextEntry
}
}
Log.i(TAG, "Extracted ${extractedFiles.size} files:")
extractedFiles.forEach { Log.i(TAG, it) }
prefs.edit()
.putString("${gamedir}_sha256", entry.sha256)
.putString("${gamedir}_filename", entry.filename)
.putString("${gamedir}_source", entry.sourceJson)
.putLong("${gamedir}_download_time", System.currentTimeMillis())
.apply()
Result.success(Unit)
} catch (e: Exception) {
// Best-effort: rewinding to a clean state on failure
File(getLibsBaseDir(), entry.modKey).deleteRecursively()
Result.failure(e)
} finally {
tempFile.delete()
}
}
}
fun logExistingLibs(gamedir: String) {
val gameDir = File(getLibsBaseDir(), gamedir)
val resolved = if (gameDir.exists()) {
gameDir
} else {
getLibsBaseDir().listFiles()?.firstOrNull {
it.isDirectory && it.name.equals(gamedir, ignoreCase = true)
}
}
if (resolved == null) {
Log.i(TAG, "Game libs dir does not exist for $gamedir")
return
}
Log.i(TAG, "Existing game libs under ${resolved.absolutePath}:")
resolved.walkTopDown().filter { it.isFile }.forEach {
Log.i(TAG, " ${it.absolutePath} (${it.length()} bytes)")
}
val storedSha = prefs.getString("${gamedir}_sha256", null)
val downloadTime = prefs.getLong("${gamedir}_download_time", 0L)
Log.i(TAG, "Archive SHA-256: $storedSha")
if (downloadTime > 0L)
Log.i(TAG, "Downloaded at: ${java.util.Date(downloadTime)}")
}
private fun computeSHA256(file: File): String {
val digest = MessageDigest.getInstance("SHA-256")
file.inputStream().use { input ->
val buffer = ByteArray(65536)
while (true) {
val read = input.read(buffer)
if (read < 0)
break
digest.update(buffer, 0, read)
}
}
return digest.digest().joinToString("") { "%02x".format(it) }
}
companion object {
private const val TAG = "GameLibDownloader"
private const val PROGRESS_INTERVAL_MS = 100L
private const val RELEASE_BASE_URL =
"https://github.com/FWGS/hlsdk-mega-build/releases/download/continuous"
private const val MANIFEST_URL =
"$RELEASE_BASE_URL/manifest.json"
private const val MANIFEST_VERSION = 1
}
}

View File

@@ -0,0 +1,42 @@
package su.xash.engine.model
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.util.Log
import android.widget.Toast
import su.xash.engine.R
class InstallStatusReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val confirm: Intent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(Intent.EXTRA_INTENT)
}
confirm?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
confirm?.let { context.startActivity(it) }
}
PackageInstaller.STATUS_SUCCESS -> Log.i(TAG, "Install succeeded")
else -> {
val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE).orEmpty()
Log.w(TAG, "Install status $status: $msg")
Toast.makeText(
context,
context.getString(R.string.engine_install_failed, msg),
Toast.LENGTH_LONG,
).show()
}
}
}
companion object {
private const val TAG = "InstallStatus"
}
}

View File

@@ -1,6 +1,8 @@
package su.xash.engine.ui.settings
import android.os.Bundle
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import su.xash.engine.R
@@ -8,5 +10,10 @@ class AppSettingsPreferenceFragment() : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = "app_preferences";
setPreferencesFromResource(R.xml.app_preferences, rootKey);
findPreference<Preference>("crash_logs")?.setOnPreferenceClickListener {
findNavController().navigate(R.id.action_appSettingsFragment_to_crashLogsFragment)
true
}
}
}

View File

@@ -0,0 +1,68 @@
package su.xash.engine.ui.settings
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import su.xash.engine.R
import su.xash.engine.util.CrashReports
import su.xash.engine.util.monospaceTextView
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class CrashLogsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceScreen = preferenceManager.createPreferenceScreen(requireContext())
populate()
}
override fun onResume() {
super.onResume()
populate()
}
private fun populate() {
val ctx = requireContext()
preferenceScreen.removeAll()
val dirs = CrashReports.historyDir(ctx).listFiles()
?.filter { it.isDirectory }
?.sortedByDescending { it.lastModified() }
?: emptyList()
if (dirs.isEmpty()) {
preferenceScreen.addPreference(Preference(ctx).apply {
setTitle(R.string.crash_logs_empty)
isSelectable = false
})
return
}
val fmt = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
dirs.forEach { dir ->
preferenceScreen.addPreference(Preference(ctx).apply {
title = fmt.format(Date(dir.lastModified()))
summary = dir.name
setOnPreferenceClickListener {
showCrashLog(CrashReports.Entry(dir))
true
}
})
}
}
private fun showCrashLog(entry: CrashReports.Entry) {
val ctx = requireContext()
AlertDialog.Builder(ctx)
.setTitle(entry.name)
.setView(monospaceTextView(ctx, entry.summary()))
.setPositiveButton(R.string.crash_send_to_developers) { _, _ -> CrashReports.sendByEmail(ctx, entry) }
.setNeutralButton(R.string.crash_share) { _, _ -> CrashReports.share(ctx, entry) }
.setNegativeButton(R.string.crash_log_delete) { _, _ ->
entry.dir.deleteRecursively()
populate()
}
.show()
}
}

View File

@@ -1,11 +1,17 @@
package su.xash.engine.ui.settings
import android.content.Intent
import android.os.Bundle
import androidx.core.net.toUri
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import su.xash.engine.R
import su.xash.engine.model.Game
import su.xash.engine.model.GameLibDownloader
import java.text.DateFormat
import java.util.Date
class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -26,6 +32,8 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
enableYaPBBots.isVisible = true
}
populateDownloadedBuildInfo()
val separatePackages = findPreference<SwitchPreferenceCompat>("separate_libraries")!!
val clientPackage = findPreference<ListPreference>("client_package")!!
val serverPackage = findPreference<ListPreference>("server_package")!!
@@ -43,4 +51,43 @@ class GameSettingsPreferenceFragment(val game: Game) : PreferenceFragmentCompat(
true
}
}
private fun populateDownloadedBuildInfo() {
val downloader = GameLibDownloader(requireContext())
val source = downloader.getSourceInfo(game.basedir.name) ?: return
val downloadedAt = downloader.getDownloadTime(game.basedir.name)
val urlPref = findPreference<Preference>("source_url")!!
urlPref.isVisible = true
urlPref.summary = source.url ?: ""
urlPref.isEnabled = source.url != null
urlPref.setOnPreferenceClickListener {
source.url?.let {
startActivity(Intent(Intent.ACTION_VIEW, it.toUri()))
}
true
}
val branchPref = findPreference<Preference>("source_branch")!!
branchPref.isVisible = true
branchPref.summary = source.branch ?: ""
val commitPref = findPreference<Preference>("source_commit")!!
commitPref.isVisible = true
commitPref.summary = source.commit ?: ""
commitPref.isEnabled = source.commit != null && source.url != null
commitPref.setOnPreferenceClickListener {
// FIXME: GitHub-styled URL!
val target = "${source.url!!.trimEnd('/')}/commit/${source.commit}"
startActivity(Intent(Intent.ACTION_VIEW, target.toUri()))
true
}
val timePref = findPreference<Preference>("downloaded_at")!!
timePref.isVisible = true
timePref.summary = if (downloadedAt > 0L)
DateFormat.getDateTimeInstance().format(Date(downloadedAt))
else
""
}
}

View File

@@ -0,0 +1,172 @@
package su.xash.engine.util
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
import su.xash.engine.BuildConfig
import su.xash.engine.R
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
object CrashReports {
private const val PREFS = "crash_reports"
private const val KEY_LAST_VERSION = "last_version_code"
private const val MAX_AGE_MS = 30L * 24L * 60L * 60L * 1000L // 30 days
private const val D = "9c8d9e8c97bf9988988cd1989e86"
const val STACKTRACE_NAME = "crash.log"
const val SYSINFO_NAME = "sysinfo.txt"
const val INTENT_NAME = "intent.txt"
class Entry(val dir: File) {
val name: String get() = dir.name
val timestamp: Long get() = dir.lastModified()
val stacktrace: File get() = File(dir, STACKTRACE_NAME)
val sysinfo: File get() = File(dir, SYSINFO_NAME)
val intent: File get() = File(dir, INTENT_NAME)
fun attachments(): List<File> = listOf(stacktrace, sysinfo, intent).filter { it.exists() && it.length() > 0 }
fun summary(): String = buildString {
if (stacktrace.exists())
append(stacktrace.readText())
if (sysinfo.exists()) {
append("\n--- System info ---\n").append(sysinfo.readText())
}
if (intent.exists()) {
append("\n--- XashActivity intent ---\n").append(intent.readText())
}
}
}
fun pendingDir(ctx: Context): File = File(ctx.filesDir, "crashes")
fun pendingStacktrace(ctx: Context): File = File(pendingDir(ctx), STACKTRACE_NAME)
fun pendingSysinfo(ctx: Context): File = File(pendingDir(ctx), SYSINFO_NAME)
fun pendingIntent(ctx: Context): File = File(pendingDir(ctx), INTENT_NAME)
fun historyDir(ctx: Context): File = File(ctx.filesDir, "crashes/history")
// wipe everything on app update; otherwise drop logs older than 30 days
fun prune(ctx: Context) {
val prefs = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
val lastVersion = prefs.getInt(KEY_LAST_VERSION, -1)
val currentVersion = BuildConfig.VERSION_CODE
if (lastVersion != currentVersion) {
historyDir(ctx).deleteRecursively()
pendingStacktrace(ctx).delete()
pendingSysinfo(ctx).delete()
pendingIntent(ctx).delete()
prefs.edit().putInt(KEY_LAST_VERSION, currentVersion).apply()
return
}
val cutoff = System.currentTimeMillis() - MAX_AGE_MS
historyDir(ctx).listFiles()?.forEach { entry ->
if (entry.lastModified() < cutoff)
entry.deleteRecursively()
}
}
@JvmStatic
fun writeSystemInfo(ctx: Context) {
val text = buildString {
append("App version: ").append(BuildConfig.VERSION_NAME).append(" (code ").append(BuildConfig.VERSION_CODE).append(")\n")
append("Application ID: ").append(BuildConfig.APPLICATION_ID).append('\n')
append("Android: ").append(Build.VERSION.RELEASE).append(" (SDK ").append(Build.VERSION.SDK_INT).append(")\n")
append("Manufacturer: ").append(Build.MANUFACTURER).append('\n')
append("Brand: ").append(Build.BRAND).append('\n')
append("Model: ").append(Build.MODEL).append('\n')
append("Device: ").append(Build.DEVICE).append('\n')
append("Product: ").append(Build.PRODUCT).append('\n')
append("Hardware: ").append(Build.HARDWARE).append('\n')
append("Fingerprint: ").append(Build.FINGERPRINT).append('\n')
append("Supported ABIs: ").append(Build.SUPPORTED_ABIS.joinToString(", ")).append('\n')
}
runCatching {
pendingDir(ctx).mkdirs()
pendingSysinfo(ctx).writeText(text)
}
}
@JvmStatic
fun writeIntentInfo(ctx: Context, text: String) {
runCatching {
pendingDir(ctx).mkdirs()
pendingIntent(ctx).writeText(text)
}
}
private fun zipUri(ctx: Context, entry: Entry): Uri {
val zipDir = File(ctx.cacheDir, "crashes").apply { mkdirs() }
val zip = File(zipDir, "${entry.name}.zip")
ZipOutputStream(BufferedOutputStream(FileOutputStream(zip))).use { zos ->
entry.attachments().forEach { f ->
zos.putNextEntry(ZipEntry(f.name))
f.inputStream().use { it.copyTo(zos) }
zos.closeEntry()
}
}
val authority = "${BuildConfig.APPLICATION_ID}.fileprovider"
return FileProvider.getUriForFile(ctx, authority, zip)
}
fun sendByEmail(ctx: Context, entry: Entry) {
val addr = D.chunked(2) { (it.toString().toInt(16) xor 0xFF).toChar() }.joinToString("")
val mailtoProbe = Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", addr, null))
val mailApps = ctx.packageManager.queryIntentActivities(mailtoProbe, 0)
if (mailApps.isEmpty()) {
AlertDialog.Builder(ctx)
.setTitle(R.string.crash_no_mail_app_title)
.setMessage(R.string.crash_no_mail_app_message)
.setPositiveButton(android.R.string.ok, null)
.show()
return
}
val uri = zipUri(ctx, entry)
val baseSend = Intent(Intent.ACTION_SEND).apply {
type = "application/zip"
putExtra(Intent.EXTRA_EMAIL, arrayOf(addr))
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
putExtra(Intent.EXTRA_TEXT, ctx.getString(R.string.crash_email_body))
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
val targeted = mailApps.map { ri ->
Intent(baseSend).apply { setPackage(ri.activityInfo.packageName) }
}
if (targeted.size == 1) {
ctx.startActivity(targeted[0])
return
}
// Multiple mail apps — show a chooser limited to them, no other share targets
val chooser = Intent.createChooser(targeted[0], ctx.getString(R.string.crash_send_to_developers))
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targeted.drop(1).toTypedArray())
ctx.startActivity(chooser)
}
fun share(ctx: Context, entry: Entry) {
val uri = zipUri(ctx, entry)
val intent = Intent(Intent.ACTION_SEND).apply {
type = "application/zip"
putExtra(Intent.EXTRA_SUBJECT, ctx.getString(R.string.crash_email_subject))
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
ctx.startActivity(Intent.createChooser(intent, ctx.getString(R.string.crash_share)))
}
}

View File

@@ -0,0 +1,69 @@
package su.xash.engine.util
import android.content.Context
import android.text.format.Formatter
import android.view.LayoutInflater
import android.widget.TextView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import su.xash.engine.R
fun showDownloadProgressDialog(
ctx: Context,
titleRes: Int,
cancelable: Boolean,
scope: CoroutineScope,
download: suspend ((Long, Long) -> Unit) -> Result<Unit>,
onSuccess: (() -> Unit)? = null,
) {
val view = LayoutInflater.from(ctx).inflate(R.layout.dialog_download_progress, null)
val progressBar = view.findViewById<LinearProgressIndicator>(R.id.downloadProgress)
val statusText = view.findViewById<TextView>(R.id.downloadStatus)
val dialog = MaterialAlertDialogBuilder(ctx)
.setTitle(titleRes)
.setView(view)
.setCancelable(cancelable)
.apply {
if (cancelable)
setNegativeButton(android.R.string.cancel) { d, _ -> d.dismiss() }
}
.create()
dialog.show()
val job = scope.launch {
val result = download { downloaded, total ->
val downloadedStr = Formatter.formatShortFileSize(ctx, downloaded)
if (total > 0) {
progressBar.isIndeterminate = false
progressBar.progress = (downloaded * 100 / total).toInt()
val totalStr = Formatter.formatShortFileSize(ctx, total)
statusText.text = ctx.getString(R.string.download_progress, downloadedStr, totalStr)
} else {
statusText.text = ctx.getString(R.string.download_progress_unknown, downloadedStr)
}
}
if (!dialog.isShowing)
return@launch
dialog.dismiss()
if (result.isSuccess) {
onSuccess?.invoke()
} else {
MaterialAlertDialogBuilder(ctx)
.setTitle(R.string.download_failed)
.setMessage(result.exceptionOrNull()?.message
?: ctx.getString(R.string.download_error))
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
if (cancelable)
dialog.setOnDismissListener { job.cancel() }
}

View File

@@ -0,0 +1,19 @@
package su.xash.engine.util
import android.content.Context
import android.graphics.Typeface
import android.util.TypedValue
import android.view.View
import android.widget.ScrollView
import android.widget.TextView
fun monospaceTextView(ctx: Context, content: String): View {
val pad = (16 * ctx.resources.displayMetrics.density).toInt()
val text = TextView(ctx).apply {
text = content
typeface = Typeface.MONOSPACE
setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f)
setPadding(pad, pad, pad, pad)
}
return ScrollView(ctx).apply { addView(text) }
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingBottom="8dp">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/downloadStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/downloading" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/downloadProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:indeterminate="true" />
</LinearLayout>

View File

@@ -16,8 +16,9 @@
<androidx.fragment.app.FragmentContainerView
android:id="@+id/settingsFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/gameCard"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout="@android:layout/list_content">
</androidx.fragment.app.FragmentContainerView>

View File

@@ -22,5 +22,13 @@
<fragment
android:id="@+id/appSettingsFragment"
android:name="su.xash.engine.ui.settings.AppSettingsFragment"
android:label="@string/app_settings" />
android:label="@string/app_settings">
<action
android:id="@+id/action_appSettingsFragment_to_crashLogsFragment"
app:destination="@id/crashLogsFragment" />
</fragment>
<fragment
android:id="@+id/crashLogsFragment"
android:name="su.xash.engine.ui.settings.CrashLogsFragment"
android:label="@string/crash_logs" />
</navigation>

View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

View File

@@ -23,4 +23,42 @@
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
<string name="game_apk_install">Скачать</string>
<string name="crash_dialog_title">Xash3D FWGS аварийно завершился в прошлый раз</string>
<string name="crash_send_to_developers">Отправить разработчикам</string>
<string name="crash_share">Поделиться</string>
<string name="crash_dismiss">Закрыть</string>
<string name="crash_logs">Журналы сбоев</string>
<string name="crash_logs_empty">Нет сохранённых журналов сбоев</string>
<string name="crash_log_delete">Удалить</string>
<string name="crash_email_body">К письму прикреплены подробности сбоя. Опишите, пожалуйста, как можно подробнее, что вы делали, когда игра вылетела — любая мелочь поможет нам исправить ошибку!</string>
<string name="crash_no_mail_app_title">Почтовое приложение не найдено</string>
<string name="crash_no_mail_app_message">На этом устройстве нет почтового клиента. Установите его (или воспользуйтесь кнопкой «Поделиться», чтобы отправить отчёт другим способом) и попробуйте снова.</string>
<string name="downloading_game_libs">Загрузка игровых библиотек</string>
<string name="downloading">Загрузка…</string>
<string name="download_progress">Загрузка… %1$s / %2$s</string>
<string name="download_progress_unknown">Загрузка… %1$s</string>
<string name="download_failed">Ошибка загрузки</string>
<string name="download_error">Произошла ошибка при загрузке игровых библиотек.</string>
<string name="update_available">Доступно обновление</string>
<string name="update_available_message">Доступна более новая версия игровых библиотек. Загрузить обновление?</string>
<string name="update_download">Обновить</string>
<string name="update_skip">Пропустить</string>
<string name="manifest_error_title">Не удалось связаться с сервером игровых библиотек</string>
<string name="manifest_error_message">Не удалось загрузить манифест сборок:\n\n%1$s\n\nИгровые библиотеки сейчас недоступны для загрузки. Можно запустить движок и без них, но он, скорее всего, не найдёт игровые DLL.</string>
<string name="launch_anyway">Запустить всё равно</string>
<string name="source_repo">Репозиторий исходников</string>
<string name="source_branch">Ветка</string>
<string name="source_commit">Коммит</string>
<string name="downloaded_at">Загружено</string>
<string name="engine_update_available">Доступно обновление движка</string>
<string name="engine_update_message">Доступна сборка %1$d. Обновить?</string>
<string name="engine_update_changelog_header">Изменения с вашей сборки:</string>
<string name="engine_update_changelog_more">+ ещё %1$d коммитов</string>
<string name="engine_update_download">Обновить</string>
<string name="engine_update_later">Позже</string>
<string name="engine_update_downloading">Загрузка обновления</string>
<string name="engine_update_permission_needed">Нужно разрешение</string>
<string name="engine_update_permission_message">Чтобы установить обновление, разрешите Xash3D FWGS устанавливать неизвестные приложения в настройках системы.</string>
<string name="engine_update_open_settings">Открыть настройки</string>
<string name="engine_install_failed">Не удалось установить обновление: %1$s</string>
</resources>

View File

@@ -24,4 +24,43 @@
<string name="game_apk_required">Game code APK required</string>
<string name="game_apk_message">To launch this game, you need additional APK installed</string>
<string name="game_apk_install">Download</string>
<string name="crash_dialog_title">Xash3D FWGS crashed last time</string>
<string name="crash_send_to_developers">Send to developers</string>
<string name="crash_share">Share</string>
<string name="crash_dismiss">Dismiss</string>
<string name="crash_logs">Crash logs</string>
<string name="crash_logs_empty">No crash logs recorded</string>
<string name="crash_log_delete">Delete</string>
<string name="crash_email_subject" translatable="false">Xash3D FWGS crash report</string>
<string name="crash_email_body">Crash details are attached. Please describe in as much detail as possible what you were doing when the game crashed — every little thing helps us fix it!</string>
<string name="crash_no_mail_app_title">No mail app found</string>
<string name="crash_no_mail_app_message">No email client is installed on this device. Install one (or use Share to send the report another way) and try again.</string>
<string name="downloading_game_libs">Downloading Game Libraries</string>
<string name="downloading">Downloading…</string>
<string name="download_progress">Downloading… %1$s / %2$s</string>
<string name="download_progress_unknown">Downloading… %1$s</string>
<string name="download_failed">Download Failed</string>
<string name="download_error">An error occurred while downloading game libraries.</string>
<string name="update_available">Update Available</string>
<string name="update_available_message">A newer version of the game libraries is available. Download the update?</string>
<string name="update_download">Update</string>
<string name="update_skip">Skip</string>
<string name="manifest_error_title">Cannot reach game library server</string>
<string name="manifest_error_message">Failed to download the build manifest:\n\n%1$s\n\nGame libraries cannot be downloaded right now. You can launch the engine anyway, but it likely won\'t find the game DLLs.</string>
<string name="launch_anyway">Launch anyway</string>
<string name="source_repo">Source repository</string>
<string name="source_branch">Branch</string>
<string name="source_commit">Commit</string>
<string name="downloaded_at">Downloaded</string>
<string name="engine_update_available">Engine Update Available</string>
<string name="engine_update_message">Build %1$d is available. Update now?</string>
<string name="engine_update_changelog_header">Changes since your build:</string>
<string name="engine_update_changelog_more">+ %1$d more commits</string>
<string name="engine_update_download">Update</string>
<string name="engine_update_later">Later</string>
<string name="engine_update_downloading">Downloading Update</string>
<string name="engine_update_permission_needed">Permission Required</string>
<string name="engine_update_permission_message">To install the update, allow Xash3D FWGS to install unknown apps in system settings.</string>
<string name="engine_update_open_settings">Open settings</string>
<string name="engine_install_failed">Update install failed: %1$s</string>
</resources>

View File

@@ -11,4 +11,8 @@
app:key="use_icons"
app:layout="@layout/switch_preference"
app:title="@string/preferences_use_icons" />
<Preference
app:key="crash_logs"
app:title="@string/crash_logs" />
</PreferenceScreen>

View File

@@ -46,4 +46,28 @@
app:title="@string/preferences_server_package"
app:useSimpleSummaryProvider="true"
app:isPreferenceVisible="false" />
<Preference
app:key="source_url"
app:layout="@layout/edit_text_preference"
app:title="@string/source_repo"
app:isPreferenceVisible="false" />
<Preference
app:key="source_branch"
app:layout="@layout/edit_text_preference"
app:title="@string/source_branch"
app:isPreferenceVisible="false" />
<Preference
app:key="source_commit"
app:layout="@layout/edit_text_preference"
app:title="@string/source_commit"
app:isPreferenceVisible="false" />
<Preference
app:key="downloaded_at"
app:layout="@layout/edit_text_preference"
app:title="@string/downloaded_at"
app:isPreferenceVisible="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="system"/>
<certificates src="@raw/isrgrootx1"/>
</trust-anchors>
</base-config>
</network-security-config>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="crash_zips" path="crashes/" />
</paths>

View File

@@ -87,6 +87,7 @@ typedef enum
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)
IL_ALLOW_WAD3_LUMA = BIT(9), // allow usage of luma textures in wad3 (tilde textures)
IL_HOST_ENDIAN = BIT(10), // buffer is already in host endian, skip byte-swapping
} ilFlags_t;
// goes into rgbdata_t->encode
@@ -116,6 +117,8 @@ typedef enum
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_PREMULTIPLIED = BIT(11),
IMAGE_PLAYERDECAL = BIT(12), // loaded with IL_LOAD_PLAYER_DECAL (custom player spray)
// Image_Process manipulation flags
IMAGE_FLIP_X = BIT(16), // flip the image by width

View File

@@ -22,6 +22,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef QUAKE_CLIENT_H
#define QUAKE_CLIENT_H
#include <stdint.h>
#include "xash3d_types.h"
// Definitions originally taken from Quake, modified for binary compatibility
typedef struct usercmd_s

View File

@@ -101,7 +101,7 @@ typedef enum
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
TF_PREMULTIPLIED = (1<<23), // RGBA was stored premultiplied (HL gradient / indexalpha)
// 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

View File

@@ -159,11 +159,22 @@ typedef struct snd_globals_s
qboolean have_ambient_sfx;
} snd_globals_t;
typedef struct voice_audio_info_s
{
uint width;
uint samplerate;
uint frame_size; // in samples
} voice_audio_info_t;
// API from engine to client (client calls these)
typedef struct sound_api_s
{
qboolean (*CL_GetEntitySpatialization)( channel_t *ch );
sfx_t* (*S_GetSfxByHandle)( sound_t handle );
qboolean (*CL_GetEntitySpatialization)( channel_t *ch );
sfx_t* (*S_GetSfxByHandle)( sound_t handle );
// Voice extensions
void (*pfnS_RawEntSamples)( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn );
void (*pfnSND_ForceInitMouth)( int entnum );
voice_audio_info_t (*pfnVoice_GetAudioInfo)( void );
} sound_api_t;
// Callbacks from client to engine (engine calls these when custom sound is active)

View File

@@ -114,6 +114,7 @@ typedef int qboolean;
#define FORMAT_CHECK( x ) __attribute__(( format( printf, x, x + 1 )))
#define ALLOC_CHECK( x ) __attribute__(( alloc_size( x )))
#define WARN_UNUSED_RESULT __attribute__(( warn_unused_result ))
#define MAYBE_UNUSED __attribute__(( unused ))
#define RENAME_SYMBOL( x ) asm( x )
#if !defined( offsetof )
#define offsetof( s, m ) __builtin_offsetof( s, m )
@@ -198,6 +199,10 @@ typedef int qboolean;
#define WARN_UNUSED_RESULT
#endif // !defined( WARN_UNUSED_RESULT )
#if !defined( MAYBE_UNUSED )
#define MAYBE_UNUSED
#endif // !defined( MAYBE_UNUSED )
#if !defined( RENAME_SYMBOL )
#define RENAME_SYMBOL( x )
#endif // !defined( RENAME_SYMBOL )
@@ -243,7 +248,10 @@ typedef int qboolean;
#define Swap32Store( x ) ( x = Swap32( x ))
#define Swap16Store( x ) ( x = Swap16( x ))
#ifdef XASH_BIG_ENDIAN
#define LittleFourCC( a, b, c, d ) (((uint32_t)( d ) << 24 ) | ((uint32_t)( c ) << 16 ) | ((uint32_t)( b ) << 8 ) | (uint32_t)( a ))
#define BigFourCC( a, b, c, d ) (((uint32_t)( a ) << 24 ) | ((uint32_t)( b ) << 16 ) | ((uint32_t)( c ) << 8 ) | (uint32_t)( d ))
#if XASH_BIG_ENDIAN
#define LittleLong( x ) Swap32( x )
#define LittleShort( x ) Swap16( x )
#define LittleLongSW( x ) Swap32Store( x )
@@ -252,6 +260,7 @@ typedef int qboolean;
#define BigLong( x ) ( x )
#define BigShort( x ) ( x )
#define BigFloat( x ) ( x )
#define HostFourCC( a, b, c, d ) BigFourCC( a, b, c, d )
#else
#define LittleLong( x ) ( x )
#define LittleShort( x ) ( x )
@@ -261,6 +270,7 @@ typedef int qboolean;
#define BigLong( x ) Swap32( x )
#define BigShort( x ) Swap16( x )
#define BigFloat( x ) SwapFloat( x )
#define HostFourCC( a, b, c, d ) LittleFourCC( a, b, c, d )
#endif
#endif // XASH_TYPES_H

View File

@@ -82,6 +82,7 @@ typedef struct cldll_func_s
void (*pfnLookEvent)( float relyaw, float relpitch );
// Sound API
int (*pfnGetSoundInterface)( int version, const sound_api_t *api, sound_interface_t *callback );
int (*pfnVoice_StartChannel)( int samples, byte *data, int entnum );
} cldll_func_t;
#endif//CDLL_EXP_H

View File

@@ -195,7 +195,6 @@ static int AVI_OpenCodecContext( AVCodecContext **dst_dec_ctx, AVFormatContext *
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 );
@@ -251,8 +250,7 @@ byte *AVI_GetVideoFrame( movie_state_t *Avi, int target )
static void AVI_StreamAudio( movie_state_t *Avi )
{
int buffer_samples, file_samples, file_bytes;
rawchan_t *ch = NULL;
rawchan_t *ch;
// 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 ? !snd.streaming : false;
@@ -273,11 +271,11 @@ static void AVI_StreamAudio( movie_state_t *Avi )
while( ch->s_rawend < snd.soundtime + ch->max_samples )
{
int buffer_samples = ch->max_samples - (ch->s_rawend - snd.soundtime);
int file_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED);
int file_bytes;
size_t copy;
buffer_samples = ch->max_samples - (ch->s_rawend - snd.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;
@@ -308,8 +306,6 @@ 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 )
@@ -337,8 +333,8 @@ static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame )
}
}
ptr = Avi->cached_audio + Avi->cached_audio_len;
outsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples );
uint8_t *ptr = Avi->cached_audio + Avi->cached_audio_len;
int 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 );
@@ -444,13 +440,16 @@ qboolean AVI_Think( movie_state_t *Avi )
if( Avi->texture == 0 )
{
int cinTexture = SCR_GetCinematicTexture();
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 );
if( redraw )
ref.dllFuncs.GL_UpdateTexture( cinTexture, Avi->xres, Avi->yres, Avi->xres, Avi->yres, Avi->dst, PF_BGRA_32 );
ref.dllFuncs.R_DrawStretchPic( Avi->x, Avi->y, w, h, 0, 0, 1, 1, cinTexture );
}
else if( redraw && Avi->texture > 0 )
ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst );
ref.dllFuncs.GL_UpdateTexture( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst, PF_BGRA_32 );
if( flushing && !decoded )
return false; // probably hit an EOF
@@ -460,8 +459,6 @@ qboolean AVI_Think( movie_state_t *Avi )
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 )
@@ -526,6 +523,8 @@ void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audi
return;
}
byte *dst[4];
int dst_linesize[4];
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 );

View File

@@ -61,16 +61,14 @@ 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 );
const char *command = Cmd_Argv( 1 );
const char *pszTrack = Cmd_Argv( 2 );
if( !enabled && Q_stricmp( command, "on" ))
return; // CD-player is disabled
@@ -238,9 +236,7 @@ static scrshot_t CL_GetScreenshotTypeFromString( const char *string )
void CL_GenericShot_f( void )
{
const char *argv0 = Cmd_Argv( 0 );
scrshot_t type;
type = CL_GetScreenshotTypeFromString( argv0 );
scrshot_t type = CL_GetScreenshotTypeFromString( argv0 );
if( type == scrshot_normal || type == scrshot_snapshot )
{
@@ -272,7 +268,6 @@ void CL_GenericShot_f( void )
case scrshot_snapshot:
{
string checkname;
int i;
// allow overriding screenshot by users request
if( Cmd_Argc() > 1 )
@@ -284,7 +279,7 @@ void CL_GenericShot_f( void )
if( type == scrshot_snapshot )
FS_AllowDirectPaths( true );
for( i = 0; i < 9999; i++ )
for( int i = 0; i < 9999; i++ )
{
int ret;
@@ -383,17 +378,14 @@ CL_WavePlayLen_f
*/
void CL_WavePlayLen_f( void )
{
const char *name;
uint msecs;
if( Cmd_Argc() != 2 )
{
Con_Printf( "waveplaylen <wave file name>: returns approximate number of milliseconds a wave file will take to play.\n" );
return;
}
name = Cmd_Argv( 1 );
msecs = Sound_GetApproxWavePlayLen( name );
const char *name = Cmd_Argv( 1 );
uint msecs = Sound_GetApproxWavePlayLen( name );
if( msecs == 0 )
{

View File

@@ -130,9 +130,7 @@ void CL_MoveToOnHandList( resource_t *pResource )
static void CL_ClearResourceList( resource_t *pList )
{
resource_t *p, *n;
for( p = pList->pNext; p != pList && p; p = n )
for( resource_t *p = pList->pNext, *n; p != pList && p; p = n )
{
n = p->pNext;

View File

@@ -71,9 +71,7 @@ const char *CL_MsgInfo( int cmd )
}
else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES ))
{
int i;
for( i = 0; i < MAX_USER_MESSAGES; i++ )
for( int i = 0; i < MAX_USER_MESSAGES; i++ )
{
if( clgame.msg[i].number == cmd )
{
@@ -106,11 +104,9 @@ 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 );
int 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;
@@ -141,9 +137,7 @@ 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 );
file_t *fp = FS_Open( buffer_file, "wb", false );
if( !fp )
{
Con_Printf( S_ERROR "%s: can't open %s for write\n", __func__, buffer_file );
@@ -170,7 +164,7 @@ void CL_WriteMessageHistory( void )
{
oldcmd_t *old;
sizebuf_t *msg = &net_message;
int i, thecmd;
int thecmd;
if( !cls.initialized || cls.state == ca_disconnected )
return;
@@ -184,7 +178,7 @@ void CL_WriteMessageHistory( void )
thecmd = cls_message_debug.currentcmd - 1;
thecmd -= ( MSG_COUNT - 1 ); // back up to here
for( i = 0; i < MSG_COUNT - 1; i++ )
for( int i = 0; i < MSG_COUNT - 1; i++ )
{
thecmd &= MSG_MASK;
old = &cls_message_debug.oldcmd[thecmd];

View File

@@ -245,8 +245,6 @@ 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;
@@ -254,7 +252,7 @@ static void CL_WriteDemoCmdHeader( byte cmd, file_t *file )
FS_Write( file, &cmd, sizeof( byte ));
// time offset
dt = (float)(CL_GetDemoRecordClock() - demo.starttime);
float dt = (float)(CL_GetDemoRecordClock() - demo.starttime);
FS_Write( file, &dt, sizeof( float ));
}
@@ -395,11 +393,6 @@ 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;
@@ -413,7 +406,7 @@ static void CL_WriteDemoHeader( const char *name )
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;
double maxfps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT;
memset( &demo.header, 0, sizeof( demo.header ));
@@ -442,6 +435,7 @@ static void CL_WriteDemoHeader( const char *name )
FS_Flush( cls.demoheader );
// now copy the stuff we cached from the server.
int copysize, savepos;
copysize = savepos = FS_Tell( cls.demoheader );
FS_Seek( cls.demoheader, 0, SEEK_SET );
@@ -458,7 +452,7 @@ static void CL_WriteDemoHeader( const char *name )
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 );
int curpos = FS_Tell( cls.demofile );
demo.entry->length = curpos - demo.entry->offset;
// now we are writing the first real lump.
@@ -487,19 +481,15 @@ 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();
float stoptime = CL_GetDemoRecordClock();
if( clgame.hInstance ) clgame.dllFuncs.pfnReset();
curpos = FS_Tell( cls.demofile );
int 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;
@@ -507,7 +497,7 @@ static void CL_StopRecord( void )
// 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++ )
for( int i = 0; i < demo.directory.numentries; i++ )
FS_Write( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t ));
Mem_Free( demo.directory.entries );
@@ -525,7 +515,7 @@ static void CL_StopRecord( void )
gameui.globals->demoname[0] = '\0';
demo.header.host_fps = 0.0;
frames = cls.td_lastframe - cls.td_startframe;
int 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;
}
@@ -539,13 +529,12 @@ 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 );
int 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 ));
@@ -847,7 +836,6 @@ static qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length )
{
vec3_t viewangles;
int msglen = 0;
demoangle_t *a;
*length = 0; // assume we fail
@@ -888,7 +876,7 @@ static qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length )
{
// select entry into circular buffer
demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK;
a = &demo.cmds[demo.angle_position];
demoangle_t *a = &demo.cmds[demo.angle_position];
// record update
a->starttime = demo.timestamp;
@@ -1060,8 +1048,7 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length )
static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next )
{
int i, i0, i1, imod;
float at;
int i0, i1, imod;
if( cls.timedemo ) return;
@@ -1071,9 +1058,9 @@ static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t
if( demo.cmds[i0].starttime >= t )
{
for( i = 0; i < ANGLE_BACKUP - 2; i++ )
for( int i = 0; i < ANGLE_BACKUP - 2; i++ )
{
at = demo.cmds[imod & ANGLE_MASK].starttime;
float at = demo.cmds[imod & ANGLE_MASK].starttime;
if( at == 0.0f ) break;
if( at < t )
@@ -1114,7 +1101,6 @@ void CL_DemoInterpolateAngles( void )
{
demoangle_t *prev = NULL, *next = NULL;
float frac = 0.0f;
float curtime;
if( cls.demoplayback == DEMO_QUAKE1 )
{
@@ -1126,7 +1112,7 @@ void CL_DemoInterpolateAngles( void )
}
else
{
curtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime;
float curtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime;
if( curtime > demo.timestamp )
curtime = demo.timestamp; // don't run too far
@@ -1239,7 +1225,6 @@ int GAME_EXPORT CL_GetDemoComment( const char *demoname, char *comment )
demodirectory_t directory;
demoentry_t entry;
float playtime = 0.0f;
int i;
if( !comment ) return false;
@@ -1279,7 +1264,7 @@ int GAME_EXPORT CL_GetDemoComment( const char *demoname, char *comment )
return false;
}
for( i = 0; i < directory.numentries; i++ )
for( int i = 0; i < directory.numentries; i++ )
{
FS_Read( demfile, &entry, sizeof( demoentry_t ));
playtime += entry.playback_time;
@@ -1388,7 +1373,6 @@ void CL_Record_f( void )
{
string demoname, demopath;
const char *name;
int n;
if( Cmd_Argc() == 1 )
{
@@ -1425,6 +1409,7 @@ void CL_Record_f( void )
if( !Q_stricmp( name, "new" ))
{
// scan for a free filename
int n;
for( n = 0; n < 10000; n++ )
{
CL_DemoGetName( n, demoname, sizeof( demoname ));
@@ -1511,7 +1496,7 @@ void CL_PlayDemo_f( void )
{
char filename[MAX_QPATH];
char demoname[MAX_QPATH];
int i, ident;
int ident;
if( Cmd_Argc() < 2 )
{
@@ -1584,7 +1569,7 @@ void CL_PlayDemo_f( void )
// 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++ )
for( int i = 0; i < demo.directory.numentries; i++ )
{
demoentry_t *entry = &demo.directory.entries[i];
@@ -1640,15 +1625,13 @@ 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;
int c = Cmd_Argc() - 1;
if( c > MAX_DEMOS )
{
Con_DPrintf( S_WARN "%s: max %i demos in demoloop\n", __func__, MAX_DEMOS );
@@ -1657,7 +1640,7 @@ void CL_StartDemos_f( void )
Con_Printf( "%i demo%s in loop\n", c, (c > 1) ? "s" : "" );
for( i = 1; i < c + 1; i++ )
for( int i = 1; i < c + 1; i++ )
Q_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] ));
cls.demos_pending = true;
}
@@ -1717,7 +1700,6 @@ void CL_ListDemo_f( void )
file_t *f;
char filename[MAX_QPATH];
char demoname[MAX_QPATH];
int i;
if( Cmd_Argc() < 2 )
{
@@ -1751,7 +1733,7 @@ void CL_ListDemo_f( void )
filename, hdr.net_protocol, hdr.dem_protocol, hdr.host_fps, hdr.mapname,
hdr.comment, hdr.gamedir );
for( i = 0; i < num_entries; i++ )
for( int i = 0; i < num_entries; i++ )
{
demoentry_t entry;

View File

@@ -39,7 +39,6 @@ static efrag_t *cl_efrags;
static efrag_t *CL_AllocEfrags( int num )
{
int i;
efrag_t *efrags;
if( !cl.worldmodel )
@@ -55,7 +54,7 @@ static efrag_t *CL_AllocEfrags( int num )
efrags = Mem_Calloc( cl.worldmodel->mempool, sizeof( *efrags ) * num );
// initialize linked list
for( i = 0; i < num - 1; i++ )
for( int i = 0; i < num - 1; i++ )
efrags[i].entnext = &efrags[i + 1];
cl_efrags_num += num;
@@ -81,8 +80,6 @@ R_SplitEntityOnNode
*/
static void R_SplitEntityOnNode( mnode_t *node )
{
efrag_t *ef;
mleaf_t *leaf;
int sides;
if( node->contents == CONTENTS_SOLID )
@@ -91,6 +88,9 @@ static void R_SplitEntityOnNode( mnode_t *node )
// add an efrag if the node is a leaf
if( node->contents < 0 )
{
efrag_t *ef;
mleaf_t *leaf;
if( !r_pefragtopnode )
r_pefragtopnode = node;
@@ -142,7 +142,6 @@ void R_AddEfrags( cl_entity_t *ent )
{
matrix3x4 transform;
vec3_t outmins, outmaxs;
int i;
if( !ent->model )
return;
@@ -155,7 +154,7 @@ void R_AddEfrags( cl_entity_t *ent )
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++ )
for( int i = 0; i < 3; i++ )
{
r_emins[i] = ent->origin[i] + outmins[i];
r_emaxs[i] = ent->origin[i] + outmaxs[i];
@@ -174,13 +173,11 @@ 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;
cl_entity_t *pent = pefrag->entity;
model_t *clmodel = pent->model;
// how this could happen?
if( unlikely( clmodel->type < mod_brush || clmodel->type > mod_studio ))

View File

@@ -44,21 +44,17 @@ 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;
float bestdiff = 999999;
int best = -1;
bestdiff = 999999;
best = -1;
for( i = 0; i < 256; i++ )
for( int i = 0; i < 256; i++ )
{
rf = r - clgame.palette[i].r;
gf = g - clgame.palette[i].g;
bf = b - clgame.palette[i].b;
float rf = r - clgame.palette[i].r;
float gf = g - clgame.palette[i].g;
float bf = b - clgame.palette[i].b;
// convert color to monochrome
diff = rf * (rf * 0.2f) + gf * (gf * 0.5f) + bf * (bf * 0.3f);
float diff = rf * (rf * 0.2f) + gf * (gf * 0.5f) + bf * (bf * 0.3f);
if ( diff < bestdiff )
{
@@ -90,13 +86,11 @@ 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++ )
for( int 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 );
@@ -116,15 +110,13 @@ 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++ )
for( int i = 0; i < GI->max_particles - 1; i++ )
cl_particles[i].next = &cl_particles[i+1];
cl_particles[GI->max_particles-1].next = NULL;
@@ -357,15 +349,13 @@ 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++ )
for( int i = 0; i < GI->max_beams - 1; i++ )
cl_viewbeams[i].next = &cl_viewbeams[i+1];
cl_viewbeams[GI->max_beams - 1].next = NULL;
}
@@ -416,7 +406,6 @@ void CL_KillDeadBeams( cl_entity_t *pDeadEntity )
while( pbeam )
{
cl_entity_t *beament;
pnext = pbeam->next;
// link into new list.
@@ -471,16 +460,13 @@ 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;
int 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 );
byte *afile = FS_LoadFile( filename, NULL, false );
if( !afile )
{
@@ -490,9 +476,9 @@ void CL_ReadLineFile_f( void )
Con_Printf( "Reading %s...\n", filename );
count = 0;
pfile = (char *)afile;
model = CL_LoadModel( DEFAULT_LASERBEAM_PATH, &modelIndex );
int count = 0;
char *pfile = (char *)afile;
model_t *model = CL_LoadModel( DEFAULT_LASERBEAM_PATH, &modelIndex );
while( 1 )
{
@@ -849,6 +835,31 @@ BEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float l
return pbeam;
}
static qboolean R_BeamVisible( const vec3_t start, const vec3_t end )
{
vec3_t mins, maxs;
for( int i = 0; i < 3; i++ )
{
if( start[i] < end[i] )
{
mins[i] = start[i];
maxs[i] = end[i];
}
else
{
mins[i] = end[i];
maxs[i] = start[i];
}
// don't let it be zero sized
if( mins[i] == maxs[i] )
maxs[i] += 1.0f;
}
return Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ));
}
/*
==============
R_BeamPoints
@@ -861,7 +872,7 @@ BEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float
{
BEAM *pbeam;
if( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true ))
if( life != 0 && !R_BeamVisible( start, end ))
return NULL;
pbeam = R_BeamAlloc();
@@ -1039,25 +1050,20 @@ 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++ )
for( int i = 0; i < NUMVERTEXNORMALS; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
angle = cl.time * cl_avelocities[i][0];
float sr, sp, sy, cr, cp, cy;
float 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 );
vec3_t forward = { cp * cy, cp * sy, -sp };
p->die = cl.time + 0.001f;
p->color = 111; // yellow
@@ -1074,19 +1080,16 @@ R_ParticleExplosion
*/
void GAME_EXPORT R_ParticleExplosion( const vec3_t org )
{
particle_t *p;
int i, j;
for( i = 0; i < 1024; i++ )
for( int i = 0; i < 1024; i++ )
{
p = R_AllocParticle( NULL );
particle_t *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++ )
for( int 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 );
@@ -1105,15 +1108,12 @@ 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;
int colorMod = 0;
int packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
for( i = 0; i < 512; i++ )
for( int i = 0; i < 512; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + 0.3f;
@@ -1123,7 +1123,7 @@ void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int col
p->type = pt_blob;
for( j = 0; j < 3; j++ )
for( int 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 );
@@ -1139,14 +1139,11 @@ R_BlobExplosion
*/
void GAME_EXPORT R_BlobExplosion( const vec3_t org )
{
particle_t *p;
int i, j, packedColor;
int packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
for( i = 0; i < 1024; i++ )
for( int i = 0; i < 1024; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + COM_RandomFloat( 1.0f, 1.4f );
@@ -1163,7 +1160,7 @@ void GAME_EXPORT R_BlobExplosion( const vec3_t org )
p->color = COM_RandomLong( 150, 155 );
}
for( j = 0; j < 3; j++ )
for( int 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 );
@@ -1180,9 +1177,6 @@ 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
@@ -1190,9 +1184,9 @@ void GAME_EXPORT R_RunParticleEffect( const vec3_t org, const vec3_t dir, int co
return;
}
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->color = (color & ~7) + COM_RandomLong( 0, 7 );
@@ -1214,20 +1208,18 @@ 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;
int pspeed = speed * 3;
VectorNormalize2( ndir, dir );
for( i = 0; i < (speed / 2); i++ )
for( int 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++ )
for( int j = 0; j < 8; j++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + 1.5f;
@@ -1237,6 +1229,8 @@ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int s
VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org );
VectorScale( vec, pspeed, p->vel );
}
pspeed -= speed;
}
}
@@ -1249,17 +1243,15 @@ 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++ )
float arc = 0.05f;
for( int i = 0; i < 100; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + 2.0f;
@@ -1275,11 +1267,10 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
accel -= 0.00001f; // so last few will drip
}
for( arc = 0.075f, i = 0; i < ( speed / 5 ); i++ )
arc = 0.075f;
for( int i = 0; i < ( speed / 5 ); i++ )
{
float num;
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + 3.0f;
@@ -1292,14 +1283,14 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
p->vel[2] -= arc;
arc -= 0.005f;
num = COM_RandomFloat( 0.0f, 1.0f );
float 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++ )
for( int j = 0; j < 2; j++ )
{
p = R_AllocParticle( NULL );
if( !p ) return;
@@ -1308,10 +1299,7 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
p->color = pcolor + COM_RandomLong( 0, 9 );
p->type = pt_4x_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 );
VectorAddScalar( org, COM_RandomFloat( -1.0f, 1.0f ), p->org );
VectorCopy( dir, p->vel );
p->vel[2] -= arc;
@@ -1329,18 +1317,15 @@ 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( int i = -16; i < 16; i++ )
{
for( j = -16; j <16; j++ )
for( int j = -16; j <16; j++ )
{
for( k = 0; k < 1; k++ )
for( int k = 0; k < 1; k++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + COM_RandomFloat( 2.0f, 2.62f );
@@ -1356,7 +1341,7 @@ void GAME_EXPORT R_LavaSplash( const vec3_t org )
p->org[2] = org[2] + COM_RandomFloat( 0.0f, 63.0f );
VectorNormalize( dir );
vel = COM_RandomFloat( 50.0f, 113.0f );
float vel = COM_RandomFloat( 50.0f, 113.0f );
VectorScale( dir, vel, p->vel );
}
}
@@ -1371,16 +1356,13 @@ 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( int i = 0; i < 32; i++ )
{
for( j = 0; j < 32; j++ )
for( int j = 0; j < 32; j++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + life + COM_RandomFloat( -0.5f, 0.5f );
@@ -1390,7 +1372,7 @@ void GAME_EXPORT R_ParticleBurst( const vec3_t org, int size, int color, float l
VectorCopy( org, p->org );
VectorAddScalar( org, COM_RandomFloat( -size, size ), dest );
VectorSubtract( dest, p->org, dir );
dist = VectorNormalizeLength( dir );
float dist = VectorNormalizeLength( dir );
VectorScale( dir, ( dist / life ), p->vel );
}
}
@@ -1404,16 +1386,13 @@ 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( int i = -8; i < 8; i++ )
{
for( j = -8; j < 8; j++ )
for( int j = -8; j < 8; j++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
dest[0] = (i * 32.0f) + org[0];
@@ -1431,10 +1410,10 @@ void GAME_EXPORT R_LargeFunnel( const vec3_t org, int reverse )
VectorSubtract( org, p->org, dir );
}
vel = dest[2] / 8.0f;
float vel = dest[2] / 8.0f;
if( vel < 64.0f ) vel = 64.0f;
dist = VectorNormalizeLength( dir );
float dist = VectorNormalizeLength( dir );
vel += COM_RandomFloat( 64.0f, 128.0f );
VectorScale( dir, vel, p->vel );
p->die = cl.time + (dist / vel );
@@ -1451,18 +1430,15 @@ 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( int i = -16; i < 16; i += 4 )
{
for( j = -16; j < 16; j += 4 )
for( int j = -16; j < 16; j += 4 )
{
for( k = -24; k < 32; k += 4 )
for( int k = -24; k < 32; k += 4 )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + COM_RandomFloat( 0.2f, 0.34f );
@@ -1478,7 +1454,7 @@ void GAME_EXPORT R_TeleportSplash( const vec3_t org )
p->org[2] = org[2] + k + COM_RandomFloat( 0.0f, 3.0f );
VectorNormalize( dir );
vel = COM_RandomFloat( 50.0f, 113.0f );
float vel = COM_RandomFloat( 50.0f, 113.0f );
VectorScale( dir, vel, p->vel );
}
}
@@ -1615,13 +1591,12 @@ 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;
float len = VectorNormalizeLength( diff );
float curdist = 0;
while( curdist <= len )
{
@@ -1655,9 +1630,8 @@ static void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t orig
{
vec3_t p[8], tmp;
float gap = BOX_GAP;
int i;
for( i = 0; i < 8; i++ )
for( int 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 ;
@@ -1667,7 +1641,7 @@ static void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t orig
VectorCopy( tmp, p[i] );
}
for( i = 0; i < 6; i++ )
for( int 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 );
}
@@ -1681,9 +1655,7 @@ 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 );
int pcolor = R_LookupColor( r, g, b );
PM_ParticleLine( start, end, pcolor, life, 0 );
}
@@ -1697,9 +1669,8 @@ void GAME_EXPORT R_ParticleBox( const vec3_t absmin, const vec3_t absmax, byte r
{
vec3_t mins, maxs;
vec3_t origin;
int pcolor;
pcolor = R_LookupColor( r, g, b );
int pcolor = R_LookupColor( r, g, b );
VectorAverage( absmax, absmin, origin );
VectorSubtract( absmax, origin, maxs );
@@ -1716,20 +1687,18 @@ R_ShowLine
*/
void GAME_EXPORT R_ShowLine( const vec3_t start, const vec3_t end )
{
vec3_t dir, org;
float len;
particle_t *p;
vec3_t dir;
VectorSubtract( end, start, dir );
len = VectorNormalizeLength( dir );
float len = VectorNormalizeLength( dir );
VectorScale( dir, 5.0f, dir );
VectorCopy( start, org );
vec3_t org = Vec3( start );
while( len > 0 )
{
len -= 5.0f;
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die = cl.time + 30;
@@ -1748,25 +1717,21 @@ 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 );
float dist = VectorLength( dir );
if( dist > 1000.0f ) dist = 1000.0f;
quantity = (1000.0f - dist) / 100.0f;
int quantity = (1000.0f - dist) / 100.0f;
if( quantity == 0 ) quantity = 1;
color = 3 - ((30 * quantity) / 100 );
int color = 3 - ((30 * quantity) / 100 );
R_SparkStreaks( pos, 2, -200, 200 );
for( i = 0; i < quantity * 4; i++ )
for( int i = 0; i < quantity * 4; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
VectorCopy( pos, p->org);
@@ -1790,12 +1755,9 @@ R_FlickerParticles
*/
void GAME_EXPORT R_FlickerParticles( const vec3_t org )
{
particle_t *p;
int i;
for( i = 0; i < 15; i++ )
for( int i = 0; i < 15; i++ )
{
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
VectorCopy( org, p->org );
@@ -1819,15 +1781,13 @@ 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++ )
for( int i = 0; i < count; i++ )
{
VectorAddScalar( vel, COM_RandomFloat( velocityMin, velocityMax ), vel2 );
p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f ));
particle_t *p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f ));
if( !p ) return;
p->type = pt_grav;
@@ -1845,9 +1805,7 @@ 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 );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
if( org ) VectorCopy( org, p->org );
@@ -1865,17 +1823,15 @@ 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 );
float speed = Q_max( tracerspeed.value, 3.0f );
VectorSubtract( end, start, dir );
len = VectorLength( dir );
float len = VectorLength( dir );
if( len == 0.0f ) return;
VectorScale( dir, 1.0f / len, dir ); // normalize
offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value;
float offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value;
VectorScale( dir, offset, vel );
VectorAdd( start, vel, pos );
VectorScale( dir, speed, vel );
@@ -1926,17 +1882,15 @@ 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; i<count; i++ )
for( int i = 0; i<count; i++ )
{
vel[0] = COM_RandomFloat( velocityMin, velocityMax );
vel[1] = COM_RandomFloat( velocityMin, velocityMax );
vel[2] = COM_RandomFloat( velocityMin, velocityMax );
p = R_AllocTracer( pos, vel, COM_RandomFloat( 0.1f, 0.5f ));
particle_t *p = R_AllocTracer( pos, vel, COM_RandomFloat( 0.1f, 0.5f ));
if( !p ) return;
p->color = 5;
@@ -1956,14 +1910,11 @@ void GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float l
{
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;
float factor = -1.0 / life;
for ( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
temp[0] = dist * COM_RandomFloat( -100.0f, 100.0f );
temp[1] = dist * COM_RandomFloat( -100.0f, 100.0f );
@@ -1971,7 +1922,8 @@ void GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float l
VectorScale( temp, factor, vel );
VectorAdd( temp, end, start );
if(( p = R_AllocTracer( start, vel, life )) == NULL )
particle_t *p = R_AllocTracer( start, vel, life );
if( p == NULL )
return;
p->type = pt_explode;
@@ -2036,16 +1988,12 @@ 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 );
byte *afile = FS_LoadFile( filename, NULL, false );
if( !afile )
{
@@ -2055,8 +2003,8 @@ void CL_ReadPointFile_f( void )
Con_Printf( "Reading %s...\n", filename );
count = 0;
pfile = (char *)afile;
int count = 0;
char *pfile = (char *)afile;
while( 1 )
{
@@ -2082,7 +2030,7 @@ void CL_ReadPointFile_f( void )
// NOTE: can't use R_AllocParticle because this command
// may be executed from the console, while frametime is 0
p = cl_free_particles;
particle_t *p = cl_free_particles;
cl_free_particles = p->next;
p->next = cl_active_particles;
cl_active_particles = p;

View File

@@ -42,10 +42,6 @@ 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 )
@@ -53,15 +49,16 @@ static void CL_CalcPlayerVelocity( int idx, vec3_t velocity )
if( idx == cl.playernum + 1 )
{
pcd = &cl.frames[cl.parsecountmod].clientdata;
clientdata_t *pcd = &cl.frames[cl.parsecountmod].clientdata;
VectorCopy( pcd->velocity, velocity );
}
else
{
dt = clgame.entities[idx].curstate.animtime - clgame.entities[idx].prevstate.animtime;
double dt = clgame.entities[idx].curstate.animtime - clgame.entities[idx].prevstate.animtime;
if( dt != 0.0 )
{
vec3_t delta;
VectorSubtract( clgame.entities[idx].curstate.velocity, clgame.entities[idx].prevstate.velocity, delta );
VectorScale( delta, 1.0f / dt, velocity );
}
@@ -130,16 +127,13 @@ 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++ )
for( int i = 0; i < MAX_EVENTS; i++ )
{
ev = clgame.events[i];
cl_user_event_t *ev = clgame.events[i];
if( !ev ) break;
if( !Q_stricmp( ev->name, szEvName ))
@@ -158,12 +152,10 @@ CL_EventIndex
*/
word CL_EventIndex( const char *name )
{
word i;
if( COM_StringEmptyOrNULL( name ))
return 0;
for( i = 1; i < MAX_EVENTS && cl.event_precache[i][0]; i++ )
for( word i = 1; i < MAX_EVENTS && cl.event_precache[i][0]; i++ )
{
if( !Q_stricmp( cl.event_precache[i], name ))
return i;
@@ -204,28 +196,24 @@ 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++ )
for( int i = 0; i < MAX_EVENTS; i++ )
{
ev = clgame.events[i];
cl_user_event_t *ev = clgame.events[i];
if( !ev )
{
idx = bound( 1, ei->index, ( MAX_EVENTS - 1 ));
int 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];
const char *name = cl.event_precache[ei->index];
if( cl_trace_events.value )
{
@@ -261,15 +249,11 @@ called right before draw frame
*/
void CL_FireEvents( void )
{
event_state_t *es;
event_info_t *ei;
int i;
event_state_t *es = &cl.events;
es = &cl.events;
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
for( int i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
event_info_t *ei = &es->ei[i];
if( ei->index == 0 )
continue;
@@ -294,16 +278,12 @@ find first empty event
*/
static event_info_t *CL_FindEmptyEvent( void )
{
int i;
event_state_t *es;
event_info_t *ei;
es = &cl.events;
event_state_t *es = &cl.events;
// look for first slot where index is != 0
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
for( int i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
event_info_t *ei = &es->ei[i];
if( ei->index != 0 )
continue;
return ei;
@@ -322,15 +302,11 @@ replace only unreliable events
*/
static event_info_t *CL_FindUnreliableEvent( void )
{
event_state_t *es;
event_info_t *ei;
int i;
event_state_t *es = &cl.events;
es = &cl.events;
for ( i = 0; i < MAX_EVENT_QUEUE; i++ )
for ( int i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
event_info_t *ei = &es->ei[i];
if( ei->index != 0 )
{
// it's reliable, so skip it
@@ -423,16 +399,10 @@ 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 );
int num_events = MSG_ReadUBitLong( msg, 5 );
if( proto == PROTO_GOLDSRC )
entity_bits = MAX_GOLDSRC_ENTITY_BITS;
@@ -440,9 +410,11 @@ void CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto )
entity_bits = MAX_ENTITY_BITS;
// parse events queue
for( i = 0 ; i < num_events; i++ )
for( int i = 0 ; i < num_events; i++ )
{
event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );
int packet_index;
float delay;
int event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS );
if( MSG_ReadOneBit( msg ))
{
@@ -467,7 +439,7 @@ void CL_ParseEvent( sizebuf_t *msg, connprotocol_t proto )
if( packet_index < frame->num_entities )
{
state = &cls.packet_entities[(frame->first_entity+packet_index)%cls.num_client_entities];
entity_state_t *state = &cls.packet_entities[(frame->first_entity+packet_index)%cls.num_client_entities];
args.entindex = state->number;
if( VectorIsNull( args.origin ))
@@ -515,8 +487,6 @@ 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;
@@ -538,20 +508,20 @@ void GAME_EXPORT CL_PlaybackEvent( int flags, const edict_t *pInvoker, word even
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;
event_args_t args =
{
.origin = Vec3( origin ),
.angles = Vec3( angles ),
.velocity = Vec3( cl.simvel ),
.entindex = cl.playernum + 1,
.ducking = ( cl.local.usehull == 1 ),
.fparam1 = fparam1,
.fparam2 = fparam2,
.iparam1 = iparam1,
.iparam2 = iparam2,
.bparam1 = bparam1,
.bparam2 = bparam2,
};
CL_QueueEvent( flags, eventindex, delay, &args );
}

View File

@@ -17,6 +17,20 @@ GNU General Public License for more details.
#include "filesystem.h"
#include "client.h"
#include "qfont.h"
#include "swaplib.h"
le_struct_begin( charinfo_swap )
le_struct_field( charinfo, startoffset )
le_struct_field( charinfo, charwidth )
le_struct_end();
le_struct_begin( qfont_swap )
le_struct_field( qfont_t, width )
le_struct_field( qfont_t, height )
le_struct_field( qfont_t, rowcount )
le_struct_field( qfont_t, rowheight )
le_struct_array_child( qfont_t, fontinfo, charinfo_swap, NUM_GLYPHS )
le_struct_end();
qboolean CL_FixedFont( cl_font_t *font )
{
@@ -25,17 +39,14 @@ qboolean CL_FixedFont( cl_font_t *font )
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 );
int tex = ref.dllFuncs.GL_LoadTexture( fontname, NULL, 0, texFlags );
if( !tex )
return 0;
font_width = REF_GET_PARM( PARM_TEX_WIDTH, tex );
int font_width = REF_GET_PARM( PARM_TEX_WIDTH, tex );
if( !font_width )
{
ref.dllFuncs.GL_FreeTexture( tex );
@@ -75,7 +86,7 @@ void CL_SetFontColor( cl_font_t *font, const rgba_t color )
qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float scale, convar_t *rendermode, uint texFlags )
{
int font_width, i;
int font_width;
if( !rendermode )
return false;
@@ -93,7 +104,7 @@ qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font, float sc
font->rendermode = rendermode;
font->charHeight = Q_rint( font_width / 16 * scale );
for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ )
for( int 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;
@@ -110,8 +121,7 @@ qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float
{
fs_offset_t length;
qfont_t src;
byte *pfile;
int font_width, i;
int font_width;
if( !rendermode )
return false;
@@ -119,7 +129,7 @@ qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float
if( font->valid )
return true;
pfile = g_fsapi.LoadFile( fontname, &length, false );
byte *pfile = g_fsapi.LoadFile( fontname, &length, false );
if( !pfile )
return false;
@@ -130,6 +140,7 @@ qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float
}
memcpy( &src, pfile, sizeof( src ));
le_struct_swap( qfont_swap, &src );
Mem_Free( pfile );
font->hFontTexture = CL_LoadFontTexture( fontname, texFlags, &font_width );
@@ -142,7 +153,7 @@ qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font, float
font->rendermode = rendermode;
font->charHeight = Q_rint( src.rowheight * scale );
for( i = 0; i < ARRAYSIZE( font->fontRc ); i++ )
for( int i = 0; i < ARRAYSIZE( font->fontRc ); i++ )
{
const charinfo *ci = &src.fontinfo[i];
@@ -180,11 +191,6 @@ static int CL_CalcTabStop( const cl_font_t *font, int x )
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;
@@ -205,21 +211,23 @@ int CL_DrawCharacter( float x, float y, int number, const rgba_t color, cl_font_
if( !number || !font->charWidths[number])
return 0;
int texw, texh;
R_GetTextureParms( &texw, &texh, font->hFontTexture );
if( !texw || !texh )
return font->charWidths[number];
rc = &font->fontRc[number];
wrect_t *rc = &font->fontRc[number];
float half = 0.5f;
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;
float s1 = ((float)rc->left + half ) / texw;
float t1 = ((float)rc->top + half ) / texh;
float s2 = ((float)rc->right - half ) / texw;
float t2 = ((float)rc->bottom - half ) / texh;
float w = ( rc->right - rc->left ) * font->scale;
float h = ( rc->bottom - rc->top ) * font->scale;
if( FBitSet( flags, FONT_DRAW_HUD ))
SPR_AdjustSize( &x, &y, &w, &h );

View File

@@ -41,12 +41,10 @@ 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];
position_history_t *prev = &ent->ph[ent->current_position & HISTORY_MASK];
ent->current_position = (ent->current_position + 1) & HISTORY_MASK;
ph = &ent->ph[ent->current_position];
position_history_t *ph = &ent->ph[ent->current_position];
VectorCopy( ent->curstate.origin, ph->origin );
VectorCopy( ent->curstate.angles, ph->angles );
@@ -74,11 +72,10 @@ Interpolation init or reset after teleporting
*/
static void CL_ResetPositions( cl_entity_t *ent )
{
position_history_t store;
if( !ent )
return;
if( !ent ) return;
store = ent->ph[ent->current_position];
position_history_t store = ent->ph[ent->current_position & HISTORY_MASK];
ent->current_position = 1;
memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX );
@@ -95,14 +92,13 @@ 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 );
float maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
float len = VectorLength( delta );
return (len > maxlen);
}
@@ -170,22 +166,23 @@ 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;
float dt = ent->curstate.impacttime - ent->curstate.starttime;
if( dt != 0.0f )
{
float t;
if( ent->lastmove > cl.time )
t = ent->lastmove;
else t = cl.time;
frac = ( t - ent->curstate.starttime ) / dt;
float frac = ( t - ent->curstate.starttime ) / dt;
frac = bound( 0.0f, frac, 1.0f );
VectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin );
@@ -240,18 +237,14 @@ 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 );
studiohdr_t *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;
int sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 );
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;
return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time );
}
}
@@ -302,8 +295,6 @@ 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;
@@ -325,7 +316,7 @@ static void CL_ProcessEntityUpdate( cl_entity_t *ent )
VectorCopy( aiment->origin, ent->curstate.origin );
}
parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f;
qboolean parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f;
// allow interpolation on bmodels too
if( ent->model && ent->model->type == mod_brush )
@@ -364,13 +355,11 @@ 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;
uint imod = ent->current_position;
uint i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end)
uint i1 = (imod - 1) & HISTORY_MASK; // oldpos (lerp start)
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++ )
for( uint i = 1; i < HISTORY_MAX - 1; i++ )
{
double at = ent->ph[( imod - i ) & HISTORY_MASK].animtime;
@@ -402,19 +391,19 @@ 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;
double t0 = ph0->animtime;
double t1 = ph1->animtime;
if( t0 != 0.0 )
{
vec4_t q, q1, q2;
vec3_t delta;
double frac;
VectorSubtract( ph0->origin, ph1->origin, delta );
@@ -450,7 +439,6 @@ 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 );
@@ -479,11 +467,11 @@ static int CL_InterpolateModel( cl_entity_t *e )
if( cl.local.moving && cl.local.onground == e->index )
return 1;
t = cl.time - cl_interp.value;
double t = cl.time - cl_interp.value;
CL_FindInterpolationUpdates( e, t, &ph0, &ph1 );
t1 = ph1->animtime;
t2 = ph0->animtime;
double t1 = ph1->animtime;
double t2 = ph0->animtime;
if( t - t1 < 0.0f )
return 0;
@@ -503,7 +491,7 @@ static int CL_InterpolateModel( cl_entity_t *e )
}
VectorSubtract( ph0->origin, ph1->origin, delta );
frac = (t - t1) / (t2 - t1);
double frac = (t - t1) / (t2 - t1);
if( frac < 0.0f )
return 0;
@@ -533,8 +521,6 @@ interpolate non-local clients
*/
void CL_ComputePlayerOrigin( cl_entity_t *ent )
{
double targettime;
vec4_t q, q1, q2;
vec3_t origin;
vec3_t angles;
@@ -550,6 +536,8 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent )
if( cls.demoplayback == DEMO_QUAKE1 )
{
vec4_t q, q1, q2;
// quake lerping is easy
VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, ent->origin );
AngleQuaternion( ent->prevstate.angles, q1, false );
@@ -559,7 +547,7 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent )
return;
}
targettime = cl.time - cl_interp.value;
double targettime = cl.time - cl_interp.value;
CL_PureOrigin( ent, targettime, origin, angles );
VectorCopy( angles, ent->angles );
@@ -575,9 +563,7 @@ 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];
entity_state_t *ps = &cl.frames[cl.parsecountmod].playerstate[playerindex];
ps->number = state->number;
ps->messagenum = cl.parsecount;
ps->msg_time = cl.mtime[0];
@@ -618,19 +604,15 @@ 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++ )
for( int 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];
entity_state_t *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];
cl_entity_t *ent = &clgame.entities[state->number];
ent->player = CL_IsPlayerIndex( state->number );
if( state->number == ( cl.playernum + 1 ))
@@ -676,7 +658,6 @@ 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 ));
@@ -687,6 +668,8 @@ static void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto )
// read it all, but ignore it
while( 1 )
{
int newnum;
if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto ))
break; // done
@@ -816,22 +799,19 @@ rest of the data stream.
*/
int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto )
{
frame_t *newframe, *oldframe;
frame_t *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
count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1;
int count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1;
newframe = &cl.frames[cl.parsecountmod];
frame_t *newframe = &cl.frames[cl.parsecountmod];
// allocate parse entities
memset( newframe->flags, 0, sizeof( newframe->flags ));
@@ -872,7 +852,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto
if( MSG_CheckOverflow( msg ))
Host_Error( "%s: overflow\n", __func__ );
player = CL_IsPlayerIndex( newnum );
qboolean player = CL_IsPlayerIndex( newnum );
while( oldnum < newnum )
{
@@ -884,7 +864,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto
if( oldnum == newnum )
{
// delta from previous state
bufStart = MSG_GetNumBytesRead( msg );
int bufStart = MSG_GetNumBytesRead( msg );
CL_DeltaEntity( msg, newframe, newnum, oldent, true );
if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;
oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );
@@ -894,7 +874,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto
if( oldnum > newnum )
{
// delta from baseline ?
bufStart = MSG_GetNumBytesRead( msg );
int bufStart = MSG_GetNumBytesRead( msg );
CL_DeltaEntity( msg, newframe, newnum, NULL, true );
if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;
continue;
@@ -981,15 +961,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType )
if( !draw_player )
return false;
if( entityType == ET_BEAM )
{
ref.dllFuncs.CL_AddCustomBeam( ent );
return true;
}
else if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
{
if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
return false;
}
// because pTemp->entity.curstate.effects
// is already occupied by FTENT_FLICKER
@@ -1037,12 +1010,10 @@ for all current players
*/
static void CL_LinkPlayers( frame_t *frame )
{
cl_entity_t *ent = CL_GetLocalPlayer();
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 );
@@ -1120,15 +1091,9 @@ 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++ )
for( int i = 0; i < frame->num_entities; i++ )
{
state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
entity_state_t *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 )
@@ -1138,7 +1103,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
if( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))
continue;
ent = CL_GetEntityByIndex( state->number );
cl_entity_t *ent = CL_GetEntityByIndex( state->number );
if( !ent )
{
@@ -1149,7 +1114,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
// animtime must keep an actual
ent->curstate.animtime = state->animtime;
ent->curstate.frame = state->frame;
interpolate = false;
qboolean interpolate = false;
if( !ent->model ) continue;
@@ -1163,7 +1128,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
}
}
parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );
qboolean parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );
if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND )
{
@@ -1376,9 +1341,6 @@ SOUND ENGINE IMPLEMENTATION
*/
qboolean CL_GetEntitySpatialization( channel_t *ch )
{
cl_entity_t *ent;
qboolean valid_origin;
if( ch->entnum == 0 )
{
SetBits( ch->flags, FL_CHAN_STATIC_SOUND );
@@ -1391,8 +1353,8 @@ qboolean CL_GetEntitySpatialization( channel_t *ch )
return true;
}
valid_origin = VectorIsNull( ch->origin ) ? false : true;
ent = CL_GetEntityByIndex( ch->entnum );
qboolean valid_origin = VectorIsNull( ch->origin ) ? false : true;
cl_entity_t *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 )

View File

@@ -36,7 +36,7 @@ CVAR_DEFINE( cl_allow_download, "cl_allowdownload", "1", FCVAR_ARCHIVE, "allow t
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_logocolor, "255 120 24", 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" );
@@ -315,7 +315,6 @@ should be put at.
static float CL_LerpPoint( void )
{
double f = cl_serverframetime();
double frac;
if( f == 0.0 || cls.timedemo )
{
@@ -332,7 +331,7 @@ static float CL_LerpPoint( void )
if( cl_interp.value <= 0.001 )
return 1.0f;
frac = ( cl.time - cl.mtime[0] ) / cl_interp.value;
double frac = ( cl.time - cl.mtime[0] ) / cl_interp.value;
return frac;
}
@@ -346,20 +345,17 @@ 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;
float fgoal = (float)goal / 1000.0f;
if( fgoal != cl.local.interp_amount )
{
maxmove = host.frametime * 0.05;
diff = fgoal - cl.local.interp_amount;
float maxmove = host.frametime * 0.05;
float diff = fgoal - cl.local.interp_amount;
diff = bound( -maxmove, diff, maxmove );
cl.local.interp_amount += diff;
}
msec = cl.local.interp_amount * 1000.0f;
int msec = cl.local.interp_amount * 1000.0f;
msec = bound( 0, msec, 100 );
return msec;
@@ -420,14 +416,14 @@ CL_ComputePacketLoss
*/
static void CL_ComputePacketLoss( void )
{
int i, lost = 0;
int 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++ )
for( int 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++;
@@ -455,18 +451,15 @@ void CL_UpdateFrameLerp( void )
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;
int imod = cl.angle_position - 1;
int i0 = (imod + 1) & ANGLE_MASK;
int i1 = (imod + 0) & ANGLE_MASK;
if( cl.predicted_angle[i0].starttime >= t )
{
for( i = 0; i < ANGLE_BACKUP - 2; i++ )
for( int i = 0; i < ANGLE_BACKUP - 2; i++ )
{
at = cl.predicted_angle[imod & ANGLE_MASK].starttime;
float at = cl.predicted_angle[imod & ANGLE_MASK].starttime;
if( at == 0.0f ) break;
if( at < t )
@@ -534,14 +527,12 @@ 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);
int changed = (oldbuttons ^ cmd->buttons);
int released = changed & (~cmd->buttons);
if( released & ( IN_RIGHT|IN_MOVERIGHT ))
Cvar_SetValue( "r_showtextures", r_showtextures.value + 1 );
@@ -613,17 +604,16 @@ 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;
client_data_t cdat =
{
.viewangles = Vec3( cl.viewangles ),
.origin = Vec3( clgame.entities[cl.viewentity].origin ),
.iWeaponBits = cl.local.weapons,
.fov = cl.local.scr_fov,
};
if( clgame.dllFuncs.pfnUpdateClientData( &cdat, cl.time ))
{
@@ -642,21 +632,18 @@ 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;
int ms;
if( cls.state <= ca_connected || cls.state == ca_cinematic )
return;
// store viewangles in case it's will be freeze
VectorCopy( cl.viewangles, angles );
vec3_t angles = Vec3( cl.viewangles );
input_override = 0;
// fix rounding error and framerate depending player move
accurate_ms = host.frametime * 1000;
double accurate_ms = host.frametime * 1000;
ms = (int)accurate_ms;
cl.frametime_remainder += accurate_ms - ms; // accumulate rounding error each frame
@@ -677,7 +664,7 @@ static void CL_CreateCmd( void )
CL_SetSolidPlayers( cl.playernum );
// message we are constructing.
i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK;
int i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK;
pcmd = &cl.commands[i];
if( !cls.demoplayback )
@@ -695,7 +682,7 @@ static void CL_CreateCmd( void )
cmd = &nullcmd;
}
active = (( cls.signon == SIGNONS ) && !cl.paused && !cls.demoplayback );
qboolean active = (( cls.signon == SIGNONS ) && !cl.paused && !cls.demoplayback );
Platform_PreCreateMove();
clgame.dllFuncs.CL_CreateMove( host.frametime, cmd, active );
IN_EngineAppendMove( host.frametime, cmd, active );
@@ -946,13 +933,12 @@ 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 );
const char *name = Cmd_Argv( 1 );
if( COM_StringEmptyOrNULL( name ))
return;
@@ -1038,9 +1024,8 @@ 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 ));
int keylength = Q_snprintf( key, sizeof( key ), "%u", COM_RandomLong( 0, 0x7ffffffe ));
MD5Init( &ctx );
MD5Update( &ctx, key, keylength );
@@ -1079,7 +1064,6 @@ static void CL_WriteSteamTicket( sizebuf_t *send )
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen )
{
const char *name;
sizebuf_t send;
byte send_buf[2048];
char protinfo[MAX_INFO_STRING];
@@ -1090,7 +1074,7 @@ void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticke
Info_SetValueForKeyf( protinfo, "unique", sizeof( protinfo ), "%i", 0xffffffff );
Info_SetValueForKey( protinfo, "raw", "steam", sizeof( protinfo ));
CL_GetCDKey( protinfo, sizeof( protinfo ));
name = Info_ValueForKey( cls.userinfo, "name" );
const char *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 );
@@ -1123,8 +1107,6 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
{
char protinfo[MAX_INFO_STRING];
netadr_t adr = { 0 };
int input_devices;
netadrtype_t adrtype;
protinfo[0] = 0;
@@ -1135,11 +1117,11 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
return;
}
adrtype = NET_NetadrType( &adr );
netadrtype_t adrtype = NET_NetadrType( &adr );
if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER );
input_devices = IN_CollectInputDevices();
int input_devices = IN_CollectInputDevices();
IN_LockInputDevices( adrtype != NA_LOOPBACK ? true : false );
// GoldSrc doesn't need useragent string
@@ -1174,7 +1156,7 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
else
{
const char *qport = Cvar_VariableString( "net_qport" );
int extensions = Host_IsLocalGame() ? 0 : NET_EXT_SPLITSIZE;
int extensions = adrtype == NA_LOOPBACK ? 0 : NET_EXT_SPLITSIZE;
string key;
ID_GetMD5ForAddress( key, adr, sizeof( key ));
@@ -1252,8 +1234,6 @@ 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 resend_time;
if( cls.internetservers_wait )
{
@@ -1287,6 +1267,7 @@ static void CL_CheckForResend( void )
else if( cl_resend.value > CL_MAX_RESEND_TIME )
Cvar_DirectSetValue( &cl_resend, CL_MAX_RESEND_TIME );
float resend_time;
if( cls.bandwidth_test.started && !cls.bandwidth_test.passed && !cls.bandwidth_test.failed )
resend_time = 2.0f;
else
@@ -1295,7 +1276,7 @@ static void CL_CheckForResend( void )
if(( host.realtime - cls.connect_time ) < resend_time )
return;
res = NET_StringToAdrNB( cls.servername, &adr, false );
net_gai_state_t res = NET_StringToAdrNB( cls.servername, &adr, false );
if( res == NET_EAI_NONAME )
{
@@ -1377,14 +1358,15 @@ 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 ));
ClearBits( cl_logofile.flags, FCVAR_CHANGED );
ClearBits( cl_logocolor.flags, FCVAR_CHANGED );
ClearBits( cl_logoext.flags, FCVAR_CHANGED );
// sanitize cvar value
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
Cvar_DirectSet( &cl_logoext, "bmp" );
@@ -1395,20 +1377,20 @@ static void CL_CreateResourceList( void )
CL_ConvertImageToWAD3( szFileName );
Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName ));
}
fp = FS_Open( szFileName, "rb", true );
file_t *fp = FS_Open( szFileName, "rb", true );
if( !fp )
return;
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
nSize = FS_FileLength( fp );
int nSize = FS_FileLength( fp );
if( nSize != 0 )
{
pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 );
resource_t *pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 );
if( pNewResource )
{
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
SetBits( pNewResource->ucFlags, RES_CUSTOM );
memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 );
HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp );
@@ -1418,6 +1400,39 @@ static void CL_CreateResourceList( void )
FS_Close( fp );
}
/*
==================
CL_CheckLogoChanged
==================
*/
static void CL_CheckLogoChanged( void )
{
if( cls.state != ca_active )
return;
if( !FBitSet( cl_logofile.flags | cl_logocolor.flags | cl_logoext.flags, FCVAR_CHANGED ))
return;
CL_CreateResourceList();
if( cl.num_resources == 0 )
return;
player_info_t *player = &cl.players[cl.playernum];
COM_ClearCustomizationList( &player->customdata, true );
for( int i = 0; i < cl.num_resources; i++ )
{
resource_t *pResource = &cl.resourcelist[i];
if( !COM_CreateCustomization( &player->customdata, pResource, cl.playernum, 0, NULL, NULL ))
Con_Printf( "Unable to create custom decal\n" );
}
CL_SendResourceList( cl.resourcelist, cl.num_resources );
}
static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto )
{
if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" ))
@@ -1493,7 +1508,6 @@ static void CL_Rcon_f( void )
char message[1024];
sizebuf_t msg;
netadr_t to;
int i;
if( COM_StringEmptyOrNULL( rcon_password.string ))
{
@@ -1525,7 +1539,7 @@ static void CL_Rcon_f( void )
MSG_WriteStringf( &msg, C2S_RCON" %s ", rcon_password.string );
MSG_SeekToBit( &msg, -8, SEEK_CUR );
for( i = 1; i < Cmd_Argc(); i++ )
for( int i = 1; i < Cmd_Argc(); i++ )
{
string command;
@@ -1548,11 +1562,9 @@ CL_ClearState
*/
void CL_ClearState( void )
{
int i;
CL_ClearResourceLists();
for( i = 0; i < MAX_CLIENTS; i++ )
for( int i = 0; i < MAX_CLIENTS; i++ )
COM_ClearCustomizationList( &cl.players[i].customdata, false );
S_StopAllSounds ( true );
@@ -1617,9 +1629,9 @@ static void CL_SendDisconnectMessage( connprotocol_t proto )
Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf ));
}
int CL_GetSplitSize( void )
size_t CL_GetSplitSize( void )
{
int splitsize = (int)cl_dlmax.value;
size_t splitsize = (int)cl_dlmax.value;
if( !FBitSet( cls.extensions, NET_EXT_SPLITSIZE ))
return 1400;
@@ -1630,7 +1642,7 @@ int CL_GetSplitSize( void )
return FRAGMENT_DEFAULT_SIZE;
}
return (int)cl_dlmax.value;
return splitsize;
}
void CL_SetupNetchanForProtocol( connprotocol_t proto )
@@ -1946,8 +1958,6 @@ 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;
@@ -2123,7 +2133,6 @@ static void CL_ParseNETInfoMessage( netadr_t from, const char *s )
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 ));
@@ -2543,7 +2552,7 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
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
MSG_ReadBytes( msg, &servadr.port, sizeof( servadr.port )); // 2 bytes for Port, in network byte order
// list is ends here
if( !servadr.port )
@@ -2584,10 +2593,6 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
Con_Reportf( "%s: %s : %s\n", __func__, NET_AdrToString( from ), c );
// server connection
if( !Q_strcmp( c, "sb_connect" ))
{
SteamBroker_HandlePacket( from, msg );
}
if( !Q_strcmp( c, S2C_GOLDSRC_CONNECTION ) || !Q_strcmp( c, S2C_CONNECTION ))
{
CL_ClientConnect( cls.legacymode, c, from );
@@ -2818,7 +2823,7 @@ static void CL_ReadPackets( void )
// hot precache and downloading resources
if( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime )
{
double checktime = Host_IsLocalGame() ? 0.1 : 1.0;
double checktime = Host_IsLocalClient() ? 0.1 : 1.0;
if( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded )
{
@@ -2920,12 +2925,6 @@ void CL_ProcessFile( qboolean successfully_received, const char *filename )
{
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 )
{
@@ -3522,11 +3521,11 @@ static void CL_InitLocal( void )
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_AddRestrictedCommand ("connect", CL_Connect_f, "connect to a server by hostname" );
Cmd_AddCommand ("reconnect", CL_Reconnect_f, "reconnect to current level" );
Cmd_AddCommand ("retry", CL_Retry_f, "retry connection to last server" );
Cmd_AddCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" );
Cmd_AddRestrictedCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" );
Cmd_AddCommand( "richpresence_gamemode", Cmd_Null_f, "compatibility command, does nothing" );
Cmd_AddCommand( "richpresence_update", Cmd_Null_f, "compatibility command, does nothing" );
@@ -3589,6 +3588,9 @@ void Host_ClientBegin( void )
// finalize connection process if needs
CL_CheckClientState();
// check if logo cvars changed and re-upload if needed
CL_CheckLogoChanged();
// tell the client.dll about client data
CL_UpdateClientData();
@@ -3684,15 +3686,16 @@ void CL_Init( void )
COM_GetCommonLibraryPath( LIBRARY_CLIENT, libpath, sizeof( libpath ));
if( !CL_LoadProgs( libpath ))
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
S_Init(); // init sound
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
ID_Init();
SteamBroker_Init();
// client must be always initialized last so it can fetch all cvars
if( !CL_LoadProgs( libpath ))
Host_Error( "can't initialize %s: %s\n", libpath, COM_GetLibraryError( ));
cls.build_num = 0;
cls.initialized = true;
cl.maxclients = 1; // allow to drawing player in menu

View File

@@ -118,8 +118,7 @@ static void NetGraph_InitColors( void )
byte mincolor[2][3];
byte maxcolor[2][3];
float dc[2][3];
int i, hfrac;
float f;
int hfrac;
mincolor[0][0] = 63;
mincolor[0][1] = 0;
@@ -137,7 +136,7 @@ static void NetGraph_InitColors( void )
maxcolor[1][1] = 0;
maxcolor[1][2] = 0;
for( i = 0; i < 3; i++ )
for( int i = 0; i < 3; i++ )
{
dc[0][i] = (float)(maxcolor[0][i] - mincolor[0][i]);
dc[1][i] = (float)(maxcolor[1][i] - mincolor[1][i]);
@@ -145,8 +144,10 @@ static void NetGraph_InitColors( void )
hfrac = NETGRAPH_LERP_HEIGHT / 3;
for( i = 0; i < NETGRAPH_LERP_HEIGHT; i++ )
for( int i = 0; i < NETGRAPH_LERP_HEIGHT; i++ )
{
float f;
if( i < hfrac )
{
f = (float)i / (float)hfrac;
@@ -170,10 +171,9 @@ get frame data info, like chokes, packet losses, also update graph, packet and c
*/
static void NetGraph_GetFrameData( float *latency, int *latency_count )
{
int i, choke_count = 0, loss_count = 0;
int choke_count = 0, loss_count = 0;
double newtime = Platform_DoubleTime();
static double nexttime = 0;
float loss, choke;
*latency_count = 0;
*latency = 0.0f;
@@ -185,7 +185,7 @@ static void NetGraph_GetFrameData( float *latency, int *latency_count )
nexttime = newtime + 0.05;
}
for( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ )
for( int i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ )
{
frame_t *f = cl.frames + ( i & CL_UPDATE_MASK );
struct packet_latency_t *p = netstat_packet_latency + ( i & NET_TIMINGS_MASK );
@@ -228,7 +228,7 @@ static void NetGraph_GetFrameData( float *latency, int *latency_count )
if( maxmsgbytes > 1000 )
maxmsgbytes = 1000;
for( i = cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.outgoing_sequence; i++ )
for( int i = cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.outgoing_sequence; i++ )
{
netstat_cmdinfo[i & NET_TIMINGS_MASK].cmd_lerp = cl.commands[i & CL_UPDATE_MASK].frame_lerp;
netstat_cmdinfo[i & NET_TIMINGS_MASK].sent = cl.commands[i & CL_UPDATE_MASK].heldback ? false : true;
@@ -236,11 +236,11 @@ static void NetGraph_GetFrameData( float *latency, int *latency_count )
}
// packet loss
loss = 100.0f * (float)loss_count / CL_UPDATE_BACKUP;
float loss = 100.0f * (float)loss_count / CL_UPDATE_BACKUP;
packet_loss = PACKETLOSS_AVG_FRAC * packet_loss + ( 1.0f - PACKETLOSS_AVG_FRAC ) * loss;
// packet choke
choke = 100.0f * (float)choke_count / CL_UPDATE_BACKUP;
float choke = 100.0f * (float)choke_count / CL_UPDATE_BACKUP;
packet_choke = PACKETCHOKE_AVG_FRAC * packet_choke + ( 1.0f - PACKETCHOKE_AVG_FRAC ) * choke;
}
@@ -252,14 +252,13 @@ NetGraph_DrawTimes
*/
static void NetGraph_DrawTimes( wrect_t rect, int x, int w )
{
int i, j, extrap_point = NETGRAPH_LERP_HEIGHT / 3, a, h;
rgba_t colors = { 0.9 * 255, 0.9 * 255, 0.7 * 255, 255 };
int extrap_point = NETGRAPH_LERP_HEIGHT / 3;
wrect_t fill;
for( a = 0; a < w; a++ )
for( int a = 0; a < w; a++ )
{
i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK;
h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight.value * 0.7f);
int i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK;
int h = Q_min(( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT, net_graphheight.value * 0.7f);
fill.left = x + w - a - 1;
fill.right = fill.bottom = 1;
@@ -278,7 +277,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w )
start = (h - 1);
}
for( j = start; j < h; j++ )
for( int j = start; j < h; j++ )
{
int color = NETGRAPH_NET_COLORS + j + extrap_point;
color = Q_min( color, ARRAYSIZE( netcolors ) - 1 );
@@ -297,7 +296,7 @@ static void NetGraph_DrawTimes( wrect_t rect, int x, int w )
if( !net_graphsolid.value )
h = 1;
for( j = 0; j < h; j++ )
for( int j = 0; j < h; j++ )
{
int color = NETGRAPH_NET_COLORS + j + oldh;
color = Q_min( color, ARRAYSIZE( netcolors ) - 1 );
@@ -496,15 +495,16 @@ NetGraph_DrawDataUsage
*/
static void NetGraph_DrawDataUsage( int x, int y, int w, int graphtype )
{
int a, i, h, lastvalidh = 0, ping;
int lastvalidh = 0;
int pingheight = net_graphheight.value - NETGRAPH_LERP_HEIGHT - 2;
wrect_t fill = { 0 };
byte color[4];
for( a = 0; a < w; a++ )
for( int a = 0; a < w; a++ )
{
i = (cls.netchan.incoming_sequence - a) & NET_TIMINGS_MASK;
h = netstat_packet_latency[i].latency;
int ping;
int i = (cls.netchan.incoming_sequence - a) & NET_TIMINGS_MASK;
int h = netstat_packet_latency[i].latency;
NetGraph_ColorForHeight( &netstat_packet_latency[i], color, &ping );

View File

@@ -43,7 +43,6 @@ return true if equal
*/
static qboolean CL_CmpStudioTextures( int numtexs, mstudiotexture_t *p1, remap_info_t *remap )
{
int i;
mstudiotexture_t *p2;
if( !p1 ) // no textures
@@ -60,7 +59,7 @@ static qboolean CL_CmpStudioTextures( int numtexs, mstudiotexture_t *p1, remap_i
p2 = remap->ptexture;
for( i = 0; i < numtexs; i++, p1++, p2++ )
for( int i = 0; i < numtexs; i++, p1++, p2++ )
{
if( p1->flags & STUDIO_NF_COLORMAP )
continue; // colormaps always has different indexes
@@ -116,18 +115,17 @@ static void CL_DuplicateTexture( cl_entity_t *entity, model_t *model, mstudiotex
const char *name;
texture_t *tx = NULL;
char texname[128];
int i, index;
size_t size;
byte paletteBackup[768];
byte *raw, *pal;
int index = ptexture->index;
// save off the real texture index
index = ptexture->index;
name = ref.dllFuncs.GL_TextureName( index );
Q_snprintf( texname, sizeof( texname ), "#%i_%s", entity->curstate.number, name + 1 );
// search for pixels
for( i = 0; i < model->numtextures; i++ )
for( int i = 0; i < model->numtextures; i++ )
{
tx = model->textures[i];
if( tx->gl_texturenum == index )
@@ -161,7 +159,7 @@ static void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptext
texture_t *tx = NULL;
char texname[128], name[128], mdlname[128];
const char *origtexname;
int i, index;
int index;
size_t size;
byte paletteBackup[768];
byte *raw, *pal;
@@ -180,7 +178,7 @@ static void CL_UpdateStudioTexture( cl_entity_t *entity, mstudiotexture_t *ptext
return; // couldn't find texture
// search for pixels
for( i = 0; i < entity->model->numtextures; i++ )
for( int i = 0; i < entity->model->numtextures; i++ )
{
tx = entity->model->textures[i];
if( tx->gl_texturenum == index )
@@ -259,12 +257,10 @@ Release remap info per entity
*/
static void CL_FreeRemapInfo( remap_info_t *info )
{
int i;
Assert( info != NULL );
// release all colormap texture copies
for( i = 0; i < info->numtextures; i++ )
for( int i = 0; i < info->numtextures; i++ )
{
if( info->ptexture != NULL )
{
@@ -438,11 +434,9 @@ Release all remap infos
*/
void CL_ClearAllRemaps( void )
{
int i;
if( clgame.remap_info )
{
for( i = 0; i < clgame.maxRemapInfos; i++ )
for( int i = 0; i < clgame.maxRemapInfos; i++ )
{
if( clgame.remap_info[i] )
CL_FreeRemapInfo( clgame.remap_info[i] );

View File

@@ -50,9 +50,7 @@ SCR_DrawFPS
*/
void SCR_DrawFPS( int height )
{
float calc;
rgba_t color;
double newtime;
static double nexttime = 0, lasttime = 0;
static double framerate = 0;
static int framecount = 0;
@@ -73,7 +71,7 @@ void SCR_DrawFPS( int height )
default: return;
}
newtime = Platform_DoubleTime();
double newtime = Platform_DoubleTime();
if( newtime >= nexttime )
{
framerate = framecount / (newtime - lasttime);
@@ -82,7 +80,7 @@ void SCR_DrawFPS( int height )
framecount = 0;
}
calc = framerate;
float calc = framerate;
framecount++;
if( calc < 1.0f )
@@ -120,15 +118,13 @@ Draw local player position, angles and velocity
void SCR_DrawPos( void )
{
static char msg[MAX_SYSPATH];
float speed;
cl_entity_t *ent;
rgba_t color;
if( cls.state != ca_active || !cl_showpos.value || cl.background )
return;
ent = CL_GetLocalPlayer();
speed = VectorLength( cl.simvel );
cl_entity_t *ent = CL_GetLocalPlayer();
float speed = VectorLength( cl.simvel );
Q_snprintf( msg, MAX_SYSPATH,
"pos: %.2f %.2f %.2f\n"
@@ -153,23 +149,22 @@ SCR_DrawEnts
void SCR_DrawEnts( void )
{
rgba_t color = { 255, 255, 255, 255 };
int i;
if( cls.state != ca_active || !cl_showents.value || ( cl.maxclients > 1 && !cls.demoplayback ))
return;
// this probably better hook CL_AddVisibleEntities
// as entities might get added by client.dll
for( i = 0; i < clgame.maxEntities; i++ )
for( int i = 0; i < clgame.maxEntities; i++ )
{
const cl_entity_t *ent = &clgame.entities[i];
string msg;
vec3_t screen, pos;
vec3_t screen;
if( ent->curstate.messagenum != cl.parsecount )
continue;
VectorCopy( ent->origin, pos );
vec3_t pos = Vec3( ent->origin );
if( ent->model != NULL )
{
@@ -241,12 +236,12 @@ void SCR_DrawUserCmd( void )
};
cl_font_t *font = Con_GetCurFont();
string msg;
int i, ypos = 100;
int ypos = 100;
if( cls.state != ca_active || !cl_showcmd.value )
return;
for( i = 0; i < ARRAYSIZE( buttons ); i++ )
for( int i = 0; i < ARRAYSIZE( buttons ); i++ )
{
rgba_t rgba;
@@ -280,7 +275,6 @@ same as r_speeds but for network channel
void SCR_NetSpeeds( void )
{
static char msg[MAX_SYSPATH];
int x, y;
float time = cl.mtime[0];
static int min_svfps = 100;
static int max_svfps = 0;
@@ -326,8 +320,8 @@ void SCR_NetSpeeds( void )
Q_memprint( cls.netchan.total_sended )
);
x = refState.width - 320 * font->scale;
y = 384;
int x = refState.width - 320 * font->scale;
int y = 384;
MakeRGBA( color, 255, 255, 255, 255 );
CL_DrawString( x, y, msg, color, font, FONT_DRAW_RESETCOLORONLF );
@@ -347,12 +341,11 @@ void SCR_RSpeeds( void )
if( ref.dllFuncs.R_SpeedsMessage( msg, sizeof( msg )))
{
int x, y;
rgba_t color;
cl_font_t *font = Con_GetCurFont();
x = refState.width - 340 * font->scale;
y = 64;
int x = refState.width - 340 * font->scale;
int y = 64;
MakeRGBA( color, 255, 255, 255, 255 );
CL_DrawString( x, y, msg, color, font, FONT_DRAW_RESETCOLORONLF );
@@ -386,11 +379,10 @@ static void VID_WriteOverviewScript( void )
{
ref_overview_t *ov = &clgame.overView;
string filename;
file_t *f;
Q_snprintf( filename, sizeof( filename ), "overviews/%s.txt", clgame.mapname );
f = FS_Open( filename, "w", false );
file_t *f = FS_Open( filename, "w", false );
if( !f )
{
Con_Printf( S_ERROR "%s: can't open %s for write\n", __func__, filename );
@@ -586,7 +578,6 @@ void SCR_TileClear( void )
{
int i, top, bottom, left, right, texnum;
dirty_t clear;
float tw, th;
if( likely( scr_viewsize.value >= 120 ))
return; // full screen rendering
@@ -634,8 +625,8 @@ void SCR_TileClear( void )
left = clgame.viewport[0];
right = left + clgame.viewport[2] - 1;
tw = REF_GET_PARM( PARM_TEX_SRC_WIDTH, texnum );
th = REF_GET_PARM( PARM_TEX_SRC_HEIGHT, texnum );
float tw = REF_GET_PARM( PARM_TEX_SRC_WIDTH, texnum );
float th = REF_GET_PARM( PARM_TEX_SRC_HEIGHT, texnum );
if( clear.y1 < top )
{
@@ -749,11 +740,9 @@ void SCR_LoadCreditsFont( void )
// copy font size for client.dll
if( success )
{
int i;
clgame.scrInfo.iCharHeight = cls.creditsFont.charHeight;
for( i = 0; i < ARRAYSIZE( cls.creditsFont.charWidths ); i++ )
for( int i = 0; i < ARRAYSIZE( cls.creditsFont.charWidths ); i++ )
clgame.scrInfo.charWidths[i] = cls.creditsFont.charWidths[i];
}
else Con_DPrintf( S_ERROR "failed to load HUD font\n" );

View File

@@ -27,10 +27,10 @@ GNU General Public License for more details.
static void CL_AdjustSprayDimensions( int *width, int *height )
{
float aspect = (float)( *width ) / (float)( *height );
int h, w;
for( h = (( *height ) / 16 ) * 16; h >= 16; h -= 16 )
for( int h = (( *height ) / 16 ) * 16; h >= 16; h -= 16 )
{
w = ((int)( h * aspect ) / 16 ) * 16;
int w = ((int)( h * aspect ) / 16 ) * 16;
if( w < 16 || w > *width )
continue;
@@ -107,7 +107,6 @@ qboolean CL_ConvertImageToWAD3( const char *filename )
{
qboolean is_indexed_img;
int width = 0, height = 0;
int i;
byte palette[SPRAY_PALETTE_BYTES];
byte *indexed = NULL;
rgbdata_t *image = NULL;
@@ -122,7 +121,7 @@ qboolean CL_ConvertImageToWAD3( const char *filename )
if( is_indexed_img )
{
// copy bmp palette from rgba to rgb
for( i = 0; i < 256; ++i )
for( int i = 0; i < 256; ++i )
{
palette[i * 3 + 0] = image->palette[i * 4 + 0]; // R
palette[i * 3 + 1] = image->palette[i * 4 + 1]; // G
@@ -141,7 +140,7 @@ qboolean CL_ConvertImageToWAD3( const char *filename )
// set index 255 for transparent pixels in rgba images
if( image->type == PF_RGBA_32 )
{
for( i = 0; i < width * height; ++i )
for( int i = 0; i < width * height; ++i )
{
if( image->buffer[i * 4 + 3] <= SPRAY_ALPHA_THRESHOLD )
quant->buffer[i] = 255;

352
engine/client/cl_sprite.c Normal file
View File

@@ -0,0 +1,352 @@
/*
cl_sprite.c - sprite loading
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 "sprite.h"
#include "ref_common.h"
static char sprite_name[MAX_QPATH];
static char group_suffix[8];
static uint r_texFlags = 0;
static int sprite_version;
/*
================
R_GetSpriteFrame
assume pModel is valid
================
*/
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
{
msprite_t *psprite;
mspriteframe_t *pspriteframe = NULL;
if( !pModel )
return NULL;
psprite = pModel->cache.data;
if( frame < 0 )
{
frame = 0;
}
else if( frame >= psprite->numframes )
{
if( frame > psprite->numframes )
Con_Printf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, pModel->name );
frame = psprite->numframes - 1;
}
if( psprite->frames[frame].type == SPR_SINGLE )
{
pspriteframe = psprite->frames[frame].frameptr;
}
else if( psprite->frames[frame].type == SPR_GROUP )
{
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
float *pintervals = pspritegroup->intervals;
int numframes = pspritegroup->numframes;
float fullinterval = pintervals[numframes - 1];
// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
// are positive, so we don't have to worry about division by zero
float targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval;
int i;
for( i = 0; i < ( numframes - 1 ); i++ )
{
if( pintervals[i] > targettime )
break;
}
pspriteframe = pspritegroup->frames[i];
}
else if( psprite->frames[frame].type == FRAME_ANGLED )
{
int angleframe = (int)( Q_rint(( refState.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;
// e.g. doom-style sprite monsters
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
pspriteframe = pspritegroup->frames[angleframe];
}
return pspriteframe;
}
void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )
{
mspriteframe_t *pFrame;
if( !pSprite || pSprite->type != mod_sprite )
return;
pFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f );
if( frameWidth )
*frameWidth = pFrame->width;
if( frameHeight )
*frameHeight = pFrame->height;
if( numFrames )
*numFrames = pSprite->numframes;
}
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )
{
if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )
return 0;
return R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum;
}
static void Mod_SpriteTextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath )
{
if( host_allow_materials.value != 2.0f )
return;
if( gl_texturenum > 0 )
Con_Printf( "Looking for %s tex replacement..." S_GREEN "OK (%s)\n", modelname, foundpath );
else if( gl_texturenum < 0 )
Con_Printf( "Looking for %s tex replacement..." S_YELLOW "MISS (%s)\n", modelname, foundpath );
else
Con_Printf( "Looking for %s tex replacement..." S_RED "FAIL (%s)\n", modelname, foundpath );
}
static qboolean Mod_SpriteSearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... )
{
va_list ap;
int ret;
va_start( ap, fmt );
ret = Q_vsnprintf( out, size, fmt, ap );
va_end( ap );
if( ret < 0 )
{
Mod_SpriteTextureReplacementReport( modelname, -1, "overflow" );
return false;
}
if( FS_FileExists( out, false ))
return true;
Mod_SpriteTextureReplacementReport( modelname, -1, out );
return false;
}
static const byte *Mod_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num )
{
dspriteframe_t pinframe;
mspriteframe_t *pspriteframe;
int gl_texturenum = 0;
char texname[128];
int bytes = 1;
memcpy( &pinframe, pin, sizeof( dspriteframe_t ));
if( sprite_version == SPRITE_VERSION_32 )
bytes = 4;
// build unique frame name
if( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite
{
Q_snprintf( texname, sizeof( texname ), "#HUD/%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 );
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );
}
else
{
// partial HD-textures support
if( Mod_AllowMaterials( ))
{
if( Mod_SpriteSearchForTextureReplacement( texname, sizeof( texname ), sprite_name, "materials/%s/%s%i%i.tga", sprite_name, group_suffix, num / 10, num % 10 ))
{
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, NULL, 0, r_texFlags );
Mod_SpriteTextureReplacementReport( sprite_name, gl_texturenum, texname );
}
}
if( gl_texturenum == 0 )
{
Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 );
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );
}
}
// setup frame description
pspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t ));
pspriteframe->width = pinframe.width;
pspriteframe->height = pinframe.height;
pspriteframe->up = pinframe.origin[1];
pspriteframe->left = pinframe.origin[0];
pspriteframe->down = pinframe.origin[1] - pinframe.height;
pspriteframe->right = pinframe.width + pinframe.origin[0];
pspriteframe->gl_texturenum = gl_texturenum;
*ppframe = pspriteframe;
return (const byte *)pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes;
}
static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )
{
const dspritegroup_t *pingroup = (const dspritegroup_t *)pin;
mspritegroup_t *pspritegroup;
const dspriteinterval_t *pin_intervals;
float *poutintervals;
int groupsize, numframes = pingroup->numframes;
const void *ptemp;
groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] );
pspritegroup = Mem_Calloc( mod->mempool, groupsize );
pspritegroup->numframes = numframes;
*ppframe = (mspriteframe_t *)pspritegroup;
pin_intervals = (const dspriteinterval_t *)( pingroup + 1 );
poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));
pspritegroup->intervals = poutintervals;
for( int i = 0; i < numframes; i++ )
{
*poutintervals = pin_intervals->interval;
if( *poutintervals <= 0.0f )
*poutintervals = 1.0f; // set error value
poutintervals++;
pin_intervals++;
}
ptemp = (const void *)pin_intervals;
for( int i = 0; i < numframes; i++ )
{
ptemp = Mod_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );
}
return ptemp;
}
void Mod_SpriteLoadTextures( model_t *mod, const void *buffer )
{
const dsprite_t *pin = buffer;
const short *numi = NULL;
const byte *pframetype;
msprite_t *psprite = mod->cache.data;
int i;
if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )
numi = NULL;
else if( pin->version == SPRITE_VERSION_HL )
numi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t ));
r_texFlags = mod->numtexinfo;
sprite_version = pin->version;
Q_strncpy( sprite_name, mod->name, sizeof( sprite_name ));
COM_StripExtension( sprite_name );
if( numi == NULL )
{
rgbdata_t *pal;
pal = FS_LoadImage( "#id.pal", (byte *)&i, 768 );
pframetype = (const byte *)buffer + sizeof( dsprite_q1_t );
FS_FreeImage( pal );
}
else if( *numi <= 256 )
{
const byte *src = (const byte *)( numi + 1 );
rgbdata_t *pal;
size_t pal_bytes = *numi * 3;
// install palette
switch( psprite->texFormat )
{
case SPR_INDEXALPHA:
pal = FS_LoadImage( "#gradient.pal", src, pal_bytes );
break;
case SPR_ALPHTEST:
pal = FS_LoadImage( "#masked.pal", src, pal_bytes );
break;
default:
pal = FS_LoadImage( "#texgamma.pal", src, pal_bytes );
break;
}
pframetype = (const byte *)( src + pal_bytes );
FS_FreeImage( pal );
}
else
{
Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be less or equal than 256)\n", mod->name, *numi );
return;
}
if( mod->numframes < 1 )
return;
for( i = 0; i < mod->numframes; i++ )
{
frametype_t frametype;
dframetype_t dframetype;
memcpy( &dframetype, pframetype, sizeof( dframetype ));
frametype = dframetype.type;
psprite->frames[i].type = (spriteframetype_t)frametype;
switch( frametype )
{
case FRAME_SINGLE:
Q_strncpy( group_suffix, "frame", sizeof( group_suffix ));
pframetype = Mod_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
break;
case FRAME_GROUP:
Q_strncpy( group_suffix, "group", sizeof( group_suffix ));
pframetype = Mod_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
break;
case FRAME_ANGLED:
Q_strncpy( group_suffix, "angle", sizeof( group_suffix ));
pframetype = Mod_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
break;
}
if( pframetype == NULL )
break;
}
}
void Mod_SpriteUnloadTextures( void *data )
{
msprite_t *psprite = data;
if( !data )
return;
// release all textures
for( int i = 0; i < psprite->numframes; i++ )
{
if( !psprite->frames[i].frameptr )
continue;
if( psprite->frames[i].type == SPR_SINGLE )
{
ref.dllFuncs.GL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum );
}
else
{
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;
for( int j = 0; j < pspritegroup->numframes; j++ )
{
if( pspritegroup->frames[j] )
ref.dllFuncs.GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );
}
}
}
}

View File

@@ -16,6 +16,8 @@ GNU General Public License for more details.
#include <inttypes.h>
#include "common.h"
#include "client.h"
#include "net_ws.h"
#include "net_ws_private.h"
// What is a broker?
// From Wikipedia, the free encyclopedia:
@@ -31,16 +33,53 @@ GNU General Public License for more details.
// network, preferrably) on a machine that has Steam client installed, the
// engine can communicate with it, acquiring needed information to log-in into
// Steam protected multiplayer servers.
// Protocol constants
#define SBRK_FRAME_HEADER "SBRK"
#define SBRK_FRAME_HEADER_SIZE (sizeof(SBRK_FRAME_HEADER) - 1)
#define SBRK_FRAME_LENGTH_SIZE 2
#define SBRK_RESPONSE_HEADER "sb_connect\n"
#define SBRK_RESPONSE_HEADER_SIZE (sizeof(SBRK_RESPONSE_HEADER) - 1)
#define SBRK_MAX_FRAME_SIZE 4096
#define SBRK_CONNECT_TIMEOUT 10.0
#define SBRK_CONNECT_RETRY_DELAY 5.0
#define SBRK_TICKET_SIZE_MAX 2048
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
static struct
typedef enum
{
SBRK_STATE_IDLE,
SBRK_STATE_CONNECTING,
SBRK_STATE_CONNECTED,
SBRK_STATE_GAMESHUTDOWN
} sbrk_state_t;
typedef struct
{
netadr_t adr;
int socket;
sbrk_state_t state;
int challenge;
netadr_t serveradr;
qboolean announced;
qboolean addr_initialized;
} broker;
double connection_timeout;
double idle_cycle_timeout;
uint8_t rx_buffer[SBRK_MAX_FRAME_SIZE + 64];
uint8_t tx_buffer[SBRK_MAX_FRAME_SIZE + 64];
uint32_t rx_buffer_pos;
uint32_t tx_buffer_pos;
} steam_broker_t;
static steam_broker_t broker;
static void SteamBroker_SetState( sbrk_state_t new_state )
{
if( broker.state != new_state )
{
// we also may logging transitions if needed
broker.state = new_state;
}
}
static qboolean SteamBroker_UpdateBrokerAddress( void )
{
@@ -52,28 +91,385 @@ static qboolean SteamBroker_UpdateBrokerAddress( void )
return true;
}
static void SteamBroker_CloseSocket( void )
{
if( NET_IsSocketValid( broker.socket ))
{
closesocket( broker.socket );
broker.socket = INVALID_SOCKET;
}
broker.rx_buffer_pos = 0;
broker.tx_buffer_pos = 0;
}
static void SteamBroker_Disconnect( void )
{
SteamBroker_CloseSocket();
SteamBroker_SetState( SBRK_STATE_IDLE );
}
static qboolean SteamBroker_ConnectImpl( void )
{
int addr_family;
struct sockaddr_storage addr = { 0 };
if( NET_NetadrType( &broker.adr ) == NA_IP )
{
addr_family = AF_INET;
}
else if( NET_NetadrType( &broker.adr ) == NA_IP6 )
{
addr_family = AF_INET6;
}
else
{
Con_Printf( S_ERROR "%s: unsupported broker address type for %s\n", __func__, cl_steam_broker_addr.string );
return false;
}
broker.socket = socket( addr_family, SOCK_STREAM, IPPROTO_TCP );
if( !NET_IsSocketValid( broker.socket ))
{
Con_Printf( S_ERROR "%s: failed to create socket\n", __func__ );
return false;
}
if( !NET_MakeSocketNonBlocking( broker.socket ))
{
Con_Printf( S_ERROR "%s: failed to set non-blocking mode, error %s\n", __func__, NET_ErrorString( ));
SteamBroker_CloseSocket();
return false;
}
NET_NetadrToSockadr( &broker.adr, &addr );
int result = connect( broker.socket, (struct sockaddr *)&addr, NET_SockAddrLen( &addr ));
if( NET_IsSocketError( result ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY && err != WSAEINPROGRESS )
{
Con_Printf( S_ERROR "%s: failed to connect to broker at %s with error %s\n", __func__, cl_steam_broker_addr.string, NET_ErrorString( ));
SteamBroker_CloseSocket();
return false;
}
}
broker.connection_timeout = Platform_DoubleTime() + SBRK_CONNECT_TIMEOUT;
SteamBroker_SetState( SBRK_STATE_CONNECTING );
return true;
}
static qboolean SteamBroker_SendFrame( const char *payload, size_t payload_size )
{
if( payload_size > SBRK_MAX_FRAME_SIZE )
{
Con_Printf( S_WARN "%s: payload too large (%zu > %u)\n", __func__, payload_size, SBRK_MAX_FRAME_SIZE );
return false;
}
uint8_t frame[SBRK_MAX_FRAME_SIZE + 6];
sizebuf_t sb;
MSG_Init( &sb, "SteamBroker_SendFrame", frame, sizeof( frame ));
MSG_WriteBytes( &sb, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE );
MSG_WriteShort( &sb, payload_size );
MSG_WriteBytes( &sb, payload, payload_size );
size_t frame_size = MSG_GetRealBytesWritten( &sb );
int sent = send( broker.socket, (const char *)frame, frame_size, 0 );
if( NET_IsSocketError( sent ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
Con_Printf( S_ERROR "%s: send error %s\n", __func__, NET_ErrorString( ));
SteamBroker_Disconnect( );
return false;
}
sent = 0;
}
// bufferize unsent data for deferred sending
size_t unsent = frame_size - sent;
if( unsent > 0 )
{
size_t available = sizeof( broker.tx_buffer ) - broker.tx_buffer_pos;
if( available < unsent )
{
Con_Printf( S_ERROR "%s: transmit buffer overflow (%zu > %zu)\n", __func__, unsent, available );
SteamBroker_Disconnect( );
return false;
}
memcpy( broker.tx_buffer + broker.tx_buffer_pos, frame + sent, unsent );
broker.tx_buffer_pos += unsent;
}
return true;
}
static qboolean SteamBroker_ProcessFrame( void )
{
if( broker.rx_buffer_pos < SBRK_FRAME_HEADER_SIZE + SBRK_FRAME_LENGTH_SIZE )
return false;
sizebuf_t sb;
MSG_Init( &sb, "SteamBroker_ProcessFrame", broker.rx_buffer, broker.rx_buffer_pos );
// verify frame header
char header[SBRK_FRAME_HEADER_SIZE];
if( !MSG_ReadBytes( &sb, header, SBRK_FRAME_HEADER_SIZE ))
return false;
if( memcmp( header, SBRK_FRAME_HEADER, SBRK_FRAME_HEADER_SIZE ) != 0 )
{
Con_Printf( S_ERROR "%s: invalid frame header\n", __func__ );
SteamBroker_Disconnect( );
return false;
}
uint16_t payload_size = MSG_ReadShort( &sb );
uint32_t frame_size = SBRK_FRAME_HEADER_SIZE + SBRK_FRAME_LENGTH_SIZE + payload_size;
if( MSG_GetNumBytesLeft( &sb ) < payload_size )
return false; // need more data
char response_header[SBRK_RESPONSE_HEADER_SIZE];
if( MSG_ReadBytes( &sb, response_header, SBRK_RESPONSE_HEADER_SIZE ))
{
if( memcmp( response_header, SBRK_RESPONSE_HEADER, SBRK_RESPONSE_HEADER_SIZE ) == 0 )
{
int32_t challenge = MSG_ReadLong( &sb );
if( broker.challenge != challenge )
{
Con_Printf( S_ERROR "%s: challenge mismatch\n", __func__ );
}
else
{
uint64_t steam_id;
MSG_ReadBytes( &sb, &steam_id, sizeof( steam_id ));
uint32_t ticket_size = MSG_ReadDword( &sb );
uint8_t ticket_data[SBRK_TICKET_SIZE_MAX];
if( ticket_size > SBRK_TICKET_SIZE_MAX )
{
Con_Printf( S_ERROR "%s: ticket size exceeds limit (%u)\n", __func__, ticket_size );
}
else if( MSG_ReadBytes( &sb, ticket_data, ticket_size ))
{
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, steam_id, ticket_data[0], ticket_data[1], ticket_data[2], ticket_data[3] );
memcpy( cls.steamid, &steam_id, sizeof( cls.steamid ));
CL_SendGoldSrcConnectPacket( broker.serveradr, broker.challenge, ticket_data, ticket_size );
cls.broker_wait = false;
}
else
{
Con_Printf( S_ERROR "%s: failed to read ticket data\n", __func__ );
}
}
}
}
// remove processed frame from buffer
memmove( broker.rx_buffer, broker.rx_buffer + frame_size, broker.rx_buffer_pos - frame_size );
broker.rx_buffer_pos -= frame_size;
return true;
}
static void SteamBroker_HandleDataTx( void )
{
if( broker.tx_buffer_pos == 0 )
return;
int sent = send( broker.socket, (const char *)broker.tx_buffer, broker.tx_buffer_pos, 0 );
if( NET_IsSocketError( sent ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
Con_Printf( S_ERROR "%s: send error %s\n", __func__, NET_ErrorString( ));
SteamBroker_Disconnect( );
}
return;
}
if( sent > 0 )
{
// remove sent data from buffer
memmove( broker.tx_buffer, broker.tx_buffer + sent, broker.tx_buffer_pos - sent );
broker.tx_buffer_pos -= sent;
}
}
static void SteamBroker_HandleDataRx( void )
{
int available = sizeof( broker.rx_buffer ) - broker.rx_buffer_pos;
if( available <= 0 )
{
Con_Printf( S_ERROR "%s: receive buffer overflow\n", __func__ );
SteamBroker_Disconnect( );
return;
}
int received = recv( broker.socket, (char *)broker.rx_buffer + broker.rx_buffer_pos, available, 0 );
if( NET_IsSocketError( received ))
{
int err = WSAGetLastError();
if( err != WSAEWOULDBLOCK && err != WSAEALREADY )
{
Con_Printf( S_ERROR "%s: recv error %s\n", __func__, NET_ErrorString( ));
SteamBroker_Disconnect( );
}
return;
}
if( received == 0 )
{
Con_Printf( S_NOTE "%s: connection closed by broker\n", __func__ );
SteamBroker_Disconnect( );
return;
}
broker.rx_buffer_pos += received;
while( SteamBroker_ProcessFrame( ));
}
static void SteamBroker_UpdateIdle( void )
{
if( broker.idle_cycle_timeout < Platform_DoubleTime( ))
{
if( SteamBroker_UpdateBrokerAddress( ))
{
SteamBroker_ConnectImpl( );
}
else
{
Con_Printf( S_ERROR "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
}
broker.idle_cycle_timeout = Platform_DoubleTime() + SBRK_CONNECT_RETRY_DELAY;
}
}
static void SteamBroker_AnnounceGameStart( const char *gamedir )
{
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
if( broker.state != SBRK_STATE_CONNECTED )
return;
// sb_gamedir <gamedir>
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_gamedir %s", gamedir );
if( len > 0 )
SteamBroker_SendFrame( buf, len );
}
static void SteamBroker_AnnounceGameShutdown( void )
{
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
if( broker.state != SBRK_STATE_CONNECTED )
return;
SteamBroker_SendFrame( "sb_terminate", sizeof( "sb_terminate" ) - 1 );
}
static void SteamBroker_UpdateConnecting( void )
{
if( Platform_DoubleTime() > broker.connection_timeout )
{
Con_Printf( S_WARN "%s: connection to %s timed out\n", __func__, cl_steam_broker_addr.string );
SteamBroker_Disconnect();
return;
}
fd_set writefds;
FD_ZERO( &writefds );
FD_SET( broker.socket, &writefds );
struct timeval tv = { 0 };
#if XASH_WIN32
int select_result = select( 0, NULL, &writefds, NULL, &tv );
#else
int select_result = select( broker.socket + 1, NULL, &writefds, NULL, &tv );
#endif
if( select_result == SOCKET_ERROR )
{
Con_Printf( S_ERROR "%s: select() failed\n", __func__ );
SteamBroker_Disconnect();
return;
}
if( FD_ISSET( broker.socket, &writefds ))
{
// socket is writable - connection established or failed
int err = 0;
socklen_t err_len = sizeof( err );
if( NET_IsSocketError( getsockopt( broker.socket, SOL_SOCKET, SO_ERROR, (char *)&err, &err_len )))
{
Con_Printf( S_ERROR "%s: getsockopt() failed\n", __func__ );
SteamBroker_Disconnect();
return;
}
else if( err != 0 )
{
Con_Printf( S_ERROR "%s: connection failed with error %d\n", __func__, err );
SteamBroker_Disconnect();
return;
}
else
{
broker.connection_timeout = 0;
Con_Printf( S_NOTE "%s: connected to broker at %s\n", __func__, cl_steam_broker_addr.string );
SteamBroker_SetState( SBRK_STATE_CONNECTED );
SteamBroker_AnnounceGameStart( GI->gamefolder );
}
}
}
static void SteamBroker_UpdateConnected( void )
{
SteamBroker_HandleDataTx( );
SteamBroker_HandleDataRx( );
}
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge )
{
// only ipv4 supported
if( NET_NetadrType( &serveradr ) != NA_IP )
return false;
if( broker.state != SBRK_STATE_CONNECTED )
{
Con_Printf( S_WARN "%s: broker not connected\n", __func__ );
return false;
}
broker.challenge = challenge;
broker.serveradr = serveradr;
// sb_connect <ip:port> <server's steam id> <secure> <challenge>
// the message calls
// sb_connect <ip:port> <server_steamid> <secure> <challenge>
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %s %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? "true" : "false", broker.challenge );
int len = Q_snprintf( buf, sizeof( buf ), "sb_connect %s %"PRIu64" %d %d", NET_AdrToString( serveradr ), cls.server_steamid, cls.vac2_secure ? 1 : 0, challenge );
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
if( !SteamBroker_SendFrame( buf, len ))
return false;
return true;
}
void SteamBroker_TerminateGameConnection( void )
{
if( NET_NetadrType( &cls.serveradr ) != NA_IP )
if( broker.state != SBRK_STATE_CONNECTED )
return;
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
@@ -83,109 +479,52 @@ void SteamBroker_TerminateGameConnection( void )
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_disconnect %s %d", NET_AdrToString( cls.serveradr ), broker.challenge );
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
}
void SteamBroker_AnnounceGameStart( const char *gamedir )
{
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
NET_Config( true, true ); // initialize sockets to be able to send packets to broker
char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_gamedir %s", gamedir );
NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
}
void SteamBroker_AnnounceGameShutdown( netadr_t broker_addr )
{
NET_SendPacket( NS_CLIENT, sizeof( "sb_terminate" ) - 1, "sb_terminate", broker_addr );
SteamBroker_SendFrame( buf, len );
}
void SteamBroker_Frame( void )
{
if( FBitSet( cl_steam_broker_addr.flags | cl_ticket_generator.flags, FCVAR_CHANGED ))
{
ClearBits( cl_ticket_generator.flags, FCVAR_CHANGED );
ClearBits( cl_steam_broker_addr.flags, FCVAR_CHANGED );
if( broker.state != SBRK_STATE_IDLE )
{
SteamBroker_Disconnect();
}
// reinitialize address
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
}
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
qboolean restart = FBitSet( cl_steam_broker_addr.flags|cl_ticket_generator.flags, FCVAR_CHANGED );
if( !broker.addr_initialized )
// update state machine
switch( broker.state )
{
if( !SteamBroker_UpdateBrokerAddress( ))
{
Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
return;
}
else
{
broker.addr_initialized = true;
}
case SBRK_STATE_IDLE:
SteamBroker_UpdateIdle( );
break;
case SBRK_STATE_CONNECTING:
SteamBroker_UpdateConnecting( );
break;
case SBRK_STATE_CONNECTED:
SteamBroker_UpdateConnected( );
break;
case SBRK_STATE_GAMESHUTDOWN:
// do nothing, just wait for game shutdown
break;
}
if( restart )
{
broker.announced = false;
ClearBits( cl_ticket_generator.flags, FCVAR_CHANGED );
ClearBits( cl_steam_broker_addr.flags, FCVAR_CHANGED );
}
if( !broker.announced )
{
netadr_t previous_addr = broker.adr;
if( !SteamBroker_UpdateBrokerAddress( ))
{
Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
}
else
{
if( restart )
{
// terminate old steam broker if instance address has changed
SteamBroker_AnnounceGameShutdown( previous_addr );
}
SteamBroker_AnnounceGameStart( GI->gamefolder );
}
broker.announced = true;
}
}
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg )
{
// message format
// sb_connect\n<4 byte challenge><8 byte steamid><unsigned 4 byte len><len bytes ticket>
int challenge;
uint32_t len;
uint8_t ticket[2048]; // 2048 bytes according to SDK docs
if( !NET_CompareAdr( from, broker.adr ))
return;
challenge = MSG_ReadLong( msg );
if( broker.challenge != challenge ) // TODO: print error
return;
MSG_ReadBytes( msg, cls.steamid, sizeof( cls.steamid ));
len = MSG_ReadDword( msg );
if( len > sizeof( ticket )) // TODO: print error, proceed without ticket?
return;
MSG_ReadBytes( msg, ticket, len );
Con_Printf( "%s: SteamID: %"PRIu64", ticket: [%d, %d, %d, %d...]\n", __func__, *(uint64_t *)cls.steamid, ticket[0], ticket[1], ticket[2], ticket[3] );
CL_SendGoldSrcConnectPacket( broker.serveradr, challenge, ticket, len );
cls.broker_wait = false;
}
void SteamBroker_Init( void )
{
broker.announced = false;
broker.addr_initialized = false;
broker.state = SBRK_STATE_IDLE;
broker.socket = INVALID_SOCKET;
broker.rx_buffer_pos = 0;
broker.tx_buffer_pos = 0;
Cvar_RegisterVariable( &cl_steam_broker_addr );
NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
}
@@ -195,5 +534,6 @@ void SteamBroker_Shutdown( void )
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return;
SteamBroker_AnnounceGameShutdown( broker.adr );
SteamBroker_AnnounceGameShutdown( );
SteamBroker_SetState( SBRK_STATE_GAMESHUTDOWN );
}

View File

@@ -120,21 +120,20 @@ void CL_AddClientResources( void )
{
const char *snd;
char filepath[MAX_QPATH];
int i;
// don't request resources from localhost or in quake-compatibility mode
if( cl.maxclients <= 1 || Host_IsQuakeCompatible( ))
return;
// check sprites first
for( i = 0; i < ARRAYSIZE( cl_default_sprites ); i++ )
for( int i = 0; i < ARRAYSIZE( cl_default_sprites ); i++ )
{
if( !FS_FileExists( cl_default_sprites[i], false ))
CL_AddClientResource( cl_default_sprites[i], t_model );
}
// then check sounds
for( i = 0; ( snd = SoundList_Get( BouncePlayerShell, i )); i++ )
for( int i = 0; ( snd = SoundList_Get( BouncePlayerShell, i )); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd );
@@ -142,7 +141,7 @@ void CL_AddClientResources( void )
CL_AddClientResource( snd, t_sound );
}
for( i = 0; ( snd = SoundList_Get( BounceWeaponShell, i )); i++ )
for( int i = 0; ( snd = SoundList_Get( BounceWeaponShell, i )); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd );
@@ -150,7 +149,7 @@ void CL_AddClientResources( void )
CL_AddClientResource( snd, t_sound );
}
for( i = 0; ( snd = SoundList_Get( Explode, i )); i++ )
for( int i = 0; ( snd = SoundList_Get( Explode, i )); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd );
@@ -159,7 +158,7 @@ void CL_AddClientResources( void )
}
#if 0 // ric sounds was precached by server-side
for( i = 0; ( snd = SoundList_Get( Ricochet, i )); i++ )
for( int i = 0; ( snd = SoundList_Get( Ricochet, i )); i++ )
{
Q_snprintf( filepath, sizeof( filepath ), DEFAULT_SOUNDPATH "%s", snd );
@@ -177,11 +176,9 @@ CL_ClearTempEnts
*/
static void CL_ClearTempEnts( void )
{
int i;
if( !cl_tempents ) return;
for( i = 0; i < GI->max_tents - 1; i++ )
for( int i = 0; i < GI->max_tents - 1; i++ )
{
cl_tempents[i].next = &cl_tempents[i+1];
cl_tempents[i].entity.trivial_accept = INVALID_HANDLE;
@@ -270,14 +267,12 @@ play collide sound
*/
static void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp )
{
float fvol;
const char *soundname = NULL;
qboolean isshellcasing = false;
int zvel;
Assert( pTemp != NULL );
fvol = 0.8f;
float fvol = 0.8f;
switch( pTemp->hitSound )
{
@@ -315,7 +310,7 @@ static void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp )
if( !soundname )
return;
zvel = abs( pTemp->entity.baseline.origin[2] );
int zvel = abs( pTemp->entity.baseline.origin[2] );
// only play one out of every n
if( isshellcasing )
@@ -333,7 +328,6 @@ static void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp )
if( damp > 0.0f )
{
int pitch;
sound_t handle;
if( isshellcasing )
fvol *= Q_min( 1.0f, ((float)zvel) / 350.0f );
@@ -343,7 +337,7 @@ static void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp )
pitch = COM_RandomLong( 95, 105 );
else pitch = PITCH_NORM;
handle = S_RegisterSound( soundname );
sound_t handle = S_RegisterSound( soundname );
S_StartSound( pTemp->entity.origin, -(pTemp - cl_tempents), CHAN_BODY, handle, fvol, ATTN_NORM, pitch, SND_STOP_LOOPING );
}
}
@@ -571,15 +565,14 @@ void GAME_EXPORT R_FizzEffect( cl_entity_t *ent, int modelIndex, int density )
{
const float base_time = cl.time - 0.1f;
model_t *mod = CL_ModelHandle( modelIndex );
vec3_t volume, mins, maxs;
vec3_t volume;
vec2_t speed;
int i;
if( !ent || !ent->model || !modelIndex || !mod )
return;
VectorCopy( ent->model->mins, mins );
VectorCopy( ent->model->maxs, maxs );
vec3_t mins = Vec3( ent->model->mins );
vec3_t maxs = Vec3( ent->model->maxs );
if( ent->angles[1] != 0.0f )
{
@@ -592,12 +585,11 @@ void GAME_EXPORT R_FizzEffect( cl_entity_t *ent, int modelIndex, int density )
VectorSubtract( maxs, mins, volume );
for( i = 0; i <= density; i++ )
for( int i = 0; i <= density; i++ )
{
TEMPENTITY *tent;
vec3_t origin;
vec3_t origin = Vec3( mins );
VectorCopy( mins, origin );
origin[0] += COM_RandomLong( 0, (int)volume[0] - 1 );
origin[1] += COM_RandomLong( 0, (int)volume[1] - 1 );
@@ -628,16 +620,13 @@ Create bubbles
void GAME_EXPORT R_Bubbles( const vec3_t mins, const vec3_t maxs, float height, int modelIndex, int count, float speed )
{
TEMPENTITY *pTemp;
float sine, cosine;
float angle, zspeed;
vec3_t origin;
model_t *mod;
int i;
if(( mod = CL_ModelHandle( modelIndex )) == NULL )
return;
for ( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
origin[0] = COM_RandomLong( mins[0], maxs[0] );
origin[1] = COM_RandomLong( mins[1], maxs[1] );
@@ -649,10 +638,11 @@ void GAME_EXPORT R_Bubbles( const vec3_t mins, const vec3_t maxs, float height,
pTemp->x = origin[0];
pTemp->y = origin[1];
angle = COM_RandomFloat( -M_PI, M_PI );
float sine, cosine;
float angle = COM_RandomFloat( -M_PI, M_PI );
SinCos( angle, &sine, &cosine );
zspeed = COM_RandomLong( 80, 140 );
float zspeed = COM_RandomLong( 80, 140 );
VectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed );
pTemp->die = cl.time + ((height - (origin[2] - mins[2])) / zspeed) - 0.1f;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
@@ -674,18 +664,15 @@ Create bubble trail
void GAME_EXPORT R_BubbleTrail( const vec3_t start, const vec3_t end, float height, int modelIndex, int count, float speed )
{
TEMPENTITY *pTemp;
float sine, cosine, zspeed;
float dist, angle;
vec3_t origin;
model_t *mod;
int i;
if(( mod = CL_ModelHandle( modelIndex )) == NULL )
return;
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
dist = COM_RandomFloat( 0, 1.0 );
float dist = COM_RandomFloat( 0, 1.0 );
VectorLerp( start, dist, end, origin );
pTemp = CL_TempEntAlloc( origin, mod );
if( !pTemp ) return;
@@ -694,10 +681,11 @@ void GAME_EXPORT R_BubbleTrail( const vec3_t start, const vec3_t end, float heig
pTemp->x = origin[0];
pTemp->y = origin[1];
angle = COM_RandomFloat( -M_PI, M_PI );
float sine, cosine;
float angle = COM_RandomFloat( -M_PI, M_PI );
SinCos( angle, &sine, &cosine );
zspeed = COM_RandomLong( 80, 140 );
float zspeed = COM_RandomLong( 80, 140 );
VectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed );
pTemp->die = cl.time + ((height - (origin[2] - start[2])) / zspeed) - 0.1f;
pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax );
@@ -719,7 +707,6 @@ Attaches entity to player
void GAME_EXPORT R_AttachTentToPlayer( int client, int modelIndex, float zoffset, float life )
{
TEMPENTITY *pTemp;
vec3_t position;
cl_entity_t *pClient;
model_t *pModel;
@@ -734,7 +721,7 @@ void GAME_EXPORT R_AttachTentToPlayer( int client, int modelIndex, float zoffset
if(( pModel = CL_ModelHandle( modelIndex )) == NULL )
return;
VectorCopy( pClient->origin, position );
vec3_t position = Vec3( pClient->origin );
position[2] += zoffset;
pTemp = CL_TempEntAllocHigh( position, pModel );
@@ -848,11 +835,9 @@ Do muzzleflash
void GAME_EXPORT R_MuzzleFlash( const vec3_t pos, int type )
{
TEMPENTITY *pTemp;
int index;
float scale;
index = ( type % 10 ) % MAX_MUZZLEFLASH;
scale = ( type / 10 ) * 0.1f;
int index = ( type % 10 ) % MAX_MUZZLEFLASH;
float scale = ( type / 10 ) * 0.1f;
if( scale == 0.0f ) scale = 0.5f;
if( !cl_sprite_muzzleflash[index] )
@@ -887,20 +872,16 @@ and some blood drops. This is high-priority tent
void GAME_EXPORT R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex, int modelIndex2, float size )
{
model_t *pModel, *pModel2;
int impactindex;
int spatterindex;
int i, splatter;
TEMPENTITY *pTemp;
vec3_t pos;
colorIndex += COM_RandomLong( 1, 3 );
impactindex = colorIndex;
spatterindex = colorIndex - 1;
int impactindex = colorIndex;
int spatterindex = colorIndex - 1;
// validate the model first
if(( pModel = CL_ModelHandle( modelIndex )) != NULL )
{
VectorCopy( org, pos );
vec3_t pos = Vec3( org );
pos[2] += COM_RandomFloat( 2.0f, 4.0f ); // make offset from ground (snarks issues)
// large, single blood sprite is a high-priority tent
@@ -926,9 +907,9 @@ void GAME_EXPORT R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex
// validate the model first
if(( pModel2 = CL_ModelHandle( modelIndex2 )) != NULL )
{
splatter = size + ( COM_RandomLong( 1, 8 ) + COM_RandomLong( 1, 8 ));
int splatter = size + ( COM_RandomLong( 1, 8 ) + COM_RandomLong( 1, 8 ));
for( i = 0; i < splatter; i++ )
for( int i = 0; i < splatter; i++ )
{
// create blood drips
if(( pTemp = CL_TempEntAlloc( org, pModel2 )) != NULL )
@@ -973,13 +954,11 @@ void GAME_EXPORT R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t
{
TEMPENTITY *pTemp;
model_t *pmodel;
char type;
int i, j;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
type = flags & BREAK_TYPEMASK;
char type = flags & BREAK_TYPEMASK;
if( count == 0 )
{
@@ -990,9 +969,10 @@ void GAME_EXPORT R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t
// limit to 100 pieces
if( count > 100 ) count = 100;
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
vec3_t vecSpot;
int j;
for( j = 0; j < 32; j++ )
{
@@ -1209,14 +1189,12 @@ apply params for exploding sprite
*/
void GAME_EXPORT R_Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags )
{
qboolean noadditive, drawalpha, rotate;
if( !pTemp )
return;
noadditive = FBitSet( flags, TE_EXPLFLAG_NOADDITIVE );
drawalpha = FBitSet( flags, TE_EXPLFLAG_DRAWALPHA );
rotate = FBitSet( flags, TE_EXPLFLAG_ROTATE );
qboolean noadditive = FBitSet( flags, TE_EXPLFLAG_NOADDITIVE );
qboolean drawalpha = FBitSet( flags, TE_EXPLFLAG_DRAWALPHA );
qboolean rotate = FBitSet( flags, TE_EXPLFLAG_ROTATE );
pTemp->entity.curstate.scale = scale;
pTemp->entity.baseline.origin[2] = 8.0f;
@@ -1242,11 +1220,9 @@ apply params for smoke sprite
*/
void GAME_EXPORT R_Sprite_Smoke( TEMPENTITY *pTemp, float scale )
{
int iColor;
if( !pTemp ) return;
iColor = COM_RandomLong( 20, 35 );
int iColor = COM_RandomLong( 20, 35 );
pTemp->entity.curstate.rendermode = kRenderTransAlpha;
pTemp->entity.curstate.renderfx = kRenderFxNone;
pTemp->entity.baseline.origin[2] = 30;
@@ -1262,7 +1238,6 @@ static void R_Spray_Generic( const char *func, const vec3_t pos, const vec3_t di
model_t *mod = CL_ModelHandle( modelIndex );
float noise = spread / 100.0f;
float znoise = noise * 1.5f;
int i;
znoise = Q_min( 1.0f, znoise );
@@ -1272,10 +1247,9 @@ static void R_Spray_Generic( const char *func, const vec3_t pos, const vec3_t di
return;
}
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
TEMPENTITY *tent = CL_TempEntAlloc( pos, mod );
float rand_speed;
vec3_t noise_vec, vout;
if( !tent )
@@ -1290,7 +1264,7 @@ static void R_Spray_Generic( const char *func, const vec3_t pos, const vec3_t di
noise_vec[0] = COM_RandomFloat( -noise, noise );
noise_vec[1] = COM_RandomFloat( -noise, noise );
noise_vec[2] = COM_RandomFloat( 0.0f, znoise );
rand_speed = COM_RandomFloat( speed * 0.8f, speed * 1.2f );
float rand_speed = COM_RandomFloat( speed * 0.8f, speed * 1.2f );
VectorAdd( dir, noise_vec, vout );
VectorScale( vout, rand_speed, tent->entity.baseline.origin );
@@ -1361,7 +1335,6 @@ void GAME_EXPORT R_Sprite_Trail( int type, vec3_t start, vec3_t end, int modelIn
TEMPENTITY *pTemp;
vec3_t delta, dir;
model_t *pmodel;
int i;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
@@ -1371,7 +1344,7 @@ void GAME_EXPORT R_Sprite_Trail( int type, vec3_t start, vec3_t end, int modelIn
amp /= 256.0f;
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
vec3_t pos, vel;
@@ -1412,9 +1385,7 @@ void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse )
{
TEMPENTITY *pTemp;
vec3_t dir, dest;
float dist, vel;
model_t *pmodel;
int i, j;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
{
@@ -1422,9 +1393,9 @@ void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse )
return;
}
for( i = -8; i < 8; i++ )
for( int i = -8; i < 8; i++ )
{
for( j = -8; j < 8; j++ )
for( int j = -8; j < 8; j++ )
{
pTemp = CL_TempEntAlloc( org, pmodel );
if( !pTemp ) return;
@@ -1451,9 +1422,9 @@ void GAME_EXPORT R_FunnelSprite( const vec3_t org, int modelIndex, int reverse )
pTemp->entity.curstate.framerate = COM_RandomFloat( 0.1f, 0.4f );
pTemp->flags = FTENT_ROTATE|FTENT_FADEOUT;
vel = dest[2] / 8.0f;
float vel = dest[2] / 8.0f;
if( vel < 64.0f ) vel = 64.0f;
dist = VectorNormalizeLength( dir );
float dist = VectorNormalizeLength( dir );
vel += COM_RandomFloat( 64.0f, 128.0f );
VectorScale( dir, vel, pTemp->entity.baseline.origin );
pTemp->die = cl.time + (dist / vel) - 0.5f;
@@ -1484,8 +1455,7 @@ Make a random ricochet sound
*/
static void R_RicochetSoundByName( const vec3_t pos, const char *name )
{
sound_t handle;
handle = S_RegisterSound( name );
sound_t handle = S_RegisterSound( name );
S_StartSound( pos, 0, CHAN_AUTO, handle, VOL_NORM, 1.0, 100, 0 );
}
@@ -1562,10 +1532,9 @@ Spherical shower of models, picks from set
void GAME_EXPORT R_TempSphereModel( const vec3_t pos, float speed, float life, int count, int modelIndex )
{
TEMPENTITY *pTemp;
int i;
// create temp models
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
pTemp = CL_TempEntAlloc( pos, CL_ModelHandle( modelIndex ));
if( !pTemp ) return;
@@ -1609,8 +1578,6 @@ Create an explosion (scale is magnitude)
*/
void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerate, int flags )
{
sound_t hSound;
if( scale != 0.0f )
{
// create explosion sprite
@@ -1621,10 +1588,8 @@ void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerat
if( !FBitSet( flags, TE_EXPLFLAG_NODLIGHTS ))
{
dlight_t *dl;
// big flash
dl = CL_AllocDlight( 0 );
dlight_t *dl = CL_AllocDlight( 0 );
VectorCopy( pos, dl->origin );
dl->radius = 200;
dl->color.r = 250;
@@ -1650,7 +1615,7 @@ void GAME_EXPORT R_Explosion( vec3_t pos, int model, float scale, float framerat
const char *name = SoundList_GetRandom( Explode );
if( name )
{
hSound = S_RegisterSound( name );
sound_t hSound = S_RegisterSound( name );
S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.3f, PITCH_NORM, 0 );
}
}
@@ -1667,21 +1632,16 @@ void GAME_EXPORT R_PlayerSprites( int client, int modelIndex, int count, int siz
{
TEMPENTITY *pTemp;
cl_entity_t *pEnt;
vec3_t position;
vec3_t dir;
float vel;
int i;
pEnt = CL_GetEntityByIndex( client );
if( !pEnt || !pEnt->player )
return;
vel = 128;
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
VectorCopy( pEnt->origin, position );
vec3_t position = Vec3( pEnt->origin );
position[0] += COM_RandomFloat( -10.0f, 10.0f );
position[1] += COM_RandomFloat( -10.0f, 10.0f );
position[2] += COM_RandomFloat( -20.0f, 36.0f );
@@ -1732,16 +1692,13 @@ void GAME_EXPORT R_FireField( float *org, int radius, int modelIndex, int count,
{
TEMPENTITY *pTemp;
model_t *pmodel;
float time;
vec3_t pos;
int i;
if(( pmodel = CL_ModelHandle( modelIndex )) == NULL )
return;
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
VectorCopy( org, pos );
vec3_t pos = Vec3( org );
pos[0] += COM_RandomFloat( -radius, radius );
pos[1] += COM_RandomFloat( -radius, radius );
@@ -1770,7 +1727,7 @@ void GAME_EXPORT R_FireField( float *org, int radius, int modelIndex, int count,
}
pTemp->entity.curstate.framerate = COM_RandomFloat( 0.75f, 1.25f );
time = life + COM_RandomFloat( -0.25f, 0.5f );
float time = life + COM_RandomFloat( -0.25f, 0.5f );
pTemp->die = cl.time + time;
if( pTemp->frameMax > 1 )
@@ -1807,13 +1764,12 @@ void GAME_EXPORT R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_
{
pmtrace_t trace;
vec3_t right, up;
vec3_t vecSrc, vecDir, vecEnd;
int i, j, decalIndex;
vec3_t vecDir, vecEnd;
VectorVectors( dir, right, up );
VectorCopy( org, vecSrc );
vec3_t vecSrc = Vec3( org );
for( i = 0; i < count; i++ )
for( int i = 0; i < count; i++ )
{
// get circular gaussian spread
float x, y, z;
@@ -1823,7 +1779,7 @@ void GAME_EXPORT R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_
z = x * x + y * y;
} while( z > 1.0f );
for( j = 0; j < 3; j++ )
for( int j = 0; j < 3; j++ )
{
vecDir[j] = dir[j] + x * noise[0] * right[j] + y * noise[1] * up[j];
vecEnd[j] = vecSrc[j] + 4096.0f * vecDir[j];
@@ -1845,7 +1801,7 @@ void GAME_EXPORT R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_
if( pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ))
{
cl_entity_t *e = CL_GetEntityByIndex( pe->info );
decalIndex = CL_DecalIndex( decalIndices[COM_RandomLong( 0, decalCount-1 )] );
int decalIndex = CL_DecalIndex( decalIndices[COM_RandomLong( 0, decalCount-1 )] );
CL_DecalShoot( decalIndex, e->index, 0, trace.endpos, 0 );
}
}
@@ -1884,7 +1840,6 @@ void CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto )
{
sizebuf_t buf, *pbuf;
byte msg_data[2048];
int iSize;
int type, color, count, flags;
int decalIndex = 0, modelIndex = 0, entityIndex = 0;
float scale, life, frameRate, vel, random;
@@ -1899,11 +1854,15 @@ void CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto )
if( proto != PROTO_GOLDSRC )
{
iSize = MSG_ReadWord( msg );
int iSize = MSG_ReadWord( msg );
// this will probably be fatal anyway
if( iSize > sizeof( msg_data ))
{
Con_Printf( S_ERROR "%s: Temp buffer overflow!\n", __func__ );
MSG_SeekToBit( msg, iSize << 3, SEEK_CUR );
return;
}
// parse user message into buffer
MSG_ReadBytes( msg, msg_data, iSize );
@@ -2429,28 +2388,25 @@ static void CL_ClearLightStyles( void )
void CL_SetLightstyle( int style, const char *s, float f )
{
int i;
lightstyle_t *ls;
if( unlikely( style < 0 || style >= MAX_LIGHTSTYLES ))
{
Con_Printf( S_WARN "%s: ignored invalid lightstyle id %d\n", __func__, style );
return;
}
ls = &cl.lightstyles[style];
lightstyle_t *ls = &cl.lightstyles[style];
ls->length = Q_strncpy( ls->pattern, s, sizeof( ls->pattern ));
ls->time = f; // set local time
for( i = 0; i < ls->length; i++ )
for( int i = 0; i < ls->length; i++ )
ls->map[i] = (float)(s[i] - 'a');
ls->interp = (ls->length <= 1) ? false : true;
// check for allow interpolate
// NOTE: fast flickering styles looks ugly when interpolation is running
for( i = 0; i < ( ls->length - 1 ); i++ )
for( int i = 0; i < ( ls->length - 1 ); i++ )
{
float val1 = ls->map[( i + 0 ) % ls->length];
float val2 = ls->map[( i + 1 ) % ls->length];
@@ -2587,9 +2543,8 @@ void CL_DecayLights( void )
{
const float time = cl.time;
const float dt = cl.time - cl.oldtime;
int i;
for( i = 0; i < MAX_DLIGHTS; i++ )
for( int i = 0; i < MAX_DLIGHTS; i++ )
{
dlight_t *dl = &cl_dlights[i];
@@ -2607,7 +2562,7 @@ void CL_DecayLights( void )
memset( dl, 0, sizeof( *dl ));
}
for( i = 0; i < MAX_ELIGHTS; i++ )
for( int i = 0; i < MAX_ELIGHTS; i++ )
{
dlight_t *dl = &cl_elights[i];
@@ -2649,7 +2604,6 @@ static void CL_UpdateFlashlight( cl_entity_t *ent )
{
vec3_t forward, view_ofs;
vec3_t vecSrc, vecEnd;
float falloff;
pmtrace_t trace;
cl_entity_t *hit;
dlight_t *dl;
@@ -2694,7 +2648,7 @@ static void CL_UpdateFlashlight( cl_entity_t *ent )
VectorCopy( trace->endpos, dl->origin );
#endif
// compute falloff
falloff = trace.fraction * FLASHLIGHT_DISTANCE;
float falloff = trace.fraction * FLASHLIGHT_DISTANCE;
if( falloff < 500.0f ) falloff = 1.0f;
else falloff = 500.0f / falloff;
falloff *= falloff;
@@ -2878,25 +2832,22 @@ if cl_testlights is set, create 32 lights models
*/
void CL_TestLights( void )
{
int i, j, numLights;
vec3_t forward, right;
float f, r;
dlight_t *dl;
if( !cl_testlights.value )
return;
numLights = bound( 1, cl_testlights.value, MAX_DLIGHTS );
int numLights = bound( 1, cl_testlights.value, MAX_DLIGHTS );
AngleVectors( cl.viewangles, forward, right, NULL );
for( i = 0; i < numLights; i++ )
for( int i = 0; i < numLights; i++ )
{
dl = &cl_dlights[i];
dlight_t *dl = &cl_dlights[i];
r = 64 * ((i % 4) - 1.5f );
f = 64 * ( i / 4) + 128;
float r = 64 * ((i % 4) - 1.5f );
float f = 64 * ( i / 4) + 128;
for( j = 0; j < 3; j++ )
for( int j = 0; j < 3; j++ )
dl->origin[j] = cl.simorg[j] + forward[j] * f + right[j] * r;
dl->color.r = ((((i % 6) + 1) & 1)>>0) * 255;
@@ -2996,13 +2947,11 @@ get decal global index from decalname
*/
int GAME_EXPORT CL_DecalIndexFromName( const char *name )
{
int i;
if( COM_StringEmptyOrNULL( name ))
return 0;
// look through the loaded sprite name list for SpriteName
for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )
for( int i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ )
{
if( !Q_stricmp( name, host.draw_decals[i] ))
return i;
@@ -3099,13 +3048,11 @@ e.g. by demo request
*/
void CL_ClearStaticEntities( void )
{
int i;
if( host.type == HOST_DEDICATED )
return;
// clear out efrags in case the level hasn't been reloaded
for( i = 0; i < cl.worldmodel->numleafs; i++ )
for( int i = 0; i < cl.worldmodel->numleafs; i++ )
cl.worldmodel->leafs[i+1].efrags = NULL;
clgame.numStatics = 0;

View File

@@ -25,6 +25,7 @@ AVI PLAYING
*/
static movie_state_t *cin_state;
static int cin_texture;
/*
==================
@@ -191,11 +192,9 @@ SCR_PlayCinematic
qboolean SCR_PlayCinematic( const char *arg )
{
int x, y, w, h;
const char *fullpath;
const char *fullpath = FS_GetDiskPath( arg, false );
double video_ratio, screen_ratio, scale;
fullpath = FS_GetDiskPath( arg, false );
if( FS_FileExists( arg, false ) && !fullpath )
{
Con_Printf( S_ERROR "Couldn't load %s from packfile. Please extract it\n", arg );
@@ -281,6 +280,12 @@ void SCR_InitCinematic( void )
{
AVI_Initailize ();
cin_state = AVI_GetState( CIN_MAIN );
cin_texture = ref.dllFuncs.GL_CreateTexture( "*cintexture", 64, 64, NULL, TF_NOMIPMAP|TF_CLAMP );
}
int SCR_GetCinematicTexture( void )
{
return cin_texture;
}
/*

View File

@@ -187,18 +187,15 @@ merge refdef with overview settings
static void V_RefApplyOverview( ref_viewpass_t *rvp )
{
ref_overview_t *ov = &clgame.overView;
float aspect;
float size_x, size_y;
vec2_t mins, maxs;
if( !CL_IsDevOverviewMode( ))
return;
// NOTE: Xash3D may use 16:9 or 16:10 aspects
aspect = (float)refState.width / (float)refState.height;
float aspect = (float)refState.width / (float)refState.height;
size_x = fabs( 8192.0f / ov->flZoom );
size_y = fabs( 8192.0f / (ov->flZoom * aspect ));
float size_x = fabs( 8192.0f / ov->flZoom );
float size_y = fabs( 8192.0f / (ov->flZoom * aspect ));
// compute rectangle
ov->xLeft = -(size_x / 2);
@@ -214,8 +211,8 @@ static void V_RefApplyOverview( ref_viewpass_t *rvp )
VectorCopy( ov->origin, rvp->vieworigin );
rvp->vieworigin[2] = ov->zFar + ov->zNear;
Vector2Copy( rvp->vieworigin, mins );
Vector2Copy( rvp->vieworigin, maxs );
vec2_t mins = Vec2( rvp->vieworigin );
vec2_t maxs = Vec2( rvp->vieworigin );
mins[!ov->rotated] += ov->xLeft;
maxs[!ov->rotated] += ov->xRight;
@@ -238,13 +235,11 @@ V_CalcFov
*/
static float V_CalcFov( float *fov_x, float width, float height )
{
float x, half_fov_y;
if( *fov_x < 1.0f || *fov_x > 179.0f )
*fov_x = 90.0f; // default value
x = width / tan( DEG2RAD( *fov_x ) * 0.5f );
half_fov_y = atan( height / x );
float x = width / tan( DEG2RAD( *fov_x ) * 0.5f );
float half_fov_y = atan( height / x );
return RAD2DEG( half_fov_y ) * 2;
}
@@ -360,6 +355,29 @@ qboolean V_PreRender( void )
//============================================================================
/*
====================
V_ApplyRefUnderwater
apply underwater fov effect
====================
*/
static void V_ApplyRefUnderwater( ref_viewpass_t *rvp )
{
if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP|RF_DRAW_OVERVIEW ))
return;
if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
return;
if( cl.local.waterlevel < 3 )
return;
rvp->fov_x = atan( tan( DEG2RAD( rvp->fov_x ) / 2 ) * ( 0.97f + sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
rvp->fov_y = atan( tan( DEG2RAD( rvp->fov_y ) / 2 ) * ( 1.03f - sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
}
/*
==================
V_RenderView
@@ -389,6 +407,7 @@ void V_RenderView( void )
clgame.dllFuncs.pfnCalcRefdef( &rp );
V_GetRefParams( &rp, &rvp );
V_RefApplyOverview( &rvp );
V_ApplyRefUnderwater( &rvp );
if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))
{
@@ -476,13 +495,12 @@ static void R_ShowTree( void )
{
float x = (float)((refState.width - (int)POINT_SIZE) >> 1);
float y = NODE_INTERVAL_Y(1.0f);
mleaf_t *viewleaf;
if( !cl.worldmodel || !r_showtree.value )
return;
world.recursion_level = 0;
viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );
mleaf_t *viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );
ref.dllFuncs.TriRenderMode( kRenderTransTexture );

View File

@@ -761,6 +761,7 @@ void CL_AddToResourceList( resource_t *pResource, resource_t *pList );
void CL_RemoveFromResourceList( resource_t *pResource );
void CL_MoveToOnHandList( resource_t *pResource );
void CL_ClearResourceLists( void );
void CL_SendResourceList( const resource_t *list, int count );
//
// cl_debug.c
@@ -874,7 +875,6 @@ struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend );
void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );
void CL_DisableScissor( scissor_state_t *scissor );
qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 );
static inline cl_entity_t *CL_EDICT_NUM( int index )
{
if( unlikely( !clgame.entities )) // not in game yet
@@ -972,6 +972,13 @@ void SCR_DrawPos( void );
void SCR_DrawEnts( void );
void SCR_DrawUserCmd( void );
//
// cl_sprite.c
//
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw );
void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite );
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame );
//
// cl_netgraph.c
//
@@ -1201,14 +1208,14 @@ void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );
void SteamBroker_Init( void );
void SteamBroker_Shutdown( void );
void SteamBroker_Frame( void );
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg );
int SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
void SteamBroker_TerminateGameConnection( void );
//
// cl_video.c
//
void SCR_InitCinematic( void );
int SCR_GetCinematicTexture( void );
void SCR_FreeCinematic( void );
qboolean SCR_PlayCinematic( const char *name );
qboolean SCR_DrawCinematic( void );
@@ -1234,7 +1241,6 @@ void Key_EnumCmds_f( void );
void Key_SetKeyDest( int key_dest );
void Key_EnableTextInput( qboolean enable, qboolean force );
int Key_ToUpper( int key );
void OSK_Draw( void );
qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboolean print_suggestions );
//

View File

@@ -159,7 +159,7 @@ static void Con_SaveHistory( con_history_t *self );
Con_BackgroundMapActive
================
*/
qboolean Con_BackgroundMapActive( void )
static qboolean Con_BackgroundMapActive( void )
{
return sv_background.value != 0.0f || cl.background;
}
@@ -182,13 +182,11 @@ Con_SetColor
*/
static void Con_SetColor( void )
{
int r, g, b;
int num;
if( !FBitSet( con_color.flags, FCVAR_CHANGED ))
return;
num = sscanf( con_color.string, "%i %i %i", &r, &g, &b );
int r, g, b;
int num = sscanf( con_color.string, "%i %i %i", &r, &g, &b );
switch( num )
{
@@ -213,9 +211,7 @@ Con_ClearNotify
*/
void Con_ClearNotify( void )
{
int i;
for( i = 0; i < CON_LINES_COUNT; i++ )
for( int i = 0; i < CON_LINES_COUNT; i++ )
CON_LINES( i ).addtime = 0.0;
}
@@ -310,15 +306,13 @@ Con_SetTimes_f
*/
static void Con_SetTimes_f( void )
{
int newtimes;
if( Cmd_Argc() != 2 )
{
Con_Printf( S_USAGE "contimes <n lines>\n" );
return;
}
newtimes = Q_atoi( Cmd_Argv( 1 ) );
int newtimes = Q_atoi( Cmd_Argv( 1 ) );
con.num_times = bound( CON_TIMES, newtimes, CON_MAX_TIMES );
}
@@ -333,15 +327,12 @@ went backwards)
*/
static void Con_FixTimes( void )
{
double diff;
int i;
if( con.lines_count <= 0 ) return;
diff = cl.time - CON_LINES_LAST().addtime;
double diff = cl.time - CON_LINES_LAST().addtime;
if( diff >= 0.0 ) return; // nothing to fix
for( i = 0; i < con.lines_count; i++ )
for( int i = 0; i < con.lines_count; i++ )
CON_LINES( i ).addtime += diff;
}
@@ -428,8 +419,7 @@ Appends a given string as a new line to the console.
*/
static void Con_AddLine( const char *line, int length, qboolean newline )
{
char *putpos;
con_lineinfo_t *p;
char *putpos;
if( !con.initialized || !con.buffer )
return;
@@ -442,6 +432,7 @@ static void Con_AddLine( const char *line, int length, qboolean newline )
while( !( putpos = Con_BytesLeft( length )) || con.lines_count >= con.maxlines )
Con_DeleteLine();
con_lineinfo_t *p;
if( newline )
{
memcpy( putpos, line, length );
@@ -475,12 +466,11 @@ If the line width has changed, reformat the buffer.
static void Con_CheckResize( void )
{
int charWidth = 8;
int i, width;
if( con.curFont && con.curFont->hFontTexture )
charWidth = con.curFont->charWidths['O'] - 1;
width = ( refState.width / charWidth ) - 2;
int width = ( refState.width / charWidth ) - 2;
if( !ref.initialized ) width = (640 / 5);
if( width == con.linewidth )
@@ -611,13 +601,12 @@ Con_LoadConchars
*/
static void Con_LoadConchars( void )
{
int i, fontSize;
// load all the console fonts
for( i = 0; i < CON_NUMFONTS; i++ )
for( int i = 0; i < CON_NUMFONTS; i++ )
Con_LoadConsoleFont( i, con.chars + i );
// select properly fontsize
int fontSize;
if( con_fontnum.value >= 0 && con_fontnum.value <= CON_NUMFONTS - 1 )
fontSize = con_fontnum.value;
else if( refState.width <= 640 )
@@ -675,7 +664,6 @@ int Con_UtfMoveLeft( char *str, int pos )
{
utfstate_t state = { 0 };
int k = 0;
int i;
if( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence
return pos - 1;
@@ -683,7 +671,7 @@ int Con_UtfMoveLeft( char *str, int pos )
if( pos == 1 )
return 0;
for( i = 0; i < pos - 1; i++ )
for( int i = 0; i < pos - 1; i++ )
{
if( Q_DecodeUTF8( &state, (byte)str[i] ))
k = i + 1;
@@ -702,12 +690,11 @@ get next of previous printful char
int Con_UtfMoveRight( char *str, int pos, int length )
{
utfstate_t state = { 0 };
int i;
if( !cls.accept_utf8 ) // incoming character is not a UTF-8 sequence
return pos + 1;
for( i = pos; i <= length; i++ )
for( int i = pos; i <= length; i++ )
{
if( Q_DecodeUTF8( &state, (byte)str[i] ))
return i + 1;
@@ -718,20 +705,15 @@ int Con_UtfMoveRight( char *str, int pos, int length )
static void Con_DrawCharToConback( int num, const byte *conchars, byte *dest )
{
int row, col;
const byte *source;
int drawline;
int x;
int row = num >> 4;
int col = num & 15;
const byte *source = conchars + (row << 10) + (col << 3);
row = num >> 4;
col = num & 15;
source = conchars + (row << 10) + (col << 3);
drawline = 8;
int drawline = 8;
while( drawline-- )
{
for( x = 0; x < 8; x++ )
for( int x = 0; x < 8; x++ )
if( source[x] != 255 )
dest[x] = 0x60 + source[x];
source += 128;
@@ -1105,7 +1087,7 @@ static void Con_ClearField( field_t *edit )
Field_Set
================
*/
static void Field_Set( field_t *f, const char *string )
MAYBE_UNUSED static void Field_Set( field_t *f, const char *string )
{
f->scroll = 0;
f->cursor = Q_strncpy( f->buffer, string, sizeof( f->buffer ));
@@ -1118,15 +1100,12 @@ Field_Paste
*/
static void Field_Paste( field_t *edit )
{
char *cbd;
int i, pasteLen;
cbd = Sys_GetClipboardData();
char *cbd = Sys_GetClipboardData();
if( !cbd ) return;
// send as if typed, so insert / overstrike works properly
pasteLen = Q_strlen( cbd );
for( i = 0; i < pasteLen; i++ )
int pasteLen = Q_strlen( cbd );
for( int i = 0; i < pasteLen; i++ )
Field_CharEvent( edit, cbd[i] );
}
@@ -1154,8 +1133,6 @@ Key events are used for non-printable characters, others are gotten from char ev
*/
static void Field_KeyDownEvent( field_t *edit, int key )
{
int len;
// shift-insert is paste
if((( key == K_INS ) || ( key == K_KP_INS )) && Key_IsDown( K_SHIFT ))
{
@@ -1163,7 +1140,7 @@ static void Field_KeyDownEvent( field_t *edit, int key )
return;
}
len = Q_strlen( edit->buffer );
int len = Q_strlen( edit->buffer );
if( key == K_DEL )
{
@@ -1225,8 +1202,6 @@ Field_CharEvent
*/
static void Field_CharEvent( field_t *edit, int ch )
{
int len;
if( ch == 'v' - 'a' + 1 )
{
// ctrl-v is paste
@@ -1241,7 +1216,7 @@ static void Field_CharEvent( field_t *edit, int ch )
return;
}
len = Q_strlen( edit->buffer );
int len = Q_strlen( edit->buffer );
if( ch == 'a' - 'a' + 1 )
{
@@ -1286,7 +1261,6 @@ Field_DrawInputLine
*/
static int Field_DrawInputLine( int x, int y, const field_t *edit, byte alpha, qboolean cursor )
{
int curPos;
char str[MAX_SYSPATH];
rgba_t colorDefault;
const int prestep = bound( 0, edit->scroll, sizeof( edit->buffer ) - 1 );
@@ -1303,6 +1277,7 @@ static int Field_DrawInputLine( int x, int y, const field_t *edit, byte alpha, q
// calc cursor position
str[cursorCharPos] = 0;
int curPos;
CL_DrawStringLen( con.curFont, str, &curPos, NULL, FONT_DRAW_UTF8 );
// draw the cursor
@@ -1415,10 +1390,7 @@ static void Con_HistoryAppend( con_history_t *self, const field_t *from )
static void Con_LoadHistory( con_history_t *self )
{
file_t *fd;
int i;
fd = FS_Open( "console_history.txt", "rb", true );
file_t *fd = FS_Open( "console_history.txt", "rb", true );
if( !fd )
return;
@@ -1448,7 +1420,7 @@ static void Con_LoadHistory( con_history_t *self )
FS_Close( fd );
for( i = self->next; i < CON_HISTORY; i++ )
for( int i = self->next; i < CON_HISTORY; i++ )
{
history_line_t *f = &self->lines[i];
@@ -1460,17 +1432,15 @@ static void Con_LoadHistory( con_history_t *self )
static void Con_SaveHistory( con_history_t *self )
{
int historyStart = self->next - CON_HISTORY, i;
file_t *f;
// do not save history if nothing was executed
if( self->next == 0 )
return;
int historyStart = self->next - CON_HISTORY;
if( historyStart < 0 )
historyStart = 0;
f = FS_Open( "console_history.txt", "wb", true );
file_t *f = FS_Open( "console_history.txt", "wb", true );
if( !f )
{
@@ -1478,7 +1448,7 @@ static void Con_SaveHistory( con_history_t *self )
return;
}
for( i = historyStart; i < self->next; i++ )
for( int i = historyStart; i < self->next; i++ )
{
const char *s = self->lines[i % CON_HISTORY].buffer;
@@ -1704,15 +1674,13 @@ The input line scrolls horizontally if typing goes beyond the right edge
*/
static void Con_DrawInput( int lines )
{
int x, y;
// don't draw anything (always draw if not active)
if( cls.key_dest != key_console || !con.curFont )
return;
y = lines - ( con.curFont->charHeight * 2 );
int y = lines - ( con.curFont->charHeight * 2 );
CL_DrawCharacter( con.curFont->charWidths[' '], y, ']', g_color_table[7], con.curFont, 0 );
x = Field_DrawInputLine( con.curFont->charWidths[' ']*2, y, &con.input, 255, true );
int x = Field_DrawInputLine( con.curFont->charWidths[' ']*2, y, &con.input, 255, true );
// HACKHACK: avoid rendering issues when scroll != 0
if( con_showcompletion.value && con.input.scroll == 0 )
@@ -1744,18 +1712,16 @@ Custom debug messages
static int Con_DrawDebugLines( void )
{
notify_t *notify = con.notify;
int i, count = 0;
int defaultX;
int count = 0;
int y = 20;
int fontTall;
if( !con.curFont || !con.curFont->valid )
return 0;
defaultX = refState.width / 4;
fontTall = con.curFont->charHeight + 1;
int defaultX = refState.width / 4;
int fontTall = con.curFont->charHeight + 1;
for( i = 0; i < ARRAYSIZE( con.notify ); i++, notify++ )
for( int i = 0; i < ARRAYSIZE( con.notify ); i++, notify++ )
{
int x, len;
@@ -1789,19 +1755,18 @@ Draws the debug messages (not passed to console history)
void Con_DrawDebug( void )
{
static double timeStart;
string dlstring;
int x, y;
if( scr_download.value != -1.0f )
{
string dlstring;
int length;
Q_snprintf( dlstring, sizeof( dlstring ), "Downloading [%d remaining]: ^2%s^7 %5.1f%% time %.f secs",
host.downloadcount, host.downloadfile, scr_download.value, Platform_DoubleTime() - timeStart );
Con_DrawStringLen( dlstring, &length, NULL );
length = Q_max( length, 300 );
x = refState.width - length * 1.05f;
y = con.curFont->charHeight * 1.05f;
int x = refState.width - length * 1.05f;
int y = con.curFont->charHeight * 1.05f;
Con_DrawString( x, y, dlstring, g_color_table[7] );
}
else
@@ -1829,15 +1794,15 @@ Draws the last few lines of output transparently over the game top
static void Con_DrawNotify( void )
{
double time = cl.time;
int i, x, y = 0;
int y = 0;
if( !con.curFont ) return;
x = con.curFont->charWidths[' ']; // offset one space at left screen side
int x = con.curFont->charWidths[' ']; // offset one space at left screen side
if( host.allow_console && !Con_BackgroundMapActive( ))
{
for( i = Q_max( 0, CON_LINES_COUNT - con.num_times ); i < CON_LINES_COUNT; i++ )
for( int i = Q_max( 0, CON_LINES_COUNT - con.num_times ); i < CON_LINES_COUNT; i++ )
{
con_lineinfo_t *l = &CON_LINES( i );
@@ -1905,13 +1870,13 @@ of it based on con.backscroll.
*/
static void Con_LastVisibleLine( int *lastline )
{
int i, lines_seen = 0;
int lines_seen = 0;
con.backscroll = Q_max( 0, con.backscroll );
*lastline = 0;
// now count until we saw con_backscroll actual lines
for( i = CON_LINES_COUNT - 1; i >= 0; i-- )
for( int i = CON_LINES_COUNT - 1; i >= 0; i-- )
{
// line is the last visible line?
*lastline = i;
@@ -1935,10 +1900,10 @@ Draws the console with the solid background
*/
static void Con_DrawSolidConsole( int lines )
{
int i, x, y;
int x, y;
float fraction;
int start;
int stringLen, width = 0, charH;
int stringLen, charH;
string curbuild;
byte color[4];
@@ -2086,8 +2051,6 @@ void Con_DrawVersion( void )
{
// draws the current build
byte *color = g_color_table[7];
int stringLen, charH = 0;
int start, height = refState.height;
string curbuild;
if( !scr_drawversion.value )
@@ -2116,9 +2079,10 @@ void Con_DrawVersion( void )
XASH_ENGINE_NAME " v%i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));
}
int stringLen, charH = 0;
Con_DrawStringLen( curbuild, &stringLen, &charH );
start = refState.width - stringLen * 1.05f;
height -= charH * 1.05f;
int start = refState.width - stringLen * 1.05f;
int height = refState.height - charH * 1.05f;
Con_DrawString( start, height, curbuild, color );
}
@@ -2132,8 +2096,6 @@ Scroll it up or down
*/
void Con_RunConsole( void )
{
float lines_per_frame;
Con_SetColor( );
// decide on the destination height of the console
@@ -2149,7 +2111,7 @@ void Con_RunConsole( void )
}
else con.showlines = 0; // none visible
lines_per_frame = fabs( scr_conspeed.value ) * host.realframetime;
float lines_per_frame = fabs( scr_conspeed.value ) * host.realframetime;
if( con.showlines < con.vislines )
{
@@ -2164,8 +2126,11 @@ void Con_RunConsole( void )
con.vislines = con.showlines;
}
if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED ))
if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED ))
{
if( con_fontscale.value < 1.0f )
Cvar_DirectSet( &con_fontscale, "1" );
// update codepage parameters
if( !Q_stricmp( con_charset.string, "cp1251" ))
{
@@ -2221,9 +2186,7 @@ void Con_CharEvent( int key )
static int Con_LoadSimpleConback( const char *name, int flags )
{
int i;
for( i = 0; i < 5; i++ )
for( int i = 0; i < 5; i++ )
{
string path;
@@ -2308,15 +2271,13 @@ void Con_VidInit( void )
{
lmp_t *cb = (lmp_t *)FS_LoadFile( "gfx/conback.lmp", &length, false );
char ver[64];
byte *dest;
int x, y, len;
if( cb && cb->width == 320 && cb->height == 200 )
{
len = Q_snprintf( ver, 64, "%i", Q_buildnum( )); // can store only buildnum
dest = (byte *)(cb + 1) + 320 * 186 + 320 - 11 - 8 * len;
y = len;
for( x = 0; x < y; x++ )
int len = Q_snprintf( ver, 64, "%i", Q_buildnum( )); // can store only buildnum
byte *dest = (byte *)(cb + 1) + 320 * 186 + 320 - 11 - 8 * len;
int y = len;
for( int x = 0; x < y; x++ )
Con_DrawCharToConback( ver[x], buf, dest + (x << 3));
con.background = ref.dllFuncs.GL_LoadTexture( "#gfx/conback.lmp", (byte *)cb, length, TF_IMAGE );
}
@@ -2341,8 +2302,7 @@ Con_InvalidateFonts
*/
void Con_InvalidateFonts( void )
{
int i;
for( i = 0; i < ARRAYSIZE( con.chars ); i++ )
for( int i = 0; i < ARRAYSIZE( con.chars ); i++ )
CL_FreeFont( &con.chars[i] );
con.curFont = NULL;
}
@@ -2396,9 +2356,8 @@ static void Test_RunConHistory( void )
const char *strs1[] = { "map t0a0", "quit", "wtf", "wtf", "", "nyan" };
const char *strs2[] = { "nyan", "wtf", "quit", "map t0a0" };
const char *testbackup = "unfinished_edit";
int i;
for( i = 0; i < ARRAYSIZE( strs1 ); i++ )
for( int i = 0; i < ARRAYSIZE( strs1 ); i++ )
{
Field_Set( &input, strs1[i] );
Con_HistoryAppend( &hist, &input );
@@ -2406,7 +2365,7 @@ static void Test_RunConHistory( void )
Field_Set( &input, testbackup );
for( i = 0; i < ARRAYSIZE( strs2 ); i++ )
for( int i = 0; i < ARRAYSIZE( strs2 ); i++ )
{
Con_HistoryUp( &hist, &input );
TASSERT_STR( input.buffer, strs2[i] );
@@ -2415,7 +2374,7 @@ static void Test_RunConHistory( void )
// check for overrun
Con_HistoryUp( &hist, &input );
for( i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- )
for( int i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- )
{
TASSERT_STR( input.buffer, strs2[i] );
Con_HistoryDown( &hist, &input );

View File

@@ -23,6 +23,8 @@ GNU General Public License for more details.
#include "client.h"
#include "const.h"
#include "triangleapi.h"
#include "r_studioint.h"
#include "mod_local.h"
#include "r_efx.h"
#include "demo_api.h"
#include "ivoicetweak.h"
@@ -105,7 +107,8 @@ static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and high
{ "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
{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D ext
{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D FWGS ext
{ "Voice_StartChannel", (void **)&clgame.dllFuncs.pfnVoice_StartChannel }, // Xash3D FWGS ext
};
static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc );
@@ -169,8 +172,6 @@ Initialize CD playlist
*/
static void CL_InitCDAudio( const char *filename )
{
byte *afile;
char *pfile;
string token;
int c = 0;
@@ -180,10 +181,10 @@ static void CL_InitCDAudio( const char *filename )
CL_CreatePlaylist( filename );
}
afile = FS_LoadFile( filename, NULL, false );
byte *afile = FS_LoadFile( filename, NULL, false );
if( !afile ) return;
pfile = (char *)afile;
char *pfile = (char *)afile;
// format: trackname\n [num]
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
@@ -315,14 +316,12 @@ 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;
float xscale = refState.width / (float)clgame.scrInfo.iWidth;
float yscale = refState.height / (float)clgame.scrInfo.iHeight;
*x *= xscale;
*y *= yscale;
@@ -366,20 +365,19 @@ 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 );
R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );
width = w;
height = h;
}
texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame );
int texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame );
if( prc )
{
@@ -427,11 +425,8 @@ 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;
byte line[MAX_LINELENGTH];
int charHeight;
if( !clgame.centerPrint.time )
return;
@@ -443,17 +438,17 @@ void CL_DrawCenterPrint( void )
return;
}
y = clgame.centerPrint.y; // start y
colorDefault = g_color_table[7];
pText = clgame.centerPrint.message;
int y = clgame.centerPrint.y; // start y
byte *colorDefault = g_color_table[7];
char *pText = clgame.centerPrint.message;
CL_DrawCharacterLen( font, 0, NULL, &charHeight );
CL_SetFontRendermode( font );
CL_SetFontColor( font, colorDefault );
for( i = 0; i < clgame.centerPrint.lines; i++ )
for( int i = 0; i < clgame.centerPrint.lines; i++ )
{
lineLength = 0;
width = 0;
int lineLength = 0;
int width = 0;
while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH )
{
@@ -463,6 +458,7 @@ void CL_DrawCenterPrint( void )
continue;
line[lineLength] = number;
int charWidth;
CL_DrawCharacterLen( font, number, &charWidth, NULL );
width += charWidth;
lineLength++;
@@ -474,9 +470,9 @@ void CL_DrawCenterPrint( void )
pText++; // Skip LineFeed
line[lineLength] = 0;
x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth );
int x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth );
for( j = 0; j < lineLength; j++ )
for( int j = 0; j < lineLength; j++ )
{
if( x >= 0 && y >= 0 && x <= refState.width )
x += CL_DrawCharacter( x, y, line[j], NULL, font, FONT_DRAW_HUD | FONT_DRAW_NORENDERMODE | FONT_DRAW_NOCOLOR );
@@ -531,9 +527,7 @@ can be modulated
static void CL_DrawScreenFade( void )
{
screenfade_t *sf = &clgame.fade;
int alpha;
alpha = V_FadeAlpha( sf );
int alpha = V_FadeAlpha( sf );
if( !alpha )
return;
@@ -616,11 +610,9 @@ 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 );
int channel = MSG_ReadByte( msg );
if( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 ))
{
@@ -629,7 +621,7 @@ void CL_ParseTextMessage( sizebuf_t *msg )
}
// grab message channel
text = &cl_textmessage[channel];
client_textmessage_t *text = &cl_textmessage[channel];
text->x = (float)(MSG_ReadShort( msg ) / 8192.0f);
text->y = (float)(MSG_ReadShort( msg ) / 8192.0f);
@@ -677,12 +669,9 @@ 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];
int hSound = cl.sound_index[index];
if( !hSound )
{
@@ -690,7 +679,7 @@ static const char *CL_SoundFromIndex( int index )
return NULL;
}
sfx = S_GetSfxByHandle( hSound );
sfx_t *sfx = S_GetSfxByHandle( hSound );
if( !sfx )
{
Con_DPrintf( S_ERROR "%s: bad sfx for index %i\n", __func__, index );
@@ -737,8 +726,6 @@ 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;
@@ -755,8 +742,8 @@ qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *
if( *y >= scissor->y + scissor->height )
return false;
dudx = (*u1 - *u0) / *width;
dvdy = (*v1 - *v0) / *height;
float dudx = (*u1 - *u0) / *width;
float dvdy = (*v1 - *v0) / *height;
if( *x < scissor->x )
{
@@ -943,9 +930,7 @@ void CL_DrawHUD( int state )
static void CL_ClearUserMessage( char *pszName, int svc_num )
{
int i;
for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )
for( int 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;
}
@@ -1069,9 +1054,7 @@ free studio cache on change level
*/
void CL_ClearSpriteTextures( void )
{
int i;
for( i = 1; i < MAX_CLIENT_SPRITES; i++ )
for( int i = 1; i < MAX_CLIENT_SPRITES; i++ )
{
if( clgame.sprites[i].needload == NL_UNREFERENCED )
continue;
@@ -1209,8 +1192,6 @@ 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 );
@@ -1240,7 +1221,8 @@ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite,
}
}
buf = FS_LoadFile( szSpriteName, &size, false );
fs_offset_t size;
byte *buf = FS_LoadFile( szSpriteName, &size, false );
if( buf == NULL )
return false;
@@ -1277,7 +1259,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla
{
char name[MAX_QPATH];
model_t *mod;
int i, start;
int i;
if( COM_StringEmptyOrNULL( filename ))
{
@@ -1307,7 +1289,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla
// 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;
int start = type == SPR_HUDSPRITE ? 0 : MAX_CLIENT_SPRITES / 2;
for( i = 0, mod = &clgame.sprites[start]; i < MAX_CLIENT_SPRITES / 2; i++, mod++ )
{
@@ -1353,9 +1335,9 @@ pfnSPR_LoadExt
*/
HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags )
{
model_t *spr;
model_t *spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags );
if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL )
if( spr == NULL )
return 0;
return (spr - clgame.sprites) + 1; // return index
@@ -1371,9 +1353,9 @@ function exported for support GoldSrc Monitor utility
HSPRITE EXPORT pfnSPR_Load( const char *szPicName );
HSPRITE EXPORT pfnSPR_Load( const char *szPicName )
{
model_t *spr;
model_t *spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 );
if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL )
if( spr == NULL )
return 0;
return (spr - clgame.sprites) + 1; // return index
@@ -1387,16 +1369,16 @@ 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];
model_t *mod = &clgame.sprites[index];
if( mod->needload == NL_NEEDS_LOADED )
{
int type = FBitSet( mod->flags, MODEL_CLIENT ) ? SPR_HUDSPRITE : SPR_MAPSPRITE;
int type = FBitSet( mod->flags, MODEL_CLIENT ) ? SPR_HUDSPRITE : SPR_MAPSPRITE;
if( CL_LoadHudSprite( mod->name, mod, type, mod->numtexinfo ))
return mod;
@@ -1423,7 +1405,7 @@ int EXPORT pfnSPR_Frames( HSPRITE hPic )
{
int numFrames = 0;
ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
return numFrames;
}
@@ -1438,7 +1420,7 @@ static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame )
{
int sprHeight = 0;
ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
return sprHeight;
}
@@ -1453,7 +1435,7 @@ static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame )
{
int sprWidth = 0;
ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
return sprWidth;
}
@@ -1865,8 +1847,6 @@ 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] )
@@ -1876,7 +1856,7 @@ static void GAME_EXPORT pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo
return;
}
player = &cl.players[ent_num];
player_info_t *player = &cl.players[ent_num];
pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false;
pinfo->name = player->name;
pinfo->model = player->model;
@@ -1907,11 +1887,9 @@ 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];
int hSound = cl.sound_index[iSound];
if( !hSound ) return;
S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING );
@@ -2172,7 +2150,7 @@ pfnGetViewModel
=============
*/
static cl_entity_t* GAME_EXPORT CL_GetViewModel( void )
static cl_entity_t *GAME_EXPORT CL_GetViewModel( void )
{
return &clgame.viewent;
}
@@ -2197,8 +2175,6 @@ 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 )
{
@@ -2213,7 +2189,7 @@ static void GAME_EXPORT pfnCalcShake( void )
return;
}
frametime = cl_clientframetime();
float frametime = cl_clientframetime();
if( cl.time > shake->next_shake )
{
@@ -2221,20 +2197,20 @@ static void GAME_EXPORT pfnCalcShake( void )
shake->next_shake = (float)cl.time + shake->frequency / shake->duration;
// randomize each shake
for( i = 0; i < 3; i++ )
for( int 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;
float fraction = ((float)cl.time - shake->time ) / shake->duration;
float 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++ )
for( int i = 0; i < 3; i++ )
shake->applied_offset[i] = shake->offset[i] * fraction;
// apply roll angle
@@ -2346,10 +2322,7 @@ 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 );
word eventIndex = CL_EventIndex( eventname );
if( eventIndex >= MAX_EVENTS )
return;
@@ -2357,12 +2330,12 @@ static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname )
if( entnum < 0 || entnum >= clgame.maxEntities )
return;
es = &cl.events;
event_state_t *es = &cl.events;
// find all events with specified index and kill it
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
for( int i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
event_info_t *ei = &es->ei[i];
if( ei->index == eventIndex && ei->entity_index == entnum )
{
@@ -2392,7 +2365,6 @@ CL_FindModelIndex
static int GAME_EXPORT CL_FindModelIndex( const char *m )
{
char filepath[MAX_QPATH];
int i;
if( COM_StringEmptyOrNULL( m ))
return 0;
@@ -2400,7 +2372,7 @@ static int GAME_EXPORT CL_FindModelIndex( const char *m )
Q_strncpy( filepath, m, sizeof( filepath ));
COM_FixSlashes( filepath );
for( i = 0; i < cl.nummodels; i++ )
for( int i = 0; i < cl.nummodels; i++ )
{
if( !cl.models[i+1] )
continue;
@@ -2732,9 +2704,7 @@ pfnLoadMapSprite
*/
static model_t *pfnLoadMapSprite( const char *filename )
{
model_t *mod;
mod = Mod_FindName( filename, false );
model_t *mod = Mod_FindName( filename, false );
if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 ))
return mod;
@@ -2810,12 +2780,10 @@ 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 );
convar_t *var = Cvar_FindVar( key );
if( var && FBitSet( var->flags, FCVAR_USERINFO ))
{
@@ -2896,11 +2864,10 @@ pfnGetMousePos
*/
static void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt )
{
int x, y;
if( !ppt )
return;
int x, y;
Platform_GetMousePos( &x, &y );
ppt->x = x;
@@ -3049,11 +3016,11 @@ static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int
{
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 );
int width;
CL_DrawStringLen( &cls.creditsFont, str, &width, NULL, flags );
x -= width;
@@ -3233,11 +3200,9 @@ 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;
float r = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness;
float g = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness;
float b = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness;
ref.dllFuncs.Color4f( r, g, b, 1.0f );
}
@@ -3284,11 +3249,9 @@ NOTE: dlights are ignored
*/
void TriLightAtPoint( float *pos, float *value )
{
colorVec vLightColor;
if( !pos || !value ) return;
vLightColor = ref.dllFuncs.R_LightPoint( pos );
colorVec vLightColor = ref.dllFuncs.R_LightPoint( pos );
value[0] = vLightColor.r;
value[1] = vLightColor.g;
@@ -3321,9 +3284,9 @@ bind current texture
*/
int TriSpriteTexture( model_t *pSpriteModel, int frame )
{
int gl_texturenum;
int gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame );
if(( gl_texturenum = ref.dllFuncs.R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
if( gl_texturenum <= 0 )
return 0;
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );
@@ -3464,13 +3427,10 @@ 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++ )
for( int i = 0; i < MAX_REQUESTS; i++ )
{
nr = &clgame.net_requests[i];
net_request_t *nr = &clgame.net_requests[i];
if( clgame.net_requests[i].resp.context == context )
{
@@ -3495,13 +3455,10 @@ 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++ )
for( int i = 0; i < MAX_REQUESTS; i++ )
{
nr = &clgame.net_requests[i];
net_request_t *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;
@@ -3928,6 +3885,68 @@ void CL_UnloadProgs( void )
memset( &clgame, 0, sizeof( clgame ));
}
static model_t *pfnStudio_Mod_ForName( const char *name, int crash )
{
return Mod_ForName( name, crash, false );
}
static void *pfnStudio_Mod_Extradata( model_t *mod )
{
return Mod_StudioExtradata( mod );
}
static void pfnStudio_GetAliasScale( float *x, float *y )
{
if( x ) *x = 1.0f;
if( y ) *y = 1.0f;
}
static float ***pfnStudio_GetAliasTransform( void )
{
return NULL;
}
static model_t *pfnStudio_GetChromeSprite( void )
{
return cl_sprite_shell;
}
static int pfnStudio_IsHardware( void )
{
return 1;
}
static engine_studio_api_t gStudioAPI =
{
.Mem_Calloc = Mod_Calloc,
.Cache_Check = Mod_CacheCheck,
.LoadCacheFile = Mod_LoadCacheFile,
.Mod_ForName = pfnStudio_Mod_ForName,
.Mod_Extradata = pfnStudio_Mod_Extradata,
.GetModelByIndex = CL_ModelHandle,
.GetCvar = pfnCVarGetPointer,
.GetChromeSprite = pfnStudio_GetChromeSprite,
.GetAliasScale = pfnStudio_GetAliasScale,
.StudioGetAliasTransform = pfnStudio_GetAliasTransform,
.GetViewEntity = CL_GetViewModel,
.IsHardware = pfnStudio_IsHardware,
};
static void CL_InitStudioAPI( void )
{
static r_studio_interface_t gDefaultStudioDraw;
if( !ref.dllFuncs.R_StudioFillAPI( &gStudioAPI, &gDefaultStudioDraw ))
return;
r_studio_interface_t *pStudioDraw = &gDefaultStudioDraw;
if( clgame.dllFuncs.pfnGetStudioModelInterface )
clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI );
ref.dllFuncs.R_StudioSetDrawInterface( pStudioDraw );
}
qboolean CL_LoadProgs( const char *name )
{
static playermove_t gpMove;
@@ -3935,7 +3954,6 @@ qboolean CL_LoadProgs( const char *name )
qboolean valid_single_export = false;
qboolean missed_exports = false;
qboolean try_internal_vgui_support = GI->internal_vgui_support;
int i;
if( clgame.hInstance ) CL_UnloadProgs();
@@ -3991,7 +4009,7 @@ qboolean CL_LoadProgs( const char *name )
if( GetClientAPI != NULL ) // check critical functions again
valid_single_export = ValidateExports( cdll_exports, ARRAYSIZE( cdll_exports ));
for( i = 0; i < ARRAYSIZE( cdll_exports ); i++ )
for( int i = 0; i < ARRAYSIZE( cdll_exports ); i++ )
{
if( *(cdll_exports[i].func) != NULL )
continue; // already got through 'F' or 'GetClientAPI'
@@ -4022,7 +4040,7 @@ qboolean CL_LoadProgs( const char *name )
if( !valid_single_export )
ClearExports( cdll_new_exports, ARRAYSIZE( cdll_new_exports ));
for( i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ )
for( int i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ )
{
if( *(cdll_new_exports[i].func) != NULL )
continue; // already gott through 'F' or 'GetClientAPI'
@@ -4065,7 +4083,8 @@ qboolean CL_LoadProgs( const char *name )
// initialize game
clgame.dllFuncs.pfnInit();
ref.dllFuncs.CL_InitStudioAPI();
CL_InitStudioAPI();
S_InitSoundAPI();
return true;
}

View File

@@ -78,8 +78,6 @@ void UI_MouseMove( int x, int y )
void UI_SetActiveMenu( qboolean fActive )
{
movie_state_t *cin_state;
if( !gameui.hInstance )
{
if( !fActive )
@@ -93,7 +91,7 @@ void UI_SetActiveMenu( qboolean fActive )
if( !fActive )
{
// close logo when menu is shutdown
cin_state = AVI_GetState( CIN_LOGO );
movie_state_t *cin_state = AVI_GetState( CIN_LOGO );
AVI_CloseVideo( cin_state );
}
}
@@ -188,7 +186,7 @@ void UI_ShowConnectionWarning( void )
if( cls.state != ca_connected )
return;
if( Host_IsLocalClient() )
if( Host_IsLocalClient( ))
return;
if( ++cl.lostpackets == 8 )
@@ -356,12 +354,10 @@ static float GAME_EXPORT UI_GetLogoLength( void )
static void UI_UpdateUserinfo( void )
{
player_info_t *player;
if( !host.userinfo_changed )
return;
player = &gameui.playerinfo;
player_info_t *player = &gameui.playerinfo;
Q_strncpy( player->userinfo, cls.userinfo, sizeof( player->userinfo ));
Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name ));
@@ -427,10 +423,8 @@ static void UI_ToOldGameInfo( GAMEINFO *out, const gameinfo2_t *in )
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++ )
for( int i = 0; i < FI->numgames; i++ )
UI_ConvertGameInfo( &gameui.modsInfo[i], FI->games[i] );
}
@@ -755,10 +749,9 @@ 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 );
int drawLen = Con_DrawString( x, y, string, gameui.ds.textColor );
MakeRGBA( gameui.ds.textColor, 255, 255, 255, 255 );
return (x + drawLen); // exclude color prexfixes
@@ -958,14 +951,12 @@ static GAMEINFO ** GAME_EXPORT pfnGetGamesList( int *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++ )
for( int 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] );
@@ -1135,12 +1126,10 @@ static void GAME_EXPORT pfnCon_DefaultColor( int r, int g, int b )
static void GAME_EXPORT pfnSetCursor( void *hCursor )
{
uintptr_t cursor;
if( !gameui.use_extended_api )
return; // ignore original Xash menus
cursor = (uintptr_t)hCursor;
uintptr_t cursor = (uintptr_t)hCursor;
if( cursor < dc_user || cursor > dc_last )
return;
@@ -1335,6 +1324,14 @@ void UI_UnloadProgs( void )
memset( &gameui, 0, sizeof( gameui ));
}
void *UI_GetMenuFactory( void )
{
if( !gameui.hInstance )
return NULL;
return COM_GetProcAddress( gameui.hInstance, "CreateInterface" );
}
qboolean UI_LoadProgs( void )
{
static ui_enginefuncs_t gpEngfuncs;

View File

@@ -63,12 +63,10 @@ static int pfnDrawScaledCharacter( int x, int y, int number, int r, int g, int b
if( fabs( g_font_scale - scale ) > 0.1f ||
g_scaled_font.hFontTexture != cls.creditsFont.hFontTexture )
{
int i;
g_scaled_font = cls.creditsFont;
g_scaled_font.scale *= scale;
g_scaled_font.charHeight *= scale;
for( i = 0; i < ARRAYSIZE( g_scaled_font.charWidths ); i++ )
for( int i = 0; i < ARRAYSIZE( g_scaled_font.charWidths ); i++ )
g_scaled_font.charWidths[i] *= scale;
g_font_scale = scale;

View File

@@ -101,10 +101,9 @@ CL_SetIdealPitch
void CL_SetIdealPitch( void )
{
float angleval, sinval, cosval;
int i, j, step, dir, steps;
int i, dir, steps;
float z[MAX_FORWARD];
vec3_t top, bottom;
pmtrace_t tr;
if( cl.local.onground == -1 )
return;
@@ -116,6 +115,8 @@ void CL_SetIdealPitch( void )
// 160 or so units to see what's below
for( i = 0; i < MAX_FORWARD; i++ )
{
pmtrace_t tr;
top[0] = cl.simorg[0] + cosval * (i + 3.0f) * 12.0f;
top[1] = cl.simorg[1] + sinval * (i + 3.0f) * 12.0f;
top[2] = cl.simorg[2] + cl.viewheight[2];
@@ -137,9 +138,10 @@ void CL_SetIdealPitch( void )
dir = 0;
steps = 0;
for( j = 1; j < i; j++ )
for( int j = 1; j < i; j++ )
{
step = z[j] - z[j-1];
int step = z[j] - z[j-1];
if( step > -ON_EPSILON && step < ON_EPSILON )
continue;
@@ -244,15 +246,10 @@ This sets up the first phase.
*/
void GAME_EXPORT CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient )
{
entity_state_t *state;
predicted_player_t *player;
cl_entity_t *ent;
int i;
for( i = 0; i < MAX_CLIENTS; i++ )
for( int i = 0; i < MAX_CLIENTS; i++ )
{
state = &cl.frames[cl.parsecountmod].playerstate[i];
player = &cls.predicted_players[i];
entity_state_t *state = &cl.frames[cl.parsecountmod].playerstate[i];
predicted_player_t *player = &cls.predicted_players[i];
player->active = false;
@@ -279,7 +276,7 @@ void GAME_EXPORT CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient )
}
else
{
ent = CL_GetEntityByIndex( i + 1 );
cl_entity_t *ent = CL_GetEntityByIndex( i + 1 );
CL_ComputePlayerOrigin( ent );
@@ -397,16 +394,13 @@ collect solid entities
*/
static void CL_AddLinksToPmove( frame_t *frame )
{
entity_state_t *state;
model_t *model;
physent_t *pe;
int i;
if( !frame->valid ) return;
for( i = 0; i < frame->num_entities; i++ )
for( int i = 0; i < frame->num_entities; i++ )
{
state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
entity_state_t *state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
model_t *model;
physent_t *pe;
if( state->number >= 1 && state->number <= cl.maxclients )
continue;
@@ -506,18 +500,14 @@ pmove must be setup with world and solid entity hulls before calling
*/
void GAME_EXPORT CL_SetSolidPlayers( int playernum )
{
entity_state_t *state;
predicted_player_t *player;
physent_t *pe;
int i;
if( !cl_solid_players.value )
return;
for( i = 0; i < MAX_CLIENTS; i++ )
for( int i = 0; i < MAX_CLIENTS; i++ )
{
state = &cl.frames[cl.parsecountmod].playerstate[i];
player = &cls.predicted_players[i];
entity_state_t *state = &cl.frames[cl.parsecountmod].playerstate[i];
predicted_player_t *player = &cls.predicted_players[i];
physent_t *pe;
if( playernum == -1 )
{
@@ -562,18 +552,17 @@ CL_WaterEntity
*/
int GAME_EXPORT CL_WaterEntity( const float *rgflPos )
{
physent_t *pe;
hull_t *hull;
vec3_t test, offset;
int i, oldhull;
int oldhull;
if( !rgflPos ) return -1;
oldhull = clgame.pmove->usehull;
for( i = 0; i < clgame.pmove->numphysent; i++ )
for( int i = 0; i < clgame.pmove->numphysent; i++ )
{
pe = &clgame.pmove->physents[i];
physent_t *pe = &clgame.pmove->physents[i];
hull_t *hull;
vec3_t test, offset;
if( pe->solid != SOLID_NOT ) // disabled ?
continue;
@@ -657,9 +646,8 @@ returns water brush where inside pos
*/
cl_entity_t *CL_GetWaterEntity( const float *rgflPos )
{
int entnum;
int entnum = CL_WaterEntity( rgflPos );
entnum = CL_WaterEntity( rgflPos );
if( entnum <= 0 ) return NULL; // world or not water
return CL_GetEntityByIndex( entnum );
@@ -732,8 +720,6 @@ CL_InitClientMove
*/
void CL_InitClientMove( void )
{
int i;
Pmove_Init ();
clgame.pmove->server = false; // running at client
@@ -741,7 +727,7 @@ void CL_InitClientMove( void )
clgame.pmove->runfuncs = false;
// enumerate client hulls
for( i = 0; i < MAX_MAP_HULLS; i++ )
for( int i = 0; i < MAX_MAP_HULLS; i++ )
{
if( clgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] ))
Con_Reportf( "CL: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\n", i,

View File

@@ -172,8 +172,8 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe
return cl.local.waterlevel;
case PARM_LOCAL_HEALTH:
return cl.local.health;
case PARM_LOCAL_GAME:
return Host_IsLocalGame();
case PARM_SINGLEPLAYER_GAME:
return Host_IsSinglePlayerGame();
case PARM_NUMENTITIES:
return pfnNumberOfEntities();
case PARM_GET_CLIENT_PTR:
@@ -214,6 +214,11 @@ static void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, floa
return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound
}
static void pfnAVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data )
{
ref.dllFuncs.GL_UpdateTexture( texture, cols, rows, width, height, data, PF_BGRA_32 );
}
static render_api_t gRenderAPI =
{
pfnRenderGetParm, // GL_RenderGetParm,
@@ -243,7 +248,7 @@ static render_api_t gRenderAPI =
AVI_GetVideoInfo,
AVI_GetVideoFrameNumber,
AVI_GetVideoFrame,
NULL, // R_UploadStretchRaw,
pfnAVI_UploadRawFrame, // AVI_UploadRawFrame
AVI_FreeVideo,
AVI_IsActive,
pfnAVI_StreamSound,
@@ -283,40 +288,6 @@ static render_api_t gRenderAPI =
COM_SetRandomSeed,
};
static void R_FillRenderAPIFromRef( render_api_t *to, const ref_interface_t *from )
{
to->GetDetailScaleForTexture = from->GetDetailScaleForTexture;
to->GetExtraParmsForTexture = from->GetExtraParmsForTexture;
to->GetFrameTime = from->GetFrameTime;
to->R_SetCurrentEntity = from->R_SetCurrentEntity;
to->R_SetCurrentModel = from->R_SetCurrentModel;
to->GL_FindTexture = from->GL_FindTexture;
to->GL_TextureName = from->GL_TextureName;
to->GL_TextureData = from->GL_TextureData;
to->GL_LoadTexture = from->GL_LoadTexture;
to->GL_CreateTexture = from->GL_CreateTexture;
to->GL_LoadTextureArray = from->GL_LoadTextureArray;
to->GL_CreateTextureArray = from->GL_CreateTextureArray;
to->GL_FreeTexture = from->GL_FreeTexture;
to->DrawSingleDecal = from->DrawSingleDecal;
to->R_DecalSetupVerts = from->R_DecalSetupVerts;
to->R_EntityRemoveDecals = from->R_EntityRemoveDecals;
to->AVI_UploadRawFrame = from->AVI_UploadRawFrame;
to->GL_Bind = from->GL_Bind;
to->GL_SelectTexture = from->GL_SelectTexture;
to->GL_LoadTextureMatrix = from->GL_LoadTextureMatrix;
to->GL_TexMatrixIdentity = from->GL_TexMatrixIdentity;
to->GL_CleanUpTextureUnits = from->GL_CleanUpTextureUnits;
to->GL_TexGen = from->GL_TexGen;
to->GL_TextureTarget = from->GL_TextureTarget;
to->GL_TexCoordArrayMode = from->GL_TexCoordArrayMode;
to->GL_UpdateTexSize = from->GL_UpdateTexSize;
to->GL_DrawParticles = from->GL_DrawParticles;
to->LightVec = from->LightVec;
to->StudioGetTexture = from->StudioGetTexture;
to->GL_GetProcAddress = from->R_GetProcAddress;
}
/*
===============
R_InitRenderAPI
@@ -329,8 +300,15 @@ qboolean R_InitRenderAPI( void )
// make sure what render functions is cleared
memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));
// fill missing functions from renderer
R_FillRenderAPIFromRef( &gRenderAPI, &ref.dllFuncs );
gRenderAPI.GetDetailScaleForTexture = ref.dllFuncs.R_GetDetailScaleForTexture;
gRenderAPI.GL_FindTexture = ref.dllFuncs.GL_FindTexture;
gRenderAPI.GL_TextureName = ref.dllFuncs.GL_TextureName;
gRenderAPI.GL_TextureData = ref.dllFuncs.GL_TextureData;
gRenderAPI.GL_LoadTexture = ref.dllFuncs.GL_LoadTexture;
gRenderAPI.GL_FreeTexture = ref.dllFuncs.GL_FreeTexture;
gRenderAPI.GL_Bind = ref.dllFuncs.GL_Bind;
ref.dllFuncs.R_FillRenderAPI( &gRenderAPI );
if( clgame.dllFuncs.pfnGetRenderInterface )
{

View File

@@ -72,18 +72,15 @@ static qboolean CheckSkybox( const char *name, char out[SKYBOX_MAX_SIDES][MAX_ST
{
static const char *skybox_ext[3] = { "dds", "tga", "bmp" };
static const char *skybox_delim[2] = { "", "_" }; // no space for HL style, underscore for Q1 style
int i;
// search for skybox images
for( i = 0; i < ARRAYSIZE( skybox_ext ); i++ )
for( int i = 0; i < ARRAYSIZE( skybox_ext ); i++ )
{
int j;
for( j = 0; j < ARRAYSIZE( skybox_delim ); j++ )
for( int j = 0; j < ARRAYSIZE( skybox_delim ); j++ )
{
int k, num_checked_sides = 0;
int num_checked_sides = 0;
for( k = 0; k < SKYBOX_MAX_SIDES; k++ )
for( int k = 0; k < SKYBOX_MAX_SIDES; k++ )
{
char sidename[MAX_VA_STRING];
@@ -109,7 +106,6 @@ void R_SetupSky( const char *name )
char sidenames[SKYBOX_MAX_SIDES][MAX_STRING];
int skyboxTextures[SKYBOX_MAX_SIDES] = { 0 };
int i, len;
qboolean result;
if( COM_StringEmptyOrNULL( name ))
{
@@ -125,10 +121,9 @@ void R_SetupSky( const char *name )
if( loadname[len - 1] == '_' )
loadname[len - 1] = '\0';
result = CheckSkybox( loadname, sidenames );
// to prevent infinite recursion if default skybox was missed
if( !result && Q_stricmp( name, DEFAULT_SKYBOX_NAME ))
if( !CheckSkybox( loadname, sidenames ) && Q_stricmp( name, DEFAULT_SKYBOX_NAME ))
{
Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", name );
R_SetupSky( DEFAULT_SKYBOX_NAME ); // force to default
@@ -287,13 +282,6 @@ static entity_state_t *R_StudioGetPlayerState( int index )
return &cl.frames[cl.parsecountmod].playerstate[index];
}
static int pfnGetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )
{
return clgame.dllFuncs.pfnGetStudioModelInterface ?
clgame.dllFuncs.pfnGetStudioModelInterface( version, ppinterface, pstudio ) :
0;
}
static const bpc_desc_t *pfnImage_GetPFDesc( int idx )
{
return &PFDesc[idx];
@@ -329,11 +317,6 @@ static qboolean R_Init_Video_( ref_graphic_apis_t type )
return R_Init_Video( type );
}
static mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node )
{
// FIXME: get rid of this on next RefAPI update
return Mod_PointInLeaf( p, node, cl.models[1] );
}
static const ref_api_t gEngfuncs =
{
@@ -375,7 +358,7 @@ static const ref_api_t gEngfuncs =
Mod_SampleSizeForFace,
Mod_BoxVisible,
pfnMod_PointInLeaf,
Mod_PointInLeaf,
R_DrawWorldHull,
R_DrawModelHull,
@@ -411,7 +394,6 @@ static const ref_api_t gEngfuncs =
Mod_CacheCheck,
Mod_LoadCacheFile,
Mod_Calloc,
pfnGetStudioModelInterface,
_Mem_AllocPool,
_Mem_FreePool,
@@ -465,6 +447,7 @@ static const ref_api_t gEngfuncs =
&g_fsapi,
R_GetWindowHandle,
R_GetSpriteFrame,
};
static void R_UnloadProgs( void )
@@ -487,28 +470,78 @@ static void R_UnloadProgs( void )
memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs ));
}
static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src )
static void CL_FillTriAPI( triangleapi_t *dst )
{
dst->version = TRI_API_VERSION;
dst->Begin = src->Begin;
dst->RenderMode = TriRenderMode;
dst->End = src->End;
dst->Color4f = TriColor4f;
dst->Color4ub = TriColor4ub;
dst->TexCoord2f = src->TexCoord2f;
dst->Vertex3f = src->Vertex3f;
dst->Vertex3fv = src->Vertex3fv;
dst->Brightness = TriBrightness;
dst->CullFace = TriCullFace;
dst->SpriteTexture = TriSpriteTexture;
dst->WorldToScreen = TriWorldToScreen;
dst->Fog = src->Fog;
dst->ScreenToWorld = src->ScreenToWorld;
dst->GetMatrix = src->GetMatrix;
dst->BoxInPVS = TriBoxInPVS;
dst->LightAtPoint = TriLightAtPoint;
dst->Color4fRendermode = TriColor4fRendermode;
dst->FogParams = src->FogParams;
dst->Begin = ref.dllFuncs.Begin;
dst->End = ref.dllFuncs.End;
dst->Vertex3f = ref.dllFuncs.Vertex3f;
dst->Vertex3fv = ref.dllFuncs.Vertex3fv;
ref.dllFuncs.R_FillTriAPI( dst );
}
static void R_CreateBuiltinTextures( void )
{
// default checkerboard
uint data16x16[256];
for( int y = 0; y < 16; y++ )
{
for( int x = 0; x < 16; x++ )
{
if(( y < 8 ) ^ ( x < 8 ))
data16x16[y * 16 + x] = HostFourCC( 255, 0, 255, 255 );
else
data16x16[y * 16 + x] = HostFourCC( 0, 0, 0, 255 );
}
}
ref.dllFuncs.GL_CreateTexture( REF_DEFAULT_TEXTURE, 16, 16, data16x16, 0 );
// particle texture
uint particle[64];
for( int y = 0; y < 8; y++ )
{
for( int x = 0; x < 8; x++ )
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 0 ); // fill it with invisible white
}
// a1ba: moved one pixel down and to the right to make it look better with linear interpolation
// set to 0 to make it look like in goldsrc
const int corner = 1;
for( int y = corner; y < 4 + corner; y++ )
{
for( int x = corner; x < 4 + corner; x++ )
{
if(( x == corner || x == 3 + corner ) && ( y == corner || y == 3 + corner ))
continue;
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 255 );
}
}
ref.dllFuncs.GL_CreateTexture( REF_PARTICLE_TEXTURE, 8, 8, particle, TF_CLAMP|TF_HAS_ALPHA );
// solid colors
uint data4x4[16];
memset( data4x4, 0xFF, sizeof( data4x4 ));
ref.dllFuncs.GL_CreateTexture( REF_WHITE_TEXTURE, 4, 4, data4x4, 0 );
for( int x = 0; x < 16; x++ )
data4x4[x] = HostFourCC( 127, 127, 127, 255 );
ref.dllFuncs.GL_CreateTexture( REF_GRAY_TEXTURE, 4, 4, data4x4, 0 );
for( int x = 0; x < 16; x++ )
data4x4[x] = HostFourCC( 0, 0, 0, 255 );
ref.dllFuncs.GL_CreateTexture( REF_BLACK_TEXTURE, 4, 4, data4x4, 0 );
}
static qboolean R_LoadProgs( const char *name )
@@ -554,8 +587,8 @@ static qboolean R_LoadProgs( const char *name )
Cvar_FullSet( "host_refloaded", "1", FCVAR_READ_ONLY );
ref.initialized = true;
// initialize TriAPI callbacks
CL_FillTriAPIFromRef( &gTriApi, &ref.dllFuncs );
R_CreateBuiltinTextures();
CL_FillTriAPI( &gTriApi );
return true;
}

View File

@@ -35,7 +35,6 @@ static CVAR_DEFINE( v_gamma, "gamma", "2.5", FCVAR_ARCHIVE, "gamma amount" );
static void BuildGammaTable( const float gamma, const float brightness, const float texgamma, const float lightgamma )
{
float g1, g2, g3;
int i;
if( gamma != 0.0 )
g1 = 1.0 / gamma;
@@ -50,7 +49,7 @@ static void BuildGammaTable( const float gamma, const float brightness, const fl
else
g3 = 0.05;
for( i = 0; i < 256; i++ )
for( int i = 0; i < 256; i++ )
{
// keep it float or texgamma test will fail with -ffast-math
float d = pow( i / 255.0, (double)g2 );
@@ -58,11 +57,9 @@ static void BuildGammaTable( const float gamma, const float brightness, const fl
texgammatable[i] = bound( 0, inf, 255 );
}
for( i = 0; i < 1024; i++ )
for( int i = 0; i < 1024; i++ )
{
double d;
float f = pow( i / 1023.0, (double)lightgamma );
int inf;
if( brightness > 1.0 )
f *= brightness;
@@ -72,8 +69,8 @@ static void BuildGammaTable( const float gamma, const float brightness, const fl
else
f = (( f - g3 ) / ( 1.0 - g3 )) * 0.875 + 0.125;
d = pow( (double)f, (double)g1 ); // do not remove the cast, or tests fail
inf = d * 1023.0;
double d = pow( (double)f, (double)g1 ); // do not remove the cast, or tests fail
int inf = d * 1023.0;
lightgammatable[i] = bound( 0, inf, 1023 );
// do these calculations in the same loop...
@@ -255,11 +252,9 @@ static void Test_PrecomputedGammaTables( void )
while(( data = Test_GetGammaTables( i )))
{
int j;
BuildGammaTable( data->gamma, data->brightness, data->texgamma, data->lightgamma );
for( j = 0; j < 1024; j++ )
for( int j = 0; j < 1024; j++ )
{
if( j < 256 )
{

View File

@@ -80,7 +80,7 @@ static uint BloomFilter_Weight( bloomfilter_t value )
return weight;
}
static qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str )
MAYBE_UNUSED static qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str )
{
bloomfilter_t value = BloomFilter_ProcessStr( str );
@@ -102,9 +102,8 @@ static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path );
static void ID_BloomFilter_f( void )
{
bloomfilter_t value = 0;
int i;
for( i = 1; i < Cmd_Argc(); i++ )
for( int i = 1; i < Cmd_Argc(); i++ )
value |= BloomFilter_ProcessStr( Cmd_Argv( i ) );
Msg( "%d %016"PRIX64"\n", BloomFilter_Weight( value ), value );
@@ -119,7 +118,6 @@ static qboolean ID_VerifyHEX( const char *hex )
uint chars = 0;
char prev = 0;
qboolean monotonic = true; // detect 11:22...
int weight = 0;
while( *hex++ )
{
@@ -142,6 +140,7 @@ static qboolean ID_VerifyHEX( const char *hex )
if( monotonic )
return false;
int weight = 0;
while( chars )
{
if( chars & 1 )
@@ -170,7 +169,7 @@ static void ID_VerifyHEX_f( void )
static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
{
int cpuinfofd = open( "/proc/cpuinfo", O_RDONLY );
char buffer[1024], *pbuf, *pbuf2;
char buffer[1024];
int ret;
if( cpuinfofd < 0 )
@@ -189,12 +188,12 @@ static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
if( !ret )
return false;
pbuf = Q_stristr( buffer, "Serial" );
char *pbuf = Q_stristr( buffer, "Serial" );
if( !pbuf )
return false;
pbuf += 6;
pbuf2 = Q_strchrnul( pbuf, '\n' );
char *pbuf2 = Q_strchrnul( pbuf, '\n' );
*pbuf2 = 0;
if( !ID_VerifyHEX( pbuf ) )
@@ -207,20 +206,18 @@ static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
static qboolean ID_ValidateNetDevice( const char *dev )
{
const char *prefix = "/sys/class/net";
byte *pfile;
int assignType;
// These devices are fake, their mac address is generated each boot, while assign_type is 0
if( !Q_strnicmp( dev, "ccmni", sizeof( "ccmni" ) ) ||
!Q_strnicmp( dev, "ifb", sizeof( "ifb" ) ) )
return false;
pfile = FS_LoadDirectFile( va( "%s/%s/addr_assign_type", prefix, dev ), NULL );
byte *pfile = FS_LoadDirectFile( va( "%s/%s/addr_assign_type", prefix, dev ), NULL );
// if NULL, it may be old kernel
if( pfile )
{
assignType = Q_atoi( (char*)pfile );
int assignType = Q_atoi( (char*)pfile );
Mem_Free( pfile );
@@ -500,12 +497,10 @@ static int ID_CheckWMIC( bloomfilter_t value, const wchar_t *cmdline )
pbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header
while(( pbuf = COM_ParseFile( pbuf, token, sizeof( token ))))
{
bloomfilter_t filter;
if( !ID_VerifyHEX( token ))
continue;
filter = BloomFilter_ProcessStr( token );
bloomfilter_t filter = BloomFilter_ProcessStr( token );
count += ( filter & value ) == filter;
}

View File

@@ -39,6 +39,10 @@ void IN_DeactivateMouse( void );
void IN_MouseSavePos( void );
void IN_MouseRestorePos( void );
void IN_ToggleClientMouse( int newstate, int oldstate );
void IN_GyroInit( void );
void IN_GyroCheckAvailability( void );
void IN_GyroEvent( vec3_t data );
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw );
uint IN_CollectInputDevices( void );
void IN_LockInputDevices( qboolean lock );
@@ -95,6 +99,15 @@ qboolean Touch_WantVisibleCursor( void );
void Touch_NotifyResize( void );
#endif
//
// in_osk.c
//
extern convar_t osk_enable;
void OSK_Init( void );
qboolean OSK_KeyEvent( int key, int down );
void OSK_EnableTextInput( qboolean enable, qboolean force );
void OSK_Draw( void );
//
// in_joy.c
//

View File

@@ -0,0 +1,119 @@
/*
in_gyro.c - System gyroscope input code
Copyright (C) 2026 Xash3D FWGS contributors
This program is free software: you can 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 "input.h"
#include "client.h"
static CVAR_DEFINE_AUTO( gyro_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with built-in device gyroscope" );
static CVAR_DEFINE_AUTO( gyro_available, "0", FCVAR_READ_ONLY, "tells whether system gyroscope hardware is available or not" );
static CVAR_DEFINE_AUTO( gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for looking up and down" );
static CVAR_DEFINE_AUTO( gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity for turning left and right" );
static CVAR_DEFINE_AUTO( gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope sensitivity when tilting the device sideways" );
static CVAR_DEFINE_AUTO( gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope pitch axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope yaw axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "built-in gyroscope roll axis deadzone (deg/s)" );
// stores the latest instantaneous rotation rates from built-in gyroscope
static vec3_t gyro_speed;
/*
==============
IN_GyroInit
==============
*/
void IN_GyroInit( void )
{
Cvar_RegisterVariable( &gyro_enable );
Cvar_RegisterVariable( &gyro_available );
Cvar_RegisterVariable( &gyro_pitch );
Cvar_RegisterVariable( &gyro_yaw );
Cvar_RegisterVariable( &gyro_roll );
Cvar_RegisterVariable( &gyro_pitch_deadzone );
Cvar_RegisterVariable( &gyro_yaw_deadzone );
Cvar_RegisterVariable( &gyro_roll_deadzone );
}
/*
==============
IN_GyroCheckAvailability
One-time late check called after startup and configs
==============
*/
void IN_GyroCheckAvailability( void )
{
#if XASH_SDL
if( gyro_available.value )
return;
if( SDLash_GyroIsAvailable() )
{
Cvar_FullSet( "gyro_available", "1", FCVAR_READ_ONLY );
}
#endif
}
/*
=============
IN_GyroEvent
System gyroscope events from platform
=============
*/
void IN_GyroEvent( vec3_t data )
{
VectorCopy( data, gyro_speed );
}
/*
=============
IN_GyroFinalizeMove
Apply gyro movement to view angles
=============
*/
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
{
float orient_scale = 1.0f;
if( !gyro_enable.value || !gyro_available.value )
return;
platform_orientation_t orient = Platform_GetDisplayOrientation();
if( orient == ORIENTATION_LANDSCAPE_FLIPPED )
orient_scale = -1.0f;
// In Landscape mode axes are swapped relative to natural (Portrait) orientation
// Y axis rotation becomes Pitch (up/down)
// X axis rotation becomes Yaw (left/right)
float pitch_speed = -orient_scale * gyro_speed[1] * ( 180.0f / M_PI );
float yaw_speed = orient_scale * gyro_speed[0] * ( 180.0f / M_PI );
float roll_speed = orient_scale * gyro_speed[2] * ( 180.0f / M_PI );
if( fabs( pitch_speed ) < gyro_pitch_deadzone.value )
pitch_speed = 0.0f;
if( fabs( yaw_speed ) < gyro_yaw_deadzone.value )
yaw_speed = 0.0f;
if( fabs( roll_speed ) < gyro_roll_deadzone.value )
roll_speed = 0.0f;
*dpitch -= gyro_pitch.value * pitch_speed * host.realframetime;
*dyaw += gyro_yaw.value * yaw_speed * host.realframetime;
*dyaw += gyro_roll.value * roll_speed * host.realframetime;
VectorClear( gyro_speed );
}

View File

@@ -65,6 +65,10 @@ static CVAR_DEFINE_AUTO( joy_calibrated, "0", FCVAR_READ_ONLY, "tells whether cu
static CVAR_DEFINE_AUTO( joy_gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for looking up and down" );
static CVAR_DEFINE_AUTO( joy_gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for turning left and right" );
static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity when tilting the device sideways" );
static CVAR_DEFINE_AUTO( joy_gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope pitch axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( joy_gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope yaw axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( joy_gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope roll axis deadzone (deg/s)" );
static CVAR_DEFINE_AUTO( joy_gyro_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with gamepad gyroscope" );
static CVAR_DEFINE_AUTO( joy_debug, "0", 0, "visualize gamepad axes and buttons" );
// stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input
@@ -125,9 +129,8 @@ static void Joy_HatMotionEvent( int value )
{ JOY_HAT_LEFT, K_LEFTARROW },
{ JOY_HAT_RIGHT, K_RIGHTARROW },
};
int i;
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( value & keys[i].mask )
{
@@ -194,7 +197,7 @@ static int Joy_GetHatValueForAxis( const engineAxis_t engineAxis )
positive = JOY_HAT_RIGHT;
break;
case JOY_AXIS_FWD:
threshold = joy_side_key_threshold.value;
threshold = joy_forward_key_threshold.value;
negative = JOY_HAT_UP;
positive = JOY_HAT_DOWN;
break;
@@ -313,9 +316,8 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
if( FBitSet( joy_axis_binding.flags, FCVAR_CHANGED ) )
{
const char *bind = joy_axis_binding.string;
size_t i;
for( i = 0; bind[i]; i++ )
for( size_t i = 0; bind[i] && i < MAX_AXES; i++ )
{
switch( bind[i] )
{
@@ -337,12 +339,19 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
*dpitch += joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime;
*dyaw -= joy_yaw.value * (float)joyaxis[JOY_AXIS_YAW ].val/(float)SHRT_MAX * host.realframetime;
if( joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
if( joy_gyro_enable.value && joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
{
float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI );
float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI );
float roll_speed = joy_gyro_speed[2] * ( 180.0f / M_PI );
if( fabs( pitch_speed ) < joy_gyro_pitch_deadzone.value )
pitch_speed = 0.0f;
if( fabs( yaw_speed ) < joy_gyro_yaw_deadzone.value )
yaw_speed = 0.0f;
if( fabs( roll_speed ) < joy_gyro_roll_deadzone.value )
roll_speed = 0.0f;
*dpitch -= joy_gyro_pitch.value * pitch_speed * host.realframetime;
*dyaw += joy_gyro_yaw.value * yaw_speed * host.realframetime;
*dyaw += joy_gyro_roll.value * roll_speed * host.realframetime;
@@ -467,6 +476,12 @@ void Joy_DrawDebug( void )
if( joy_have_gyro.value )
{
static const char *gyronames[3] = { "GYRO P", "GYRO Y", "GYRO R" };
static const convar_t *const gyro_deadzone[3] =
{
&joy_gyro_pitch_deadzone,
&joy_gyro_yaw_deadzone,
&joy_gyro_roll_deadzone,
};
for( int i = 0; i < 3; i++ )
{
@@ -485,10 +500,22 @@ void Joy_DrawDebug( void )
float filled = fval * halfbar_w;
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + Q_min( 0, filled ), y, fabs( filled ), bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] );
// deadzone in deg/s; display scale is +-5 rad/s
float fthreshold = gyro_deadzone[i]->value * ( M_PI / 180.0f ) / 5.0f;
fthreshold = bound( 0.0f, fthreshold, 1.0f );
if( fthreshold > 0.0f )
{
float thresh_x = fthreshold * halfbar_w;
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, thresh_x * 2, bar_h, 180, 40, 40, 120 );
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
}
// mark the center
ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 );
// gyro doesn't have deadzones or prevval like axes
// gyro doesn't have prevval like axes
y += bar_h + 2;
}
}
@@ -506,8 +533,6 @@ void Joy_DrawDebug( void )
}
qboolean pressed = Key_IsDown( buttons[i] );
int btn_w = 0;
rgba_t color;
string name;
Q_strncpy( name, Key_KeynumToString( buttons[i] ), sizeof( name ));
@@ -516,8 +541,11 @@ void Joy_DrawDebug( void )
if( p )
*p = 0;
rgba_t color;
MakeRGBA( color, pressed ? 0 : 255, pressed ? 255 : 0, 0, 255 );
CL_DrawString( x, y, name, color, font, 0 );
int btn_w = 0;
CL_DrawStringLen( font, name, &btn_w, NULL, 0 );
x += btn_w + 6;
}
@@ -568,10 +596,16 @@ void Joy_Init( void )
Cvar_RegisterVariable( &joy_have_gyro );
Cvar_RegisterVariable( &joy_calibrated );
Cvar_RegisterVariable( &joy_enable );
Cvar_RegisterVariable( &joy_gyro_enable );
Cvar_RegisterVariable( &joy_gyro_pitch );
Cvar_RegisterVariable( &joy_gyro_yaw );
Cvar_RegisterVariable( &joy_gyro_roll );
Cvar_RegisterVariable( &joy_gyro_pitch_deadzone );
Cvar_RegisterVariable( &joy_gyro_yaw_deadzone );
Cvar_RegisterVariable( &joy_gyro_roll_deadzone );
Cvar_RegisterVariable( &joy_debug );
// renamed from -nojoy to -noenginejoy to not conflict with

View File

@@ -158,9 +158,6 @@ static const keyname_t keynames[] =
{ "INTERNATIONAL9", K_INTERNATIONAL + 8, "" },
};
static void OSK_EnableTextInput( qboolean enable, qboolean force );
static qboolean OSK_KeyEvent( int key, int down );
static CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "enable built-in on-screen keyboard" );
static CVAR_DEFINE_AUTO( key_rotate, "0", FCVAR_ARCHIVE|FCVAR_FILTERABLE, "rotate arrow keys (0-3)" );
/*
@@ -190,8 +187,6 @@ to be configured even if they don't have defined names.
*/
static int Key_StringToKeynum( const char *str )
{
int i;
if( !str || !str[0] )
return -1;
@@ -200,10 +195,17 @@ static int Key_StringToKeynum( const char *str )
// check for hex code
if( str[0] == '0' && str[1] == 'x' )
return Q_atoi( str );
{
int key = Q_atoi( str );
if( key >= ARRAYSIZE( keys ))
return -1;
return key;
}
// scan for a text match
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
for( int i = 0; i < ARRAYSIZE( keynames ); i++ )
{
if( !Q_stricmp( str, keynames[i].name ))
return keynames[i].keynum;
@@ -289,19 +291,16 @@ Key_GetKey
*/
static int Key_GetKey( const char *pBinding )
{
int i, len;
const char *p;
if( !pBinding ) return -1;
len = Q_strlen( pBinding );
int len = Q_strlen( pBinding );
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( !keys[i].binding )
continue;
p = keys[i].binding;
const char *p = keys[i].binding;
if( *p == '+' )
p++;
@@ -336,15 +335,13 @@ Key_Unbind_f
*/
static void Key_Unbind_f( void )
{
int b;
if( Cmd_Argc() != 2 )
{
Con_Printf( S_USAGE "unbind <key> : remove commands from a key\n" );
return;
}
b = Key_StringToKeynum( Cmd_Argv( 1 ));
int b = Key_StringToKeynum( Cmd_Argv( 1 ));
if( b == -1 )
{
@@ -368,9 +365,7 @@ Key_Unbindall_f
*/
static void Key_Unbindall_f( void )
{
int i;
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( keys[i].binding )
Key_SetBinding( i, "" );
@@ -388,17 +383,15 @@ Key_Reset_f
*/
static void Key_Reset_f( void )
{
int i;
// clear all keys first
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( keys[i].binding )
Key_SetBinding( i, "" );
}
// apply default values
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
for( int i = 0; i < ARRAYSIZE( keynames ); i++ )
Key_SetBinding( keynames[i].keynum, keynames[i].binding );
}
@@ -409,10 +402,7 @@ Key_Bind_f
*/
static void Key_Bind_f( void )
{
char cmd[1024];
int i, c, b;
c = Cmd_Argc();
int c = Cmd_Argc();
if( c < 2 )
{
@@ -420,7 +410,7 @@ static void Key_Bind_f( void )
return;
}
b = Key_StringToKeynum( Cmd_Argv( 1 ));
int b = Key_StringToKeynum( Cmd_Argv( 1 ));
if( b == -1 )
{
@@ -437,9 +427,10 @@ static void Key_Bind_f( void )
}
// copy the rest of the command line
char cmd[1024];
cmd[0] = 0; // start out with a null string
for( i = 2; i < c; i++ )
for( int i = 2; i < c; i++ )
{
Q_strncat( cmd, Cmd_Argv( i ), sizeof( cmd ));
if( i != ( c - 1 )) Q_strncat( cmd, " ", sizeof( cmd ));
@@ -457,18 +448,16 @@ Writes lines containing "bind key value"
*/
void Key_WriteBindings( file_t *f )
{
int i;
string newCommand;
if( !f ) return;
FS_Printf( f, "unbindall\n" );
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( COM_StringEmptyOrNULL( keys[i].binding ))
continue;
string newCommand;
Cmd_Escape( newCommand, keys[i].binding, sizeof( newCommand ));
// NOTE: as TheKingFireS figured out, some particular mods (like CoF) do not
@@ -487,9 +476,7 @@ Key_Bindlist_f
*/
static void Key_Bindlist_f( void )
{
int i;
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( COM_StringEmptyOrNULL( keys[i].binding ))
continue;
@@ -510,17 +497,16 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboole
size_t i, numkeys;
string keys_strings[ARRAYSIZE( keys )];
string matchbuf;
int len;
// compare keys list with current keyword
len = Q_strlen( s );
int len = Q_strlen( s );
for( i = 0, numkeys = 0; i < ARRAYSIZE( keys ); i++ )
{
const char *keyname = Key_KeynumToString( i );
if(( *s == '*' ) || !Q_strnicmp( keyname, s, len ))
Q_strncpy( keys_strings[numkeys++], keyname, sizeof( keys[0] ));
Q_strncpy( keys_strings[numkeys++], keyname, sizeof( keys_strings[0] ));
}
if( !numkeys ) return false;
@@ -565,8 +551,6 @@ Key_Init
*/
void Key_Init( void )
{
int i;
// register our functions
Cmd_AddRestrictedCommand( "bind", Key_Bind_f, "binds a command to the specified key in bindmap" );
Cmd_AddRestrictedCommand( "unbind", Key_Unbind_f, "removes a command on the specified key in bindmap" );
@@ -576,10 +560,9 @@ void Key_Init( void )
Cmd_AddCommand( "makehelp", Key_EnumCmds_f, "write help.txt that contains all console cvars and cmds" );
// setup default binding. "unbindall" from config.cfg will be reset it
for( i = 0; i < ARRAYSIZE( keynames ); i++ )
for( int i = 0; i < ARRAYSIZE( keynames ); i++ )
Key_SetBinding( keynames[i].keynum, keynames[i].binding );
Cvar_RegisterVariable( &osk_enable );
Cvar_RegisterVariable( &key_rotate );
}
@@ -591,21 +574,20 @@ Key_AddKeyCommands
*/
static void Key_AddKeyCommands( int key, const char *kb, qboolean down )
{
char button[1024];
char *buttonPtr;
char cmd[1024];
int i;
if( !kb ) return;
buttonPtr = button;
for( i = 0; ; i++ )
char button[1024];
char *buttonPtr = button;
for( int i = 0; ; i++ )
{
if( kb[i] == ';' || !kb[i] )
{
*buttonPtr = '\0';
if( button[0] == '+' )
{
char cmd[1024];
// button commands add keynum as a parm
if( down ) Q_snprintf( cmd, sizeof( cmd ), "%s %i\n", button, key );
else Q_snprintf( cmd, sizeof( cmd ), "-%s %i\n", button + 1, key );
@@ -705,8 +687,6 @@ Called by the system for both key up and key down events
*/
void GAME_EXPORT Key_Event( int key, int down )
{
const char *kb;
key = Key_Rotate( key );
if( OSK_KeyEvent( key, down ) )
@@ -716,7 +696,7 @@ void GAME_EXPORT Key_Event( int key, int down )
if( !keys[key].down && !down )
return;
kb = keys[key].binding;
const char *kb = keys[key].binding;
keys[key].down = down ? true : false;
#ifdef HACKS_RELATED_HLMODS
@@ -914,13 +894,11 @@ Key_ClearStates
*/
void GAME_EXPORT Key_ClearStates( void )
{
int i;
// don't clear keys during changelevel
if( cls.changelevel )
return;
for( i = 0; i < ARRAYSIZE( keys ); i++ )
for( int i = 0; i < ARRAYSIZE( keys ); i++ )
{
if( i >= K_MOUSE1 && i <= K_MOUSE5 )
IN_MouseEvent( i - K_MOUSE1, false );
@@ -985,288 +963,3 @@ int Key_ToUpper( int keynum )
return Q_toupper( keynum );
}
/* On-screen keyboard:
*
* 4 lines with 13 buttons each
* Left trigger == backspace
* Right trigger == space
* Any button press is button press on keyboard
*
* Our layout:
* 0 1 2 3 4 5 6 7 8 9 10 11 12
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define MAX_OSK_ROWS 13
#define MAX_OSK_LINES 4
enum
{
OSK_DEFAULT = 0,
OSK_UPPER, // on caps, shift
/*
OSK_RUSSIAN,
OSK_RUSSIAN_UPPER,
*/
OSK_LAST
};
enum
{
OSK_TAB = 16,
OSK_SHIFT,
OSK_BACKSPACE,
OSK_ENTER,
OSK_SPECKEY_LAST
};
static const char *osk_keylayout[][4] =
{
{
"`1234567890-=", // 13
"qwertyuiop[]\\", // 13
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
},
{
"~!@#$%^&*()_+",
"QWERTYUIOP{}|",
"\x10" "ASDFGHJKL:\"" "\x12",
"\x11" "ZXCVBNM<>? " "\x13"
}
};
struct osk_s
{
qboolean enable;
int curlayout;
qboolean shift;
qboolean sending;
struct {
signed char x;
signed char y;
char val;
} curbutton;
} osk;
static qboolean OSK_KeyEvent( int key, int down )
{
if( !osk.enable || !osk_enable.value )
return false;
if( osk.sending )
{
osk.sending = false;
return false;
}
if( osk.curbutton.val == 0 )
{
if( key == K_ENTER || key == K_A_BUTTON )
{
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
return false;
}
switch ( key )
{
case K_A_BUTTON:
case K_ENTER:
switch( osk.curbutton.val )
{
case OSK_ENTER:
osk.sending = true;
Key_Event( K_ENTER, down );
//osk_enable = false; // TODO: handle multiline
break;
case OSK_SHIFT:
if( !down )
break;
if( osk.curlayout & 1 )
osk.curlayout--;
else
osk.curlayout++;
osk.shift = true;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
case OSK_BACKSPACE:
Key_Event( K_BACKSPACE, down ); break;
case OSK_TAB:
Key_Event( K_TAB, down ); break;
default:
{
int ch;
if( !down )
{
if( osk.shift && osk.curlayout & 1 )
osk.curlayout--;
osk.shift = false;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
}
ch = (byte)osk.curbutton.val;
// do not pass UTF-8 sequence into the engine, convert it here
if( !cls.accept_utf8 )
ch = Con_UtfProcessCharForce( ch );
if( !ch )
break;
CL_CharEvent( ch );
break;
}
}
break;
case K_UPARROW:
if( down && --osk.curbutton.y < 0 )
{
osk.curbutton.y = MAX_OSK_LINES - 1;
osk.curbutton.val = 0;
return true;
}
break;
case K_DOWNARROW:
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
{
osk.curbutton.y = 0;
osk.curbutton.val = 0;
return true;
}
break;
case K_LEFTARROW:
if( down && --osk.curbutton.x < 0 )
osk.curbutton.x = MAX_OSK_ROWS - 1;
break;
case K_RIGHTARROW:
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
osk.curbutton.x = 0;
break;
default:
return false;
}
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
/*
=============
OSK_EnableTextInput
Enables built-in IME
=============
*/
static void OSK_EnableTextInput( qboolean enable, qboolean force )
{
qboolean old = osk.enable;
osk.enable = enable;
if( osk.enable && (!old || force) )
{
osk.curlayout = 0;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
}
}
#define X_START 0.1347475f
#define Y_START 0.567f
#define X_STEP 0.05625
#define Y_STEP 0.0825
/*
============
Joy_DrawSymbolButton
Draw button with symbol on it
============
*/
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
{
cl_font_t *font = Con_GetCurFont();
byte color[] = { 255, 255, 255, 255 };
int x1 = x * refState.width,
y1 = y * refState.height,
w = width * refState.width,
h = height * refState.height;
if( symb == osk.curbutton.val )
ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );
if( !symb || symb == ' ' || (symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ) )
return;
CL_DrawCharacter(
x1 + width * 0.4 * refState.width,
y1 + height * 0.4 * refState.height,
symb, color, font, 0 );
}
/*
=============
Joy_DrawSpecialButton
Draw special button, like shift, enter or esc
=============
*/
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
{
byte color[] = { 0, 255, 0, 255 };
Con_DrawString(
x * refState.width + width * 0.4 * refState.width,
y * refState.height + height * 0.4 * refState.height,
name,
color );
}
/*
=============
Joy_DrawOnScreenKeyboard
Draw on screen keyboard, if enabled
=============
*/
void OSK_Draw( void )
{
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
float x, y;
int i, j;
if( !osk.enable || !osk_enable.value || !osk.curbutton.val )
return;
// draw keyboard
ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,
X_STEP * MAX_OSK_ROWS * refState.width,
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
}

View File

@@ -0,0 +1,310 @@
/*
in_osk.c - on-screen keyboard input
Copyright (C) 2016-2026 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 "input.h"
#include "client.h"
CVAR_DEFINE_AUTO( osk_enable, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable built-in on-screen keyboard" );
/* On-screen keyboard:
*
* 4 lines with 13 buttons each
* Left trigger == backspace
* Right trigger == space
* Any button press is button press on keyboard
*
* Our layout:
* 0 1 2 3 4 5 6 7 8 9 10 11 12
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |` |1 |2 |3 |4 |5 |6 |7 |8 |9 |0 |- |= | 0
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |q |w |e |r |t |y |u |i |o |p |[ |] |\ | 1
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |CL|a |s |d |f |g |h |j |k |l |; |' |BS| 2
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
* |SH|z |x |c |v |b |n |m |, |. |/ |SP|EN| 3
* +--+--+--+--+--+--+--+--+--+--+--+--+--+
*/
#define MAX_OSK_ROWS 13
#define MAX_OSK_LINES 4
enum
{
OSK_DEFAULT = 0,
OSK_UPPER, // on caps, shift
/*
OSK_RUSSIAN,
OSK_RUSSIAN_UPPER,
*/
OSK_LAST
};
enum
{
OSK_TAB = 16,
OSK_SHIFT,
OSK_BACKSPACE,
OSK_ENTER,
OSK_SPECKEY_LAST
};
static const char *osk_keylayout[][4] =
{
{
"`1234567890-=", // 13
"qwertyuiop[]\\", // 13
"\x10" "asdfghjkl;'" "\x12", // 11 + caps on a left, enter on a right
"\x11" "zxcvbnm,./ " "\x13" // 10 + esc on left + shift on a left/right
},
{
"~!@#$%^&*()_+",
"QWERTYUIOP{}|",
"\x10" "ASDFGHJKL:\"" "\x12",
"\x11" "ZXCVBNM<>? " "\x13"
}
};
static struct osk_s
{
qboolean enable;
int curlayout;
qboolean shift;
qboolean sending;
struct
{
signed char x;
signed char y;
char val;
} curbutton;
} osk;
qboolean OSK_KeyEvent( int key, int down )
{
if( !osk.enable || !osk_enable.value )
return false;
if( osk.sending )
{
osk.sending = false;
return false;
}
if( osk.curbutton.val == 0 )
{
if( key == K_ENTER || key == K_A_BUTTON )
{
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
return false;
}
switch( key )
{
case K_A_BUTTON:
case K_ENTER:
switch( osk.curbutton.val )
{
case OSK_ENTER:
osk.sending = true;
Key_Event( K_ENTER, down );
// osk_enable = false; // TODO: handle multiline
break;
case OSK_SHIFT:
if( !down )
break;
if( osk.curlayout & 1 )
osk.curlayout--;
else
osk.curlayout++;
osk.shift = true;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
case OSK_BACKSPACE:
Key_Event( K_BACKSPACE, down );
break;
case OSK_TAB:
Key_Event( K_TAB, down );
break;
default:
{
if( !down )
{
if( osk.shift && osk.curlayout & 1 )
osk.curlayout--;
osk.shift = false;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
break;
}
int ch = (byte)osk.curbutton.val;
// do not pass UTF-8 sequence into the engine, convert it here
if( !cls.accept_utf8 )
ch = Con_UtfProcessCharForce( ch );
if( !ch )
break;
CL_CharEvent( ch );
break;
}
}
break;
case K_UPARROW:
if( down && --osk.curbutton.y < 0 )
{
osk.curbutton.y = MAX_OSK_LINES - 1;
osk.curbutton.val = 0;
return true;
}
break;
case K_DOWNARROW:
if( down && ++osk.curbutton.y >= MAX_OSK_LINES )
{
osk.curbutton.y = 0;
osk.curbutton.val = 0;
return true;
}
break;
case K_LEFTARROW:
if( down && --osk.curbutton.x < 0 )
osk.curbutton.x = MAX_OSK_ROWS - 1;
break;
case K_RIGHTARROW:
if( down && ++osk.curbutton.x >= MAX_OSK_ROWS )
osk.curbutton.x = 0;
break;
default:
return false;
}
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
return true;
}
/*
=============
OSK_EnableTextInput
Enables built-in IME
=============
*/
void OSK_EnableTextInput( qboolean enable, qboolean force )
{
qboolean old = osk.enable;
osk.enable = enable;
if( osk.enable && ( !old || force ))
{
osk.curlayout = 0;
osk.curbutton.val = osk_keylayout[osk.curlayout][osk.curbutton.y][osk.curbutton.x];
}
}
#define X_START 0.1347475f
#define Y_START 0.567f
#define X_STEP 0.05625
#define Y_STEP 0.0825
/*
============
OSK_DrawSymbolButton
Draw button with symbol on it
============
*/
static void OSK_DrawSymbolButton( int symb, float x, float y, float width, float height )
{
cl_font_t *font = Con_GetCurFont();
byte color[] = { 255, 255, 255, 255 };
int x1 = x * refState.width,
y1 = y * refState.height,
w = width * refState.width,
h = height * refState.height;
if( symb == osk.curbutton.val )
ref.dllFuncs.FillRGBA( kRenderTransTexture, x1, y1, w, h, 255, 160, 0, 100 );
if( !symb || symb == ' ' || ( symb >= OSK_TAB && symb < OSK_SPECKEY_LAST ))
return;
CL_DrawCharacter(
x1 + width * 0.4 * refState.width,
y1 + height * 0.4 * refState.height,
symb, color, font, 0 );
}
/*
=============
OSK_DrawSpecialButton
Draw special button, like shift, enter or esc
=============
*/
static void OSK_DrawSpecialButton( const char *name, float x, float y, float width, float height )
{
byte color[] = { 0, 255, 0, 255 };
Con_DrawString(
x * refState.width + width * 0.4 * refState.width,
y * refState.height + height * 0.4 * refState.height,
name,
color );
}
/*
=============
OSK_Draw
Draw on screen keyboard, if enabled
=============
*/
void OSK_Draw( void )
{
const char **curlayout = osk_keylayout[osk.curlayout]; // shortcut :)
float x, y;
int i, j;
if( !osk.enable || !osk_enable.value || !osk.curbutton.val )
return;
// draw keyboard
ref.dllFuncs.FillRGBA( kRenderTransTexture, X_START * refState.width, Y_START * refState.height,
X_STEP * MAX_OSK_ROWS * refState.width,
Y_STEP * MAX_OSK_LINES * refState.height, 100, 100, 100, 100 );
OSK_DrawSpecialButton( "-]", X_START, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "<-", X_START + X_STEP * 12, Y_START + Y_STEP * 2, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "sh", X_START, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
OSK_DrawSpecialButton( "en", X_START + X_STEP * 12, Y_START + Y_STEP * 3, X_STEP, Y_STEP );
for( y = Y_START, j = 0; j < MAX_OSK_LINES; j++, y += Y_STEP )
for( x = X_START, i = 0; i < MAX_OSK_ROWS; i++, x += X_STEP )
OSK_DrawSymbolButton( curlayout[j][i], x, y, X_STEP, Y_STEP );
}
void OSK_Init( void )
{
Cvar_RegisterVariable( &osk_enable );
}

View File

@@ -185,10 +185,10 @@ void Touch_NotifyResize( void )
static inline float Touch_AspectRatio( void )
{
if( touch.config_aspect_ratio )
if( touch.config_aspect_ratio >= 0.25f )
return touch.config_aspect_ratio;
if( touch.actual_aspect_ratio )
if( touch.actual_aspect_ratio >= 0.25f )
return touch.actual_aspect_ratio;
if( refState.width && refState.height )
@@ -249,10 +249,7 @@ Dump config to file
*/
static qboolean Touch_DumpConfig( const char *name, const char *profilename )
{
file_t *f;
const touch_button_t *button;
f = FS_Open( name, "w", true );
file_t *f = FS_Open( name, "w", true );
if( !f )
{
@@ -299,7 +296,7 @@ static qboolean Touch_DumpConfig( const char *name, const char *profilename )
FS_Printf( f, "touch_removeall\n" );
FS_Printf( f, "touch_aspectratio %g\n", Touch_AspectRatio());
for( button = touch.list_user.first; button; button = button->next )
for( const touch_button_t *button = touch.list_user.first; button; button = button->next )
Touch_ExportButtonToConfig( f, button, false );
FS_Close( f );
@@ -347,9 +344,6 @@ export current touch configuration into profile
*/
static void Touch_ExportConfig_f( void )
{
const char *name;
string profilename;
if( Cmd_Argc() != 2 )
{
Con_Printf( S_USAGE "touch_exportconfig <name>\n" );
@@ -362,14 +356,15 @@ static void Touch_ExportConfig_f( void )
return;
}
name = Cmd_Argv( 1 );
const char *name = Cmd_Argv( 1 );
string profilename;
if( Q_strstr( name, "touch_presets/" ))
{
string profilebase;
COM_FileBase( name, profilebase, sizeof( profilebase ));
Q_snprintf( profilename, sizeof( profilebase ), "touch_profiles/%s (copy).cfg", profilebase );
Q_snprintf( profilename, sizeof( profilename ), "touch_profiles/%s (copy).cfg", profilebase );
}
else Q_strncpy( profilename, name, sizeof( profilename ));
@@ -386,7 +381,6 @@ export current touch configuration into C code
*/
static void Touch_GenerateCode_f( void )
{
const touch_button_t *button;
rgba_t c = { 0 };
if( !touch.list_user.first )
@@ -395,11 +389,9 @@ static void Touch_GenerateCode_f( void )
return;
}
for( button = touch.list_user.first; button; button = button->next )
for( const touch_button_t *button = touch.list_user.first; button; button = button->next )
{
float aspect;
int flags = button->flags;
int round;
if( FBitSet( flags, TOUCH_FL_CLIENT ))
continue; // skip temporary buttons
@@ -410,7 +402,7 @@ static void Touch_GenerateCode_f( void )
if( FBitSet( flags, TOUCH_FL_DEF_HIDE ))
SetBits( flags, TOUCH_FL_HIDE );
aspect = ( button->y2 - button->y1 ) / (( button->x2 - button->x1 ) / Touch_AspectRatio( ));
float aspect = ( button->y2 - button->y1 ) / (( button->x2 - button->x1 ) / Touch_AspectRatio( ));
if( memcmp( c, button->color, sizeof( c )))
{
@@ -419,6 +411,7 @@ static void Touch_GenerateCode_f( void )
memcpy( c, button->color, sizeof( c ));
}
int round;
if( button->type == touch_command )
{
if( fabs( aspect - 1.0f ) < 0.001 )
@@ -438,22 +431,18 @@ static void Touch_GenerateCode_f( void )
static void Touch_RoundAll_f( void )
{
touch_button_t *button;
if( !touch_grid_enable.value )
return;
for( button = touch.list_user.first; button; button = button->next )
for( touch_button_t *button = touch.list_user.first; button; button = button->next )
IN_TouchCheckCoords( &button->x1, &button->y1, &button->x2, &button->y2 );
}
static void Touch_ListButtons_f( void )
{
touch_button_t *button;
Touch_InitConfig();
for( button = touch.list_user.first; button; button = button->next )
for( touch_button_t *button = touch.list_user.first; button; button = button->next )
{
Con_Printf( "%s %s %s %g %g %g %g %d %d %d %d %d\n",
button->name, button->texture, button->command,
@@ -483,9 +472,7 @@ static void Touch_Stroke_f( void )
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 )
for( touch_button_t *b = buttons; b; b = b->next )
{
if( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED ))
continue;
@@ -504,13 +491,12 @@ static touch_button_t *Touch_FindButtonNoPattern( touchbuttonlist_t *list, const
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 )
for( touch_button_t *b = buttons; b; b = b->next )
{
if( !privileged && !FBitSet( b->flags, TOUCH_FL_UNPRIVILEGED ))
continue;
@@ -584,11 +570,10 @@ static void Touch_SetClientOnly_f( void )
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 )))
touch_button_t *button;
while(( button = Touch_FindFirst( list, name, privileged )))
{
if( button->prev )
button->prev->next = button->next;
@@ -640,9 +625,7 @@ static void Touch_RemoveAll_f( void )
static void Touch_SetColor( touchbuttonlist_t *list, const char *name, byte *color, qboolean privileged )
{
touch_button_t *b;
for( b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))
for( touch_button_t *b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))
Vector4Copy( color, b->color );
}
@@ -677,9 +660,7 @@ static void Touch_SetCommand( touch_button_t *button, const char *command )
void Touch_HideButtons( const char *name, byte hide, qboolean privileged )
{
touch_button_t *b;
for( b = Touch_FindFirst( &touch.list_user, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))
for( touch_button_t *b = Touch_FindFirst( &touch.list_user, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))
{
if( hide )
SetBits( b->flags, TOUCH_FL_HIDE );
@@ -718,9 +699,7 @@ static void Touch_Show_f( void )
static void Touch_FadeButtons( touchbuttonlist_t *list, const char *name, float speed, float end, float start, qboolean privileged )
{
touch_button_t *b;
for( b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))
for( touch_button_t *b = Touch_FindFirst( list, name, privileged ); b != NULL; b = Touch_FindNext( b->next, name, privileged ))
{
if( start >= 0 )
b->fade = start;
@@ -855,26 +834,22 @@ static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, const char *nam
void Touch_AddClientButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags )
{
touch_button_t *button;
if( !touch.initialized )
return;
if( round )
IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
IN_TouchCheckCoords( &x1, &y1, &x2, &y2 );
if( round == round_aspect )
y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect;
button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, true );
touch_button_t *button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, true );
SetBits( button->flags, TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT );
button->aspect = aspect;
}
static void Touch_LoadDefaults_f( void )
{
int i;
for( i = 0; i < g_DefaultButtonsLength; i++ )
for( int i = 0; i < g_DefaultButtonsLength; i++ )
{
touch_button_t *button;
float x1 = g_DefaultButtons[i].x1,
@@ -904,11 +879,9 @@ static void Touch_LoadDefaults_f( void )
// Add default button from client
void Touch_AddDefaultButton( const char *name, const char *texture, const char *command, float x1, float y1, float x2, float y2, byte *color, int round, float aspect, int flags )
{
touchdefaultbutton_t *b;
g_DefaultButtons = Mem_Realloc( touch.mempool, g_DefaultButtons, sizeof( *g_DefaultButtons ) * ( g_DefaultButtonsLength + 1 ));
b = &g_DefaultButtons[g_DefaultButtonsLength];
touchdefaultbutton_t *b = &g_DefaultButtons[g_DefaultButtonsLength];
Q_strncpy( b->name, name, sizeof( b->name ));
Q_strncpy( b->texture, texture, sizeof( b->texture ));
@@ -939,22 +912,18 @@ void Touch_ResetDefaultButtons( void )
static void Touch_AddButton_f( void )
{
rgba_t color = { 255, 255, 255, 255 };
touch_button_t *button = NULL;
const char *name, *command;
float x1 = 0.4f, y1 = 0.4f, x2 = 0.6f, y2 = 0.6f;
qboolean privileged = Cmd_CurrentCommandIsPrivileged();
string texture;
if( Cmd_Argc( ) < 4 )
{
Con_Printf( S_USAGE "touch_addbutton <name> <texture> <command> [<x1> <y1> <x2> <y2> [ r g b a ] ]\n" );
return;
}
name = Cmd_Argv( 1 );
const char *name = Cmd_Argv( 1 );
string texture;
Q_strncpy( texture, Cmd_Argv( 2 ), sizeof( texture ));
command = Cmd_Argv( 3 );
const char *command = Cmd_Argv( 3 );
float x1 = 0.4f, y1 = 0.4f, x2 = 0.6f, y2 = 0.6f;
rgba_t color = { 255, 255, 255, 255 };
// HACKHACK: old engine specifically used .tga for touch buttons
// and because new engine extras.pk3 don't have .tga textures
@@ -981,7 +950,8 @@ static void Touch_AddButton_f( void )
color[3] = Q_atoi( Cmd_Argv( 11 ));
}
button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, privileged );
qboolean privileged = Cmd_CurrentCommandIsPrivileged();
touch_button_t *button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, privileged );
if( Cmd_Argc( ) >= 13 )
SetBits( button->flags, Q_atoi( Cmd_Argv( 12 )));
@@ -1024,9 +994,7 @@ static void Touch_EnableEdit_f( void )
// convert coordinates to actual aspect ratio after it was updated
if( touch.config_aspect_ratio != touch.actual_aspect_ratio )
{
touch_button_t *button;
for( button = touch.list_user.first; button; button = button->next )
for( touch_button_t *button = touch.list_user.first; button; button = button->next )
{
button->y1 /= touch.actual_aspect_ratio / touch.config_aspect_ratio;
button->y2 /= touch.actual_aspect_ratio / touch.config_aspect_ratio;
@@ -1068,7 +1036,7 @@ static void Touch_InitEditor( void )
temp = Touch_AddButton( &touch.list_edit, "close", "touch_default/edit_close", "touch_disableedit", 0, y, x, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
temp = Touch_AddButton( &touch.list_edit, "close", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
temp = Touch_AddButton( &touch.list_edit, "close_label", "#Close and save", "", x, y, x + 0.2f, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
y += 0.2f;
@@ -1076,7 +1044,7 @@ static void Touch_InitEditor( void )
temp = Touch_AddButton( &touch.list_edit, "cancel", "touch_default/edit_reset", "touch_reloadconfig", 0, y, x, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
temp = Touch_AddButton( &touch.list_edit, "close", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
temp = Touch_AddButton( &touch.list_edit, "cancel_label", "#Cancel and reset", "", x, y, x + 0.2f, y + 0.1f, color, true );
SetBits( temp->flags, TOUCH_FL_NOEDIT );
y += 0.2f;
@@ -1256,12 +1224,21 @@ static void Touch_DrawTexture( float x1, float y1, float x2, float y2, int textu
0, 0, 1, 1, texture );
}
#define GRID_COUNT_X ((int)touch_grid_count.value )
#define GRID_COUNT_Y (((int)touch_grid_count.value ) * Touch_AspectRatio( ))
#define GRID_X ( 1.0f / GRID_COUNT_X )
#define GRID_Y ( 1.0f / Touch_AspectRatio() / GRID_COUNT_X )
#define GRID_ROUND_X( x ) ((float)round(( x ) * GRID_COUNT_X ) / GRID_COUNT_X )
#define GRID_ROUND_Y( x ) ((float)round(( x ) * GRID_COUNT_Y ) / GRID_COUNT_Y )
static inline int Touch_GridCountX( void )
{
return Q_max((int)touch_grid_count.value, 1 );
}
static inline int Touch_GridCountY( void )
{
float grid_count_y = touch_grid_count.value * Touch_AspectRatio();
return Q_max((int)grid_count_y, 1 );
}
#define GRID_X ( 1.0f / Touch_GridCountX( ))
#define GRID_Y ( 1.0f / Touch_GridCountY( ))
#define GRID_ROUND_X( x ) ((float)round(( x ) * Touch_GridCountX() ) / Touch_GridCountX())
#define GRID_ROUND_Y( x ) ((float)round(( x ) * Touch_GridCountY() ) / Touch_GridCountY())
static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 )
{
@@ -1307,10 +1284,6 @@ static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 )
static float Touch_DrawCharacter( float x, float y, int number, float size )
{
float s1, s2, t1, t2, width, height;
int w, h;
wrect_t *prc;
if( !cls.creditsFont.valid )
return 0;
@@ -1320,16 +1293,17 @@ static float Touch_DrawCharacter( float x, float y, int number, float size )
if( !number )
return 0;
int w, h;
R_GetTextureParms( &w, &h, cls.creditsFont.hFontTexture );
prc = &cls.creditsFont.fontRc[number];
wrect_t *prc = &cls.creditsFont.fontRc[number];
s1 = prc->left / (float)w;
t1 = prc->top / (float)h;
s2 = prc->right / (float)w;
t2 = prc->bottom / (float)h;
float s1 = prc->left / (float)w;
float t1 = prc->top / (float)h;
float s2 = prc->right / (float)w;
float t2 = prc->bottom / (float)h;
width = ( prc->right - prc->left ) / 1024.0f * size;
height = ( prc->bottom - prc->top ) / 1024.0f * size;
float width = ( prc->right - prc->left ) / 1024.0f * size;
float height = ( prc->bottom - prc->top ) / 1024.0f * size;
ref.dllFuncs.R_DrawStretchPic( TO_SCRN_X( x ), TO_SCRN_Y( y ), TO_SCRN_X( width ), TO_SCRN_X( height ),
s1, t1, s2, t2, cls.creditsFont.hFontTexture );
@@ -1376,9 +1350,7 @@ static float Touch_DrawText( float x1, float y1, float x2, float y2, const char
static void Touch_DrawButtons( touchbuttonlist_t *list )
{
touch_button_t *b;
for( b = list->first; b; b = b->next )
for( touch_button_t *b = list->first; b; b = b->next )
{
if( Touch_IsVisible( b ))
{
@@ -1498,17 +1470,15 @@ void Touch_Draw( void )
if( touch.state >= state_edit && touch_grid_enable.value )
{
float x;
if( touch_in_menu.value )
ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, 1, 1, 32, 32, 32, 255 );
else
ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, 0, 1, 1, 0, 0, 0, 112 );
for( x = 0.0f; x < 1.0f; x += GRID_X )
for( float x = 0.0f; x < 1.0f; x += GRID_X )
ref.dllFuncs.FillRGBA( kRenderTransTexture, TO_SCRN_X( x ), 0, 1, TO_SCRN_Y( 1 ), 0, 224, 224, 112 );
for( x = 0.0f; x < 1.0f; x += GRID_Y )
for( float x = 0.0f; x < 1.0f; x += GRID_Y )
ref.dllFuncs.FillRGBA( kRenderTransTexture, 0, TO_SCRN_Y( x ), TO_SCRN_X( 1 ), 1, 0, 224, 224, 112 );
}
@@ -1730,16 +1700,14 @@ static void Touch_Motion( int fingerID, float x, float y, float dx, float dy )
if( touch_nonlinear_look.value )
{
float dabs, dcos, dsin;
// save angle, modify only velocity
dabs = sqrt( dx * dx + dy * dy );
float dabs = sqrt( dx * dx + dy * dy );
if( dabs < 0.000001f )
return; // no motion, avoid division by zero
dcos = dx / dabs;
dsin = dy / dabs;
float dcos = dx / dabs;
float dsin = dy / dabs;
if( touch_exp_mult.value > 1 )
dabs = ( exp( dabs * touch_exp_mult.value ) - 1 ) / touch_exp_mult.value;
@@ -1763,14 +1731,13 @@ static void Touch_Motion( int fingerID, float x, float y, float dx, float dy )
static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type, int fingerID, float x, float y )
{
touch_button_t *button;
qboolean result = false;
if( type != event_down && type != event_up )
return false;
// run from end(front) to start(back)
for( button = list->last; button; button = button->prev )
for( touch_button_t *button = list->last; button; button = button->prev )
{
// skip invisible buttons
if( !Touch_IsVisible( button ))
@@ -1842,14 +1809,12 @@ static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type,
if( touch.look_finger == fingerID )
{
touch_button_t *newbutton;
// this is an error, try recover
touch.move_finger = touch.look_finger = -1;
// player touched touch_move with enabled look mode
// and same finger id. release all move triggers
for( newbutton = list->first; newbutton; newbutton = newbutton->next )
for( touch_button_t *newbutton = list->first; newbutton; newbutton = newbutton->next )
{
if( newbutton->type == touch_move || newbutton->type == touch_look )
newbutton->finger = -1;
@@ -1902,14 +1867,12 @@ static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type,
if( touch.move_finger == fingerID )
{
touch_button_t *newbutton;
// this is an error, try recover
touch.move_finger = touch.look_finger = -1;
// player touched touch_move with enabled look mode
// and same finger id. release all move triggers
for( newbutton = list->first; newbutton; newbutton = newbutton->next )
for( touch_button_t *newbutton = list->first; newbutton; newbutton = newbutton->next )
{
if( newbutton->type == touch_move || newbutton->type == touch_look )
newbutton->finger = -1;
@@ -1986,8 +1949,6 @@ static qboolean Touch_ButtonPress( touchbuttonlist_t *list, touchEventType type,
static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, float y )
{
touch_button_t *button;
// edit buttons are on y1
if( type == event_down )
{
@@ -2002,7 +1963,7 @@ static qboolean Touch_ButtonEdit( touchEventType type, int fingerID, float x, fl
}
// run from end(front) to start(back)
for( button = touch.list_user.last; button; button = button->prev )
for( touch_button_t *button = touch.list_user.last; button; button = button->prev )
{
if( type == event_down )
{

View File

@@ -28,7 +28,6 @@ GNU General Public License for more details.
static qboolean in_mouseactive; // false when not focus app
static qboolean in_mouseinitialized;
static qboolean in_mouse_suspended;
static struct
{
int x, y;
@@ -338,8 +337,6 @@ IN_MouseMove
*/
static void IN_MouseMove( void )
{
int x, y;
if( !in_mouseinitialized )
return;
@@ -351,6 +348,7 @@ static void IN_MouseMove( void )
}
// find mouse movement
int x, y;
Platform_GetMousePos( &x, &y );
VGui_MouseMove( x, y );
@@ -445,6 +443,10 @@ void IN_Init( void )
{
IN_StartupMouse( );
IN_GyroInit();
OSK_Init();
Joy_Init(); // common joystick support init
Touch_Init();
@@ -560,6 +562,7 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y
#endif
}
IN_GyroFinalizeMove( forward, side, pitch, yaw );
Joy_FinalizeMove( forward, side, pitch, yaw );
Touch_GetMove( forward, side, pitch, yaw );
@@ -582,18 +585,15 @@ Called from cl_main.c after generating command in client
*/
void IN_EngineAppendMove( float frametime, usercmd_t *cmd, qboolean active )
{
float forward, side, pitch, yaw;
if( clgame.dllFuncs.pfnLookEvent )
return;
if( cls.key_dest != key_game || cl.paused || cl.intermission )
return;
forward = side = pitch = yaw = 0;
if( active )
{
float forward = 0, side = 0, pitch = 0, yaw = 0;
float sensitivity = 1;//( (float)cl.local.scr_fov / (float)90.0f );
IN_CollectInput( &forward, &side, &pitch, &yaw, false );

View File

@@ -67,18 +67,13 @@ static winding_t * winding_alloc( uint numpoints )
static void free_winding( winding_t *w )
{
// simple sentinel by Carmack
if( *(unsigned *)w == 0xDEADC0DE )
Host_Error( "%s: freed a freed winding\n", __func__ );
*(unsigned *)w = 0xDEADC0DE;
free( w );
}
static winding_t *winding_copy( winding_t *w )
{
winding_t *neww;
neww = winding_alloc( w->numpoints );
winding_t *neww = winding_alloc( w->numpoints );
memcpy( neww, w, offsetof( winding_t, p[w->numpoints] ) );
return neww;
@@ -86,12 +81,9 @@ static winding_t *winding_copy( winding_t *w )
static void winding_reverse( winding_t *w )
{
vec3_t point;
int i;
for( i = 0; i < w->numpoints / 2; i++ )
for( int i = 0; i < w->numpoints / 2; i++ )
{
VectorCopy( w->p[i], point );
vec3_t point = Vec3( w->p[i] );
VectorCopy( w->p[w->numpoints - i - 1], w->p[i] );
VectorCopy( point, w->p[w->numpoints - i - 1] );
}
@@ -120,15 +112,13 @@ winding_for_plane
static winding_t *winding_for_plane( const mplane_t *p )
{
vec3_t org, vright, vup;
int i, axis;
vec_t max, v;
winding_t *w;
vec_t v;
// find the major axis
max = -BOGUS_RANGE;
axis = -1;
vec_t max = -BOGUS_RANGE;
int axis = -1;
for( i = 0; i < 3; i++ )
for( int i = 0; i < 3; i++ )
{
v = fabs( p->normal[i] );
if( v > max )
@@ -162,7 +152,7 @@ static winding_t *winding_for_plane( const mplane_t *p )
VectorScale( vright, BOGUS_RANGE, vright );
// project a really big axis aligned box onto the plane
w = winding_alloc( 4 );
winding_t *w = winding_alloc( 4 );
memset( w->p, 0, sizeof( vec3_t ) * 4 );
w->numpoints = 4;
w->plane = p;
@@ -251,46 +241,42 @@ If keepon is true, an exactly on-plane winding will be saved, otherwise
*/
static winding_t *winding_clip( winding_t *in, const mplane_t *split, qboolean keepon, int side, vec_t epsilon )
{
vec_t *dists;
int *sides;
int counts[3];
vec_t dot;
int i, j;
winding_t *neww;
vec_t *p1, *p2, *mid;
int maxpts;
dists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));
sides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));
vec_t *dists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));
int *sides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));
CalcSides( in, split, sides, dists, counts, epsilon );
if( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] )
{
neww = in;
goto out_free;
free( dists );
free( sides );
return in;
}
if( !counts[side] )
{
free_winding( in );
neww = NULL;
goto out_free;
free( dists );
free( sides );
return NULL;
}
if( !counts[side ^ 1] )
{
neww = in;
goto out_free;
free( dists );
free( sides );
return in;
}
maxpts = in->numpoints + 4;
neww = winding_alloc( maxpts );
int maxpts = in->numpoints + 4;
winding_t *neww = winding_alloc( maxpts );
neww->numpoints = 0;
neww->plane = in->plane;
for( i = 0; i < in->numpoints; i++ )
for( int i = 0; i < in->numpoints; i++ )
{
p1 = in->p[i];
vec_t *p1 = in->p[i];
if( sides[i] == SIDE_ON )
{
@@ -309,11 +295,11 @@ static winding_t *winding_clip( winding_t *in, const mplane_t *split, qboolean k
continue;
// generate a split point
p2 = in->p[(i + 1) % in->numpoints];
mid = neww->p[neww->numpoints++];
vec_t *p2 = in->p[(i + 1) % in->numpoints];
vec_t *mid = neww->p[neww->numpoints++];
dot = dists[i] / (dists[i] - dists[i + 1]);
for( j = 0; j < 3; j++ )
vec_t dot = dists[i] / (dists[i] - dists[i + 1]);
for( int j = 0; j < 3; j++ )
{
// avoid round off error when possible
if( in->plane->normal[j] == 1.0f )
@@ -336,7 +322,6 @@ static winding_t *winding_clip( winding_t *in, const mplane_t *split, qboolean k
// Shrink the winding back to just what it needs...
neww = winding_shrink(neww);
out_free:
free( dists );
free( sides );
@@ -355,17 +340,10 @@ new windings will be created.
*/
static void winding_split( winding_t *in, const mplane_t *split, winding_t **pfront, winding_t **pback )
{
vec_t *dists;
int *sides;
int counts[3];
vec_t dot;
int i, j;
winding_t *front, *back;
vec_t *p1, *p2, *mid;
int maxpts;
dists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));
sides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));
vec_t *dists = (vec_t *)malloc(( in->numpoints + 1 ) * sizeof( vec_t ));
int *sides = (int *)malloc(( in->numpoints + 1 ) * sizeof( int ));
CalcSides(in, split, sides, dists, counts, 0.04f );
if( !counts[0] && !counts[1] )
@@ -373,34 +351,40 @@ static void winding_split( winding_t *in, const mplane_t *split, winding_t **pfr
// winding on the split plane - return copies on both sides
*pfront = winding_copy( in );
*pback = winding_copy( in );
goto out_free;
free( dists );
free( sides );
return;
}
if( !counts[0] )
{
*pfront = NULL;
*pback = in;
goto out_free;
free( dists );
free( sides );
return;
}
if( !counts[1] )
{
*pfront = in;
*pback = NULL;
goto out_free;
free( dists );
free( sides );
return;
}
maxpts = in->numpoints + 4;
front = winding_alloc( maxpts );
int maxpts = in->numpoints + 4;
winding_t *front = winding_alloc( maxpts );
front->numpoints = 0;
front->plane = in->plane;
back = winding_alloc( maxpts );
winding_t *back = winding_alloc( maxpts );
back->numpoints = 0;
back->plane = in->plane;
for( i = 0; i < in->numpoints; i++ )
for( int i = 0; i < in->numpoints; i++ )
{
p1 = in->p[i];
vec_t *p1 = in->p[i];
if( sides[i] == SIDE_ON )
{
@@ -426,11 +410,11 @@ static void winding_split( winding_t *in, const mplane_t *split, winding_t **pfr
continue;
// generate a split point
p2 = in->p[(i + 1) % in->numpoints];
mid = front->p[front->numpoints++];
vec_t *p2 = in->p[(i + 1) % in->numpoints];
vec_t *mid = front->p[front->numpoints++];
dot = dists[i] / (dists[i] - dists[i + 1]);
for( j = 0; j < 3; j++ )
vec_t dot = dists[i] / (dists[i] - dists[i + 1]);
for( int j = 0; j < 3; j++ )
{
// avoid round off error when possible
if( in->plane->normal[j] == 1.0f )
@@ -452,7 +436,6 @@ static void winding_split( winding_t *in, const mplane_t *split, winding_t **pfr
*pfront = winding_shrink( front );
*pback = winding_shrink( back );
out_free:
free( dists );
free( sides );
}
@@ -725,7 +708,7 @@ static void R_DrawHull( hull_model_t *hull )
{
int i;
srand((unsigned int)poly );
srand((unsigned int)(uintptr_t)poly );
ref.dllFuncs.Color4ub( rand() & 255, rand() & 255, rand() & 255, 255 );
ref.dllFuncs.Begin( TRI_POLYGON );

View File

@@ -15,6 +15,7 @@ GNU General Public License for more details.
#include "common.h"
#include "client.h"
#include "mod_local.h"
#include "net_encode.h"
#include "cl_tent.h"
#include "shake.h"
@@ -59,33 +60,34 @@ CL_ParseSoundPacket
*/
static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
{
vec3_t pos;
int chan, sound;
float volume, attn;
int flags, pitch, entnum, wordIndex = 0;
int wordIndex = 0;
sound_t handle = 0;
double samplePos = 0, forcedEnd = 0;
flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );
sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );
chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );
int flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );
int sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );
int chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );
float volume;
if( FBitSet( flags, SND_VOLUME ))
volume = (float)MSG_ReadByte( msg ) / 255.0f;
else volume = VOL_NORM;
float attn;
if( FBitSet( flags, SND_ATTENUATION ))
attn = (float)MSG_ReadByte( msg ) / 64.0f;
else attn = ATTN_NONE;
int pitch;
if( FBitSet( flags, SND_PITCH ))
pitch = MSG_ReadByte( msg );
else pitch = PITCH_NORM;
// entity reletive
entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
int entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
// positioned in space
vec3_t pos;
MSG_ReadVec3Coord( msg, pos );
if( FBitSet( flags, SND_SENTENCE ))
@@ -128,8 +130,6 @@ CL_ParseServerTime
*/
void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto )
{
double dt;
cl.mtime[1] = cl.mtime[0];
cl.mtime[0] = MSG_ReadFloat( msg );
@@ -139,7 +139,7 @@ void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto )
if( cl.maxclients == 1 )
cl.time = cl.mtime[0];
dt = cl.time - cl.mtime[0];
double dt = cl.time - cl.mtime[0];
if( fabs( dt ) > cl_clockreset.value ) // 0.1 by default
{
@@ -211,29 +211,26 @@ CL_ParseParticles
*/
static void CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto )
{
vec3_t org, dir;
int i, count, color;
float life;
vec3_t org, dir;
MSG_ReadVec3Coord( msg, org );
for( i = 0; i < 3; i++ )
for( int i = 0; i < 3; i++ )
dir[i] = MSG_ReadChar( msg ) * 0.0625f;
count = MSG_ReadByte( msg );
color = MSG_ReadByte( msg );
int count = MSG_ReadByte( msg );
int color = MSG_ReadByte( msg );
if( count == 255 )
count = 1024;
float life;
if( proto == PROTO_GOLDSRC )
life = 0.0f;
else life = MSG_ReadByte( msg ) * 0.125f;
if( life != 0.0f && count == 1 )
{
particle_t *p;
p = R_AllocParticle( NULL );
particle_t *p = R_AllocParticle( NULL );
if( !p ) return;
p->die += life;
@@ -255,25 +252,23 @@ static client entity
*/
static void CL_ParseStaticEntity( sizebuf_t *msg )
{
int i, newnum;
const entity_state_t from = { 0 };
entity_state_t to;
cl_entity_t *ent;
if( !clgame.static_entities )
clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );
newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
MSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] );
i = clgame.numStatics;
int i = clgame.numStatics;
if( i >= MAX_STATIC_ENTITIES )
{
Con_Printf( S_ERROR "MAX_STATIC_ENTITIES limit exceeded!\n" );
return;
}
ent = &clgame.static_entities[i];
cl_entity_t *ent = &clgame.static_entities[i];
clgame.numStatics++;
// all states are same
@@ -314,7 +309,7 @@ Set new weapon animation
*/
void GAME_EXPORT CL_WeaponAnim( int iAnim, int body )
{
cl_entity_t *view = &clgame.viewent;
cl_entity_t *view = &clgame.viewent;
cl.local.weaponstarttime = 0.0f;
cl.local.weaponsequence = iAnim;
@@ -338,20 +333,18 @@ CL_ParseStaticDecal
*/
void CL_ParseStaticDecal( sizebuf_t *msg )
{
vec3_t origin;
int decalIndex, entityIndex, modelIndex;
float scale;
int flags;
vec3_t origin;
MSG_ReadVec3Coord( msg, origin );
decalIndex = MSG_ReadWord( msg );
entityIndex = MSG_ReadShort( msg );
int decalIndex = MSG_ReadWord( msg );
int entityIndex = MSG_ReadShort( msg );
int modelIndex;
if( entityIndex > 0 )
modelIndex = MSG_ReadWord( msg );
else modelIndex = 0;
flags = MSG_ReadByte( msg );
scale = (float)MSG_ReadWord( msg ) / 4096.0f;
int flags = MSG_ReadByte( msg );
float scale = (float)MSG_ReadWord( msg ) / 4096.0f;
CL_FireCustomDecal( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, origin, flags, scale );
}
@@ -380,11 +373,9 @@ CL_RequestMissingResources
*/
qboolean CL_RequestMissingResources( void )
{
resource_t *p;
if( !cls.dl.doneregistering && ( cls.dl.custom || cls.state == ca_validate ))
{
p = cl.resourcesneeded.pNext;
resource_t *p = cl.resourcesneeded.pNext;
if( p == &cl.resourcesneeded )
{
@@ -403,17 +394,16 @@ qboolean CL_RequestMissingResources( void )
void CL_BatchResourceRequest( qboolean initialize )
{
byte data[MAX_INIT_MSG];
resource_t *p, *n;
sizebuf_t msg;
qboolean done_downloading = true;
byte data[MAX_INIT_MSG];
sizebuf_t msg;
qboolean done_downloading = true;
MSG_Init( &msg, "Resource Batch", data, sizeof( data ));
// client resources is not precached by server
if( initialize ) CL_AddClientResources();
for( p = cl.resourcesneeded.pNext; p && p != &cl.resourcesneeded; p = n )
for( resource_t *p = cl.resourcesneeded.pNext, *n; p && p != &cl.resourcesneeded; p = n )
{
n = p->pNext;
@@ -490,10 +480,9 @@ void CL_BatchResourceRequest( qboolean initialize )
int CL_EstimateNeededResources( void )
{
resource_t *p;
int nTotalSize = 0;
int nTotalSize = 0;
for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )
for( resource_t *p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )
{
switch( p->type )
{
@@ -569,9 +558,7 @@ static void CL_StartResourceDownloading( const char *pszMessage, qboolean bCusto
static customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type )
{
customization_t *pList;
for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext )
for( customization_t *pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext )
{
if( pList->resource.type == type )
return pList;
@@ -581,10 +568,8 @@ static customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_
static void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove )
{
customization_t *pList;
customization_t *pNext;
for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext )
customization_t *pNext;
for( customization_t *pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext )
{
pNext = pList->pNext;
@@ -618,17 +603,11 @@ CL_ParseCustomization
*/
static void CL_ParseCustomization( sizebuf_t *msg )
{
customization_t *pExistingCustomization;
customization_t *pList;
qboolean bFound;
resource_t *pRes;
int i;
i = MSG_ReadByte( msg );
int i = MSG_ReadByte( msg );
if( i >= MAX_CLIENTS )
Host_Error( "Bogus player index during customization parsing.\n" );
pRes = Mem_Calloc( cls.mempool, sizeof( resource_t ));
resource_t *pRes = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pRes->type = MSG_ReadByte( msg );
Q_strncpy( pRes->szFileName, MSG_ReadString( msg ), sizeof( pRes->szFileName ));
@@ -655,13 +634,13 @@ static void CL_ParseCustomization( sizebuf_t *msg )
return;
}
pExistingCustomization = CL_PlayerHasCustomization( i, pRes->type );
customization_t *pExistingCustomization = CL_PlayerHasCustomization( i, pRes->type );
if( pExistingCustomization )
CL_RemoveCustomization( i, pExistingCustomization );
bFound = false;
for( pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext )
qboolean bFound = false;
for( customization_t *pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext )
{
if( !memcmp( pList->resource.rgucMD5_hash, pRes->rgucMD5_hash, 16 ))
{
@@ -672,11 +651,11 @@ static void CL_ParseCustomization( sizebuf_t *msg )
if( HPAK_GetDataPointer( hpk_custom_file.string, pRes, NULL, NULL ))
{
qboolean bError = false;
qboolean bError = false;
if( !bFound )
{
pList = &cl.players[pRes->playernum].customdata;
customization_t *pList = &cl.players[pRes->playernum].customdata;
if( !COM_CreateCustomization( pList, pRes, pRes->playernum, FCUST_FROMHPAK, NULL, NULL ))
bError = true;
@@ -704,45 +683,43 @@ CL_ParseResourceRequest
==================
*/
static void CL_ParseResourceRequest( sizebuf_t *msg )
void CL_SendResourceList( const resource_t *list, int count )
{
byte buffer[MAX_INIT_MSG];
int i, arg, nStartIndex;
sizebuf_t sbuf;
byte buffer[MAX_INIT_MSG];
sizebuf_t sbuf;
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
MSG_WriteShort( &sbuf, count );
arg = MSG_ReadLong( msg );
nStartIndex = MSG_ReadLong( msg );
for( int i = 0; i < count; i++ )
{
MSG_WriteString( &sbuf, list[i].szFileName );
MSG_WriteByte( &sbuf, list[i].type );
MSG_WriteShort( &sbuf, list[i].nIndex );
MSG_WriteLong( &sbuf, list[i].nDownloadSize );
MSG_WriteByte( &sbuf, list[i].ucFlags );
if( FBitSet( list[i].ucFlags, RES_CUSTOM ))
MSG_WriteBytes( &sbuf, list[i].rgucMD5_hash, 16 );
}
Netchan_CreateFragments( &cls.netchan, &sbuf );
Netchan_FragSend( &cls.netchan );
}
static void CL_ParseResourceRequest( sizebuf_t *msg )
{
int arg = MSG_ReadLong( msg );
int nStartIndex = MSG_ReadLong( msg );
if( cl.servercount != arg )
return;
if( nStartIndex < 0 && nStartIndex > cl.num_resources )
if( nStartIndex < 0 || nStartIndex >= cl.num_resources )
return;
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
MSG_WriteShort( &sbuf, cl.num_resources );
for( i = nStartIndex; i < cl.num_resources; i++ )
{
MSG_WriteString( &sbuf, cl.resourcelist[i].szFileName );
MSG_WriteByte( &sbuf, cl.resourcelist[i].type );
MSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex );
MSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize );
MSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags );
if( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM ))
MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 );
}
// a1ba: useless check? MSG_BeginClientCmd and MSG_WriteShort will always
// write to the buffer
// if( MSG_GetNumBytesWritten( &sbuf ) > 0 )
{
Netchan_CreateFragments( &cls.netchan, &sbuf );
Netchan_FragSend( &cls.netchan );
}
CL_SendResourceList( &cl.resourcelist[nStartIndex], cl.num_resources - nStartIndex );
}
/*
@@ -754,16 +731,12 @@ loading custom decal for self
*/
static void CL_CreateCustomizationList( void )
{
resource_t *pResource;
player_info_t *pPlayer;
int i;
pPlayer = &cl.players[cl.playernum];
player_info_t *pPlayer = &cl.players[cl.playernum];
pPlayer->customdata.pNext = NULL;
for( i = 0; i < cl.num_resources; i++ )
for( int i = 0; i < cl.num_resources; i++ )
{
pResource = &cl.resourcelist[i];
resource_t *pResource = &cl.resourcelist[i];
if( !COM_CreateCustomization( &pPlayer->customdata, pResource, cl.playernum, 0, NULL, NULL ))
Con_Printf( "problem with client customization %s, ignoring...", pResource->szFileName );
@@ -889,6 +862,15 @@ static void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto )
}
}
if( cl.playernum < 0 || cl.playernum >= MAX_CLIENTS || cl.maxclients <= 0 || cl.maxclients > MAX_CLIENTS )
{
Con_Printf( S_ERROR "%s: invalid playernum or maxclients (%d and %d must be less than %d)\n", __func__,
cl.playernum, cl.maxclients, MAX_CLIENTS );
CL_Disconnect_f();
Host_AbortCurrentFrame();
return;
}
Q_snprintf( mapfile, sizeof( mapfile ), "maps/%s.bsp", clgame.mapname );
if( CRC32_MapFile( &cl.worldmapCRC, mapfile, cl.maxclients > 1 ))
{
@@ -1236,12 +1218,10 @@ CL_ParseLightStyle
*/
static void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto )
{
int style;
const char *s;
float f = cl.mtime[0];
float f = cl.mtime[0];
style = MSG_ReadByte( msg );
s = MSG_ReadString( msg );
int style = MSG_ReadByte( msg );
const char *s = MSG_ReadString( msg );
if( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE )
f = MSG_ReadFloat( msg );
@@ -1269,10 +1249,7 @@ add the view angle yaw
*/
static void CL_ParseAddAngle( sizebuf_t *msg )
{
pred_viewangle_t *a;
float delta_yaw;
delta_yaw = MSG_ReadBitAngle( msg, 16 );
float delta_yaw = MSG_ReadBitAngle( msg, 16 );
#if 0
cl.viewangles[YAW] += delta_yaw;
return;
@@ -1282,7 +1259,7 @@ static void CL_ParseAddAngle( sizebuf_t *msg )
// select entry into circular buffer
cl.angle_position = (cl.angle_position + 1) & ANGLE_MASK;
a = &cl.predicted_angle[cl.angle_position];
pred_viewangle_t *a = &cl.predicted_angle[cl.angle_position];
// record update
a->starttime = cl.mtime[0];
@@ -1312,20 +1289,18 @@ reading decals, etc.
*/
static void CL_ParseRestore( sizebuf_t *msg )
{
string filename;
int i, mapCount;
char *pMapName;
string filename;
// mapname.HL2
Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename ));
mapCount = MSG_ReadByte( msg );
int mapCount = MSG_ReadByte( msg );
// g-cont. acutally in Xash3D this does nothing.
// decals already restored on a server, and correctly transferred through levels
// but i'm leave this message for backward compatibility
for( i = 0; i < mapCount; i++ )
for( int i = 0; i < mapCount; i++ )
{
pMapName = MSG_ReadString( msg );
char *pMapName = MSG_ReadString( msg );
Con_Printf( "Loading decals from %s\n", pMapName );
}
}
@@ -1339,10 +1314,9 @@ register new user message or update existing
*/
static void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto )
{
char *pszName;
char szName[17];
int size;
int svc_num = MSG_ReadByte( msg );
int size;
if( proto == PROTO_GOLDSRC )
{
@@ -1357,6 +1331,7 @@ static void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto )
size = -1;
}
char *pszName;
if( proto == PROTO_GOLDSRC )
{
MSG_ReadBytes( msg, szName, sizeof( szName ) - 1 );
@@ -1377,9 +1352,8 @@ collect userinfo from all players
*/
static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
{
int slot, id;
qboolean active;
player_info_t *player;
int slot, id;
qboolean active;
if( proto == PROTO_GOLDSRC )
{
@@ -1397,7 +1371,7 @@ static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
if( slot >= MAX_CLIENTS )
Host_Error( "%s: svc_updateuserinfo >= MAX_CLIENTS\n", __func__ );
player = &cl.players[slot];
player_info_t *player = &cl.players[slot];
if( active )
{
@@ -1438,9 +1412,7 @@ downloading and precache resource in-game
*/
void CL_ParseResource( sizebuf_t *msg )
{
resource_t *pResource;
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
resource_t *pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = MSG_ReadUBitLong( msg, 4 );
Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));
@@ -1454,31 +1426,31 @@ void CL_ParseResource( sizebuf_t *msg )
if( MSG_ReadOneBit( msg ))
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
if( pResource->type == t_sound && pResource->nIndex > MAX_SOUNDS )
if( pResource->type == t_sound && pResource->nIndex >= MAX_SOUNDS )
{
Mem_Free( pResource );
Host_Error( "bad sound index\n" );
}
if( pResource->type == t_model && pResource->nIndex > MAX_MODELS )
if( pResource->type == t_model && pResource->nIndex >= MAX_MODELS )
{
Mem_Free( pResource );
Host_Error( "bad model index\n" );
}
if( pResource->type == t_eventscript && pResource->nIndex > MAX_EVENTS )
if( pResource->type == t_eventscript && pResource->nIndex >= MAX_EVENTS )
{
Mem_Free( pResource );
Host_Error( "bad event index\n" );
}
if( pResource->type == t_generic && pResource->nIndex > MAX_CUSTOM )
if( pResource->type == t_generic && pResource->nIndex >= MAX_CUSTOM )
{
Mem_Free( pResource );
Host_Error( "bad file index\n" );
}
if( pResource->type == t_decal && pResource->nIndex > MAX_DECALS )
if( pResource->type == t_decal && pResource->nIndex >= MAX_DECALS )
{
Mem_Free( pResource );
Host_Error( "bad decal index\n" );
@@ -1500,13 +1472,10 @@ void CL_UpdateUserPings( sizebuf_t *msg )
// because pings message always ends by null bit
while( 1 )
{
int slot;
player_info_t *player;
if( !MSG_ReadOneBit( msg ))
break; // end of message
slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );
int slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );
if( unlikely( slot >= MAX_CLIENTS ))
{
@@ -1514,7 +1483,7 @@ void CL_UpdateUserPings( sizebuf_t *msg )
return;
}
player = &cl.players[slot];
player_info_t *player = &cl.players[slot];
player->ping = MSG_ReadUBitLong( msg, 12 );
player->packet_loss = MSG_ReadUBitLong( msg, 7 );
}
@@ -1539,18 +1508,14 @@ static const char *CL_CheckTypeToString( int check_type )
static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
{
qboolean user_changed_diskfile;
vec3_t mins, maxs;
string filename;
byte md5[16] = { 0 };
int pos;
byte md5[16] = { 0 };
if( !cl.need_force_consistency_response )
return;
cl.need_force_consistency_response = false;
MSG_BeginClientCmd( msg, clc_fileconsistency );
pos = MSG_GetNumBytesWritten( msg );
int pos = MSG_GetNumBytesWritten( msg );
if( proto == PROTO_GOLDSRC )
{
MSG_WriteShort( msg, 0 );
@@ -1562,9 +1527,11 @@ static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
for( int i = 0; i < cl.num_consistency; i++ )
{
qboolean have_file = true;
qboolean user_changed_diskfile = false;
vec3_t mins, maxs;
string filename;
consistency_t *pc = &cl.consistency_list[i];
user_changed_diskfile = false;
MSG_WriteOneBit( msg, 1 );
MSG_WriteUBitLong( msg, pc->orig_index, MAX_MODEL_BITS );
@@ -1643,10 +1610,9 @@ static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
if( proto == PROTO_GOLDSRC )
{
int len;
MSG_EndBitWriting( msg );
len = MSG_GetNumBytesWritten( msg ) - pos - 2;
int len = MSG_GetNumBytesWritten( msg ) - pos - 2;
*(short *)&msg->pData[pos] = len;
COM_Munge( &msg->pData[pos + 2], len, cl.servercount );
@@ -1662,11 +1628,10 @@ static void CL_StartDark( void )
{
if( v_dark.value )
{
screenfade_t *sf = &clgame.fade;
float fadetime = 5.0f;
client_textmessage_t *title;
screenfade_t *sf = &clgame.fade;
float fadetime = 5.0f;
title = CL_TextMessageGet( "GAMETITLE" );
client_textmessage_t *title = CL_TextMessageGet( "GAMETITLE" );
if( Host_IsQuakeCompatible( ))
fadetime = 1.0f;
@@ -1738,6 +1703,8 @@ void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )
// tell rendering system we have a new set of models.
ref.dllFuncs.R_NewMap ();
Mod_LoadDetailTextures( cl.worldmodel );
// check if this map must start from dark screen
CL_StartDark ();
@@ -1776,20 +1743,12 @@ void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )
static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
{
int lastcheck;
int delta;
int i;
int isdelta;
resource_t *pResource;
resource_t *skip_crc_change;
int skip;
consistency_t *pc;
byte nullbuffer[32];
byte nullbuffer[32];
memset( nullbuffer, 0, 32 );
cl.need_force_consistency_response = MSG_ReadOneBit( msg );
pResource = cl.resourcesneeded.pNext;
resource_t *pResource = cl.resourcesneeded.pNext;
if( !cl.need_force_consistency_response )
return;
@@ -1803,19 +1762,20 @@ static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
if( cl_trace_consistency.value )
Con_Printf( "Server wants consistency of the following resources:\n" );
skip_crc_change = NULL;
lastcheck = 0;
resource_t *skip_crc_change = NULL;
int lastcheck = 0;
while( MSG_ReadOneBit( msg ))
{
isdelta = MSG_ReadOneBit( msg );
int isdelta = MSG_ReadOneBit( msg );
int delta;
if( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck;
else delta = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_MODEL_BITS : MAX_MODEL_BITS );
skip = delta - lastcheck;
int skip = delta - lastcheck;
for( i = 0; i < skip; i++ )
for( int i = 0; i < skip; i++ )
{
if( pResource != skip_crc_change && Q_strstr( pResource->szFileName, "models/" ))
Mod_NeedCRC( pResource->szFileName, false );
@@ -1831,7 +1791,7 @@ static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
if( cl.num_consistency >= MAX_MODELS )
Host_Error( "%s: MAX_MODELS limit exceeded (%d)\n", __func__, MAX_MODELS );
pc = &cl.consistency_list[cl.num_consistency];
consistency_t *pc = &cl.consistency_list[cl.num_consistency];
cl.num_consistency++;
memset( pc, 0, sizeof( *pc ));
@@ -1863,14 +1823,11 @@ CL_ParseResourceList
*/
void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto )
{
resource_t *pResource;
int i, total;
int total = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_RESOURCE_BITS : MAX_RESOURCE_BITS );
total = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_RESOURCE_BITS : MAX_RESOURCE_BITS );
for( i = 0; i < total; i++ )
for( int i = 0; i < total; i++ )
{
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
resource_t *pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = MSG_ReadUBitLong( msg, 4 );
Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));
@@ -2067,14 +2024,12 @@ Set screen fade
*/
static void CL_ParseScreenFade( sizebuf_t *msg )
{
float duration, holdTime;
screenfade_t *sf = &clgame.fade;
float flScale;
screenfade_t *sf = &clgame.fade;
duration = (float)MSG_ReadWord( msg );
holdTime = (float)MSG_ReadWord( msg );
float duration = (float)MSG_ReadWord( msg );
float holdTime = (float)MSG_ReadWord( msg );
sf->fadeFlags = MSG_ReadShort( msg );
flScale = FBitSet( sf->fadeFlags, FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f);
float flScale = FBitSet( sf->fadeFlags, FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f);
sf->fader = MSG_ReadByte( msg );
sf->fadeg = MSG_ReadByte( msg );
@@ -2183,9 +2138,6 @@ Exec map/class specific configs
*/
static void CL_ParseExec( sizebuf_t *msg )
{
qboolean is_class;
int class_idx;
string mapname;
const char *class_cfgs[] = {
"",
"exec scout.cfg\n",
@@ -2201,17 +2153,18 @@ static void CL_ParseExec( sizebuf_t *msg )
"exec civilian.cfg\n"
};
is_class = MSG_ReadByte( msg );
qboolean is_class = MSG_ReadByte( msg );
if( is_class )
{
class_idx = MSG_ReadByte( msg );
int class_idx = MSG_ReadByte( msg );
if( class_idx >= 0 && class_idx <= 11 && !Q_stricmp( GI->gamefolder, "tfc" ) )
Cbuf_AddText( class_cfgs[class_idx] );
}
else if( !Q_stricmp( GI->gamefolder, "tfc" ) )
{
string mapname;
Cbuf_AddText( "exec mapdefault.cfg\n" );
COM_FileBase( clgame.mapname, mapname, sizeof( mapname ));
@@ -2365,17 +2318,15 @@ show display finale or cutscene message
*/
static void CL_ParseFinaleCutscene( sizebuf_t *msg, int level )
{
static int msgindex = 0;
client_textmessage_t *text;
int channel;
static int msgindex = 0;
cl.intermission = level;
channel = msgindex;
int channel = msgindex;
msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);
// grab message channel
text = &cl_textmessage[channel];
client_textmessage_t *text = &cl_textmessage[channel];
// NOTE: svc_finale and svc_cutscene has a
// predefined settings like Quake-style
@@ -2587,9 +2538,9 @@ dispatch messages
*/
void CL_ParseServerMessage( sizebuf_t *msg )
{
size_t bufStart, playerbytes;
int cmd;
int old_background;
size_t bufStart, playerbytes;
int cmd;
int old_background;
// parse the message
while( 1 )

View File

@@ -285,19 +285,17 @@ static void CL_CopyPacketEntity( frame_t *frame, int num, const entity_state_t *
static int CL_ParsePacketEntitiesGS( sizebuf_t *msg, qboolean delta )
{
frame_t *frame, *oldframe;
int oldindex, oldnum, numbase = 0;
entity_state_t *oldent;
int count;
frame_t *oldframe;
int numbase = 0;
int playerbytes = 0;
// save first uncompressed packet as timestamp
if( cls.changelevel && !delta && cls.demorecording )
CL_WriteDemoJumpTime();
count = MSG_ReadWord( msg );
int count = MSG_ReadWord( msg );
frame = &cl.frames[cl.parsecountmod];
frame_t *frame = &cl.frames[cl.parsecountmod];
memset( frame->flags, 0, sizeof( frame->flags ));
frame->first_entity = cls.next_client_entities;
frame->num_entities = 0;
@@ -326,9 +324,9 @@ static int CL_ParsePacketEntitiesGS( sizebuf_t *msg, qboolean delta )
MSG_StartBitWriting( msg );
oldent = NULL;
oldindex = 0;
oldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent );
entity_state_t *oldent = NULL;
int oldindex = 0;
int oldnum = CL_UpdateOldEntNum( oldindex, oldframe, &oldent );
// read it all but ignore it
while( 1 )
@@ -415,10 +413,9 @@ static int CL_ParsePacketEntitiesGS( sizebuf_t *msg, qboolean delta )
static float MSG_ReadGSBitCoord( sizebuf_t *sb )
{
float value = 0;
int ival, fval;
ival = MSG_ReadOneBit( sb );
fval = MSG_ReadOneBit( sb );
int ival = MSG_ReadOneBit( sb );
int fval = MSG_ReadOneBit( sb );
if( ival || fval )
{
@@ -439,13 +436,11 @@ static float MSG_ReadGSBitCoord( sizebuf_t *sb )
static void MSG_ReadGSBitVec3Coord( sizebuf_t *sb, vec3_t fa )
{
qboolean x, y, z;
VectorClear( fa );
x = MSG_ReadOneBit( sb );
y = MSG_ReadOneBit( sb );
z = MSG_ReadOneBit( sb );
qboolean x = MSG_ReadOneBit( sb );
qboolean y = MSG_ReadOneBit( sb );
qboolean z = MSG_ReadOneBit( sb );
if( x )
fa[0] = MSG_ReadGSBitCoord( sb );
if( y )
@@ -456,31 +451,32 @@ static void MSG_ReadGSBitVec3Coord( sizebuf_t *sb, vec3_t fa )
static void CL_ParseSoundPacketGS( sizebuf_t *msg )
{
vec3_t pos;
int chan, sound;
float volume, attn;
int flags, pitch, entnum;
sound_t handle = 0;
MSG_StartBitWriting( msg );
flags = MSG_ReadUBitLong( msg, 9 );
int flags = MSG_ReadUBitLong( msg, 9 );
float volume;
if( FBitSet( flags, SND_VOLUME ))
volume = (float)MSG_ReadByte( msg ) / 255.0f;
else volume = VOL_NORM;
float attn;
if( FBitSet( flags, SND_ATTENUATION ))
attn = (float)MSG_ReadByte( msg ) / 64.0f;
else attn = ATTN_NONE;
chan = MSG_ReadUBitLong( msg, 3 );
entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );
int chan = MSG_ReadUBitLong( msg, 3 );
int entnum = MSG_ReadUBitLong( msg, MAX_GOLDSRC_ENTITY_BITS );
int sound;
if( FBitSet( flags, SND_GOLDSRC_LARGE_INDEX ))
sound = MSG_ReadWord( msg );
else sound = MSG_ReadByte( msg );
vec3_t pos;
MSG_ReadGSBitVec3Coord( msg, pos );
int pitch;
if( FBitSet( flags, SND_PITCH ))
pitch = MSG_ReadByte( msg );
else pitch = PITCH_NORM;
@@ -514,16 +510,23 @@ static void CL_ParseSoundPacketGS( sizebuf_t *msg )
static void CL_ParseSpawnStaticSound( sizebuf_t *msg )
{
vec3_t pos;
int handle, entnum, pitch, flags;
float volume, attn;
sound_t handle = 0;
MSG_ReadVec3Coord( msg, pos );
handle = MSG_ReadShort( msg );
volume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f );
attn = MSG_ReadByte( msg ) * ( 1.0f / 64.0f );
entnum = MSG_ReadShort( msg );
pitch = MSG_ReadByte( msg );
flags = MSG_ReadByte( msg );
int sound = MSG_ReadShort( msg );
float volume = MSG_ReadByte( msg ) * ( 1.0f / 255.0f );
float attn = MSG_ReadByte( msg ) * ( 1.0f / 64.0f );
int entnum = MSG_ReadShort( msg );
int pitch = MSG_ReadByte( msg );
int flags = MSG_ReadByte( msg );
if( FBitSet( flags, SND_SENTENCE ))
{
char sentenceName[32];
Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound );
handle = S_RegisterSound( sentenceName );
}
else handle = cl.sound_index[sound]; // see precached sound
S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags );
}

View File

@@ -80,14 +80,13 @@ we don't want interpolate this
*/
static qboolean CL_QuakeEntityTeleported( cl_entity_t *ent, entity_state_t *newstate )
{
float len, maxlen;
vec3_t delta;
vec3_t delta;
VectorSubtract( newstate->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 );
float maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
float len = VectorLength( delta );
return (len > maxlen);
}
@@ -139,33 +138,30 @@ CL_ParseQuakeSound
*/
static void CL_ParseQuakeSound( sizebuf_t *msg )
{
int channel, sound;
int flags, entnum;
float volume, attn;
sound_t handle;
vec3_t pos;
flags = MSG_ReadByte( msg );
int flags = MSG_ReadByte( msg );
float volume;
if( FBitSet( flags, SND_VOLUME ))
volume = (float)MSG_ReadByte( msg ) / 255.0f;
else volume = VOL_NORM;
float attn;
if( FBitSet( flags, SND_ATTENUATION ))
attn = (float)MSG_ReadByte( msg ) / 64.0f;
else attn = ATTN_NONE;
channel = MSG_ReadWord( msg );
sound = MSG_ReadByte( msg ); // Quake1 have max 255 precached sounds. erm
int channel = MSG_ReadWord( msg );
int sound = MSG_ReadByte( msg ); // Quake1 have max 255 precached sounds. erm
// positioned in space
vec3_t pos;
MSG_ReadVec3Coord( msg, pos );
entnum = channel >> 3; // entity reletive
int entnum = channel >> 3; // entity reletive
channel &= 7;
// see precached sound
handle = cl.sound_index[sound];
sound_t handle = cl.sound_index[sound];
if( !cl.audio_prepped )
return; // too early
@@ -181,11 +177,6 @@ CL_ParseQuakeServerInfo
*/
static void CL_ParseQuakeServerInfo( sizebuf_t *msg )
{
resource_t *pResource;
const char *pResName;
int gametype;
int i;
Con_Reportf( "Serverdata packet received.\n" );
cls.timestart = Platform_DoubleTime();
@@ -198,7 +189,7 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg )
cls.state = ca_connected;
// parse protocol version number
i = MSG_ReadLong( msg );
int i = MSG_ReadLong( msg );
if( i != PROTOCOL_VERSION_QUAKE )
{
@@ -208,7 +199,7 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg )
}
cl.maxclients = MSG_ReadByte( msg );
gametype = MSG_ReadByte( msg );
int gametype = MSG_ReadByte( msg );
clgame.maxEntities = GI->max_edicts;
clgame.maxEntities = bound( 600, clgame.maxEntities, MAX_EDICTS );
clgame.maxModels = MAX_MODELS;
@@ -264,12 +255,12 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg )
// Quake just have a large packet of initialization data
for( i = 1; i < MAX_MODELS; i++ )
{
pResName = MSG_ReadString( msg );
const char *pResName = MSG_ReadString( msg );
if( COM_StringEmptyOrNULL( pResName ))
break; // end of list
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
resource_t *pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = t_model;
Q_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName ));
@@ -282,12 +273,12 @@ static void CL_ParseQuakeServerInfo( sizebuf_t *msg )
for( i = 1; i < MAX_SOUNDS; i++ )
{
pResName = MSG_ReadString( msg );
const char *pResName = MSG_ReadString( msg );
if( COM_StringEmptyOrNULL( pResName ))
break; // end of list
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
resource_t *pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = t_sound;
Q_strncpy( pResource->szFileName, pResName, sizeof( pResource->szFileName ));
@@ -340,15 +331,12 @@ CL_ParseQuakeClientData
*/
static void CL_ParseQuakeClientData( sizebuf_t *msg )
{
int i, bits = MSG_ReadWord( msg );
frame_t *frame;
int bits = MSG_ReadWord( msg );
// this is the frame update that this message corresponds to
i = cls.netchan.incoming_sequence;
cl.parsecount = i; // ack'd incoming messages.
cl.parsecount = cls.netchan.incoming_sequence; // ack'd incoming messages.
cl.parsecountmod = cl.parsecount & CL_UPDATE_MASK; // index into window.
frame = &cl.frames[cl.parsecountmod]; // frame at index.
frame_t *frame = &cl.frames[cl.parsecountmod]; // frame at index.
frame->time = cl.mtime[0]; // mark network received time
frame->receivedtime = host.realtime; // time now that we are parsing.
memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t ));
@@ -365,7 +353,7 @@ static void CL_ParseQuakeClientData( sizebuf_t *msg )
cl.local.idealpitch = MSG_ReadChar( msg );
else cl.local.idealpitch = 0;
for( i = 0; i < 3; i++ )
for( int i = 0; i < 3; i++ )
{
if( FBitSet( bits, SU_PUNCH1 << i ))
frame->clientdata.punchangle[i] = (float)MSG_ReadChar( msg );
@@ -568,15 +556,14 @@ CL_ParseQuakeParticles
*/
static void CL_ParseQuakeParticle( sizebuf_t *msg )
{
int count, color;
vec3_t org, dir;
vec3_t org, dir;
MSG_ReadVec3Coord( msg, org );
dir[0] = MSG_ReadChar( msg ) * 0.0625f;
dir[1] = MSG_ReadChar( msg ) * 0.0625f;
dir[2] = MSG_ReadChar( msg ) * 0.0625f;
count = MSG_ReadByte( msg );
color = MSG_ReadByte( msg );
int count = MSG_ReadByte( msg );
int color = MSG_ReadByte( msg );
if( count == 255 ) count = 1024;
R_RunParticleEffect( org, dir, color, count );
@@ -590,14 +577,12 @@ CL_ParseQuakeStaticSound
*/
static void CL_ParseQuakeStaticSound( sizebuf_t *msg )
{
int sound_num;
float vol, attn;
vec3_t org;
vec3_t org;
MSG_ReadVec3Coord( msg, org );
sound_num = MSG_ReadByte( msg );
vol = (float)MSG_ReadByte( msg ) / 255.0f;
attn = (float)MSG_ReadByte( msg ) / 64.0f;
int sound_num = MSG_ReadByte( msg );
float vol = (float)MSG_ReadByte( msg ) / 255.0f;
float attn = (float)MSG_ReadByte( msg ) / 64.0f;
S_StartSound( org, 0, CHAN_STATIC, cl.sound_index[sound_num], vol, attn, PITCH_NORM, 0 );
}
@@ -628,8 +613,6 @@ CL_ParseStaticEntity
static void CL_ParseQuakeStaticEntity( sizebuf_t *msg )
{
entity_state_t state = { 0 };
cl_entity_t *ent;
int i;
if( !clgame.static_entities )
clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );
@@ -645,14 +628,14 @@ static void CL_ParseQuakeStaticEntity( sizebuf_t *msg )
state.origin[2] = MSG_ReadCoord( msg );
state.angles[2] = MSG_ReadAngle( msg );
i = clgame.numStatics;
int i = clgame.numStatics;
if( i >= MAX_STATIC_ENTITIES )
{
Con_Printf( S_ERROR "%s: static entities limit exceeded!\n", __func__ );
return;
}
ent = &clgame.static_entities[i];
cl_entity_t *ent = &clgame.static_entities[i];
clgame.numStatics++;
ent->index = 0; // ???
@@ -692,16 +675,13 @@ CL_ParseQuakeBaseline
*/
static void CL_ParseQuakeBaseline( sizebuf_t *msg )
{
entity_state_t state;
cl_entity_t *ent;
int newnum;
newnum = MSG_ReadWord( msg ); // entnum
int newnum = MSG_ReadWord( msg ); // entnum
if( newnum >= clgame.maxEntities )
Host_Error( "%s: no free edicts\n", __func__ );
// parse baseline
entity_state_t state;
memset( &state, 0, sizeof( state ));
state.modelindex = MSG_ReadByte( msg );
state.frame = MSG_ReadByte( msg );
@@ -714,7 +694,7 @@ static void CL_ParseQuakeBaseline( sizebuf_t *msg )
state.origin[2] = MSG_ReadCoord( msg );
state.angles[2] = MSG_ReadAngle( msg );
ent = CL_EDICT_NUM( newnum );
cl_entity_t *ent = CL_EDICT_NUM( newnum );
ent->index = newnum;
ent->player = CL_IsPlayerIndex( newnum );
ent->prevstate = ent->baseline = state;
@@ -973,12 +953,21 @@ void CL_ParseQuakeMessage( sizebuf_t *msg )
break;
case svc_updatename:
param1 = MSG_ReadByte( msg );
if( param1 >= ARRAYSIZE( cl.players ))
{
MSG_ReadString( msg );
break;
}
Q_strncpy( cl.players[param1].name, MSG_ReadString( msg ), sizeof( cl.players[0].name ));
Q_strncpy( cl.players[param1].model, "player", sizeof( cl.players[0].name ));
break;
case svc_updatefrags:
param1 = MSG_ReadByte( msg );
param2 = MSG_ReadShort( msg );
if( param1 >= ARRAYSIZE( cl.players ))
break;
// HACKHACK: store frags into spectator
cl.players[param1].spectator = param2;
break;
@@ -994,6 +983,9 @@ void CL_ParseQuakeMessage( sizebuf_t *msg )
case svc_updatecolors:
param1 = MSG_ReadByte( msg );
param2 = MSG_ReadByte( msg );
if( param1 >= ARRAYSIZE( cl.players ) || param2 >= ARRAYSIZE( cl.players ))
break;
cl.players[param1].topcolor = param2 & 0xF;
cl.players[param1].bottomcolor = (param2 & 0xF0) >> 4;
break;

View File

@@ -99,6 +99,7 @@ void SX_RoomFX( portable_samplepair_t *paint, int num_samples );
void SX_ClearState( void );
qboolean S_Init( void );
qboolean S_InitSoundAPI( void );
void S_Shutdown( void );
void S_SoundList_f( void );
void S_SoundInfo_f( void );
@@ -111,7 +112,7 @@ int S_GetCurrentDynamicSounds( soundlist_t *pout, int size );
sfx_t *S_GetSfxByHandle( sound_t handle );
rawchan_t *S_FindRawChannel( int entnum, qboolean create );
uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data );
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol );
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn );
void S_StopSound( int entnum, int channel, const char *soundname );
void S_UpdateFrame( struct ref_viewpass_s *rvp );
void S_StopAllSounds( qboolean ambient );

View File

@@ -386,13 +386,14 @@ Do stereo processing
static void DLY_DoStereoDelay( portable_samplepair_t *paint, int count )
{
dly_t *dly = &stereodly;
int delay, samplexf;
if( !dly->lpdelayline )
return; // inactive
for( ; count; count--, paint++ )
{
int delay;
if( dly->mod && --dly->modcur < 0 )
dly->modcur = dly->mod;
@@ -414,7 +415,7 @@ static void DLY_DoStereoDelay( portable_samplepair_t *paint, int count )
// modify delay, if crossfading
if( dly->xfade )
{
samplexf = dly->lpdelayline[dly->idelayoutputxf] * (128 - dly->xfade) >> 7;
int samplexf = dly->lpdelayline[dly->idelayoutputxf] * (128 - dly->xfade) >> 7;
delay = samplexf + ((delay * dly->xfade) >> 7);
if( ++dly->idelayoutputxf >= dly->cdelaysamplesmax )
@@ -496,14 +497,13 @@ Do delay processing
static void DLY_DoDelay( portable_samplepair_t *paint, int count )
{
dly_t *dly = &monodly;
int delay;
if( !dly->lpdelayline || !count )
return; // inactive
for( ; count; count--, paint++ )
{
delay = dly->lpdelayline[dly->idelayoutput];
int delay = dly->lpdelayline[dly->idelayoutput];
// don't process if delay line and left/right samples are zero
if( delay || paint->left || paint->right )
@@ -613,8 +613,6 @@ Do reverberation for one dly
static int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samplepair_t *samplepair )
{
int delay;
int samplexf;
int val, valt;
int voutm = 0;
if( --dly->modcur < 0 )
@@ -624,6 +622,8 @@ static int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samp
if( dly->xfade || delay || samplepair->left || samplepair->right )
{
int val, valt;
// modulate delay rate
if( !dly->mod )
{
@@ -636,7 +636,7 @@ static int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samp
if( dly->xfade )
{
samplexf = (dly->lpdelayline[dly->idelayoutputxf] * (REVERB_XFADE - dly->xfade)) / REVERB_XFADE;
int samplexf = (dly->lpdelayline[dly->idelayoutputxf] * (REVERB_XFADE - dly->xfade)) / REVERB_XFADE;
delay = ((delay * dly->xfade) / REVERB_XFADE) + samplexf;
if( ++dly->idelayoutputxf >= dly->cdelaysamplesmax )
@@ -688,14 +688,14 @@ static void RVB_DoReverb( portable_samplepair_t *paint, int count )
{
dly_t *dly1 = &reverbdly[0];
dly_t *dly2 = &reverbdly[1];
int vlr, voutm;
if( !dly1->lpdelayline )
return;
for( ; count; count--, paint++ )
{
vlr = ( paint->left + paint->right ) >> 1;
int vlr = ( paint->left + paint->right ) >> 1;
int voutm;
voutm = RVB_DoReverbForOneDly( dly1, vlr, paint );
voutm += RVB_DoReverbForOneDly( dly2, vlr, paint );
@@ -881,9 +881,8 @@ static void SX_Profiling_f( void )
portable_samplepair_t testbuffer[512];
float oldroom = room_type.value;
double start, end;
int i, calls;
for( i = 0; i < ARRAYSIZE( testbuffer ); i++ )
for( int i = 0; i < ARRAYSIZE( testbuffer ); i++ )
{
testbuffer[i].left = COM_RandomLong( 0, 3000 );
testbuffer[i].right = COM_RandomLong( 0, 3000 );
@@ -899,7 +898,7 @@ static void SX_Profiling_f( void )
Con_Printf( "Profiling 10000 calls to DSP. Sample count is 512, room_type is %i\n", idsp_room );
start = Platform_DoubleTime();
for( calls = 10000; calls; calls-- )
for( int calls = 10000; calls; calls-- )
{
SX_RoomFX( testbuffer, 512 );
}

View File

@@ -39,17 +39,16 @@ S_SoundList_f
*/
void S_SoundList_f( void )
{
sfx_t *sfx;
wavdata_t *sc;
int i, totalSfx = 0;
int totalSize = 0;
sfx_t *sfx;
int i, totalSfx = 0;
int totalSize = 0;
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
{
if( !sfx->name[0] )
continue;
sc = sfx->cache;
wavdata_t *sc = sfx->cache;
if( sc )
{
totalSize += sc->size;
@@ -149,7 +148,7 @@ S_FindName
sfx_t *S_FindName( const char *pname, qboolean *pfInCache )
{
sfx_t *sfx;
uint i, hash;
uint i;
string name;
if( COM_StringEmptyOrNULL( pname ) || !snd.initialized )
@@ -162,7 +161,7 @@ sfx_t *S_FindName( const char *pname, qboolean *pfInCache )
COM_FixSlashes( name );
// see if already loaded
hash = COM_HashKey( name, MAX_SFX_HASH );
uint hash = COM_HashKey( name, MAX_SFX_HASH );
for( sfx = s_sfxHashList[hash]; sfx; sfx = sfx->hashNext )
{
if( !Q_strcmp( sfx->name, name ))
@@ -210,17 +209,14 @@ S_FreeSound
*/
void S_FreeSound( sfx_t *sfx )
{
sfx_t *hashSfx;
sfx_t **prev;
if( !sfx || !sfx->name[0] )
return;
// de-link it from the hash tree
prev = &s_sfxHashList[sfx->hashValue];
sfx_t **prev = &s_sfxHashList[sfx->hashValue];
while( 1 )
{
hashSfx = *prev;
sfx_t *hashSfx = *prev;
if( !hashSfx )
break;
@@ -251,12 +247,10 @@ S_BeginRegistration
*/
void S_BeginRegistration( void )
{
int i;
snd.have_ambient_sfx = false;
// check for automatic ambient sounds
for( i = 0; i < NUM_AMBIENTS; i++ )
for( int i = 0; i < NUM_AMBIENTS; i++ )
{
if( !GI->ambientsound[i][0] )
continue; // empty slot
@@ -386,6 +380,7 @@ void S_FreeSounds( void )
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
S_FreeSound( sfx );
memset( s_knownSfx, 0, sizeof( s_knownSfx ));
memset( s_sfxHashList, 0, sizeof( s_sfxHashList ));

View File

@@ -290,8 +290,6 @@ static int SND_GetChannelTimeLeft( const channel_t *ch )
if( ch->words ) // sentences are special, count all remaining words
{
int i;
if( FBitSet( ch->flags, FL_CHAN_SENTENCE_FINISHED ) )
return 0;
@@ -300,7 +298,7 @@ static int SND_GetChannelTimeLeft( const channel_t *ch )
// here we count all remaining words, stopping if no sfx or sound file is available
// see VOX_LoadWord
for( i = ch->word_index + 1; i < CVOXWORDMAX; i++ )
for( int i = ch->word_index + 1; i < CVOXWORDMAX; i++ )
{
wavdata_t *sc;
int end;
@@ -341,14 +339,9 @@ exceptions).
*/
channel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean *ignore )
{
int ch_idx;
int first_to_die;
int life_left;
int timeleft;
int first_to_die = -1;
int life_left = 0x7fffffff;
// check for replacement sound, or find the best one to replace
first_to_die = -1;
life_left = 0x7fffffff;
if( ignore ) *ignore = false;
if( channel == CHAN_STREAM && SND_FStreamIsPlaying( sfx ))
@@ -358,9 +351,10 @@ channel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean
return NULL;
}
for( ch_idx = NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++ )
for( int ch_idx = NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++ )
{
channel_t *ch = &snd.channels[ch_idx];
int timeleft;
// Never override a streaming sound that is currently playing or
// voice over IP data that is playing or any sound on CHAN_VOICE( acting )
@@ -627,8 +621,8 @@ void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fv
{
wavdata_t *pSource;
sfx_t *sfx = NULL;
channel_t *target_chan, *check;
int vol, ch_idx;
channel_t *target_chan;
int vol;
qboolean bIgnore = false;
if( !snd.initialized ) return;
@@ -874,7 +868,7 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
channel_t *ch;
wavdata_t *pSource = NULL;
sfx_t *sfx = NULL;
int vol, fvox = 0;
int vol;
if( !snd.initialized ) return;
sfx = S_GetSfxByHandle( handle );
@@ -911,7 +905,6 @@ void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, floa
Q_strncpy( ch->name, sfx->name, sizeof( ch->name ));
sfx = ch->sfx;
if( sfx ) pSource = sfx->cache;
fvox = 1;
}
else
{
@@ -1020,14 +1013,14 @@ grab all static sounds playing at current channel
int S_GetCurrentDynamicSounds( soundlist_t *pout, int size )
{
int sounds_left = size;
int i, looped;
if( !snd.initialized )
return 0;
for( i = 0; i < snd.max_channels && sounds_left; i++ )
for( int i = 0; i < snd.max_channels && sounds_left; i++ )
{
const channel_t *ch = &snd.channels[i];
int looped;
if( !ch->sfx || !ch->sfx->name[0] || !Q_stricmp( ch->sfx->name, "*default" ))
continue; // don't serialize default sounds
@@ -1085,8 +1078,6 @@ S_UpdateAmbientSounds
*/
static void S_UpdateAmbientSounds( void )
{
int ambient_channel;
if( !snd.have_ambient_sfx )
return;
@@ -1117,7 +1108,7 @@ static void S_UpdateAmbientSounds( void )
continue;
}
float vol = s_ambient_level.value * leaf->ambient_sound_level[ambient_channel];
float vol = s_ambient_level.value * leaf->ambient_sound_level[i];
if( vol < 0.0f )
vol = 0.0f;
@@ -1223,14 +1214,13 @@ S_RawSamplesStereo
*/
uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data )
{
uint fracstep, samplefrac;
uint src, dst;
uint src;
if( rawend < snd.paintedtime )
rawend = snd.paintedtime;
fracstep = ((double) rate / (double)SOUND_DMA_SPEED) * (double)(1 << S_RAW_SAMPLES_PRECISION_BITS);
samplefrac = 0;
uint fracstep = ((double) rate / (double)SOUND_DMA_SPEED) * (double)(1 << S_RAW_SAMPLES_PRECISION_BITS);
uint samplefrac = 0;
if( width == 2 )
{
@@ -1240,7 +1230,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma
{
for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))
{
dst = rawend++ & ( max_samples - 1 );
uint dst = rawend++ & ( max_samples - 1 );
rawsamples[dst].left = in[src*2+0];
rawsamples[dst].right = in[src*2+1];
}
@@ -1249,7 +1239,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma
{
for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))
{
dst = rawend++ & ( max_samples - 1 );
uint dst = rawend++ & ( max_samples - 1 );
rawsamples[dst].left = in[src];
rawsamples[dst].right = in[src];
}
@@ -1263,7 +1253,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma
for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))
{
dst = rawend++ & ( max_samples - 1 );
uint dst = rawend++ & ( max_samples - 1 );
rawsamples[dst].left = in[src*2+0] << 8;
rawsamples[dst].right = in[src*2+1] << 8;
}
@@ -1272,7 +1262,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma
{
for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS ))
{
dst = rawend++ & ( max_samples - 1 );
uint dst = rawend++ & ( max_samples - 1 );
rawsamples[dst].left = ( data[src] - 128 ) << 8;
rawsamples[dst].right = ( data[src] - 128 ) << 8;
}
@@ -1287,7 +1277,7 @@ uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint ma
S_RawEntSamples
===================
*/
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol )
void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn )
{
rawchan_t *ch;
@@ -1298,7 +1288,7 @@ void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word chan
return;
ch->master_vol = snd_vol;
ch->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE);
ch->dist_mult = (attn / SND_CLIP_DISTANCE);
ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, samples, rate, width, channels, data );
ch->leftvol = ch->rightvol = snd_vol;
}
@@ -1312,9 +1302,7 @@ Free raw channel that have been idling for too long.
*/
static void S_FreeIdleRawChannels( void )
{
int i;
for( i = 0; i < snd.max_raw_channels; i++ )
for( int i = 0; i < snd.max_raw_channels; i++ )
{
rawchan_t *ch = snd.raw_channels[i];
@@ -1347,9 +1335,7 @@ S_ClearRawChannels
*/
static void S_ClearRawChannels( void )
{
int i;
for( i = 0; i < snd.max_raw_channels; i++ )
for( int i = 0; i < snd.max_raw_channels; i++ )
{
rawchan_t *ch = snd.raw_channels[i];
@@ -1467,12 +1453,10 @@ S_StopAllSounds
*/
void S_StopAllSounds( qboolean ambient )
{
int i;
if( !snd.initialized ) return;
snd.total_channels = MAX_DYNAMIC_CHANNELS; // no statics
for( i = 0; i < snd.max_channels; i++ )
for( int i = 0; i < snd.max_channels; i++ )
{
if( !snd.channels[i].sfx )
continue;
@@ -1506,14 +1490,13 @@ update global soundtime
static int S_GetSoundtime( void )
{
static int buffers, oldsamplepos;
int samplepos, fullsamples;
fullsamples = snd.samples / 2;
int fullsamples = snd.samples / 2;
// it is possible to miscount buffers
// if it has wrapped twice between
// calls to S_Update. Oh well.
samplepos = snd.samplepos;
int samplepos = snd.samplepos;
if( samplepos < oldsamplepos )
{
@@ -1779,13 +1762,12 @@ static void S_Music_f( void )
{
string intro, main, track;
const char *ext[] = { "mp3", "wav" };
int i;
Q_strncpy( track, Cmd_Argv( 1 ), sizeof( track ));
Q_snprintf( intro, sizeof( intro ), "%s_intro", Cmd_Argv( 1 ));
Q_snprintf( main, sizeof( main ), "%s_main", Cmd_Argv( 1 ));
for( i = 0; i < 2; i++ )
for( int i = 0; i < 2; i++ )
{
char intro_path[MAX_VA_STRING];
char main_path[MAX_VA_STRING];
@@ -1863,8 +1845,8 @@ static void S_SoundFade_f( void )
int c = Cmd_Argc();
int fade_percent;
int hold_time;
int fade_out_seconds;
int fade_in_seconds;
int fade_out_seconds = 0;
int fade_in_seconds = 0;
if( c != 3 && c != 5 )
{
@@ -1938,6 +1920,9 @@ static void S_VoiceRecordStop_f( void )
static const sound_api_t gSoundAPI = {
CL_GetEntitySpatialization,
S_GetSfxByHandle,
S_RawEntSamples,
SND_ForceInitMouth,
Voice_GetAudioInfo,
};
/*
@@ -1945,7 +1930,7 @@ static const sound_api_t gSoundAPI = {
S_InitSoundAPI
================
*/
static qboolean S_InitSoundAPI( void )
qboolean S_InitSoundAPI( void )
{
// make sure what sound functions is cleared
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
@@ -1964,6 +1949,9 @@ static qboolean S_InitSoundAPI( void )
memset( &clgame.soundFuncs, 0, sizeof( clgame.soundFuncs ));
}
if( clgame.soundFuncs.pfnS_Init )
clgame.soundFuncs.pfnS_Init( &snd );
return false;
}
@@ -2027,17 +2015,15 @@ qboolean S_Init( void )
S_InitSounds ();
VOX_Init ();
S_InitSoundAPI();
if( clgame.soundFuncs.pfnS_Init )
clgame.soundFuncs.pfnS_Init( &snd );
return true;
}
// =======================================================================
// Shutdown sound engine
// =======================================================================
/*
============
S_Shutdown
============
*/
void S_Shutdown( void )
{
if( !snd.initialized ) return;

View File

@@ -71,6 +71,39 @@ static portable_samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)], paintbuffer[(PAIN
} \
} \
#define S_MakeMixMonoLerp( x ) \
static void S_MixMonoLerp ## x( portable_samplepair_t *pbuf, const int *volume, const void *buf, double offset_frac, double rate_scale, int num_samples ) \
{ \
const int##x##_t *data = buf; \
uint sample_idx = 0; \
for( int i = 0; i < num_samples; i++ ) \
{ \
int s = (int)( data[sample_idx] * ( 1.0 - offset_frac ) + data[sample_idx + 1] * offset_frac ); \
pbuf[i].left += ( s * volume[0] ) >> ( x - 8 ); \
pbuf[i].right += ( s * volume[1] ) >> ( x - 8 ); \
offset_frac += rate_scale; \
sample_idx += (uint)offset_frac; \
offset_frac -= (uint)offset_frac; \
} \
} \
#define S_MakeMixStereoLerp( x ) \
static void S_MixStereoLerp ## x( portable_samplepair_t *pbuf, const int *volume, const void *buf, double offset_frac, double rate_scale, int num_samples ) \
{ \
const int##x##_t *data = buf; \
uint sample_idx = 0; \
for( int i = 0; i < num_samples; i++ ) \
{ \
int sl = (int)( data[sample_idx + 0] * ( 1.0 - offset_frac ) + data[sample_idx + 2] * offset_frac ); \
int sr = (int)( data[sample_idx + 1] * ( 1.0 - offset_frac ) + data[sample_idx + 3] * offset_frac ); \
pbuf[i].left += ( sl * volume[0] ) >> ( x - 8 ); \
pbuf[i].right += ( sr * volume[1] ) >> ( x - 8 ); \
offset_frac += rate_scale; \
sample_idx += (uint)offset_frac << 1; \
offset_frac -= (uint)offset_frac; \
} \
} \
S_MakeMixMono( 8 )
S_MakeMixMono( 16 )
S_MakeMixStereo( 8 )
@@ -79,10 +112,14 @@ S_MakeMixMonoPitch( 8 )
S_MakeMixMonoPitch( 16 )
S_MakeMixStereoPitch( 8 )
S_MakeMixStereoPitch( 16 )
S_MakeMixMonoLerp( 8 )
S_MakeMixMonoLerp( 16 )
S_MakeMixStereoLerp( 8 )
S_MakeMixStereoLerp( 16 )
static void S_MixAudio( portable_samplepair_t *pbuf, const int *pvol, const void *buf, int channels, int width, double offset_frac, double rate_scale, int num_samples )
static void S_MixAudio( portable_samplepair_t *pbuf, const int *pvol, const void *buf, int channels, int width, double offset_frac, double rate_scale, int num_samples, qboolean lerp )
{
if( Q_equal( rate_scale, 1.0 ))
if( Q_equal( rate_scale, 1.0 ) && Q_equal( offset_frac, 0.0 ))
{
if( channels == 1 )
{
@@ -99,6 +136,23 @@ static void S_MixAudio( portable_samplepair_t *pbuf, const int *pvol, const void
S_MixStereo16( pbuf, pvol, buf, num_samples );
}
}
else if( lerp )
{
if( channels == 1 )
{
if( width == 1 )
S_MixMonoLerp8( pbuf, pvol, buf, offset_frac, rate_scale, num_samples );
else
S_MixMonoLerp16( pbuf, pvol, buf, offset_frac, rate_scale, num_samples );
}
else
{
if( width == 1 )
S_MixStereoLerp8( pbuf, pvol, buf, offset_frac, rate_scale, num_samples );
else
S_MixStereoLerp16( pbuf, pvol, buf, offset_frac, rate_scale, num_samples );
}
}
else
{
if( channels == 1 )
@@ -163,13 +217,16 @@ static int S_MixChannelToBuffer( portable_samplepair_t *pbuf, channel_t *chan, i
if( num_samples == 0 )
return 0;
// linear interpolation needs one sample of lookahead beyond the last read position
const int lookahead = s_lerping.value ? 1 : 0;
while( num_samples > 0 )
{
// calculate the last sample position
double end_sample = chan->sample + rate * num_samples * timecompress_rate;
// and get total amount of samples we want
int request_num_samples = (int)(ceil( end_sample ) - floor( chan->sample ));
// and get total amount of samples we want, including lookahead for interpolation
int request_num_samples = (int)(ceil( end_sample ) - floor( chan->sample )) + lookahead;
// get sample pointer and also amount of samples available
const void *audio = NULL;
@@ -181,13 +238,30 @@ static int S_MixChannelToBuffer( portable_samplepair_t *pbuf, channel_t *chan, i
double sample_frac = chan->sample - floor( chan->sample );
// can interpolate only when at least two source samples are available
qboolean lerp = lookahead && available >= 2;
// this is how much data we output
int out_count = num_samples;
if( request_num_samples > available ) // but we can't write more than we have
out_count = (int)ceil(( available - sample_frac ) / ( rate ));
if( request_num_samples > available )
{
// lerp needs sample_idx + 1 to stay within available, so cap by available - 1
if( lerp )
out_count = (int)floor(( available - 1 - sample_frac ) / rate );
else
out_count = (int)ceil(( available - sample_frac ) / rate );
}
// near a buffer boundary (e.g. just before a loop wrap) lerp may yield zero;
// fall back to nearest for one sample to keep chan->sample advancing
if( out_count <= 0 )
{
lerp = false;
out_count = 1;
}
const wavdata_t *wav = chan->sfx->cache;
S_MixAudio( pbuf + offset, pvol, audio, wav->channels, wav->width, sample_frac, rate, out_count );
S_MixAudio( pbuf + offset, pvol, audio, wav->channels, wav->width, sample_frac, rate, out_count, lerp );
chan->sample += out_count * rate * timecompress_rate;
offset += out_count;
@@ -231,11 +305,12 @@ static int VOX_MixChannelToBuffer( portable_samplepair_t *pbuf, channel_t *chan,
return offset;
}
static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
static int S_MixNormalChannelsToRoombuffer( portable_samplepair_t *dst, int end )
{
const qboolean local = Host_IsLocalGame();
const qboolean sp = Host_IsSinglePlayerGame();
const qboolean ingame = CL_IsInGame();
const int num_samples = ( end - snd.paintedtime ) / ( SOUND_DMA_SPEED / rate );
const int out_rate = snd.format.speed;
const int num_samples = end - snd.paintedtime;
// FWGS feature: make everybody sound like chipmunks when we're going fast
const float pitch_mult = ( sys_timescale.value + 1 ) / 2;
@@ -261,7 +336,7 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
{
// play, playvol
}
else if(( cls.key_dest == key_menu || cl.paused ) && !FBitSet( ch->flags, FL_CHAN_LOCAL_SOUND ) && local )
else if(( cls.key_dest == key_menu || cl.paused ) && !FBitSet( ch->flags, FL_CHAN_LOCAL_SOUND ) && sp )
{
// play only local sounds, keep pause for other
continue;
@@ -284,7 +359,7 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
// if the sound is unaudible, skip it
// if it's also not looping, free it
if( ch->leftvol < 8 && ch->rightvol < 8 )
{
{
if( !FBitSet( sc->flags, SOUND_LOOPED ) || !FBitSet( ch->flags, FL_CHAN_USE_LOOP ))
{
if( ch->inauduble_free_time == 0.0f )
@@ -298,19 +373,18 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
ch->inauduble_free_time = 0.0f;
if( rate != sc->rate )
continue;
if( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM )
{
cl_entity_t *ent = CL_GetEntityByIndex( ch->entnum );
if( ent != NULL )
{
int mouth_count = (int)( num_samples * (double)sc->rate / out_rate );
if( sc->width == 1 )
SND_MoveMouth8( &ent->mouth, ch->sample, sc, num_samples, FBitSet( ch->flags, FL_CHAN_USE_LOOP ));
SND_MoveMouth8( &ent->mouth, ch->sample, sc, mouth_count, FBitSet( ch->flags, FL_CHAN_USE_LOOP ));
else
SND_MoveMouth16( &ent->mouth, ch->sample, sc, num_samples, FBitSet( ch->flags, FL_CHAN_USE_LOOP ));
SND_MoveMouth16( &ent->mouth, ch->sample, sc, mouth_count, FBitSet( ch->flags, FL_CHAN_USE_LOOP ));
}
}
@@ -320,14 +394,14 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
if( ch->words )
{
VOX_MixChannelToBuffer( dst, ch, num_samples, rate, pitch );
VOX_MixChannelToBuffer( dst, ch, num_samples, out_rate, pitch );
if( FBitSet( ch->flags, FL_CHAN_SENTENCE_FINISHED ))
S_FreeChannel( ch );
}
else
{
S_MixChannelToBuffer( dst, ch, num_samples, rate, pitch, 0, 0 );
S_MixChannelToBuffer( dst, ch, num_samples, out_rate, pitch, 0, 0 );
if( FBitSet( ch->flags, FL_CHAN_FINISHED ))
S_FreeChannel( ch );
@@ -337,63 +411,6 @@ static int S_MixNormalChannels( portable_samplepair_t *dst, int end, int rate )
return num_mixed_channels;
}
static int S_AverageSample( int a, int b )
{
return ( a >> 1 ) + ( b >> 1 ) + ((( a & 1 ) + ( b & 1 )) >> 1 );
}
static void S_UpsampleBuffer( portable_samplepair_t *dst, size_t num_samples )
{
if( s_lerping.value )
{
// copy even positions and average odd
for( size_t i = num_samples - 1; i > 0; i-- )
{
dst[i * 2] = dst[i];
dst[i * 2 + 1].left = S_AverageSample( dst[i].left, dst[i - 1].left );
dst[i * 2 + 1].right = S_AverageSample( dst[i].right, dst[i - 1].right );
}
}
else
{
// copy into even and odd positions
for( size_t i = num_samples - 1; i > 0; i-- )
{
dst[i * 2] = dst[i];
dst[i * 2 + 1] = dst[i];
}
}
dst[1] = dst[0];
}
static int S_MixNormalChannelsToRoombuffer( int end, int count )
{
// for room buffer we only support CD rates like 11k, 22k, and 44k
// TODO: 48k output would require support from platform-specific backends first
// until there is no real usecase, let's keep it simple
int num_mixed_channels = S_MixNormalChannels( roombuffer, end, SOUND_11k );
if( snd.format.speed >= SOUND_22k )
{
if( num_mixed_channels > 0 )
S_UpsampleBuffer( roombuffer, count / ( SOUND_22k / SOUND_11k ));
num_mixed_channels += S_MixNormalChannels( roombuffer, end, SOUND_22k );
}
if( snd.format.speed >= SOUND_44k )
{
if( num_mixed_channels > 0 )
S_UpsampleBuffer( roombuffer, count / ( SOUND_44k / SOUND_22k ));
num_mixed_channels += S_MixNormalChannels( roombuffer, end, SOUND_44k );
}
return num_mixed_channels;
}
static int S_MixRawChannels( int end )
{
int num_room_channels = 0;
@@ -537,7 +554,7 @@ void S_PaintChannels( int endtime )
S_ClearBuffers( num_samples );
int room_channels = S_MixNormalChannelsToRoombuffer( end, num_samples );
int room_channels = S_MixNormalChannelsToRoombuffer( roombuffer, end );
room_channels += S_MixRawChannels( end );

View File

@@ -36,20 +36,17 @@ static void SND_CommitMouthValue( mouth_t *mouth, int savg, int scount )
void SND_MoveMouth8( mouth_t *mouth, int pos, const wavdata_t *sc, int count, qboolean use_loop )
{
const int8_t *pdata = NULL;
int scount;
int savg, data;
uint i;
count = S_RetrieveAudioSamples( sc, (const void **)&pdata, pos, count, use_loop );
if( pdata == NULL ) return;
i = 0;
scount = mouth->sndcount;
savg = 0;
uint i = 0;
int scount = mouth->sndcount;
int savg = 0;
while( i < count && scount < CAVGSAMPLES )
{
data = pdata[i];
int data = pdata[i];
savg += abs( data );
i += 80 + ((byte)data & 0x1F);
@@ -62,20 +59,17 @@ void SND_MoveMouth8( mouth_t *mouth, int pos, const wavdata_t *sc, int count, qb
void SND_MoveMouth16( mouth_t *mouth, int pos, const wavdata_t *sc, int count, qboolean use_loop )
{
const int16_t *pdata = NULL;
int savg, data;
int scount;
uint i;
count = S_RetrieveAudioSamples( sc, (const void **)&pdata, pos, count, use_loop );
if( pdata == NULL ) return;
i = 0;
scount = mouth->sndcount;
savg = 0;
uint i = 0;
int scount = mouth->sndcount;
int savg = 0;
while( i < count && scount < CAVGSAMPLES )
{
data = pdata[i];
int data = pdata[i];
data = (bound( -32767, data, 0x7ffe ) >> 8);
savg += abs( data );
@@ -88,9 +82,7 @@ void SND_MoveMouth16( mouth_t *mouth, int pos, const wavdata_t *sc, int count, q
void SND_ForceInitMouth( int entnum )
{
cl_entity_t *clientEntity;
clientEntity = CL_GetEntityByIndex( entnum );
cl_entity_t *clientEntity = CL_GetEntityByIndex( entnum );
if( clientEntity )
{
@@ -102,9 +94,7 @@ void SND_ForceInitMouth( int entnum )
void SND_ForceCloseMouth( int entnum )
{
cl_entity_t *clientEntity;
clientEntity = CL_GetEntityByIndex( entnum );
cl_entity_t *clientEntity = CL_GetEntityByIndex( entnum );
if( clientEntity )
clientEntity->mouth.mouthopen = 0;
@@ -112,17 +102,13 @@ void SND_ForceCloseMouth( int entnum )
void SND_MoveMouthRaw( mouth_t *mouth, const portable_samplepair_t *buf, int count )
{
int savg, data;
int scount = 0;
uint i;
i = 0;
scount = mouth->sndcount;
savg = 0;
uint i = 0;
int scount = mouth->sndcount;
int savg = 0;
while ( i < count && scount < CAVGSAMPLES )
{
data = buf[i].left; // mono sound anyway
int data = buf[i].left; // mono sound anyway
data = ( bound( -32767, data, 0x7ffe ) >> 8 );
savg += abs( data );

View File

@@ -66,14 +66,14 @@ S_GetMusicVolume
*/
float S_GetMusicVolume( void )
{
float scale = 1.0f;
if( host.status == HOST_NOFOCUS && snd_mute_losefocus.value != 0.0f )
{
// we return zero volume to keep sounds running
return 0.0f;
}
float scale = 1.0f;
if( cls.key_dest != key_menu && musicfade.percent != 0 )
{
scale = bound( 0.0f, musicfade.percent / 100.0f, 1.0f );
@@ -191,11 +191,7 @@ S_StreamBackgroundTrack
*/
void S_StreamBackgroundTrack( void )
{
int bufferSamples;
int fileSamples;
byte raw[MAX_RAW_SAMPLES];
int r, fileBytes;
rawchan_t *ch = NULL;
if( !snd.initialized || !s_bgTrack.stream || snd.streaming )
return;
@@ -213,7 +209,7 @@ void S_StreamBackgroundTrack( void )
else if( cls.key_dest == key_console )
return;
ch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, true );
rawchan_t *ch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, true );
Assert( ch != NULL );
@@ -225,14 +221,14 @@ void S_StreamBackgroundTrack( void )
{
const stream_t *info = s_bgTrack.stream;
bufferSamples = ch->max_samples - (ch->s_rawend - snd.soundtime);
int bufferSamples = ch->max_samples - (ch->s_rawend - snd.soundtime);
// decide how much data needs to be read from the file
fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED );
int fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED );
if( fileSamples <= 1 ) return; // no more samples need
// our max buffer size
fileBytes = fileSamples * ( info->width * info->channels );
int fileBytes = fileSamples * ( info->width * info->channels );
if( fileBytes > sizeof( raw ))
{
@@ -241,7 +237,7 @@ void S_StreamBackgroundTrack( void )
}
// read
r = FS_ReadStream( s_bgTrack.stream, fileBytes, raw );
int r = FS_ReadStream( s_bgTrack.stream, fileBytes, raw );
if( r < fileBytes )
{
@@ -253,7 +249,7 @@ void S_StreamBackgroundTrack( void )
{
// add to raw buffer
int music_vol = (int)(255.0f * S_GetMusicVolume());
S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol );
S_RawEntSamples( S_RAW_SOUND_BACKGROUNDTRACK, fileSamples, info->rate, info->width, info->channels, raw, music_vol, ATTN_NONE );
}
else
{

View File

@@ -52,7 +52,7 @@ static qboolean S_ShouldTrimSample16( const int16_t *buf, int channels )
static int S_TrimStart( const wavdata_t *wav, int start )
{
size_t channels = wav->channels, width = wav->width, i;
size_t channels = wav->channels, width = wav->width;
if( wav->type != WF_PCMDATA )
return start;
@@ -61,7 +61,7 @@ static int S_TrimStart( const wavdata_t *wav, int start )
{
const int8_t *data = (const int8_t *)&wav->buffer[channels * width * start];
for( i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ )
for( size_t i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ )
{
if( !S_ShouldTrimSample8( data, wav->channels ))
break;
@@ -74,7 +74,7 @@ static int S_TrimStart( const wavdata_t *wav, int start )
{
const int16_t *data = (const int16_t *)&wav->buffer[channels * width * start];
for( i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ )
for( size_t i = 0; i < TRIM_SCAN_MAX && start < wav->samples; i++ )
{
if( !S_ShouldTrimSample16( data, wav->channels ))
break;
@@ -89,7 +89,7 @@ static int S_TrimStart( const wavdata_t *wav, int start )
static int S_TrimEnd( const wavdata_t *wav, int end )
{
size_t channels = wav->channels, width = wav->width, i;
size_t channels = wav->channels, width = wav->width;
if( wav->type != WF_PCMDATA )
return end;
@@ -98,7 +98,7 @@ static int S_TrimEnd( const wavdata_t *wav, int end )
{
const int8_t *data = (const int8_t *)&wav->buffer[channels * width * ( end - 1 )];
for( i = 0; i < TRIM_SCAN_MAX && end > 0; i++ )
for( size_t i = 0; i < TRIM_SCAN_MAX && end > 0; i++ )
{
if( !S_ShouldTrimSample8( data, wav->channels ))
break;
@@ -111,7 +111,7 @@ static int S_TrimEnd( const wavdata_t *wav, int end )
{
const int16_t *data = (const int16_t *)&wav->buffer[channels * width * ( end - 1 )];
for( i = 0; i < TRIM_SCAN_MAX && end > 0; i++ )
for( size_t i = 0; i < TRIM_SCAN_MAX && end > 0; i++ )
{
if( !S_ShouldTrimSample16( data, wav->channels ))
break;
@@ -222,9 +222,6 @@ float VOX_ModifyPitch( channel_t *ch, float pitch )
static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize )
{
const char *p;
int len;
// HACKHACK: some modders send strings like "/fvox/_period four"
// which should get parsed as "_period four" said by fvox
// it might be incorrect but ignore first slash here for now
@@ -232,7 +229,7 @@ static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize )
psz++;
// search / backwards
p = Q_strrchr( psz, '/' );
const char *p = Q_strrchr( psz, '/' );
if( !p )
{
@@ -240,7 +237,7 @@ static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize )
return psz;
}
len = p - psz + 1;
int len = p - psz + 1;
if( len > nsize )
{
@@ -256,8 +253,7 @@ static const char *VOX_GetDirectory( char *szpath, const char *psz, int nsize )
static const char *VOX_LookupString( const char *pszin )
{
int i = -1, len;
const char *c;
int i = -1;
// check if we are an immediate sentence
if( *pszin == '#' )
@@ -289,9 +285,9 @@ static const char *VOX_LookupString( const char *pszin )
if( i == cszrawsentences )
return NULL;
len = Q_strlen( rgpszrawsentence[i] );
int len = Q_strlen( rgpszrawsentence[i] );
c = &rgpszrawsentence[i][len + 1];
const char *c = &rgpszrawsentence[i][len + 1];
for( ; *c == ' ' || *c == '\t'; c++ );
return c;
@@ -362,12 +358,11 @@ static void VOX_MakeDefaultWordParams( voxword_t *voxword )
static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, voxword_t *default_voxword )
{
int len, i;
char sznum[8], *pszsave = psz;
*pvoxword = *default_voxword;
len = Q_strlen( psz );
int len = Q_strlen( psz );
if( len == 0 )
return false;
@@ -388,6 +383,7 @@ static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, voxword_t *
for( ;; )
{
char command;
int i;
// find command
for( ; *psz &&
@@ -403,11 +399,11 @@ static qboolean VOX_ParseWordParams( char *psz, voxword_t *pvoxword, voxword_t *
command = *psz++;
if( !isdigit( *psz ))
if( !isdigit((byte)*psz ))
break;
memset( sznum, 0, sizeof( sznum ));
for( i = 0; i < sizeof( sznum ) - 1 && isdigit( *psz ); i++, psz++ )
for( i = 0; i < sizeof( sznum ) - 1 && isdigit((byte)*psz ); i++, psz++ )
sznum[i] = *psz;
i = Q_atoi( sznum );
@@ -446,7 +442,7 @@ void VOX_LoadSound( channel_t *ch, const char *pszin )
char buffer[512] = { 0 }, szpath[32] = { 0 };
char *rgpparseword[CVOXWORDMAX] = { 0 };
const char *psz;
int i, j;
int j;
int num_words;
voxword_t default_voxword;
voxword_t words_buf[CVOXWORDMAX + 1]; // local scratch: parsed words + null terminator
@@ -490,7 +486,8 @@ void VOX_LoadSound( channel_t *ch, const char *pszin )
VOX_MakeDefaultWordParams( &default_voxword );
memset( words_buf, 0, sizeof( words_buf ));
for( i = 0, j = 0; i < num_words; i++ )
j = 0;
for( int i = 0; i < num_words; i++ )
{
char pathbuffer[MAX_SYSPATH];
@@ -589,9 +586,7 @@ void VOX_Init( void )
void VOX_Shutdown( void )
{
int i;
for( i = 0; i < cszrawsentences; i++ )
for( int i = 0; i < cszrawsentences; i++ )
Mem_Free( rgpszrawsentence[i] );
cszrawsentences = 0;
@@ -609,9 +604,8 @@ static void Test_VOX_GetDirectory( void )
"barney/meow", "meow", "barney/",
"/fvox/_period", "_period", "fvox/",
};
int i;
for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 3 )
for( int i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 3 )
{
string szpath;
const char *p = VOX_GetDirectory( szpath, data[i+0], sizeof( szpath ));
@@ -623,7 +617,6 @@ static void Test_VOX_GetDirectory( void )
static void Test_VOX_LookupString( void )
{
int i;
const char *p, *data[] =
{
"0", "123",
@@ -645,7 +638,7 @@ static void Test_VOX_LookupString( void )
rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithSpaces\0 SPAAACE";
rgpszrawsentence[cszrawsentences++] = (char*)"SentenceWithTabsAndSpaces\0\t \t\t MEOW";
for( i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 2 )
for( int i = 0; i < sizeof( data ) / sizeof( data[0] ); i += 2 )
{
p = VOX_LookupString( data[i] );

View File

@@ -381,18 +381,17 @@ Apply automatic gain control to voice samples
*/
static void Voice_ApplyGainAdjust( int16_t *samples, int count, float scale )
{
float gain, modifiedMax;
int average, blockOffset = 0;
int blockOffset = 0;
for( ;; )
{
int i, localMax = 0, localSum = 0;
int localMax = 0, localSum = 0;
int blockSize = Q_min( count - blockOffset, voice.autogain.block_size );
if( blockSize < 1 )
break;
for( i = 0; i < blockSize; ++i )
for( int i = 0; i < blockSize; ++i )
{
int sample = samples[blockOffset + i];
int absSample = abs( sample );
@@ -401,14 +400,14 @@ static void Voice_ApplyGainAdjust( int16_t *samples, int count, float scale )
localMax = absSample;
localSum += absSample;
gain = voice.autogain.current_gain + i * voice.autogain.gain_multiplier;
float gain = voice.autogain.current_gain + i * voice.autogain.gain_multiplier;
samples[blockOffset + i] = bound( SHRT_MIN, (int)( sample * gain ), SHRT_MAX );
}
if( blockOffset % voice.autogain.block_size == 0 )
{
average = localSum / blockSize;
modifiedMax = average + ( localMax - average ) * voice_avggain.value;
int average = localSum / blockSize;
float modifiedMax = average + ( localMax - average ) * voice_avggain.value;
voice.autogain.current_gain = voice.autogain.next_gain * scale;
voice.autogain.next_gain = Q_min((float)SHRT_MAX / ( modifiedMax > 1 ? modifiedMax : 1 ), voice_maxgain.value ) * scale;
@@ -435,13 +434,11 @@ static uint Voice_GetOpusCompressedData( byte *out, uint maxsize, uint *frames )
if( voice.input_file )
{
uint numbytes;
double updateInterval, curtime = Platform_DoubleTime();
updateInterval = curtime - voice.start_time;
double curtime = Platform_DoubleTime();
double updateInterval = curtime - voice.start_time;
voice.start_time = curtime;
numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS;
uint numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS;
numbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos );
numbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos );
@@ -510,13 +507,11 @@ static uint Voice_GetGSCompressedData( byte *out, uint maxsize, uint *frames )
if( voice.input_file )
{
uint numbytes;
double updateInterval, curtime = Platform_DoubleTime();
updateInterval = curtime - voice.start_time;
double curtime = Platform_DoubleTime();
double updateInterval = curtime - voice.start_time;
voice.start_time = curtime;
numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS;
uint numbytes = updateInterval * voice.samplerate * voice.width * VOICE_PCM_CHANNELS;
numbytes = Q_min( numbytes, voice.input_file->size - voice.input_file_pos );
numbytes = Q_min( numbytes, sizeof( voice.input_buffer ) - voice.input_buffer_pos );
@@ -597,24 +592,15 @@ Process GoldSrc voice data and return number of samples
*/
static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
{
uint32_t crc_in_packet;
uint32_t crc;
size_t offset;
size_t samples;
uint16_t sample_rate;
uint8_t vpc_type;
uint16_t data_len;
OpusDecoder *decoder;
size_t opus_offset;
int decoded;
size_t silence_samples;
uint16_t frame_size;
if( !data || ent <= 0 || ent > cl.maxclients )
return 0;
crc_in_packet = LittleLong( *(uint32_t *)( data + size - sizeof( uint32_t )));
crc = CRC32_INIT_VALUE;
uint32_t crc_in_packet = LittleLong( *(uint32_t *)( data + size - sizeof( uint32_t )));
uint32_t crc = CRC32_INIT_VALUE;
CRC32_ProcessBuffer( &crc, data, size - sizeof( uint32_t ));
crc = CRC32_Final( crc );
@@ -624,7 +610,7 @@ static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
return 0;
}
offset = sizeof( uint64_t );
size_t offset = sizeof( uint64_t );
samples = 0;
if( offset >= size - sizeof( uint32_t ) || data[offset] != GS_VPC_SETSAMPLERATE )
@@ -637,7 +623,6 @@ static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
if( offset + sizeof( uint32_t ) > size - sizeof( uint32_t ))
return 0;
sample_rate = LittleShort( *(uint16_t *)( data + offset ));
offset += sizeof( uint16_t );
vpc_type = data[offset++];
@@ -646,7 +631,9 @@ static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
if( vpc_type == GS_VPC_VDATA_OPUS_PLC )
{
decoder = voice.gs_decoders[ent - 1];
OpusDecoder *decoder = voice.gs_decoders[ent - 1];
size_t opus_offset;
if( !decoder )
{
Con_Printf( S_WARN "No decoder available for entity %d\n", ent );
@@ -655,7 +642,7 @@ static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
opus_offset = 0;
while( opus_offset + sizeof( uint32_t ) <= data_len )
{
frame_size = LittleShort( *(uint16_t *)( data + offset + opus_offset ));
uint16_t frame_size = LittleShort( *(uint16_t *)( data + offset + opus_offset ));
opus_offset += sizeof( uint32_t );
// if frame size is 0, it means silence
if( frame_size == 0 )
@@ -679,7 +666,7 @@ static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
Con_Printf( S_WARN "Opus frame size exceeds data length\n" );
return 0;
}
decoded = opus_decode( decoder, data + offset + opus_offset, frame_size,
int decoded = opus_decode( decoder, data + offset + opus_offset, frame_size,
( (int16_t *)voice.decompress_buffer ) + samples, voice.frame_size, 0 );
if( decoded < 0 )
{
@@ -692,7 +679,7 @@ static int Voice_ProcessGSData( int ent, const uint8_t *data, uint32_t size )
}
else if( vpc_type == GS_VPC_VDATA_SILENCE )
{
silence_samples = data_len / 2;
size_t silence_samples = data_len / 2;
if( silence_samples > GS_MAX_DECOMPRESSED_SAMPLES )
{
Con_Printf( S_WARN "Silence data too large\n" );
@@ -908,8 +895,6 @@ stop recording and notify user dlls
*/
void Voice_Disconnect( void )
{
int i;
Voice_RecordStop();
if( voice.local.talking_ack )
@@ -918,7 +903,7 @@ void Voice_Disconnect( void )
voice.local.talking_ack = false;
}
for( i = 1; i <= MAX_CLIENTS; i++ )
for( int i = 1; i <= MAX_CLIENTS; i++ )
Voice_Status( i, false );
VoiceCapture_Shutdown();
@@ -939,8 +924,14 @@ Feed the decoded data to engine sound subsystem
*/
static void Voice_StartChannel( uint samples, byte *data, int entnum )
{
if( clgame.dllFuncs.pfnVoice_StartChannel != NULL )
{
if( clgame.dllFuncs.pfnVoice_StartChannel( samples, data, entnum ))
return;
}
SND_ForceInitMouth( entnum );
S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 ));
S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, VOICE_PCM_CHANNELS, data, bound( 0, 255 * voice_scale.value, 255 ), ATTN_NONE );
Voice_Status( entnum, true );
}
@@ -981,7 +972,6 @@ void Voice_AddIncomingData( int ent, const byte *data, uint size, uint frames )
const int playernum = ent - 1;
int samples = 0;
int ofs = 0;
voice_status_t *status = NULL;
if( !voice.initialized || !voice_enable.value )
return;
@@ -1035,7 +1025,7 @@ Encode our voice data and send it to server
void CL_AddVoiceToDatagram( void )
{
byte buffer[VOICE_MAX_DATA_SIZE];
uint size, frames;
uint size, frames = 0;
if( cls.state != ca_active || !voice.device_opened || !Voice_IsRecording())
return;
@@ -1125,8 +1115,6 @@ Run timeout for clients
*/
void Voice_Idle( double frametime )
{
int i;
if( FBitSet( voice_enable.flags, FCVAR_CHANGED ))
{
ClearBits( voice_enable.flags, FCVAR_CHANGED );
@@ -1194,3 +1182,15 @@ qboolean Voice_Init( const char *pszCodecName, int quality, qboolean preinit )
voice.initialized = true;
return true;
}
voice_audio_info_t Voice_GetAudioInfo( void )
{
voice_audio_info_t info =
{
.width = voice.width,
.samplerate = voice.samplerate,
.frame_size = voice.frame_size,
};
return info;
}

View File

@@ -59,7 +59,6 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size )
const char *ext = COM_FileExtension( filename );
string loadname;
qboolean anyformat = true;
const loadwavfmt_t *format;
Sound_Reset(); // clear old sounddata
Q_strncpy( loadname, filename, sizeof( loadname ));
@@ -68,7 +67,7 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size )
{
// we needs to compare file extension with list of supported formats
// and be sure what is real extension, not a filename with dot
for( format = sound.loadformats; format && format->ext; format++ )
for( const loadwavfmt_t *format = sound.loadformats; format && format->ext; format++ )
{
if( !Q_stricmp( format->ext, ext ))
{
@@ -84,18 +83,17 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size )
goto load_internal;
// now try all the formats in the selected list
for( format = sound.loadformats; format && format->ext; format++)
for( const loadwavfmt_t *format = sound.loadformats; format && format->ext; format++)
{
if( anyformat || !Q_stricmp( ext, format->ext ))
{
qboolean success = false;
fs_offset_t filesize = 0;
byte *f;
string path;
Q_snprintf( path, sizeof( path ), DEFAULT_SOUNDPATH "%s.%s", loadname, format->ext );
f = FS_LoadFile( path, &filesize, false );
byte *f = FS_LoadFile( path, &filesize, false );
if( f && filesize > 0 )
{
success = format->loadfunc( path, f, filesize );
@@ -119,7 +117,7 @@ wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size )
}
load_internal:
for( format = sound.loadformats; format && format->ext; format++ )
for( const loadwavfmt_t *format = sound.loadformats; format && format->ext; format++ )
{
if( anyformat || !Q_stricmp( ext, format->ext ))
{
@@ -162,7 +160,6 @@ stream_t *FS_OpenStream( const char *filename )
const char *ext = COM_FileExtension( filename );
string loadname;
qboolean anyformat = true;
const streamfmt_t *format;
stream_t *stream = NULL;
Sound_Reset(); // clear old streaminfo
@@ -172,7 +169,7 @@ stream_t *FS_OpenStream( const char *filename )
{
// we needs to compare file extension with list of supported formats
// and be sure what is real extension, not a filename with dot
for( format = sound.streamformat; format && format->ext; format++ )
for( const streamfmt_t *format = sound.streamformat; format && format->ext; format++ )
{
if( !Q_stricmp( format->ext, ext ))
{
@@ -184,7 +181,7 @@ stream_t *FS_OpenStream( const char *filename )
}
// now try all the formats in the selected list
for( format = sound.streamformat; format && format->ext; format++)
for( const streamfmt_t *format = sound.streamformat; format && format->ext; format++)
{
if( anyformat || !Q_stricmp( ext, format->ext ))
{

Some files were not shown because too many files have changed in this diff Show More