mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 11:35:05 +08:00
Compare commits
414 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3366ad06d0 | ||
|
|
57206b3651 | ||
|
|
d9ecd03b4a | ||
|
|
2de4d0d364 | ||
|
|
cc3889146c | ||
|
|
09f5886751 | ||
|
|
fb5b4fed6e | ||
|
|
1e31fbcd51 | ||
|
|
b2a5f0db5a | ||
|
|
abd7b5ccdd | ||
|
|
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 | ||
|
|
74869c8f5d | ||
|
|
dd712a0df9 | ||
|
|
bfdb62d31e | ||
|
|
ae7b9eb28d | ||
|
|
c63e13c8f4 | ||
|
|
d3e1407675 | ||
|
|
e2e4728c7c | ||
|
|
dd3583d11a | ||
|
|
e6924844da | ||
|
|
a49db03561 | ||
|
|
84b7b5803b | ||
|
|
c224d39ba7 | ||
|
|
9b24b08df1 | ||
|
|
a37b0e58f1 | ||
|
|
afca422e35 | ||
|
|
3440ddf443 | ||
|
|
6730ed9555 | ||
|
|
fdd3a44fa0 | ||
|
|
241c926076 | ||
|
|
148a9a5a8c | ||
|
|
513984d7fd | ||
|
|
1009795d5c | ||
|
|
e3d2da74db | ||
|
|
ff3a57ae8a | ||
|
|
271a6e2210 | ||
|
|
0d40357b93 | ||
|
|
a9dcdd034c | ||
|
|
7324ff43fc | ||
|
|
d85e7fc2b1 | ||
|
|
d91ecfa3df | ||
|
|
b7a3a02aad | ||
|
|
5aca78951c | ||
|
|
81abae65b5 | ||
|
|
f5f6d9bbe4 | ||
|
|
ef73b55413 | ||
|
|
8334695522 | ||
|
|
d7f416d659 | ||
|
|
5f6577df2e | ||
|
|
e87f307ac1 | ||
|
|
7e015a268b | ||
|
|
a679c53111 | ||
|
|
9e8d248044 | ||
|
|
055d646225 | ||
|
|
a2514b6916 | ||
|
|
0ac5e78040 | ||
|
|
a82221e3df | ||
|
|
bb734456f6 | ||
|
|
a219ca7371 | ||
|
|
c3b5e43a7a | ||
|
|
e7597d2e84 | ||
|
|
386ca1b9bb | ||
|
|
da3960c4f0 | ||
|
|
cd85762b09 | ||
|
|
d92b3a730c | ||
|
|
ffa53f14e5 | ||
|
|
b8bd906832 | ||
|
|
1c73d8d6f4 | ||
|
|
2c3fdb5eee | ||
|
|
4bbe52e3b4 | ||
|
|
2d241ed96b | ||
|
|
ba4bce06b1 | ||
|
|
f7e933aea1 | ||
|
|
dbcf77c9a9 | ||
|
|
8a9aa2da6c | ||
|
|
0e8143aefc | ||
|
|
4345e9e6a6 | ||
|
|
e7fa45d5ca | ||
|
|
abda0a07c2 | ||
|
|
9d22827395 | ||
|
|
7b665b3763 | ||
|
|
58692f397a | ||
|
|
2253fcc955 | ||
|
|
cbbae0c9bf | ||
|
|
ef513f27ac | ||
|
|
e8ae48fd3d | ||
|
|
434e508186 | ||
|
|
4a2b3e89b4 | ||
|
|
6c3f8c7ec5 | ||
|
|
3fffe061d5 | ||
|
|
729c985eaf | ||
|
|
75fb6c5f34 | ||
|
|
8bc9b1865a | ||
|
|
d6993c47d6 | ||
|
|
b8792e4084 | ||
|
|
2234dd48bc | ||
|
|
c8693e1e89 | ||
|
|
3ace4a61d2 | ||
|
|
bd3f865cf7 | ||
|
|
b68e71abfd | ||
|
|
6179bef160 | ||
|
|
983427ebb3 | ||
|
|
1b2ab34745 | ||
|
|
2713f03ec2 | ||
|
|
238083c174 | ||
|
|
78de3ee6d9 | ||
|
|
695081e079 | ||
|
|
2c5e2e1ded | ||
|
|
d151c48d1f | ||
|
|
35998bce81 | ||
|
|
a10520ca9e | ||
|
|
cb4a03ded2 | ||
|
|
31a0d17de5 | ||
|
|
5f765a35b9 | ||
|
|
5606346cc1 | ||
|
|
6faee8fd89 | ||
|
|
255c9bcaed | ||
|
|
25a3fdbcec | ||
|
|
8e450d869b | ||
|
|
e8ef7ad60c | ||
|
|
216eedcdcd | ||
|
|
fd55716633 | ||
|
|
0b920be381 | ||
|
|
e6dfccb6ba | ||
|
|
3129081050 | ||
|
|
00dac928c9 | ||
|
|
3726256514 | ||
|
|
5eb43505cb |
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
|
||||
3
.github/workflows/c-cpp.yml
vendored
3
.github/workflows/c-cpp.yml
vendored
@@ -69,7 +69,8 @@ jobs:
|
||||
targetarch: amd64
|
||||
env:
|
||||
SDL_VERSION: 2.32.10
|
||||
FFMPEG_VERSION: 7.1
|
||||
FFMPEG_VERSION: 8.1
|
||||
YY_THUNKS_VERSION: v1.2.1
|
||||
GH_CPU_ARCH: ${{ matrix.targetarch }}
|
||||
GH_CPU_OS: ${{ matrix.targetos }}
|
||||
GH_CROSSCOMPILING: ${{ matrix.cross }}
|
||||
|
||||
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: 5b670f638b...a35ac9c12e
2
3rdparty/mainui
vendored
2
3rdparty/mainui
vendored
Submodule 3rdparty/mainui updated: e198199f6c...493b08fb49
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...d2aaec107d
59
3rdparty/yy-thunks/wscript
vendored
Normal file
59
3rdparty/yy-thunks/wscript
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
#! /usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import hashlib
|
||||
from waflib import Task, TaskGen, Logs
|
||||
|
||||
OBJ_NAME = 'YY_Thunks_for_WinXP.obj'
|
||||
OBJ_SHA256 = '51a13c4b667c82181d357e274a50308a273be63f5f85ae958465774857721573'
|
||||
DLL_LINKFLAGS = ['-ENTRY:DllMainCRTStartupForYY_Thunks', '-alternatename:_YY_ThunksOriginalDllMainCRTStartup@12=__DllMainCRTStartup@12']
|
||||
|
||||
class copy_yy_thunks(Task.Task):
|
||||
color = 'CYAN'
|
||||
def run(self):
|
||||
return self.outputs[0].write(self.inputs[0].read('rb'), 'wb')
|
||||
|
||||
@TaskGen.feature('yy_thunks')
|
||||
def add_yy_thunks_obj(self):
|
||||
src = self.path.find_node(OBJ_NAME)
|
||||
out = self.path.find_or_declare(OBJ_NAME)
|
||||
self.compiled_tasks = [self.create_task('copy_yy_thunks', src, out)]
|
||||
|
||||
@TaskGen.feature('cshlib', 'cxxshlib')
|
||||
@TaskGen.after_method('process_use')
|
||||
def apply_yy_thunks_dll(self):
|
||||
if 'yy_thunks' not in self.to_list(getattr(self, 'use', [])):
|
||||
return
|
||||
|
||||
try:
|
||||
self.bld.get_tgen_by_name('yy_thunks')
|
||||
except Exception:
|
||||
return
|
||||
self.env.append_value('LINKFLAGS', DLL_LINKFLAGS)
|
||||
|
||||
def options(opt):
|
||||
pass
|
||||
|
||||
def configure(conf):
|
||||
if conf.env.DEST_OS != 'win32' or conf.env.COMPILER_CC != 'msvc':
|
||||
return
|
||||
|
||||
if conf.env.MSVC_TARGETS[0] not in ['amd64_x86', 'x86']:
|
||||
return
|
||||
|
||||
obj = conf.path.find_node(OBJ_NAME)
|
||||
if not obj:
|
||||
Logs.warn('YY-Thunks: %s not found, XP compatibility disabled' % OBJ_NAME)
|
||||
return
|
||||
|
||||
with open(obj.abspath(), 'rb') as f:
|
||||
digest = hashlib.sha256(f.read()).hexdigest()
|
||||
|
||||
if digest != OBJ_SHA256:
|
||||
conf.fatal('%s checksum mismatch: expected %s, got %s' % (OBJ_NAME, OBJ_SHA256, digest))
|
||||
|
||||
conf.env.YY_THUNKS = True
|
||||
|
||||
def build(bld):
|
||||
if bld.env.YY_THUNKS:
|
||||
bld(name='yy_thunks', features='yy_thunks')
|
||||
@@ -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. |
|
||||
|
||||
72
Documentation/protocol/http-server-list.md
Normal file
72
Documentation/protocol/http-server-list.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# 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, parsed line by line. Each line is tokenized with
|
||||
`COM_ParseFileSafe` (whitespace separates, `//` and `#` start line
|
||||
comments, `"..."` quotes a token). Blank lines and comment-only lines
|
||||
are ignored. One directive per line:
|
||||
|
||||
* `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`. Lines
|
||||
starting with an unknown directive are skipped entirely, so new keywords
|
||||
with any number of operands 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>`.
|
||||
@@ -1,3 +1,6 @@
|
||||
> [!CAUTION]
|
||||
> **Only download Xash3D FWGS from official sources.** Third-party builds, "modded launchers", "optimized" repacks and random mirrors are frequently bundled with malware, miners, spyware and credential stealers. We cannot vouch for anything we did not build. Get official binaries only from the [releases page](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous).
|
||||
|
||||
# Xash3D FWGS Engine <img align="right" width="128" height="128" src="https://github.com/FWGS/xash3d-fwgs/raw/master/game_launch/icon-xash-material.png" alt="Xash3D FWGS icon" />
|
||||
[](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
|
||||
[](http://xash.su/discord/) [](https://t.me/flyingwithgauss) \
|
||||
@@ -134,7 +137,7 @@ To build you should clone [SDL](https://github.com/libsdl-org/SDL) from `SDL2` b
|
||||
0) (optional) Examine which build options are available: `./waf --help`.
|
||||
1) Configure build: `./waf configure --ios --enable-bundled-deps --sdl2 (path/to/SDL2.framework)`, set `--ios-simulator` instead of `--ios` if you want to build for simulator.
|
||||
2) Compile `./waf build`.
|
||||
3) Navigate to `build` and copy your compiled SDL2.framework there, then add your client dylibs to `build/ios/cl_dlls` and any other dylibs to `build/ios/dlls` (You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
|
||||
3) Navigate to `build` and copy your compiled SDL2.framework there, then add your game dylibs to `build/ios/libs/(gamedir)/(dlls/cl_dlls)`(You can also run `scripts/ios/buildhlsdk.sh` instead to automatically create an ipa with hlsdk dylibs)
|
||||
4) Run `scripts/ios/createipa.sh` to create an installable ipa
|
||||
|
||||
### Running tests
|
||||
|
||||
3
android/.gitignore
vendored
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
|
||||
@@ -116,6 +117,8 @@ typedef enum
|
||||
IMAGE_MULTILAYER = BIT(8), // to differentiate from 3D texture
|
||||
IMAGE_ONEBIT_ALPHA = BIT(9), // binary alpha
|
||||
IMAGE_QUAKEPAL = BIT(10), // image has quake1 palette
|
||||
IMAGE_PREMULTIPLIED = BIT(11),
|
||||
IMAGE_PLAYERDECAL = BIT(12), // loaded with IL_LOAD_PLAYER_DECAL (custom player spray)
|
||||
|
||||
// Image_Process manipulation flags
|
||||
IMAGE_FLIP_X = BIT(16), // flip the image by width
|
||||
|
||||
@@ -612,9 +612,9 @@ static inline mnode_t *node_child( const mnode_t *n, int side, const model_t *mo
|
||||
}
|
||||
}
|
||||
|
||||
return n->children_[side];
|
||||
return n->children_[side ? 1 : 0];
|
||||
#else
|
||||
return n->children_[side];
|
||||
return n->children_[side ? 1 : 0];
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,15 @@ 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
|
||||
{
|
||||
int16_t lerp_msec; // added in HL
|
||||
int8_t msec; // added in QW
|
||||
uint8_t msec; // added in QW
|
||||
uint8_t pad1;
|
||||
vec3_t viewangles;
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ typedef enum
|
||||
TF_TEXTURE_3D = (1<<20), // this is GL_TEXTURE_3D
|
||||
TF_ATLAS_PAGE = (1<<21), // bit who indicate lightmap page or deluxemap page
|
||||
TF_ALPHACONTRAST = (1<<22), // special texture mode for A2C
|
||||
// reserved
|
||||
TF_PREMULTIPLIED = (1<<23), // RGBA was stored premultiplied (HL gradient / indexalpha)
|
||||
// reserved
|
||||
TF_IMG_UPLOADED = (1<<25), // this is set for first time when called glTexImage, otherwise it will be call glTexSubImage
|
||||
TF_ARB_FLOAT = (1<<26), // float textures
|
||||
|
||||
196
common/sound_api.h
Normal file
196
common/sound_api.h
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
sound_api.h - Xash3D extension for client sound interface
|
||||
Copyright (C) 2026 Xash3D FWGS Developers
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
*/
|
||||
|
||||
#ifndef SOUND_API_H
|
||||
#define SOUND_API_H
|
||||
|
||||
#include "xash3d_types.h"
|
||||
|
||||
// Experimental implementation, backward compatibility is not guaranteed.
|
||||
#define CL_SOUND_INTERFACE_VERSION 1
|
||||
|
||||
#define FL_VOXWORD_IN_CACHE BIT( 0 ) // if set, it was loaded prior and shouldn't be freed
|
||||
|
||||
#define FL_CHAN_USE_LOOP BIT( 0 ) // don't loop default and local sounds
|
||||
#define FL_CHAN_STATIC_SOUND BIT( 1 ) // use origin instead of fetching entnum's origin
|
||||
#define FL_CHAN_LOCAL_SOUND BIT( 2 ) // it's a local menu sound (not looped, not paused)
|
||||
#define FL_CHAN_SENTENCE_FINISHED BIT( 4 ) // if set, finished playing sentence
|
||||
#define FL_CHAN_FINISHED BIT( 5 ) // if set, finished playing single word
|
||||
|
||||
typedef int sound_t;
|
||||
|
||||
typedef struct portable_samplepair_s
|
||||
{
|
||||
int left;
|
||||
int right;
|
||||
} portable_samplepair_t;
|
||||
|
||||
typedef struct sfx_s
|
||||
{
|
||||
char name[MAX_QPATH];
|
||||
wavdata_t *cache;
|
||||
|
||||
int servercount;
|
||||
uint hashValue;
|
||||
struct sfx_s *hashNext;
|
||||
} sfx_t;
|
||||
|
||||
typedef struct voxword_s
|
||||
{
|
||||
sfx_t *sfx;
|
||||
uint16_t volume; // volume percent
|
||||
uint16_t pitch; // pitch shift percent (keep large for extra chipmunk fun)
|
||||
uint8_t timecompress; // percent of skipped data (speeds up playback without pitch shift)
|
||||
uint8_t start; // percent at which playback starts
|
||||
uint8_t end; // percent at which playback ends
|
||||
uint8_t flags;
|
||||
} voxword_t;
|
||||
|
||||
typedef struct channel_s
|
||||
{
|
||||
char name[16]; // keep sentence name
|
||||
sfx_t *sfx; // sfx number
|
||||
|
||||
vec3_t origin; // only use if fixed_origin is set
|
||||
float dist_mult; // distance multiplier (attenuation/clipK)
|
||||
|
||||
int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.)
|
||||
uint flags;
|
||||
short entnum; // entity soundsource
|
||||
short master_vol; // 0-255 master volume
|
||||
short leftvol; // 0-255 left volume
|
||||
short rightvol; // 0-255 right volume
|
||||
short basePitch; // base pitch percent (100% is normal pitch playback)
|
||||
byte word_index;
|
||||
|
||||
// HACKHACK: count when this channel became inaudible
|
||||
// to not free it when it could be respatialized soon
|
||||
#define MAX_CHANNEL_INAUDIBLE_TIME 0.1f
|
||||
float inauduble_free_time;
|
||||
|
||||
double sample;
|
||||
double forced_end;
|
||||
wavdata_t *data;
|
||||
voxword_t *words; // dynamically allocated, (num_words + 1) entries, null sfx terminates
|
||||
|
||||
uintptr_t engine_reserved[8]; // only for engine developers
|
||||
uintptr_t game_reserved[8]; // free space for game developers
|
||||
} channel_t;
|
||||
|
||||
typedef struct rawchan_s
|
||||
{
|
||||
short entnum;
|
||||
short master_vol;
|
||||
short leftvol; // 0-255 left volume
|
||||
short rightvol; // 0-255 right volume
|
||||
float dist_mult; // distance multiplier (attenuation/clipK)
|
||||
vec3_t origin; // only use if fixed_origin is set
|
||||
volatile uint s_rawend;
|
||||
float oldtime; // catch time jumps
|
||||
|
||||
uintptr_t engine_reserved[8]; // only for engine developers
|
||||
uintptr_t game_reserved[8]; // free space for game developers
|
||||
|
||||
size_t max_samples; // buffer length
|
||||
portable_samplepair_t rawsamples[]; // variable sized
|
||||
} rawchan_t;
|
||||
|
||||
typedef struct snd_format_s
|
||||
{
|
||||
uint speed;
|
||||
byte width;
|
||||
byte channels;
|
||||
} snd_format_t;
|
||||
|
||||
typedef struct snd_globals_s
|
||||
{
|
||||
// dma
|
||||
const char *backend_name;
|
||||
byte *buffer;
|
||||
snd_format_t format;
|
||||
qboolean initialized; // sound engine is active
|
||||
int samples; // mono samples in buffer
|
||||
int samplepos; // in mono samples
|
||||
|
||||
int paintedtime; // total samples that have been mixed at speed
|
||||
int soundtime; // total samples that have been played out to hardware at dma speed
|
||||
|
||||
// listener (client, camera, etc)
|
||||
vec3_t origin;
|
||||
vec3_t forward, right, up;
|
||||
int entnum;
|
||||
qboolean streaming; // playing AVI-file
|
||||
qboolean stream_paused; // pause only background track
|
||||
|
||||
// SoundAPI shared pointers
|
||||
channel_t *const channels;
|
||||
int max_channels;
|
||||
int total_channels;
|
||||
rawchan_t **const raw_channels;
|
||||
int max_raw_channels;
|
||||
sound_t ambient_sfx[NUM_AMBIENTS];
|
||||
qboolean have_ambient_sfx;
|
||||
} snd_globals_t;
|
||||
|
||||
typedef struct voice_audio_info_s
|
||||
{
|
||||
uint width;
|
||||
uint samplerate;
|
||||
uint frame_size; // in samples
|
||||
} voice_audio_info_t;
|
||||
|
||||
// API from engine to client (client calls these)
|
||||
typedef struct sound_api_s
|
||||
{
|
||||
qboolean (*CL_GetEntitySpatialization)( channel_t *ch );
|
||||
sfx_t* (*S_GetSfxByHandle)( sound_t handle );
|
||||
// Voice extensions
|
||||
void (*pfnS_RawEntSamples)( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol, float attn );
|
||||
void (*pfnSND_ForceInitMouth)( int entnum );
|
||||
voice_audio_info_t (*pfnVoice_GetAudioInfo)( void );
|
||||
} sound_api_t;
|
||||
|
||||
// Callbacks from client to engine (engine calls these when custom sound is active)
|
||||
typedef struct sound_interface_s
|
||||
{
|
||||
int version;
|
||||
|
||||
qboolean (*pfnS_Init)( snd_globals_t *globals );
|
||||
void (*pfnS_Shutdown)( void );
|
||||
void (*pfnS_UpdateSound)( void );
|
||||
/* Full paint: endtime (sample pairs), dma buffer, paintedtime in/out. Client does mix + transfer to dma.buffer. */
|
||||
void (*pfnS_PaintChannels)( int endtime );
|
||||
void (*pfnS_UpdateChannel)( int ch_idx, const channel_t *ch, sound_t handle ); // ch=NULL -> channel freed
|
||||
void (*pfnS_UpdateRawChannel)( int raw_idx, rawchan_t *ch ); // ch=NULL -> channel freed
|
||||
void (*pfnS_Spatialize)( channel_t *ch );
|
||||
void (*pfnS_FreeSound)( sfx_t *sfx, sound_t handle );
|
||||
} sound_interface_t;
|
||||
|
||||
#endif // SOUND_API_H
|
||||
@@ -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
|
||||
|
||||
@@ -23,6 +23,12 @@ struct mstudioevent_s;
|
||||
struct engine_studio_api_s;
|
||||
struct r_studio_interface_s;
|
||||
|
||||
struct sound_api_s;
|
||||
struct sound_interface_s;
|
||||
|
||||
typedef struct sound_api_s sound_api_t;
|
||||
typedef struct sound_interface_s sound_interface_t;
|
||||
|
||||
// NOTE: ordering is important!
|
||||
typedef struct cldll_func_s
|
||||
{
|
||||
@@ -74,6 +80,9 @@ typedef struct cldll_func_s
|
||||
int (*pfnTouchEvent)( int type, int fingerID, float x, float y, float dx, float dy );
|
||||
void (*pfnMoveEvent)( float forwardmove, float sidemove );
|
||||
void (*pfnLookEvent)( float relyaw, float relpitch );
|
||||
// Sound API
|
||||
int (*pfnGetSoundInterface)( int version, const sound_api_t *api, sound_interface_t *callback );
|
||||
int (*pfnVoice_StartChannel)( int samples, byte *data, int entnum );
|
||||
} cldll_func_t;
|
||||
|
||||
#endif//CDLL_EXP_H
|
||||
|
||||
@@ -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,13 +250,12 @@ 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 ? !s_listener.streaming : false;
|
||||
qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !snd.streaming : false;
|
||||
|
||||
if( !dma.initialized || disable_stream || cl.paused || !Avi->cached_audio )
|
||||
if( !snd.initialized || disable_stream || cl.paused || !Avi->cached_audio )
|
||||
return;
|
||||
|
||||
ch = S_FindRawChannel( Avi->entnum, true );
|
||||
@@ -268,16 +266,16 @@ static void AVI_StreamAudio( movie_state_t *Avi )
|
||||
ch->master_vol = Avi->volume;
|
||||
ch->dist_mult = (Avi->attn / SND_CLIP_DISTANCE);
|
||||
|
||||
if( ch->s_rawend < soundtime )
|
||||
ch->s_rawend = soundtime;
|
||||
if( ch->s_rawend < snd.soundtime )
|
||||
ch->s_rawend = snd.soundtime;
|
||||
|
||||
while( ch->s_rawend < soundtime + ch->max_samples )
|
||||
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 - soundtime);
|
||||
|
||||
file_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED);
|
||||
if( file_samples <= 1 ) return; // no more samples need
|
||||
|
||||
file_bytes = file_samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;
|
||||
@@ -308,8 +306,6 @@ static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame )
|
||||
{
|
||||
int samples = frame->nb_samples;
|
||||
size_t len = samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;
|
||||
int outsamples;
|
||||
uint8_t *ptr;
|
||||
|
||||
// allocate data
|
||||
if( !Avi->cached_audio )
|
||||
@@ -337,8 +333,8 @@ static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame )
|
||||
}
|
||||
}
|
||||
|
||||
ptr = Avi->cached_audio + Avi->cached_audio_len;
|
||||
outsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples );
|
||||
uint8_t *ptr = Avi->cached_audio + Avi->cached_audio_len;
|
||||
int outsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples );
|
||||
Avi->cached_audio_len += outsamples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;
|
||||
|
||||
// Con_Printf( "%s: got audio chunk of size %d samples\n", __func__, outsamples );
|
||||
@@ -444,13 +440,16 @@ qboolean AVI_Think( movie_state_t *Avi )
|
||||
|
||||
if( Avi->texture == 0 )
|
||||
{
|
||||
int cinTexture = SCR_GetCinematicTexture();
|
||||
int w = Avi->w >= 0 ? Avi->w : refState.width;
|
||||
int h = Avi->h >= 0 ? Avi->h : refState.height;
|
||||
|
||||
ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, redraw );
|
||||
if( redraw )
|
||||
ref.dllFuncs.GL_UpdateTexture( cinTexture, Avi->xres, Avi->yres, Avi->xres, Avi->yres, Avi->dst, PF_BGRA_32 );
|
||||
ref.dllFuncs.R_DrawStretchPic( Avi->x, Avi->y, w, h, 0, 0, 1, 1, cinTexture );
|
||||
}
|
||||
else if( redraw && Avi->texture > 0 )
|
||||
ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst );
|
||||
ref.dllFuncs.GL_UpdateTexture( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst, PF_BGRA_32 );
|
||||
|
||||
if( flushing && !decoded )
|
||||
return false; // probably hit an EOF
|
||||
@@ -460,8 +459,6 @@ qboolean AVI_Think( movie_state_t *Avi )
|
||||
|
||||
void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet )
|
||||
{
|
||||
byte *dst[4];
|
||||
int dst_linesize[4];
|
||||
int ret;
|
||||
|
||||
if( Avi->active )
|
||||
@@ -526,6 +523,8 @@ void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audi
|
||||
return;
|
||||
}
|
||||
|
||||
byte *dst[4];
|
||||
int dst_linesize[4];
|
||||
if(( ret = pav_image_alloc( dst, dst_linesize, Avi->xres, Avi->yres, AV_PIX_FMT_BGR0, 1 )) < 0 )
|
||||
{
|
||||
AVI_SpewAvError( quiet, "av_image_alloc", ret );
|
||||
|
||||
@@ -26,16 +26,82 @@ GNU General Public License for more details.
|
||||
|
||||
#if XASH_FFMPEG_DLOPEN
|
||||
|
||||
// using typeof gives more resilience to function declaration changes
|
||||
// but it's only available in GCC and standardized in C23
|
||||
#if defined(__GNUC__) || __STDC_VERSION__ >= 202311L
|
||||
|
||||
#define SUPPORTED_AVU_VERSION_MAJOR LIBAVUTIL_VERSION_MAJOR
|
||||
#define SUPPORTED_AVF_VERSION_MAJOR LIBAVFORMAT_VERSION_MAJOR
|
||||
#define SUPPORTED_AVC_VERSION_MAJOR LIBAVCODEC_VERSION_MAJOR
|
||||
#define SUPPORTED_SWR_VERSION_MAJOR LIBSWRESAMPLE_VERSION_MAJOR
|
||||
#define SUPPORTED_SWS_VERSION_MAJOR LIBSWSCALE_VERSION_MAJOR
|
||||
|
||||
#define F( func ) \
|
||||
typedef typeof( func ) func##_t; \
|
||||
func##_t *p##func
|
||||
|
||||
// libavutil
|
||||
F( avutil_version );
|
||||
F( av_frame_alloc );
|
||||
F( av_frame_free );
|
||||
F( av_frame_ref );
|
||||
F( av_frame_unref );
|
||||
F( av_strerror );
|
||||
F( av_free );
|
||||
F( av_get_bytes_per_sample );
|
||||
F( av_get_media_type_string );
|
||||
F( av_image_alloc );
|
||||
|
||||
// libavformat
|
||||
F( avformat_version );
|
||||
F( av_find_best_stream );
|
||||
F( av_read_frame );
|
||||
F( av_seek_frame );
|
||||
F( avformat_close_input );
|
||||
F( avformat_find_stream_info );
|
||||
F( avformat_open_input );
|
||||
|
||||
// libavcodec
|
||||
F( avcodec_version );
|
||||
F( av_packet_alloc );
|
||||
F( av_packet_free );
|
||||
F( av_packet_unref );
|
||||
F( avcodec_alloc_context3 );
|
||||
F( avcodec_find_decoder );
|
||||
F( avcodec_flush_buffers );
|
||||
F( avcodec_free_context );
|
||||
F( avcodec_open2 );
|
||||
F( avcodec_parameters_to_context );
|
||||
F( avcodec_receive_frame );
|
||||
F( avcodec_send_packet );
|
||||
|
||||
// libswresample
|
||||
F( swresample_version );
|
||||
F( swr_alloc_set_opts2 );
|
||||
F( swr_convert );
|
||||
F( swr_free );
|
||||
F( swr_init );
|
||||
|
||||
// libswscale
|
||||
F( swscale_version );
|
||||
F( sws_freeContext );
|
||||
F( sws_getContext );
|
||||
F( sws_scale );
|
||||
|
||||
#undef F
|
||||
|
||||
#else // !defined(__GNUC__) && __STDC_VERSION__ < 202311L
|
||||
|
||||
// the following symbols were taken from ffmpeg public headers
|
||||
// on each major ffmpeg uprgade, they must be validated
|
||||
// ffmpeg guarantees API and ABI compatibility between major versions
|
||||
// so complain if this gets compiled against unsupported yet version
|
||||
// the same check will be done in runtime to ensure compatibility
|
||||
#define SUPPORTED_AVU_VERSION_MAJOR 59
|
||||
#define SUPPORTED_AVF_VERSION_MAJOR 61
|
||||
#define SUPPORTED_AVC_VERSION_MAJOR 61
|
||||
#define SUPPORTED_SWR_VERSION_MAJOR 5
|
||||
#define SUPPORTED_SWS_VERSION_MAJOR 8
|
||||
#define SUPPORTED_AVU_VERSION_MAJOR 60
|
||||
#define SUPPORTED_AVF_VERSION_MAJOR 62
|
||||
#define SUPPORTED_AVC_VERSION_MAJOR 62
|
||||
#define SUPPORTED_SWR_VERSION_MAJOR 6
|
||||
#define SUPPORTED_SWS_VERSION_MAJOR 9
|
||||
|
||||
#if SUPPORTED_AVU_VERSION_MAJOR != LIBAVUTIL_VERSION_MAJOR
|
||||
#error "unsupported libavutil version"
|
||||
@@ -105,6 +171,8 @@ void (*psws_freeContext)( struct SwsContext *swsContext );
|
||||
struct SwsContext *(*psws_getContext)( int srcW, int srcH, enum AVPixelFormat srcFormat, int dstW, int dstH, enum AVPixelFormat dstFormat, int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param );
|
||||
int (*psws_scale)( struct SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[], int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[] );
|
||||
|
||||
#endif // defined(__GNUC__) || __STDC_VERSION__ >= 202311L
|
||||
|
||||
#else // !XASH_FFMPEG_DLOPEN
|
||||
|
||||
#define SUPPORTED_AVU_VERSION_MAJOR LIBAVUTIL_VERSION_MAJOR
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
@@ -849,6 +835,31 @@ BEAM * GAME_EXPORT R_BeamEnts( int startEnt, int endEnt, int modelIndex, float l
|
||||
return pbeam;
|
||||
}
|
||||
|
||||
static qboolean R_BeamVisible( const vec3_t start, const vec3_t end )
|
||||
{
|
||||
vec3_t mins, maxs;
|
||||
|
||||
for( int i = 0; i < 3; i++ )
|
||||
{
|
||||
if( start[i] < end[i] )
|
||||
{
|
||||
mins[i] = start[i];
|
||||
maxs[i] = end[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
mins[i] = end[i];
|
||||
maxs[i] = start[i];
|
||||
}
|
||||
|
||||
// don't let it be zero sized
|
||||
if( mins[i] == maxs[i] )
|
||||
maxs[i] += 1.0f;
|
||||
}
|
||||
|
||||
return Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ));
|
||||
}
|
||||
|
||||
/*
|
||||
==============
|
||||
R_BeamPoints
|
||||
@@ -861,7 +872,7 @@ BEAM * GAME_EXPORT R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float
|
||||
{
|
||||
BEAM *pbeam;
|
||||
|
||||
if( life != 0 && ref.dllFuncs.R_BeamCull( start, end, true ))
|
||||
if( life != 0 && !R_BeamVisible( start, end ))
|
||||
return NULL;
|
||||
|
||||
pbeam = R_BeamAlloc();
|
||||
@@ -1039,25 +1050,20 @@ set EF_BRIGHTFIELD effect
|
||||
*/
|
||||
void GAME_EXPORT R_EntityParticles( cl_entity_t *ent )
|
||||
{
|
||||
float angle;
|
||||
float sr, sp, sy, cr, cp, cy;
|
||||
vec3_t forward;
|
||||
particle_t *p;
|
||||
int i;
|
||||
|
||||
for( i = 0; i < NUMVERTEXNORMALS; i++ )
|
||||
for( int i = 0; i < NUMVERTEXNORMALS; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
angle = cl.time * cl_avelocities[i][0];
|
||||
float sr, sp, sy, cr, cp, cy;
|
||||
float angle = cl.time * cl_avelocities[i][0];
|
||||
SinCos( angle, &sy, &cy );
|
||||
angle = cl.time * cl_avelocities[i][1];
|
||||
SinCos( angle, &sp, &cp );
|
||||
angle = cl.time * cl_avelocities[i][2];
|
||||
SinCos( angle, &sr, &cr );
|
||||
|
||||
VectorSet( forward, cp * cy, cp * sy, -sp );
|
||||
vec3_t forward = { cp * cy, cp * sy, -sp };
|
||||
|
||||
p->die = cl.time + 0.001f;
|
||||
p->color = 111; // yellow
|
||||
@@ -1074,19 +1080,16 @@ R_ParticleExplosion
|
||||
*/
|
||||
void GAME_EXPORT R_ParticleExplosion( const vec3_t org )
|
||||
{
|
||||
particle_t *p;
|
||||
int i, j;
|
||||
|
||||
for( i = 0; i < 1024; i++ )
|
||||
for( int i = 0; i < 1024; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + 5.0f;
|
||||
p->ramp = COM_RandomLong( 0, 3 );
|
||||
p->color = ramp1[0];
|
||||
|
||||
for( j = 0; j < 3; j++ )
|
||||
for( int j = 0; j < 3; j++ )
|
||||
{
|
||||
p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f );
|
||||
p->vel[j] = COM_RandomFloat( -256.0f, 256.0f );
|
||||
@@ -1105,15 +1108,12 @@ R_ParticleExplosion2
|
||||
*/
|
||||
void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength )
|
||||
{
|
||||
int i, j;
|
||||
int colorMod = 0, packedColor;
|
||||
particle_t *p;
|
||||
int colorMod = 0;
|
||||
int packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
|
||||
|
||||
packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
|
||||
|
||||
for( i = 0; i < 512; i++ )
|
||||
for( int i = 0; i < 512; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + 0.3f;
|
||||
@@ -1123,7 +1123,7 @@ void GAME_EXPORT R_ParticleExplosion2( const vec3_t org, int colorStart, int col
|
||||
|
||||
p->type = pt_blob;
|
||||
|
||||
for( j = 0; j < 3; j++ )
|
||||
for( int j = 0; j < 3; j++ )
|
||||
{
|
||||
p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f );
|
||||
p->vel[j] = COM_RandomFloat( -256.0f, 256.0f );
|
||||
@@ -1139,14 +1139,11 @@ R_BlobExplosion
|
||||
*/
|
||||
void GAME_EXPORT R_BlobExplosion( const vec3_t org )
|
||||
{
|
||||
particle_t *p;
|
||||
int i, j, packedColor;
|
||||
int packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
|
||||
|
||||
packedColor = Host_IsQuakeCompatible( ) ? 255 : 0; // use old code for blob particles
|
||||
|
||||
for( i = 0; i < 1024; i++ )
|
||||
for( int i = 0; i < 1024; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + COM_RandomFloat( 1.0f, 1.4f );
|
||||
@@ -1163,7 +1160,7 @@ void GAME_EXPORT R_BlobExplosion( const vec3_t org )
|
||||
p->color = COM_RandomLong( 150, 155 );
|
||||
}
|
||||
|
||||
for( j = 0; j < 3; j++ )
|
||||
for( int j = 0; j < 3; j++ )
|
||||
{
|
||||
p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f );
|
||||
p->vel[j] = COM_RandomFloat( -256.0f, 256.0f );
|
||||
@@ -1180,9 +1177,6 @@ PARTICLE_EFFECT on server
|
||||
*/
|
||||
void GAME_EXPORT R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count )
|
||||
{
|
||||
particle_t *p;
|
||||
int i;
|
||||
|
||||
if( count == 1024 )
|
||||
{
|
||||
// rocket explosion
|
||||
@@ -1190,9 +1184,9 @@ void GAME_EXPORT R_RunParticleEffect( const vec3_t org, const vec3_t dir, int co
|
||||
return;
|
||||
}
|
||||
|
||||
for( i = 0; i < count; i++ )
|
||||
for( int i = 0; i < count; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->color = (color & ~7) + COM_RandomLong( 0, 7 );
|
||||
@@ -1214,20 +1208,18 @@ particle spray
|
||||
void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed )
|
||||
{
|
||||
vec3_t pos, dir, vec;
|
||||
float pspeed = speed * 3.0f;
|
||||
int i, j;
|
||||
particle_t *p;
|
||||
int pspeed = speed * 3;
|
||||
|
||||
VectorNormalize2( ndir, dir );
|
||||
|
||||
for( i = 0; i < (speed / 2); i++ )
|
||||
for( int i = 0; i < (speed / 2); i++ )
|
||||
{
|
||||
VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos );
|
||||
VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec );
|
||||
|
||||
for( j = 0; j < 7; j++ )
|
||||
for( int j = 0; j < 8; j++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + 1.5f;
|
||||
@@ -1237,6 +1229,8 @@ void GAME_EXPORT R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int s
|
||||
VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org );
|
||||
VectorScale( vec, pspeed, p->vel );
|
||||
}
|
||||
|
||||
pspeed -= speed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1249,17 +1243,15 @@ particle spray 2
|
||||
*/
|
||||
void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor, int speed )
|
||||
{
|
||||
particle_t *p;
|
||||
int i, j;
|
||||
float arc;
|
||||
int accel = speed; // must be integer due to bug in GoldSrc
|
||||
vec3_t dir;
|
||||
|
||||
VectorNormalize2( ndir, dir );
|
||||
|
||||
for( arc = 0.05f, i = 0; i < 100; i++ )
|
||||
float arc = 0.05f;
|
||||
for( int i = 0; i < 100; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + 2.0f;
|
||||
@@ -1275,11 +1267,10 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
|
||||
accel -= 0.00001f; // so last few will drip
|
||||
}
|
||||
|
||||
for( arc = 0.075f, i = 0; i < ( speed / 5 ); i++ )
|
||||
arc = 0.075f;
|
||||
for( int i = 0; i < ( speed / 5 ); i++ )
|
||||
{
|
||||
float num;
|
||||
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + 3.0f;
|
||||
@@ -1292,14 +1283,14 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
|
||||
p->vel[2] -= arc;
|
||||
arc -= 0.005f;
|
||||
|
||||
num = COM_RandomFloat( 0.0f, 1.0f );
|
||||
float num = COM_RandomFloat( 0.0f, 1.0f );
|
||||
accel = speed * num;
|
||||
num *= 1.7f;
|
||||
|
||||
VectorScale( p->vel, num, p->vel );
|
||||
VectorScale( p->vel, accel, p->vel );
|
||||
|
||||
for( j = 0; j < 2; j++ )
|
||||
for( int j = 0; j < 2; j++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
@@ -1308,10 +1299,7 @@ void GAME_EXPORT R_BloodStream( const vec3_t org, const vec3_t ndir, int pcolor,
|
||||
p->color = pcolor + COM_RandomLong( 0, 9 );
|
||||
p->type = pt_4x_slowgrav;
|
||||
|
||||
p->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f );
|
||||
p->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f );
|
||||
p->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f );
|
||||
|
||||
VectorAddScalar( org, COM_RandomFloat( -1.0f, 1.0f ), p->org );
|
||||
VectorCopy( dir, p->vel );
|
||||
p->vel[2] -= arc;
|
||||
|
||||
@@ -1329,18 +1317,15 @@ R_LavaSplash
|
||||
*/
|
||||
void GAME_EXPORT R_LavaSplash( const vec3_t org )
|
||||
{
|
||||
particle_t *p;
|
||||
float vel;
|
||||
vec3_t dir;
|
||||
int i, j, k;
|
||||
|
||||
for( i = -16; i < 16; i++ )
|
||||
for( int i = -16; i < 16; i++ )
|
||||
{
|
||||
for( j = -16; j <16; j++ )
|
||||
for( int j = -16; j <16; j++ )
|
||||
{
|
||||
for( k = 0; k < 1; k++ )
|
||||
for( int k = 0; k < 1; k++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + COM_RandomFloat( 2.0f, 2.62f );
|
||||
@@ -1356,7 +1341,7 @@ void GAME_EXPORT R_LavaSplash( const vec3_t org )
|
||||
p->org[2] = org[2] + COM_RandomFloat( 0.0f, 63.0f );
|
||||
|
||||
VectorNormalize( dir );
|
||||
vel = COM_RandomFloat( 50.0f, 113.0f );
|
||||
float vel = COM_RandomFloat( 50.0f, 113.0f );
|
||||
VectorScale( dir, vel, p->vel );
|
||||
}
|
||||
}
|
||||
@@ -1371,16 +1356,13 @@ R_ParticleBurst
|
||||
*/
|
||||
void GAME_EXPORT R_ParticleBurst( const vec3_t org, int size, int color, float life )
|
||||
{
|
||||
particle_t *p;
|
||||
vec3_t dir, dest;
|
||||
int i, j;
|
||||
float dist;
|
||||
|
||||
for( i = 0; i < 32; i++ )
|
||||
for( int i = 0; i < 32; i++ )
|
||||
{
|
||||
for( j = 0; j < 32; j++ )
|
||||
for( int j = 0; j < 32; j++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + life + COM_RandomFloat( -0.5f, 0.5f );
|
||||
@@ -1390,7 +1372,7 @@ void GAME_EXPORT R_ParticleBurst( const vec3_t org, int size, int color, float l
|
||||
VectorCopy( org, p->org );
|
||||
VectorAddScalar( org, COM_RandomFloat( -size, size ), dest );
|
||||
VectorSubtract( dest, p->org, dir );
|
||||
dist = VectorNormalizeLength( dir );
|
||||
float dist = VectorNormalizeLength( dir );
|
||||
VectorScale( dir, ( dist / life ), p->vel );
|
||||
}
|
||||
}
|
||||
@@ -1404,16 +1386,13 @@ R_LargeFunnel
|
||||
*/
|
||||
void GAME_EXPORT R_LargeFunnel( const vec3_t org, int reverse )
|
||||
{
|
||||
particle_t *p;
|
||||
float vel, dist;
|
||||
vec3_t dir, dest;
|
||||
int i, j;
|
||||
|
||||
for( i = -8; i < 8; i++ )
|
||||
for( int i = -8; i < 8; i++ )
|
||||
{
|
||||
for( j = -8; j < 8; j++ )
|
||||
for( int j = -8; j < 8; j++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
dest[0] = (i * 32.0f) + org[0];
|
||||
@@ -1431,10 +1410,10 @@ void GAME_EXPORT R_LargeFunnel( const vec3_t org, int reverse )
|
||||
VectorSubtract( org, p->org, dir );
|
||||
}
|
||||
|
||||
vel = dest[2] / 8.0f;
|
||||
float vel = dest[2] / 8.0f;
|
||||
if( vel < 64.0f ) vel = 64.0f;
|
||||
|
||||
dist = VectorNormalizeLength( dir );
|
||||
float dist = VectorNormalizeLength( dir );
|
||||
vel += COM_RandomFloat( 64.0f, 128.0f );
|
||||
VectorScale( dir, vel, p->vel );
|
||||
p->die = cl.time + (dist / vel );
|
||||
@@ -1451,18 +1430,15 @@ R_TeleportSplash
|
||||
*/
|
||||
void GAME_EXPORT R_TeleportSplash( const vec3_t org )
|
||||
{
|
||||
particle_t *p;
|
||||
vec3_t dir;
|
||||
float vel;
|
||||
int i, j, k;
|
||||
|
||||
for( i = -16; i < 16; i += 4 )
|
||||
for( int i = -16; i < 16; i += 4 )
|
||||
{
|
||||
for( j = -16; j < 16; j += 4 )
|
||||
for( int j = -16; j < 16; j += 4 )
|
||||
{
|
||||
for( k = -24; k < 32; k += 4 )
|
||||
for( int k = -24; k < 32; k += 4 )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + COM_RandomFloat( 0.2f, 0.34f );
|
||||
@@ -1478,7 +1454,7 @@ void GAME_EXPORT R_TeleportSplash( const vec3_t org )
|
||||
p->org[2] = org[2] + k + COM_RandomFloat( 0.0f, 3.0f );
|
||||
|
||||
VectorNormalize( dir );
|
||||
vel = COM_RandomFloat( 50.0f, 113.0f );
|
||||
float vel = COM_RandomFloat( 50.0f, 113.0f );
|
||||
VectorScale( dir, vel, p->vel );
|
||||
}
|
||||
}
|
||||
@@ -1615,13 +1591,12 @@ draw line from particles
|
||||
*/
|
||||
static void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel )
|
||||
{
|
||||
float len, curdist;
|
||||
vec3_t diff, pos;
|
||||
|
||||
// determine distance
|
||||
VectorSubtract( end, start, diff );
|
||||
len = VectorNormalizeLength( diff );
|
||||
curdist = 0;
|
||||
float len = VectorNormalizeLength( diff );
|
||||
float curdist = 0;
|
||||
|
||||
while( curdist <= len )
|
||||
{
|
||||
@@ -1655,9 +1630,8 @@ static void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t orig
|
||||
{
|
||||
vec3_t p[8], tmp;
|
||||
float gap = BOX_GAP;
|
||||
int i;
|
||||
|
||||
for( i = 0; i < 8; i++ )
|
||||
for( int i = 0; i < 8; i++ )
|
||||
{
|
||||
tmp[0] = (i & 1) ? mins[0] - gap : maxs[0] + gap;
|
||||
tmp[1] = (i & 2) ? mins[1] - gap : maxs[1] + gap ;
|
||||
@@ -1667,7 +1641,7 @@ static void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t orig
|
||||
VectorCopy( tmp, p[i] );
|
||||
}
|
||||
|
||||
for( i = 0; i < 6; i++ )
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
PM_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], pcolor, life );
|
||||
}
|
||||
@@ -1681,9 +1655,7 @@ R_ParticleLine
|
||||
*/
|
||||
void GAME_EXPORT R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life )
|
||||
{
|
||||
int pcolor;
|
||||
|
||||
pcolor = R_LookupColor( r, g, b );
|
||||
int pcolor = R_LookupColor( r, g, b );
|
||||
PM_ParticleLine( start, end, pcolor, life, 0 );
|
||||
}
|
||||
|
||||
@@ -1697,9 +1669,8 @@ void GAME_EXPORT R_ParticleBox( const vec3_t absmin, const vec3_t absmax, byte r
|
||||
{
|
||||
vec3_t mins, maxs;
|
||||
vec3_t origin;
|
||||
int pcolor;
|
||||
|
||||
pcolor = R_LookupColor( r, g, b );
|
||||
int pcolor = R_LookupColor( r, g, b );
|
||||
|
||||
VectorAverage( absmax, absmin, origin );
|
||||
VectorSubtract( absmax, origin, maxs );
|
||||
@@ -1716,20 +1687,18 @@ R_ShowLine
|
||||
*/
|
||||
void GAME_EXPORT R_ShowLine( const vec3_t start, const vec3_t end )
|
||||
{
|
||||
vec3_t dir, org;
|
||||
float len;
|
||||
particle_t *p;
|
||||
vec3_t dir;
|
||||
|
||||
VectorSubtract( end, start, dir );
|
||||
len = VectorNormalizeLength( dir );
|
||||
float len = VectorNormalizeLength( dir );
|
||||
VectorScale( dir, 5.0f, dir );
|
||||
VectorCopy( start, org );
|
||||
vec3_t org = Vec3( start );
|
||||
|
||||
while( len > 0 )
|
||||
{
|
||||
len -= 5.0f;
|
||||
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
p->die = cl.time + 30;
|
||||
@@ -1748,25 +1717,21 @@ R_BulletImpactParticles
|
||||
*/
|
||||
void GAME_EXPORT R_BulletImpactParticles( const vec3_t pos )
|
||||
{
|
||||
int i, quantity;
|
||||
int color;
|
||||
float dist;
|
||||
vec3_t dir;
|
||||
particle_t *p;
|
||||
|
||||
VectorSubtract( pos, refState.vieworg, dir );
|
||||
dist = VectorLength( dir );
|
||||
float dist = VectorLength( dir );
|
||||
if( dist > 1000.0f ) dist = 1000.0f;
|
||||
|
||||
quantity = (1000.0f - dist) / 100.0f;
|
||||
int quantity = (1000.0f - dist) / 100.0f;
|
||||
if( quantity == 0 ) quantity = 1;
|
||||
|
||||
color = 3 - ((30 * quantity) / 100 );
|
||||
int color = 3 - ((30 * quantity) / 100 );
|
||||
R_SparkStreaks( pos, 2, -200, 200 );
|
||||
|
||||
for( i = 0; i < quantity * 4; i++ )
|
||||
for( int i = 0; i < quantity * 4; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
VectorCopy( pos, p->org);
|
||||
@@ -1790,12 +1755,9 @@ R_FlickerParticles
|
||||
*/
|
||||
void GAME_EXPORT R_FlickerParticles( const vec3_t org )
|
||||
{
|
||||
particle_t *p;
|
||||
int i;
|
||||
|
||||
for( i = 0; i < 15; i++ )
|
||||
for( int i = 0; i < 15; i++ )
|
||||
{
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
VectorCopy( org, p->org );
|
||||
@@ -1819,15 +1781,13 @@ create a splash of streaks
|
||||
void GAME_EXPORT R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velocityMin, int velocityMax )
|
||||
{
|
||||
vec3_t vel, vel2;
|
||||
particle_t *p;
|
||||
int i;
|
||||
|
||||
VectorScale( dir, speed, vel );
|
||||
|
||||
for( i = 0; i < count; i++ )
|
||||
for( int i = 0; i < count; i++ )
|
||||
{
|
||||
VectorAddScalar( vel, COM_RandomFloat( velocityMin, velocityMax ), vel2 );
|
||||
p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f ));
|
||||
particle_t *p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f ));
|
||||
if( !p ) return;
|
||||
|
||||
p->type = pt_grav;
|
||||
@@ -1845,9 +1805,7 @@ pmove debugging particle
|
||||
*/
|
||||
void CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel )
|
||||
{
|
||||
particle_t *p;
|
||||
|
||||
p = R_AllocParticle( NULL );
|
||||
particle_t *p = R_AllocParticle( NULL );
|
||||
if( !p ) return;
|
||||
|
||||
if( org ) VectorCopy( org, p->org );
|
||||
@@ -1865,17 +1823,15 @@ R_TracerEffect
|
||||
void GAME_EXPORT R_TracerEffect( const vec3_t start, const vec3_t end )
|
||||
{
|
||||
vec3_t pos, vel, dir;
|
||||
float len, speed;
|
||||
float offset;
|
||||
|
||||
speed = Q_max( tracerspeed.value, 3.0f );
|
||||
float speed = Q_max( tracerspeed.value, 3.0f );
|
||||
|
||||
VectorSubtract( end, start, dir );
|
||||
len = VectorLength( dir );
|
||||
float len = VectorLength( dir );
|
||||
if( len == 0.0f ) return;
|
||||
|
||||
VectorScale( dir, 1.0f / len, dir ); // normalize
|
||||
offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value;
|
||||
float offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset.value;
|
||||
VectorScale( dir, offset, vel );
|
||||
VectorAdd( start, vel, pos );
|
||||
VectorScale( dir, speed, vel );
|
||||
@@ -1926,17 +1882,15 @@ create a streak tracers
|
||||
*/
|
||||
void GAME_EXPORT R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax )
|
||||
{
|
||||
particle_t *p;
|
||||
vec3_t vel;
|
||||
int i;
|
||||
|
||||
for( i = 0; i<count; i++ )
|
||||
for( int i = 0; i<count; i++ )
|
||||
{
|
||||
vel[0] = COM_RandomFloat( velocityMin, velocityMax );
|
||||
vel[1] = COM_RandomFloat( velocityMin, velocityMax );
|
||||
vel[2] = COM_RandomFloat( velocityMin, velocityMax );
|
||||
|
||||
p = R_AllocTracer( pos, vel, COM_RandomFloat( 0.1f, 0.5f ));
|
||||
particle_t *p = R_AllocTracer( pos, vel, COM_RandomFloat( 0.1f, 0.5f ));
|
||||
if( !p ) return;
|
||||
|
||||
p->color = 5;
|
||||
@@ -1956,14 +1910,11 @@ void GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float l
|
||||
{
|
||||
float dist = ( radius / 100.0f );
|
||||
vec3_t start, temp, vel;
|
||||
float factor;
|
||||
particle_t *p;
|
||||
int i;
|
||||
|
||||
if( life <= 0.0f ) life = 0.1f; // to avoid divide by zero
|
||||
factor = -1.0 / life;
|
||||
float factor = -1.0 / life;
|
||||
|
||||
for ( i = 0; i < count; i++ )
|
||||
for( int i = 0; i < count; i++ )
|
||||
{
|
||||
temp[0] = dist * COM_RandomFloat( -100.0f, 100.0f );
|
||||
temp[1] = dist * COM_RandomFloat( -100.0f, 100.0f );
|
||||
@@ -1971,7 +1922,8 @@ void GAME_EXPORT R_Implosion( const vec3_t end, float radius, int count, float l
|
||||
VectorScale( temp, factor, vel );
|
||||
VectorAdd( temp, end, start );
|
||||
|
||||
if(( p = R_AllocTracer( start, vel, life )) == NULL )
|
||||
particle_t *p = R_AllocTracer( start, vel, life );
|
||||
if( p == NULL )
|
||||
return;
|
||||
|
||||
p->type = pt_explode;
|
||||
@@ -2036,16 +1988,12 @@ CL_ReadPointFile_f
|
||||
*/
|
||||
void CL_ReadPointFile_f( void )
|
||||
{
|
||||
byte *afile;
|
||||
char *pfile;
|
||||
vec3_t org;
|
||||
int count;
|
||||
particle_t *p;
|
||||
char filename[64];
|
||||
string token;
|
||||
|
||||
Q_snprintf( filename, sizeof( filename ), "maps/%s.pts", clgame.mapname );
|
||||
afile = FS_LoadFile( filename, NULL, false );
|
||||
byte *afile = FS_LoadFile( filename, NULL, false );
|
||||
|
||||
if( !afile )
|
||||
{
|
||||
@@ -2055,8 +2003,8 @@ void CL_ReadPointFile_f( void )
|
||||
|
||||
Con_Printf( "Reading %s...\n", filename );
|
||||
|
||||
count = 0;
|
||||
pfile = (char *)afile;
|
||||
int count = 0;
|
||||
char *pfile = (char *)afile;
|
||||
|
||||
while( 1 )
|
||||
{
|
||||
@@ -2082,7 +2030,7 @@ void CL_ReadPointFile_f( void )
|
||||
|
||||
// NOTE: can't use R_AllocParticle because this command
|
||||
// may be executed from the console, while frametime is 0
|
||||
p = cl_free_particles;
|
||||
particle_t *p = cl_free_particles;
|
||||
cl_free_particles = p->next;
|
||||
p->next = cl_active_particles;
|
||||
cl_active_particles = p;
|
||||
|
||||
@@ -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 );
|
||||
@@ -70,16 +81,12 @@ void CL_SetFontRendermode( cl_font_t *font )
|
||||
|
||||
void CL_SetFontColor( cl_font_t *font, const rgba_t color )
|
||||
{
|
||||
// don't apply color to fixed fonts it's already colored
|
||||
if( font->type != FONT_FIXED || REF_GET_PARM( PARM_TEX_GLFORMAT, font->hFontTexture ) == 0x8045 ) // GL_LUMINANCE8_ALPHA8
|
||||
ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );
|
||||
else
|
||||
ref.dllFuncs.Color4ub( 255, 255, 255, color[3] );
|
||||
ref.dllFuncs.Color4ub( color[0], color[1], color[2], color[3] );
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -97,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;
|
||||
@@ -114,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;
|
||||
@@ -123,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;
|
||||
|
||||
@@ -134,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 );
|
||||
@@ -146,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];
|
||||
|
||||
@@ -184,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;
|
||||
|
||||
@@ -209,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;
|
||||
@@ -981,15 +961,8 @@ qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType )
|
||||
if( !draw_player )
|
||||
return false;
|
||||
|
||||
if( entityType == ET_BEAM )
|
||||
{
|
||||
ref.dllFuncs.CL_AddCustomBeam( ent );
|
||||
return true;
|
||||
}
|
||||
else if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
|
||||
{
|
||||
if( !ref.dllFuncs.R_AddEntity( ent, entityType ))
|
||||
return false;
|
||||
}
|
||||
|
||||
// because pTemp->entity.curstate.effects
|
||||
// is already occupied by FTENT_FLICKER
|
||||
@@ -1037,12 +1010,10 @@ for all current players
|
||||
*/
|
||||
static void CL_LinkPlayers( frame_t *frame )
|
||||
{
|
||||
cl_entity_t *ent = CL_GetLocalPlayer();
|
||||
entity_state_t *state;
|
||||
cl_entity_t *ent;
|
||||
int i;
|
||||
|
||||
ent = CL_GetLocalPlayer();
|
||||
|
||||
// apply muzzleflash to weaponmodel
|
||||
if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ))
|
||||
SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH );
|
||||
@@ -1120,15 +1091,9 @@ CL_LinkPacketEntities
|
||||
*/
|
||||
static void CL_LinkPacketEntities( frame_t *frame )
|
||||
{
|
||||
cl_entity_t *ent;
|
||||
entity_state_t *state;
|
||||
qboolean parametric;
|
||||
qboolean interpolate;
|
||||
int i;
|
||||
|
||||
for( i = 0; i < frame->num_entities; i++ )
|
||||
for( int i = 0; i < frame->num_entities; i++ )
|
||||
{
|
||||
state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
|
||||
entity_state_t *state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
|
||||
|
||||
// clients are should be done in CL_LinkPlayers
|
||||
if( state->number >= 1 && state->number <= cl.maxclients )
|
||||
@@ -1138,7 +1103,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
|
||||
if( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))
|
||||
continue;
|
||||
|
||||
ent = CL_GetEntityByIndex( state->number );
|
||||
cl_entity_t *ent = CL_GetEntityByIndex( state->number );
|
||||
|
||||
if( !ent )
|
||||
{
|
||||
@@ -1149,7 +1114,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
|
||||
// animtime must keep an actual
|
||||
ent->curstate.animtime = state->animtime;
|
||||
ent->curstate.frame = state->frame;
|
||||
interpolate = false;
|
||||
qboolean interpolate = false;
|
||||
|
||||
if( !ent->model ) continue;
|
||||
|
||||
@@ -1163,7 +1128,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
|
||||
}
|
||||
}
|
||||
|
||||
parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );
|
||||
qboolean parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );
|
||||
|
||||
if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND )
|
||||
{
|
||||
@@ -1376,12 +1341,9 @@ SOUND ENGINE IMPLEMENTATION
|
||||
*/
|
||||
qboolean CL_GetEntitySpatialization( channel_t *ch )
|
||||
{
|
||||
cl_entity_t *ent;
|
||||
qboolean valid_origin;
|
||||
|
||||
if( ch->entnum == 0 )
|
||||
{
|
||||
ch->staticsound = true;
|
||||
SetBits( ch->flags, FL_CHAN_STATIC_SOUND );
|
||||
return true; // static sound
|
||||
}
|
||||
|
||||
@@ -1391,8 +1353,8 @@ qboolean CL_GetEntitySpatialization( channel_t *ch )
|
||||
return true;
|
||||
}
|
||||
|
||||
valid_origin = VectorIsNull( ch->origin ) ? false : true;
|
||||
ent = CL_GetEntityByIndex( ch->entnum );
|
||||
qboolean valid_origin = VectorIsNull( ch->origin ) ? false : true;
|
||||
cl_entity_t *ent = CL_GetEntityByIndex( ch->entnum );
|
||||
|
||||
// entity is not present on the client but has valid origin
|
||||
if( !ent || !ent->model || ent->curstate.messagenum != cl.parsecount )
|
||||
|
||||
@@ -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 = 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();
|
||||
|
||||
@@ -3678,20 +3776,22 @@ void CL_Init( void )
|
||||
CL_InitLocal();
|
||||
|
||||
VID_Init(); // init video
|
||||
S_Init(); // init sound
|
||||
Voice_Init( VOICE_DEFAULT_CODEC, 3, true ); // init voice (do not open the device)
|
||||
|
||||
// unreliable buffer. unsed for unreliable commands and voice stream
|
||||
MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf ));
|
||||
|
||||
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 )
|
||||
{
|
||||
@@ -744,16 +735,14 @@ void SCR_LoadCreditsFont( void )
|
||||
success = Con_LoadVariableWidthFont( "gfx/creditsfont.fnt", font, scale, &hud_fontrender, TF_FONT );
|
||||
|
||||
if( !success )
|
||||
success = Con_LoadFixedWidthFont( "gfx/conchars", font, scale, &hud_fontrender, TF_FONT );
|
||||
success = Con_LoadFixedWidthFont( "gfx/conchars", font, scale, &hud_fontrender, TF_FONT|TF_NEAREST );
|
||||
|
||||
// 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;
|
||||
|
||||
352
engine/client/cl_sprite.c
Normal file
352
engine/client/cl_sprite.c
Normal file
@@ -0,0 +1,352 @@
|
||||
/*
|
||||
cl_sprite.c - sprite loading
|
||||
Copyright (C) 2010 Uncle Mike
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "client.h"
|
||||
#include "sprite.h"
|
||||
#include "ref_common.h"
|
||||
|
||||
static char sprite_name[MAX_QPATH];
|
||||
static char group_suffix[8];
|
||||
static uint r_texFlags = 0;
|
||||
static int sprite_version;
|
||||
|
||||
/*
|
||||
================
|
||||
R_GetSpriteFrame
|
||||
|
||||
assume pModel is valid
|
||||
================
|
||||
*/
|
||||
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
|
||||
{
|
||||
msprite_t *psprite;
|
||||
mspriteframe_t *pspriteframe = NULL;
|
||||
|
||||
if( !pModel )
|
||||
return NULL;
|
||||
|
||||
psprite = pModel->cache.data;
|
||||
|
||||
if( frame < 0 )
|
||||
{
|
||||
frame = 0;
|
||||
}
|
||||
else if( frame >= psprite->numframes )
|
||||
{
|
||||
if( frame > psprite->numframes )
|
||||
Con_Printf( S_WARN "%s: no such frame %d (%s)\n", __func__, frame, pModel->name );
|
||||
frame = psprite->numframes - 1;
|
||||
}
|
||||
|
||||
if( psprite->frames[frame].type == SPR_SINGLE )
|
||||
{
|
||||
pspriteframe = psprite->frames[frame].frameptr;
|
||||
}
|
||||
else if( psprite->frames[frame].type == SPR_GROUP )
|
||||
{
|
||||
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
|
||||
float *pintervals = pspritegroup->intervals;
|
||||
int numframes = pspritegroup->numframes;
|
||||
float fullinterval = pintervals[numframes - 1];
|
||||
|
||||
// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
|
||||
// are positive, so we don't have to worry about division by zero
|
||||
float targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval;
|
||||
int i;
|
||||
|
||||
for( i = 0; i < ( numframes - 1 ); i++ )
|
||||
{
|
||||
if( pintervals[i] > targettime )
|
||||
break;
|
||||
}
|
||||
pspriteframe = pspritegroup->frames[i];
|
||||
}
|
||||
else if( psprite->frames[frame].type == FRAME_ANGLED )
|
||||
{
|
||||
int angleframe = (int)( Q_rint(( refState.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;
|
||||
|
||||
// e.g. doom-style sprite monsters
|
||||
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
|
||||
pspriteframe = pspritegroup->frames[angleframe];
|
||||
}
|
||||
|
||||
return pspriteframe;
|
||||
}
|
||||
|
||||
void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite )
|
||||
{
|
||||
mspriteframe_t *pFrame;
|
||||
|
||||
if( !pSprite || pSprite->type != mod_sprite )
|
||||
return;
|
||||
|
||||
pFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f );
|
||||
|
||||
if( frameWidth )
|
||||
*frameWidth = pFrame->width;
|
||||
if( frameHeight )
|
||||
*frameHeight = pFrame->height;
|
||||
if( numFrames )
|
||||
*numFrames = pSprite->numframes;
|
||||
}
|
||||
|
||||
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )
|
||||
{
|
||||
if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )
|
||||
return 0;
|
||||
|
||||
return R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum;
|
||||
}
|
||||
|
||||
static void Mod_SpriteTextureReplacementReport( const char *modelname, int gl_texturenum, const char *foundpath )
|
||||
{
|
||||
if( host_allow_materials.value != 2.0f )
|
||||
return;
|
||||
|
||||
if( gl_texturenum > 0 )
|
||||
Con_Printf( "Looking for %s tex replacement..." S_GREEN "OK (%s)\n", modelname, foundpath );
|
||||
else if( gl_texturenum < 0 )
|
||||
Con_Printf( "Looking for %s tex replacement..." S_YELLOW "MISS (%s)\n", modelname, foundpath );
|
||||
else
|
||||
Con_Printf( "Looking for %s tex replacement..." S_RED "FAIL (%s)\n", modelname, foundpath );
|
||||
}
|
||||
|
||||
static qboolean Mod_SpriteSearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *fmt, ... )
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
va_start( ap, fmt );
|
||||
ret = Q_vsnprintf( out, size, fmt, ap );
|
||||
va_end( ap );
|
||||
|
||||
if( ret < 0 )
|
||||
{
|
||||
Mod_SpriteTextureReplacementReport( modelname, -1, "overflow" );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( FS_FileExists( out, false ))
|
||||
return true;
|
||||
|
||||
Mod_SpriteTextureReplacementReport( modelname, -1, out );
|
||||
return false;
|
||||
}
|
||||
|
||||
static const byte *Mod_SpriteLoadFrame( model_t *mod, const void *pin, mspriteframe_t **ppframe, int num )
|
||||
{
|
||||
dspriteframe_t pinframe;
|
||||
mspriteframe_t *pspriteframe;
|
||||
int gl_texturenum = 0;
|
||||
char texname[128];
|
||||
int bytes = 1;
|
||||
|
||||
memcpy( &pinframe, pin, sizeof( dspriteframe_t ));
|
||||
|
||||
if( sprite_version == SPRITE_VERSION_32 )
|
||||
bytes = 4;
|
||||
|
||||
// build unique frame name
|
||||
if( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite
|
||||
{
|
||||
Q_snprintf( texname, sizeof( texname ), "#HUD/%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 );
|
||||
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );
|
||||
}
|
||||
else
|
||||
{
|
||||
// partial HD-textures support
|
||||
if( Mod_AllowMaterials( ))
|
||||
{
|
||||
if( Mod_SpriteSearchForTextureReplacement( texname, sizeof( texname ), sprite_name, "materials/%s/%s%i%i.tga", sprite_name, group_suffix, num / 10, num % 10 ))
|
||||
{
|
||||
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, NULL, 0, r_texFlags );
|
||||
Mod_SpriteTextureReplacementReport( sprite_name, gl_texturenum, texname );
|
||||
}
|
||||
}
|
||||
|
||||
if( gl_texturenum == 0 )
|
||||
{
|
||||
Q_snprintf( texname, sizeof( texname ), "#%s(%s:%i%i).spr", sprite_name, group_suffix, num / 10, num % 10 );
|
||||
gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, pin, pinframe.width * pinframe.height * bytes, r_texFlags );
|
||||
}
|
||||
}
|
||||
|
||||
// setup frame description
|
||||
pspriteframe = Mem_Malloc( mod->mempool, sizeof( mspriteframe_t ));
|
||||
pspriteframe->width = pinframe.width;
|
||||
pspriteframe->height = pinframe.height;
|
||||
pspriteframe->up = pinframe.origin[1];
|
||||
pspriteframe->left = pinframe.origin[0];
|
||||
pspriteframe->down = pinframe.origin[1] - pinframe.height;
|
||||
pspriteframe->right = pinframe.width + pinframe.origin[0];
|
||||
pspriteframe->gl_texturenum = gl_texturenum;
|
||||
*ppframe = pspriteframe;
|
||||
|
||||
return (const byte *)pin + sizeof( dspriteframe_t ) + pinframe.width * pinframe.height * bytes;
|
||||
}
|
||||
|
||||
static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )
|
||||
{
|
||||
const dspritegroup_t *pingroup = (const dspritegroup_t *)pin;
|
||||
mspritegroup_t *pspritegroup;
|
||||
const dspriteinterval_t *pin_intervals;
|
||||
float *poutintervals;
|
||||
int groupsize, numframes = pingroup->numframes;
|
||||
const void *ptemp;
|
||||
|
||||
groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] );
|
||||
pspritegroup = Mem_Calloc( mod->mempool, groupsize );
|
||||
pspritegroup->numframes = numframes;
|
||||
|
||||
*ppframe = (mspriteframe_t *)pspritegroup;
|
||||
pin_intervals = (const dspriteinterval_t *)( pingroup + 1 );
|
||||
poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));
|
||||
pspritegroup->intervals = poutintervals;
|
||||
|
||||
for( int i = 0; i < numframes; i++ )
|
||||
{
|
||||
*poutintervals = pin_intervals->interval;
|
||||
if( *poutintervals <= 0.0f )
|
||||
*poutintervals = 1.0f; // set error value
|
||||
poutintervals++;
|
||||
pin_intervals++;
|
||||
}
|
||||
|
||||
ptemp = (const void *)pin_intervals;
|
||||
for( int i = 0; i < numframes; i++ )
|
||||
{
|
||||
ptemp = Mod_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );
|
||||
}
|
||||
|
||||
return ptemp;
|
||||
}
|
||||
|
||||
void Mod_SpriteLoadTextures( model_t *mod, const void *buffer )
|
||||
{
|
||||
const dsprite_t *pin = buffer;
|
||||
const short *numi = NULL;
|
||||
const byte *pframetype;
|
||||
msprite_t *psprite = mod->cache.data;
|
||||
int i;
|
||||
|
||||
if( pin->version == SPRITE_VERSION_Q1 || pin->version == SPRITE_VERSION_32 )
|
||||
numi = NULL;
|
||||
else if( pin->version == SPRITE_VERSION_HL )
|
||||
numi = (const short *)((const byte *)buffer + sizeof( dsprite_hl_t ));
|
||||
|
||||
r_texFlags = mod->numtexinfo;
|
||||
sprite_version = pin->version;
|
||||
Q_strncpy( sprite_name, mod->name, sizeof( sprite_name ));
|
||||
COM_StripExtension( sprite_name );
|
||||
|
||||
if( numi == NULL )
|
||||
{
|
||||
rgbdata_t *pal;
|
||||
|
||||
pal = FS_LoadImage( "#id.pal", (byte *)&i, 768 );
|
||||
pframetype = (const byte *)buffer + sizeof( dsprite_q1_t );
|
||||
FS_FreeImage( pal );
|
||||
}
|
||||
else if( *numi <= 256 )
|
||||
{
|
||||
const byte *src = (const byte *)( numi + 1 );
|
||||
rgbdata_t *pal;
|
||||
size_t pal_bytes = *numi * 3;
|
||||
|
||||
// install palette
|
||||
switch( psprite->texFormat )
|
||||
{
|
||||
case SPR_INDEXALPHA:
|
||||
pal = FS_LoadImage( "#gradient.pal", src, pal_bytes );
|
||||
break;
|
||||
case SPR_ALPHTEST:
|
||||
pal = FS_LoadImage( "#masked.pal", src, pal_bytes );
|
||||
break;
|
||||
default:
|
||||
pal = FS_LoadImage( "#texgamma.pal", src, pal_bytes );
|
||||
break;
|
||||
}
|
||||
|
||||
pframetype = (const byte *)( src + pal_bytes );
|
||||
FS_FreeImage( pal );
|
||||
}
|
||||
else
|
||||
{
|
||||
Con_DPrintf( S_ERROR "%s has wrong number of palette colors %i (should be less or equal than 256)\n", mod->name, *numi );
|
||||
return;
|
||||
}
|
||||
|
||||
if( mod->numframes < 1 )
|
||||
return;
|
||||
|
||||
for( i = 0; i < mod->numframes; i++ )
|
||||
{
|
||||
frametype_t frametype;
|
||||
dframetype_t dframetype;
|
||||
|
||||
memcpy( &dframetype, pframetype, sizeof( dframetype ));
|
||||
frametype = dframetype.type;
|
||||
psprite->frames[i].type = (spriteframetype_t)frametype;
|
||||
|
||||
switch( frametype )
|
||||
{
|
||||
case FRAME_SINGLE:
|
||||
Q_strncpy( group_suffix, "frame", sizeof( group_suffix ));
|
||||
pframetype = Mod_SpriteLoadFrame( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
|
||||
break;
|
||||
case FRAME_GROUP:
|
||||
Q_strncpy( group_suffix, "group", sizeof( group_suffix ));
|
||||
pframetype = Mod_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
|
||||
break;
|
||||
case FRAME_ANGLED:
|
||||
Q_strncpy( group_suffix, "angle", sizeof( group_suffix ));
|
||||
pframetype = Mod_SpriteLoadGroup( mod, pframetype + sizeof( dframetype_t ), &psprite->frames[i].frameptr, i );
|
||||
break;
|
||||
}
|
||||
if( pframetype == NULL )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Mod_SpriteUnloadTextures( void *data )
|
||||
{
|
||||
msprite_t *psprite = data;
|
||||
|
||||
if( !data )
|
||||
return;
|
||||
|
||||
// release all textures
|
||||
for( int i = 0; i < psprite->numframes; i++ )
|
||||
{
|
||||
if( !psprite->frames[i].frameptr )
|
||||
continue;
|
||||
|
||||
if( psprite->frames[i].type == SPR_SINGLE )
|
||||
{
|
||||
ref.dllFuncs.GL_FreeTexture( psprite->frames[i].frameptr->gl_texturenum );
|
||||
}
|
||||
else
|
||||
{
|
||||
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;
|
||||
|
||||
for( int j = 0; j < pspritegroup->numframes; j++ )
|
||||
{
|
||||
if( pspritegroup->frames[j] )
|
||||
ref.dllFuncs.GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,7 @@ AVI PLAYING
|
||||
*/
|
||||
|
||||
static movie_state_t *cin_state;
|
||||
static int cin_texture;
|
||||
|
||||
/*
|
||||
==================
|
||||
@@ -191,11 +192,9 @@ SCR_PlayCinematic
|
||||
qboolean SCR_PlayCinematic( const char *arg )
|
||||
{
|
||||
int x, y, w, h;
|
||||
const char *fullpath;
|
||||
const char *fullpath = FS_GetDiskPath( arg, false );
|
||||
double video_ratio, screen_ratio, scale;
|
||||
|
||||
fullpath = FS_GetDiskPath( arg, false );
|
||||
|
||||
if( FS_FileExists( arg, false ) && !fullpath )
|
||||
{
|
||||
Con_Printf( S_ERROR "Couldn't load %s from packfile. Please extract it\n", arg );
|
||||
@@ -281,6 +280,12 @@ void SCR_InitCinematic( void )
|
||||
{
|
||||
AVI_Initailize ();
|
||||
cin_state = AVI_GetState( CIN_MAIN );
|
||||
cin_texture = ref.dllFuncs.GL_CreateTexture( "*cintexture", 64, 64, NULL, TF_NOMIPMAP|TF_CLAMP );
|
||||
}
|
||||
|
||||
int SCR_GetCinematicTexture( void )
|
||||
{
|
||||
return cin_texture;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -187,18 +187,15 @@ merge refdef with overview settings
|
||||
static void V_RefApplyOverview( ref_viewpass_t *rvp )
|
||||
{
|
||||
ref_overview_t *ov = &clgame.overView;
|
||||
float aspect;
|
||||
float size_x, size_y;
|
||||
vec2_t mins, maxs;
|
||||
|
||||
if( !CL_IsDevOverviewMode( ))
|
||||
return;
|
||||
|
||||
// NOTE: Xash3D may use 16:9 or 16:10 aspects
|
||||
aspect = (float)refState.width / (float)refState.height;
|
||||
float aspect = (float)refState.width / (float)refState.height;
|
||||
|
||||
size_x = fabs( 8192.0f / ov->flZoom );
|
||||
size_y = fabs( 8192.0f / (ov->flZoom * aspect ));
|
||||
float size_x = fabs( 8192.0f / ov->flZoom );
|
||||
float size_y = fabs( 8192.0f / (ov->flZoom * aspect ));
|
||||
|
||||
// compute rectangle
|
||||
ov->xLeft = -(size_x / 2);
|
||||
@@ -214,8 +211,8 @@ static void V_RefApplyOverview( ref_viewpass_t *rvp )
|
||||
|
||||
VectorCopy( ov->origin, rvp->vieworigin );
|
||||
rvp->vieworigin[2] = ov->zFar + ov->zNear;
|
||||
Vector2Copy( rvp->vieworigin, mins );
|
||||
Vector2Copy( rvp->vieworigin, maxs );
|
||||
vec2_t mins = Vec2( rvp->vieworigin );
|
||||
vec2_t maxs = Vec2( rvp->vieworigin );
|
||||
|
||||
mins[!ov->rotated] += ov->xLeft;
|
||||
maxs[!ov->rotated] += ov->xRight;
|
||||
@@ -238,13 +235,11 @@ V_CalcFov
|
||||
*/
|
||||
static float V_CalcFov( float *fov_x, float width, float height )
|
||||
{
|
||||
float x, half_fov_y;
|
||||
|
||||
if( *fov_x < 1.0f || *fov_x > 179.0f )
|
||||
*fov_x = 90.0f; // default value
|
||||
|
||||
x = width / tan( DEG2RAD( *fov_x ) * 0.5f );
|
||||
half_fov_y = atan( height / x );
|
||||
float x = width / tan( DEG2RAD( *fov_x ) * 0.5f );
|
||||
float half_fov_y = atan( height / x );
|
||||
|
||||
return RAD2DEG( half_fov_y ) * 2;
|
||||
}
|
||||
@@ -360,6 +355,29 @@ qboolean V_PreRender( void )
|
||||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
====================
|
||||
V_ApplyRefUnderwater
|
||||
|
||||
apply underwater fov effect
|
||||
====================
|
||||
*/
|
||||
static void V_ApplyRefUnderwater( ref_viewpass_t *rvp )
|
||||
{
|
||||
if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP|RF_DRAW_OVERVIEW ))
|
||||
return;
|
||||
|
||||
if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
|
||||
return;
|
||||
|
||||
if( cl.local.waterlevel < 3 )
|
||||
return;
|
||||
|
||||
rvp->fov_x = atan( tan( DEG2RAD( rvp->fov_x ) / 2 ) * ( 0.97f + sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
|
||||
rvp->fov_y = atan( tan( DEG2RAD( rvp->fov_y ) / 2 ) * ( 1.03f - sin( cl.time * 1.5f ) * 0.03f )) * 2 / (M_PI_F / 180.0f);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
==================
|
||||
V_RenderView
|
||||
@@ -389,6 +407,7 @@ void V_RenderView( void )
|
||||
clgame.dllFuncs.pfnCalcRefdef( &rp );
|
||||
V_GetRefParams( &rp, &rvp );
|
||||
V_RefApplyOverview( &rvp );
|
||||
V_ApplyRefUnderwater( &rvp );
|
||||
|
||||
if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))
|
||||
{
|
||||
@@ -476,13 +495,12 @@ static void R_ShowTree( void )
|
||||
{
|
||||
float x = (float)((refState.width - (int)POINT_SIZE) >> 1);
|
||||
float y = NODE_INTERVAL_Y(1.0f);
|
||||
mleaf_t *viewleaf;
|
||||
|
||||
if( !cl.worldmodel || !r_showtree.value )
|
||||
return;
|
||||
|
||||
world.recursion_level = 0;
|
||||
viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );
|
||||
mleaf_t *viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );
|
||||
|
||||
ref.dllFuncs.TriRenderMode( kRenderTransTexture );
|
||||
|
||||
@@ -535,6 +553,8 @@ void V_PostRender( void )
|
||||
SCR_DrawEnts();
|
||||
SCR_DrawNetGraph();
|
||||
SCR_DrawUserCmd();
|
||||
Joy_DrawDebug();
|
||||
IN_GyroDrawDebug();
|
||||
SV_DrawOrthoTriangles();
|
||||
CL_DrawDemoRecording();
|
||||
CL_DrawHUD( CL_CHANGELEVEL );
|
||||
|
||||
@@ -26,6 +26,7 @@ GNU General Public License for more details.
|
||||
#include "pm_defs.h"
|
||||
#include "ref_params.h"
|
||||
#include "render_api.h"
|
||||
#include "sound_api.h"
|
||||
#include "cdll_exp.h"
|
||||
#include "screenfade.h"
|
||||
#include "protocol.h"
|
||||
@@ -36,11 +37,6 @@ GNU General Public License for more details.
|
||||
#include "voice.h"
|
||||
#include "q_client.h"
|
||||
|
||||
// client sprite types
|
||||
#define SPR_CLIENT 0 // client sprite for temp-entities or user-textures
|
||||
#define SPR_HUDSPRITE 1 // hud sprite
|
||||
#define SPR_MAPSPRITE 2 // contain overview.bmp that diced into frames 128x128
|
||||
|
||||
//=============================================================================
|
||||
typedef struct netbandwithgraph_s
|
||||
{
|
||||
@@ -111,6 +107,8 @@ extern int CL_UPDATE_BACKUP;
|
||||
|
||||
#define MAX_EX_INTERP 0.1f
|
||||
|
||||
#define MAX_TEXTCHANNELS 8 // must be power of two (GoldSrc uses 4 channels)
|
||||
|
||||
#define CL_MIN_RESEND_TIME 1.5f // mininum time gap (in seconds) before a subsequent connection request is sent.
|
||||
#define CL_MAX_RESEND_TIME 20.0f // max time. The cvar cl_resend is bounded by these.
|
||||
|
||||
@@ -310,6 +308,14 @@ typedef enum
|
||||
CL_CHANGELEVEL, // draw 'loading' during changelevel
|
||||
} scrstate_t;
|
||||
|
||||
// client sprite types
|
||||
enum
|
||||
{
|
||||
SPR_CLIENT = 0, // client sprite for temp-entities or user-textures
|
||||
SPR_HUDSPRITE, // hud sprite
|
||||
SPR_MAPSPRITE, // contain overview.bmp that diced into frames 128x128
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char name[32];
|
||||
@@ -449,6 +455,7 @@ typedef struct
|
||||
void *hInstance; // pointer to client.dll
|
||||
cldll_func_t dllFuncs; // dll exported funcs
|
||||
render_interface_t drawFuncs; // custom renderer support
|
||||
sound_interface_t soundFuncs; // custom sound support
|
||||
poolhandle_t mempool; // client edicts pool
|
||||
string mapname; // map name
|
||||
string maptitle; // display map title
|
||||
@@ -572,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;
|
||||
@@ -637,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;
|
||||
@@ -731,10 +738,7 @@ extern convar_t cl_fixmodelinterpolationartifacts;
|
||||
|
||||
//=============================================================================
|
||||
|
||||
void CL_SetLightstyle( int style, const char* s, float f );
|
||||
void CL_DecayLights( void );
|
||||
dlight_t *CL_GetDynamicLight( int number );
|
||||
dlight_t *CL_GetEntityLight( int number );
|
||||
extern client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS];
|
||||
|
||||
//=================================================
|
||||
|
||||
@@ -757,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
|
||||
@@ -787,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
|
||||
@@ -853,6 +859,7 @@ void CL_FreeEdicts( void );
|
||||
void CL_ClearWorld( void );
|
||||
void CL_DrawCenterPrint( void );
|
||||
void CL_ClearSpriteTextures( void );
|
||||
void CL_HudMessage( const char *pMessage );
|
||||
void CL_CenterPrint( const char *text, float y );
|
||||
client_textmessage_t *CL_TextMessageGet( const char *pName );
|
||||
void NetAPI_CancelAllRequests( void );
|
||||
@@ -869,7 +876,6 @@ struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend );
|
||||
void CL_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );
|
||||
void CL_DisableScissor( scissor_state_t *scissor );
|
||||
qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 );
|
||||
|
||||
static inline cl_entity_t *CL_EDICT_NUM( int index )
|
||||
{
|
||||
if( unlikely( !clgame.entities )) // not in game yet
|
||||
@@ -923,45 +929,23 @@ static inline cl_entity_t *CL_GetLocalPlayer( void )
|
||||
//
|
||||
// cl_parse.c
|
||||
//
|
||||
void CL_ParseSetAngle( sizebuf_t *msg );
|
||||
void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseResource( sizebuf_t *msg );
|
||||
void CL_ParseClientData( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_UpdateUserPings( sizebuf_t *msg );
|
||||
void CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseSignon( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseRestore( sizebuf_t *msg );
|
||||
void CL_ParseStaticDecal( sizebuf_t *msg );
|
||||
void CL_ParseAddAngle( sizebuf_t *msg );
|
||||
void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseMovevars( sizebuf_t *msg );
|
||||
void CL_ParseResourceRequest( sizebuf_t *msg );
|
||||
void CL_ParseCustomization( sizebuf_t *msg );
|
||||
void CL_ParseCrosshairAngle( sizebuf_t *msg );
|
||||
void CL_ParseSoundFade( sizebuf_t *msg );
|
||||
void CL_ParseFileTransferFailed( sizebuf_t *msg );
|
||||
void CL_ParseHLTV( sizebuf_t *msg );
|
||||
void CL_ParseDirector( sizebuf_t *msg );
|
||||
void CL_ParseVoiceInit( sizebuf_t *msg );
|
||||
void CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseResLocation( sizebuf_t *msg );
|
||||
void CL_ParseCvarValue( sizebuf_t *msg, const qboolean ext, const connprotocol_t proto );
|
||||
void CL_ParseServerMessage( sizebuf_t *msg );
|
||||
qboolean CL_ParseCommonDLLMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset );
|
||||
qboolean CL_ParseCommonMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset );
|
||||
qboolean CL_ParseCommonHLMessage( sizebuf_t *msg, connprotocol_t proto, int svc_num, int startoffset );
|
||||
void CL_ParseTempEntity( sizebuf_t *msg, connprotocol_t proto );
|
||||
qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf );
|
||||
qboolean CL_RequestMissingResources( void );
|
||||
void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseViewEntity( sizebuf_t *msg );
|
||||
void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto );
|
||||
void CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto );
|
||||
void CL_ParseFinaleCutscene( sizebuf_t *msg, int level );
|
||||
void CL_ParseTextMessage( sizebuf_t *msg );
|
||||
void CL_ParseExec( sizebuf_t *msg );
|
||||
void CL_BatchResourceRequest( qboolean initialize );
|
||||
int CL_EstimateNeededResources( void );
|
||||
|
||||
@@ -989,6 +973,13 @@ void SCR_DrawPos( void );
|
||||
void SCR_DrawEnts( void );
|
||||
void SCR_DrawUserCmd( void );
|
||||
|
||||
//
|
||||
// cl_sprite.c
|
||||
//
|
||||
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw );
|
||||
void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite );
|
||||
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame );
|
||||
|
||||
//
|
||||
// cl_netgraph.c
|
||||
//
|
||||
@@ -1083,6 +1074,10 @@ void R_AddEfrags( cl_entity_t *ent );
|
||||
// cl_tent.c
|
||||
//
|
||||
struct particle_s;
|
||||
void CL_SetLightstyle( int style, const char* s, float f );
|
||||
void CL_DecayLights( void );
|
||||
dlight_t *CL_GetDynamicLight( int number );
|
||||
dlight_t *CL_GetEntityLight( int number );
|
||||
void CL_WeaponAnim( int iAnim, int body );
|
||||
void CL_ClearEffects( void );
|
||||
void CL_ClearEfrags( void );
|
||||
@@ -1160,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 );
|
||||
@@ -1214,14 +1209,14 @@ void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );
|
||||
void SteamBroker_Init( void );
|
||||
void SteamBroker_Shutdown( void );
|
||||
void SteamBroker_Frame( void );
|
||||
void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg );
|
||||
int SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
|
||||
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
|
||||
void SteamBroker_TerminateGameConnection( void );
|
||||
|
||||
//
|
||||
// cl_video.c
|
||||
//
|
||||
void SCR_InitCinematic( void );
|
||||
int SCR_GetCinematicTexture( void );
|
||||
void SCR_FreeCinematic( void );
|
||||
qboolean SCR_PlayCinematic( const char *name );
|
||||
qboolean SCR_DrawCinematic( void );
|
||||
@@ -1247,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;
|
||||
|
||||
@@ -1573,7 +1543,7 @@ void Key_Console( int key )
|
||||
}
|
||||
|
||||
// command completion
|
||||
if( key == K_TAB || key == K_L2_BUTTON )
|
||||
if( key == K_TAB || key == K_L2_BUTTON || key == K_LTRIGGER )
|
||||
{
|
||||
Con_CompleteCommand( &con.input, true );
|
||||
Con_Bottom();
|
||||
@@ -1704,15 +1674,13 @@ The input line scrolls horizontally if typing goes beyond the right edge
|
||||
*/
|
||||
static void Con_DrawInput( int lines )
|
||||
{
|
||||
int x, y;
|
||||
|
||||
// don't draw anything (always draw if not active)
|
||||
if( cls.key_dest != key_console || !con.curFont )
|
||||
return;
|
||||
|
||||
y = lines - ( con.curFont->charHeight * 2 );
|
||||
int y = lines - ( con.curFont->charHeight * 2 );
|
||||
CL_DrawCharacter( con.curFont->charWidths[' '], y, ']', g_color_table[7], con.curFont, 0 );
|
||||
x = Field_DrawInputLine( con.curFont->charWidths[' ']*2, y, &con.input, 255, true );
|
||||
int x = Field_DrawInputLine( con.curFont->charWidths[' ']*2, y, &con.input, 255, true );
|
||||
|
||||
// HACKHACK: avoid rendering issues when scroll != 0
|
||||
if( con_showcompletion.value && con.input.scroll == 0 )
|
||||
@@ -1744,18 +1712,16 @@ Custom debug messages
|
||||
static int Con_DrawDebugLines( void )
|
||||
{
|
||||
notify_t *notify = con.notify;
|
||||
int i, count = 0;
|
||||
int defaultX;
|
||||
int count = 0;
|
||||
int y = 20;
|
||||
int fontTall;
|
||||
|
||||
if( !con.curFont || !con.curFont->valid )
|
||||
return 0;
|
||||
|
||||
defaultX = refState.width / 4;
|
||||
fontTall = con.curFont->charHeight + 1;
|
||||
int defaultX = refState.width / 4;
|
||||
int fontTall = con.curFont->charHeight + 1;
|
||||
|
||||
for( i = 0; i < ARRAYSIZE( con.notify ); i++, notify++ )
|
||||
for( int i = 0; i < ARRAYSIZE( con.notify ); i++, notify++ )
|
||||
{
|
||||
int x, len;
|
||||
|
||||
@@ -1789,19 +1755,18 @@ Draws the debug messages (not passed to console history)
|
||||
void Con_DrawDebug( void )
|
||||
{
|
||||
static double timeStart;
|
||||
string dlstring;
|
||||
int x, y;
|
||||
|
||||
if( scr_download.value != -1.0f )
|
||||
{
|
||||
string dlstring;
|
||||
int length;
|
||||
Q_snprintf( dlstring, sizeof( dlstring ), "Downloading [%d remaining]: ^2%s^7 %5.1f%% time %.f secs",
|
||||
host.downloadcount, host.downloadfile, scr_download.value, Platform_DoubleTime() - timeStart );
|
||||
|
||||
Con_DrawStringLen( dlstring, &length, NULL );
|
||||
length = Q_max( length, 300 );
|
||||
x = refState.width - length * 1.05f;
|
||||
y = con.curFont->charHeight * 1.05f;
|
||||
int x = refState.width - length * 1.05f;
|
||||
int y = con.curFont->charHeight * 1.05f;
|
||||
Con_DrawString( x, y, dlstring, g_color_table[7] );
|
||||
}
|
||||
else
|
||||
@@ -1829,15 +1794,15 @@ Draws the last few lines of output transparently over the game top
|
||||
static void Con_DrawNotify( void )
|
||||
{
|
||||
double time = cl.time;
|
||||
int i, x, y = 0;
|
||||
int y = 0;
|
||||
|
||||
if( !con.curFont ) return;
|
||||
|
||||
x = con.curFont->charWidths[' ']; // offset one space at left screen side
|
||||
int x = con.curFont->charWidths[' ']; // offset one space at left screen side
|
||||
|
||||
if( host.allow_console && !Con_BackgroundMapActive( ))
|
||||
{
|
||||
for( i = Q_max( 0, CON_LINES_COUNT - con.num_times ); i < CON_LINES_COUNT; i++ )
|
||||
for( int i = Q_max( 0, CON_LINES_COUNT - con.num_times ); i < CON_LINES_COUNT; i++ )
|
||||
{
|
||||
con_lineinfo_t *l = &CON_LINES( i );
|
||||
|
||||
@@ -1905,13 +1870,13 @@ of it based on con.backscroll.
|
||||
*/
|
||||
static void Con_LastVisibleLine( int *lastline )
|
||||
{
|
||||
int i, lines_seen = 0;
|
||||
int lines_seen = 0;
|
||||
|
||||
con.backscroll = Q_max( 0, con.backscroll );
|
||||
*lastline = 0;
|
||||
|
||||
// now count until we saw con_backscroll actual lines
|
||||
for( i = CON_LINES_COUNT - 1; i >= 0; i-- )
|
||||
for( int i = CON_LINES_COUNT - 1; i >= 0; i-- )
|
||||
{
|
||||
// line is the last visible line?
|
||||
*lastline = i;
|
||||
@@ -1935,10 +1900,10 @@ Draws the console with the solid background
|
||||
*/
|
||||
static void Con_DrawSolidConsole( int lines )
|
||||
{
|
||||
int i, x, y;
|
||||
int x, y;
|
||||
float fraction;
|
||||
int start;
|
||||
int stringLen, width = 0, charH;
|
||||
int stringLen, charH;
|
||||
string curbuild;
|
||||
byte color[4];
|
||||
|
||||
@@ -2086,8 +2051,6 @@ void Con_DrawVersion( void )
|
||||
{
|
||||
// draws the current build
|
||||
byte *color = g_color_table[7];
|
||||
int stringLen, charH = 0;
|
||||
int start, height = refState.height;
|
||||
string curbuild;
|
||||
|
||||
if( !scr_drawversion.value )
|
||||
@@ -2116,9 +2079,10 @@ void Con_DrawVersion( void )
|
||||
XASH_ENGINE_NAME " v%i/" XASH_VERSION " (%s-%s build %i)", PROTOCOL_VERSION, Q_buildos(), Q_buildarch(), Q_buildnum( ));
|
||||
}
|
||||
|
||||
int stringLen, charH = 0;
|
||||
Con_DrawStringLen( curbuild, &stringLen, &charH );
|
||||
start = refState.width - stringLen * 1.05f;
|
||||
height -= charH * 1.05f;
|
||||
int start = refState.width - stringLen * 1.05f;
|
||||
int height = refState.height - charH * 1.05f;
|
||||
|
||||
Con_DrawString( start, height, curbuild, color );
|
||||
}
|
||||
@@ -2132,8 +2096,6 @@ Scroll it up or down
|
||||
*/
|
||||
void Con_RunConsole( void )
|
||||
{
|
||||
float lines_per_frame;
|
||||
|
||||
Con_SetColor( );
|
||||
|
||||
// decide on the destination height of the console
|
||||
@@ -2149,7 +2111,7 @@ void Con_RunConsole( void )
|
||||
}
|
||||
else con.showlines = 0; // none visible
|
||||
|
||||
lines_per_frame = fabs( scr_conspeed.value ) * host.realframetime;
|
||||
float lines_per_frame = fabs( scr_conspeed.value ) * host.realframetime;
|
||||
|
||||
if( con.showlines < con.vislines )
|
||||
{
|
||||
@@ -2164,8 +2126,11 @@ void Con_RunConsole( void )
|
||||
con.vislines = con.showlines;
|
||||
}
|
||||
|
||||
if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED ))
|
||||
if( FBitSet( con_charset.flags|con_fontscale.flags|con_fontnum.flags|cl_charset.flags|con_oldfont.flags, FCVAR_CHANGED ))
|
||||
{
|
||||
if( con_fontscale.value < 1.0f )
|
||||
Cvar_DirectSet( &con_fontscale, "1" );
|
||||
|
||||
// update codepage parameters
|
||||
if( !Q_stricmp( con_charset.string, "cp1251" ))
|
||||
{
|
||||
@@ -2221,9 +2186,7 @@ void Con_CharEvent( int key )
|
||||
|
||||
static int Con_LoadSimpleConback( const char *name, int flags )
|
||||
{
|
||||
int i;
|
||||
|
||||
for( i = 0; i < 5; i++ )
|
||||
for( int i = 0; i < 5; i++ )
|
||||
{
|
||||
string path;
|
||||
|
||||
@@ -2308,15 +2271,13 @@ void Con_VidInit( void )
|
||||
{
|
||||
lmp_t *cb = (lmp_t *)FS_LoadFile( "gfx/conback.lmp", &length, false );
|
||||
char ver[64];
|
||||
byte *dest;
|
||||
int x, y, len;
|
||||
|
||||
if( cb && cb->width == 320 && cb->height == 200 )
|
||||
{
|
||||
len = Q_snprintf( ver, 64, "%i", Q_buildnum( )); // can store only buildnum
|
||||
dest = (byte *)(cb + 1) + 320 * 186 + 320 - 11 - 8 * len;
|
||||
y = len;
|
||||
for( x = 0; x < y; x++ )
|
||||
int len = Q_snprintf( ver, 64, "%i", Q_buildnum( )); // can store only buildnum
|
||||
byte *dest = (byte *)(cb + 1) + 320 * 186 + 320 - 11 - 8 * len;
|
||||
int y = len;
|
||||
for( int x = 0; x < y; x++ )
|
||||
Con_DrawCharToConback( ver[x], buf, dest + (x << 3));
|
||||
con.background = ref.dllFuncs.GL_LoadTexture( "#gfx/conback.lmp", (byte *)cb, length, TF_IMAGE );
|
||||
}
|
||||
@@ -2341,8 +2302,7 @@ Con_InvalidateFonts
|
||||
*/
|
||||
void Con_InvalidateFonts( void )
|
||||
{
|
||||
int i;
|
||||
for( i = 0; i < ARRAYSIZE( con.chars ); i++ )
|
||||
for( int i = 0; i < ARRAYSIZE( con.chars ); i++ )
|
||||
CL_FreeFont( &con.chars[i] );
|
||||
con.curFont = NULL;
|
||||
}
|
||||
@@ -2396,9 +2356,8 @@ static void Test_RunConHistory( void )
|
||||
const char *strs1[] = { "map t0a0", "quit", "wtf", "wtf", "", "nyan" };
|
||||
const char *strs2[] = { "nyan", "wtf", "quit", "map t0a0" };
|
||||
const char *testbackup = "unfinished_edit";
|
||||
int i;
|
||||
|
||||
for( i = 0; i < ARRAYSIZE( strs1 ); i++ )
|
||||
for( int i = 0; i < ARRAYSIZE( strs1 ); i++ )
|
||||
{
|
||||
Field_Set( &input, strs1[i] );
|
||||
Con_HistoryAppend( &hist, &input );
|
||||
@@ -2406,7 +2365,7 @@ static void Test_RunConHistory( void )
|
||||
|
||||
Field_Set( &input, testbackup );
|
||||
|
||||
for( i = 0; i < ARRAYSIZE( strs2 ); i++ )
|
||||
for( int i = 0; i < ARRAYSIZE( strs2 ); i++ )
|
||||
{
|
||||
Con_HistoryUp( &hist, &input );
|
||||
TASSERT_STR( input.buffer, strs2[i] );
|
||||
@@ -2415,7 +2374,7 @@ static void Test_RunConHistory( void )
|
||||
// check for overrun
|
||||
Con_HistoryUp( &hist, &input );
|
||||
|
||||
for( i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- )
|
||||
for( int i = ARRAYSIZE( strs2 ) - 1; i >= 0; i-- )
|
||||
{
|
||||
TASSERT_STR( input.buffer, strs2[i] );
|
||||
Con_HistoryDown( &hist, &input );
|
||||
|
||||
@@ -23,6 +23,8 @@ GNU General Public License for more details.
|
||||
#include "client.h"
|
||||
#include "const.h"
|
||||
#include "triangleapi.h"
|
||||
#include "r_studioint.h"
|
||||
#include "mod_local.h"
|
||||
#include "r_efx.h"
|
||||
#include "demo_api.h"
|
||||
#include "ivoicetweak.h"
|
||||
@@ -37,11 +39,20 @@ GNU General Public License for more details.
|
||||
#include "platform/platform.h"
|
||||
|
||||
#define MAX_LINELENGTH 80
|
||||
#define MAX_TEXTCHANNELS 8 // must be power of two (GoldSrc uses 4 channels)
|
||||
#define TEXT_MSGNAME "TextMessage%i"
|
||||
#define TEXT_MSGNAME "TextMessage"
|
||||
|
||||
static char cl_textbuffer[MAX_TEXTCHANNELS][2048];
|
||||
static client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS];
|
||||
client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS] =
|
||||
{
|
||||
{ .pName = "TextMessage0", .pMessage = cl_textbuffer[0] },
|
||||
{ .pName = "TextMessage1", .pMessage = cl_textbuffer[1] },
|
||||
{ .pName = "TextMessage2", .pMessage = cl_textbuffer[2] },
|
||||
{ .pName = "TextMessage3", .pMessage = cl_textbuffer[3] },
|
||||
{ .pName = "TextMessage4", .pMessage = cl_textbuffer[4] },
|
||||
{ .pName = "TextMessage5", .pMessage = cl_textbuffer[5] },
|
||||
{ .pName = "TextMessage6", .pMessage = cl_textbuffer[6] },
|
||||
{ .pName = "TextMessage7", .pMessage = cl_textbuffer[7] },
|
||||
};
|
||||
|
||||
static const dllfunc_t cdll_exports[] =
|
||||
{
|
||||
@@ -96,6 +107,8 @@ static const dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and high
|
||||
{ "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext
|
||||
{ "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext
|
||||
{ "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext
|
||||
{ "HUD_GetSoundInterface", (void **)&clgame.dllFuncs.pfnGetSoundInterface }, // Xash3D FWGS ext
|
||||
{ "Voice_StartChannel", (void **)&clgame.dllFuncs.pfnVoice_StartChannel }, // Xash3D FWGS ext
|
||||
};
|
||||
|
||||
static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc );
|
||||
@@ -159,8 +172,6 @@ Initialize CD playlist
|
||||
*/
|
||||
static void CL_InitCDAudio( const char *filename )
|
||||
{
|
||||
byte *afile;
|
||||
char *pfile;
|
||||
string token;
|
||||
int c = 0;
|
||||
|
||||
@@ -170,16 +181,22 @@ 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 )
|
||||
{
|
||||
if( !Q_stricmp( token, "blank" ))
|
||||
{
|
||||
clgame.cdtracks[c][0] = '\0';
|
||||
}
|
||||
else if( token[0] == '/' ) // allow custom path
|
||||
{
|
||||
Q_strncpy( clgame.cdtracks[c], &token[1], sizeof( clgame.cdtracks[c] ));
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_snprintf( clgame.cdtracks[c], sizeof( clgame.cdtracks[c] ),
|
||||
@@ -299,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;
|
||||
@@ -350,20 +365,19 @@ draw hudsprite routine
|
||||
static void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc )
|
||||
{
|
||||
float s1, s2, t1, t2;
|
||||
int texnum;
|
||||
|
||||
if( width == -1 && height == -1 )
|
||||
{
|
||||
int w, h;
|
||||
|
||||
// assume we get sizes from image
|
||||
ref.dllFuncs.R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );
|
||||
R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );
|
||||
|
||||
width = w;
|
||||
height = h;
|
||||
}
|
||||
|
||||
texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame );
|
||||
int texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame );
|
||||
|
||||
if( prc )
|
||||
{
|
||||
@@ -411,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;
|
||||
@@ -427,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 )
|
||||
{
|
||||
@@ -447,6 +458,7 @@ void CL_DrawCenterPrint( void )
|
||||
continue;
|
||||
|
||||
line[lineLength] = number;
|
||||
int charWidth;
|
||||
CL_DrawCharacterLen( font, number, &charWidth, NULL );
|
||||
width += charWidth;
|
||||
lineLength++;
|
||||
@@ -458,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 );
|
||||
@@ -515,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;
|
||||
@@ -560,31 +570,18 @@ and hold them into permament memory pool
|
||||
*/
|
||||
static void CL_InitTitles( const char *filename )
|
||||
{
|
||||
fs_offset_t fileSize;
|
||||
byte *pMemFile;
|
||||
int i;
|
||||
|
||||
// initialize text messages (game_text)
|
||||
for( i = 0; i < MAX_TEXTCHANNELS; i++ )
|
||||
{
|
||||
char name[MAX_VA_STRING];
|
||||
|
||||
Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i );
|
||||
|
||||
cl_textmessage[i].pName = copystringpool( clgame.mempool, name );
|
||||
cl_textmessage[i].pMessage = cl_textbuffer[i];
|
||||
}
|
||||
|
||||
// clear out any old data that's sitting around.
|
||||
if( clgame.titles ) Mem_Free( clgame.titles );
|
||||
Mem_Free( clgame.titles );
|
||||
|
||||
clgame.titles = NULL;
|
||||
clgame.numTitles = 0;
|
||||
|
||||
pMemFile = FS_LoadFile( filename, &fileSize, false );
|
||||
if( !pMemFile ) return;
|
||||
fs_offset_t fileSize = 0;
|
||||
char *pMemFile = (char *)FS_LoadFile( filename, &fileSize, false );
|
||||
if( !pMemFile )
|
||||
return;
|
||||
|
||||
clgame.titles = CL_TextMessageParse( clgame.mempool, (char *)pMemFile, fileSize, &clgame.numTitles );
|
||||
clgame.titles = CL_TextMessageParse( clgame.mempool, pMemFile, fileSize, &clgame.numTitles );
|
||||
Mem_Free( pMemFile );
|
||||
}
|
||||
|
||||
@@ -613,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 ))
|
||||
{
|
||||
@@ -626,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);
|
||||
@@ -653,54 +648,6 @@ void CL_ParseTextMessage( sizebuf_t *msg )
|
||||
CL_HudMessage( text->pName );
|
||||
}
|
||||
|
||||
/*
|
||||
================
|
||||
CL_ParseFinaleCutscene
|
||||
|
||||
show display finale or cutscene message
|
||||
================
|
||||
*/
|
||||
void CL_ParseFinaleCutscene( sizebuf_t *msg, int level )
|
||||
{
|
||||
static int msgindex = 0;
|
||||
client_textmessage_t *text;
|
||||
int channel;
|
||||
|
||||
cl.intermission = level;
|
||||
|
||||
channel = msgindex;
|
||||
msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);
|
||||
|
||||
// grab message channel
|
||||
text = &cl_textmessage[channel];
|
||||
|
||||
// NOTE: svc_finale and svc_cutscene has a
|
||||
// predefined settings like Quake-style
|
||||
text->x = -1.0f;
|
||||
text->y = 0.15f;
|
||||
text->effect = 2; // scan out effect
|
||||
text->r1 = 245;
|
||||
text->g1 = 245;
|
||||
text->b1 = 245;
|
||||
text->a1 = 0; // unused
|
||||
text->r2 = 0;
|
||||
text->g2 = 0;
|
||||
text->b2 = 0;
|
||||
text->a2 = 0;
|
||||
text->fadein = 0.15f;
|
||||
text->fadeout = 0.0f;
|
||||
text->holdtime = 99999.0f;
|
||||
text->fxtime = 0.0f;
|
||||
|
||||
// to prevent grab too long messages
|
||||
Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 );
|
||||
|
||||
if( *text->pMessage == '\0' )
|
||||
return; // no real text
|
||||
|
||||
CL_HudMessage( text->pName );
|
||||
}
|
||||
|
||||
/*
|
||||
====================
|
||||
CL_GetMaxlients
|
||||
@@ -722,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 )
|
||||
{
|
||||
@@ -735,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 );
|
||||
@@ -782,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;
|
||||
|
||||
@@ -800,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 )
|
||||
{
|
||||
@@ -988,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;
|
||||
}
|
||||
@@ -1114,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;
|
||||
@@ -1254,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 );
|
||||
@@ -1285,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;
|
||||
|
||||
@@ -1322,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 ))
|
||||
{
|
||||
@@ -1352,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++ )
|
||||
{
|
||||
@@ -1398,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
|
||||
@@ -1416,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
|
||||
@@ -1432,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;
|
||||
@@ -1468,7 +1405,7 @@ int EXPORT pfnSPR_Frames( HSPRITE hPic )
|
||||
{
|
||||
int numFrames = 0;
|
||||
|
||||
ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
|
||||
R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
@@ -1483,7 +1420,7 @@ static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame )
|
||||
{
|
||||
int sprHeight = 0;
|
||||
|
||||
ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
|
||||
R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
|
||||
|
||||
return sprHeight;
|
||||
}
|
||||
@@ -1498,7 +1435,7 @@ static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame )
|
||||
{
|
||||
int sprWidth = 0;
|
||||
|
||||
ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
|
||||
R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
|
||||
|
||||
return sprWidth;
|
||||
}
|
||||
@@ -1910,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] )
|
||||
@@ -1921,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;
|
||||
@@ -1952,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 );
|
||||
@@ -1974,13 +1907,12 @@ client_textmessage_t *CL_TextMessageGet( const char *pName )
|
||||
int i;
|
||||
|
||||
// first check internal messages
|
||||
for( i = 0; i < MAX_TEXTCHANNELS; i++ )
|
||||
if( Q_strlen( pName ) == sizeof( TEXT_MSGNAME ) // including the digit
|
||||
&& !Q_strncmp( pName, TEXT_MSGNAME, sizeof( TEXT_MSGNAME ) - 1 ))
|
||||
{
|
||||
char name[MAX_VA_STRING];
|
||||
i = pName[sizeof( TEXT_MSGNAME ) - 1] - '0';
|
||||
|
||||
Q_snprintf( name, sizeof( name ), TEXT_MSGNAME, i );
|
||||
|
||||
if( !Q_strcmp( pName, name ))
|
||||
if( i >= 0 && i < MAX_TEXTCHANNELS )
|
||||
return cl_textmessage + i;
|
||||
}
|
||||
|
||||
@@ -2218,7 +2150,7 @@ pfnGetViewModel
|
||||
|
||||
=============
|
||||
*/
|
||||
static cl_entity_t* GAME_EXPORT CL_GetViewModel( void )
|
||||
static cl_entity_t *GAME_EXPORT CL_GetViewModel( void )
|
||||
{
|
||||
return &clgame.viewent;
|
||||
}
|
||||
@@ -2243,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 )
|
||||
{
|
||||
@@ -2259,7 +2189,7 @@ static void GAME_EXPORT pfnCalcShake( void )
|
||||
return;
|
||||
}
|
||||
|
||||
frametime = cl_clientframetime();
|
||||
float frametime = cl_clientframetime();
|
||||
|
||||
if( cl.time > shake->next_shake )
|
||||
{
|
||||
@@ -2267,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
|
||||
@@ -2392,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;
|
||||
@@ -2403,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 )
|
||||
{
|
||||
@@ -2438,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;
|
||||
@@ -2446,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;
|
||||
@@ -2778,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;
|
||||
@@ -2856,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 ))
|
||||
{
|
||||
@@ -2942,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;
|
||||
@@ -3095,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;
|
||||
@@ -3279,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 );
|
||||
}
|
||||
@@ -3330,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;
|
||||
@@ -3367,9 +3284,9 @@ bind current texture
|
||||
*/
|
||||
int TriSpriteTexture( model_t *pSpriteModel, int frame )
|
||||
{
|
||||
int gl_texturenum;
|
||||
int gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame );
|
||||
|
||||
if(( gl_texturenum = ref.dllFuncs.R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
|
||||
if( gl_texturenum <= 0 )
|
||||
return 0;
|
||||
|
||||
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );
|
||||
@@ -3510,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 )
|
||||
{
|
||||
@@ -3541,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;
|
||||
@@ -3657,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;
|
||||
|
||||
@@ -3879,7 +3800,7 @@ static cl_enginefunc_t gEngfuncs =
|
||||
pfnHookEvent,
|
||||
Con_Visible,
|
||||
pfnGetGameDirectory,
|
||||
pfnCVarGetPointer,
|
||||
CL_CvarGetPointer,
|
||||
Key_LookupBinding,
|
||||
pfnGetLevelName,
|
||||
pfnGetScreenFade,
|
||||
@@ -3974,6 +3895,68 @@ void CL_UnloadProgs( void )
|
||||
memset( &clgame, 0, sizeof( clgame ));
|
||||
}
|
||||
|
||||
static model_t *pfnStudio_Mod_ForName( const char *name, int crash )
|
||||
{
|
||||
return Mod_ForName( name, crash, false );
|
||||
}
|
||||
|
||||
static void *pfnStudio_Mod_Extradata( model_t *mod )
|
||||
{
|
||||
return Mod_StudioExtradata( mod );
|
||||
}
|
||||
|
||||
static void pfnStudio_GetAliasScale( float *x, float *y )
|
||||
{
|
||||
if( x ) *x = 1.0f;
|
||||
if( y ) *y = 1.0f;
|
||||
}
|
||||
|
||||
static float ***pfnStudio_GetAliasTransform( void )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static model_t *pfnStudio_GetChromeSprite( void )
|
||||
{
|
||||
return cl_sprite_shell;
|
||||
}
|
||||
|
||||
static int pfnStudio_IsHardware( void )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static engine_studio_api_t gStudioAPI =
|
||||
{
|
||||
.Mem_Calloc = Mod_Calloc,
|
||||
.Cache_Check = Mod_CacheCheck,
|
||||
.LoadCacheFile = Mod_LoadCacheFile,
|
||||
.Mod_ForName = pfnStudio_Mod_ForName,
|
||||
.Mod_Extradata = pfnStudio_Mod_Extradata,
|
||||
.GetModelByIndex = CL_ModelHandle,
|
||||
.GetCvar = CL_CvarGetPointer,
|
||||
.GetChromeSprite = pfnStudio_GetChromeSprite,
|
||||
.GetAliasScale = pfnStudio_GetAliasScale,
|
||||
.StudioGetAliasTransform = pfnStudio_GetAliasTransform,
|
||||
.GetViewEntity = CL_GetViewModel,
|
||||
.IsHardware = pfnStudio_IsHardware,
|
||||
};
|
||||
|
||||
static void CL_InitStudioAPI( void )
|
||||
{
|
||||
static r_studio_interface_t gDefaultStudioDraw;
|
||||
|
||||
if( !ref.dllFuncs.R_StudioFillAPI( &gStudioAPI, &gDefaultStudioDraw ))
|
||||
return;
|
||||
|
||||
r_studio_interface_t *pStudioDraw = &gDefaultStudioDraw;
|
||||
|
||||
if( clgame.dllFuncs.pfnGetStudioModelInterface )
|
||||
clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI );
|
||||
|
||||
ref.dllFuncs.R_StudioSetDrawInterface( pStudioDraw );
|
||||
}
|
||||
|
||||
qboolean CL_LoadProgs( const char *name )
|
||||
{
|
||||
static playermove_t gpMove;
|
||||
@@ -3981,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();
|
||||
|
||||
@@ -4037,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'
|
||||
@@ -4068,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'
|
||||
@@ -4095,14 +4077,14 @@ qboolean CL_LoadProgs( const char *name )
|
||||
|
||||
CL_InitCDAudio( "media/cdaudio.txt" );
|
||||
CL_InitTitles( "titles.txt" );
|
||||
CL_InitParticles ();
|
||||
CL_InitViewBeams ();
|
||||
CL_InitTempEnts ();
|
||||
CL_InitParticles( );
|
||||
CL_InitViewBeams( );
|
||||
CL_InitTempEnts( );
|
||||
|
||||
if( !R_InitRenderAPI()) // Xash3D extension
|
||||
if( !R_InitRenderAPI( )) // Xash3D extension
|
||||
Con_Reportf( S_WARN "%s: couldn't get render API\n", __func__ );
|
||||
|
||||
if( !Mobile_Init() ) // Xash3D FWGS extension: mobile interface
|
||||
if( !Mobile_Init( )) // Xash3D FWGS extension: mobile interface
|
||||
Con_Reportf( S_WARN "%s: couldn't get mobility API\n", __func__ );
|
||||
|
||||
CL_InitEdicts( cl.maxclients ); // initailize local player and world
|
||||
@@ -4111,7 +4093,8 @@ qboolean CL_LoadProgs( const char *name )
|
||||
// initialize game
|
||||
clgame.dllFuncs.pfnInit();
|
||||
|
||||
ref.dllFuncs.CL_InitStudioAPI();
|
||||
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:
|
||||
@@ -214,6 +214,11 @@ static void pfnAVI_StreamSound( movie_state_t *avi, int entnum, float fvol, floa
|
||||
return; // stub, use AVI_SetParm and AVI_Think to stream AVI sound
|
||||
}
|
||||
|
||||
static void pfnAVI_UploadRawFrame( int texture, int cols, int rows, int width, int height, const byte *data )
|
||||
{
|
||||
ref.dllFuncs.GL_UpdateTexture( texture, cols, rows, width, height, data, PF_BGRA_32 );
|
||||
}
|
||||
|
||||
static render_api_t gRenderAPI =
|
||||
{
|
||||
pfnRenderGetParm, // GL_RenderGetParm,
|
||||
@@ -243,7 +248,7 @@ static render_api_t gRenderAPI =
|
||||
AVI_GetVideoInfo,
|
||||
AVI_GetVideoFrameNumber,
|
||||
AVI_GetVideoFrame,
|
||||
NULL, // R_UploadStretchRaw,
|
||||
pfnAVI_UploadRawFrame, // AVI_UploadRawFrame
|
||||
AVI_FreeVideo,
|
||||
AVI_IsActive,
|
||||
pfnAVI_StreamSound,
|
||||
@@ -283,40 +288,6 @@ static render_api_t gRenderAPI =
|
||||
COM_SetRandomSeed,
|
||||
};
|
||||
|
||||
static void R_FillRenderAPIFromRef( render_api_t *to, const ref_interface_t *from )
|
||||
{
|
||||
to->GetDetailScaleForTexture = from->GetDetailScaleForTexture;
|
||||
to->GetExtraParmsForTexture = from->GetExtraParmsForTexture;
|
||||
to->GetFrameTime = from->GetFrameTime;
|
||||
to->R_SetCurrentEntity = from->R_SetCurrentEntity;
|
||||
to->R_SetCurrentModel = from->R_SetCurrentModel;
|
||||
to->GL_FindTexture = from->GL_FindTexture;
|
||||
to->GL_TextureName = from->GL_TextureName;
|
||||
to->GL_TextureData = from->GL_TextureData;
|
||||
to->GL_LoadTexture = from->GL_LoadTexture;
|
||||
to->GL_CreateTexture = from->GL_CreateTexture;
|
||||
to->GL_LoadTextureArray = from->GL_LoadTextureArray;
|
||||
to->GL_CreateTextureArray = from->GL_CreateTextureArray;
|
||||
to->GL_FreeTexture = from->GL_FreeTexture;
|
||||
to->DrawSingleDecal = from->DrawSingleDecal;
|
||||
to->R_DecalSetupVerts = from->R_DecalSetupVerts;
|
||||
to->R_EntityRemoveDecals = from->R_EntityRemoveDecals;
|
||||
to->AVI_UploadRawFrame = from->AVI_UploadRawFrame;
|
||||
to->GL_Bind = from->GL_Bind;
|
||||
to->GL_SelectTexture = from->GL_SelectTexture;
|
||||
to->GL_LoadTextureMatrix = from->GL_LoadTextureMatrix;
|
||||
to->GL_TexMatrixIdentity = from->GL_TexMatrixIdentity;
|
||||
to->GL_CleanUpTextureUnits = from->GL_CleanUpTextureUnits;
|
||||
to->GL_TexGen = from->GL_TexGen;
|
||||
to->GL_TextureTarget = from->GL_TextureTarget;
|
||||
to->GL_TexCoordArrayMode = from->GL_TexCoordArrayMode;
|
||||
to->GL_UpdateTexSize = from->GL_UpdateTexSize;
|
||||
to->GL_DrawParticles = from->GL_DrawParticles;
|
||||
to->LightVec = from->LightVec;
|
||||
to->StudioGetTexture = from->StudioGetTexture;
|
||||
to->GL_GetProcAddress = from->R_GetProcAddress;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
R_InitRenderAPI
|
||||
@@ -329,8 +300,15 @@ qboolean R_InitRenderAPI( void )
|
||||
// make sure what render functions is cleared
|
||||
memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));
|
||||
|
||||
// fill missing functions from renderer
|
||||
R_FillRenderAPIFromRef( &gRenderAPI, &ref.dllFuncs );
|
||||
gRenderAPI.GetDetailScaleForTexture = ref.dllFuncs.R_GetDetailScaleForTexture;
|
||||
gRenderAPI.GL_FindTexture = ref.dllFuncs.GL_FindTexture;
|
||||
gRenderAPI.GL_TextureName = ref.dllFuncs.GL_TextureName;
|
||||
gRenderAPI.GL_TextureData = ref.dllFuncs.GL_TextureData;
|
||||
gRenderAPI.GL_LoadTexture = ref.dllFuncs.GL_LoadTexture;
|
||||
gRenderAPI.GL_FreeTexture = ref.dllFuncs.GL_FreeTexture;
|
||||
gRenderAPI.GL_Bind = ref.dllFuncs.GL_Bind;
|
||||
|
||||
ref.dllFuncs.R_FillRenderAPI( &gRenderAPI );
|
||||
|
||||
if( clgame.dllFuncs.pfnGetRenderInterface )
|
||||
{
|
||||
@@ -72,18 +72,15 @@ static qboolean CheckSkybox( const char *name, char out[SKYBOX_MAX_SIDES][MAX_ST
|
||||
{
|
||||
static const char *skybox_ext[3] = { "dds", "tga", "bmp" };
|
||||
static const char *skybox_delim[2] = { "", "_" }; // no space for HL style, underscore for Q1 style
|
||||
int i;
|
||||
|
||||
// search for skybox images
|
||||
for( i = 0; i < ARRAYSIZE( skybox_ext ); i++ )
|
||||
for( int i = 0; i < ARRAYSIZE( skybox_ext ); i++ )
|
||||
{
|
||||
int j;
|
||||
|
||||
for( j = 0; j < ARRAYSIZE( skybox_delim ); j++ )
|
||||
for( int j = 0; j < ARRAYSIZE( skybox_delim ); j++ )
|
||||
{
|
||||
int k, num_checked_sides = 0;
|
||||
int num_checked_sides = 0;
|
||||
|
||||
for( k = 0; k < SKYBOX_MAX_SIDES; k++ )
|
||||
for( int k = 0; k < SKYBOX_MAX_SIDES; k++ )
|
||||
{
|
||||
char sidename[MAX_VA_STRING];
|
||||
|
||||
@@ -109,7 +106,6 @@ void R_SetupSky( const char *name )
|
||||
char sidenames[SKYBOX_MAX_SIDES][MAX_STRING];
|
||||
int skyboxTextures[SKYBOX_MAX_SIDES] = { 0 };
|
||||
int i, len;
|
||||
qboolean result;
|
||||
|
||||
if( COM_StringEmptyOrNULL( name ))
|
||||
{
|
||||
@@ -125,10 +121,9 @@ void R_SetupSky( const char *name )
|
||||
|
||||
if( loadname[len - 1] == '_' )
|
||||
loadname[len - 1] = '\0';
|
||||
result = CheckSkybox( loadname, sidenames );
|
||||
|
||||
// to prevent infinite recursion if default skybox was missed
|
||||
if( !result && Q_stricmp( name, DEFAULT_SKYBOX_NAME ))
|
||||
if( !CheckSkybox( loadname, sidenames ) && Q_stricmp( name, DEFAULT_SKYBOX_NAME ))
|
||||
{
|
||||
Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", name );
|
||||
R_SetupSky( DEFAULT_SKYBOX_NAME ); // force to default
|
||||
@@ -287,13 +282,6 @@ static entity_state_t *R_StudioGetPlayerState( int index )
|
||||
return &cl.frames[cl.parsecountmod].playerstate[index];
|
||||
}
|
||||
|
||||
static int pfnGetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio )
|
||||
{
|
||||
return clgame.dllFuncs.pfnGetStudioModelInterface ?
|
||||
clgame.dllFuncs.pfnGetStudioModelInterface( version, ppinterface, pstudio ) :
|
||||
0;
|
||||
}
|
||||
|
||||
static const bpc_desc_t *pfnImage_GetPFDesc( int idx )
|
||||
{
|
||||
return &PFDesc[idx];
|
||||
@@ -329,11 +317,6 @@ static qboolean R_Init_Video_( ref_graphic_apis_t type )
|
||||
return R_Init_Video( type );
|
||||
}
|
||||
|
||||
static mleaf_t *pfnMod_PointInLeaf( const vec3_t p, mnode_t *node )
|
||||
{
|
||||
// FIXME: get rid of this on next RefAPI update
|
||||
return Mod_PointInLeaf( p, node, cl.models[1] );
|
||||
}
|
||||
|
||||
static const ref_api_t gEngfuncs =
|
||||
{
|
||||
@@ -375,7 +358,7 @@ static const ref_api_t gEngfuncs =
|
||||
|
||||
Mod_SampleSizeForFace,
|
||||
Mod_BoxVisible,
|
||||
pfnMod_PointInLeaf,
|
||||
Mod_PointInLeaf,
|
||||
R_DrawWorldHull,
|
||||
R_DrawModelHull,
|
||||
|
||||
@@ -411,7 +394,6 @@ static const ref_api_t gEngfuncs =
|
||||
Mod_CacheCheck,
|
||||
Mod_LoadCacheFile,
|
||||
Mod_Calloc,
|
||||
pfnGetStudioModelInterface,
|
||||
|
||||
_Mem_AllocPool,
|
||||
_Mem_FreePool,
|
||||
@@ -465,6 +447,7 @@ static const ref_api_t gEngfuncs =
|
||||
&g_fsapi,
|
||||
|
||||
R_GetWindowHandle,
|
||||
R_GetSpriteFrame,
|
||||
};
|
||||
|
||||
static void R_UnloadProgs( void )
|
||||
@@ -487,28 +470,78 @@ static void R_UnloadProgs( void )
|
||||
memset( &ref.dllFuncs, 0, sizeof( ref.dllFuncs ));
|
||||
}
|
||||
|
||||
static void CL_FillTriAPIFromRef( triangleapi_t *dst, const ref_interface_t *src )
|
||||
static void CL_FillTriAPI( triangleapi_t *dst )
|
||||
{
|
||||
dst->version = TRI_API_VERSION;
|
||||
dst->Begin = src->Begin;
|
||||
dst->RenderMode = TriRenderMode;
|
||||
dst->End = src->End;
|
||||
dst->Color4f = TriColor4f;
|
||||
dst->Color4ub = TriColor4ub;
|
||||
dst->TexCoord2f = src->TexCoord2f;
|
||||
dst->Vertex3f = src->Vertex3f;
|
||||
dst->Vertex3fv = src->Vertex3fv;
|
||||
dst->Brightness = TriBrightness;
|
||||
dst->CullFace = TriCullFace;
|
||||
dst->SpriteTexture = TriSpriteTexture;
|
||||
dst->WorldToScreen = TriWorldToScreen;
|
||||
dst->Fog = src->Fog;
|
||||
dst->ScreenToWorld = src->ScreenToWorld;
|
||||
dst->GetMatrix = src->GetMatrix;
|
||||
dst->BoxInPVS = TriBoxInPVS;
|
||||
dst->LightAtPoint = TriLightAtPoint;
|
||||
dst->Color4fRendermode = TriColor4fRendermode;
|
||||
dst->FogParams = src->FogParams;
|
||||
dst->Begin = ref.dllFuncs.Begin;
|
||||
dst->End = ref.dllFuncs.End;
|
||||
dst->Vertex3f = ref.dllFuncs.Vertex3f;
|
||||
dst->Vertex3fv = ref.dllFuncs.Vertex3fv;
|
||||
|
||||
ref.dllFuncs.R_FillTriAPI( dst );
|
||||
}
|
||||
|
||||
static void R_CreateBuiltinTextures( void )
|
||||
{
|
||||
// default checkerboard
|
||||
uint data16x16[256];
|
||||
for( int y = 0; y < 16; y++ )
|
||||
{
|
||||
for( int x = 0; x < 16; x++ )
|
||||
{
|
||||
if(( y < 8 ) ^ ( x < 8 ))
|
||||
data16x16[y * 16 + x] = HostFourCC( 255, 0, 255, 255 );
|
||||
else
|
||||
data16x16[y * 16 + x] = HostFourCC( 0, 0, 0, 255 );
|
||||
}
|
||||
}
|
||||
ref.dllFuncs.GL_CreateTexture( REF_DEFAULT_TEXTURE, 16, 16, data16x16, 0 );
|
||||
|
||||
// particle texture
|
||||
uint particle[64];
|
||||
for( int y = 0; y < 8; y++ )
|
||||
{
|
||||
for( int x = 0; x < 8; x++ )
|
||||
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 0 ); // fill it with invisible white
|
||||
}
|
||||
|
||||
// a1ba: moved one pixel down and to the right to make it look better with linear interpolation
|
||||
// set to 0 to make it look like in goldsrc
|
||||
const int corner = 1;
|
||||
for( int y = corner; y < 4 + corner; y++ )
|
||||
{
|
||||
for( int x = corner; x < 4 + corner; x++ )
|
||||
{
|
||||
if(( x == corner || x == 3 + corner ) && ( y == corner || y == 3 + corner ))
|
||||
continue;
|
||||
|
||||
particle[y * 8 + x] = HostFourCC( 255, 255, 255, 255 );
|
||||
}
|
||||
}
|
||||
ref.dllFuncs.GL_CreateTexture( REF_PARTICLE_TEXTURE, 8, 8, particle, TF_CLAMP|TF_HAS_ALPHA );
|
||||
|
||||
// solid colors
|
||||
uint data4x4[16];
|
||||
memset( data4x4, 0xFF, sizeof( data4x4 ));
|
||||
ref.dllFuncs.GL_CreateTexture( REF_WHITE_TEXTURE, 4, 4, data4x4, 0 );
|
||||
|
||||
for( int x = 0; x < 16; x++ )
|
||||
data4x4[x] = HostFourCC( 127, 127, 127, 255 );
|
||||
ref.dllFuncs.GL_CreateTexture( REF_GRAY_TEXTURE, 4, 4, data4x4, 0 );
|
||||
|
||||
for( int x = 0; x < 16; x++ )
|
||||
data4x4[x] = HostFourCC( 0, 0, 0, 255 );
|
||||
ref.dllFuncs.GL_CreateTexture( REF_BLACK_TEXTURE, 4, 4, data4x4, 0 );
|
||||
}
|
||||
|
||||
static qboolean R_LoadProgs( const char *name )
|
||||
@@ -554,8 +587,8 @@ static qboolean R_LoadProgs( const char *name )
|
||||
Cvar_FullSet( "host_refloaded", "1", FCVAR_READ_ONLY );
|
||||
ref.initialized = true;
|
||||
|
||||
// initialize TriAPI callbacks
|
||||
CL_FillTriAPIFromRef( &gTriApi, &ref.dllFuncs );
|
||||
R_CreateBuiltinTextures();
|
||||
CL_FillTriAPI( &gTriApi );
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -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 );
|
||||
@@ -71,7 +76,7 @@ static inline void Touch_AddDefaultButton( const char *name, const char *texture
|
||||
static inline void Touch_WriteConfig( void ) { }
|
||||
static inline void Touch_Init( void ) { }
|
||||
static inline void Touch_Shutdown( void ) { }
|
||||
static inline void Touch_GetMove( float * forward, float *side, float *yaw, float *pitch ) { }
|
||||
static inline void Touch_GetMove( float * forward, float *side, float *pitch, float *yaw ) { }
|
||||
static inline void Touch_ResetDefaultButtons( void ) { }
|
||||
static inline int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) { return 0; }
|
||||
static inline void Touch_KeyEvent( int key, int down ) { }
|
||||
@@ -87,7 +92,7 @@ void Touch_AddDefaultButton( const char *name, const char *texturefile, const ch
|
||||
void Touch_WriteConfig( void );
|
||||
void Touch_Init( void );
|
||||
void Touch_Shutdown( void );
|
||||
void Touch_GetMove( float * forward, float *side, float *yaw, float *pitch );
|
||||
void Touch_GetMove( float * forward, float *side, float *pitch, float *yaw );
|
||||
void Touch_ResetDefaultButtons( void );
|
||||
int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy );
|
||||
void Touch_KeyEvent( int key, int down );
|
||||
@@ -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
|
||||
//
|
||||
@@ -119,7 +133,7 @@ typedef enum engineAxis_e
|
||||
JOY_AXIS_YAW,
|
||||
JOY_AXIS_RT,
|
||||
JOY_AXIS_LT,
|
||||
JOY_AXIS_NULL
|
||||
MAX_AXES,
|
||||
} engineAxis_t;
|
||||
|
||||
typedef enum joy_calibration_state_s
|
||||
@@ -136,6 +150,7 @@ void Joy_SetCalibrationState( joy_calibration_state_t state );
|
||||
void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value );
|
||||
void Joy_GyroEvent( vec3_t data );
|
||||
void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw );
|
||||
void Joy_DrawDebug( void );
|
||||
void Joy_Init( void );
|
||||
void Joy_Shutdown( void );
|
||||
|
||||
|
||||
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 );
|
||||
}
|
||||
@@ -24,8 +24,6 @@ GNU General Public License for more details.
|
||||
#define SHRT_MAX 0x7FFF
|
||||
#endif
|
||||
|
||||
#define MAX_AXES JOY_AXIS_NULL
|
||||
|
||||
// index - axis num come from event
|
||||
// value - inner axis
|
||||
static engineAxis_t joyaxesmap[MAX_AXES] =
|
||||
@@ -42,7 +40,9 @@ static struct joy_axis_s
|
||||
{
|
||||
short val;
|
||||
short prevval;
|
||||
short rawval; // value before deadzone zeroing, for debug display
|
||||
} joyaxis[MAX_AXES] = { 0 };
|
||||
|
||||
static qboolean joy_initialized;
|
||||
|
||||
static CVAR_DEFINE_AUTO( joy_pitch, "100.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "joystick pitch sensitivity" );
|
||||
@@ -62,6 +62,19 @@ static CVAR_DEFINE_AUTO( joy_axis_binding, "sfpyrl", FCVAR_ARCHIVE | FCVAR_FILTE
|
||||
CVAR_DEFINE_AUTO( joy_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enable joystick" );
|
||||
static CVAR_DEFINE_AUTO( joy_have_gyro, "0", FCVAR_READ_ONLY, "tells whether current active gamepad has gyroscope or not" );
|
||||
static CVAR_DEFINE_AUTO( joy_calibrated, "0", FCVAR_READ_ONLY, "tells whether current active gamepad gyroscope has been calibrated or not" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_pitch, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for looking up and down" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_yaw, "1.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity for turning left and right" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope sensitivity when tilting the device sideways" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope pitch axis deadzone (deg/s)" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_yaw_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope yaw axis deadzone (deg/s)" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_roll_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope roll axis deadzone (deg/s)" );
|
||||
static CVAR_DEFINE_AUTO( joy_gyro_enable, "1", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "enables aiming with gamepad gyroscope" );
|
||||
static CVAR_DEFINE_AUTO( joy_debug, "0", 0, "visualize gamepad axes and buttons" );
|
||||
|
||||
// stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input
|
||||
static vec3_t joy_gyro_speed;
|
||||
// last non-zero gyro speed, retained for debug display after joy_gyro_speed is cleared
|
||||
static vec3_t joy_gyro_speed_display;
|
||||
|
||||
/*
|
||||
============
|
||||
@@ -116,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 )
|
||||
{
|
||||
@@ -185,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;
|
||||
@@ -228,6 +240,8 @@ static void Joy_ProcessStick( const engineAxis_t engineAxis, short value )
|
||||
break;
|
||||
}
|
||||
|
||||
joyaxis[engineAxis].rawval = value;
|
||||
|
||||
if( value < deadzone && value > -deadzone )
|
||||
value = 0; // caught new event in deadzone, fill it with zero(no motion)
|
||||
|
||||
@@ -257,7 +271,12 @@ Axis events
|
||||
*/
|
||||
void Joy_AxisMotionEvent( engineAxis_t engineAxis, short value )
|
||||
{
|
||||
if( engineAxis >= JOY_AXIS_NULL )
|
||||
if( engineAxis >= MAX_AXES )
|
||||
return;
|
||||
|
||||
engineAxis = joyaxesmap[engineAxis];
|
||||
|
||||
if( engineAxis >= MAX_AXES )
|
||||
return;
|
||||
|
||||
if( value == joyaxis[engineAxis].val )
|
||||
@@ -278,7 +297,8 @@ Gyroscope events
|
||||
*/
|
||||
void Joy_GyroEvent( vec3_t data )
|
||||
{
|
||||
|
||||
VectorCopy( data, joy_gyro_speed );
|
||||
VectorCopy( data, joy_gyro_speed_display );
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -290,15 +310,14 @@ Append movement from axis. Called everyframe
|
||||
*/
|
||||
void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
||||
{
|
||||
if( !Joy_IsActive() )
|
||||
if( !Joy_IsActive( ))
|
||||
return;
|
||||
|
||||
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] )
|
||||
{
|
||||
@@ -308,7 +327,7 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
||||
case 'p': joyaxesmap[i] = JOY_AXIS_PITCH; break;
|
||||
case 'r': joyaxesmap[i] = JOY_AXIS_RT; break;
|
||||
case 'l': joyaxesmap[i] = JOY_AXIS_LT; break;
|
||||
default : joyaxesmap[i] = JOY_AXIS_NULL; break;
|
||||
default : joyaxesmap[i] = MAX_AXES; break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,8 +336,219 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
|
||||
|
||||
*fw -= joy_forward.value * (float)joyaxis[JOY_AXIS_FWD ].val/(float)SHRT_MAX; // must be form -1.0 to 1.0
|
||||
*side += joy_side.value * (float)joyaxis[JOY_AXIS_SIDE].val/(float)SHRT_MAX;
|
||||
*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;
|
||||
*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_gyro_enable.value && joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
|
||||
{
|
||||
float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI );
|
||||
float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI );
|
||||
float roll_speed = joy_gyro_speed[2] * ( 180.0f / M_PI );
|
||||
|
||||
if( fabs( pitch_speed ) < joy_gyro_pitch_deadzone.value )
|
||||
pitch_speed = 0.0f;
|
||||
if( fabs( yaw_speed ) < joy_gyro_yaw_deadzone.value )
|
||||
yaw_speed = 0.0f;
|
||||
if( fabs( roll_speed ) < joy_gyro_roll_deadzone.value )
|
||||
roll_speed = 0.0f;
|
||||
|
||||
*dpitch -= joy_gyro_pitch.value * pitch_speed * host.realframetime;
|
||||
*dyaw += joy_gyro_yaw.value * yaw_speed * host.realframetime;
|
||||
*dyaw += joy_gyro_roll.value * roll_speed * host.realframetime;
|
||||
}
|
||||
|
||||
VectorClear( joy_gyro_speed );
|
||||
}
|
||||
|
||||
/*
|
||||
=============
|
||||
Joy_DrawDebug
|
||||
|
||||
Draw gamepad axes and buttons for debugging (joy_debug 1)
|
||||
=============
|
||||
*/
|
||||
void Joy_DrawDebug( void )
|
||||
{
|
||||
static const int buttons[] =
|
||||
{
|
||||
K_A_BUTTON, K_B_BUTTON, K_X_BUTTON, K_Y_BUTTON, 0,
|
||||
K_L1_BUTTON, K_R1_BUTTON, 0,
|
||||
K_L2_BUTTON, K_R2_BUTTON, 0,
|
||||
K_BACK_BUTTON, K_MODE_BUTTON, K_START_BUTTON, 0,
|
||||
K_LSTICK, K_RSTICK, 0,
|
||||
|
||||
// simulated buttons
|
||||
K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT, 0,
|
||||
K_LTRIGGER, K_RTRIGGER, 0,
|
||||
|
||||
// additional buttons
|
||||
K_C_BUTTON, K_Z_BUTTON, K_MISC_BUTTON, 0,
|
||||
K_PADDLE1_BUTTON, K_PADDLE2_BUTTON, K_PADDLE3_BUTTON, K_PADDLE4_BUTTON, 0
|
||||
};
|
||||
static const char *const axisnames[MAX_AXES] = { "SIDE", "FWD", "PITCH", "YAW", "RT", "LT" };
|
||||
|
||||
// deadzone for sticks, threshold for triggers
|
||||
static const convar_t *const axis_threshold[MAX_AXES] =
|
||||
{
|
||||
&joy_side_deadzone,
|
||||
&joy_forward_deadzone,
|
||||
&joy_pitch_deadzone,
|
||||
&joy_yaw_deadzone,
|
||||
&joy_rt_threshold,
|
||||
&joy_lt_threshold,
|
||||
};
|
||||
|
||||
cl_font_t *font = Con_GetCurFont();
|
||||
|
||||
if( !joy_debug.value || !Joy_IsActive( ))
|
||||
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;
|
||||
|
||||
const rgba_t bar_backcolor = { 40, 40, 40, 180 };
|
||||
const rgba_t bar_fillcolor = { 100, 200, 100, 200 };
|
||||
|
||||
// draw axes as labeled bars
|
||||
for( int i = 0; i < MAX_AXES; i++ )
|
||||
{
|
||||
|
||||
qboolean is_trigger = ( i >= JOY_AXIS_RT );
|
||||
float fval = (float)joyaxis[i].val / SHRT_MAX;
|
||||
float fprev = (float)joyaxis[i].prevval / SHRT_MAX;
|
||||
float fthreshold = axis_threshold[i]->value / SHRT_MAX;
|
||||
|
||||
CL_DrawString( x, y, axisnames[i], g_color_table[7], font, 0 );
|
||||
y += font->charHeight;
|
||||
|
||||
// background
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, bar_w, bar_h, bar_backcolor[0], bar_backcolor[1], bar_backcolor[2], bar_backcolor[3] );
|
||||
|
||||
if( is_trigger )
|
||||
{
|
||||
// fill bar from the left for triggers
|
||||
float filled = fval * bar_w;
|
||||
|
||||
if( filled > 0 )
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, filled, bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] );
|
||||
|
||||
// fill threshold with red, and yellow edge marker
|
||||
float thresh_x = x + fthreshold * bar_w;
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, thresh_x - x, bar_h, 180, 40, 40, 120 );
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
|
||||
|
||||
// prevval marker
|
||||
float prev_x = x + fprev * bar_w;
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, prev_x, y, 1, bar_h, 200, 200, 200, 180 );
|
||||
}
|
||||
else
|
||||
{
|
||||
// for sticks bar is filled from the center
|
||||
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] );
|
||||
|
||||
// fill deadzone with red, and yellow edge marker
|
||||
float thresh_x = fthreshold * halfbar_w;
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, thresh_x * 2, bar_h, 180, 40, 40, 120 );
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
|
||||
|
||||
// mark the center
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 );
|
||||
|
||||
// now draw the actual value
|
||||
float raw_x = center + ((float)joyaxis[i].rawval / SHRT_MAX * halfbar_w );
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, raw_x, y, 1, bar_h, 255, 255, 100, 200 );
|
||||
|
||||
// prevval marker
|
||||
float prev_x = center + fprev * halfbar_w;
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, prev_x, y, 1, bar_h, 200, 200, 200, 180 );
|
||||
}
|
||||
|
||||
y += bar_h + 2;
|
||||
}
|
||||
|
||||
// draw gyro if available
|
||||
if( joy_have_gyro.value )
|
||||
{
|
||||
static const char *gyronames[3] = { "GYRO P", "GYRO Y", "GYRO R" };
|
||||
static const convar_t *const gyro_deadzone[3] =
|
||||
{
|
||||
&joy_gyro_pitch_deadzone,
|
||||
&joy_gyro_yaw_deadzone,
|
||||
&joy_gyro_roll_deadzone,
|
||||
};
|
||||
|
||||
for( int i = 0; i < 3; i++ )
|
||||
{
|
||||
// clamp to +-5 rad/s for display
|
||||
float fval = joy_gyro_speed_display[i] / 5.0f;
|
||||
|
||||
fval = bound( -1.0f, fval, 1.0f );
|
||||
|
||||
CL_DrawString( x, y, gyronames[i], g_color_table[7], font, 0 );
|
||||
y += font->charHeight;
|
||||
|
||||
// background
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, x, y, bar_w, bar_h, bar_backcolor[0], bar_backcolor[1], bar_backcolor[2], bar_backcolor[3] );
|
||||
|
||||
// for gyro bar is filled from the center
|
||||
float filled = fval * halfbar_w;
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + Q_min( 0, filled ), y, fabs( filled ), bar_h, bar_fillcolor[0], bar_fillcolor[1], bar_fillcolor[2], bar_fillcolor[3] );
|
||||
|
||||
// deadzone in deg/s; display scale is +-5 rad/s
|
||||
float fthreshold = gyro_deadzone[i]->value * ( M_PI / 180.0f ) / 5.0f;
|
||||
fthreshold = bound( 0.0f, fthreshold, 1.0f );
|
||||
|
||||
if( fthreshold > 0.0f )
|
||||
{
|
||||
float thresh_x = fthreshold * halfbar_w;
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, thresh_x * 2, bar_h, 180, 40, 40, 120 );
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center + thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center - thresh_x, y, 1, bar_h, 255, 200, 0, 220 );
|
||||
}
|
||||
|
||||
// mark the center
|
||||
ref.dllFuncs.FillRGBA( kRenderTransTexture, center, y, 1, bar_h, 180, 180, 180, 220 );
|
||||
|
||||
// gyro doesn't have prevval like axes
|
||||
y += bar_h + 2;
|
||||
}
|
||||
}
|
||||
|
||||
// draw buttons in a row
|
||||
y += 4;
|
||||
x = 8;
|
||||
for( int i = 0; i < ARRAYSIZE( buttons ); i++ )
|
||||
{
|
||||
if( !buttons[i] )
|
||||
{
|
||||
x = 8;
|
||||
y += font->charHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
qboolean pressed = Key_IsDown( buttons[i] );
|
||||
string name;
|
||||
|
||||
Q_strncpy( name, Key_KeynumToString( buttons[i] ), sizeof( name ));
|
||||
|
||||
char *p = Q_strstr( name, "_BUTTON" );
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static void Joy_CalibrateGyro_f( void )
|
||||
@@ -366,6 +596,17 @@ void Joy_Init( void )
|
||||
Cvar_RegisterVariable( &joy_have_gyro );
|
||||
Cvar_RegisterVariable( &joy_calibrated );
|
||||
Cvar_RegisterVariable( &joy_enable );
|
||||
Cvar_RegisterVariable( &joy_gyro_enable );
|
||||
|
||||
Cvar_RegisterVariable( &joy_gyro_pitch );
|
||||
Cvar_RegisterVariable( &joy_gyro_yaw );
|
||||
Cvar_RegisterVariable( &joy_gyro_roll );
|
||||
|
||||
Cvar_RegisterVariable( &joy_gyro_pitch_deadzone );
|
||||
Cvar_RegisterVariable( &joy_gyro_yaw_deadzone );
|
||||
Cvar_RegisterVariable( &joy_gyro_roll_deadzone );
|
||||
|
||||
Cvar_RegisterVariable( &joy_debug );
|
||||
|
||||
// renamed from -nojoy to -noenginejoy to not conflict with
|
||||
// client.dll's joystick support
|
||||
@@ -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 )
|
||||
{
|
||||
@@ -2189,12 +2150,12 @@ int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx
|
||||
return Touch_ControlsEvent( type, fingerID, x, y, dx, dy );
|
||||
}
|
||||
|
||||
void Touch_GetMove( float *forward, float *side, float *yaw, float *pitch )
|
||||
void Touch_GetMove( float *forward, float *side, float *pitch, float *yaw )
|
||||
{
|
||||
*forward += touch.forward;
|
||||
*side += touch.side;
|
||||
*yaw += touch.yaw;
|
||||
*pitch += touch.pitch;
|
||||
*yaw += touch.yaw;
|
||||
touch.yaw = touch.pitch = 0;
|
||||
}
|
||||
|
||||
@@ -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,8 +562,9 @@ static void IN_CollectInput( float *forward, float *side, float *pitch, float *y
|
||||
#endif
|
||||
}
|
||||
|
||||
Joy_FinalizeMove( forward, side, yaw, pitch );
|
||||
Touch_GetMove( forward, side, yaw, pitch );
|
||||
IN_GyroFinalizeMove( forward, side, pitch, yaw );
|
||||
Joy_FinalizeMove( forward, side, pitch, yaw );
|
||||
Touch_GetMove( forward, side, pitch, yaw );
|
||||
|
||||
if( look_filter.value )
|
||||
{
|
||||
@@ -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 );
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user