mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 11:35:05 +08:00
Compare commits
287 Commits
continuous
...
continuous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c9cd59c71 | ||
|
|
c79b29687c | ||
|
|
1eb922a0f1 | ||
|
|
f6e36994be | ||
|
|
e83ba6509e | ||
|
|
5cfb67940b | ||
|
|
e31c893852 | ||
|
|
85e5f032d8 | ||
|
|
92fd51ad99 | ||
|
|
5480055997 | ||
|
|
d634c6aad5 | ||
|
|
034efb3b31 | ||
|
|
eae45b2e81 | ||
|
|
567d279007 | ||
|
|
27f8385bb1 | ||
|
|
1cfabd5ec9 | ||
|
|
ab6672fb8a | ||
|
|
2d39a7fa24 | ||
|
|
7db12ade3a | ||
|
|
61a84f6953 | ||
|
|
75ffba64c5 | ||
|
|
c134840148 | ||
|
|
51d1bfbe08 | ||
|
|
79ff8332ac | ||
|
|
f155005bb1 | ||
|
|
a8de8cf5e6 | ||
|
|
74fcab9bfb | ||
|
|
efad71bcc9 | ||
|
|
c444ab882a | ||
|
|
8fad3bbad6 | ||
|
|
4cd6d25ee6 | ||
|
|
33e47ef5a9 | ||
|
|
d19e9606e9 | ||
|
|
fe1827f5e4 | ||
|
|
918323fcea | ||
|
|
e75256ec9d | ||
|
|
47582182e6 | ||
|
|
4a2c218cfc | ||
|
|
376817478d | ||
|
|
b96dc61e31 | ||
|
|
b997d5657b | ||
|
|
e9f5bca5c4 | ||
|
|
b36f107a2e | ||
|
|
a305ffb33f | ||
|
|
1cc1b60b10 | ||
|
|
648db4a2b3 | ||
|
|
2c0c20dfe6 | ||
|
|
17e167432a | ||
|
|
97b8ca6b38 | ||
|
|
6dabc6f2c5 | ||
|
|
e808038fee | ||
|
|
a60d0d142d | ||
|
|
bf5d88d9a4 | ||
|
|
adca77672a | ||
|
|
ef5295c5ab | ||
|
|
37da317133 | ||
|
|
c93cb49a9e | ||
|
|
4f342fb19a | ||
|
|
8ee3544507 | ||
|
|
d44e6d5db7 | ||
|
|
21cc9feca7 | ||
|
|
e87b14468d | ||
|
|
e01921dd10 | ||
|
|
e5c4e49782 | ||
|
|
d61983a5d2 | ||
|
|
c3f4eb026b | ||
|
|
fe9b8bcaca | ||
|
|
33cd20b21b | ||
|
|
78a3b958dd | ||
|
|
0e56fa10f9 | ||
|
|
b6a67b56b0 | ||
|
|
eb9de1cac8 | ||
|
|
24abd2deb2 | ||
|
|
996dcc637d | ||
|
|
8b89bdca6f | ||
|
|
3a6e3ba3ca | ||
|
|
42ab099a07 | ||
|
|
09f15042fe | ||
|
|
344712b990 | ||
|
|
2904c3e88b | ||
|
|
25ca2ecb12 | ||
|
|
f6b28e32e2 | ||
|
|
e118376735 | ||
|
|
5ef6123f03 | ||
|
|
e4600b5fe0 | ||
|
|
074bd8b2b3 | ||
|
|
fca347dd00 | ||
|
|
2a284090ef | ||
|
|
ecd74a0a7f | ||
|
|
853a67b0c5 | ||
|
|
80ec1c83f1 | ||
|
|
41681229f9 | ||
|
|
7fce99481f | ||
|
|
142dc7b87a | ||
|
|
3e24d5a347 | ||
|
|
64b1b54f5b | ||
|
|
1fed340330 | ||
|
|
64be4251c8 | ||
|
|
c5a8160c32 | ||
|
|
3c1cf81c2d | ||
|
|
c6c1163aa0 | ||
|
|
e8845fc404 | ||
|
|
d49178d5b6 | ||
|
|
e2cf580818 | ||
|
|
d0a98447c4 | ||
|
|
29a9fcf76c | ||
|
|
37d9791f00 | ||
|
|
c5fbc98d16 | ||
|
|
4c2302ec6c | ||
|
|
de585f80fb | ||
|
|
af58ef4a2a | ||
|
|
dbe3f42485 | ||
|
|
0053edbe7b | ||
|
|
61f3369957 | ||
|
|
334bebb1fb | ||
|
|
463ed05340 | ||
|
|
92bad81d9a | ||
|
|
4c667d9d8c | ||
|
|
cb04e9cbd5 | ||
|
|
c2c781f039 | ||
|
|
dbe1a2742e | ||
|
|
fe2019ee06 | ||
|
|
e5c091c303 | ||
|
|
9d050e7ed3 | ||
|
|
be35b3503e | ||
|
|
5fad4ae6ca | ||
|
|
2290ae0239 | ||
|
|
9082a47348 | ||
|
|
5d7ed9d8a7 | ||
|
|
8120669e5b | ||
|
|
fb6c4a224c | ||
|
|
166763a887 | ||
|
|
e56cf4e1da | ||
|
|
fc4b982ecc | ||
|
|
d305292340 | ||
|
|
9ee6be87d7 | ||
|
|
4e4a54ab68 | ||
|
|
516554b704 | ||
|
|
7be955c493 | ||
|
|
c6fb702fb1 | ||
|
|
cae1d32c66 | ||
|
|
5e70995989 | ||
|
|
50f7f3bb87 | ||
|
|
7c3da8c381 | ||
|
|
e38d3511b5 | ||
|
|
d4fcebb377 | ||
|
|
996f0e906b | ||
|
|
0f490a2f16 | ||
|
|
cef6cb58bb | ||
|
|
47aad1b3a3 | ||
|
|
ff8efebba2 | ||
|
|
be6571a6a2 | ||
|
|
1d67585df4 | ||
|
|
10d441f825 | ||
|
|
2dc132f817 | ||
|
|
e9bdbf2f33 | ||
|
|
b630a84447 | ||
|
|
dccd076b92 | ||
|
|
cb7ea5729a | ||
|
|
9bad17505e | ||
|
|
bf990d1040 | ||
|
|
63e112bf4c | ||
|
|
0aac2e4d32 | ||
|
|
33b3cdb0f5 | ||
|
|
9c4edaed21 | ||
|
|
d4205867dd | ||
|
|
3c07680263 | ||
|
|
e5d6f66f61 | ||
|
|
a106924c7f | ||
|
|
11ee883c84 | ||
|
|
881f0c5ca7 | ||
|
|
ac0c8a2210 | ||
|
|
dba090e0da | ||
|
|
262f32b002 | ||
|
|
843f6297ed | ||
|
|
d1510345f1 | ||
|
|
36307bb386 | ||
|
|
52953a7647 | ||
|
|
ebeff144f9 | ||
|
|
c8cc953d48 | ||
|
|
d6bf06e997 | ||
|
|
568b7abc5f | ||
|
|
1b7f50d3c6 | ||
|
|
c8e1c2673f | ||
|
|
c2df265de7 | ||
|
|
e9e885534a | ||
|
|
95cc6c4445 | ||
|
|
086cb6aab8 | ||
|
|
e6a44b70e0 | ||
|
|
fdb59e990e | ||
|
|
eb79bc6b40 | ||
|
|
e907e9fc95 | ||
|
|
fc356c88b3 | ||
|
|
bc672e5aca | ||
|
|
2635fd9739 | ||
|
|
653fe51fd4 | ||
|
|
980d9fcaab | ||
|
|
72ef5cffca | ||
|
|
a707f5933d | ||
|
|
b9b0ff5769 | ||
|
|
982a216df4 | ||
|
|
7d87d65a3f | ||
|
|
6e4e048ef8 | ||
|
|
5b6b372cfe | ||
|
|
2f31cf315c | ||
|
|
910cce56ca | ||
|
|
68dba8b4e0 | ||
|
|
dbfeacfecc | ||
|
|
0bdd4648bc | ||
|
|
ec8059173d | ||
|
|
f91e5a7647 | ||
|
|
7942d9b1bc | ||
|
|
7abf0c819f | ||
|
|
85eb82a370 | ||
|
|
90e25cc623 | ||
|
|
fca4152d5a | ||
|
|
2ccef63754 | ||
|
|
87615784b9 | ||
|
|
d3b01d313b | ||
|
|
a3897f402a | ||
|
|
21d84ee692 | ||
|
|
e30ce9c150 | ||
|
|
22391eba1b | ||
|
|
800725453a | ||
|
|
22ecec095e | ||
|
|
8545ed5ccb | ||
|
|
a4fdf19ad8 | ||
|
|
75e4a21d7a | ||
|
|
1099344a7d | ||
|
|
ab1eaed0a5 | ||
|
|
76a01d0855 | ||
|
|
b00679ee30 | ||
|
|
b3bb2fc16f | ||
|
|
86a2ea29e4 | ||
|
|
d4f8543556 | ||
|
|
e850728016 | ||
|
|
11877f4c96 | ||
|
|
2ba5030b62 | ||
|
|
06b14800e1 | ||
|
|
bb7c85fd22 | ||
|
|
37ce62fa6a | ||
|
|
e1b29e2da3 | ||
|
|
b60afd73e9 | ||
|
|
84447a5292 | ||
|
|
fe4ea4aabf | ||
|
|
878822290c | ||
|
|
04ca5d7cb0 | ||
|
|
d46ed40f33 | ||
|
|
82e031205c | ||
|
|
6f5f04a29d | ||
|
|
f9ed578bf1 | ||
|
|
86c84c7de7 | ||
|
|
489c1c9dd5 | ||
|
|
1df6a040ec | ||
|
|
82c2ca53fb | ||
|
|
3ddd1e2448 | ||
|
|
371d619685 | ||
|
|
480f29e96e | ||
|
|
95548053eb | ||
|
|
0daf2c2b4d | ||
|
|
e2d5bea327 | ||
|
|
306a5812a9 | ||
|
|
a3dba64513 | ||
|
|
491ba9bc7d | ||
|
|
1c462df8a2 | ||
|
|
ed975976f8 | ||
|
|
bbf8ab378d | ||
|
|
5383e36af5 | ||
|
|
897fb98b93 | ||
|
|
96e88adfcd | ||
|
|
4dde4047f1 | ||
|
|
905161529f | ||
|
|
044523434d | ||
|
|
da4c4bb76a | ||
|
|
95b3271d68 | ||
|
|
3361a5d760 | ||
|
|
b725d2ece0 | ||
|
|
66bee8b468 | ||
|
|
5e664dc3e9 | ||
|
|
bfdfeb3dc0 | ||
|
|
49248f5e14 | ||
|
|
d03ea4c10a | ||
|
|
780dbd6183 | ||
|
|
50da79f1e0 | ||
|
|
62201900cf | ||
|
|
ae233ac491 | ||
|
|
dd1cb04cb7 |
32
.cirrus.yml
32
.cirrus.yml
@@ -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
13
.gitignore
vendored
@@ -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
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -43,3 +43,6 @@
|
||||
[submodule "3rdparty/library_suffix"]
|
||||
path = 3rdparty/library_suffix
|
||||
url = https://github.com/FWGS/library-suffix.git
|
||||
[submodule "3rdparty/mbedtls/mbedtls"]
|
||||
path = 3rdparty/mbedtls/mbedtls
|
||||
url = https://github.com/FWGS/mbedtls-releases.git
|
||||
|
||||
2
3rdparty/MultiEmulator
vendored
2
3rdparty/MultiEmulator
vendored
Submodule 3rdparty/MultiEmulator updated: 6a56b7b112...e369abc23c
2
3rdparty/extras/wscript
vendored
2
3rdparty/extras/wscript
vendored
@@ -23,6 +23,6 @@ def build(bld):
|
||||
|
||||
bld(features='zip',
|
||||
name = 'extras.pk3',
|
||||
files = srcdir.ant_glob('**/*'),
|
||||
files = srcdir.ant_glob('**/*', excl=['scripts/**']),
|
||||
relative_to = srcdir,
|
||||
install_path = install_path)
|
||||
|
||||
2
3rdparty/extras/xash-extras
vendored
2
3rdparty/extras/xash-extras
vendored
Submodule 3rdparty/extras/xash-extras updated: 0c83a63d00...9f5b017985
2
3rdparty/gl-wes-v2
vendored
2
3rdparty/gl-wes-v2
vendored
Submodule 3rdparty/gl-wes-v2 updated: 5e5a7192e6...9c8ef67f97
2
3rdparty/gl4es/gl4es
vendored
2
3rdparty/gl4es/gl4es
vendored
Submodule 3rdparty/gl4es/gl4es updated: a744af14d4...1543ec5074
14
3rdparty/libbacktrace/wscript
vendored
14
3rdparty/libbacktrace/wscript
vendored
@@ -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/',
|
||||
|
||||
2
3rdparty/library_suffix
vendored
2
3rdparty/library_suffix
vendored
Submodule 3rdparty/library_suffix updated: 6e14ae31e8...a35ac9c12e
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: af2b5abdde...623c34db69
107
3rdparty/mbedtls/compat.c
vendored
Normal file
107
3rdparty/mbedtls/compat.c
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
compat.c - mbedTLS platform overrides for targets upstream doesn't cover
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
WinXP: legacy CryptGenRandom via advapi32
|
||||
Source: ttps://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
|
||||
|
||||
PSVita: sceKernelGetRandomNumber from psp2/kernel/rng.h (64-byte cap).
|
||||
Source: https://github.com/vitasdk/vita-headers/blob/master/include/psp2/kernel/rng.h
|
||||
|
||||
NSwitch: randomGet from libnx.
|
||||
Source: https://github.com/switchbrew/libnx/blob/master/nx/include/switch/kernel/random.h
|
||||
*/
|
||||
|
||||
#include "mbedtls/platform.h"
|
||||
#include "psa/crypto.h"
|
||||
|
||||
#if defined( MBEDTLS_PLATFORM_MS_TIME_ALT )
|
||||
|
||||
mbedtls_ms_time_t mbedtls_ms_time( void )
|
||||
{
|
||||
extern double Platform_DoubleTime( void );
|
||||
|
||||
return (mbedtls_ms_time_t)( Platform_DoubleTime() * 1000.0 );
|
||||
}
|
||||
|
||||
#endif /* MBEDTLS_PLATFORM_MS_TIME_ALT */
|
||||
|
||||
#if defined( MBEDTLS_PSA_DRIVER_GET_ENTROPY )
|
||||
#if defined( _WIN32 ) && !defined( _WIN64 )
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
HCRYPTPROV prov;
|
||||
if( !CryptAcquireContextW( &prov, NULL, NULL, PROV_RSA_FULL,
|
||||
CRYPT_VERIFYCONTEXT | CRYPT_SILENT ))
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
|
||||
if( !CryptGenRandom( prov, (DWORD)output_size, output ))
|
||||
{
|
||||
CryptReleaseContext( prov, 0 );
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
}
|
||||
|
||||
CryptReleaseContext( prov, 0 );
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
#elif defined( __vita__ )
|
||||
#include <psp2/kernel/rng.h>
|
||||
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
size_t total = 0;
|
||||
while( total < output_size )
|
||||
{
|
||||
size_t chunk = output_size - total;
|
||||
|
||||
if( chunk > 64 )
|
||||
chunk = 64;
|
||||
|
||||
if( sceKernelGetRandomNumber( output + total, chunk ) != 0 )
|
||||
return PSA_ERROR_INSUFFICIENT_ENTROPY;
|
||||
|
||||
total += chunk;
|
||||
}
|
||||
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#elif defined( __SWITCH__ )
|
||||
#include <switch.h>
|
||||
|
||||
int mbedtls_platform_get_entropy( psa_driver_get_entropy_flags_t flags, size_t *estimate_bits, unsigned char *output, size_t output_size )
|
||||
{
|
||||
if( flags != 0 )
|
||||
return PSA_ERROR_NOT_SUPPORTED;
|
||||
|
||||
randomGet( output, output_size );
|
||||
*estimate_bits = 8 * output_size;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
#error "MBEDTLS_PSA_DRIVER_GET_ENTROPY enabled but no platform impl in compat.c"
|
||||
#endif
|
||||
|
||||
#endif /* MBEDTLS_PSA_DRIVER_GET_ENTROPY */
|
||||
1
3rdparty/mbedtls/mbedtls
vendored
Submodule
1
3rdparty/mbedtls/mbedtls
vendored
Submodule
Submodule 3rdparty/mbedtls/mbedtls added at 701bb7cfde
67
3rdparty/mbedtls/wscript
vendored
Normal file
67
3rdparty/mbedtls/wscript
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
def options(opt):
|
||||
grp = opt.add_option_group('mbedTLS options')
|
||||
grp.add_option('--disable-mbedtls', action='store_false', dest='MBEDTLS', default=True,
|
||||
help='disable bundled mbedTLS and built-in HTTPS support [default: enabled]')
|
||||
|
||||
def configure(conf):
|
||||
conf.env.MBEDTLS = conf.options.MBEDTLS
|
||||
if not conf.env.MBEDTLS:
|
||||
return
|
||||
|
||||
if not conf.path.find_dir('mbedtls') or not conf.path.find_dir('mbedtls/library'):
|
||||
conf.fatal('Can\'t find mbedtls submodule. Run `git submodule update --init --recursive`.')
|
||||
|
||||
if not conf.path.find_dir('mbedtls/tf-psa-crypto/core'):
|
||||
conf.fatal('mbedTLS nested submodule tf-psa-crypto is missing. Run `git submodule update --init --recursive`.')
|
||||
|
||||
def build(bld):
|
||||
if not bld.env.MBEDTLS:
|
||||
return
|
||||
|
||||
sources = bld.path.ant_glob([
|
||||
'mbedtls/library/*.c',
|
||||
'mbedtls/tf-psa-crypto/core/*.c',
|
||||
'mbedtls/tf-psa-crypto/extras/*.c',
|
||||
'mbedtls/tf-psa-crypto/platform/*.c',
|
||||
'mbedtls/tf-psa-crypto/utilities/*.c',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/src/*.c',
|
||||
])
|
||||
|
||||
sources += ['compat.c']
|
||||
|
||||
defines = [
|
||||
'MBEDTLS_USER_CONFIG_FILE="xash_mbedtls_config.h"',
|
||||
'TF_PSA_CRYPTO_USER_CONFIG_FILE="xash_psa_config.h"',
|
||||
]
|
||||
|
||||
includes = [
|
||||
'.',
|
||||
'mbedtls/include',
|
||||
'mbedtls/library',
|
||||
'mbedtls/tf-psa-crypto/include',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/include',
|
||||
'mbedtls/tf-psa-crypto/core',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/src',
|
||||
'mbedtls/tf-psa-crypto/utilities',
|
||||
'mbedtls/tf-psa-crypto/extras',
|
||||
'mbedtls/tf-psa-crypto/platform',
|
||||
'mbedtls/tf-psa-crypto/dispatch',
|
||||
]
|
||||
|
||||
bld.stlib(
|
||||
source = sources,
|
||||
target = 'mbedtls',
|
||||
features = 'c',
|
||||
includes = includes,
|
||||
defines = defines,
|
||||
export_defines = ['XASH_MBEDTLS'],
|
||||
export_includes = [
|
||||
'.',
|
||||
'mbedtls/include',
|
||||
'mbedtls/tf-psa-crypto/include',
|
||||
'mbedtls/tf-psa-crypto/drivers/builtin/include',
|
||||
]
|
||||
)
|
||||
36
3rdparty/mbedtls/xash_mbedtls_config.h
vendored
Normal file
36
3rdparty/mbedtls/xash_mbedtls_config.h
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef XASH_MBEDTLS_CONFIG_H
|
||||
#define XASH_MBEDTLS_CONFIG_H
|
||||
|
||||
#undef MBEDTLS_NET_C
|
||||
#undef MBEDTLS_PKCS7_C
|
||||
#undef MBEDTLS_TIMING_C
|
||||
#undef MBEDTLS_SSL_SRV_C
|
||||
#undef MBEDTLS_SSL_PROTO_DTLS
|
||||
#undef MBEDTLS_SSL_DTLS_ANTI_REPLAY
|
||||
#undef MBEDTLS_SSL_DTLS_CONNECTION_ID
|
||||
#undef MBEDTLS_SSL_DTLS_HELLO_VERIFY
|
||||
#undef MBEDTLS_SSL_DTLS_SRTP
|
||||
#undef MBEDTLS_SSL_DTLS_CLIENT_PORT_REUSE
|
||||
#undef MBEDTLS_SSL_COOKIE_C
|
||||
#undef MBEDTLS_SSL_CACHE_C
|
||||
#undef MBEDTLS_SSL_TICKET_C
|
||||
#undef MBEDTLS_SSL_SESSION_TICKETS
|
||||
#undef MBEDTLS_SSL_RENEGOTIATION
|
||||
#undef MBEDTLS_SSL_CONTEXT_SERIALIZATION
|
||||
#undef MBEDTLS_SSL_KEYING_MATERIAL_EXPORT
|
||||
#undef MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
|
||||
#undef MBEDTLS_SSL_ALL_ALERT_MESSAGES
|
||||
#undef MBEDTLS_SSL_ALPN
|
||||
#undef MBEDTLS_SSL_ENCRYPT_THEN_MAC
|
||||
#undef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
|
||||
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED
|
||||
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_ENABLED
|
||||
#undef MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_PSK_EPHEMERAL_ENABLED
|
||||
#undef MBEDTLS_X509_CREATE_C
|
||||
#undef MBEDTLS_X509_CRT_WRITE_C
|
||||
#undef MBEDTLS_X509_CSR_PARSE_C
|
||||
#undef MBEDTLS_X509_CSR_WRITE_C
|
||||
#undef MBEDTLS_X509_CRL_PARSE_C
|
||||
#undef MBEDTLS_VERSION_FEATURES
|
||||
|
||||
#endif /* XASH_MBEDTLS_CONFIG_H */
|
||||
82
3rdparty/mbedtls/xash_psa_config.h
vendored
Normal file
82
3rdparty/mbedtls/xash_psa_config.h
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef XASH_PSA_CONFIG_H
|
||||
#define XASH_PSA_CONFIG_H
|
||||
|
||||
#if (defined( _WIN32 ) && !defined( _WIN64 )) || defined( __vita__ ) || defined( __SWITCH__ )
|
||||
/* WinXP, PSVita and NSwitch use entropy paths upstream doesn't cover.
|
||||
compat.c provides mbedtls_platform_get_entropy() for all three. */
|
||||
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
|
||||
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
||||
#endif
|
||||
|
||||
#if defined( __vita__ ) || defined( __SWITCH__ )
|
||||
/* Upstream has no Vita/NSW support; compat.c fills in */
|
||||
#define MBEDTLS_PLATFORM_MS_TIME_ALT
|
||||
#endif
|
||||
|
||||
#undef MBEDTLS_FS_IO
|
||||
#undef MBEDTLS_PSA_ITS_FILE_C
|
||||
#undef MBEDTLS_PSA_CRYPTO_STORAGE_C
|
||||
#undef MBEDTLS_SELF_TEST
|
||||
#undef MBEDTLS_HMAC_DRBG_C
|
||||
#undef MBEDTLS_LMS_C
|
||||
#undef MBEDTLS_NIST_KW_C
|
||||
#undef MBEDTLS_PKCS5_C
|
||||
|
||||
#undef PSA_WANT_ALG_CCM
|
||||
#undef PSA_WANT_ALG_CCM_STAR_NO_TAG
|
||||
#undef PSA_WANT_ALG_CBC_NO_PADDING
|
||||
#undef PSA_WANT_ALG_CBC_PKCS7
|
||||
#undef PSA_WANT_ALG_CFB
|
||||
#undef PSA_WANT_ALG_CMAC
|
||||
#undef PSA_WANT_ALG_CTR
|
||||
#undef PSA_WANT_ALG_ECB_NO_PADDING
|
||||
#undef PSA_WANT_ALG_OFB
|
||||
#undef PSA_WANT_ALG_STREAM_CIPHER
|
||||
#undef PSA_WANT_ALG_JPAKE
|
||||
#undef PSA_WANT_ALG_TLS12_ECJPAKE_TO_PMS
|
||||
#undef PSA_WANT_ALG_TLS12_PSK_TO_MS
|
||||
#undef PSA_WANT_ALG_FFDH
|
||||
#undef PSA_WANT_ALG_PBKDF2_HMAC
|
||||
#undef PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128
|
||||
#undef PSA_WANT_KEY_TYPE_PASSWORD
|
||||
#undef PSA_WANT_KEY_TYPE_PASSWORD_HASH
|
||||
#undef PSA_WANT_ALG_SHA3_224
|
||||
#undef PSA_WANT_ALG_SHA3_256
|
||||
#undef PSA_WANT_ALG_SHA3_384
|
||||
#undef PSA_WANT_ALG_SHA3_512
|
||||
#undef PSA_WANT_ALG_SHAKE128
|
||||
#undef PSA_WANT_ALG_SHAKE256
|
||||
#undef PSA_WANT_ALG_SHA_224
|
||||
#undef PSA_WANT_ALG_SHA_1
|
||||
#undef PSA_WANT_ALG_MD5
|
||||
#undef PSA_WANT_ALG_RIPEMD160
|
||||
#undef PSA_WANT_KEY_TYPE_ARIA
|
||||
#undef PSA_WANT_KEY_TYPE_CAMELLIA
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_256
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_384
|
||||
#undef PSA_WANT_ECC_BRAINPOOL_P_R1_512
|
||||
#undef PSA_WANT_ECC_SECP_K1_256
|
||||
#undef PSA_WANT_ECC_MONTGOMERY_448
|
||||
#undef PSA_WANT_ECC_SECP_R1_521
|
||||
#undef PSA_WANT_DH_RFC7919_2048
|
||||
#undef PSA_WANT_DH_RFC7919_3072
|
||||
#undef PSA_WANT_DH_RFC7919_4096
|
||||
#undef PSA_WANT_DH_RFC7919_6144
|
||||
#undef PSA_WANT_DH_RFC7919_8192
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_BASIC
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_GENERATE
|
||||
#undef PSA_WANT_KEY_TYPE_DH_KEY_PAIR_IMPORT
|
||||
#undef PSA_WANT_KEY_TYPE_DH_PUBLIC_KEY
|
||||
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_EXPORT
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE
|
||||
#undef PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_IMPORT
|
||||
#undef MBEDTLS_PEM_WRITE_C
|
||||
#undef MBEDTLS_PK_WRITE_C
|
||||
#undef MBEDTLS_PK_PARSE_EC_COMPRESSED
|
||||
#undef MBEDTLS_PK_PARSE_EC_EXTENDED
|
||||
#undef MBEDTLS_ASN1_WRITE_C
|
||||
|
||||
#endif /* XASH_PSA_CONFIG_H */
|
||||
2
3rdparty/nanogl
vendored
2
3rdparty/nanogl
vendored
Submodule 3rdparty/nanogl updated: c75f1326f9...6c8aeca5e6
2
3rdparty/vgui_support
vendored
2
3rdparty/vgui_support
vendored
Submodule 3rdparty/vgui_support updated: 36219aedfc...e021fb5e7f
@@ -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.
|
||||
|
||||
|
||||
19
Documentation/extensions/litwater.md
Normal file
19
Documentation/extensions/litwater.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
---
|
||||
|
||||
181
Documentation/extensions/xrcon-protocol.md
Normal file
181
Documentation/extensions/xrcon-protocol.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# XRCON Protocol Specification
|
||||
|
||||
## Overview
|
||||
|
||||
XRCON is a TCP-based remote console protocol implemented as a server within the engine. It provides an alternative to the legacy RCON (which operates over UDP as out-of-band packets). XRCON uses a framed binary protocol over a single persistent TCP connection, accepting at most one client at a time.
|
||||
|
||||
Key characteristics:
|
||||
|
||||
- Based on TCP (stream-oriented, reliable)
|
||||
- Default port is 27000
|
||||
- Maximum only one concurrent client
|
||||
- Binary framed protocol with a 12-byte header + variable-length payload
|
||||
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The XRCON server is controlled by two configuration variables, both restricted to privileged users:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `xrcon_enable` | `0` (disabled) | Master switch; when enabled, the server starts listening for connections |
|
||||
| `xrcon_address` | `127.0.0.1:27000` | Bind address and port; supports both IPv4 and IPv6. Changing this at runtime triggers a XRCON server restart (stop + rebind) |
|
||||
|
||||
By default, XRCON binds only to localhost (`127.0.0.1`), providing a degree of access control. The server will not start listening until explicitly enabled.
|
||||
|
||||
|
||||
|
||||
## Frame Format
|
||||
|
||||
All XRCON messages are encapsulated in a framed binary format. Each frame consists of a fixed 12-byte header followed by a variable-length payload.
|
||||
|
||||
### Header (12 bytes)
|
||||
|
||||
| Offset | Size | Field | Description |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | Type | 4-character message type string (null-terminated, occupies 5 bytes in the structure but the 5th byte is padding) |
|
||||
| 4 | 4 | Version | Protocol version (32-bit unsigned integer, network byte order) |
|
||||
| 8 | 2 | Length | Total frame length in bytes, including the header (16-bit unsigned integer, network byte order) |
|
||||
| 10 | 2 | Handle | Handle or sequence number (16-bit unsigned integer, network byte order; currently always zero) |
|
||||
|
||||
|
||||
### Protocol Version
|
||||
|
||||
The current protocol version is `0x000000D4` (212 decimal). The server also accepts `0x00D40000` for compatibility with certain third-party clients (e.g., CS2RemoteConsole).
|
||||
|
||||
### Maximum Frame Size
|
||||
|
||||
- Maximum payload per frame: 4096 bytes
|
||||
- Maximum total packet size (header + payload): approximately 4136 bytes
|
||||
- Receive buffer size: 4096 + 64 bytes
|
||||
- Transmit buffer size: 16384 bytes (4 × maximum frame size)
|
||||
|
||||
### Byte Order
|
||||
|
||||
Multi-byte integer fields (version, length, handle) are transmitted in **network byte order** (big-endian).
|
||||
|
||||
|
||||
|
||||
## Message Types
|
||||
|
||||
| Type | Direction | Description |
|
||||
|---|---|---|
|
||||
| `CMND` | Client → Server | Execute a console command |
|
||||
| `PRNT` | Server → Client | Console output / print message |
|
||||
| `CHAN` | Server → Client | Channel list (channel metadata) |
|
||||
| `AINF` | Server → Client | Application / server info |
|
||||
| `ADON` | Server → Client | Additional info / server name |
|
||||
|
||||
### CMND — Client Command
|
||||
|
||||
**Direction**: Client → Server
|
||||
|
||||
Sent by the client to execute a console command on the server.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"CMND"`, version, length, handle |
|
||||
| Payload | Variable | Raw command string (null-terminated) |
|
||||
|
||||
The command string is injected into the engine's command buffer for execution. There is **no authentication** — any connected client may execute arbitrary commands.
|
||||
|
||||
### PRNT — Server Print
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server to stream console output to the connected client.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"PRNT"`, version, length, handle |
|
||||
| Channel ID | 4 bytes | Channel identifier (unsigned 32-bit integer; always 0 for "Console") |
|
||||
| Padding | 20 bytes | Reserved (5 × 32-bit unsigned integers, all zero) |
|
||||
| Red | 1 byte | Red color component (always 255) |
|
||||
| Green | 1 byte | Green color component (always 255) |
|
||||
| Blue | 1 byte | Blue color component (always 255) |
|
||||
| Alpha | 1 byte | Alpha / opacity component (always 255) |
|
||||
| Text | Variable | The console output text (up to 4096 bytes) |
|
||||
|
||||
|
||||
### CHAN — Channel List
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server during the initial handshake (and potentially later) to describe the available console output channels.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"CHAN"`, version, length, handle |
|
||||
| Channel Count | 2 bytes | Number of channel records (unsigned 16-bit integer; always 1) |
|
||||
|
||||
Each channel record (58 bytes):
|
||||
|
||||
| Field | Size | Description |
|
||||
|---|---|---|
|
||||
| Channel ID | 4 bytes | Unique channel identifier (unsigned 32-bit integer; always 0) |
|
||||
| Unknown 1 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
|
||||
| Unknown 2 | 4 bytes | Reserved (unsigned 32-bit integer; always 0) |
|
||||
| Default Verbosity | 4 bytes | Default verbosity level (unsigned 32-bit integer; always 5) |
|
||||
| Current Verbosity | 4 bytes | Current verbosity level (unsigned 32-bit integer; always 5) |
|
||||
| Red | 1 byte | Red color component for the channel (always 255) |
|
||||
| Green | 1 byte | Green color component (always 255) |
|
||||
| Blue | 1 byte | Blue color component (always 255) |
|
||||
| Alpha | 1 byte | Alpha component (always 255) |
|
||||
| Name | 34 bytes | Channel name string, null-padded (always `"Console"`) |
|
||||
|
||||
Total payload size for this frame: 2 + 58 = 60 bytes.
|
||||
|
||||
### AINF — Application Info
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server immediately after a client connects, before `ADON` and `CHAN`.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"AINF"`, version, length, handle |
|
||||
| Payload | 77 bytes | All zeros (placeholder / reserved for structured application information) |
|
||||
|
||||
This packet currently serves as a placeholder and contains no meaningful data. It may be extended in future versions to carry structured metadata about the server application.
|
||||
|
||||
### ADON — Additional Info
|
||||
|
||||
**Direction**: Server → Client
|
||||
|
||||
Sent by the server immediately after `AINF` during the connection handshake.
|
||||
|
||||
| Section | Size | Description |
|
||||
|---|---|---|
|
||||
| Header | 12 bytes | Type = `"ADON"`, version, length, handle |
|
||||
| Unknown | 2 bytes | Reserved (unsigned 16-bit integer; always 0) |
|
||||
| Name Length | 2 bytes | Length of the name string (unsigned 16-bit integer) |
|
||||
| Name | Variable | Server name / identifier string (e.g., `"HLDS"`) |
|
||||
|
||||
This packet carries the server application name, which is a short string identifying the server type (e.g., `"HLDS"` for Half-Life Dedicated Server).
|
||||
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
XRCON has **no built-in authentication or encryption**. There is no password validation or transport-layer security. Any client that can establish a TCP connection to the XRCON port has full command execution access to the engine console.
|
||||
|
||||
The only security measures are:
|
||||
|
||||
1. **Default localhost binding**: The server binds to `127.0.0.1` by default, accepting connections only from the local machine.
|
||||
2. **Disabled by default**: The `xrcon_enable` variable defaults to `0`, so the server does not listen unless explicitly enabled.
|
||||
3. **Privileged variables**: Both configuration variables are restricted to privileged users and cannot be modified through unprivileged console commands or external access.
|
||||
|
||||
|
||||
|
||||
## Comparison with Legacy RCON
|
||||
|
||||
| Feature | XRCON | Legacy RCON |
|
||||
|---|---|---|
|
||||
| Transport | TCP (stream) | UDP (datagram, out-of-band) |
|
||||
| Port | Configurable, default 27000 | Game port (default 27015) |
|
||||
| Framing | Binary frame header | Plain text over UDP |
|
||||
| Authentication | None | Password-based |
|
||||
| Max clients | 1 | Multiple |
|
||||
| Interaction format | Full console access | Request-response only for submitted command |
|
||||
| Supported environments | Client and server | Server only |
|
||||
@@ -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. |
|
||||
|
||||
71
Documentation/protocol/http-server-list.md
Normal file
71
Documentation/protocol/http-server-list.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Static HTTP server list
|
||||
|
||||
Replacement for the UDP `S2M_SCAN_REQUEST` / `M2A_SERVERSLIST` exchange
|
||||
described in [02-connectionless.md](02-connectionless.md). Read-only HTTP,
|
||||
no NAT punching, no filtering or pagination in the request.
|
||||
|
||||
## URL
|
||||
|
||||
`xashcomm.lst` carries a base URL per source:
|
||||
|
||||
```
|
||||
masterstatic http://master.example.org/server-list
|
||||
```
|
||||
|
||||
The engine appends `/v1/servers/<gamedir>` and `GET`s the result. For
|
||||
`gamedir = valve`:
|
||||
|
||||
```
|
||||
GET http://master.example.org/server-list/v1/servers/valve
|
||||
```
|
||||
|
||||
Trailing slashes on the base URL are stripped. Multiple `masterstatic`
|
||||
lines are allowed; results are merged.
|
||||
|
||||
The request is bare: no body, no auth, no cookies, no compressed encodings.
|
||||
Only the standard `User-Agent` is set. `POST` / `PUT` / `DELETE` are not
|
||||
used; servers register out of band.
|
||||
|
||||
## Response
|
||||
|
||||
UTF-8 text, tokenized with `COM_ParseFileSafe` (whitespace separates,
|
||||
`//` and `#` start line comments, `"..."` quotes a token). One directive
|
||||
per record:
|
||||
|
||||
* `ip <address>` — Xash3D server (protocol 49).
|
||||
* `gs <address>` — GoldSrc server (protocol 48).
|
||||
|
||||
`<address>` is parsed by `NET_StringToAdr` (`1.2.3.4:27015`,
|
||||
`[2001:db8::1]:27015`, hostnames). Port defaults to `27015`. Unknown
|
||||
directives are skipped together with one operand so new keywords can be
|
||||
added without breaking older clients.
|
||||
|
||||
`Content-Type` is not inspected, `text/plain; charset=utf-8` expected.
|
||||
|
||||
### Example
|
||||
|
||||
```
|
||||
# diffusion servers
|
||||
ip 192.0.2.10:27015
|
||||
ip [2001:db8::1]:27015
|
||||
gs 198.51.100.5:27015
|
||||
```
|
||||
|
||||
A file with zero records is valid and represents an empty list.
|
||||
|
||||
### Versioning
|
||||
|
||||
The `/v1/` segment is fixed in this revision. A future protocol revision
|
||||
adds a sibling `/v2/...` resource without breaking older clients.
|
||||
|
||||
## Client behaviour
|
||||
|
||||
Each `ip` / `gs` record from the response triggers a probe to the listed
|
||||
address, identical to a server discovered through the UDP master. DNS
|
||||
errors, non-200 responses, and malformed bodies are reported to the
|
||||
console.
|
||||
|
||||
## Server behaviour
|
||||
|
||||
Any static HTTP server works. A typical setup is a periodic job that
|
||||
probes a set of known addresses and writes `v1/servers/<gamedir>`.
|
||||
3
android/.gitignore
vendored
3
android/.gitignore
vendored
@@ -11,4 +11,5 @@ local.properties
|
||||
release/
|
||||
*.hprof
|
||||
.vscode/
|
||||
*.bak
|
||||
*.bak
|
||||
.kotlin/
|
||||
|
||||
@@ -20,10 +20,12 @@ extensions.configure<ApplicationExtension> {
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
|
||||
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
|
||||
|
||||
externalNativeBuild {
|
||||
val engineRoot = projectDir.parentFile.parent
|
||||
|
||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
experimentalProperties["ninja.abiFilters"] = setOf("armeabi-v7a", "arm64-v8a", "x86")
|
||||
experimentalProperties["ninja.path"] = File(engineRoot, "wscript").path
|
||||
experimentalProperties["ninja.configure"] = "run-python"
|
||||
experimentalProperties["ninja.arguments"] = setOf(
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,143 @@ 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))
|
||||
moveOrCopy(CrashReports.pendingEngineLog(this), File(entryDir, CrashReports.ENGINELOG_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
|
||||
|
||||
src.copyTo(dst, overwrite = true)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
205
android/app/src/main/java/su/xash/engine/model/AppUpdater.kt
Normal file
205
android/app/src/main/java/su/xash/engine/model/AppUpdater.kt
Normal 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+)""")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
"—"
|
||||
}
|
||||
}
|
||||
|
||||
176
android/app/src/main/java/su/xash/engine/util/CrashReports.kt
Normal file
176
android/app/src/main/java/su/xash/engine/util/CrashReports.kt
Normal file
@@ -0,0 +1,176 @@
|
||||
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"
|
||||
const val ENGINELOG_NAME = "engine.log"
|
||||
|
||||
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)
|
||||
val engineLog: File get() = File(dir, ENGINELOG_NAME)
|
||||
|
||||
fun attachments(): List<File> = listOf(stacktrace, sysinfo, intent, engineLog).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 pendingEngineLog(ctx: Context): File = File(pendingDir(ctx), ENGINELOG_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()
|
||||
pendingEngineLog(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)))
|
||||
}
|
||||
}
|
||||
@@ -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() }
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
22
android/app/src/main/res/layout/dialog_download_progress.xml
Normal file
22
android/app/src/main/res/layout/dialog_download_progress.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
31
android/app/src/main/res/raw/isrgrootx1.pem
Normal file
31
android/app/src/main/res/raw/isrgrootx1.pem
Normal 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-----
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
9
android/app/src/main/res/xml/network_security_config.xml
Normal file
9
android/app/src/main/res/xml/network_security_config.xml
Normal 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>
|
||||
4
android/app/src/main/res/xml/paths.xml
Normal file
4
android/app/src/main/res/xml/paths.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="crash_zips" path="crashes/" />
|
||||
</paths>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#define CS_SIZE 64 // size of one config string
|
||||
#define CS_TIME 16 // size of time string
|
||||
|
||||
// FIXME: find better place for the shared definition
|
||||
#define MAX_CLIENT_BITS 5
|
||||
#define MAX_CLIENTS (1<<MAX_CLIENT_BITS)// 5 bits == 32 clients ( int32 limit )
|
||||
|
||||
// platform-specific alignment for types, to not break ABI
|
||||
#if XASH_PSP
|
||||
#define MAYBE_ALIGNED( x ) __attribute__(( aligned( 16 )))
|
||||
@@ -114,6 +118,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 +203,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 +252,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 +264,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 +274,14 @@ 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
|
||||
|
||||
static inline short UnalignedShort( short *x )
|
||||
{
|
||||
short y;
|
||||
memcpy( &y, x, sizeof( y ) );
|
||||
return y;
|
||||
}
|
||||
|
||||
#endif // XASH_TYPES_H
|
||||
|
||||
@@ -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 );
|
||||
@@ -463,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 )
|
||||
@@ -529,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 );
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 ))
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
@@ -1064,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
|
||||
@@ -1099,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 );
|
||||
@@ -1130,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;
|
||||
@@ -1148,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 );
|
||||
@@ -1164,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 );
|
||||
@@ -1188,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 );
|
||||
@@ -1205,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
|
||||
@@ -1215,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 );
|
||||
@@ -1239,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;
|
||||
@@ -1262,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,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;
|
||||
@@ -1300,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;
|
||||
@@ -1317,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;
|
||||
@@ -1333,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;
|
||||
|
||||
@@ -1354,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 );
|
||||
@@ -1381,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 );
|
||||
}
|
||||
}
|
||||
@@ -1396,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 );
|
||||
@@ -1415,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 );
|
||||
}
|
||||
}
|
||||
@@ -1429,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];
|
||||
@@ -1456,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 );
|
||||
@@ -1476,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 );
|
||||
@@ -1503,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 );
|
||||
}
|
||||
}
|
||||
@@ -1640,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 )
|
||||
{
|
||||
@@ -1680,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 ;
|
||||
@@ -1692,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 );
|
||||
}
|
||||
@@ -1706,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 );
|
||||
}
|
||||
|
||||
@@ -1722,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 );
|
||||
@@ -1741,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;
|
||||
@@ -1773,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);
|
||||
@@ -1815,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 );
|
||||
@@ -1844,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;
|
||||
@@ -1870,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 );
|
||||
@@ -1890,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 );
|
||||
@@ -1951,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;
|
||||
@@ -1981,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 );
|
||||
@@ -1996,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;
|
||||
@@ -2061,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 )
|
||||
{
|
||||
@@ -2080,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 )
|
||||
{
|
||||
@@ -2107,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;
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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;
|
||||
@@ -1030,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 );
|
||||
@@ -1113,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 )
|
||||
@@ -1131,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 )
|
||||
{
|
||||
@@ -1142,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;
|
||||
|
||||
@@ -1156,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 )
|
||||
{
|
||||
@@ -1369,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 );
|
||||
@@ -1384,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 )
|
||||
|
||||
@@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "common.h"
|
||||
#include "client.h"
|
||||
#include "net_encode.h"
|
||||
@@ -36,8 +37,9 @@ 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" );
|
||||
static CVAR_DEFINE( cl_logoupdate, "@cl_logoupdate", "0", 0, "set by menu to trigger clan logo update" );
|
||||
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" );
|
||||
|
||||
@@ -47,18 +49,18 @@ CVAR_DEFINE( cl_draw_beams, "r_drawbeams", "1", FCVAR_CHEAT, "render beams" );
|
||||
|
||||
static CVAR_DEFINE_AUTO( rcon_address, "", FCVAR_PRIVILEGED, "remote control address" );
|
||||
CVAR_DEFINE_AUTO( cl_timeout, "60", 0, "connect timeout (in-seconds)" );
|
||||
CVAR_DEFINE_AUTO( cl_nopred, "0", FCVAR_ARCHIVE|FCVAR_USERINFO, "disable client movement prediction" );
|
||||
CVAR_DEFINE_AUTO( cl_nopred, "0", FCVAR_USERINFO, "disable client movement prediction" );
|
||||
static CVAR_DEFINE_AUTO( cl_nodelta, "0", 0, "disable delta-compression for server messages" );
|
||||
CVAR_DEFINE( cl_crosshair, "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" );
|
||||
static CVAR_DEFINE_AUTO( cl_cmdbackup, "10", FCVAR_ARCHIVE, "how many additional history commands are sent" );
|
||||
CVAR_DEFINE_AUTO( cl_showerror, "0", FCVAR_ARCHIVE, "show prediction error" );
|
||||
CVAR_DEFINE_AUTO( cl_bmodelinterp, "1", FCVAR_ARCHIVE, "enable bmodel interpolation" );
|
||||
CVAR_DEFINE_AUTO( cl_bmodelinterp, "1", 0, "enable bmodel interpolation" );
|
||||
static CVAR_DEFINE_AUTO( cl_lightstyle_lerping, "0", FCVAR_ARCHIVE, "enables animated light lerping (perfomance option)" );
|
||||
CVAR_DEFINE_AUTO( cl_idealpitchscale, "0.8", 0, "how much to look up/down slopes and stairs when not using freelook" );
|
||||
CVAR_DEFINE_AUTO( cl_nosmooth, "0", FCVAR_ARCHIVE, "disable smooth up stair climbing" );
|
||||
CVAR_DEFINE_AUTO( cl_smoothtime, "0.1", FCVAR_ARCHIVE, "time to smooth up" );
|
||||
CVAR_DEFINE_AUTO( cl_clockreset, "0.1", FCVAR_ARCHIVE, "frametime delta maximum value before reset" );
|
||||
static CVAR_DEFINE_AUTO( cl_fixtimerate, "7.5", FCVAR_ARCHIVE, "time in msec to client clock adjusting" );
|
||||
CVAR_DEFINE_AUTO( cl_nosmooth, "0", 0, "disable smooth up stair climbing" );
|
||||
CVAR_DEFINE_AUTO( cl_smoothtime, "0.1", 0, "time to smooth up" );
|
||||
CVAR_DEFINE_AUTO( cl_clockreset, "0.1", 0, "frametime delta maximum value before reset" );
|
||||
static CVAR_DEFINE_AUTO( cl_fixtimerate, "7.5", 0, "time in msec to client clock adjusting" );
|
||||
CVAR_DEFINE_AUTO( hud_fontscale, "1.0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud font texture" );
|
||||
CVAR_DEFINE_AUTO( hud_fontrender, "0", FCVAR_ARCHIVE, "hud font render mode (0: additive, 1: holes, 2: trans)" );
|
||||
CVAR_DEFINE_AUTO( hud_scale, "0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud at current resolution" );
|
||||
@@ -94,7 +96,7 @@ static CVAR_DEFINE_AUTO( topcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTE
|
||||
static CVAR_DEFINE_AUTO( bottomcolor, "0", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player bottom color" );
|
||||
CVAR_DEFINE_AUTO( rate, "25000", FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_FILTERABLE, "player network rate" );
|
||||
|
||||
CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE, "you wouldn't steal a car" );
|
||||
CVAR_DEFINE_AUTO( cl_ticket_generator, "revemu2013", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "you wouldn't steal a car" );
|
||||
static CVAR_DEFINE_AUTO( cl_advertise_engine_in_name, "1", FCVAR_ARCHIVE|FCVAR_PRIVILEGED, "add [Xash3D] to the nickname when connecting to GoldSrc servers" );
|
||||
static CVAR_DEFINE_AUTO( cl_log_outofband, "0", FCVAR_ARCHIVE, "log out of band messages, can be useful for server admins and for engine debugging" );
|
||||
static CVAR_DEFINE_AUTO( cl_autorecord, "0", 0, "automatically start recording a demo after joining the server" );
|
||||
@@ -195,6 +197,120 @@ void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats )
|
||||
}
|
||||
}
|
||||
|
||||
static resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index )
|
||||
{
|
||||
resource_t *r = &cl.resourcelist[cl.num_resources];
|
||||
|
||||
if( cl.num_resources >= MAX_RESOURCES )
|
||||
Host_Error( "Too many resources on client\n" );
|
||||
cl.num_resources++;
|
||||
|
||||
Q_strncpy( r->szFileName, name, sizeof( r->szFileName ));
|
||||
r->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0;
|
||||
r->nDownloadSize = size;
|
||||
r->nIndex = index;
|
||||
r->type = type;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void CL_CreateResourceList( void )
|
||||
{
|
||||
char szFileName[MAX_OSPATH];
|
||||
byte rgucMD5_hash[16] = { 0 };
|
||||
|
||||
HPAK_FlushHostQueue();
|
||||
cl.num_resources = 0;
|
||||
memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));
|
||||
|
||||
ClearBits( cl_logoupdate.flags, FCVAR_CHANGED );
|
||||
#if 1 // FIXME: deprecated, remove later
|
||||
ClearBits( cl_logofile.flags, FCVAR_CHANGED );
|
||||
ClearBits( cl_logocolor.flags, FCVAR_CHANGED );
|
||||
ClearBits( cl_logoext.flags, FCVAR_CHANGED );
|
||||
#endif
|
||||
|
||||
// sanitize cvar value
|
||||
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
|
||||
Cvar_DirectSet( &cl_logoext, "bmp" );
|
||||
|
||||
Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string );
|
||||
if( cls.legacymode == PROTO_GOLDSRC )
|
||||
{
|
||||
CL_ConvertImageToWAD3( szFileName );
|
||||
Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName ));
|
||||
}
|
||||
file_t *fp = FS_Open( szFileName, "rb", true );
|
||||
|
||||
if( !fp )
|
||||
return;
|
||||
|
||||
int nSize = FS_FileLength( fp );
|
||||
|
||||
if( nSize != 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 );
|
||||
}
|
||||
}
|
||||
|
||||
FS_Close( fp );
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
CL_UpdateLogo
|
||||
|
||||
repackage the clan logo and upload it to the server
|
||||
==================
|
||||
*/
|
||||
static void CL_UpdateLogo( void )
|
||||
{
|
||||
if( cls.state != ca_active )
|
||||
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 void CL_CheckLogoChanged( void )
|
||||
{
|
||||
if( FBitSet( cl_logoupdate.flags, FCVAR_CHANGED ))
|
||||
{
|
||||
CL_UpdateLogo();
|
||||
return;
|
||||
}
|
||||
|
||||
#if 1 // FIXME: deprecated, remove later
|
||||
if( FBitSet( cl_logofile.flags | cl_logocolor.flags | cl_logoext.flags, FCVAR_CHANGED ))
|
||||
{
|
||||
CL_UpdateLogo();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_CheckClientState
|
||||
@@ -213,6 +329,8 @@ static void CL_CheckClientState( void )
|
||||
cls.changedemo = false; // changedemo is done
|
||||
cl.first_frame = true; // first rendering frame
|
||||
|
||||
CL_UpdateLogo();
|
||||
|
||||
SCR_MakeLevelShot(); // make levelshot if needs
|
||||
Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar
|
||||
Netchan_ReportFlow( &cls.netchan );
|
||||
@@ -315,7 +433,6 @@ should be put at.
|
||||
static float CL_LerpPoint( void )
|
||||
{
|
||||
double f = cl_serverframetime();
|
||||
double frac;
|
||||
|
||||
if( f == 0.0 || cls.timedemo )
|
||||
{
|
||||
@@ -332,7 +449,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 +463,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 +534,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 +569,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 +645,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 +722,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 +750,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 +782,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 +800,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 +1051,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 +1142,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 +1182,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 +1192,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 +1225,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 +1235,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
|
||||
@@ -1153,6 +1253,7 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
|
||||
}
|
||||
|
||||
cls.broker_wait = false;
|
||||
cls.netchan_pending_cookie = 0;
|
||||
|
||||
if( proto == PROTO_GOLDSRC )
|
||||
{
|
||||
@@ -1174,7 +1275,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 | NET_EXT_NETCHAN_COOKIE );
|
||||
string key;
|
||||
|
||||
ID_GetMD5ForAddress( key, adr, sizeof( key ));
|
||||
@@ -1189,6 +1290,16 @@ static void CL_SendConnectPacket( connprotocol_t proto, int challenge )
|
||||
Info_SetValueForKey( protinfo, "qport", qport, sizeof( protinfo ));
|
||||
Info_SetValueForKeyf( protinfo, "ext", sizeof( protinfo ), "%d", extensions );
|
||||
|
||||
if( FBitSet( extensions, NET_EXT_NETCHAN_COOKIE ))
|
||||
{
|
||||
uint64_t a = COM_RandomLong( 0, 0xFFFF );
|
||||
uint64_t b = COM_RandomLong( 0, 0xFFFF );
|
||||
uint64_t c = COM_RandomLong( 0, 0xFFFF );
|
||||
uint64_t d = COM_RandomLong( 0, 0xFFFF );
|
||||
cls.netchan_pending_cookie = ( a << 48 ) | ( b << 32 ) | ( c << 16 ) | d;
|
||||
Info_SetValueForKeyf( protinfo, "cookie", sizeof( protinfo ), "%016"PRIx64, cls.netchan_pending_cookie );
|
||||
}
|
||||
|
||||
Netchan_OutOfBandPrint( NS_CLIENT, adr, C2S_CONNECT" %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, challenge, protinfo, cls.userinfo );
|
||||
Con_Printf( "Trying to connect with modern protocol\n" );
|
||||
}
|
||||
@@ -1252,17 +1363,9 @@ 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 )
|
||||
{
|
||||
cls.internetservers_wait = NET_MasterQuery(
|
||||
cls.internetservers_key,
|
||||
cls.internetservers_nat,
|
||||
cls.internetservers_customfilter
|
||||
);
|
||||
}
|
||||
cls.internetservers_wait = NET_MasterQuery( 1, cls.internetservers_nat, cls.internetservers_customfilter );
|
||||
|
||||
// if the local server is running and we aren't then connect
|
||||
if( cls.state == ca_disconnected && SV_Active( ))
|
||||
@@ -1287,6 +1390,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 +1399,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 )
|
||||
{
|
||||
@@ -1356,68 +1460,6 @@ static void CL_CheckForResend( void )
|
||||
}
|
||||
}
|
||||
|
||||
static resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index )
|
||||
{
|
||||
resource_t *r = &cl.resourcelist[cl.num_resources];
|
||||
|
||||
if( cl.num_resources >= MAX_RESOURCES )
|
||||
Host_Error( "Too many resources on client\n" );
|
||||
cl.num_resources++;
|
||||
|
||||
Q_strncpy( r->szFileName, name, sizeof( r->szFileName ));
|
||||
r->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0;
|
||||
r->nDownloadSize = size;
|
||||
r->nIndex = index;
|
||||
r->type = type;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void CL_CreateResourceList( void )
|
||||
{
|
||||
char szFileName[MAX_OSPATH];
|
||||
byte rgucMD5_hash[16] = { 0 };
|
||||
resource_t *pNewResource;
|
||||
int nSize;
|
||||
file_t *fp;
|
||||
|
||||
HPAK_FlushHostQueue();
|
||||
cl.num_resources = 0;
|
||||
memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash ));
|
||||
|
||||
// sanitize cvar value
|
||||
if( Q_strcmp( cl_logoext.string, "bmp" ) && Q_strcmp( cl_logoext.string, "png" ))
|
||||
Cvar_DirectSet( &cl_logoext, "bmp" );
|
||||
|
||||
Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.%s", cl_logoext.string );
|
||||
if( cls.legacymode == PROTO_GOLDSRC )
|
||||
{
|
||||
CL_ConvertImageToWAD3( szFileName );
|
||||
Q_strncpy( szFileName, "tempdecal.wad", sizeof( szFileName ));
|
||||
}
|
||||
fp = FS_Open( szFileName, "rb", true );
|
||||
|
||||
if( !fp )
|
||||
return;
|
||||
|
||||
MD5_HashFile( rgucMD5_hash, szFileName, NULL );
|
||||
nSize = FS_FileLength( fp );
|
||||
|
||||
if( nSize != 0 )
|
||||
{
|
||||
pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 );
|
||||
|
||||
if( pNewResource )
|
||||
{
|
||||
SetBits( pNewResource->ucFlags, RES_CUSTOM );
|
||||
memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 );
|
||||
HPAK_AddLump( false, hpk_custom_file.string, pNewResource, NULL, fp );
|
||||
}
|
||||
}
|
||||
|
||||
FS_Close( fp );
|
||||
}
|
||||
|
||||
static qboolean CL_StringToProtocol( const char *s, connprotocol_t *proto )
|
||||
{
|
||||
if( !Q_stricmp( s, "current" ) || !Q_strcmp( s, "49" ))
|
||||
@@ -1493,7 +1535,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 +1566,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 +1589,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 +1656,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 +1669,7 @@ int CL_GetSplitSize( void )
|
||||
return FRAGMENT_DEFAULT_SIZE;
|
||||
}
|
||||
|
||||
return (int)cl_dlmax.value;
|
||||
return splitsize;
|
||||
}
|
||||
|
||||
void CL_SetupNetchanForProtocol( connprotocol_t proto )
|
||||
@@ -1652,10 +1691,19 @@ void CL_SetupNetchanForProtocol( connprotocol_t proto )
|
||||
|
||||
if( FBitSet( cls.extensions, NET_EXT_SPLITSIZE ))
|
||||
Con_Reportf( "^2NET_EXT_SPLITSIZE enabled^7 (packet size is %d)\n", (int)cl_dlmax.value );
|
||||
|
||||
if( FBitSet( cls.extensions, NET_EXT_NETCHAN_COOKIE ))
|
||||
{
|
||||
Con_Reportf( "^2NET_EXT_NETCHAN_COOKIE enabled^7\n" );
|
||||
SetBits( flags, NETCHAN_USE_COOKIE );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, pfnBlockSize, flags );
|
||||
|
||||
if( FBitSet( flags, NETCHAN_USE_COOKIE ))
|
||||
Netchan_SetCookie( &cls.netchan, cls.netchan_pending_cookie );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1810,27 +1858,10 @@ static void CL_InternetServers_f( void )
|
||||
|
||||
cls.internetservers_nat = cl_nat.value != 0.0f;
|
||||
cls.internetservers_pending = true;
|
||||
cls.internetservers_key = COM_RandomLong( 0, 0xFFFFFFFF );
|
||||
Q_strncpy( cls.internetservers_customfilter, Cmd_Argv( 1 ), sizeof( cls.internetservers_customfilter ));
|
||||
|
||||
cls.internetservers_wait = NET_MasterQuery(
|
||||
cls.internetservers_key,
|
||||
cls.internetservers_nat,
|
||||
cls.internetservers_customfilter
|
||||
);
|
||||
}
|
||||
|
||||
static void CL_QueryServer( netadr_t adr, connprotocol_t proto )
|
||||
{
|
||||
switch( proto )
|
||||
{
|
||||
case PROTO_GOLDSRC:
|
||||
Netchan_OutOfBand( NS_CLIENT, adr, sizeof( A2S_GOLDSRC_INFO ), A2S_GOLDSRC_INFO ); // includes null terminator!
|
||||
break;
|
||||
case PROTO_CURRENT:
|
||||
Netchan_OutOfBandPrint( NS_CLIENT, adr, A2A_INFO" %i", PROTOCOL_VERSION );
|
||||
break;
|
||||
}
|
||||
// the key is dead extension, keep for compatibility until we use UDP based master server protocol
|
||||
cls.internetservers_wait = NET_MasterQuery( 1, cls.internetservers_nat, cls.internetservers_customfilter );
|
||||
}
|
||||
|
||||
static void CL_QueryServer_f( void )
|
||||
@@ -1858,7 +1889,7 @@ static void CL_QueryServer_f( void )
|
||||
if( !CL_StringToProtocol( Cmd_Argv( 2 ), &proto ))
|
||||
return;
|
||||
|
||||
CL_QueryServer( adr, proto );
|
||||
NET_QueryServerByAddress( adr, proto );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1946,8 +1977,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;
|
||||
|
||||
@@ -2043,7 +2072,7 @@ static void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg )
|
||||
UI_AddServerToList( from, infostring );
|
||||
}
|
||||
|
||||
static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg )
|
||||
static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg, qboolean legacy_format )
|
||||
{
|
||||
static char s[512+8];
|
||||
int p, numcl, maxcl, password, remaining, bots;
|
||||
@@ -2053,19 +2082,58 @@ static void CL_ParseGoldSrcStatusMessage( netadr_t from, sizebuf_t *msg )
|
||||
// set to beginning but skip header
|
||||
MSG_SeekToBit( msg, (sizeof( uint32_t ) + sizeof( uint8_t )) << 3, SEEK_SET );
|
||||
|
||||
p = MSG_ReadByte( msg );
|
||||
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
|
||||
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
|
||||
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
|
||||
MSG_ReadString( msg ); // game description
|
||||
MSG_ReadShort( msg ); // app id
|
||||
numcl = MSG_ReadByte( msg );
|
||||
maxcl = MSG_ReadByte( msg );
|
||||
bots = MSG_ReadByte( msg ); // bots count
|
||||
MSG_ReadByte( msg ); // dedicated
|
||||
MSG_ReadByte( msg ); // operating system
|
||||
password = MSG_ReadByte( msg );
|
||||
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
|
||||
if( legacy_format )
|
||||
{
|
||||
string address;
|
||||
int mod;
|
||||
|
||||
p = MSG_ReadByte( msg );
|
||||
Q_strncpy( address, MSG_ReadString( msg ), sizeof( address ));
|
||||
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
|
||||
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
|
||||
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
|
||||
MSG_ReadString( msg ); // game description
|
||||
numcl = MSG_ReadByte( msg );
|
||||
maxcl = MSG_ReadByte( msg );
|
||||
MSG_ReadByte( msg ); // protocol version
|
||||
MSG_ReadByte( msg ); // server type
|
||||
MSG_ReadByte( msg ); // operating system
|
||||
password = MSG_ReadByte( msg );
|
||||
mod = MSG_ReadByte( msg ); // mod flag
|
||||
|
||||
if( mod == 1 )
|
||||
{
|
||||
MSG_ReadString( msg ); // mod URL
|
||||
MSG_ReadString( msg ); // mod download URL
|
||||
MSG_ReadLong( msg ); // mod version
|
||||
MSG_ReadLong( msg ); // mod size
|
||||
MSG_ReadByte( msg ); // mod type (SP/MP)
|
||||
MSG_ReadByte( msg ); // custom DLL flag
|
||||
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
|
||||
bots = MSG_ReadByte( msg ); // bots count
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
|
||||
bots = MSG_ReadByte( msg ); // bots count
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
p = MSG_ReadByte( msg );
|
||||
Q_strncpy( host, MSG_ReadString( msg ), sizeof( host ));
|
||||
Q_strncpy( map, MSG_ReadString( msg ), sizeof( map ));
|
||||
Q_strncpy( gamedir, MSG_ReadString( msg ), sizeof( gamedir ));
|
||||
MSG_ReadString( msg ); // game description
|
||||
MSG_ReadShort( msg ); // app id
|
||||
numcl = MSG_ReadByte( msg );
|
||||
maxcl = MSG_ReadByte( msg );
|
||||
bots = MSG_ReadByte( msg ); // bots count
|
||||
MSG_ReadByte( msg ); // server type
|
||||
MSG_ReadByte( msg ); // operating system
|
||||
password = MSG_ReadByte( msg );
|
||||
Q_strncpy( version, MSG_ReadString( msg ), sizeof( version ));
|
||||
}
|
||||
|
||||
// sanity check
|
||||
if( maxcl > MAX_CLIENTS || numcl > MAX_CLIENTS || bots > MAX_CLIENTS || numcl > maxcl || bots > maxcl )
|
||||
@@ -2123,7 +2191,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 ));
|
||||
@@ -2275,7 +2342,7 @@ CL_IsFromConnectingServer
|
||||
Used for connectionless packets, when netchan may not be ready.
|
||||
=================
|
||||
*/
|
||||
static qboolean CL_IsFromConnectingServer( netadr_t from )
|
||||
qboolean CL_IsFromConnectingServer( netadr_t from )
|
||||
{
|
||||
return NET_IsLocalAddress( from ) ||
|
||||
NET_CompareAdr( cls.serveradr, from );
|
||||
@@ -2315,7 +2382,7 @@ static void CL_HandleTestPacket( netadr_t from, sizebuf_t *msg )
|
||||
}
|
||||
|
||||
// reading test buffer
|
||||
MSG_ReadBytes( msg, recv_buf, realsize );
|
||||
MSG_ReadBytes( msg, recv_buf, sizeof( recv_buf ), realsize );
|
||||
|
||||
// procssing the CRC
|
||||
CRC32_ProcessBuffer( &crcValue2, recv_buf, realsize );
|
||||
@@ -2376,6 +2443,39 @@ static void CL_ClientConnect( connprotocol_t proto, const char *c, netadr_t from
|
||||
return;
|
||||
}
|
||||
|
||||
if( cls.netchan_pending_cookie != 0 )
|
||||
{
|
||||
int server_extensions = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "ext" ));
|
||||
|
||||
if( FBitSet( server_extensions, NET_EXT_NETCHAN_COOKIE ))
|
||||
{
|
||||
const char *cookie_str = Info_ValueForKey( Cmd_Argv( 1 ), "cookie" );
|
||||
|
||||
if( Q_strlen( cookie_str ) != 16 )
|
||||
{
|
||||
Con_Reportf( S_WARN "%s: missing cookie echo from %s, ignoring (possible spoof)\n", __func__, NET_AdrToString( from ));
|
||||
return;
|
||||
}
|
||||
|
||||
byte buf[8];
|
||||
COM_HexConvert( cookie_str, 16, buf );
|
||||
|
||||
uint64_t echoed = 0;
|
||||
for( int i = 0; i < 8; i++ )
|
||||
echoed = ( echoed << 8 ) | buf[i];
|
||||
|
||||
if( echoed != cls.netchan_pending_cookie )
|
||||
{
|
||||
Con_Reportf( S_WARN "%s: invalid cookie echo from %s, ignoring (possible spoof)\n", __func__, NET_AdrToString( from ));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cls.netchan_pending_cookie = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cls.build_num = 0; // not used in Xash3D protocols
|
||||
cls.allow_cheats = Q_atoi( Info_ValueForKey( Cmd_Argv( 1 ), "cheats" ));
|
||||
}
|
||||
@@ -2494,6 +2594,21 @@ static void CL_Reject( const char *c, const char *args, netadr_t from )
|
||||
CL_Disconnect_f();
|
||||
}
|
||||
|
||||
/*
|
||||
=================
|
||||
CL_NotifyServerListResponse
|
||||
|
||||
=================
|
||||
*/
|
||||
void CL_NotifyServerListResponse( void )
|
||||
{
|
||||
if( !cls.internetservers_pending )
|
||||
return;
|
||||
|
||||
UI_ResetPing();
|
||||
cls.internetservers_pending = false;
|
||||
}
|
||||
|
||||
static void CL_ServerList( netadr_t from, sizebuf_t *msg )
|
||||
{
|
||||
connprotocol_t proto;
|
||||
@@ -2504,25 +2619,13 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
|
||||
return;
|
||||
}
|
||||
|
||||
// check the extra header
|
||||
if( proto == PROTO_CURRENT )
|
||||
{
|
||||
// dead extension
|
||||
if( MSG_ReadByte( msg ) == 0x7f )
|
||||
{
|
||||
uint32_t key = MSG_ReadDword( msg );
|
||||
|
||||
if( cls.internetservers_key != key )
|
||||
{
|
||||
Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from ));
|
||||
return;
|
||||
}
|
||||
|
||||
MSG_ReadByte( msg ); // reserved byte
|
||||
}
|
||||
else
|
||||
{
|
||||
Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from ));
|
||||
return;
|
||||
MSG_ReadDword( msg ); // was key
|
||||
MSG_ReadByte( msg ); // was reserved
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2534,30 +2637,27 @@ static void CL_ServerList( netadr_t from, sizebuf_t *msg )
|
||||
|
||||
if( NET_NetadrType( &from ) == NA_IP6 ) // IPv6 master server only sends IPv6 addresses
|
||||
{
|
||||
MSG_ReadBytes( msg, addr, sizeof( addr ));
|
||||
MSG_ReadBytes( msg, addr, sizeof( addr ), sizeof( addr ));
|
||||
NET_IP6BytesToNetadr( &servadr, addr );
|
||||
NET_NetadrSetType( &servadr, NA_IP6 );
|
||||
}
|
||||
else
|
||||
{
|
||||
MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP
|
||||
MSG_ReadBytes( msg, servadr.ip, sizeof( 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 ), sizeof( servadr.port )); // 2 bytes for Port, in network byte order
|
||||
|
||||
// list is ends here
|
||||
if( !servadr.port )
|
||||
break;
|
||||
|
||||
NET_Config( true, false ); // allow remote
|
||||
CL_QueryServer( servadr, proto );
|
||||
NET_QueryServerByAddress( servadr, proto );
|
||||
}
|
||||
|
||||
if( cls.internetservers_pending )
|
||||
{
|
||||
UI_ResetPing();
|
||||
cls.internetservers_pending = false;
|
||||
}
|
||||
CL_NotifyServerListResponse();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2584,10 +2684,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 );
|
||||
@@ -2598,7 +2694,11 @@ static void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg )
|
||||
}
|
||||
else if( c[0] == S2A_GOLDSRC_INFO )
|
||||
{
|
||||
CL_ParseGoldSrcStatusMessage( from, msg );
|
||||
CL_ParseGoldSrcStatusMessage( from, msg, false );
|
||||
}
|
||||
else if( c[0] == S2A_GOLDSRC_LEGACY_INFO )
|
||||
{
|
||||
CL_ParseGoldSrcStatusMessage( from, msg, true );
|
||||
}
|
||||
else if( !Q_strcmp( c, A2A_NETINFO ))
|
||||
{
|
||||
@@ -2818,7 +2918,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 +3020,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 )
|
||||
{
|
||||
@@ -3398,6 +3492,7 @@ static void CL_InitLocal( void )
|
||||
Cvar_RegisterVariable( &cl_logofile );
|
||||
Cvar_RegisterVariable( &cl_logocolor );
|
||||
Cvar_RegisterVariable( &cl_logoext );
|
||||
Cvar_RegisterVariable( &cl_logoupdate );
|
||||
Cvar_RegisterVariable( &cl_logomaxdim );
|
||||
Cvar_RegisterVariable( &cl_test_bandwidth );
|
||||
|
||||
@@ -3522,11 +3617,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 +3684,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 +3782,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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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] );
|
||||
|
||||
@@ -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" );
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,11 +33,7 @@ assume pModel is valid
|
||||
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
|
||||
{
|
||||
msprite_t *psprite;
|
||||
mspritegroup_t *pspritegroup;
|
||||
mspriteframe_t *pspriteframe = NULL;
|
||||
float *pintervals, fullinterval;
|
||||
int i, numframes;
|
||||
float targettime;
|
||||
|
||||
if( !pModel )
|
||||
return NULL;
|
||||
@@ -61,14 +57,15 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
|
||||
}
|
||||
else if( psprite->frames[frame].type == SPR_GROUP )
|
||||
{
|
||||
pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
|
||||
pintervals = pspritegroup->intervals;
|
||||
numframes = pspritegroup->numframes;
|
||||
fullinterval = pintervals[numframes - 1];
|
||||
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
|
||||
targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval;
|
||||
float targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval;
|
||||
int i;
|
||||
|
||||
for( i = 0; i < ( numframes - 1 ); i++ )
|
||||
{
|
||||
@@ -82,7 +79,7 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
|
||||
int angleframe = (int)( Q_rint(( refState.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;
|
||||
|
||||
// e.g. doom-style sprite monsters
|
||||
pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
|
||||
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
|
||||
pspriteframe = pspritegroup->frames[angleframe];
|
||||
}
|
||||
|
||||
@@ -203,16 +200,13 @@ static const byte *Mod_SpriteLoadFrame( model_t *mod, const void *pin, mspritefr
|
||||
|
||||
static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )
|
||||
{
|
||||
const dspritegroup_t *pingroup;
|
||||
const dspritegroup_t *pingroup = (const dspritegroup_t *)pin;
|
||||
mspritegroup_t *pspritegroup;
|
||||
const dspriteinterval_t *pin_intervals;
|
||||
float *poutintervals;
|
||||
int i, groupsize, numframes;
|
||||
int groupsize, numframes = pingroup->numframes;
|
||||
const void *ptemp;
|
||||
|
||||
pingroup = (const dspritegroup_t *)pin;
|
||||
numframes = pingroup->numframes;
|
||||
|
||||
groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] );
|
||||
pspritegroup = Mem_Calloc( mod->mempool, groupsize );
|
||||
pspritegroup->numframes = numframes;
|
||||
@@ -222,7 +216,7 @@ static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspritefr
|
||||
poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));
|
||||
pspritegroup->intervals = poutintervals;
|
||||
|
||||
for( i = 0; i < numframes; i++ )
|
||||
for( int i = 0; i < numframes; i++ )
|
||||
{
|
||||
*poutintervals = pin_intervals->interval;
|
||||
if( *poutintervals <= 0.0f )
|
||||
@@ -232,7 +226,7 @@ static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspritefr
|
||||
}
|
||||
|
||||
ptemp = (const void *)pin_intervals;
|
||||
for( i = 0; i < numframes; i++ )
|
||||
for( int i = 0; i < numframes; i++ )
|
||||
{
|
||||
ptemp = Mod_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );
|
||||
}
|
||||
@@ -330,13 +324,12 @@ void Mod_SpriteLoadTextures( model_t *mod, const void *buffer )
|
||||
void Mod_SpriteUnloadTextures( void *data )
|
||||
{
|
||||
msprite_t *psprite = data;
|
||||
int i;
|
||||
|
||||
if( !data )
|
||||
return;
|
||||
|
||||
// release all textures
|
||||
for( i = 0; i < psprite->numframes; i++ )
|
||||
for( int i = 0; i < psprite->numframes; i++ )
|
||||
{
|
||||
if( !psprite->frames[i].frameptr )
|
||||
continue;
|
||||
@@ -348,9 +341,8 @@ void Mod_SpriteUnloadTextures( void *data )
|
||||
else
|
||||
{
|
||||
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;
|
||||
int j;
|
||||
|
||||
for( j = 0; j < pspritegroup->numframes; j++ )
|
||||
for( int j = 0; j < pspritegroup->numframes; j++ )
|
||||
{
|
||||
if( pspritegroup->frames[j] )
|
||||
ref.dllFuncs.GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );
|
||||
|
||||
@@ -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.
|
||||
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
|
||||
|
||||
static struct
|
||||
// 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_PRIVILEGED|FCVAR_ARCHIVE, "address of steam broker instance" );
|
||||
|
||||
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, sizeof( 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, sizeof( 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 ), 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, sizeof( 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 );
|
||||
}
|
||||
|
||||
@@ -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,14 +1854,18 @@ 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 );
|
||||
MSG_ReadBytes( msg, msg_data, sizeof( msg_data ), iSize );
|
||||
|
||||
// init a safe tempbuffer
|
||||
MSG_Init( &buf, "TempEntity", 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;
|
||||
|
||||
@@ -192,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 );
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -500,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 );
|
||||
|
||||
@@ -560,6 +554,7 @@ void V_PostRender( void )
|
||||
SCR_DrawNetGraph();
|
||||
SCR_DrawUserCmd();
|
||||
Joy_DrawDebug();
|
||||
IN_GyroDrawDebug();
|
||||
SV_DrawOrthoTriangles();
|
||||
CL_DrawDemoRecording();
|
||||
CL_DrawHUD( CL_CHANGELEVEL );
|
||||
|
||||
@@ -579,6 +579,7 @@ typedef struct
|
||||
sizebuf_t datagram; // unreliable stuff. gets sent in CL_Move about cl_cmdrate times per second.
|
||||
byte datagram_buf[MAX_DATAGRAM];
|
||||
|
||||
uint64_t netchan_pending_cookie; // random NET_EXT_NETCHAN_COOKIE
|
||||
netchan_t netchan;
|
||||
|
||||
float packet_loss;
|
||||
@@ -644,7 +645,6 @@ typedef struct
|
||||
qboolean internetservers_pending; // if true, clean master server pings
|
||||
qboolean internetservers_nat;
|
||||
string internetservers_customfilter;
|
||||
uint32_t internetservers_key; // compare key to validate master server reply
|
||||
|
||||
// multiprotocol support
|
||||
connprotocol_t legacymode;
|
||||
@@ -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
|
||||
@@ -791,6 +792,7 @@ void CL_SignonReply( connprotocol_t proto );
|
||||
void CL_ClearState( void );
|
||||
void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats );
|
||||
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen );
|
||||
void CL_NotifyServerListResponse( void );
|
||||
|
||||
//
|
||||
// cl_demo.c
|
||||
@@ -1153,7 +1155,7 @@ void S_StopStreaming( void );
|
||||
void S_BeginRegistration( void );
|
||||
sound_t S_RegisterSound( const char *sample );
|
||||
void S_EndRegistration( void );
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex );
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, uint wordIndex );
|
||||
void S_StartSound( const vec3_t pos, int ent, int chan, sound_t sfx, float vol, float attn, int pitch, int flags );
|
||||
void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags );
|
||||
void S_SoundFade( int fade_percent, int hold_time, int fade_out_seconds, int fade_in_seconds );
|
||||
@@ -1207,8 +1209,7 @@ 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 );
|
||||
|
||||
//
|
||||
@@ -1241,7 +1242,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 );
|
||||
|
||||
//
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
@@ -2224,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;
|
||||
|
||||
@@ -2311,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 );
|
||||
}
|
||||
@@ -2344,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;
|
||||
}
|
||||
@@ -2399,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 );
|
||||
@@ -2409,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] );
|
||||
@@ -2418,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 );
|
||||
|
||||
@@ -172,8 +172,6 @@ Initialize CD playlist
|
||||
*/
|
||||
static void CL_InitCDAudio( const char *filename )
|
||||
{
|
||||
byte *afile;
|
||||
char *pfile;
|
||||
string token;
|
||||
int c = 0;
|
||||
|
||||
@@ -183,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 )
|
||||
@@ -318,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;
|
||||
@@ -369,7 +365,6 @@ 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 )
|
||||
{
|
||||
@@ -382,7 +377,7 @@ static void SPR_DrawGeneric( int frame, float x, float y, float width, float hei
|
||||
height = h;
|
||||
}
|
||||
|
||||
texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame );
|
||||
int texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame );
|
||||
|
||||
if( prc )
|
||||
{
|
||||
@@ -430,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;
|
||||
@@ -446,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 )
|
||||
{
|
||||
@@ -466,6 +458,7 @@ void CL_DrawCenterPrint( void )
|
||||
continue;
|
||||
|
||||
line[lineLength] = number;
|
||||
int charWidth;
|
||||
CL_DrawCharacterLen( font, number, &charWidth, NULL );
|
||||
width += charWidth;
|
||||
lineLength++;
|
||||
@@ -477,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 );
|
||||
@@ -534,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;
|
||||
@@ -619,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 ))
|
||||
{
|
||||
@@ -632,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);
|
||||
@@ -680,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 )
|
||||
{
|
||||
@@ -693,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 );
|
||||
@@ -740,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;
|
||||
|
||||
@@ -758,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 )
|
||||
{
|
||||
@@ -946,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;
|
||||
}
|
||||
@@ -1072,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;
|
||||
@@ -1212,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 );
|
||||
@@ -1243,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;
|
||||
|
||||
@@ -1280,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 ))
|
||||
{
|
||||
@@ -1310,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++ )
|
||||
{
|
||||
@@ -1356,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
|
||||
@@ -1374,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
|
||||
@@ -1390,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;
|
||||
@@ -1868,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] )
|
||||
@@ -1879,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;
|
||||
@@ -1910,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 );
|
||||
@@ -2200,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 )
|
||||
{
|
||||
@@ -2216,7 +2189,7 @@ static void GAME_EXPORT pfnCalcShake( void )
|
||||
return;
|
||||
}
|
||||
|
||||
frametime = cl_clientframetime();
|
||||
float frametime = cl_clientframetime();
|
||||
|
||||
if( cl.time > shake->next_shake )
|
||||
{
|
||||
@@ -2224,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
|
||||
@@ -2349,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;
|
||||
@@ -2360,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 )
|
||||
{
|
||||
@@ -2395,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;
|
||||
@@ -2403,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;
|
||||
@@ -2735,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;
|
||||
@@ -2813,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 ))
|
||||
{
|
||||
@@ -2899,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;
|
||||
@@ -3052,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;
|
||||
@@ -3236,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 );
|
||||
}
|
||||
@@ -3287,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;
|
||||
@@ -3324,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 = R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
|
||||
if( gl_texturenum <= 0 )
|
||||
return 0;
|
||||
|
||||
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );
|
||||
@@ -3467,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 )
|
||||
{
|
||||
@@ -3498,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;
|
||||
@@ -3614,6 +3568,16 @@ static void GAME_EXPORT VGui_ViewportPaintBackground( int extents[4] )
|
||||
// stub
|
||||
}
|
||||
|
||||
static cvar_t* GAME_EXPORT CL_CvarGetPointer( const char *szVarName )
|
||||
{
|
||||
cvar_t *result = (cvar_t *)Cvar_FindVar( szVarName );
|
||||
|
||||
if( !result )
|
||||
Con_DPrintf( S_WARN "%s: client tried to get non-existent cvar \"%s\"\n", __func__, szVarName );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// shared between client and server
|
||||
triangleapi_t gTriApi;
|
||||
|
||||
@@ -3836,7 +3800,7 @@ static cl_enginefunc_t gEngfuncs =
|
||||
pfnHookEvent,
|
||||
Con_Visible,
|
||||
pfnGetGameDirectory,
|
||||
pfnCVarGetPointer,
|
||||
CL_CvarGetPointer,
|
||||
Key_LookupBinding,
|
||||
pfnGetLevelName,
|
||||
pfnGetScreenFade,
|
||||
@@ -3970,7 +3934,7 @@ static engine_studio_api_t gStudioAPI =
|
||||
.Mod_ForName = pfnStudio_Mod_ForName,
|
||||
.Mod_Extradata = pfnStudio_Mod_Extradata,
|
||||
.GetModelByIndex = CL_ModelHandle,
|
||||
.GetCvar = pfnCVarGetPointer,
|
||||
.GetCvar = CL_CvarGetPointer,
|
||||
.GetChromeSprite = pfnStudio_GetChromeSprite,
|
||||
.GetAliasScale = pfnStudio_GetAliasScale,
|
||||
.StudioGetAliasTransform = pfnStudio_GetAliasTransform,
|
||||
@@ -3981,12 +3945,11 @@ static engine_studio_api_t gStudioAPI =
|
||||
static void CL_InitStudioAPI( void )
|
||||
{
|
||||
static r_studio_interface_t gDefaultStudioDraw;
|
||||
r_studio_interface_t *pStudioDraw;
|
||||
|
||||
if( !ref.dllFuncs.R_StudioFillAPI( &gStudioAPI, &gDefaultStudioDraw ))
|
||||
return;
|
||||
|
||||
pStudioDraw = &gDefaultStudioDraw;
|
||||
r_studio_interface_t *pStudioDraw = &gDefaultStudioDraw;
|
||||
|
||||
if( clgame.dllFuncs.pfnGetStudioModelInterface )
|
||||
clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI );
|
||||
@@ -4001,7 +3964,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();
|
||||
|
||||
@@ -4057,7 +4019,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'
|
||||
@@ -4088,7 +4050,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'
|
||||
@@ -4132,6 +4094,7 @@ qboolean CL_LoadProgs( const char *name )
|
||||
clgame.dllFuncs.pfnInit();
|
||||
|
||||
CL_InitStudioAPI();
|
||||
S_InitSoundAPI();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
@@ -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:
|
||||
@@ -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
|
||||
@@ -496,61 +491,57 @@ static void CL_FillTriAPI( triangleapi_t *dst )
|
||||
ref.dllFuncs.R_FillTriAPI( dst );
|
||||
}
|
||||
|
||||
static const byte dottexture[8][8] =
|
||||
{
|
||||
{ 0, 1, 1, 0, 0, 0, 0, 0 },
|
||||
{ 1, 1, 1, 1, 0, 0, 0, 0 },
|
||||
{ 1, 1, 1, 1, 0, 0, 0, 0 },
|
||||
{ 0, 1, 1, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
static void R_CreateBuiltinTextures( void )
|
||||
{
|
||||
uint data4x4[16];
|
||||
uint data16x16[256];
|
||||
byte particle[8 * 8 * 4];
|
||||
int x, y;
|
||||
|
||||
// default checkerboard
|
||||
for( y = 0; y < 16; y++ )
|
||||
uint data16x16[256];
|
||||
for( int y = 0; y < 16; y++ )
|
||||
{
|
||||
for( x = 0; x < 16; x++ )
|
||||
for( int x = 0; x < 16; x++ )
|
||||
{
|
||||
if(( y < 8 ) ^ ( x < 8 ))
|
||||
data16x16[y * 16 + x] = 0xFFFF00FF;
|
||||
data16x16[y * 16 + x] = HostFourCC( 255, 0, 255, 255 );
|
||||
else
|
||||
data16x16[y * 16 + x] = 0xFF000000;
|
||||
data16x16[y * 16 + x] = HostFourCC( 0, 0, 0, 255 );
|
||||
}
|
||||
}
|
||||
ref.dllFuncs.GL_CreateTexture( REF_DEFAULT_TEXTURE, 16, 16, data16x16, TF_COLORMAP );
|
||||
ref.dllFuncs.GL_CreateTexture( REF_DEFAULT_TEXTURE, 16, 16, data16x16, 0 );
|
||||
|
||||
// particle texture
|
||||
memset( particle, 0, sizeof( particle ));
|
||||
for( x = 0; x < 8; x++ )
|
||||
uint particle[64];
|
||||
for( int y = 0; y < 8; y++ )
|
||||
{
|
||||
for( 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( dottexture[x][y] )
|
||||
particle[( y * 8 + x ) * 4 + 3] = 255;
|
||||
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 );
|
||||
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, TF_COLORMAP );
|
||||
ref.dllFuncs.GL_CreateTexture( REF_WHITE_TEXTURE, 4, 4, data4x4, 0 );
|
||||
|
||||
for( x = 0; x < 16; x++ )
|
||||
data4x4[x] = 0xFF7F7F7F;
|
||||
ref.dllFuncs.GL_CreateTexture( REF_GRAY_TEXTURE, 4, 4, data4x4, TF_COLORMAP );
|
||||
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( x = 0; x < 16; x++ )
|
||||
data4x4[x] = 0xFF000000;
|
||||
ref.dllFuncs.GL_CreateTexture( REF_BLACK_TEXTURE, 4, 4, data4x4, TF_COLORMAP );
|
||||
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 )
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ 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 );
|
||||
void IN_GyroDrawDebug( void );
|
||||
|
||||
uint IN_CollectInputDevices( void );
|
||||
void IN_LockInputDevices( qboolean lock );
|
||||
@@ -95,6 +100,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
|
||||
//
|
||||
|
||||
214
engine/client/input/in_gyro.c
Normal file
214
engine/client/input/in_gyro.c
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
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)" );
|
||||
static CVAR_DEFINE_AUTO( gyro_debug, "0", 0, "visualize built-in device gyroscope" );
|
||||
|
||||
// stores the latest instantaneous rotation rates from built-in gyroscope
|
||||
static vec3_t gyro_speed;
|
||||
static vec3_t gyro_speed_display;
|
||||
|
||||
/*
|
||||
==============
|
||||
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 );
|
||||
Cvar_RegisterVariable( &gyro_debug );
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
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 );
|
||||
}
|
||||
|
||||
static void IN_GyroMap( const vec3_t src, vec3_t dst )
|
||||
{
|
||||
float orient_scale = 1.0f;
|
||||
|
||||
platform_orientation_t orient = Platform_GetDisplayOrientation();
|
||||
if( orient == ORIENTATION_LANDSCAPE_FLIPPED )
|
||||
orient_scale = -1.0f;
|
||||
|
||||
#if XASH_ANDROID || XASH_IOS
|
||||
// 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)
|
||||
dst[0] = -orient_scale * src[1] * ( 180.0f / M_PI );
|
||||
dst[1] = orient_scale * src[0] * ( 180.0f / M_PI );
|
||||
dst[2] = orient_scale * src[2] * ( 180.0f / M_PI );
|
||||
#else
|
||||
dst[0] = orient_scale * src[0] * ( 180.0f / M_PI );
|
||||
dst[1] = orient_scale * src[1] * ( 180.0f / M_PI );
|
||||
dst[2] = orient_scale * src[2] * ( 180.0f / M_PI );
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
IN_GyroFinalizeMove
|
||||
|
||||
Apply gyro movement to view angles
|
||||
=============
|
||||
*/
|
||||
void IN_GyroFinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
||||
{
|
||||
vec3_t mapped_speed;
|
||||
|
||||
if( !gyro_enable.value || !gyro_available.value )
|
||||
return;
|
||||
|
||||
IN_GyroMap( gyro_speed, mapped_speed );
|
||||
VectorCopy( mapped_speed, gyro_speed_display );
|
||||
|
||||
if( fabs( mapped_speed[0] ) < gyro_pitch_deadzone.value )
|
||||
mapped_speed[0] = 0.0f;
|
||||
if( fabs( mapped_speed[1] ) < gyro_yaw_deadzone.value )
|
||||
mapped_speed[1] = 0.0f;
|
||||
if( fabs( mapped_speed[2] ) < gyro_roll_deadzone.value )
|
||||
mapped_speed[2] = 0.0f;
|
||||
|
||||
*dpitch -= gyro_pitch.value * mapped_speed[0] * host.realframetime;
|
||||
*dyaw += gyro_yaw.value * mapped_speed[1] * host.realframetime;
|
||||
*dyaw += gyro_roll.value * mapped_speed[2] * host.realframetime;
|
||||
|
||||
VectorClear( gyro_speed );
|
||||
}
|
||||
|
||||
void IN_GyroDrawDebug( void )
|
||||
{
|
||||
cl_font_t *font = Con_GetCurFont();
|
||||
|
||||
if( !gyro_debug.value || !gyro_available.value )
|
||||
return;
|
||||
|
||||
float x = 8;
|
||||
float y = 100;
|
||||
const float bar_w = 100;
|
||||
const float halfbar_w = bar_w * 0.5f;
|
||||
const float bar_h = font->charHeight - 2;
|
||||
const float center = x + halfbar_w;
|
||||
|
||||
platform_orientation_t orient = Platform_GetDisplayOrientation();
|
||||
const char *orient_name = "UNKNOWN";
|
||||
switch( orient )
|
||||
{
|
||||
case ORIENTATION_LANDSCAPE:
|
||||
orient_name = "LANDSCAPE";
|
||||
break;
|
||||
case ORIENTATION_LANDSCAPE_FLIPPED:
|
||||
orient_name = "LANDSCAPE FLIPPED";
|
||||
break;
|
||||
case ORIENTATION_PORTRAIT:
|
||||
orient_name = "PORTRAIT";
|
||||
break;
|
||||
case ORIENTATION_PORTRAIT_FLIPPED:
|
||||
orient_name = "PORTRAIT FLIPPED";
|
||||
break;
|
||||
default:
|
||||
orient_name = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
char orient_text[32];
|
||||
Q_snprintf( orient_text, sizeof( orient_text ), "ORIENT: %s", orient_name );
|
||||
|
||||
const rgba_t bar_backcolor = { 40, 40, 40, 180 };
|
||||
const rgba_t bar_fillcolor = { 100, 200, 100, 200 };
|
||||
static const char *gyronames[3] = { "GYRO P", "GYRO Y", "GYRO R" };
|
||||
static const convar_t *const gyro_deadzone[3] =
|
||||
{
|
||||
&gyro_pitch_deadzone,
|
||||
&gyro_yaw_deadzone,
|
||||
&gyro_roll_deadzone,
|
||||
};
|
||||
|
||||
for( int i = 0; i < 3; i++ )
|
||||
{
|
||||
float fval = gyro_speed_display[i] / 180.0f;
|
||||
fval = bound( -1.0f, fval, 1.0f );
|
||||
|
||||
CL_DrawString( x, y, gyronames[i], g_color_table[7], font, 0 );
|
||||
y += font->charHeight;
|
||||
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, bar_w, bar_h, bar_backcolor[0], bar_backcolor[1], bar_backcolor[2], bar_backcolor[3] );
|
||||
|
||||
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] );
|
||||
|
||||
float fthreshold = gyro_deadzone[i]->value / 180.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 );
|
||||
}
|
||||
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 );
|
||||
y += bar_h + 2;
|
||||
}
|
||||
|
||||
CL_DrawString( x, y, orient_text, g_color_table[1], font, 0 );
|
||||
}
|
||||
@@ -68,6 +68,7 @@ static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE,
|
||||
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
|
||||
@@ -128,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 )
|
||||
{
|
||||
@@ -197,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;
|
||||
@@ -316,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] )
|
||||
{
|
||||
@@ -340,7 +339,7 @@ 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 );
|
||||
@@ -534,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 ));
|
||||
@@ -544,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;
|
||||
}
|
||||
@@ -596,6 +596,7 @@ 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 );
|
||||
@@ -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 );
|
||||
}
|
||||
310
engine/client/input/in_osk.c
Normal file
310
engine/client/input/in_osk.c
Normal 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 );
|
||||
}
|
||||
@@ -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 )
|
||||
{
|
||||
@@ -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 );
|
||||
@@ -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 );
|
||||
|
||||
@@ -60,35 +60,33 @@ 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;
|
||||
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 );
|
||||
|
||||
sound_t handle = 0;
|
||||
if( FBitSet( flags, SND_SENTENCE ))
|
||||
{
|
||||
char sentenceName[32];
|
||||
@@ -101,13 +99,15 @@ static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
|
||||
}
|
||||
else handle = cl.sound_index[sound]; // see precached sound
|
||||
|
||||
uint wordIndex = 0;
|
||||
double samplePos = 0, forcedEnd = 0;
|
||||
if( restore )
|
||||
{
|
||||
wordIndex = MSG_ReadByte( msg );
|
||||
|
||||
// 16 bytes here
|
||||
MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ));
|
||||
MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ));
|
||||
MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ), sizeof( samplePos ));
|
||||
MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ), sizeof( forcedEnd ));
|
||||
}
|
||||
|
||||
if( !cl.audio_prepped )
|
||||
@@ -129,8 +129,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 );
|
||||
|
||||
@@ -140,7 +138,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
|
||||
{
|
||||
@@ -212,29 +210,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;
|
||||
@@ -256,25 +251,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
|
||||
@@ -315,7 +308,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;
|
||||
@@ -339,20 +332,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 );
|
||||
}
|
||||
@@ -381,11 +372,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 )
|
||||
{
|
||||
@@ -404,17 +393,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;
|
||||
|
||||
@@ -491,10 +479,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 )
|
||||
{
|
||||
@@ -570,9 +557,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;
|
||||
@@ -582,10 +567,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;
|
||||
|
||||
@@ -619,17 +602,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 ));
|
||||
@@ -639,7 +616,7 @@ static void CL_ParseCustomization( sizebuf_t *msg )
|
||||
pRes->pNext = pRes->pPrev = NULL;
|
||||
|
||||
if( FBitSet( pRes->ucFlags, RES_CUSTOM ))
|
||||
MSG_ReadBytes( msg, pRes->rgucMD5_hash, 16 );
|
||||
MSG_ReadBytes( msg, pRes->rgucMD5_hash, sizeof( pRes->rgucMD5_hash ), 16 );
|
||||
pRes->playernum = i;
|
||||
|
||||
if( !cl_allow_download.value )
|
||||
@@ -656,13 +633,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 ))
|
||||
{
|
||||
@@ -673,11 +650,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;
|
||||
@@ -705,45 +682,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 );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -755,16 +730,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 );
|
||||
@@ -842,7 +813,7 @@ static void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto )
|
||||
byte clientdllmd5[16];
|
||||
const char *s;
|
||||
|
||||
MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ));
|
||||
MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ), sizeof( clientdllmd5 ));
|
||||
cl.maxclients = MSG_ReadByte( msg );
|
||||
cl.playernum = MSG_ReadByte( msg );
|
||||
COM_UnMunge3((byte *)&cl.checksum, sizeof( cl.checksum ), ( 0xff - cl.playernum ) & 0xff );
|
||||
@@ -890,6 +861,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 ))
|
||||
{
|
||||
@@ -1237,12 +1217,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 );
|
||||
|
||||
@@ -1270,10 +1248,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;
|
||||
@@ -1283,7 +1258,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];
|
||||
@@ -1313,20 +1288,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 );
|
||||
}
|
||||
}
|
||||
@@ -1340,10 +1313,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 )
|
||||
{
|
||||
@@ -1358,9 +1330,10 @@ 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 );
|
||||
MSG_ReadBytes( msg, szName, sizeof( szName ), sizeof( szName ) - 1 );
|
||||
szName[16] = 0;
|
||||
pszName = szName;
|
||||
}
|
||||
@@ -1378,9 +1351,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 )
|
||||
{
|
||||
@@ -1398,7 +1370,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 )
|
||||
{
|
||||
@@ -1408,7 +1380,7 @@ static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
|
||||
player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
|
||||
player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
|
||||
player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" ));
|
||||
MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ));
|
||||
MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ), sizeof( player->hashedcdkey ));
|
||||
|
||||
if( proto == PROTO_GOLDSRC && ( COM_StringEmpty( player->userinfo ) || COM_StringEmpty( player->name )))
|
||||
active = false;
|
||||
@@ -1439,9 +1411,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 ));
|
||||
@@ -1450,36 +1420,36 @@ void CL_ParseResource( sizebuf_t *msg )
|
||||
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
|
||||
|
||||
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
|
||||
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
|
||||
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ), sizeof( pResource->rgucMD5_hash ));
|
||||
|
||||
if( MSG_ReadOneBit( msg ))
|
||||
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
|
||||
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( 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" );
|
||||
@@ -1501,13 +1471,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 ))
|
||||
{
|
||||
@@ -1515,7 +1482,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 );
|
||||
}
|
||||
@@ -1540,18 +1507,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 );
|
||||
@@ -1563,9 +1526,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 );
|
||||
|
||||
@@ -1644,10 +1609,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 );
|
||||
@@ -1663,11 +1627,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;
|
||||
|
||||
@@ -1779,20 +1742,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;
|
||||
@@ -1806,19 +1761,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 );
|
||||
@@ -1834,7 +1790,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 ));
|
||||
@@ -1866,14 +1822,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 ));
|
||||
@@ -1882,10 +1835,10 @@ void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto )
|
||||
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
|
||||
|
||||
if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
|
||||
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
|
||||
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ), sizeof( pResource->rgucMD5_hash ));
|
||||
|
||||
if( MSG_ReadOneBit( msg ))
|
||||
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
|
||||
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), sizeof( pResource->rguc_reserved ));
|
||||
|
||||
CL_AddToResourceList( pResource, &cl.resourcesneeded );
|
||||
}
|
||||
@@ -1954,7 +1907,7 @@ static void CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto )
|
||||
if ( !size )
|
||||
return;
|
||||
|
||||
MSG_ReadBytes( msg, received, size );
|
||||
MSG_ReadBytes( msg, received, sizeof( received ), size );
|
||||
|
||||
Voice_AddIncomingData( idx, received, size, frames );
|
||||
}
|
||||
@@ -2034,7 +1987,7 @@ static void CL_ParseDirector( sizebuf_t *msg )
|
||||
byte pbuf[256];
|
||||
|
||||
// parse user message into buffer
|
||||
MSG_ReadBytes( msg, pbuf, iSize );
|
||||
MSG_ReadBytes( msg, pbuf, sizeof( pbuf ), iSize );
|
||||
clgame.dllFuncs.pfnDirectorMessage( iSize, pbuf );
|
||||
}
|
||||
|
||||
@@ -2070,14 +2023,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 );
|
||||
@@ -2186,9 +2137,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",
|
||||
@@ -2204,17 +2152,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 ));
|
||||
@@ -2322,7 +2271,7 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto )
|
||||
}
|
||||
|
||||
// parse user message into buffer
|
||||
MSG_ReadBytes( msg, pbuf, iSize );
|
||||
MSG_ReadBytes( msg, pbuf, sizeof( pbuf ), iSize );
|
||||
|
||||
if( cl_trace_messages.value )
|
||||
{
|
||||
@@ -2368,17 +2317,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
|
||||
@@ -2590,9 +2537,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 )
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 );
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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 ));
|
||||
|
||||
@@ -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;
|
||||
@@ -746,7 +740,7 @@ S_RestoreSound
|
||||
Restore a sound effect for the given entity on the given channel
|
||||
====================
|
||||
*/
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex )
|
||||
void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, uint wordIndex )
|
||||
{
|
||||
wavdata_t *pSource;
|
||||
sfx_t *sfx = NULL;
|
||||
@@ -810,15 +804,30 @@ void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float
|
||||
// not a first word in sentence!
|
||||
if( wordIndex != 0 )
|
||||
{
|
||||
VOX_FreeWord( target_chan ); // release first loaded word
|
||||
target_chan->word_index = wordIndex; // restore current word
|
||||
VOX_LoadWord( target_chan );
|
||||
uint word_count = 0;
|
||||
|
||||
if( !FBitSet( target_chan->flags, FL_CHAN_SENTENCE_FINISHED ))
|
||||
if( target_chan->words )
|
||||
{
|
||||
target_chan->sfx = target_chan->words[target_chan->word_index].sfx;
|
||||
sfx = target_chan->sfx;
|
||||
pSource = sfx->cache;
|
||||
while( word_count < CVOXWORDMAX && target_chan->words[word_count].sfx )
|
||||
word_count++;
|
||||
}
|
||||
|
||||
if( wordIndex >= word_count )
|
||||
{
|
||||
SetBits( target_chan->flags, FL_CHAN_SENTENCE_FINISHED );
|
||||
}
|
||||
else
|
||||
{
|
||||
VOX_FreeWord( target_chan ); // release first loaded word
|
||||
target_chan->word_index = wordIndex; // restore current word
|
||||
VOX_LoadWord( target_chan );
|
||||
|
||||
if( !FBitSet( target_chan->flags, FL_CHAN_SENTENCE_FINISHED ))
|
||||
{
|
||||
target_chan->sfx = target_chan->words[target_chan->word_index].sfx;
|
||||
sfx = target_chan->sfx;
|
||||
pSource = sfx->cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -874,7 +883,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 +920,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 +1028,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 +1093,6 @@ S_UpdateAmbientSounds
|
||||
*/
|
||||
static void S_UpdateAmbientSounds( void )
|
||||
{
|
||||
int ambient_channel;
|
||||
|
||||
if( !snd.have_ambient_sfx )
|
||||
return;
|
||||
|
||||
@@ -1117,7 +1123,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 +1229,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 +1245,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 +1254,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 +1268,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 +1277,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;
|
||||
}
|
||||
@@ -1312,9 +1317,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 +1350,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 +1468,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 +1505,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 +1777,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 +1860,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 )
|
||||
{
|
||||
@@ -1948,7 +1945,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 ));
|
||||
@@ -1967,6 +1964,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;
|
||||
}
|
||||
|
||||
@@ -2030,17 +2030,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;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user