Compare commits

..

12 Commits

Author SHA1 Message Date
Alibek Omarov
0cf571602c ref: common: wscript: fix psvita build 2026-04-13 12:48:55 +05:00
Alibek Omarov
a125f90d12 ref: soft: fix build 2026-04-13 10:00:15 +05:00
Alibek Omarov
313cda5478 ref: add GL_ResampleTexture to libref_common 2026-04-12 23:30:08 +05:00
Alibek Omarov
fc4e7eb18b ref: move remaining light functions to libref_common 2026-04-12 23:18:31 +05:00
Alibek Omarov
0836519b2d ref: gl: refactor R_LightVec 2026-04-12 23:06:43 +05:00
Alibek Omarov
5eeb250907 ref: common: add R_PushDlightsForBmodel 2026-04-12 22:44:55 +05:00
Alibek Omarov
2e579d0dd3 ref: move R_PushDlights and R_MarkLights to ref_common 2026-04-12 22:38:43 +05:00
Alibek Omarov
8cd40db261 ref: move RunLightstyles to ref_common 2026-04-12 22:12:50 +05:00
Alibek Omarov
9d03959196 engine: move sprite texture loading back to engine, only leave rendering for renderer 2026-04-12 17:29:45 +05:00
Alibek Omarov
acd3b0258d ref: common: move malloc and GL_InitRandomTable 2026-04-12 16:10:48 +05:00
Alibek Omarov
0b49e60c22 ref: common: draft new static library, containing shared code for ref_gl and ref_soft 2026-04-12 15:52:33 +05:00
Alibek Omarov
6719443d99 engine: draft new RefAPI 14: remove reserved functions from render api, remove CL_AddCustomBeam 2026-04-12 15:25:19 +05:00
357 changed files with 23837 additions and 30424 deletions

32
.cirrus.yml Normal file
View File

@@ -0,0 +1,32 @@
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

View File

@@ -1,113 +0,0 @@
Language: Cpp
Standard: c++17
UseTab: ForContinuationAndIndentation
TabWidth: 4
IndentWidth: 4
ContinuationIndentWidth: 4
IndentCaseLabels: false
IndentPPDirectives: None
NamespaceIndentation: None
AccessModifierOffset: -4
IndentAccessModifiers: false
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
BeforeWhile: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
MaxEmptyLinesToKeep: 2
KeepEmptyLinesAtTheStartOfBlocks: false
KeepEmptyLinesAtEOF: false
SeparateDefinitionBlocks: Always
InsertNewlineAtEOF: true
LineEnding: LF
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
BinPackArguments: true
BinPackParameters: true
AlwaysBreakAfterReturnType: None
AlwaysBreakAfterDefinitionReturnType: None
AllowShortFunctionsOnASingleLine: None
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
BreakBeforeBinaryOperators: All
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
PointerAlignment: Right
DerivePointerAlignment: false
ReferenceAlignment: Pointer
SpaceAroundPointerQualifiers: Default
SpacesInParens: Custom
SpacesInParensOptions:
ExceptDoubleParentheses: true
InCStyleCasts: false
InConditionalStatements: true
InEmptyParentheses: false
Other: true
# XashCollapseParens: true
SpaceBeforeParens: Never
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInSquareBrackets: false
Cpp11BracedListStyle: false
SpacesBeforeTrailingComments: 1
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
AlignAfterOpenBracket: DontAlign
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
ColumnLimit: 0
ReflowComments: false
SortIncludes: Never
SortUsingDeclarations: false
InsertBraces: false
RemoveBracesLLVM: false
FixNamespaceComments: false

View File

@@ -70,7 +70,6 @@ jobs:
env: env:
SDL_VERSION: 2.32.10 SDL_VERSION: 2.32.10
FFMPEG_VERSION: 8.1 FFMPEG_VERSION: 8.1
YY_THUNKS_VERSION: v1.2.1
GH_CPU_ARCH: ${{ matrix.targetarch }} GH_CPU_ARCH: ${{ matrix.targetarch }}
GH_CPU_OS: ${{ matrix.targetos }} GH_CPU_OS: ${{ matrix.targetos }}
GH_CROSSCOMPILING: ${{ matrix.cross }} GH_CROSSCOMPILING: ${{ matrix.cross }}

13
.gitignore vendored
View File

@@ -361,16 +361,3 @@ cmake-build-*
3rdparty/SDL 3rdparty/SDL
3rdparty/hlsdk-portable 3rdparty/hlsdk-portable
!scripts/build-ninja.py !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
View File

@@ -43,6 +43,3 @@
[submodule "3rdparty/library_suffix"] [submodule "3rdparty/library_suffix"]
path = 3rdparty/library_suffix path = 3rdparty/library_suffix
url = https://github.com/FWGS/library-suffix.git url = https://github.com/FWGS/library-suffix.git
[submodule "3rdparty/mbedtls/mbedtls"]
path = 3rdparty/mbedtls/mbedtls
url = https://github.com/FWGS/mbedtls-releases.git

View File

@@ -23,6 +23,6 @@ def build(bld):
bld(features='zip', bld(features='zip',
name = 'extras.pk3', name = 'extras.pk3',
files = srcdir.ant_glob('**/*', excl=['scripts/**']), files = srcdir.ant_glob('**/*'),
relative_to = srcdir, relative_to = srcdir,
install_path = install_path) install_path = install_path)

View File

@@ -16,8 +16,6 @@ def build(bld):
cflags = [] cflags = []
if bld.env.COMPILER_CC != 'msvc': if bld.env.COMPILER_CC != 'msvc':
cflags += ['-w', '-fvisibility=hidden', '-std=gnu99'] cflags += ['-w', '-fvisibility=hidden', '-std=gnu99']
else:
cflags += ['-wd4098']
bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']), bld.stlib(source = gl4es_srcdir.ant_glob(['gl/*.c', 'gl/*/*.c', 'glx/hardext.c']),
target = 'gl4es', target = 'gl4es',
includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'], includes = ['gl4es/src', 'gl4es/src/gl', 'gl4es/src/glx', 'gl4es/include'],

View File

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

View File

@@ -1,107 +0,0 @@
/*
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 */

View File

@@ -1,67 +0,0 @@
#! /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',
]
)

View File

@@ -1,36 +0,0 @@
#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 */

View File

@@ -1,82 +0,0 @@
#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 */

View File

@@ -1,59 +0,0 @@
#! /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')

View File

@@ -1,55 +1,53 @@
## If you are reporting bugs ## If you are reporting bugs
1. Check that you are using the latest version. To build the latest Xash3D FWGS, look at README.md. 1. Check you are using latest version. You can build latest Xash3D FWGS for yourself, look to README.md.
2. Check the open issues to make sure your bug is not already reported and closed issues if it reported and fixed. Don't send a bug if it's already been reported. 2. Check open issues is your bug is already reported and closed issues if it reported and fixed. Don't send bug if it's already reported.
3. Re-run engine with `-dev 2 -log` arguments, then reproduce the bug and post `engine.log` which can be found in your working directory. 3. Re-run engine with `-dev 2 -log` arguments, reproduce bug and post engine.log which can be found in your working directory.
3. Describe the steps to reproduce the bug. 3. Describe steps to reproduce bug.
4. Describe which OS and architecture you are using. 4. Describe which OS and architecture you are using.
6. Attach a screenshot if it will help clarify the situation. 6. Attach screenshot if it will help clarify the situation.
## If you are contributing code ## If you are contributing code
### Which branch? ### Which branch?
* We recommend using the `master` branch. * We recommend using `master` branch.
### Third-party libraries ### Third-party libraries
* The philosophy of any Xash Project by Uncle Mike: don't be bloated. We follow it too. * Philosophy of any Xash Project by Uncle Mike: don't be bloated. We follow it too.
* Adding new libraries are allowed only if there is a REAL reason to use it. It will be nice, if you leave a possibility to remove the new dependency at build-time. * Adding new library is allowed only if there is a REAL reason to use it. It's will be nice, if you will leave a possibility to remove new dependency at build-time.
* Adding new dependencies for the Waf Build System is not welcomed. * Adding new dependencies for Waf Build System is not welcomed.
### Portability level ### Portability level
* Xash3D has it's own crt library. It's recommended to use it. In most cases it's just a wrapper around the standard C library. * Xash3D have it's own crt library. It's recommended to use it. It most cases it's just a wrappers around standart C library.
* If your feature needs platform-specific code, move it to `engine/platform` and try to implement every supported OS and every supported compiler or at least leave a stub. * If your feature need platform-specific code, move it to `engine/platform` and try to implement to every supported OS and every supported compiler or at least leave a stubs.
* You must put it under the appropriate macro. It's a rule: Xash3D FWGS must compile everywhere. For list of platforms we support, refer to `public/build.h`. * You must put it under appopriate macro. It's a rule: Xash3D FWGS must compile everywhere. For list of platforms we support, refer to public/build.h file.
### Code style ### Code style
* This project uses a mix of Quake's and HLSDK's C/C++ code style convention. * This project uses mixed Quake's and HLSDK's C/C++ code style convention.
* In short: * In short:
* Use spaces in parenthesis, but not when two parentheses are consecutive. * Use spaces in parenthesis, but not when two parentheses are consecutive.
* Use only tabs for indentation. * Only tabs for indentation.
* Any brace must have it's own line. * Any brace must have it's own line.
* Use short blocks, if statements and loops on single line are allowed. * Short blocks, if statements and loops on single line are allowed.
* Prefer generic utilities from libpublic over rolling your own. * Prefer generic utilities from libpublic over rolling your own.
* Avoid magic numbers. * Avoid magic numbers.
* While macros are powerful, it's better to avoid overusing them. * While macros are powerful, it's better to avoid overusing them.
* If you are unsure, try to mimic the code style from anywhere else in the engine source code. * If you unsure, try to mimic code style from anywhere else of engine source code.
* **ANY** commit message should start from declaring tags, in the format: * **ANY** commit message should start from declaring a tags, in format:
`tag: added some bugs` `tag: added some bugs`
`tag: subtag: fixed some features` `tag: subtag: fixed some features`
The tags can be a: Tags can be any: subsystem, simple feature name or even just a filename, without extension.
* subsystem Just keep them always same, it helps keep history clean and commit messages short.
* simple feature name or even just a filename, without the extension.
Just always keep them the same because it helps keep the commit history clean and messages short.
## LLM-based tools usage. ## LLM-based tools usage.
While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay. While we wouldn't recommend using any LLM-based (also misleadingly called AI) tools, we understand that they are here to stay.
Whether you're reporting a bug or contributing to the code, you will take complete authorship and responsibility over the provided content, and the same rules will apply to you as for everybody else, so validate the bug report or the patch it before sending it. Whether you're reporting bug or contributing the code, you take complete authorship and responsibility over provided content and the same rules will apply to you as for everybody else, so validate the bug report or the patch before sending it.

View File

@@ -22,7 +22,7 @@ The other yet unsupported configuration is the big endian.
2) Open `public/build.h` file. Add appropriate checks for your operating system and/or CPU architecture. It shouldn't be hard. 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-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). Also, you'll need to build [Half-Life SDK](https://github.com/FWGS/hlsdk-xash3d/). It has same `public/build.h` so reflect changes into it. Note that to be compatible with HLSDK proprietary license, it's relicensed as public domain under [Unlicense](https://unlicense.org).
We have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information. We have a library naming scheme that allows us and game creators to distribute binaries for different platforms in one archive. Read `Documentation/extensions/library-naming.md` for more information.

View File

@@ -29,7 +29,9 @@ Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of
## [$_Vladislav](https://github.com/Vladislav4KZ) ## [$_Vladislav](https://github.com/Vladislav4KZ)
[YaPB Project](https://github.com/yapb) member and [contributor](https://github.com/yapb/yapb/commits?author=Vladislav4KZ) ([Docs](https://github.com/yapb/docs), [Nav Graphs](https://github.com/yapb/graph), [RU translation](https://github.com/yapb/yapb/blob/master/cfg/addons/yapb/conf/lang/ru_lang.cfg)), Xash3D FWGS [tester](https://github.com/FWGS/xash3d-fwgs/issues?q=is%3Aissue%20state%3Aopen%20author%3AVladislav4KZ) and [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Vladislav4KZ) (gyro-aim support, scr-parser, etc.), [CS16Client](https://github.com/Velaron/cs16-client) [contributor](https://github.com/Velaron/cs16-client/commits?author=Vladislav4KZ). [YaPB Project](https://github.com/yapb) member, [Xash3D FWGS](https://github.com/FWGS/xash3d-fwgs) tester,
editor of the [Official YaPB Documentation](https://github.com/yapb/docs) (in English and Russian), curator of the [YaPB Graph Database](https://github.com/yapb/graph) and author of [YaPB Waypoint/Graph Pack](https://gamebanana.com/mods/40087).
Also does the Russian localization of text, images (gfx/shell), as well as additional menu buttons in English used in Xash3D FWGS for various mods.
* [Boosty page](https://boosty.to/rasstaman1337) * [Boosty page](https://boosty.to/rasstaman1337)
* [PayPal link](https://paypal.me/rasstaman1337)

View File

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

View File

@@ -16,7 +16,6 @@ Only these objects are guaranteed to be available on all targets.
|-------------|-----------| |-------------|-----------|
| `VFileSystem009` | Provides C++ interface to filesystem, binary-compatible with Valve's VFileSystem009. | | `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`. | | `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 #### Android-specific objects

View File

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

View File

@@ -1,181 +0,0 @@
# 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 |

View File

@@ -4,7 +4,7 @@
|---- |------- |--------------- |---------------------- |---- |------- |--------------- |----------------------
|Area 51 |Update 1 |Uses outdated BSP31 map format and custom HLFX SDK libraries. |You can try [this tool](https://hlfx.ru/forum/showthread.php?threadid=5250) to convert maps but there no warranty if it works. |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. | |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-portable/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-xash3d/tree/bshift.
|Counter Strike |Beta 6.5- |Uses an old WON HL 1.0.0.16- interface. | |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.4 |Has encrypted blob instead of normal client.dll. |You can try [this tool](https://aluigi.altervista.org/papers/hldlldec.zip) to decrypt client.dll but there no warranty if it works.
| |1.5 |Has encrypted blob instead of normal client.dll. |Decrypted blob here: https://csm.dev/threads/cs-1-5-client-dll-decrypted-patched-for-usage.38845. | |1.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. |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. |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. | |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-portable/tree/rebellion. |Rebellion |1.0 |Uses an old WON HL 1.0.0.16- interface. |Recreated source code here: https://github.com/FWGS/hlsdk-xash3d/tree/rebellion.
|Sven-Coop |5.0+ |Uses custom GoldSrc engine. | |Sven-Coop |5.0+ |Uses custom GoldSrc engine. |
|Time Shadows |Beta 0.1 |Uses Direct3D renderer. | |Time Shadows |Beta 0.1 |Uses Direct3D renderer. |

View File

@@ -38,9 +38,6 @@ Mirrored on github - https://github.com/solidi/hl-mods/tree/master/cir
## Counter-Life ## Counter-Life
Available on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code Available on ModDB - https://www.moddb.com/mods/counter-life/downloads/cl-version-1-source-code
## Crack-Life Anniversary Campaign
Available on ModDB - https://www.moddb.com/mods/crack-life/downloads/crack-life-anniversary-campaign-sdk
## Cthulhu ## Cthulhu
Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK Uploaded to github by Oleg Cherkasky - https://github.com/gunrunners-paradise/Cthulhu-HLmod-SDK
@@ -65,14 +62,11 @@ Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/Half-Life_2D_
## Gang Wars ## Gang Wars
Mirrored on github - https://github.com/nekonomicon/gw1.45src Mirrored on github - https://github.com/nekonomicon/gw1.45src
## Go-Mod ## Go-mod
Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads Versions 2.0 and 3.0, available in mod archives on ModDB - https://www.moddb.com/mods/go-mod/downloads
Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30 Version 3.0, mirrored on github - https://github.com/nekonomicon/Go-mod30
## Go-Mod: Reborn
Official github repository - https://github.com/LambdaLuke87/Go-Mod-Reborn-Lite-Build
## GT mod ## GT mod
Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki) Available on GamerLab - http://gamer-lab.com/eng/code_mods_goldsrc/GT_mod_(Polnie_ishodniki)
@@ -91,21 +85,12 @@ Available on ModDB - https://www.moddb.com/mods/half-life-echoes
## Half-Life: Expanded Arsenal ## Half-Life: Expanded Arsenal
Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal Available on ModDB - https://www.moddb.com/mods/half-life-expanded-arsenal
## Half-Life: Featureful
Branch **featureful** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful
## Half-Life: Field Intensity ## Half-Life: Field Intensity
Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7 Branch **field_intensity_1.7** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/field_intensity_1.7
## Half-Life: Gravgun mod ## Half-Life: Gravgun mod
Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun Branch **gravgun** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/gravgun
## Half-Life: Induction
Version 1.4: Branch **induction** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/induction
## Half-Life: Insecure
Version 1.5 is available on ModDB - https://www.moddb.com/mods/half-life-insecure/downloads/half-life-insecure-map-tools-and-source-code-ver-15
## Half-Life: Invasion ## Half-Life: Invasion
Official github repository - https://github.com/jlecorre/hlinvasion Official github repository - https://github.com/jlecorre/hlinvasion
@@ -115,13 +100,6 @@ Mirrored on github - https://github.com/solidi/hl-mods/tree/master/pong
## Half-Life: Quest Mode ## Half-Life: Quest Mode
Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030 Available on cs-mapping.com.ua - https://old.cs-mapping.com.ua/forum/showthread.php?t=38030
## Half-Life: Sum
Original Version: Branch **sum_orig** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/sum_orig
Version 2022: Branch **sum_2022** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/sum_2022
Current Version: Branch **sum** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/sum
## Half-Life: Top-Down ## Half-Life: Top-Down
Official gitlab repository - https://gitlab.com/Sockman/hltopdown Official gitlab repository - https://gitlab.com/Sockman/hltopdown
@@ -131,9 +109,6 @@ Official github repository - https://github.com/Fograin/hl-subsmod-ex
## Half-Life: Rally ## Half-Life: Rally
Official gitlab repository - https://gitlab.com/hlrally/src Official gitlab repository - https://gitlab.com/hlrally/src
## Half-Life: Reprocessing
Official github repository - https://github.com/EgorYak/ReprocessingSourceCode
## Half-Life: RXT ## Half-Life: RXT
Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57 Available on ModDB - https://www.moddb.com/mods/half-life-rxt/downloads/rxt-source-code-v57
@@ -172,9 +147,6 @@ Mirrored on github (Includes maps and models source files) - https://github.com/
## Ingram Chillin' Mod ## Ingram Chillin' Mod
Official SourceForge repository - https://sourceforge.net/projects/icm-hl/ Official SourceForge repository - https://sourceforge.net/projects/icm-hl/
## Intense Force
Branch **intense_force** in halflife-featureful - https://github.com/FreeSlave/halflife-featureful/tree/intense_force
## Master Sword ## Master Sword
Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic Official github repository of Master Sword Classic - https://github.com/BerntA/MasterSwordClassic
@@ -217,9 +189,6 @@ Available in Valve's Half-Life repository - https://github.com/ValveSoftware/hal
## Spirit of Half-Life ## Spirit of Half-Life
[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life [Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life
## The Last Bullet
Official github repository - https://github.com/GT-Project-Cat/The-Last-Bullet-Updated-Code
## The Wastes ## The Wastes
Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk
@@ -243,19 +212,6 @@ Download page on official site - http://www.thothie.com/ww/
## Wrong Door ## Wrong Door
Available on ModDB - https://www.moddb.com/mods/wrong-door/downloads/wrong-door-sdk Available on ModDB - https://www.moddb.com/mods/wrong-door/downloads/wrong-door-sdk
## XDM
*This mod is opensource but access to source code is always rebus*
How to get sources of version 3.0.4.3:
Download SFX-archive from ModDB - https://www.moddb.com/mods/xdm/downloads/xdm3043
Extract it.
Open client.dll in HEX-editor and patch header - change `Win!` to `Rar!`
Extract it using unrar - password `iamtheone`
## XashXT ## XashXT
Mirrored on github - https://github.com/a1batross/XashXT_original Mirrored on github - https://github.com/a1batross/XashXT_original
@@ -264,12 +220,6 @@ Mirrored on github - https://github.com/a1batross/XashXT_original
[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life [Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life
## Yet another PS2 Half-Life port
Official github repository - https://github.com/supadupaplex/ya-ps2hl-dll
## Zombie Panic!
Official github repository - https://github.com/Monochrome-Inc/ZombiePanic-HL
## Zombie-X ## Zombie-X
Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version
@@ -351,7 +301,7 @@ Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253
Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift
## Half-Life: Induction ## Half-Life: Induction
Version 1.0-1.2: Branch **induction_1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction_1.2 Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction
## Half-Life: Opposing Force ## Half-Life: Opposing Force
Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d
@@ -425,9 +375,6 @@ Branch **aghl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/
## Bubblemod ## Bubblemod
Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod Branch **bubblemod** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bubblemod
## Counter-Life
Branch **counter-life** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/counter-life
## Deathmatch Classic ## Deathmatch Classic
Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc Deathmatch Classic: Adrenaline Gamer Edition - https://github.com/martinwebrant/agmod/tree/master/src/dmc
@@ -441,9 +388,6 @@ Branch **delta_particles** in hlsdk-portable - https://github.com/FWGS/hlsdk-por
## Half-Life: Echoes ## Half-Life: Echoes
Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes Branch **echoes** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/echoes
## Half-Life: Insecure
Branch **insecure** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/insecure
## Half-Life: Invasion ## Half-Life: Invasion
Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion Port to HLSDK 2.4 by malortie - https://github.com/HL1-Mods-Reimplementations-and-Ports/hl-invasion
@@ -477,8 +421,6 @@ Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/d
## Xen-Warrior ## Xen-Warrior
Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2
## Yet another PS2 Half-Life port
Branch **ps2hl** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/ps2hl
## Zombie-X ## Zombie-X
Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x

View File

@@ -12,18 +12,14 @@ All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all on
- [Any](#any) - [Any](#any)
- [Any to Any](#any-to-any) - [Any to Any](#any-to-any)
- [`A2A_NETINFO`](#a2a_netinfo)
- [`A2A_INFO`](#a2a_info)
- [`A2A_PING` and `A2A_GOLDSRC_PING`](#a2a_ping-and-a2a_goldsrc_ping) - [`A2A_PING` and `A2A_GOLDSRC_PING`](#a2a_ping-and-a2a_goldsrc_ping)
- [`A2A_ACK` and `A2A_GOLDSRC_ACK`](#a2a_ack-and-a2a_goldsrc_ack) - [`A2A_ACK` and `A2A_GOLDSRC_ACK`](#a2a_ack-and-a2a_goldsrc_ack)
- [Deprecated queries](#deprecated-queries)
- [`A2A_NETINFO`](#a2a_netinfo)
- [`A2A_INFO`](#a2a_info)
- [Any to Client](#any-to-client) - [Any to Client](#any-to-client)
- [`A2C_PRINT` and `A2C_GOLDSRC_PRINT`](#a2c_print-and-a2c_goldsrc_print) - [`A2C_PRINT` and `A2C_GOLDSRC_PRINT`](#a2c_print-and-a2c_goldsrc_print)
- [Any to Server and Server to Any](#any-to-server-and-server-to-any) - [Any to Server and Server to Any](#any-to-server-and-server-to-any)
- [GoldSrc server queries](#goldsrc-server-queries) - [`A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses](#a2s_goldsrc_info-a2s_goldsrc_rules-and-a2s_goldsrc_players-and-their-responses)
- [`A2S_GOLDSRC_INFO`](#a2s_goldsrc_info)
- [`A2S_GOLDSRC_PLAYERS`](#a2s_goldsrc_players)
- [`A2S_GOLDSRC_RULES`](#a2s_goldsrc_rules)
- [Any to Master](#any-to-master) - [Any to Master](#any-to-master)
- [`S2M_SCAN_REQUEST`](#s2m_scan_request) - [`S2M_SCAN_REQUEST`](#s2m_scan_request)
- [Master to Any](#master-to-any) - [Master to Any](#master-to-any)
@@ -54,23 +50,7 @@ All connectionless packets have `\xff\xff\xff\xff` (32-bit integer set to all on
### Any to Any ### Any to Any
#### `A2A_PING` and `A2A_GOLDSRC_PING` #### `A2A_NETINFO`
Simple ping message.
* Request, in ASCII: `ping` or `i` in GoldSrc format
* Response is always `A2A_ACK` or `A2A_GOLDSRC_ACK`
#### `A2A_ACK` and `A2A_GOLDSRC_ACK`
Simple ack message.
* Request, in ASCII: `ack` or `j` in GoldSrc format
* Response: none
#### Deprecated queries
These text-based Xash protocol queries are deprecated in favor of [GoldSrc server queries](#goldsrc-server-queries), which Xash3D FWGS servers implement as well. New implementations must not send them. The server still responds to `A2A_INFO` for compatibility with older clients, `A2A_NETINFO` is not handled anymore.
##### `A2A_NETINFO`
Used to implement Half-Life's NetAPI. Used to implement Half-Life's NetAPI.
* Request message, in ASCII: `netinfo <version> <context> <request_id>` * Request message, in ASCII: `netinfo <version> <context> <request_id>`
@@ -101,7 +81,7 @@ Possible requests and response formats:
* On any other server will respond with `neterror` set to `undefined` * On any other server will respond with `neterror` set to `undefined`
* If protocol version doesn't match, server responds with `neterror` set to `protocol` * If protocol version doesn't match, server responds with `neterror` set to `protocol`
##### `A2A_INFO` #### `A2A_INFO`
Used to request server's details. Used to request server's details.
* Request message, in ASCII: `info <version>` * Request message, in ASCII: `info <version>`
@@ -121,6 +101,18 @@ Used to request server's details.
- `password` set to 1 if server is protected with password otherwise 0 - `password` set to 1 if server is protected with password otherwise 0
- `host` is server's name - `host` is server's name
#### `A2A_PING` and `A2A_GOLDSRC_PING`
Simple ping message.
* Request, in ASCII: `ping` or `i` in GoldSrc format
* Response is always `A2A_ACK` or `A2A_GOLDSRC_ACK`
#### `A2A_ACK` and `A2A_GOLDSRC_ACK`
Simple ack message.
* Request, in ASCII: `ack` or `j` in GoldSrc format
* Response: none
### Any to Client ### Any to Client
#### `A2C_PRINT` and `A2C_GOLDSRC_PRINT` #### `A2C_PRINT` and `A2C_GOLDSRC_PRINT`
@@ -132,62 +124,9 @@ Simple print message.
### Any to Server and Server to Any ### Any to Server and Server to Any
#### GoldSrc server queries #### `A2S_GOLDSRC_INFO`, `A2S_GOLDSRC_RULES` and `A2S_GOLDSRC_PLAYERS` and their responses
These match Source Engine Query messages used in GoldSrc and Source engine and implemented for compatibility with existing server browsers and monitoring tools. The client uses them to query GoldSrc servers in the server browser, and as text-based Xash protocol queries are [deprecated](#deprecated-queries), it will use them to query Xash servers as well. Read documentation for them on VDC: https://developer.valvesoftware.com/wiki/Server_queries These match Source Engine Query messages used in GoldSrc and Source engine and implemented for compatibility with existing tools. Read documentation for them on VDC: https://developer.valvesoftware.com/wiki/Server_queries
Unlike other connectionless messages these are binary: integers are little-endian, floats are 32-bit IEEE 754 and strings are null-terminated.
`A2S_GOLDSRC_PLAYERS` and `A2S_GOLDSRC_RULES` requests carry a 32-bit challenge, `A2S_GOLDSRC_INFO` may have one appended after the query string. When no challenge is known yet, `\xff\xff\xff\xff` is sent in it's place. The server may answer with a challenge instead of the data: byte `A` (`0x41`) followed by a 32-bit challenge value, 9 bytes total with the header. The request is then repeated with the received challenge. Don't confuse this message with textual `S2C_GOLDSRC_CHALLENGE` used during connection, they only share the first byte.
Responses that don't fit into a single packet (usually rules) come from GoldSrc servers split into fragments. Such packets start with `\xfe\xff\xff\xff` instead of `\xff\xff\xff\xff`, followed by a 32-bit sequence number, same in all fragments of one response, and a byte with total fragment count in the lower 4 bits and this fragment's index in the upper 4 bits. Fragment payloads concatenated in index order form a normal `\xff\xff\xff\xff` message. Xash servers never split query responses and send them as single datagrams.
#### `A2S_GOLDSRC_INFO`
Used to request server's details.
* Request: `TSource Engine Query\0`, optionally followed by a 32-bit challenge. Xash servers ignore it.
* Response: byte `I` (`S2A_GOLDSRC_INFO`), followed by:
- byte: protocol version, 48 on GoldSrc, 49 on Xash3D FWGS
- string: server's name
- string: server's current level
- string: game directory name
- string: game description
- int16: Steam AppID, always 0 on Xash servers
- byte: total player count, bots included
- byte: max players limit
- byte: bot count
- byte: server type, `d` for dedicated or `l` for listen server
- byte: server's operating system, `w` for Windows, `l` for Linux, `m` for macOS
- byte: set to 1 if server is protected with password otherwise 0
- byte: set to 1 if server is VAC secured, Xash servers put the `secure` value from gameinfo here
- string: server's version
Same as the Source engine response format but without the extra data flag and its fields.
Legacy GoldSrc servers respond with the obsolete `m` format (`S2A_GOLDSRC_LEGACY_INFO`) instead, see VDC for its layout. Xash servers never send it but the client understands it and assumes protocol 48.
#### `A2S_GOLDSRC_PLAYERS`
Used to request server's player list.
* Request: byte `U` (`0x55`), then a 32-bit challenge.
* Response: byte `D` (`S2A_GOLDSRC_PLAYERS`), followed by a byte with total player count, then for each player:
- byte: player's index. Xash servers send sequential indexes starting from 0, GoldSrc servers always send 0 here.
- string: player's name
- int32: player's kill count
- float: time in seconds the player is connected, -1.0 for bots on Xash servers
Xash server doesn't respond at all if the player list is empty, the server is protected with password or exposing the player list is disabled with `sv_expose_player_list` cvar.
#### `A2S_GOLDSRC_RULES`
Used to request server's game rules.
* Request: byte `V` (`0x56`), then a 32-bit challenge.
* Response: byte `E` (`S2A_GOLDSRC_RULES`), followed by an int16 with total rule count, then rule name and rule value strings for each.
Only server cvars (`FCVAR_SERVER`) are sent. Values of protected cvars (`FCVAR_PROTECTED`) are replaced with `1` if the value is set otherwise `0`. If there are no cvars to expose, server doesn't respond at all.
### Any to Master ### Any to Master
@@ -215,7 +154,7 @@ Only server cvars (`FCVAR_SERVER`) are sent. Values of protected cvars (`FCVAR_P
- `reserved` is single reserved 8-bit byte - `reserved` is single reserved 8-bit byte
- Following is list of IP addresses in binary form: 6-bytes for IPv4 address + port and 18-bytes for IPv6 address + port - Following is list of IP addresses in binary form: 6-bytes for IPv4 address + port and 18-bytes for IPv6 address + port
- If port is 0, it means the end of list, otherwise it's the last IP used for pagination (not implemented in Xash3D) - If port is 0, it means the end of list, otherwise it's the last IP used for pagination (not implemented in Xash3D)
* Response: client doesn't make response to master server but might request `A2S_GOLDSRC_INFO` from servers * Response: client doesn't make response to master server but might request `A2S_INFO` from servers
## Master and game server ## Master and game server

View File

@@ -1,72 +0,0 @@
# 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>`.

View File

@@ -892,8 +892,6 @@ Currently, fresh Xash3D FWGS builds for Android, PS Vita and Nintendo Switch use
## List of games and mods with custom gamedll ## List of games and mods with custom gamedll
For mods from this category - first of all place mod folder beside **valve** folder. For mods from this category - first of all place mod folder beside **valve** folder.
NOTE: Precompiled libraries for some mods from this category may be already available for your platform in [hlsdk-mega-build](https://github.com/FWGS/hlsdk-mega-build) and this mods may be launch on Android out-of-the-box.
1. **[Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)** 1. **[Absolute Redemption](https://www.moddb.com/mods/absolute-redemption)**
To run this mod on specific platforms you may be need to compile libraries from **redemption** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **redemption** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
@@ -952,11 +950,7 @@ This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
14. **[Counter-Life](https://www.moddb.com/mods/counter-life)** 14. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)**
To run this mod on specific platforms you may be need to compile libraries from **counter-life** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
15. **[Counter Strike 1.6](https://store.steampowered.com/app/10/CounterStrike/)**
On x86 you may be need to compile client part. On x86 you may be need to compile client part.
@@ -968,65 +962,57 @@ Client: https://github.com/Velaron/cs16-client/
Server: https://github.com/rehlds/ReGameDLL_CS Server: https://github.com/rehlds/ReGameDLL_CS
16. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)** 15. **[Crack-Life](https://www.moddb.com/mods/crack-life/downloads/crack-life)**
To run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **cracklife** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
17. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)** 16. **[Crack-Life: Campaign Mode](https://www.moddb.com/mods/crack-life/downloads/crack-life-campaign-mode)**
To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **clcampaign** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
18. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)** 17. **[Deathmatch Classic](https://store.steampowered.com/app/40/Deathmatch_Classic/)**
Steam version already supports x86 Linux. Steam version already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
19. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)** 18. **[Delta Particles](https://www.moddb.com/mods/half-life-delta)**
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **delta_particles** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
20. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
21. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
22. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)** 19. **[Escape from the Darkness](https://www.moddb.com/mods/escape-from-the-darkness)**
To run this mod on specific platforms you may be need to compile libraries from **eftd** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
20. **[Gang Wars](https://www.moddb.com/mods/gangwars)**
This mod already supports x86 Linux.
Support for other platforms may be available later.
21. **[Half-Life: Blue Shift](https://store.steampowered.com/app/130/HalfLife_Blue_Shift/)**
To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **bshift** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
23. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)** 22. **[Half-Life: Decay](https://www.moddb.com/mods/half-life-decay)**
Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). Currently, you can compile mod libraries for x86 Linux from **decay-pc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
Support for other platforms may be available later. Support for other platforms may be available later.
24. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)** 23. **[Half-Life: Echoes](https://www.moddb.com/mods/half-life-echoes)**
To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **echoes** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
25. [Half-Life: Featureful](https://freeslave.github.io/halflife-featureful/) 24. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)**
*It's modding SDK*
This mod already supports x86 Linux. This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **featureful** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful). Support for other platforms may be available later.
26. **[Half-Life: Field Intensity](https://www.moddb.com/mods/field-intensity)** 25. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **field_intensity_1.7** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
27. **Half-Life: Gravgun** (unfinished) by *mittorn*, *a1batross* and *Solexid*
*Support temporary abandoned* *Support temporary abandoned*
@@ -1036,197 +1022,163 @@ Currently, MSVC is not supported, use MinGW if you need build for Windows.
Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras). Mod files: [1](http://mittorn.fwgs.ru/coop-entpatches/), [2](http://mittorn.fwgs.ru/ggm/), [3](https://github.com/nillerusr/gravgun-extras).
28. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)** 26. **[Half-Life: Induction 1.2](https://www.moddb.com/mods/half-life-induction)**
To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **induction_1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
29. **[Half-Life: Induction 1.4](https://www.moddb.com/mods/half-life-induction)** 27. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **induction** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
30. **[Half-Life: Insecure](https://www.moddb.com/mods/half-life-insecure)**
To run this mod on specific platforms you may be need to compile libraries from **insecure** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
31. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
32. **[Half-Life: Invasion](https://www.moddb.com/mods/half-life-invasion)**
This mod supports x86 Linux. This mod supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
33. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)** 28. **[Half-Life: Intense Force](https://www.moddb.com/downloads/intense-force)**
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **intense_force** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
29. **[Half-Life: Opposing Force](https://store.steampowered.com/app/50/HalfLife_Opposing_Force/)**
Steam version already supports x86 Linux. Steam version already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **opfor** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
34. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)** 30. **[Half-Life: Quest mod](https://csm.dev/threads/half-life-the-quest-mod-isxodniki.38030/)**
To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **sci** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
35. **[Half-Life: Sum](https://www.moddb.com/mods/half-life-sum)** 31. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
This mod already supports x86 Linux.
To run this mod on specific platforms you may be need to compile libraries from **sum** branch of [halflife-featureful](https://github.com/FreeSlave/halflife-featureful).
36. **[Half-Life: TopDown](https://www.moddb.com/mods/half-life-top-downs)**
To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **topdown** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
37. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)** 32. **[Half-Life: Urbicide](https://www.moddb.com/mods/half-life-urbicide)**
To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **hl_urbicide** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
38. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)** 33. **[Half-Life: Visitors](https://www.moddb.com/mods/half-life-visitors)**
To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **visitors** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
39. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)** 34. **[Half-Rats: Parasomnia](https://www.moddb.com/mods/half-rats-parasomnia/downloads/half-rats-parasomnia-v10b-steamlinux)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
40. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)** 35. **[Half-Screwed: Death and Rebirth](https://www.moddb.com/mods/half-screwed)**
To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **half-screwed** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
41. **[Half-Secret](https://www.moddb.com/mods/half-secret)** 36. **[Half-Secret](https://www.moddb.com/mods/half-secret)**
To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **half-secret** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
42. **[Mortal Kombat](https://www.moddb.com/mods/hl-mortal-kombat)** 37. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
This mod already supports Android and x86 Linux.
43. **[Natural Selection](https://www.moddb.com/mods/natural-selection)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
44. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)** 38. **[Night at the Office](https://www.moddb.com/mods/night-at-the-office)**
To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **noffice** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
45. **[Paranoia](https://www.moddb.com/mods/paranoia)** 39. **[Paranoia](https://www.moddb.com/mods/paranoia)**
*Support temporary abandoned* *Support temporary abandoned*
[Source code](https://github.com/FWGS/paranoia_toolkit). [Source code](https://github.com/FWGS/paranoia_toolkit).
46. **[Poke646](https://poke646.com/)** 40. **[Poke646](https://poke646.com/)**
To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **poke646** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
47. **[Poke646: Vendetta](https://poke646.com/)** 41. **[Poke646: Vendetta](https://poke646.com/)**
To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **poke646_vendetta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
48. [PrimeXT](https://snmetamorph.github.io/PrimeXT/) 42. [PrimeXT](https://snmetamorph.github.io/PrimeXT/)
*It's modding SDK*
[Source code](https://github.com/SNMetamorph/PrimeXT) [Source code](https://github.com/SNMetamorph/PrimeXT)
49. **[Quake Remake](https://www.moddb.com/games/quake-remake)** 43. **[Quake Remake](https://www.moddb.com/games/quake-remake)**
*Support was fully abandoned due Quake Wrapper release.* *Support was fully abandoned due Quake Wrapper release.*
[Actual source code](https://github.com/FWGS/quakeremake) [Actual source code](https://github.com/FWGS/quakeremake)
50. **[Rebellion](https://www.moddb.com/mods/rebellion1)** 44. **[Rebellion](https://www.moddb.com/mods/rebellion1)**
To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **rebellion** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
51. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)** 45. **[Residual Life](https://www.moddb.com/mods/hl-residual-life)**
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
52. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)** 46. **[Residual Point](https://www.moddb.com/mods/hl-residual-point)**
To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **residual_point** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
53. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)** 47. **[Ricochet](https://store.steampowered.com/app/60/Ricochet/)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
54. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta) 48. [Sewer](https://www.moddb.com/games/half-life/addons/sewer-beta)
To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **sewer_beta** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
55. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life) 49. [Spirit of Half-Life 1.2](https://www.moddb.com/mods/spirit-of-half-life)
*It's modding SDK*
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
56. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)** 50. **[Swiss Cheese Halloween](https://www.moddb.com/mods/half-life-halloween-mod)**
To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **halloween** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
57. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)** 51. **[Team Fortress Classic](https://store.steampowered.com/app/20/Team_Fortress_Classic/)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available. Currently, only source code of [client part](https://github.com/Velaron/tf15-client) is available.
58. **[The Gate](https://www.moddb.com/mods/the-gate)** 52. **[The Gate](https://www.moddb.com/mods/the-gate)**
To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **thegate** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
59. **[The Last Bullet](https://www.moddb.com/mods/the-last-bullet-update)** 53. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
To run this mod on specific platforms you may be need to compile libraries from **fwgs** branch of [The-Last-Bullet-Updated-Code](https://github.com/GT-Project-Cat/The-Last-Bullet-Updated-Code).
60. **[They Hunger: Trilogy](https://www.moddb.com/mods/they-hunger)**
To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **theyhunger** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
61. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)** 54. **[ThreeWave CTF](https://www.moddb.com/games/deathmatch-classic/downloads/threewave-ctf)**
To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **dmc** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
62. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)** 55. **[Times of Troubles](https://www.moddb.com/mods/times-of-troubles)**
To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **tot** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
63. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/) 56. [XashXT](https://csm.dev/threads/xashxt-0-65-rev-4-release-stable.38319/)
*It's modding SDK*
*Support was fully abandoned due PrimeXT development* *Support was fully abandoned due PrimeXT development*
[Source code](https://github.com/FWGS/XashXT-FWGS/) [Source code](https://github.com/FWGS/XashXT-FWGS/)
64. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)** 57. **[X-Half-Life DeathMatch](https://www.moddb.com/mods/xdm)**
This mod already supports x86 Linux. This mod already supports x86 Linux.
Support for other platforms may be available later. Support for other platforms may be available later.
65. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)** 58. **[Xen-Warrior](https://www.moddb.com/mods/xen-warrior)**
To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **sohl1.2** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
Don't forget to enable XENWARRIOR build option!!! Don't forget to enable XENWARRIOR build option!!!
66. **[Yet another PS2 Half-Life port](https://www.moddb.com/mods/yet-another-ps2-half-life-pc-port)** 59. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
To run this mod on specific platforms you may be need to compile libraries from **ps2hl** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).
67. [Zombie-X](https://www.moddb.com/mods/zombie-x-10-final)
To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable). To run this mod on specific platforms you may be need to compile libraries from **zombie-x** branch of [hlsdk-portable](https://github.com/FWGS/hlsdk-portable).

View File

@@ -1,6 +1,3 @@
> [!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" /> # 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" />
[![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \ [![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) \
[![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://xash.su/discord/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \ [![Discord Server](https://img.shields.io/discord/355697768582610945?logo=Discord&label=International%20Discord%20chat)](http://xash.su/discord/) [![Russian speakers Telegram Chat](https://img.shields.io/badge/Russian_speakers_Telegram_chat-gray?logo=Telegram)](https://t.me/flyingwithgauss) \

1
android/.gitignore vendored
View File

@@ -12,4 +12,3 @@ release/
*.hprof *.hprof
.vscode/ .vscode/
*.bak *.bak
.kotlin/

View File

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

View File

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

View File

@@ -48,8 +48,6 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!-- Dedicated server --> <!-- Dedicated server -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Self-update: install downloaded APK -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:name=".MainApplication" android:name=".MainApplication"
@@ -59,7 +57,6 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:theme="@style/Theme.App" android:theme="@style/Theme.App"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true"> android:requestLegacyExternalStorage="true">
<activity <activity
@@ -71,23 +68,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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 <activity
android:name=".XashActivity" android:name=".XashActivity"
android:alwaysRetainTaskState="true" android:alwaysRetainTaskState="true"
@@ -105,9 +85,5 @@
<intent> <intent>
<action android:name="su.xash.engine.MOD" /> <action android:name="su.xash.engine.MOD" />
</intent> </intent>
<intent>
<action android:name="android.intent.action.SENDTO" />
<data android:scheme="mailto" />
</intent>
</queries> </queries>
</manifest> </manifest>

View File

@@ -1,30 +1,13 @@
package su.xash.engine package su.xash.engine
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController 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.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() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
@@ -44,143 +27,9 @@ class MainActivity : AppCompatActivity() {
navController = navHostFragment.navController navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration(navController.graph) appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration) 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 { override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() 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
}
} }

View File

@@ -1,7 +1,6 @@
package su.xash.engine; package su.xash.engine;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.os.Build; import android.os.Build;
@@ -15,9 +14,7 @@ import android.view.WindowManager;
import org.libsdl.app.SDLActivity; import org.libsdl.app.SDLActivity;
import su.xash.engine.util.AndroidBug5497Workaround; import su.xash.engine.util.AndroidBug5497Workaround;
import su.xash.engine.util.CrashReports;
import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -123,54 +120,9 @@ public class XashActivity extends SDLActivity {
return getWindow().superDispatchKeyEvent(event); 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? // TODO: REMOVE LATER, temporary launchers support?
@Override @Override
protected String[] getArguments() { protected String[] getArguments() {
File crashDir = new File(getFilesDir(), "crashes");
crashDir.mkdirs();
nativeSetenv("XASH3D_CRASH_DIR", crashDir.getAbsolutePath());
recordLaunchInfo();
String gamedir = getIntent().getStringExtra("gamedir"); String gamedir = getIntent().getStringExtra("gamedir");
if (gamedir == null) gamedir = "valve"; if (gamedir == null) gamedir = "valve";
nativeSetenv("XASH3D_GAME", gamedir); nativeSetenv("XASH3D_GAME", gamedir);
@@ -178,14 +130,6 @@ public class XashActivity extends SDLActivity {
String gamelibdir = getIntent().getStringExtra("gamelibdir"); String gamelibdir = getIntent().getStringExtra("gamelibdir");
if (gamelibdir != null) nativeSetenv("XASH3D_GAMELIBDIR", 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"); String pakfile = getIntent().getStringExtra("pakfile");
if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile); if (pakfile != null) nativeSetenv("XASH3D_EXTRAS_PAK2", pakfile);

View File

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

View File

@@ -8,15 +8,12 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import androidx.core.net.toUri import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.R
import su.xash.engine.XashActivity import su.xash.engine.XashActivity
import su.xash.engine.util.showDownloadProgressDialog
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.util.Arrays
import android.util.Log
class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) { class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
private var iconName = "game.ico" private var iconName = "game.ico"
@@ -55,7 +52,7 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
fun startEngine(ctx: Context) { fun startEngine(ctx: Context) {
val packageNames = getPackageNamesForGameDir(basedir.name) val packageNames = getPackageNamesForGameDir(basedir.name)
var externalGame = false var externalGame = false
var commandLineArgs = "" var commandLineArgs = "";
if (basedir.name != defaultGameDir) if (basedir.name != defaultGameDir)
commandLineArgs += "-game ${basedir.name} " commandLineArgs += "-game ${basedir.name} "
@@ -71,18 +68,17 @@ 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) { if (externalGame && packageNames != null) {
var packageName: String? = null
var gameLibDir: String? = null
for (pn in packageNames) { for (pn in packageNames) {
gameLibDir = try { gameLibDir = try {
getGameLibDir(ctx, pn) getGameLibDir(ctx, pn)
} catch (e: PackageManager.NameNotFoundException) { } catch(e: PackageManager.NameNotFoundException) {
null null
} catch (e: Exception) { } catch(e: Exception) {
e.printStackTrace() e.printStackTrace()
null null
} }
@@ -103,65 +99,12 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
ctx.startActivity(intent) ctx.startActivity(intent)
} }
show() show()
}
return
}
launchEngine(ctx, commandLineArgs, packageName = packageName, gameLibDir = gameLibDir) return
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 { ctx.startActivity(Intent(ctx, XashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
@@ -170,8 +113,13 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
putExtra("usevolume", pref.getBoolean("use_volume_buttons", false)) putExtra("usevolume", pref.getBoolean("use_volume_buttons", false))
putExtra("basedir", basedir.parent) putExtra("basedir", basedir.parent)
if (gameLibDir != null) putExtra("gamelibdir", gameLibDir) if (gameLibDir != null) {
if (packageName != null) putExtra("package", packageName) putExtra("gamelibdir", gameLibDir)
}
if (packageName != null) {
putExtra("package", packageName)
}
}) })
} }
@@ -204,6 +152,9 @@ class Game(val ctx: Context, val basedir: File, val gameInfoFile: File) {
if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) }) if (mobileHacksGames.any { it.equals(gamedir, ignoreCase = true) })
return arrayOf("su.xash.engine") return arrayOf("su.xash.engine")
// return if (mDbEntry != null) {
// mDbEntry.getPackageName()
// } else null
return null return null
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,176 +0,0 @@
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)))
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,13 +22,5 @@
<fragment <fragment
android:id="@+id/appSettingsFragment" android:id="@+id/appSettingsFragment"
android:name="su.xash.engine.ui.settings.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> </navigation>

View File

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

View File

@@ -23,42 +23,4 @@
<string name="game_apk_required">Требуется APK с игровыми библиотеками</string> <string name="game_apk_required">Требуется APK с игровыми библиотеками</string>
<string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string> <string name="game_apk_message">Чтобы запустить эту игру, нужно установить дополнительный APK</string>
<string name="game_apk_install">Скачать</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> </resources>

View File

@@ -24,43 +24,4 @@
<string name="game_apk_required">Game code APK required</string> <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_message">To launch this game, you need additional APK installed</string>
<string name="game_apk_install">Download</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> </resources>

View File

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

View File

@@ -46,28 +46,4 @@
app:title="@string/preferences_server_package" app:title="@string/preferences_server_package"
app:useSimpleSummaryProvider="true" app:useSimpleSummaryProvider="true"
app:isPreferenceVisible="false" /> 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> </PreferenceScreen>

View File

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

View File

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

View File

@@ -87,7 +87,6 @@ typedef enum
IL_LOAD_PLAYER_DECAL = BIT(7), // special mode for player decals 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_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_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; } ilFlags_t;
// goes into rgbdata_t->encode // goes into rgbdata_t->encode

View File

@@ -22,15 +22,12 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef QUAKE_CLIENT_H #ifndef QUAKE_CLIENT_H
#define QUAKE_CLIENT_H #define QUAKE_CLIENT_H
#include <stdint.h>
#include "xash3d_types.h"
// Definitions originally taken from Quake, modified for binary compatibility // Definitions originally taken from Quake, modified for binary compatibility
typedef struct usercmd_s typedef struct usercmd_s
{ {
int16_t lerp_msec; // added in HL int16_t lerp_msec; // added in HL
uint8_t msec; // added in QW int8_t msec; // added in QW
uint8_t pad1; uint8_t pad1;
vec3_t viewangles; vec3_t viewangles;

View File

@@ -16,10 +16,6 @@
#define CS_SIZE 64 // size of one config string #define CS_SIZE 64 // size of one config string
#define CS_TIME 16 // size of time 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 // platform-specific alignment for types, to not break ABI
#if XASH_PSP #if XASH_PSP
#define MAYBE_ALIGNED( x ) __attribute__(( aligned( 16 ))) #define MAYBE_ALIGNED( x ) __attribute__(( aligned( 16 )))
@@ -118,7 +114,6 @@ typedef int qboolean;
#define FORMAT_CHECK( x ) __attribute__(( format( printf, x, x + 1 ))) #define FORMAT_CHECK( x ) __attribute__(( format( printf, x, x + 1 )))
#define ALLOC_CHECK( x ) __attribute__(( alloc_size( x ))) #define ALLOC_CHECK( x ) __attribute__(( alloc_size( x )))
#define WARN_UNUSED_RESULT __attribute__(( warn_unused_result )) #define WARN_UNUSED_RESULT __attribute__(( warn_unused_result ))
#define MAYBE_UNUSED __attribute__(( unused ))
#define RENAME_SYMBOL( x ) asm( x ) #define RENAME_SYMBOL( x ) asm( x )
#if !defined( offsetof ) #if !defined( offsetof )
#define offsetof( s, m ) __builtin_offsetof( s, m ) #define offsetof( s, m ) __builtin_offsetof( s, m )
@@ -203,10 +198,6 @@ typedef int qboolean;
#define WARN_UNUSED_RESULT #define WARN_UNUSED_RESULT
#endif // !defined( WARN_UNUSED_RESULT ) #endif // !defined( WARN_UNUSED_RESULT )
#if !defined( MAYBE_UNUSED )
#define MAYBE_UNUSED
#endif // !defined( MAYBE_UNUSED )
#if !defined( RENAME_SYMBOL ) #if !defined( RENAME_SYMBOL )
#define RENAME_SYMBOL( x ) #define RENAME_SYMBOL( x )
#endif // !defined( RENAME_SYMBOL ) #endif // !defined( RENAME_SYMBOL )
@@ -252,10 +243,7 @@ typedef int qboolean;
#define Swap32Store( x ) ( x = Swap32( x )) #define Swap32Store( x ) ( x = Swap32( x ))
#define Swap16Store( x ) ( x = Swap16( x )) #define Swap16Store( x ) ( x = Swap16( x ))
#define LittleFourCC( a, b, c, d ) (((uint32_t)( d ) << 24 ) | ((uint32_t)( c ) << 16 ) | ((uint32_t)( b ) << 8 ) | (uint32_t)( a )) #ifdef XASH_BIG_ENDIAN
#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 LittleLong( x ) Swap32( x )
#define LittleShort( x ) Swap16( x ) #define LittleShort( x ) Swap16( x )
#define LittleLongSW( x ) Swap32Store( x ) #define LittleLongSW( x ) Swap32Store( x )
@@ -264,7 +252,6 @@ typedef int qboolean;
#define BigLong( x ) ( x ) #define BigLong( x ) ( x )
#define BigShort( x ) ( x ) #define BigShort( x ) ( x )
#define BigFloat( x ) ( x ) #define BigFloat( x ) ( x )
#define HostFourCC( a, b, c, d ) BigFourCC( a, b, c, d )
#else #else
#define LittleLong( x ) ( x ) #define LittleLong( x ) ( x )
#define LittleShort( x ) ( x ) #define LittleShort( x ) ( x )
@@ -274,14 +261,6 @@ typedef int qboolean;
#define BigLong( x ) Swap32( x ) #define BigLong( x ) Swap32( x )
#define BigShort( x ) Swap16( x ) #define BigShort( x ) Swap16( x )
#define BigFloat( x ) SwapFloat( x ) #define BigFloat( x ) SwapFloat( x )
#define HostFourCC( a, b, c, d ) LittleFourCC( a, b, c, d )
#endif #endif
static inline short UnalignedShort( short *x )
{
short y;
memcpy( &y, x, sizeof( y ) );
return y;
}
#endif // XASH_TYPES_H #endif // XASH_TYPES_H

View File

@@ -195,6 +195,7 @@ static int AVI_OpenCodecContext( AVCodecContext **dst_dec_ctx, AVFormatContext *
AVI_SpewError( quiet, S_ERROR "Failed to allocate %s codec context", dec->name ); AVI_SpewError( quiet, S_ERROR "Failed to allocate %s codec context", dec->name );
return AVERROR( ENOMEM ); return AVERROR( ENOMEM );
} }
if(( ret = pavcodec_parameters_to_context( dec_ctx, st->codecpar )) < 0 ) if(( ret = pavcodec_parameters_to_context( dec_ctx, st->codecpar )) < 0 )
{ {
AVI_SpewAvError( quiet, "avcodec_parameters_to_context", ret ); AVI_SpewAvError( quiet, "avcodec_parameters_to_context", ret );
@@ -250,7 +251,8 @@ byte *AVI_GetVideoFrame( movie_state_t *Avi, int target )
static void AVI_StreamAudio( movie_state_t *Avi ) static void AVI_StreamAudio( movie_state_t *Avi )
{ {
rawchan_t *ch; int buffer_samples, file_samples, file_bytes;
rawchan_t *ch = NULL;
// keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_StartStreaming wasn't enabled // keep the same semantics, when S_RAW_SOUND_SOUNDTRACK doesn't play if S_StartStreaming wasn't enabled
qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !snd.streaming : false; qboolean disable_stream = Avi->entnum == S_RAW_SOUND_SOUNDTRACK ? !snd.streaming : false;
@@ -271,11 +273,11 @@ static void AVI_StreamAudio( movie_state_t *Avi )
while( ch->s_rawend < snd.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; size_t copy;
buffer_samples = ch->max_samples - (ch->s_rawend - snd.soundtime);
file_samples = buffer_samples * ((float)Avi->rate / SOUND_DMA_SPEED);
if( file_samples <= 1 ) return; // no more samples need if( file_samples <= 1 ) return; // no more samples need
file_bytes = file_samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; file_bytes = file_samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;
@@ -306,6 +308,8 @@ static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame )
{ {
int samples = frame->nb_samples; int samples = frame->nb_samples;
size_t len = samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels; size_t len = samples * pav_get_bytes_per_sample( Avi->s_fmt ) * Avi->channels;
int outsamples;
uint8_t *ptr;
// allocate data // allocate data
if( !Avi->cached_audio ) if( !Avi->cached_audio )
@@ -333,8 +337,8 @@ static void AVI_HandleAudio( movie_state_t *Avi, const AVFrame *frame )
} }
} }
uint8_t *ptr = Avi->cached_audio + Avi->cached_audio_len; ptr = Avi->cached_audio + Avi->cached_audio_len;
int outsamples = pswr_convert( Avi->swr_ctx, &ptr, samples, (void *)frame->data, samples ); 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; 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 ); // Con_Printf( "%s: got audio chunk of size %d samples\n", __func__, outsamples );
@@ -440,16 +444,13 @@ qboolean AVI_Think( movie_state_t *Avi )
if( Avi->texture == 0 ) if( Avi->texture == 0 )
{ {
int cinTexture = SCR_GetCinematicTexture();
int w = Avi->w >= 0 ? Avi->w : refState.width; int w = Avi->w >= 0 ? Avi->w : refState.width;
int h = Avi->h >= 0 ? Avi->h : refState.height; int h = Avi->h >= 0 ? Avi->h : refState.height;
if( redraw ) ref.dllFuncs.R_DrawStretchRaw( Avi->x, Avi->y, w, h, Avi->xres, Avi->yres, Avi->dst, 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 ) else if( redraw && Avi->texture > 0 )
ref.dllFuncs.GL_UpdateTexture( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst, PF_BGRA_32 ); ref.dllFuncs.AVI_UploadRawFrame( Avi->texture, Avi->xres, Avi->yres, Avi->w, Avi->h, Avi->dst );
if( flushing && !decoded ) if( flushing && !decoded )
return false; // probably hit an EOF return false; // probably hit an EOF
@@ -459,6 +460,8 @@ qboolean AVI_Think( movie_state_t *Avi )
void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet )
{ {
byte *dst[4];
int dst_linesize[4];
int ret; int ret;
if( Avi->active ) if( Avi->active )
@@ -523,8 +526,6 @@ void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audi
return; 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 ) 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 ); AVI_SpewAvError( quiet, "av_image_alloc", ret );

View File

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

View File

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

View File

@@ -50,7 +50,7 @@ const char *CL_MsgInfo( int cmd )
// get engine message name // get engine message name
const char *svc_string = NULL; const char *svc_string = NULL;
switch( cls.net_protocol ) switch( cls.legacymode )
{ {
case PROTO_CURRENT: case PROTO_CURRENT:
svc_string = svc_strings[cmd]; svc_string = svc_strings[cmd];
@@ -71,7 +71,9 @@ const char *CL_MsgInfo( int cmd )
} }
else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES ))
{ {
for( int i = 0; i < MAX_USER_MESSAGES; i++ ) int i;
for( i = 0; i < MAX_USER_MESSAGES; i++ )
{ {
if( clgame.msg[i].number == cmd ) if( clgame.msg[i].number == cmd )
{ {
@@ -104,9 +106,11 @@ record new message params into debug buffer
*/ */
void CL_Parse_RecordCommand( int cmd, int startoffset ) void CL_Parse_RecordCommand( int cmd, int startoffset )
{ {
int slot;
if( cmd == svc_nop ) return; if( cmd == svc_nop ) return;
int slot = ( cls_message_debug.currentcmd++ & MSG_MASK ); slot = ( cls_message_debug.currentcmd++ & MSG_MASK );
cls_message_debug.oldcmd[slot].command = cmd; cls_message_debug.oldcmd[slot].command = cmd;
cls_message_debug.oldcmd[slot].starting_offset = startoffset; cls_message_debug.oldcmd[slot].starting_offset = startoffset;
cls_message_debug.oldcmd[slot].frame_number = host.framecount; cls_message_debug.oldcmd[slot].frame_number = host.framecount;
@@ -137,7 +141,9 @@ write net_message into buffer.dat for debugging
static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg ) static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg )
{ {
const char *buffer_file = "buffer.dat"; const char *buffer_file = "buffer.dat";
file_t *fp = FS_Open( buffer_file, "wb", false ); file_t *fp;
fp = FS_Open( buffer_file, "wb", false );
if( !fp ) if( !fp )
{ {
Con_Printf( S_ERROR "%s: can't open %s for write\n", __func__, buffer_file ); Con_Printf( S_ERROR "%s: can't open %s for write\n", __func__, buffer_file );
@@ -146,7 +152,7 @@ static void CL_WriteErrorMessage( int current_count, sizebuf_t *msg )
FS_Write( fp, &cls.starting_count, sizeof( int )); FS_Write( fp, &cls.starting_count, sizeof( int ));
FS_Write( fp, &current_count, sizeof( int )); FS_Write( fp, &current_count, sizeof( int ));
FS_Write( fp, &cls.net_protocol, sizeof( cls.net_protocol )); FS_Write( fp, &cls.legacymode, sizeof( cls.legacymode ));
FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg )); FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg ));
FS_Close( fp ); FS_Close( fp );
@@ -164,7 +170,7 @@ void CL_WriteMessageHistory( void )
{ {
oldcmd_t *old; oldcmd_t *old;
sizebuf_t *msg = &net_message; sizebuf_t *msg = &net_message;
int thecmd; int i, thecmd;
if( !cls.initialized || cls.state == ca_disconnected ) if( !cls.initialized || cls.state == ca_disconnected )
return; return;
@@ -178,7 +184,7 @@ void CL_WriteMessageHistory( void )
thecmd = cls_message_debug.currentcmd - 1; thecmd = cls_message_debug.currentcmd - 1;
thecmd -= ( MSG_COUNT - 1 ); // back up to here thecmd -= ( MSG_COUNT - 1 ); // back up to here
for( int i = 0; i < MSG_COUNT - 1; i++ ) for( i = 0; i < MSG_COUNT - 1; i++ )
{ {
thecmd &= MSG_MASK; thecmd &= MSG_MASK;
old = &cls_message_debug.oldcmd[thecmd]; old = &cls_message_debug.oldcmd[thecmd];
@@ -207,7 +213,7 @@ void CL_ReplayBufferDat_f( void )
FS_Read( f, &current_count, sizeof( current_count )); FS_Read( f, &current_count, sizeof( current_count ));
FS_Read( f, &protocol, sizeof( protocol )); FS_Read( f, &protocol, sizeof( protocol ));
cls.net_protocol = protocol; cls.legacymode = protocol;
len = FS_Read( f, buffer, sizeof( buffer )); len = FS_Read( f, buffer, sizeof( buffer ));
FS_Close( f ); FS_Close( f );

View File

@@ -245,6 +245,8 @@ Writes the demo command header and time-delta
*/ */
static void CL_WriteDemoCmdHeader( byte cmd, file_t *file ) static void CL_WriteDemoCmdHeader( byte cmd, file_t *file )
{ {
float dt;
Assert( cmd >= 1 && cmd <= dem_lastcmd ); Assert( cmd >= 1 && cmd <= dem_lastcmd );
if( !file ) return; if( !file ) return;
@@ -252,7 +254,7 @@ static void CL_WriteDemoCmdHeader( byte cmd, file_t *file )
FS_Write( file, &cmd, sizeof( byte )); FS_Write( file, &cmd, sizeof( byte ));
// time offset // time offset
float dt = (float)(CL_GetDemoRecordClock() - demo.starttime); dt = (float)(CL_GetDemoRecordClock() - demo.starttime);
FS_Write( file, &dt, sizeof( float )); FS_Write( file, &dt, sizeof( float ));
} }
@@ -393,6 +395,11 @@ Write demo header
*/ */
static void CL_WriteDemoHeader( const char *name ) static void CL_WriteDemoHeader( const char *name )
{ {
double maxfps;
int copysize;
int savepos;
int curpos;
Con_Printf( "recording to %s.\n", name ); Con_Printf( "recording to %s.\n", name );
cls.demofile = FS_Open( name, "wb", false ); cls.demofile = FS_Open( name, "wb", false );
cls.demotime = 0.0; cls.demotime = 0.0;
@@ -406,13 +413,13 @@ static void CL_WriteDemoHeader( const char *name )
cls.demorecording = true; cls.demorecording = true;
cls.demowaiting = true; // don't start saving messages until a non-delta compressed message is received cls.demowaiting = true; // don't start saving messages until a non-delta compressed message is received
double maxfps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT; maxfps = fps_override.value ? MAX_FPS_HARD : MAX_FPS_SOFT;
memset( &demo.header, 0, sizeof( demo.header )); memset( &demo.header, 0, sizeof( demo.header ));
demo.header.id = IDEMOHEADER; demo.header.id = IDEMOHEADER;
demo.header.dem_protocol = DEMO_PROTOCOL; demo.header.dem_protocol = DEMO_PROTOCOL;
demo.header.net_protocol = CL_GetDemoNetProtocol( cls.net_protocol ); demo.header.net_protocol = CL_GetDemoNetProtocol( cls.legacymode );
demo.header.host_fps = host_maxfps.value ? bound( MIN_FPS, host_maxfps.value, maxfps ) : maxfps; demo.header.host_fps = host_maxfps.value ? bound( MIN_FPS, host_maxfps.value, maxfps ) : maxfps;
Q_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname )); Q_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname ));
Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment )); Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment ));
@@ -435,7 +442,6 @@ static void CL_WriteDemoHeader( const char *name )
FS_Flush( cls.demoheader ); FS_Flush( cls.demoheader );
// now copy the stuff we cached from the server. // now copy the stuff we cached from the server.
int copysize, savepos;
copysize = savepos = FS_Tell( cls.demoheader ); copysize = savepos = FS_Tell( cls.demoheader );
FS_Seek( cls.demoheader, 0, SEEK_SET ); FS_Seek( cls.demoheader, 0, SEEK_SET );
@@ -452,7 +458,7 @@ static void CL_WriteDemoHeader( const char *name )
cls.td_lastframe = -1; // get a new message this frame cls.td_lastframe = -1; // get a new message this frame
// now move on to entry # 1, the first data chunk. // now move on to entry # 1, the first data chunk.
int curpos = FS_Tell( cls.demofile ); curpos = FS_Tell( cls.demofile );
demo.entry->length = curpos - demo.entry->offset; demo.entry->length = curpos - demo.entry->offset;
// now we are writing the first real lump. // now we are writing the first real lump.
@@ -481,15 +487,19 @@ finish recording demo
*/ */
static void CL_StopRecord( void ) static void CL_StopRecord( void )
{ {
int i, curpos;
float stoptime;
int frames;
if( !cls.demorecording ) return; if( !cls.demorecording ) return;
// demo playback should read this as an incoming message. // demo playback should read this as an incoming message.
CL_WriteDemoCmdHeader( dem_stop, cls.demofile ); CL_WriteDemoCmdHeader( dem_stop, cls.demofile );
float stoptime = CL_GetDemoRecordClock(); stoptime = CL_GetDemoRecordClock();
if( clgame.hInstance ) clgame.dllFuncs.pfnReset(); if( clgame.hInstance ) clgame.dllFuncs.pfnReset();
int curpos = FS_Tell( cls.demofile ); curpos = FS_Tell( cls.demofile );
demo.entry->length = curpos - demo.entry->offset; demo.entry->length = curpos - demo.entry->offset;
demo.entry->playback_time = stoptime - demo.realstarttime; demo.entry->playback_time = stoptime - demo.realstarttime;
demo.entry->playback_frames = demo.framecount; demo.entry->playback_frames = demo.framecount;
@@ -497,7 +507,7 @@ static void CL_StopRecord( void )
// Now write out the directory and free it and touch up the demo header. // Now write out the directory and free it and touch up the demo header.
FS_Write( cls.demofile, &demo.directory.numentries, sizeof( int )); FS_Write( cls.demofile, &demo.directory.numentries, sizeof( int ));
for( int i = 0; i < demo.directory.numentries; i++ ) for( i = 0; i < demo.directory.numentries; i++ )
FS_Write( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t )); FS_Write( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t ));
Mem_Free( demo.directory.entries ); Mem_Free( demo.directory.entries );
@@ -515,7 +525,7 @@ static void CL_StopRecord( void )
gameui.globals->demoname[0] = '\0'; gameui.globals->demoname[0] = '\0';
demo.header.host_fps = 0.0; demo.header.host_fps = 0.0;
int frames = cls.td_lastframe - cls.td_startframe; 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 ); 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; cls.demotime = 0.0;
} }
@@ -529,12 +539,13 @@ void CL_DrawDemoRecording( void )
{ {
char string[64]; char string[64];
rgba_t color = { 255, 255, 255, 255 }; rgba_t color = { 255, 255, 255, 255 };
int pos;
int len; int len;
if(!( host_developer.value && cls.demorecording )) if(!( host_developer.value && cls.demorecording ))
return; return;
int pos = FS_Tell( cls.demofile ); pos = FS_Tell( cls.demofile );
Q_snprintf( string, sizeof( string ), "^1RECORDING:^7 %s: %s time: %02d:%02d", cls.demoname, 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 )); Q_memprint( pos ), (int)(cls.demotime / 60.0f ), (int)fmod( cls.demotime, 60.0f ));
@@ -723,7 +734,7 @@ static void CL_DemoStartPlayback( int mode )
demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message
CL_SetupNetchanForProtocol( cls.net_protocol ); CL_SetupNetchanForProtocol( cls.legacymode );
memset( demo.cmds, 0, sizeof( demo.cmds )); memset( demo.cmds, 0, sizeof( demo.cmds ));
demo.angle_position = 1; demo.angle_position = 1;
@@ -836,6 +847,7 @@ static qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length )
{ {
vec3_t viewangles; vec3_t viewangles;
int msglen = 0; int msglen = 0;
demoangle_t *a;
*length = 0; // assume we fail *length = 0; // assume we fail
@@ -876,7 +888,7 @@ static qboolean CL_DemoReadMessageQuake( byte *buffer, size_t *length )
{ {
// select entry into circular buffer // select entry into circular buffer
demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK; demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK;
demoangle_t *a = &demo.cmds[demo.angle_position]; a = &demo.cmds[demo.angle_position];
// record update // record update
a->starttime = demo.timestamp; a->starttime = demo.timestamp;
@@ -1048,7 +1060,8 @@ qboolean CL_DemoReadMessage( byte *buffer, size_t *length )
static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next ) static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next )
{ {
int i0, i1, imod; int i, i0, i1, imod;
float at;
if( cls.timedemo ) return; if( cls.timedemo ) return;
@@ -1058,9 +1071,9 @@ static void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t
if( demo.cmds[i0].starttime >= t ) if( demo.cmds[i0].starttime >= t )
{ {
for( int i = 0; i < ANGLE_BACKUP - 2; i++ ) for( i = 0; i < ANGLE_BACKUP - 2; i++ )
{ {
float at = demo.cmds[imod & ANGLE_MASK].starttime; at = demo.cmds[imod & ANGLE_MASK].starttime;
if( at == 0.0f ) break; if( at == 0.0f ) break;
if( at < t ) if( at < t )
@@ -1101,6 +1114,7 @@ void CL_DemoInterpolateAngles( void )
{ {
demoangle_t *prev = NULL, *next = NULL; demoangle_t *prev = NULL, *next = NULL;
float frac = 0.0f; float frac = 0.0f;
float curtime;
if( cls.demoplayback == DEMO_QUAKE1 ) if( cls.demoplayback == DEMO_QUAKE1 )
{ {
@@ -1112,7 +1126,7 @@ void CL_DemoInterpolateAngles( void )
} }
else else
{ {
float curtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime; curtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime;
if( curtime > demo.timestamp ) if( curtime > demo.timestamp )
curtime = demo.timestamp; // don't run too far curtime = demo.timestamp; // don't run too far
@@ -1225,6 +1239,7 @@ int GAME_EXPORT CL_GetDemoComment( const char *demoname, char *comment )
demodirectory_t directory; demodirectory_t directory;
demoentry_t entry; demoentry_t entry;
float playtime = 0.0f; float playtime = 0.0f;
int i;
if( !comment ) return false; if( !comment ) return false;
@@ -1264,7 +1279,7 @@ int GAME_EXPORT CL_GetDemoComment( const char *demoname, char *comment )
return false; return false;
} }
for( int i = 0; i < directory.numentries; i++ ) for( i = 0; i < directory.numentries; i++ )
{ {
FS_Read( demfile, &entry, sizeof( demoentry_t )); FS_Read( demfile, &entry, sizeof( demoentry_t ));
playtime += entry.playback_time; playtime += entry.playback_time;
@@ -1373,6 +1388,7 @@ void CL_Record_f( void )
{ {
string demoname, demopath; string demoname, demopath;
const char *name; const char *name;
int n;
if( Cmd_Argc() == 1 ) if( Cmd_Argc() == 1 )
{ {
@@ -1409,7 +1425,6 @@ void CL_Record_f( void )
if( !Q_stricmp( name, "new" )) if( !Q_stricmp( name, "new" ))
{ {
// scan for a free filename // scan for a free filename
int n;
for( n = 0; n < 10000; n++ ) for( n = 0; n < 10000; n++ )
{ {
CL_DemoGetName( n, demoname, sizeof( demoname )); CL_DemoGetName( n, demoname, sizeof( demoname ));
@@ -1496,7 +1511,7 @@ void CL_PlayDemo_f( void )
{ {
char filename[MAX_QPATH]; char filename[MAX_QPATH];
char demoname[MAX_QPATH]; char demoname[MAX_QPATH];
int ident; int i, ident;
if( Cmd_Argc() < 2 ) if( Cmd_Argc() < 2 )
{ {
@@ -1555,7 +1570,7 @@ void CL_PlayDemo_f( void )
if( neg ) cls.forcetrack = -cls.forcetrack; if( neg ) cls.forcetrack = -cls.forcetrack;
CL_DemoStartPlayback( DEMO_QUAKE1 ); CL_DemoStartPlayback( DEMO_QUAKE1 );
cls.net_protocol = PROTO_QUAKE; cls.legacymode = PROTO_QUAKE;
return; // quake demo is started return; // quake demo is started
} }
@@ -1569,7 +1584,7 @@ void CL_PlayDemo_f( void )
// allocate demo entries // allocate demo entries
demo.directory.entries = Mem_Malloc( cls.mempool, sizeof( *demo.directory.entries ) * demo.directory.numentries ); demo.directory.entries = Mem_Malloc( cls.mempool, sizeof( *demo.directory.entries ) * demo.directory.numentries );
for( int i = 0; i < demo.directory.numentries; i++ ) for( i = 0; i < demo.directory.numentries; i++ )
{ {
demoentry_t *entry = &demo.directory.entries[i]; demoentry_t *entry = &demo.directory.entries[i];
@@ -1591,7 +1606,7 @@ void CL_PlayDemo_f( void )
CL_DemoStartPlayback( DEMO_XASH3D ); CL_DemoStartPlayback( DEMO_XASH3D );
// must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol // must be after DemoStartPlayback, as CL_Disconnect_f resets the protocol
cls.net_protocol = CL_GetProtocolFromDemo( demo.header.net_protocol ); cls.legacymode = CL_GetProtocolFromDemo( demo.header.net_protocol );
// g-cont. is this need? // g-cont. is this need?
Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); Q_strncpy( cls.servername, demoname, sizeof( cls.servername ));
@@ -1625,13 +1640,15 @@ CL_StartDemos_f
*/ */
void CL_StartDemos_f( void ) void CL_StartDemos_f( void )
{ {
int i, c;
if( cls.key_dest != key_menu ) if( cls.key_dest != key_menu )
{ {
Con_Printf( "'startdemos' is not valid from the console\n" ); Con_Printf( "'startdemos' is not valid from the console\n" );
return; return;
} }
int c = Cmd_Argc() - 1; c = Cmd_Argc() - 1;
if( c > MAX_DEMOS ) if( c > MAX_DEMOS )
{ {
Con_DPrintf( S_WARN "%s: max %i demos in demoloop\n", __func__, MAX_DEMOS ); Con_DPrintf( S_WARN "%s: max %i demos in demoloop\n", __func__, MAX_DEMOS );
@@ -1640,7 +1657,7 @@ void CL_StartDemos_f( void )
Con_Printf( "%i demo%s in loop\n", c, (c > 1) ? "s" : "" ); Con_Printf( "%i demo%s in loop\n", c, (c > 1) ? "s" : "" );
for( int i = 1; i < c + 1; i++ ) for( i = 1; i < c + 1; i++ )
Q_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] )); Q_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] ));
cls.demos_pending = true; cls.demos_pending = true;
} }
@@ -1700,6 +1717,7 @@ void CL_ListDemo_f( void )
file_t *f; file_t *f;
char filename[MAX_QPATH]; char filename[MAX_QPATH];
char demoname[MAX_QPATH]; char demoname[MAX_QPATH];
int i;
if( Cmd_Argc() < 2 ) if( Cmd_Argc() < 2 )
{ {
@@ -1733,7 +1751,7 @@ void CL_ListDemo_f( void )
filename, hdr.net_protocol, hdr.dem_protocol, hdr.host_fps, hdr.mapname, filename, hdr.net_protocol, hdr.dem_protocol, hdr.host_fps, hdr.mapname,
hdr.comment, hdr.gamedir ); hdr.comment, hdr.gamedir );
for( int i = 0; i < num_entries; i++ ) for( i = 0; i < num_entries; i++ )
{ {
demoentry_t entry; demoentry_t entry;

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,10 +41,12 @@ Store another position into interpolation circular buffer
*/ */
static void CL_UpdatePositions( cl_entity_t *ent ) static void CL_UpdatePositions( cl_entity_t *ent )
{ {
position_history_t *prev = &ent->ph[ent->current_position & HISTORY_MASK]; position_history_t *ph, *prev;
prev = &ent->ph[ent->current_position];
ent->current_position = (ent->current_position + 1) & HISTORY_MASK; ent->current_position = (ent->current_position + 1) & HISTORY_MASK;
position_history_t *ph = &ent->ph[ent->current_position]; ph = &ent->ph[ent->current_position];
VectorCopy( ent->curstate.origin, ph->origin ); VectorCopy( ent->curstate.origin, ph->origin );
VectorCopy( ent->curstate.angles, ph->angles ); VectorCopy( ent->curstate.angles, ph->angles );
@@ -72,10 +74,11 @@ Interpolation init or reset after teleporting
*/ */
static void CL_ResetPositions( cl_entity_t *ent ) static void CL_ResetPositions( cl_entity_t *ent )
{ {
if( !ent ) position_history_t store;
return;
position_history_t store = ent->ph[ent->current_position & HISTORY_MASK]; if( !ent ) return;
store = ent->ph[ent->current_position];
ent->current_position = 1; ent->current_position = 1;
memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX ); memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX );
@@ -92,13 +95,14 @@ we don't want interpolate this
*/ */
static qboolean CL_EntityTeleported( cl_entity_t *ent ) static qboolean CL_EntityTeleported( cl_entity_t *ent )
{ {
float len, maxlen;
vec3_t delta; vec3_t delta;
VectorSubtract( ent->curstate.origin, ent->prevstate.origin, delta ); VectorSubtract( ent->curstate.origin, ent->prevstate.origin, delta );
// compute potential max movement in units per frame and compare with entity movement // compute potential max movement in units per frame and compare with entity movement
float maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS )); maxlen = ( clgame.movevars.maxvelocity * ( 1.0f / GAME_FPS ));
float len = VectorLength( delta ); len = VectorLength( delta );
return (len > maxlen); return (len > maxlen);
} }
@@ -166,23 +170,22 @@ check for parametrical moved entities
*/ */
static qboolean CL_ParametricMove( cl_entity_t *ent ) static qboolean CL_ParametricMove( cl_entity_t *ent )
{ {
float frac, dt, t;
vec3_t delta; vec3_t delta;
if( ent->curstate.starttime == 0.0f || ent->curstate.impacttime == 0.0f ) if( ent->curstate.starttime == 0.0f || ent->curstate.impacttime == 0.0f )
return false; return false;
VectorSubtract( ent->curstate.endpos, ent->curstate.startpos, delta ); VectorSubtract( ent->curstate.endpos, ent->curstate.startpos, delta );
float dt = ent->curstate.impacttime - ent->curstate.starttime; dt = ent->curstate.impacttime - ent->curstate.starttime;
if( dt != 0.0f ) if( dt != 0.0f )
{ {
float t;
if( ent->lastmove > cl.time ) if( ent->lastmove > cl.time )
t = ent->lastmove; t = ent->lastmove;
else t = cl.time; else t = cl.time;
float frac = ( t - ent->curstate.starttime ) / dt; frac = ( t - ent->curstate.starttime ) / dt;
frac = bound( 0.0f, frac, 1.0f ); frac = bound( 0.0f, frac, 1.0f );
VectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin ); VectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin );
@@ -237,14 +240,18 @@ CL_GetStudioEstimatedFrame
*/ */
static float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) 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 ) if( ent->model != NULL && ent->model->type == mod_studio )
{ {
studiohdr_t *pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model ); pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model );
if( pstudiohdr && pstudiohdr->numseq > 0 ) if( pstudiohdr && pstudiohdr->numseq > 0 )
{ {
int sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 ); sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 );
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;
return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time ); return ref.dllFuncs.R_StudioEstimateFrame( ent, pseqdesc, cl.time );
} }
} }
@@ -295,6 +302,8 @@ apply changes since new frame received
*/ */
static void CL_ProcessEntityUpdate( cl_entity_t *ent ) static void CL_ProcessEntityUpdate( cl_entity_t *ent )
{ {
qboolean parametric;
ent->model = CL_ModelHandle( ent->curstate.modelindex ); ent->model = CL_ModelHandle( ent->curstate.modelindex );
ent->index = ent->curstate.number; ent->index = ent->curstate.number;
@@ -316,7 +325,7 @@ static void CL_ProcessEntityUpdate( cl_entity_t *ent )
VectorCopy( aiment->origin, ent->curstate.origin ); VectorCopy( aiment->origin, ent->curstate.origin );
} }
qboolean parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f; parametric = ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f;
// allow interpolation on bmodels too // allow interpolation on bmodels too
if( ent->model && ent->model->type == mod_brush ) if( ent->model && ent->model->type == mod_brush )
@@ -355,11 +364,13 @@ find two timestamps
static qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, double targettime, position_history_t **ph0, position_history_t **ph1 ) static qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, double targettime, position_history_t **ph0, position_history_t **ph1 )
{ {
qboolean extrapolate = true; qboolean extrapolate = true;
uint imod = ent->current_position; uint i, i0, i1, imod;
uint i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end)
uint i1 = (imod - 1) & HISTORY_MASK; // oldpos (lerp start)
for( uint i = 1; i < HISTORY_MAX - 1; i++ ) imod = ent->current_position;
i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end)
i1 = (imod - 1) & HISTORY_MASK; // oldpos (lerp start)
for( i = 1; i < HISTORY_MAX - 1; i++ )
{ {
double at = ent->ph[( imod - i ) & HISTORY_MASK].animtime; double at = ent->ph[( imod - i ) & HISTORY_MASK].animtime;
@@ -391,19 +402,19 @@ non-local players interpolation
*/ */
static void CL_PureOrigin( cl_entity_t *ent, double t, vec3_t outorigin, vec3_t outangles ) 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; position_history_t *ph0, *ph1;
vec3_t delta;
// NOTE: ph0 is next, ph1 is a prev // NOTE: ph0 is next, ph1 is a prev
CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 ); CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 );
double t0 = ph0->animtime; t0 = ph0->animtime;
double t1 = ph1->animtime; t1 = ph1->animtime;
if( t0 != 0.0 ) if( t0 != 0.0 )
{ {
vec4_t q, q1, q2; vec4_t q, q1, q2;
vec3_t delta;
double frac;
VectorSubtract( ph0->origin, ph1->origin, delta ); VectorSubtract( ph0->origin, ph1->origin, delta );
@@ -439,6 +450,7 @@ static int CL_InterpolateModel( cl_entity_t *e )
{ {
position_history_t *ph0 = NULL, *ph1 = NULL; position_history_t *ph0 = NULL, *ph1 = NULL;
vec3_t origin, angles, delta; vec3_t origin, angles, delta;
double t, t1, t2, frac;
vec4_t q, q1, q2; vec4_t q, q1, q2;
VectorCopy( e->curstate.origin, e->origin ); VectorCopy( e->curstate.origin, e->origin );
@@ -467,11 +479,11 @@ static int CL_InterpolateModel( cl_entity_t *e )
if( cl.local.moving && cl.local.onground == e->index ) if( cl.local.moving && cl.local.onground == e->index )
return 1; return 1;
double t = cl.time - cl_interp.value; t = cl.time - cl_interp.value;
CL_FindInterpolationUpdates( e, t, &ph0, &ph1 ); CL_FindInterpolationUpdates( e, t, &ph0, &ph1 );
double t1 = ph1->animtime; t1 = ph1->animtime;
double t2 = ph0->animtime; t2 = ph0->animtime;
if( t - t1 < 0.0f ) if( t - t1 < 0.0f )
return 0; return 0;
@@ -491,7 +503,7 @@ static int CL_InterpolateModel( cl_entity_t *e )
} }
VectorSubtract( ph0->origin, ph1->origin, delta ); VectorSubtract( ph0->origin, ph1->origin, delta );
double frac = (t - t1) / (t2 - t1); frac = (t - t1) / (t2 - t1);
if( frac < 0.0f ) if( frac < 0.0f )
return 0; return 0;
@@ -521,6 +533,8 @@ interpolate non-local clients
*/ */
void CL_ComputePlayerOrigin( cl_entity_t *ent ) void CL_ComputePlayerOrigin( cl_entity_t *ent )
{ {
double targettime;
vec4_t q, q1, q2;
vec3_t origin; vec3_t origin;
vec3_t angles; vec3_t angles;
@@ -536,8 +550,6 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent )
if( cls.demoplayback == DEMO_QUAKE1 ) if( cls.demoplayback == DEMO_QUAKE1 )
{ {
vec4_t q, q1, q2;
// quake lerping is easy // quake lerping is easy
VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, ent->origin ); VectorLerp( ent->prevstate.origin, cl.lerpFrac, ent->curstate.origin, ent->origin );
AngleQuaternion( ent->prevstate.angles, q1, false ); AngleQuaternion( ent->prevstate.angles, q1, false );
@@ -547,7 +559,7 @@ void CL_ComputePlayerOrigin( cl_entity_t *ent )
return; return;
} }
double targettime = cl.time - cl_interp.value; targettime = cl.time - cl_interp.value;
CL_PureOrigin( ent, targettime, origin, angles ); CL_PureOrigin( ent, targettime, origin, angles );
VectorCopy( angles, ent->angles ); VectorCopy( angles, ent->angles );
@@ -563,7 +575,9 @@ process player states after the new packet has received
*/ */
static void CL_ProcessPlayerState( int playerindex, entity_state_t *state ) static void CL_ProcessPlayerState( int playerindex, entity_state_t *state )
{ {
entity_state_t *ps = &cl.frames[cl.parsecountmod].playerstate[playerindex]; entity_state_t *ps;
ps = &cl.frames[cl.parsecountmod].playerstate[playerindex];
ps->number = state->number; ps->number = state->number;
ps->messagenum = cl.parsecount; ps->messagenum = cl.parsecount;
ps->msg_time = cl.mtime[0]; ps->msg_time = cl.mtime[0];
@@ -604,15 +618,19 @@ process player states after the new packet has received
*/ */
void CL_ProcessPacket( frame_t *frame ) void CL_ProcessPacket( frame_t *frame )
{ {
for( int pnum = 0; pnum < frame->num_entities; pnum++ ) entity_state_t *state;
cl_entity_t *ent;
int pnum;
for( pnum = 0; pnum < frame->num_entities; pnum++ )
{ {
// request the entity state from circular buffer // request the entity state from circular buffer
entity_state_t *state = &cls.packet_entities[(frame->first_entity+pnum) % cls.num_client_entities]; state = &cls.packet_entities[(frame->first_entity+pnum) % cls.num_client_entities];
state->messagenum = cl.parsecount; state->messagenum = cl.parsecount;
state->msg_time = cl.mtime[0]; state->msg_time = cl.mtime[0];
// mark all the players // mark all the players
cl_entity_t *ent = &clgame.entities[state->number]; ent = &clgame.entities[state->number];
ent->player = CL_IsPlayerIndex( state->number ); ent->player = CL_IsPlayerIndex( state->number );
if( state->number == ( cl.playernum + 1 )) if( state->number == ( cl.playernum + 1 ))
@@ -658,6 +676,7 @@ Read and ignore whole entity packet.
*/ */
static void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto ) static void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto )
{ {
int newnum;
entity_state_t from, to; entity_state_t from, to;
memset( &from, 0, sizeof( from )); memset( &from, 0, sizeof( from ));
@@ -668,8 +687,6 @@ static void CL_FlushEntityPacket( sizebuf_t *msg, connprotocol_t proto )
// read it all, but ignore it // read it all, but ignore it
while( 1 ) while( 1 )
{ {
int newnum;
if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto )) if( !CL_ParseEntityNumFromPacket( msg, &newnum, proto ))
break; // done break; // done
@@ -799,19 +816,22 @@ rest of the data stream.
*/ */
int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto ) int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto )
{ {
frame_t *oldframe; frame_t *newframe, *oldframe;
int oldindex, newnum, oldnum; int oldindex, newnum, oldnum;
int playerbytes = 0; int playerbytes = 0;
int bufStart;
entity_state_t *oldent; entity_state_t *oldent;
qboolean player;
int count;
// save first uncompressed packet as timestamp // save first uncompressed packet as timestamp
if( cls.changelevel && !delta && cls.demorecording ) if( cls.changelevel && !delta && cls.demorecording )
CL_WriteDemoJumpTime(); CL_WriteDemoJumpTime();
// sentinel count. save it for debug checking // sentinel count. save it for debug checking
int count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1; count = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1;
frame_t *newframe = &cl.frames[cl.parsecountmod]; newframe = &cl.frames[cl.parsecountmod];
// allocate parse entities // allocate parse entities
memset( newframe->flags, 0, sizeof( newframe->flags )); memset( newframe->flags, 0, sizeof( newframe->flags ));
@@ -852,7 +872,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto
if( MSG_CheckOverflow( msg )) if( MSG_CheckOverflow( msg ))
Host_Error( "%s: overflow\n", __func__ ); Host_Error( "%s: overflow\n", __func__ );
qboolean player = CL_IsPlayerIndex( newnum ); player = CL_IsPlayerIndex( newnum );
while( oldnum < newnum ) while( oldnum < newnum )
{ {
@@ -864,7 +884,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto
if( oldnum == newnum ) if( oldnum == newnum )
{ {
// delta from previous state // delta from previous state
int bufStart = MSG_GetNumBytesRead( msg ); bufStart = MSG_GetNumBytesRead( msg );
CL_DeltaEntity( msg, newframe, newnum, oldent, true ); CL_DeltaEntity( msg, newframe, newnum, oldent, true );
if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;
oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent ); oldnum = CL_UpdateOldEntNum( ++oldindex, oldframe, &oldent );
@@ -874,7 +894,7 @@ int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta, connprotocol_t proto
if( oldnum > newnum ) if( oldnum > newnum )
{ {
// delta from baseline ? // delta from baseline ?
int bufStart = MSG_GetNumBytesRead( msg ); bufStart = MSG_GetNumBytesRead( msg );
CL_DeltaEntity( msg, newframe, newnum, NULL, true ); CL_DeltaEntity( msg, newframe, newnum, NULL, true );
if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart;
continue; continue;
@@ -1010,10 +1030,12 @@ for all current players
*/ */
static void CL_LinkPlayers( frame_t *frame ) static void CL_LinkPlayers( frame_t *frame )
{ {
cl_entity_t *ent = CL_GetLocalPlayer();
entity_state_t *state; entity_state_t *state;
cl_entity_t *ent;
int i; int i;
ent = CL_GetLocalPlayer();
// apply muzzleflash to weaponmodel // apply muzzleflash to weaponmodel
if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH )) if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH ))
SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH ); SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH );
@@ -1091,9 +1113,15 @@ CL_LinkPacketEntities
*/ */
static void CL_LinkPacketEntities( frame_t *frame ) static void CL_LinkPacketEntities( frame_t *frame )
{ {
for( int i = 0; i < frame->num_entities; i++ ) cl_entity_t *ent;
entity_state_t *state;
qboolean parametric;
qboolean interpolate;
int i;
for( i = 0; i < frame->num_entities; i++ )
{ {
entity_state_t *state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities]; state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities];
// clients are should be done in CL_LinkPlayers // clients are should be done in CL_LinkPlayers
if( state->number >= 1 && state->number <= cl.maxclients ) if( state->number >= 1 && state->number <= cl.maxclients )
@@ -1103,7 +1131,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
if( !state->modelindex || FBitSet( state->effects, EF_NODRAW )) if( !state->modelindex || FBitSet( state->effects, EF_NODRAW ))
continue; continue;
cl_entity_t *ent = CL_GetEntityByIndex( state->number ); ent = CL_GetEntityByIndex( state->number );
if( !ent ) if( !ent )
{ {
@@ -1114,7 +1142,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
// animtime must keep an actual // animtime must keep an actual
ent->curstate.animtime = state->animtime; ent->curstate.animtime = state->animtime;
ent->curstate.frame = state->frame; ent->curstate.frame = state->frame;
qboolean interpolate = false; interpolate = false;
if( !ent->model ) continue; if( !ent->model ) continue;
@@ -1128,7 +1156,7 @@ static void CL_LinkPacketEntities( frame_t *frame )
} }
} }
qboolean parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f ); parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f );
if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND ) if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND )
{ {
@@ -1341,6 +1369,9 @@ SOUND ENGINE IMPLEMENTATION
*/ */
qboolean CL_GetEntitySpatialization( channel_t *ch ) qboolean CL_GetEntitySpatialization( channel_t *ch )
{ {
cl_entity_t *ent;
qboolean valid_origin;
if( ch->entnum == 0 ) if( ch->entnum == 0 )
{ {
SetBits( ch->flags, FL_CHAN_STATIC_SOUND ); SetBits( ch->flags, FL_CHAN_STATIC_SOUND );
@@ -1353,8 +1384,8 @@ qboolean CL_GetEntitySpatialization( channel_t *ch )
return true; return true;
} }
qboolean valid_origin = VectorIsNull( ch->origin ) ? false : true; valid_origin = VectorIsNull( ch->origin ) ? false : true;
cl_entity_t *ent = CL_GetEntityByIndex( ch->entnum ); ent = CL_GetEntityByIndex( ch->entnum );
// entity is not present on the client but has valid origin // entity is not present on the client but has valid origin
if( !ent || !ent->model || ent->curstate.messagenum != cl.parsecount ) if( !ent || !ent->model || ent->curstate.messagenum != cl.parsecount )

View File

@@ -23,8 +23,6 @@ GNU General Public License for more details.
#include "client.h" #include "client.h"
#include "const.h" #include "const.h"
#include "triangleapi.h" #include "triangleapi.h"
#include "r_studioint.h"
#include "mod_local.h"
#include "r_efx.h" #include "r_efx.h"
#include "demo_api.h" #include "demo_api.h"
#include "ivoicetweak.h" #include "ivoicetweak.h"
@@ -172,6 +170,8 @@ Initialize CD playlist
*/ */
static void CL_InitCDAudio( const char *filename ) static void CL_InitCDAudio( const char *filename )
{ {
byte *afile;
char *pfile;
string token; string token;
int c = 0; int c = 0;
@@ -181,10 +181,10 @@ static void CL_InitCDAudio( const char *filename )
CL_CreatePlaylist( filename ); CL_CreatePlaylist( filename );
} }
byte *afile = FS_LoadFile( filename, NULL, false ); afile = FS_LoadFile( filename, NULL, false );
if( !afile ) return; if( !afile ) return;
char *pfile = (char *)afile; pfile = (char *)afile;
// format: trackname\n [num] // format: trackname\n [num]
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL ) while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
@@ -316,12 +316,14 @@ draw hudsprite routine
*/ */
void SPR_AdjustSize( float *x, float *y, float *w, float *h ) 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 ) if( refState.width == clgame.scrInfo.iWidth && refState.height == clgame.scrInfo.iHeight )
return; return;
// scale for screen sizes // scale for screen sizes
float xscale = refState.width / (float)clgame.scrInfo.iWidth; xscale = refState.width / (float)clgame.scrInfo.iWidth;
float yscale = refState.height / (float)clgame.scrInfo.iHeight; yscale = refState.height / (float)clgame.scrInfo.iHeight;
*x *= xscale; *x *= xscale;
*y *= yscale; *y *= yscale;
@@ -365,6 +367,7 @@ draw hudsprite routine
static void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc ) static void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc )
{ {
float s1, s2, t1, t2; float s1, s2, t1, t2;
int texnum;
if( width == -1 && height == -1 ) if( width == -1 && height == -1 )
{ {
@@ -377,7 +380,7 @@ static void SPR_DrawGeneric( int frame, float x, float y, float width, float hei
height = h; height = h;
} }
int texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame ); texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame );
if( prc ) if( prc )
{ {
@@ -425,8 +428,11 @@ called each frame
void CL_DrawCenterPrint( void ) void CL_DrawCenterPrint( void )
{ {
cl_font_t *font = Con_GetCurFont(); cl_font_t *font = Con_GetCurFont();
byte line[MAX_LINELENGTH]; char *pText;
int charHeight; int i, j, x, y;
int width, lineLength;
byte *colorDefault, line[MAX_LINELENGTH];
int charWidth, charHeight;
if( !clgame.centerPrint.time ) if( !clgame.centerPrint.time )
return; return;
@@ -438,17 +444,17 @@ void CL_DrawCenterPrint( void )
return; return;
} }
int y = clgame.centerPrint.y; // start y y = clgame.centerPrint.y; // start y
byte *colorDefault = g_color_table[7]; colorDefault = g_color_table[7];
char *pText = clgame.centerPrint.message; pText = clgame.centerPrint.message;
CL_DrawCharacterLen( font, 0, NULL, &charHeight ); CL_DrawCharacterLen( font, 0, NULL, &charHeight );
CL_SetFontRendermode( font ); CL_SetFontRendermode( font );
CL_SetFontColor( font, colorDefault ); CL_SetFontColor( font, colorDefault );
for( int i = 0; i < clgame.centerPrint.lines; i++ ) for( i = 0; i < clgame.centerPrint.lines; i++ )
{ {
int lineLength = 0; lineLength = 0;
int width = 0; width = 0;
while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH ) while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH )
{ {
@@ -458,7 +464,6 @@ void CL_DrawCenterPrint( void )
continue; continue;
line[lineLength] = number; line[lineLength] = number;
int charWidth;
CL_DrawCharacterLen( font, number, &charWidth, NULL ); CL_DrawCharacterLen( font, number, &charWidth, NULL );
width += charWidth; width += charWidth;
lineLength++; lineLength++;
@@ -470,9 +475,9 @@ void CL_DrawCenterPrint( void )
pText++; // Skip LineFeed pText++; // Skip LineFeed
line[lineLength] = 0; line[lineLength] = 0;
int x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth ); x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth );
for( int j = 0; j < lineLength; j++ ) for( j = 0; j < lineLength; j++ )
{ {
if( x >= 0 && y >= 0 && x <= refState.width ) 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 ); x += CL_DrawCharacter( x, y, line[j], NULL, font, FONT_DRAW_HUD | FONT_DRAW_NORENDERMODE | FONT_DRAW_NOCOLOR );
@@ -527,7 +532,9 @@ can be modulated
static void CL_DrawScreenFade( void ) static void CL_DrawScreenFade( void )
{ {
screenfade_t *sf = &clgame.fade; screenfade_t *sf = &clgame.fade;
int alpha = V_FadeAlpha( sf ); int alpha;
alpha = V_FadeAlpha( sf );
if( !alpha ) if( !alpha )
return; return;
@@ -610,9 +617,11 @@ Parse TE_TEXTMESSAGE
void CL_ParseTextMessage( sizebuf_t *msg ) void CL_ParseTextMessage( sizebuf_t *msg )
{ {
static int msgindex = 0; static int msgindex = 0;
client_textmessage_t *text;
int channel;
// read channel ( 0 - auto) // read channel ( 0 - auto)
int channel = MSG_ReadByte( msg ); channel = MSG_ReadByte( msg );
if( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 )) if( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 ))
{ {
@@ -621,7 +630,7 @@ void CL_ParseTextMessage( sizebuf_t *msg )
} }
// grab message channel // grab message channel
client_textmessage_t *text = &cl_textmessage[channel]; text = &cl_textmessage[channel];
text->x = (float)(MSG_ReadShort( msg ) / 8192.0f); text->x = (float)(MSG_ReadShort( msg ) / 8192.0f);
text->y = (float)(MSG_ReadShort( msg ) / 8192.0f); text->y = (float)(MSG_ReadShort( msg ) / 8192.0f);
@@ -669,9 +678,12 @@ return soundname from index
*/ */
static const char *CL_SoundFromIndex( int index ) static const char *CL_SoundFromIndex( int index )
{ {
sfx_t *sfx = NULL;
int hSound;
// make sure what we in-bounds // make sure what we in-bounds
index = bound( 0, index, MAX_SOUNDS ); index = bound( 0, index, MAX_SOUNDS );
int hSound = cl.sound_index[index]; hSound = cl.sound_index[index];
if( !hSound ) if( !hSound )
{ {
@@ -679,7 +691,7 @@ static const char *CL_SoundFromIndex( int index )
return NULL; return NULL;
} }
sfx_t *sfx = S_GetSfxByHandle( hSound ); sfx = S_GetSfxByHandle( hSound );
if( !sfx ) if( !sfx )
{ {
Con_DPrintf( S_ERROR "%s: bad sfx for index %i\n", __func__, index ); Con_DPrintf( S_ERROR "%s: bad sfx for index %i\n", __func__, index );
@@ -726,6 +738,8 @@ 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 ) 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 ) if( !scissor->test )
return true; return true;
@@ -742,8 +756,8 @@ qboolean CL_Scissor( const scissor_state_t *scissor, float *x, float *y, float *
if( *y >= scissor->y + scissor->height ) if( *y >= scissor->y + scissor->height )
return false; return false;
float dudx = (*u1 - *u0) / *width; dudx = (*u1 - *u0) / *width;
float dvdy = (*v1 - *v0) / *height; dvdy = (*v1 - *v0) / *height;
if( *x < scissor->x ) if( *x < scissor->x )
{ {
@@ -930,7 +944,9 @@ void CL_DrawHUD( int state )
static void CL_ClearUserMessage( char *pszName, int svc_num ) static void CL_ClearUserMessage( char *pszName, int svc_num )
{ {
for( int i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) int i;
for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )
if( ( clgame.msg[i].number == svc_num ) && Q_stricmp( clgame.msg[i].name, pszName ) ) if( ( clgame.msg[i].number == svc_num ) && Q_stricmp( clgame.msg[i].name, pszName ) )
clgame.msg[i].number = 0; clgame.msg[i].number = 0;
} }
@@ -1054,7 +1070,9 @@ free studio cache on change level
*/ */
void CL_ClearSpriteTextures( void ) void CL_ClearSpriteTextures( void )
{ {
for( int i = 1; i < MAX_CLIENT_SPRITES; i++ ) int i;
for( i = 1; i < MAX_CLIENT_SPRITES; i++ )
{ {
if( clgame.sprites[i].needload == NL_UNREFERENCED ) if( clgame.sprites[i].needload == NL_UNREFERENCED )
continue; continue;
@@ -1192,6 +1210,8 @@ upload sprite frames
*/ */
static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags ) static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags )
{ {
byte *buf;
fs_offset_t size;
qboolean loaded; qboolean loaded;
Assert( m_pSprite != NULL ); Assert( m_pSprite != NULL );
@@ -1221,8 +1241,7 @@ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite,
} }
} }
fs_offset_t size; buf = FS_LoadFile( szSpriteName, &size, false );
byte *buf = FS_LoadFile( szSpriteName, &size, false );
if( buf == NULL ) if( buf == NULL )
return false; return false;
@@ -1259,7 +1278,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla
{ {
char name[MAX_QPATH]; char name[MAX_QPATH];
model_t *mod; model_t *mod;
int i; int i, start;
if( COM_StringEmptyOrNULL( filename )) if( COM_StringEmptyOrNULL( filename ))
{ {
@@ -1289,7 +1308,7 @@ static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFla
// find a free model slot spot // find a free model slot spot
// use low indices only for HUD sprites // use low indices only for HUD sprites
// for GoldSrc bug compatibility // for GoldSrc bug compatibility
int start = type == SPR_HUDSPRITE ? 0 : MAX_CLIENT_SPRITES / 2; start = type == SPR_HUDSPRITE ? 0 : MAX_CLIENT_SPRITES / 2;
for( i = 0, mod = &clgame.sprites[start]; i < MAX_CLIENT_SPRITES / 2; i++, mod++ ) for( i = 0, mod = &clgame.sprites[start]; i < MAX_CLIENT_SPRITES / 2; i++, mod++ )
{ {
@@ -1335,9 +1354,9 @@ pfnSPR_LoadExt
*/ */
HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags )
{ {
model_t *spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags ); model_t *spr;
if( spr == NULL ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL )
return 0; return 0;
return (spr - clgame.sprites) + 1; // return index return (spr - clgame.sprites) + 1; // return index
@@ -1353,9 +1372,9 @@ function exported for support GoldSrc Monitor utility
HSPRITE EXPORT pfnSPR_Load( const char *szPicName ); HSPRITE EXPORT pfnSPR_Load( const char *szPicName );
HSPRITE EXPORT pfnSPR_Load( const char *szPicName ) HSPRITE EXPORT pfnSPR_Load( const char *szPicName )
{ {
model_t *spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 ); model_t *spr;
if( spr == NULL ) if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL )
return 0; return 0;
return (spr - clgame.sprites) + 1; // return index return (spr - clgame.sprites) + 1; // return index
@@ -1369,16 +1388,16 @@ CL_GetSpritePointer
*/ */
static const model_t *CL_GetSpritePointer( HSPRITE hSprite ) static const model_t *CL_GetSpritePointer( HSPRITE hSprite )
{ {
model_t *mod;
int index = hSprite - 1; int index = hSprite - 1;
if( index < 0 || index >= MAX_CLIENT_SPRITES ) if( index < 0 || index >= MAX_CLIENT_SPRITES )
return NULL; // bad image return NULL; // bad image
mod = &clgame.sprites[index];
model_t *mod = &clgame.sprites[index];
if( mod->needload == NL_NEEDS_LOADED ) 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 )) if( CL_LoadHudSprite( mod->name, mod, type, mod->numtexinfo ))
return mod; return mod;
@@ -1847,6 +1866,8 @@ pfnGetPlayerInfo
*/ */
static void GAME_EXPORT pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo ) 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 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] ) if( ent_num >= cl.maxclients || ent_num < 0 || !cl.players[ent_num].name[0] )
@@ -1856,7 +1877,7 @@ static void GAME_EXPORT pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo
return; return;
} }
player_info_t *player = &cl.players[ent_num]; player = &cl.players[ent_num];
pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false; pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false;
pinfo->name = player->name; pinfo->name = player->name;
pinfo->model = player->model; pinfo->model = player->model;
@@ -1887,9 +1908,11 @@ pfnPlaySoundByIndex
*/ */
static void GAME_EXPORT pfnPlaySoundByIndex( int iSound, float volume ) static void GAME_EXPORT pfnPlaySoundByIndex( int iSound, float volume )
{ {
int hSound;
// make sure what we in-bounds // make sure what we in-bounds
iSound = bound( 0, iSound, MAX_SOUNDS ); iSound = bound( 0, iSound, MAX_SOUNDS );
int hSound = cl.sound_index[iSound]; hSound = cl.sound_index[iSound];
if( !hSound ) return; if( !hSound ) return;
S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING ); S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING );
@@ -2150,7 +2173,7 @@ pfnGetViewModel
============= =============
*/ */
static cl_entity_t *GAME_EXPORT CL_GetViewModel( void ) static cl_entity_t* GAME_EXPORT CL_GetViewModel( void )
{ {
return &clgame.viewent; return &clgame.viewent;
} }
@@ -2175,6 +2198,8 @@ pfnCalcShake
static void GAME_EXPORT pfnCalcShake( void ) static void GAME_EXPORT pfnCalcShake( void )
{ {
screen_shake_t *const shake = &clgame.shake; 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 ) if( cl.time > shake->time || shake->amplitude <= 0 || shake->frequency <= 0 || shake->duration <= 0 )
{ {
@@ -2189,7 +2214,7 @@ static void GAME_EXPORT pfnCalcShake( void )
return; return;
} }
float frametime = cl_clientframetime(); frametime = cl_clientframetime();
if( cl.time > shake->next_shake ) if( cl.time > shake->next_shake )
{ {
@@ -2197,20 +2222,20 @@ static void GAME_EXPORT pfnCalcShake( void )
shake->next_shake = (float)cl.time + shake->frequency / shake->duration; shake->next_shake = (float)cl.time + shake->frequency / shake->duration;
// randomize each shake // randomize each shake
for( int i = 0; i < 3; i++ ) for( i = 0; i < 3; i++ )
shake->offset[i] = COM_RandomFloat( -shake->amplitude, shake->amplitude ); shake->offset[i] = COM_RandomFloat( -shake->amplitude, shake->amplitude );
shake->angle = COM_RandomFloat( -shake->amplitude * 0.25f, shake->amplitude * 0.25f ); shake->angle = COM_RandomFloat( -shake->amplitude * 0.25f, shake->amplitude * 0.25f );
} }
// get initial fraction and frequency values over the duration // get initial fraction and frequency values over the duration
float fraction = ((float)cl.time - shake->time ) / shake->duration; fraction = ((float)cl.time - shake->time ) / shake->duration;
float freq = fraction != 0.0f ? ( shake->frequency / fraction ) * shake->frequency : 0.0f; freq = fraction != 0.0f ? ( shake->frequency / fraction ) * shake->frequency : 0.0f;
// quickly approach zero but apply time over sine wave // quickly approach zero but apply time over sine wave
fraction *= fraction * sin( cl.time * freq ); fraction *= fraction * sin( cl.time * freq );
// apply shake offset // apply shake offset
for( int i = 0; i < 3; i++ ) for( i = 0; i < 3; i++ )
shake->applied_offset[i] = shake->offset[i] * fraction; shake->applied_offset[i] = shake->offset[i] * fraction;
// apply roll angle // apply roll angle
@@ -2322,7 +2347,10 @@ pfnKillEvent
*/ */
static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname ) static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname )
{ {
word eventIndex = CL_EventIndex( eventname ); int i;
event_state_t *es;
event_info_t *ei;
word eventIndex = CL_EventIndex( eventname );
if( eventIndex >= MAX_EVENTS ) if( eventIndex >= MAX_EVENTS )
return; return;
@@ -2330,12 +2358,12 @@ static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname )
if( entnum < 0 || entnum >= clgame.maxEntities ) if( entnum < 0 || entnum >= clgame.maxEntities )
return; return;
event_state_t *es = &cl.events; es = &cl.events;
// find all events with specified index and kill it // find all events with specified index and kill it
for( int i = 0; i < MAX_EVENT_QUEUE; i++ ) for( i = 0; i < MAX_EVENT_QUEUE; i++ )
{ {
event_info_t *ei = &es->ei[i]; ei = &es->ei[i];
if( ei->index == eventIndex && ei->entity_index == entnum ) if( ei->index == eventIndex && ei->entity_index == entnum )
{ {
@@ -2365,6 +2393,7 @@ CL_FindModelIndex
static int GAME_EXPORT CL_FindModelIndex( const char *m ) static int GAME_EXPORT CL_FindModelIndex( const char *m )
{ {
char filepath[MAX_QPATH]; char filepath[MAX_QPATH];
int i;
if( COM_StringEmptyOrNULL( m )) if( COM_StringEmptyOrNULL( m ))
return 0; return 0;
@@ -2372,7 +2401,7 @@ static int GAME_EXPORT CL_FindModelIndex( const char *m )
Q_strncpy( filepath, m, sizeof( filepath )); Q_strncpy( filepath, m, sizeof( filepath ));
COM_FixSlashes( filepath ); COM_FixSlashes( filepath );
for( int i = 0; i < cl.nummodels; i++ ) for( i = 0; i < cl.nummodels; i++ )
{ {
if( !cl.models[i+1] ) if( !cl.models[i+1] )
continue; continue;
@@ -2704,7 +2733,9 @@ pfnLoadMapSprite
*/ */
static model_t *pfnLoadMapSprite( const char *filename ) static model_t *pfnLoadMapSprite( const char *filename )
{ {
model_t *mod = Mod_FindName( filename, false ); model_t *mod;
mod = Mod_FindName( filename, false );
if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 )) if( CL_LoadHudSprite( filename, mod, SPR_MAPSPRITE, 0 ))
return mod; return mod;
@@ -2780,10 +2811,12 @@ PlayerInfo_SetValueForKey
*/ */
static void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value ) static void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value )
{ {
convar_t *var;
if( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value )) if( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value ))
return; // no changes ? return; // no changes ?
convar_t *var = Cvar_FindVar( key ); var = Cvar_FindVar( key );
if( var && FBitSet( var->flags, FCVAR_USERINFO )) if( var && FBitSet( var->flags, FCVAR_USERINFO ))
{ {
@@ -2864,10 +2897,11 @@ pfnGetMousePos
*/ */
static void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt ) static void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt )
{ {
int x, y;
if( !ppt ) if( !ppt )
return; return;
int x, y;
Platform_GetMousePos( &x, &y ); Platform_GetMousePos( &x, &y );
ppt->x = x; ppt->x = x;
@@ -3016,11 +3050,11 @@ static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int
{ {
rgba_t color = { r, g, b, 255 }; rgba_t color = { r, g, b, 255 };
int flags = FONT_DRAW_HUD | FONT_DRAW_NOLF; int flags = FONT_DRAW_HUD | FONT_DRAW_NOLF;
int width;
if( hud_utf8.value ) if( hud_utf8.value )
SetBits( flags, FONT_DRAW_UTF8 ); SetBits( flags, FONT_DRAW_UTF8 );
int width;
CL_DrawStringLen( &cls.creditsFont, str, &width, NULL, flags ); CL_DrawStringLen( &cls.creditsFont, str, &width, NULL, flags );
x -= width; x -= width;
@@ -3200,9 +3234,11 @@ TriBrightness
*/ */
void TriBrightness( float brightness ) void TriBrightness( float brightness )
{ {
float r = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness; float r, g, b;
float g = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness;
float b = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness; r = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness;
g = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness;
b = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness;
ref.dllFuncs.Color4f( r, g, b, 1.0f ); ref.dllFuncs.Color4f( r, g, b, 1.0f );
} }
@@ -3249,9 +3285,11 @@ NOTE: dlights are ignored
*/ */
void TriLightAtPoint( float *pos, float *value ) void TriLightAtPoint( float *pos, float *value )
{ {
colorVec vLightColor;
if( !pos || !value ) return; if( !pos || !value ) return;
colorVec vLightColor = ref.dllFuncs.R_LightPoint( pos ); vLightColor = ref.dllFuncs.R_LightPoint( pos );
value[0] = vLightColor.r; value[0] = vLightColor.r;
value[1] = vLightColor.g; value[1] = vLightColor.g;
@@ -3284,9 +3322,9 @@ bind current texture
*/ */
int TriSpriteTexture( model_t *pSpriteModel, int frame ) int TriSpriteTexture( model_t *pSpriteModel, int frame )
{ {
int gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame ); int gl_texturenum;
if( gl_texturenum <= 0 ) if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
return 0; return 0;
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum ); ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );
@@ -3364,60 +3402,44 @@ NetAPI_SendRequest
*/ */
static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response ) static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response )
{ {
net_request_t *nr = NULL;
int i;
if( !response ) if( !response )
{ {
Con_DPrintf( S_ERROR "%s: no callback specified for request with context %i!\n", __func__, context ); Con_DPrintf( S_ERROR "%s: no callbcak specified for request with context %i!\n", __func__, context );
return; return;
} }
// FIXME: call pfnFunc and tell client.dll about errors?
if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX ) if( NET_NetadrType( remote_address ) == NA_IPX || NET_NetadrType( remote_address ) == NA_BROADCAST_IPX )
return; // IPX no longer support return; // IPX no longer support
switch( request ) if( request == NETAPI_REQUEST_SERVERLIST )
{
case NETAPI_REQUEST_SERVERLIST:
return; // no support for server list requests return; // no support for server list requests
case NETAPI_REQUEST_PING:
case NETAPI_REQUEST_RULES:
case NETAPI_REQUEST_PLAYERS:
case NETAPI_REQUEST_DETAILS:
break;
default:
Con_Printf( S_ERROR "%s: unknown request type %d, context %i\n", __func__, request, context );
return;
}
// find a free request // find a free request
net_request_t *nr = NULL, *oldest_nr = NULL; for( i = 0; i < MAX_REQUESTS; i++ )
int i;
double max_timeout = 0;
for( i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ )
{ {
nr = &clgame.net_requests[i]; nr = &clgame.net_requests[i];
if( !nr->pfnFunc ) break;
if(( host.realtime - nr->timesend ) > max_timeout )
{
max_timeout = host.realtime - nr->timesend;
oldest_nr = nr;
}
if( !nr->pfnFunc )
break;
} }
if( i == ARRAYSIZE( clgame.net_requests )) if( i == MAX_REQUESTS )
{ {
double max_timeout = 0;
// no free requests? use oldest // no free requests? use oldest
nr = oldest_nr; for( i = 0, nr = NULL; i < MAX_REQUESTS; i++ )
{
if(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout )
{
max_timeout = host.realtime - clgame.net_requests[i].timesend;
nr = &clgame.net_requests[i];
}
}
} }
if( !nr ) Assert( nr != NULL );
{
Con_Printf( S_ERROR "%s: no free requests\n", __func__ );
return;
}
// clear slot // clear slot
memset( nr, 0, sizeof( *nr )); memset( nr, 0, sizeof( *nr ));
@@ -3430,10 +3452,9 @@ static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags,
nr->resp.type = request; nr->resp.type = request;
nr->resp.remote_address = *remote_address; nr->resp.remote_address = *remote_address;
nr->flags = flags; nr->flags = flags;
nr->challenge = -1;
if( !CL_NetRequestSend( nr )) // local servers request
Con_Printf( S_ERROR "%s: failed to send net request for type %d with context %i\n", __func__, request, context ); Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, A2A_NETINFO" %i %i %i", PROTOCOL_VERSION, context, request );
} }
/* /*
@@ -3444,10 +3465,13 @@ NetAPI_CancelRequest
*/ */
static void GAME_EXPORT NetAPI_CancelRequest( int context ) static void GAME_EXPORT NetAPI_CancelRequest( int context )
{ {
net_request_t *nr;
int i;
;
// find a specified request // find a specified request
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ ) for( i = 0; i < MAX_REQUESTS; i++ )
{ {
net_request_t *nr = &clgame.net_requests[i]; nr = &clgame.net_requests[i];
if( clgame.net_requests[i].resp.context == context ) if( clgame.net_requests[i].resp.context == context )
{ {
@@ -3472,10 +3496,13 @@ NetAPI_CancelAllRequests
*/ */
void GAME_EXPORT NetAPI_CancelAllRequests( void ) void GAME_EXPORT NetAPI_CancelAllRequests( void )
{ {
net_request_t *nr;
int i;
// tell the user about cancel // tell the user about cancel
for( int i = 0; i < ARRAYSIZE( clgame.net_requests ); i++ ) for( i = 0; i < MAX_REQUESTS; i++ )
{ {
net_request_t *nr = &clgame.net_requests[i]; nr = &clgame.net_requests[i];
if( !nr->pfnFunc ) continue; // not used if( !nr->pfnFunc ) continue; // not used
SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); SetBits( nr->resp.error, NET_ERROR_TIMEOUT );
nr->resp.ping = host.realtime - nr->timesend; nr->resp.ping = host.realtime - nr->timesend;
@@ -3585,16 +3612,6 @@ static void GAME_EXPORT VGui_ViewportPaintBackground( int extents[4] )
// stub // 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 // shared between client and server
triangleapi_t gTriApi; triangleapi_t gTriApi;
@@ -3817,7 +3834,7 @@ static cl_enginefunc_t gEngfuncs =
pfnHookEvent, pfnHookEvent,
Con_Visible, Con_Visible,
pfnGetGameDirectory, pfnGetGameDirectory,
CL_CvarGetPointer, pfnCVarGetPointer,
Key_LookupBinding, Key_LookupBinding,
pfnGetLevelName, pfnGetLevelName,
pfnGetScreenFade, pfnGetScreenFade,
@@ -3912,68 +3929,6 @@ void CL_UnloadProgs( void )
memset( &clgame, 0, sizeof( clgame )); 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 ) qboolean CL_LoadProgs( const char *name )
{ {
static playermove_t gpMove; static playermove_t gpMove;
@@ -3981,6 +3936,7 @@ qboolean CL_LoadProgs( const char *name )
qboolean valid_single_export = false; qboolean valid_single_export = false;
qboolean missed_exports = false; qboolean missed_exports = false;
qboolean try_internal_vgui_support = GI->internal_vgui_support; qboolean try_internal_vgui_support = GI->internal_vgui_support;
int i;
if( clgame.hInstance ) CL_UnloadProgs(); if( clgame.hInstance ) CL_UnloadProgs();
@@ -4036,7 +3992,7 @@ qboolean CL_LoadProgs( const char *name )
if( GetClientAPI != NULL ) // check critical functions again if( GetClientAPI != NULL ) // check critical functions again
valid_single_export = ValidateExports( cdll_exports, ARRAYSIZE( cdll_exports )); valid_single_export = ValidateExports( cdll_exports, ARRAYSIZE( cdll_exports ));
for( int i = 0; i < ARRAYSIZE( cdll_exports ); i++ ) for( i = 0; i < ARRAYSIZE( cdll_exports ); i++ )
{ {
if( *(cdll_exports[i].func) != NULL ) if( *(cdll_exports[i].func) != NULL )
continue; // already got through 'F' or 'GetClientAPI' continue; // already got through 'F' or 'GetClientAPI'
@@ -4067,7 +4023,7 @@ qboolean CL_LoadProgs( const char *name )
if( !valid_single_export ) if( !valid_single_export )
ClearExports( cdll_new_exports, ARRAYSIZE( cdll_new_exports )); ClearExports( cdll_new_exports, ARRAYSIZE( cdll_new_exports ));
for( int i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ ) for( i = 0; i < ARRAYSIZE( cdll_new_exports ); i++ )
{ {
if( *(cdll_new_exports[i].func) != NULL ) if( *(cdll_new_exports[i].func) != NULL )
continue; // already gott through 'F' or 'GetClientAPI' continue; // already gott through 'F' or 'GetClientAPI'
@@ -4110,8 +4066,7 @@ qboolean CL_LoadProgs( const char *name )
// initialize game // initialize game
clgame.dllFuncs.pfnInit(); clgame.dllFuncs.pfnInit();
CL_InitStudioAPI(); ref.dllFuncs.CL_InitStudioAPI();
S_InitSoundAPI();
return true; return true;
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -15,7 +15,6 @@ GNU General Public License for more details.
#include "common.h" #include "common.h"
#include "client.h" #include "client.h"
#include "mod_local.h"
#include "net_encode.h" #include "net_encode.h"
#include "cl_tent.h" #include "cl_tent.h"
#include "shake.h" #include "shake.h"
@@ -60,33 +59,35 @@ CL_ParseSoundPacket
*/ */
static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore ) static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
{ {
int flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS ); vec3_t pos;
int sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS ); int chan, sound;
int chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS ); float volume, attn;
int flags, pitch, entnum, wordIndex = 0;
sound_t handle = 0;
double samplePos = 0, forcedEnd = 0;
flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS );
sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS );
chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS );
float volume;
if( FBitSet( flags, SND_VOLUME )) if( FBitSet( flags, SND_VOLUME ))
volume = (float)MSG_ReadByte( msg ) / 255.0f; volume = (float)MSG_ReadByte( msg ) / 255.0f;
else volume = VOL_NORM; else volume = VOL_NORM;
float attn;
if( FBitSet( flags, SND_ATTENUATION )) if( FBitSet( flags, SND_ATTENUATION ))
attn = (float)MSG_ReadByte( msg ) / 64.0f; attn = (float)MSG_ReadByte( msg ) / 64.0f;
else attn = ATTN_NONE; else attn = ATTN_NONE;
int pitch;
if( FBitSet( flags, SND_PITCH )) if( FBitSet( flags, SND_PITCH ))
pitch = MSG_ReadByte( msg ); pitch = MSG_ReadByte( msg );
else pitch = PITCH_NORM; else pitch = PITCH_NORM;
// entity reletive // entity reletive
int entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
// positioned in space // positioned in space
vec3_t pos;
MSG_ReadVec3Coord( msg, pos ); MSG_ReadVec3Coord( msg, pos );
sound_t handle = 0;
if( FBitSet( flags, SND_SENTENCE )) if( FBitSet( flags, SND_SENTENCE ))
{ {
char sentenceName[32]; char sentenceName[32];
@@ -99,15 +100,13 @@ static void CL_ParseSoundPacket( sizebuf_t *msg, qboolean restore )
} }
else handle = cl.sound_index[sound]; // see precached sound else handle = cl.sound_index[sound]; // see precached sound
uint wordIndex = 0;
double samplePos = 0, forcedEnd = 0;
if( restore ) if( restore )
{ {
wordIndex = MSG_ReadByte( msg ); wordIndex = MSG_ReadByte( msg );
// 16 bytes here // 16 bytes here
MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ), sizeof( samplePos )); MSG_ReadBytes( msg, &samplePos, sizeof( samplePos ));
MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ), sizeof( forcedEnd )); MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd ));
} }
if( !cl.audio_prepped ) if( !cl.audio_prepped )
@@ -129,6 +128,8 @@ CL_ParseServerTime
*/ */
void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto ) void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto )
{ {
double dt;
cl.mtime[1] = cl.mtime[0]; cl.mtime[1] = cl.mtime[0];
cl.mtime[0] = MSG_ReadFloat( msg ); cl.mtime[0] = MSG_ReadFloat( msg );
@@ -138,7 +139,7 @@ void CL_ParseServerTime( sizebuf_t *msg, connprotocol_t proto )
if( cl.maxclients == 1 ) if( cl.maxclients == 1 )
cl.time = cl.mtime[0]; cl.time = cl.mtime[0];
double dt = cl.time - cl.mtime[0]; dt = cl.time - cl.mtime[0];
if( fabs( dt ) > cl_clockreset.value ) // 0.1 by default if( fabs( dt ) > cl_clockreset.value ) // 0.1 by default
{ {
@@ -210,26 +211,29 @@ CL_ParseParticles
*/ */
static void CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto ) static void CL_ParseParticles( sizebuf_t *msg, connprotocol_t proto )
{ {
vec3_t org, dir; vec3_t org, dir;
int i, count, color;
float life;
MSG_ReadVec3Coord( msg, org ); MSG_ReadVec3Coord( msg, org );
for( int i = 0; i < 3; i++ ) for( i = 0; i < 3; i++ )
dir[i] = MSG_ReadChar( msg ) * 0.0625f; dir[i] = MSG_ReadChar( msg ) * 0.0625f;
int count = MSG_ReadByte( msg ); count = MSG_ReadByte( msg );
int color = MSG_ReadByte( msg ); color = MSG_ReadByte( msg );
if( count == 255 ) if( count == 255 )
count = 1024; count = 1024;
float life;
if( proto == PROTO_GOLDSRC ) if( proto == PROTO_GOLDSRC )
life = 0.0f; life = 0.0f;
else life = MSG_ReadByte( msg ) * 0.125f; else life = MSG_ReadByte( msg ) * 0.125f;
if( life != 0.0f && count == 1 ) if( life != 0.0f && count == 1 )
{ {
particle_t *p = R_AllocParticle( NULL ); particle_t *p;
p = R_AllocParticle( NULL );
if( !p ) return; if( !p ) return;
p->die += life; p->die += life;
@@ -251,23 +255,25 @@ static client entity
*/ */
static void CL_ParseStaticEntity( sizebuf_t *msg ) static void CL_ParseStaticEntity( sizebuf_t *msg )
{ {
int i, newnum;
const entity_state_t from = { 0 }; const entity_state_t from = { 0 };
entity_state_t to; entity_state_t to;
cl_entity_t *ent;
if( !clgame.static_entities ) if( !clgame.static_entities )
clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES ); clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );
MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS );
MSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] ); MSG_ReadDeltaEntity( msg, &from, &to, 0, DELTA_STATIC, cl.mtime[0] );
int i = clgame.numStatics; i = clgame.numStatics;
if( i >= MAX_STATIC_ENTITIES ) if( i >= MAX_STATIC_ENTITIES )
{ {
Con_Printf( S_ERROR "MAX_STATIC_ENTITIES limit exceeded!\n" ); Con_Printf( S_ERROR "MAX_STATIC_ENTITIES limit exceeded!\n" );
return; return;
} }
cl_entity_t *ent = &clgame.static_entities[i]; ent = &clgame.static_entities[i];
clgame.numStatics++; clgame.numStatics++;
// all states are same // all states are same
@@ -308,7 +314,7 @@ Set new weapon animation
*/ */
void GAME_EXPORT CL_WeaponAnim( int iAnim, int body ) void GAME_EXPORT CL_WeaponAnim( int iAnim, int body )
{ {
cl_entity_t *view = &clgame.viewent; cl_entity_t *view = &clgame.viewent;
cl.local.weaponstarttime = 0.0f; cl.local.weaponstarttime = 0.0f;
cl.local.weaponsequence = iAnim; cl.local.weaponsequence = iAnim;
@@ -332,18 +338,20 @@ CL_ParseStaticDecal
*/ */
void CL_ParseStaticDecal( sizebuf_t *msg ) void CL_ParseStaticDecal( sizebuf_t *msg )
{ {
vec3_t origin; vec3_t origin;
int decalIndex, entityIndex, modelIndex;
float scale;
int flags;
MSG_ReadVec3Coord( msg, origin ); MSG_ReadVec3Coord( msg, origin );
int decalIndex = MSG_ReadWord( msg ); decalIndex = MSG_ReadWord( msg );
int entityIndex = MSG_ReadShort( msg ); entityIndex = MSG_ReadShort( msg );
int modelIndex;
if( entityIndex > 0 ) if( entityIndex > 0 )
modelIndex = MSG_ReadWord( msg ); modelIndex = MSG_ReadWord( msg );
else modelIndex = 0; else modelIndex = 0;
int flags = MSG_ReadByte( msg ); flags = MSG_ReadByte( msg );
float scale = (float)MSG_ReadWord( msg ) / 4096.0f; scale = (float)MSG_ReadWord( msg ) / 4096.0f;
CL_FireCustomDecal( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, origin, flags, scale ); CL_FireCustomDecal( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, origin, flags, scale );
} }
@@ -372,9 +380,11 @@ CL_RequestMissingResources
*/ */
qboolean CL_RequestMissingResources( void ) qboolean CL_RequestMissingResources( void )
{ {
resource_t *p;
if( !cls.dl.doneregistering && ( cls.dl.custom || cls.state == ca_validate )) if( !cls.dl.doneregistering && ( cls.dl.custom || cls.state == ca_validate ))
{ {
resource_t *p = cl.resourcesneeded.pNext; p = cl.resourcesneeded.pNext;
if( p == &cl.resourcesneeded ) if( p == &cl.resourcesneeded )
{ {
@@ -393,16 +403,17 @@ qboolean CL_RequestMissingResources( void )
void CL_BatchResourceRequest( qboolean initialize ) void CL_BatchResourceRequest( qboolean initialize )
{ {
byte data[MAX_INIT_MSG]; byte data[MAX_INIT_MSG];
sizebuf_t msg; resource_t *p, *n;
qboolean done_downloading = true; sizebuf_t msg;
qboolean done_downloading = true;
MSG_Init( &msg, "Resource Batch", data, sizeof( data )); MSG_Init( &msg, "Resource Batch", data, sizeof( data ));
// client resources is not precached by server // client resources is not precached by server
if( initialize ) CL_AddClientResources(); if( initialize ) CL_AddClientResources();
for( resource_t *p = cl.resourcesneeded.pNext, *n; p && p != &cl.resourcesneeded; p = n ) for( p = cl.resourcesneeded.pNext; p && p != &cl.resourcesneeded; p = n )
{ {
n = p->pNext; n = p->pNext;
@@ -469,7 +480,7 @@ void CL_BatchResourceRequest( qboolean initialize )
{ {
if( done_downloading && CL_PrecacheResources( )) if( done_downloading && CL_PrecacheResources( ))
{ {
CL_RegisterResources( &msg, cls.net_protocol ); CL_RegisterResources( &msg, cls.legacymode );
} }
Netchan_CreateFragments( &cls.netchan, &msg ); Netchan_CreateFragments( &cls.netchan, &msg );
@@ -479,9 +490,10 @@ void CL_BatchResourceRequest( qboolean initialize )
int CL_EstimateNeededResources( void ) int CL_EstimateNeededResources( void )
{ {
int nTotalSize = 0; resource_t *p;
int nTotalSize = 0;
for( resource_t *p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext )
{ {
switch( p->type ) switch( p->type )
{ {
@@ -557,7 +569,9 @@ static void CL_StartResourceDownloading( const char *pszMessage, qboolean bCusto
static customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type ) static customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type )
{ {
for( customization_t *pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext ) customization_t *pList;
for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext )
{ {
if( pList->resource.type == type ) if( pList->resource.type == type )
return pList; return pList;
@@ -567,8 +581,10 @@ static customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_
static void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove ) static void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove )
{ {
customization_t *pNext; customization_t *pList;
for( customization_t *pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext ) customization_t *pNext;
for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext )
{ {
pNext = pList->pNext; pNext = pList->pNext;
@@ -602,11 +618,17 @@ CL_ParseCustomization
*/ */
static void CL_ParseCustomization( sizebuf_t *msg ) static void CL_ParseCustomization( sizebuf_t *msg )
{ {
int i = MSG_ReadByte( msg ); customization_t *pExistingCustomization;
customization_t *pList;
qboolean bFound;
resource_t *pRes;
int i;
i = MSG_ReadByte( msg );
if( i >= MAX_CLIENTS ) if( i >= MAX_CLIENTS )
Host_Error( "Bogus player index during customization parsing.\n" ); Host_Error( "Bogus player index during customization parsing.\n" );
resource_t *pRes = Mem_Calloc( cls.mempool, sizeof( resource_t )); pRes = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pRes->type = MSG_ReadByte( msg ); pRes->type = MSG_ReadByte( msg );
Q_strncpy( pRes->szFileName, MSG_ReadString( msg ), sizeof( pRes->szFileName )); Q_strncpy( pRes->szFileName, MSG_ReadString( msg ), sizeof( pRes->szFileName ));
@@ -616,7 +638,7 @@ static void CL_ParseCustomization( sizebuf_t *msg )
pRes->pNext = pRes->pPrev = NULL; pRes->pNext = pRes->pPrev = NULL;
if( FBitSet( pRes->ucFlags, RES_CUSTOM )) if( FBitSet( pRes->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, pRes->rgucMD5_hash, sizeof( pRes->rgucMD5_hash ), 16 ); MSG_ReadBytes( msg, pRes->rgucMD5_hash, 16 );
pRes->playernum = i; pRes->playernum = i;
if( !cl_allow_download.value ) if( !cl_allow_download.value )
@@ -633,13 +655,13 @@ static void CL_ParseCustomization( sizebuf_t *msg )
return; return;
} }
customization_t *pExistingCustomization = CL_PlayerHasCustomization( i, pRes->type ); pExistingCustomization = CL_PlayerHasCustomization( i, pRes->type );
if( pExistingCustomization ) if( pExistingCustomization )
CL_RemoveCustomization( i, pExistingCustomization ); CL_RemoveCustomization( i, pExistingCustomization );
bFound = false;
qboolean bFound = false; for( pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext )
for( customization_t *pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext )
{ {
if( !memcmp( pList->resource.rgucMD5_hash, pRes->rgucMD5_hash, 16 )) if( !memcmp( pList->resource.rgucMD5_hash, pRes->rgucMD5_hash, 16 ))
{ {
@@ -650,11 +672,11 @@ static void CL_ParseCustomization( sizebuf_t *msg )
if( HPAK_GetDataPointer( hpk_custom_file.string, pRes, NULL, NULL )) if( HPAK_GetDataPointer( hpk_custom_file.string, pRes, NULL, NULL ))
{ {
qboolean bError = false; qboolean bError = false;
if( !bFound ) if( !bFound )
{ {
customization_t *pList = &cl.players[pRes->playernum].customdata; pList = &cl.players[pRes->playernum].customdata;
if( !COM_CreateCustomization( pList, pRes, pRes->playernum, FCUST_FROMHPAK, NULL, NULL )) if( !COM_CreateCustomization( pList, pRes, pRes->playernum, FCUST_FROMHPAK, NULL, NULL ))
bError = true; bError = true;
@@ -682,43 +704,45 @@ CL_ParseResourceRequest
================== ==================
*/ */
void CL_SendResourceList( const resource_t *list, int count )
{
byte buffer[MAX_INIT_MSG];
sizebuf_t sbuf;
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
MSG_BeginClientCmd( &sbuf, clc_resourcelist );
MSG_WriteShort( &sbuf, count );
for( int i = 0; i < count; i++ )
{
MSG_WriteString( &sbuf, list[i].szFileName );
MSG_WriteByte( &sbuf, list[i].type );
MSG_WriteShort( &sbuf, list[i].nIndex );
MSG_WriteLong( &sbuf, list[i].nDownloadSize );
MSG_WriteByte( &sbuf, list[i].ucFlags );
if( FBitSet( list[i].ucFlags, RES_CUSTOM ))
MSG_WriteBytes( &sbuf, list[i].rgucMD5_hash, 16 );
}
Netchan_CreateFragments( &cls.netchan, &sbuf );
Netchan_FragSend( &cls.netchan );
}
static void CL_ParseResourceRequest( sizebuf_t *msg ) static void CL_ParseResourceRequest( sizebuf_t *msg )
{ {
int arg = MSG_ReadLong( msg ); byte buffer[MAX_INIT_MSG];
int nStartIndex = MSG_ReadLong( msg ); int i, arg, nStartIndex;
sizebuf_t sbuf;
MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer ));
arg = MSG_ReadLong( msg );
nStartIndex = MSG_ReadLong( msg );
if( cl.servercount != arg ) if( cl.servercount != arg )
return; return;
if( nStartIndex < 0 || nStartIndex >= cl.num_resources ) if( nStartIndex < 0 && nStartIndex > cl.num_resources )
return; return;
CL_SendResourceList( &cl.resourcelist[nStartIndex], cl.num_resources - nStartIndex ); MSG_BeginClientCmd( &sbuf, clc_resourcelist );
MSG_WriteShort( &sbuf, cl.num_resources );
for( i = nStartIndex; i < cl.num_resources; i++ )
{
MSG_WriteString( &sbuf, cl.resourcelist[i].szFileName );
MSG_WriteByte( &sbuf, cl.resourcelist[i].type );
MSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex );
MSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize );
MSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags );
if( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM ))
MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 );
}
// a1ba: useless check? MSG_BeginClientCmd and MSG_WriteShort will always
// write to the buffer
// if( MSG_GetNumBytesWritten( &sbuf ) > 0 )
{
Netchan_CreateFragments( &cls.netchan, &sbuf );
Netchan_FragSend( &cls.netchan );
}
} }
/* /*
@@ -730,12 +754,16 @@ loading custom decal for self
*/ */
static void CL_CreateCustomizationList( void ) static void CL_CreateCustomizationList( void )
{ {
player_info_t *pPlayer = &cl.players[cl.playernum]; resource_t *pResource;
player_info_t *pPlayer;
int i;
pPlayer = &cl.players[cl.playernum];
pPlayer->customdata.pNext = NULL; pPlayer->customdata.pNext = NULL;
for( int i = 0; i < cl.num_resources; i++ ) for( i = 0; i < cl.num_resources; i++ )
{ {
resource_t *pResource = &cl.resourcelist[i]; pResource = &cl.resourcelist[i];
if( !COM_CreateCustomization( &pPlayer->customdata, pResource, cl.playernum, 0, NULL, NULL )) if( !COM_CreateCustomization( &pPlayer->customdata, pResource, cl.playernum, 0, NULL, NULL ))
Con_Printf( "problem with client customization %s, ignoring...", pResource->szFileName ); Con_Printf( "problem with client customization %s, ignoring...", pResource->szFileName );
@@ -813,7 +841,7 @@ static void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto )
byte clientdllmd5[16]; byte clientdllmd5[16];
const char *s; const char *s;
MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ), sizeof( clientdllmd5 )); MSG_ReadBytes( msg, clientdllmd5, sizeof( clientdllmd5 ));
cl.maxclients = MSG_ReadByte( msg ); cl.maxclients = MSG_ReadByte( msg );
cl.playernum = MSG_ReadByte( msg ); cl.playernum = MSG_ReadByte( msg );
COM_UnMunge3((byte *)&cl.checksum, sizeof( cl.checksum ), ( 0xff - cl.playernum ) & 0xff ); COM_UnMunge3((byte *)&cl.checksum, sizeof( cl.checksum ), ( 0xff - cl.playernum ) & 0xff );
@@ -861,15 +889,6 @@ static void CL_ParseServerData( sizebuf_t *msg, connprotocol_t proto )
} }
} }
if( cl.playernum < 0 || cl.playernum >= MAX_CLIENTS || cl.maxclients <= 0 || cl.maxclients > MAX_CLIENTS )
{
Con_Printf( S_ERROR "%s: invalid playernum or maxclients (%d and %d must be less than %d)\n", __func__,
cl.playernum, cl.maxclients, MAX_CLIENTS );
CL_Disconnect_f();
Host_AbortCurrentFrame();
return;
}
Q_snprintf( mapfile, sizeof( mapfile ), "maps/%s.bsp", clgame.mapname ); Q_snprintf( mapfile, sizeof( mapfile ), "maps/%s.bsp", clgame.mapname );
if( CRC32_MapFile( &cl.worldmapCRC, mapfile, cl.maxclients > 1 )) if( CRC32_MapFile( &cl.worldmapCRC, mapfile, cl.maxclients > 1 ))
{ {
@@ -1186,8 +1205,8 @@ void CL_ParseBaseline( sizebuf_t *msg, connprotocol_t proto )
int type = MSG_ReadUBitLong( msg, 2 ); int type = MSG_ReadUBitLong( msg, 2 );
int delta_type; int delta_type;
if( FBitSet( type, ENTITY_BEAM )) delta_type = DT_CUSTOM_ENTITY_STATE_T; if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T;
else if( player ) delta_type = DT_ENTITY_STATE_PLAYER_T; else if( type != ENTITY_NORMAL ) delta_type = DT_CUSTOM_ENTITY_STATE_T;
else delta_type = DT_ENTITY_STATE_T; else delta_type = DT_ENTITY_STATE_T;
Delta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f ); Delta_ReadGSFields( msg, delta_type, &ent->prevstate, &ent->baseline, 1.0f );
@@ -1217,10 +1236,12 @@ CL_ParseLightStyle
*/ */
static void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto ) static void CL_ParseLightStyle( sizebuf_t *msg, connprotocol_t proto )
{ {
float f = cl.mtime[0]; int style;
const char *s;
float f = cl.mtime[0];
int style = MSG_ReadByte( msg ); style = MSG_ReadByte( msg );
const char *s = MSG_ReadString( msg ); s = MSG_ReadString( msg );
if( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE ) if( proto != PROTO_GOLDSRC && proto != PROTO_QUAKE )
f = MSG_ReadFloat( msg ); f = MSG_ReadFloat( msg );
@@ -1248,7 +1269,10 @@ add the view angle yaw
*/ */
static void CL_ParseAddAngle( sizebuf_t *msg ) static void CL_ParseAddAngle( sizebuf_t *msg )
{ {
float delta_yaw = MSG_ReadBitAngle( msg, 16 ); pred_viewangle_t *a;
float delta_yaw;
delta_yaw = MSG_ReadBitAngle( msg, 16 );
#if 0 #if 0
cl.viewangles[YAW] += delta_yaw; cl.viewangles[YAW] += delta_yaw;
return; return;
@@ -1258,7 +1282,7 @@ static void CL_ParseAddAngle( sizebuf_t *msg )
// select entry into circular buffer // select entry into circular buffer
cl.angle_position = (cl.angle_position + 1) & ANGLE_MASK; cl.angle_position = (cl.angle_position + 1) & ANGLE_MASK;
pred_viewangle_t *a = &cl.predicted_angle[cl.angle_position]; a = &cl.predicted_angle[cl.angle_position];
// record update // record update
a->starttime = cl.mtime[0]; a->starttime = cl.mtime[0];
@@ -1288,18 +1312,20 @@ reading decals, etc.
*/ */
static void CL_ParseRestore( sizebuf_t *msg ) static void CL_ParseRestore( sizebuf_t *msg )
{ {
string filename; string filename;
int i, mapCount;
char *pMapName;
// mapname.HL2 // mapname.HL2
Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename )); Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename ));
int mapCount = MSG_ReadByte( msg ); mapCount = MSG_ReadByte( msg );
// g-cont. acutally in Xash3D this does nothing. // g-cont. acutally in Xash3D this does nothing.
// decals already restored on a server, and correctly transferred through levels // decals already restored on a server, and correctly transferred through levels
// but i'm leave this message for backward compatibility // but i'm leave this message for backward compatibility
for( int i = 0; i < mapCount; i++ ) for( i = 0; i < mapCount; i++ )
{ {
char *pMapName = MSG_ReadString( msg ); pMapName = MSG_ReadString( msg );
Con_Printf( "Loading decals from %s\n", pMapName ); Con_Printf( "Loading decals from %s\n", pMapName );
} }
} }
@@ -1313,9 +1339,10 @@ register new user message or update existing
*/ */
static void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto ) static void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto )
{ {
char *pszName;
char szName[17]; char szName[17];
int svc_num = MSG_ReadByte( msg );
int size; int size;
int svc_num = MSG_ReadByte( msg );
if( proto == PROTO_GOLDSRC ) if( proto == PROTO_GOLDSRC )
{ {
@@ -1330,10 +1357,9 @@ static void CL_RegisterUserMessage( sizebuf_t *msg, connprotocol_t proto )
size = -1; size = -1;
} }
char *pszName;
if( proto == PROTO_GOLDSRC ) if( proto == PROTO_GOLDSRC )
{ {
MSG_ReadBytes( msg, szName, sizeof( szName ), sizeof( szName ) - 1 ); MSG_ReadBytes( msg, szName, sizeof( szName ) - 1 );
szName[16] = 0; szName[16] = 0;
pszName = szName; pszName = szName;
} }
@@ -1351,8 +1377,9 @@ collect userinfo from all players
*/ */
static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto ) static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
{ {
int slot, id; int slot, id;
qboolean active; qboolean active;
player_info_t *player;
if( proto == PROTO_GOLDSRC ) if( proto == PROTO_GOLDSRC )
{ {
@@ -1370,7 +1397,7 @@ static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
if( slot >= MAX_CLIENTS ) if( slot >= MAX_CLIENTS )
Host_Error( "%s: svc_updateuserinfo >= MAX_CLIENTS\n", __func__ ); Host_Error( "%s: svc_updateuserinfo >= MAX_CLIENTS\n", __func__ );
player_info_t *player = &cl.players[slot]; player = &cl.players[slot];
if( active ) if( active )
{ {
@@ -1380,7 +1407,7 @@ static void CL_UpdateUserinfo( sizebuf_t *msg, connprotocol_t proto )
player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" )); player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" )); player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" )); player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" ));
MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ), sizeof( player->hashedcdkey )); MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey ));
if( proto == PROTO_GOLDSRC && ( COM_StringEmpty( player->userinfo ) || COM_StringEmpty( player->name ))) if( proto == PROTO_GOLDSRC && ( COM_StringEmpty( player->userinfo ) || COM_StringEmpty( player->name )))
active = false; active = false;
@@ -1411,7 +1438,9 @@ downloading and precache resource in-game
*/ */
void CL_ParseResource( sizebuf_t *msg ) void CL_ParseResource( sizebuf_t *msg )
{ {
resource_t *pResource = Mem_Calloc( cls.mempool, sizeof( resource_t )); resource_t *pResource;
pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = MSG_ReadUBitLong( msg, 4 ); pResource->type = MSG_ReadUBitLong( msg, 4 );
Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName )); Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));
@@ -1420,36 +1449,36 @@ void CL_ParseResource( sizebuf_t *msg )
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING; pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
if( FBitSet( pResource->ucFlags, RES_CUSTOM )) if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ), sizeof( pResource->rgucMD5_hash )); MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
if( MSG_ReadOneBit( msg )) if( MSG_ReadOneBit( msg ))
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), sizeof( pResource->rguc_reserved )); MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
if( pResource->type == t_sound && pResource->nIndex >= MAX_SOUNDS ) if( pResource->type == t_sound && pResource->nIndex > MAX_SOUNDS )
{ {
Mem_Free( pResource ); Mem_Free( pResource );
Host_Error( "bad sound index\n" ); Host_Error( "bad sound index\n" );
} }
if( pResource->type == t_model && pResource->nIndex >= MAX_MODELS ) if( pResource->type == t_model && pResource->nIndex > MAX_MODELS )
{ {
Mem_Free( pResource ); Mem_Free( pResource );
Host_Error( "bad model index\n" ); Host_Error( "bad model index\n" );
} }
if( pResource->type == t_eventscript && pResource->nIndex >= MAX_EVENTS ) if( pResource->type == t_eventscript && pResource->nIndex > MAX_EVENTS )
{ {
Mem_Free( pResource ); Mem_Free( pResource );
Host_Error( "bad event index\n" ); Host_Error( "bad event index\n" );
} }
if( pResource->type == t_generic && pResource->nIndex >= MAX_CUSTOM ) if( pResource->type == t_generic && pResource->nIndex > MAX_CUSTOM )
{ {
Mem_Free( pResource ); Mem_Free( pResource );
Host_Error( "bad file index\n" ); Host_Error( "bad file index\n" );
} }
if( pResource->type == t_decal && pResource->nIndex >= MAX_DECALS ) if( pResource->type == t_decal && pResource->nIndex > MAX_DECALS )
{ {
Mem_Free( pResource ); Mem_Free( pResource );
Host_Error( "bad decal index\n" ); Host_Error( "bad decal index\n" );
@@ -1471,10 +1500,13 @@ void CL_UpdateUserPings( sizebuf_t *msg )
// because pings message always ends by null bit // because pings message always ends by null bit
while( 1 ) while( 1 )
{ {
int slot;
player_info_t *player;
if( !MSG_ReadOneBit( msg )) if( !MSG_ReadOneBit( msg ))
break; // end of message break; // end of message
int slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS );
if( unlikely( slot >= MAX_CLIENTS )) if( unlikely( slot >= MAX_CLIENTS ))
{ {
@@ -1482,7 +1514,7 @@ void CL_UpdateUserPings( sizebuf_t *msg )
return; return;
} }
player_info_t *player = &cl.players[slot]; player = &cl.players[slot];
player->ping = MSG_ReadUBitLong( msg, 12 ); player->ping = MSG_ReadUBitLong( msg, 12 );
player->packet_loss = MSG_ReadUBitLong( msg, 7 ); player->packet_loss = MSG_ReadUBitLong( msg, 7 );
} }
@@ -1507,14 +1539,18 @@ static const char *CL_CheckTypeToString( int check_type )
static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto ) static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
{ {
byte md5[16] = { 0 }; qboolean user_changed_diskfile;
vec3_t mins, maxs;
string filename;
byte md5[16] = { 0 };
int pos;
if( !cl.need_force_consistency_response ) if( !cl.need_force_consistency_response )
return; return;
cl.need_force_consistency_response = false; cl.need_force_consistency_response = false;
MSG_BeginClientCmd( msg, clc_fileconsistency ); MSG_BeginClientCmd( msg, clc_fileconsistency );
int pos = MSG_GetNumBytesWritten( msg ); pos = MSG_GetNumBytesWritten( msg );
if( proto == PROTO_GOLDSRC ) if( proto == PROTO_GOLDSRC )
{ {
MSG_WriteShort( msg, 0 ); MSG_WriteShort( msg, 0 );
@@ -1526,11 +1562,9 @@ static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
for( int i = 0; i < cl.num_consistency; i++ ) for( int i = 0; i < cl.num_consistency; i++ )
{ {
qboolean have_file = true; qboolean have_file = true;
qboolean user_changed_diskfile = false;
vec3_t mins, maxs;
string filename;
consistency_t *pc = &cl.consistency_list[i]; consistency_t *pc = &cl.consistency_list[i];
user_changed_diskfile = false;
MSG_WriteOneBit( msg, 1 ); MSG_WriteOneBit( msg, 1 );
MSG_WriteUBitLong( msg, pc->orig_index, MAX_MODEL_BITS ); MSG_WriteUBitLong( msg, pc->orig_index, MAX_MODEL_BITS );
@@ -1609,9 +1643,10 @@ static void CL_SendConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
if( proto == PROTO_GOLDSRC ) if( proto == PROTO_GOLDSRC )
{ {
int len;
MSG_EndBitWriting( msg ); MSG_EndBitWriting( msg );
int len = MSG_GetNumBytesWritten( msg ) - pos - 2; len = MSG_GetNumBytesWritten( msg ) - pos - 2;
*(short *)&msg->pData[pos] = len; *(short *)&msg->pData[pos] = len;
COM_Munge( &msg->pData[pos + 2], len, cl.servercount ); COM_Munge( &msg->pData[pos + 2], len, cl.servercount );
@@ -1627,10 +1662,11 @@ static void CL_StartDark( void )
{ {
if( v_dark.value ) if( v_dark.value )
{ {
screenfade_t *sf = &clgame.fade; screenfade_t *sf = &clgame.fade;
float fadetime = 5.0f; float fadetime = 5.0f;
client_textmessage_t *title;
client_textmessage_t *title = CL_TextMessageGet( "GAMETITLE" ); title = CL_TextMessageGet( "GAMETITLE" );
if( Host_IsQuakeCompatible( )) if( Host_IsQuakeCompatible( ))
fadetime = 1.0f; fadetime = 1.0f;
@@ -1702,8 +1738,6 @@ void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )
// tell rendering system we have a new set of models. // tell rendering system we have a new set of models.
ref.dllFuncs.R_NewMap (); ref.dllFuncs.R_NewMap ();
Mod_LoadDetailTextures( cl.worldmodel );
// check if this map must start from dark screen // check if this map must start from dark screen
CL_StartDark (); CL_StartDark ();
@@ -1742,12 +1776,20 @@ void CL_RegisterResources( sizebuf_t *msg, connprotocol_t proto )
static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto ) static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
{ {
byte nullbuffer[32]; int lastcheck;
int delta;
int i;
int isdelta;
resource_t *pResource;
resource_t *skip_crc_change;
int skip;
consistency_t *pc;
byte nullbuffer[32];
memset( nullbuffer, 0, 32 ); memset( nullbuffer, 0, 32 );
cl.need_force_consistency_response = MSG_ReadOneBit( msg ); cl.need_force_consistency_response = MSG_ReadOneBit( msg );
resource_t *pResource = cl.resourcesneeded.pNext; pResource = cl.resourcesneeded.pNext;
if( !cl.need_force_consistency_response ) if( !cl.need_force_consistency_response )
return; return;
@@ -1761,20 +1803,19 @@ static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
if( cl_trace_consistency.value ) if( cl_trace_consistency.value )
Con_Printf( "Server wants consistency of the following resources:\n" ); Con_Printf( "Server wants consistency of the following resources:\n" );
resource_t *skip_crc_change = NULL; skip_crc_change = NULL;
int lastcheck = 0; lastcheck = 0;
while( MSG_ReadOneBit( msg )) while( MSG_ReadOneBit( msg ))
{ {
int isdelta = MSG_ReadOneBit( msg ); isdelta = MSG_ReadOneBit( msg );
int delta;
if( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck; if( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck;
else delta = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_MODEL_BITS : MAX_MODEL_BITS ); else delta = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_MODEL_BITS : MAX_MODEL_BITS );
int skip = delta - lastcheck; skip = delta - lastcheck;
for( int i = 0; i < skip; i++ ) for( i = 0; i < skip; i++ )
{ {
if( pResource != skip_crc_change && Q_strstr( pResource->szFileName, "models/" )) if( pResource != skip_crc_change && Q_strstr( pResource->szFileName, "models/" ))
Mod_NeedCRC( pResource->szFileName, false ); Mod_NeedCRC( pResource->szFileName, false );
@@ -1790,7 +1831,7 @@ static void CL_ParseConsistencyInfo( sizebuf_t *msg, connprotocol_t proto )
if( cl.num_consistency >= MAX_MODELS ) if( cl.num_consistency >= MAX_MODELS )
Host_Error( "%s: MAX_MODELS limit exceeded (%d)\n", __func__, MAX_MODELS ); Host_Error( "%s: MAX_MODELS limit exceeded (%d)\n", __func__, MAX_MODELS );
consistency_t *pc = &cl.consistency_list[cl.num_consistency]; pc = &cl.consistency_list[cl.num_consistency];
cl.num_consistency++; cl.num_consistency++;
memset( pc, 0, sizeof( *pc )); memset( pc, 0, sizeof( *pc ));
@@ -1822,11 +1863,14 @@ CL_ParseResourceList
*/ */
void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto ) void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto )
{ {
int total = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_RESOURCE_BITS : MAX_RESOURCE_BITS ); resource_t *pResource;
int i, total;
for( int i = 0; i < total; i++ ) total = MSG_ReadUBitLong( msg, proto == PROTO_GOLDSRC ? MAX_GOLDSRC_RESOURCE_BITS : MAX_RESOURCE_BITS );
for( i = 0; i < total; i++ )
{ {
resource_t *pResource = Mem_Calloc( cls.mempool, sizeof( resource_t )); pResource = Mem_Calloc( cls.mempool, sizeof( resource_t ));
pResource->type = MSG_ReadUBitLong( msg, 4 ); pResource->type = MSG_ReadUBitLong( msg, 4 );
Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName )); Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName ));
@@ -1835,10 +1879,10 @@ void CL_ParseResourceList( sizebuf_t *msg, connprotocol_t proto )
pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING; pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING;
if( FBitSet( pResource->ucFlags, RES_CUSTOM )) if( FBitSet( pResource->ucFlags, RES_CUSTOM ))
MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ), sizeof( pResource->rgucMD5_hash )); MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash ));
if( MSG_ReadOneBit( msg )) if( MSG_ReadOneBit( msg ))
MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ), sizeof( pResource->rguc_reserved )); MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved ));
CL_AddToResourceList( pResource, &cl.resourcesneeded ); CL_AddToResourceList( pResource, &cl.resourcesneeded );
} }
@@ -1907,7 +1951,7 @@ static void CL_ParseVoiceData( sizebuf_t *msg, connprotocol_t proto )
if ( !size ) if ( !size )
return; return;
MSG_ReadBytes( msg, received, sizeof( received ), size ); MSG_ReadBytes( msg, received, size );
Voice_AddIncomingData( idx, received, size, frames ); Voice_AddIncomingData( idx, received, size, frames );
} }
@@ -1987,7 +2031,7 @@ static void CL_ParseDirector( sizebuf_t *msg )
byte pbuf[256]; byte pbuf[256];
// parse user message into buffer // parse user message into buffer
MSG_ReadBytes( msg, pbuf, sizeof( pbuf ), iSize ); MSG_ReadBytes( msg, pbuf, iSize );
clgame.dllFuncs.pfnDirectorMessage( iSize, pbuf ); clgame.dllFuncs.pfnDirectorMessage( iSize, pbuf );
} }
@@ -2023,12 +2067,14 @@ Set screen fade
*/ */
static void CL_ParseScreenFade( sizebuf_t *msg ) static void CL_ParseScreenFade( sizebuf_t *msg )
{ {
screenfade_t *sf = &clgame.fade; float duration, holdTime;
screenfade_t *sf = &clgame.fade;
float flScale;
float duration = (float)MSG_ReadWord( msg ); duration = (float)MSG_ReadWord( msg );
float holdTime = (float)MSG_ReadWord( msg ); holdTime = (float)MSG_ReadWord( msg );
sf->fadeFlags = MSG_ReadShort( msg ); sf->fadeFlags = MSG_ReadShort( msg );
float flScale = FBitSet( sf->fadeFlags, FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f); flScale = FBitSet( sf->fadeFlags, FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f);
sf->fader = MSG_ReadByte( msg ); sf->fader = MSG_ReadByte( msg );
sf->fadeg = MSG_ReadByte( msg ); sf->fadeg = MSG_ReadByte( msg );
@@ -2137,6 +2183,9 @@ Exec map/class specific configs
*/ */
static void CL_ParseExec( sizebuf_t *msg ) static void CL_ParseExec( sizebuf_t *msg )
{ {
qboolean is_class;
int class_idx;
string mapname;
const char *class_cfgs[] = { const char *class_cfgs[] = {
"", "",
"exec scout.cfg\n", "exec scout.cfg\n",
@@ -2152,18 +2201,17 @@ static void CL_ParseExec( sizebuf_t *msg )
"exec civilian.cfg\n" "exec civilian.cfg\n"
}; };
qboolean is_class = MSG_ReadByte( msg ); is_class = MSG_ReadByte( msg );
if( is_class ) if( is_class )
{ {
int class_idx = MSG_ReadByte( msg ); class_idx = MSG_ReadByte( msg );
if( class_idx >= 0 && class_idx <= 11 && !Q_stricmp( GI->gamefolder, "tfc" ) ) if( class_idx >= 0 && class_idx <= 11 && !Q_stricmp( GI->gamefolder, "tfc" ) )
Cbuf_AddText( class_cfgs[class_idx] ); Cbuf_AddText( class_cfgs[class_idx] );
} }
else if( !Q_stricmp( GI->gamefolder, "tfc" ) ) else if( !Q_stricmp( GI->gamefolder, "tfc" ) )
{ {
string mapname;
Cbuf_AddText( "exec mapdefault.cfg\n" ); Cbuf_AddText( "exec mapdefault.cfg\n" );
COM_FileBase( clgame.mapname, mapname, sizeof( mapname )); COM_FileBase( clgame.mapname, mapname, sizeof( mapname ));
@@ -2271,7 +2319,7 @@ void CL_ParseUserMessage( sizebuf_t *msg, int svc_num, connprotocol_t proto )
} }
// parse user message into buffer // parse user message into buffer
MSG_ReadBytes( msg, pbuf, sizeof( pbuf ), iSize ); MSG_ReadBytes( msg, pbuf, iSize );
if( cl_trace_messages.value ) if( cl_trace_messages.value )
{ {
@@ -2317,15 +2365,17 @@ show display finale or cutscene message
*/ */
static void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ) static void CL_ParseFinaleCutscene( sizebuf_t *msg, int level )
{ {
static int msgindex = 0; static int msgindex = 0;
client_textmessage_t *text;
int channel;
cl.intermission = level; cl.intermission = level;
int channel = msgindex; channel = msgindex;
msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1); msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);
// grab message channel // grab message channel
client_textmessage_t *text = &cl_textmessage[channel]; text = &cl_textmessage[channel];
// NOTE: svc_finale and svc_cutscene has a // NOTE: svc_finale and svc_cutscene has a
// predefined settings like Quake-style // predefined settings like Quake-style
@@ -2537,9 +2587,9 @@ dispatch messages
*/ */
void CL_ParseServerMessage( sizebuf_t *msg ) void CL_ParseServerMessage( sizebuf_t *msg )
{ {
size_t bufStart, playerbytes; size_t bufStart, playerbytes;
int cmd; int cmd;
int old_background; int old_background;
// parse the message // parse the message
while( 1 ) while( 1 )

View File

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

View File

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

View File

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

View File

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

View File

@@ -172,8 +172,8 @@ intptr_t CL_RenderGetParm( const int parm, const int arg, const qboolean checkRe
return cl.local.waterlevel; return cl.local.waterlevel;
case PARM_LOCAL_HEALTH: case PARM_LOCAL_HEALTH:
return cl.local.health; return cl.local.health;
case PARM_SINGLEPLAYER_GAME: case PARM_LOCAL_GAME:
return Host_IsSinglePlayerGame(); return Host_IsLocalGame();
case PARM_NUMENTITIES: case PARM_NUMENTITIES:
return pfnNumberOfEntities(); return pfnNumberOfEntities();
case PARM_GET_CLIENT_PTR: case PARM_GET_CLIENT_PTR:
@@ -214,11 +214,6 @@ 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 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 = static render_api_t gRenderAPI =
{ {
pfnRenderGetParm, // GL_RenderGetParm, pfnRenderGetParm, // GL_RenderGetParm,
@@ -248,7 +243,7 @@ static render_api_t gRenderAPI =
AVI_GetVideoInfo, AVI_GetVideoInfo,
AVI_GetVideoFrameNumber, AVI_GetVideoFrameNumber,
AVI_GetVideoFrame, AVI_GetVideoFrame,
pfnAVI_UploadRawFrame, // AVI_UploadRawFrame NULL, // R_UploadStretchRaw,
AVI_FreeVideo, AVI_FreeVideo,
AVI_IsActive, AVI_IsActive,
pfnAVI_StreamSound, pfnAVI_StreamSound,
@@ -288,6 +283,40 @@ static render_api_t gRenderAPI =
COM_SetRandomSeed, 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 R_InitRenderAPI
@@ -300,15 +329,8 @@ qboolean R_InitRenderAPI( void )
// make sure what render functions is cleared // make sure what render functions is cleared
memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs )); memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs ));
gRenderAPI.GetDetailScaleForTexture = ref.dllFuncs.R_GetDetailScaleForTexture; // fill missing functions from renderer
gRenderAPI.GL_FindTexture = ref.dllFuncs.GL_FindTexture; R_FillRenderAPIFromRef( &gRenderAPI, &ref.dllFuncs );
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 ) if( clgame.dllFuncs.pfnGetRenderInterface )
{ {

View File

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

View File

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

View File

@@ -33,7 +33,11 @@ assume pModel is valid
mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
{ {
msprite_t *psprite; msprite_t *psprite;
mspritegroup_t *pspritegroup;
mspriteframe_t *pspriteframe = NULL; mspriteframe_t *pspriteframe = NULL;
float *pintervals, fullinterval;
int i, numframes;
float targettime;
if( !pModel ) if( !pModel )
return NULL; return NULL;
@@ -57,15 +61,14 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
} }
else if( psprite->frames[frame].type == SPR_GROUP ) else if( psprite->frames[frame].type == SPR_GROUP )
{ {
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
float *pintervals = pspritegroup->intervals; pintervals = pspritegroup->intervals;
int numframes = pspritegroup->numframes; numframes = pspritegroup->numframes;
float fullinterval = pintervals[numframes - 1]; fullinterval = pintervals[numframes - 1];
// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
// are positive, so we don't have to worry about division by zero // are positive, so we don't have to worry about division by zero
float targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval; targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval;
int i;
for( i = 0; i < ( numframes - 1 ); i++ ) for( i = 0; i < ( numframes - 1 ); i++ )
{ {
@@ -79,7 +82,7 @@ mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw )
int angleframe = (int)( Q_rint(( refState.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7; int angleframe = (int)( Q_rint(( refState.viewangles[1] - yaw + 45.0f ) / 360 * 8 ) - 4 ) & 7;
// e.g. doom-style sprite monsters // e.g. doom-style sprite monsters
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
pspriteframe = pspritegroup->frames[angleframe]; pspriteframe = pspritegroup->frames[angleframe];
} }
@@ -200,13 +203,16 @@ static const byte *Mod_SpriteLoadFrame( model_t *mod, const void *pin, mspritefr
static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum ) static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspriteframe_t **ppframe, int framenum )
{ {
const dspritegroup_t *pingroup = (const dspritegroup_t *)pin; const dspritegroup_t *pingroup;
mspritegroup_t *pspritegroup; mspritegroup_t *pspritegroup;
const dspriteinterval_t *pin_intervals; const dspriteinterval_t *pin_intervals;
float *poutintervals; float *poutintervals;
int groupsize, numframes = pingroup->numframes; int i, groupsize, numframes;
const void *ptemp; const void *ptemp;
pingroup = (const dspritegroup_t *)pin;
numframes = pingroup->numframes;
groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] ); groupsize = sizeof( mspritegroup_t ) + ( numframes - 1 ) * sizeof( pspritegroup->frames[0] );
pspritegroup = Mem_Calloc( mod->mempool, groupsize ); pspritegroup = Mem_Calloc( mod->mempool, groupsize );
pspritegroup->numframes = numframes; pspritegroup->numframes = numframes;
@@ -216,7 +222,7 @@ static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspritefr
poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float )); poutintervals = Mem_Calloc( mod->mempool, numframes * sizeof( float ));
pspritegroup->intervals = poutintervals; pspritegroup->intervals = poutintervals;
for( int i = 0; i < numframes; i++ ) for( i = 0; i < numframes; i++ )
{ {
*poutintervals = pin_intervals->interval; *poutintervals = pin_intervals->interval;
if( *poutintervals <= 0.0f ) if( *poutintervals <= 0.0f )
@@ -226,7 +232,7 @@ static const byte *Mod_SpriteLoadGroup( model_t *mod, const void *pin, mspritefr
} }
ptemp = (const void *)pin_intervals; ptemp = (const void *)pin_intervals;
for( int i = 0; i < numframes; i++ ) for( i = 0; i < numframes; i++ )
{ {
ptemp = Mod_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i ); ptemp = Mod_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i );
} }
@@ -324,12 +330,13 @@ void Mod_SpriteLoadTextures( model_t *mod, const void *buffer )
void Mod_SpriteUnloadTextures( void *data ) void Mod_SpriteUnloadTextures( void *data )
{ {
msprite_t *psprite = data; msprite_t *psprite = data;
int i;
if( !data ) if( !data )
return; return;
// release all textures // release all textures
for( int i = 0; i < psprite->numframes; i++ ) for( i = 0; i < psprite->numframes; i++ )
{ {
if( !psprite->frames[i].frameptr ) if( !psprite->frames[i].frameptr )
continue; continue;
@@ -341,8 +348,9 @@ void Mod_SpriteUnloadTextures( void *data )
else else
{ {
mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; mspritegroup_t *pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr;
int j;
for( int j = 0; j < pspritegroup->numframes; j++ ) for( j = 0; j < pspritegroup->numframes; j++ )
{ {
if( pspritegroup->frames[j] ) if( pspritegroup->frames[j] )
ref.dllFuncs.GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum ); ref.dllFuncs.GL_FreeTexture( pspritegroup->frames[j]->gl_texturenum );

View File

@@ -16,8 +16,6 @@ GNU General Public License for more details.
#include <inttypes.h> #include <inttypes.h>
#include "common.h" #include "common.h"
#include "client.h" #include "client.h"
#include "net_ws.h"
#include "net_ws_private.h"
// What is a broker? // What is a broker?
// From Wikipedia, the free encyclopedia: // From Wikipedia, the free encyclopedia:
@@ -33,53 +31,16 @@ GNU General Public License for more details.
// network, preferrably) on a machine that has Steam client installed, the // network, preferrably) on a machine that has Steam client installed, the
// engine can communicate with it, acquiring needed information to log-in into // engine can communicate with it, acquiring needed information to log-in into
// Steam protected multiplayer servers. // Steam protected multiplayer servers.
static CVAR_DEFINE_AUTO( cl_steam_broker_addr, "127.0.0.1:27420", FCVAR_ARCHIVE, "address of steam broker instance" );
// Protocol constants static struct
#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; netadr_t adr;
int socket;
sbrk_state_t state;
int challenge; int challenge;
netadr_t serveradr; netadr_t serveradr;
double connection_timeout; qboolean announced;
double idle_cycle_timeout; qboolean addr_initialized;
uint8_t rx_buffer[SBRK_MAX_FRAME_SIZE + 64]; } broker;
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 ) static qboolean SteamBroker_UpdateBrokerAddress( void )
{ {
@@ -91,385 +52,28 @@ static qboolean SteamBroker_UpdateBrokerAddress( void )
return true; 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 ) qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge )
{ {
// only ipv4 supported // only ipv4 supported
if( NET_NetadrType( &serveradr ) != NA_IP ) if( NET_NetadrType( &serveradr ) != NA_IP )
return false; return false;
if( broker.state != SBRK_STATE_CONNECTED )
{
Con_Printf( S_WARN "%s: broker not connected\n", __func__ );
return false;
}
broker.challenge = challenge; broker.challenge = challenge;
broker.serveradr = serveradr; broker.serveradr = serveradr;
// sb_connect <ip:port> <server_steamid> <secure> <challenge> // sb_connect <ip:port> <server's steam id> <secure> <challenge>
// the message calls
char buf[512]; char buf[512];
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 ); 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 );
if( !SteamBroker_SendFrame( buf, len )) NET_SendPacket( NS_CLIENT, len, buf, broker.adr );
return false;
return true; return true;
} }
void SteamBroker_TerminateGameConnection( void ) void SteamBroker_TerminateGameConnection( void )
{ {
if( broker.state != SBRK_STATE_CONNECTED ) if( NET_NetadrType( &cls.serveradr ) != NA_IP )
return; return;
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 ) if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
@@ -479,52 +83,109 @@ void SteamBroker_TerminateGameConnection( void )
char buf[512]; char buf[512];
int len = Q_snprintf( buf, sizeof( buf ), "sb_disconnect %s %d", NET_AdrToString( cls.serveradr ), broker.challenge ); int len = Q_snprintf( buf, sizeof( buf ), "sb_disconnect %s %d", NET_AdrToString( cls.serveradr ), broker.challenge );
SteamBroker_SendFrame( buf, len ); 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 );
} }
void SteamBroker_Frame( void ) 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 ) if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return; return;
// update state machine qboolean restart = FBitSet( cl_steam_broker_addr.flags|cl_ticket_generator.flags, FCVAR_CHANGED );
switch( broker.state )
if( !broker.addr_initialized )
{ {
case SBRK_STATE_IDLE: if( !SteamBroker_UpdateBrokerAddress( ))
SteamBroker_UpdateIdle( ); {
break; Con_Printf( "%s: failed to resolve broker address \"%s\"\n", __func__, cl_steam_broker_addr.string );
case SBRK_STATE_CONNECTING: return;
SteamBroker_UpdateConnecting( ); }
break; else
case SBRK_STATE_CONNECTED: {
SteamBroker_UpdateConnected( ); broker.addr_initialized = true;
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 ) void SteamBroker_Init( void )
{ {
broker.state = SBRK_STATE_IDLE; broker.announced = false;
broker.socket = INVALID_SOCKET; broker.addr_initialized = false;
broker.rx_buffer_pos = 0;
broker.tx_buffer_pos = 0;
Cvar_RegisterVariable( &cl_steam_broker_addr ); Cvar_RegisterVariable( &cl_steam_broker_addr );
NET_NetadrSetType( &broker.adr, NA_UNDEFINED ); NET_NetadrSetType( &broker.adr, NA_UNDEFINED );
} }
@@ -534,6 +195,5 @@ void SteamBroker_Shutdown( void )
if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 ) if( Q_stricmp( cl_ticket_generator.string, "steam" ) != 0 )
return; return;
SteamBroker_AnnounceGameShutdown( ); SteamBroker_AnnounceGameShutdown( broker.adr );
SteamBroker_SetState( SBRK_STATE_GAMESHUTDOWN );
} }

View File

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

View File

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

View File

@@ -187,15 +187,18 @@ merge refdef with overview settings
static void V_RefApplyOverview( ref_viewpass_t *rvp ) static void V_RefApplyOverview( ref_viewpass_t *rvp )
{ {
ref_overview_t *ov = &clgame.overView; ref_overview_t *ov = &clgame.overView;
float aspect;
float size_x, size_y;
vec2_t mins, maxs;
if( !CL_IsDevOverviewMode( )) if( !CL_IsDevOverviewMode( ))
return; return;
// NOTE: Xash3D may use 16:9 or 16:10 aspects // NOTE: Xash3D may use 16:9 or 16:10 aspects
float aspect = (float)refState.width / (float)refState.height; aspect = (float)refState.width / (float)refState.height;
float size_x = fabs( 8192.0f / ov->flZoom ); size_x = fabs( 8192.0f / ov->flZoom );
float size_y = fabs( 8192.0f / (ov->flZoom * aspect )); size_y = fabs( 8192.0f / (ov->flZoom * aspect ));
// compute rectangle // compute rectangle
ov->xLeft = -(size_x / 2); ov->xLeft = -(size_x / 2);
@@ -211,8 +214,8 @@ static void V_RefApplyOverview( ref_viewpass_t *rvp )
VectorCopy( ov->origin, rvp->vieworigin ); VectorCopy( ov->origin, rvp->vieworigin );
rvp->vieworigin[2] = ov->zFar + ov->zNear; rvp->vieworigin[2] = ov->zFar + ov->zNear;
vec2_t mins = Vec2( rvp->vieworigin ); Vector2Copy( rvp->vieworigin, mins );
vec2_t maxs = Vec2( rvp->vieworigin ); Vector2Copy( rvp->vieworigin, maxs );
mins[!ov->rotated] += ov->xLeft; mins[!ov->rotated] += ov->xLeft;
maxs[!ov->rotated] += ov->xRight; maxs[!ov->rotated] += ov->xRight;
@@ -235,11 +238,13 @@ V_CalcFov
*/ */
static float V_CalcFov( float *fov_x, float width, float height ) 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 ) if( *fov_x < 1.0f || *fov_x > 179.0f )
*fov_x = 90.0f; // default value *fov_x = 90.0f; // default value
float x = width / tan( DEG2RAD( *fov_x ) * 0.5f ); x = width / tan( DEG2RAD( *fov_x ) * 0.5f );
float half_fov_y = atan( height / x ); half_fov_y = atan( height / x );
return RAD2DEG( half_fov_y ) * 2; return RAD2DEG( half_fov_y ) * 2;
} }
@@ -355,29 +360,6 @@ 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 V_RenderView
@@ -407,7 +389,6 @@ void V_RenderView( void )
clgame.dllFuncs.pfnCalcRefdef( &rp ); clgame.dllFuncs.pfnCalcRefdef( &rp );
V_GetRefParams( &rp, &rvp ); V_GetRefParams( &rp, &rvp );
V_RefApplyOverview( &rvp ); V_RefApplyOverview( &rvp );
V_ApplyRefUnderwater( &rvp );
if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW )) if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW ))
{ {
@@ -495,12 +476,13 @@ static void R_ShowTree( void )
{ {
float x = (float)((refState.width - (int)POINT_SIZE) >> 1); float x = (float)((refState.width - (int)POINT_SIZE) >> 1);
float y = NODE_INTERVAL_Y(1.0f); float y = NODE_INTERVAL_Y(1.0f);
mleaf_t *viewleaf;
if( !cl.worldmodel || !r_showtree.value ) if( !cl.worldmodel || !r_showtree.value )
return; return;
world.recursion_level = 0; world.recursion_level = 0;
mleaf_t *viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel ); viewleaf = Mod_PointInLeaf( refState.vieworg, cl.worldmodel->nodes, cl.worldmodel );
ref.dllFuncs.TriRenderMode( kRenderTransTexture ); ref.dllFuncs.TriRenderMode( kRenderTransTexture );
@@ -554,7 +536,6 @@ void V_PostRender( void )
SCR_DrawNetGraph(); SCR_DrawNetGraph();
SCR_DrawUserCmd(); SCR_DrawUserCmd();
Joy_DrawDebug(); Joy_DrawDebug();
IN_GyroDrawDebug();
SV_DrawOrthoTriangles(); SV_DrawOrthoTriangles();
CL_DrawDemoRecording(); CL_DrawDemoRecording();
CL_DrawHUD( CL_CHANGELEVEL ); CL_DrawHUD( CL_CHANGELEVEL );

View File

@@ -445,7 +445,6 @@ typedef struct
double timeout; double timeout;
double timesend; // time when request was sended double timesend; // time when request was sended
int flags; // FNETAPI_MULTIPLE_RESPONSE etc int flags; // FNETAPI_MULTIPLE_RESPONSE etc
int challenge; // GoldSrc query challenge, -1 until received
} net_request_t; } net_request_t;
// new versions of client dlls have a single export with all callbacks // new versions of client dlls have a single export with all callbacks
@@ -580,7 +579,6 @@ typedef struct
sizebuf_t datagram; // unreliable stuff. gets sent in CL_Move about cl_cmdrate times per second. sizebuf_t datagram; // unreliable stuff. gets sent in CL_Move about cl_cmdrate times per second.
byte datagram_buf[MAX_DATAGRAM]; byte datagram_buf[MAX_DATAGRAM];
uint64_t netchan_pending_cookie; // random NET_EXT_NETCHAN_COOKIE
netchan_t netchan; netchan_t netchan;
float packet_loss; float packet_loss;
@@ -646,9 +644,10 @@ typedef struct
qboolean internetservers_pending; // if true, clean master server pings qboolean internetservers_pending; // if true, clean master server pings
qboolean internetservers_nat; qboolean internetservers_nat;
string internetservers_customfilter; string internetservers_customfilter;
uint32_t internetservers_key; // compare key to validate master server reply
// multiprotocol support // multiprotocol support
connprotocol_t net_protocol; connprotocol_t legacymode;
int extensions; int extensions;
netadr_t serveradr; netadr_t serveradr;
@@ -762,7 +761,6 @@ void CL_AddToResourceList( resource_t *pResource, resource_t *pList );
void CL_RemoveFromResourceList( resource_t *pResource ); void CL_RemoveFromResourceList( resource_t *pResource );
void CL_MoveToOnHandList( resource_t *pResource ); void CL_MoveToOnHandList( resource_t *pResource );
void CL_ClearResourceLists( void ); void CL_ClearResourceLists( void );
void CL_SendResourceList( const resource_t *list, int count );
// //
// cl_debug.c // cl_debug.c
@@ -793,8 +791,6 @@ void CL_SignonReply( connprotocol_t proto );
void CL_ClearState( void ); void CL_ClearState( void );
void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats ); void CL_SetCheatState( qboolean multiplayer, qboolean allow_cheats );
void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen ); void CL_SendGoldSrcConnectPacket( netadr_t adr, int challenge, const void *ticket, size_t ticketlen );
void CL_NotifyServerListResponse( void );
qboolean CL_NetRequestSend( net_request_t *nr );
// //
// cl_demo.c // cl_demo.c
@@ -878,6 +874,7 @@ 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_EnableScissor( scissor_state_t *scissor, int x, int y, int width, int height );
void CL_DisableScissor( scissor_state_t *scissor ); 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 ); 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 ) static inline cl_entity_t *CL_EDICT_NUM( int index )
{ {
if( unlikely( !clgame.entities )) // not in game yet if( unlikely( !clgame.entities )) // not in game yet
@@ -1157,7 +1154,7 @@ void S_StopStreaming( void );
void S_BeginRegistration( void ); void S_BeginRegistration( void );
sound_t S_RegisterSound( const char *sample ); sound_t S_RegisterSound( const char *sample );
void S_EndRegistration( void ); 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, uint 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, int 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_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_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 ); void S_SoundFade( int fade_percent, int hold_time, int fade_out_seconds, int fade_in_seconds );
@@ -1211,14 +1208,14 @@ void CL_GetSecuredClientAPI( CL_EXPORT_FUNCS F );
void SteamBroker_Init( void ); void SteamBroker_Init( void );
void SteamBroker_Shutdown( void ); void SteamBroker_Shutdown( void );
void SteamBroker_Frame( void ); void SteamBroker_Frame( void );
qboolean SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge ); void SteamBroker_HandlePacket( netadr_t from, sizebuf_t *msg );
int SteamBroker_InitiateGameConnection( netadr_t serveradr, int challenge );
void SteamBroker_TerminateGameConnection( void ); void SteamBroker_TerminateGameConnection( void );
// //
// cl_video.c // cl_video.c
// //
void SCR_InitCinematic( void ); void SCR_InitCinematic( void );
int SCR_GetCinematicTexture( void );
void SCR_FreeCinematic( void ); void SCR_FreeCinematic( void );
qboolean SCR_PlayCinematic( const char *name ); qboolean SCR_PlayCinematic( const char *name );
qboolean SCR_DrawCinematic( void ); qboolean SCR_DrawCinematic( void );
@@ -1244,6 +1241,7 @@ void Key_EnumCmds_f( void );
void Key_SetKeyDest( int key_dest ); void Key_SetKeyDest( int key_dest );
void Key_EnableTextInput( qboolean enable, qboolean force ); void Key_EnableTextInput( qboolean enable, qboolean force );
int Key_ToUpper( int key ); int Key_ToUpper( int key );
void OSK_Draw( void );
qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboolean print_suggestions ); qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboolean print_suggestions );
// //

View File

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

View File

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

View File

@@ -15,13 +15,14 @@ GNU General Public License for more details.
#include "build.h" #include "build.h"
#include <inttypes.h> #include <inttypes.h>
#if XASH_LINUX
#include <fcntl.h> #include <fcntl.h>
#if !XASH_WIN32
#include <dirent.h> #include <dirent.h>
#else
#include <io.h>
#endif #endif
#include "common.h" #include "common.h"
#include "client.h" #include "client.h"
#include "platform/platform.h"
/* /*
========================================================== ==========================================================
@@ -79,7 +80,7 @@ static uint BloomFilter_Weight( bloomfilter_t value )
return weight; return weight;
} }
MAYBE_UNUSED static qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str ) static qboolean BloomFilter_ContainsString( bloomfilter_t filter, const char *str )
{ {
bloomfilter_t value = BloomFilter_ProcessStr( str ); bloomfilter_t value = BloomFilter_ProcessStr( str );
@@ -96,11 +97,14 @@ IDENTIFICATION
#define MAXBITS_GEN 30 #define MAXBITS_GEN 30
#define MAXBITS_CHECK MAXBITS_GEN + 6 #define MAXBITS_CHECK MAXBITS_GEN + 6
static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path );
static void ID_BloomFilter_f( void ) static void ID_BloomFilter_f( void )
{ {
bloomfilter_t value = 0; bloomfilter_t value = 0;
int i;
for( int i = 1; i < Cmd_Argc(); i++ ) for( i = 1; i < Cmd_Argc(); i++ )
value |= BloomFilter_ProcessStr( Cmd_Argv( i ) ); value |= BloomFilter_ProcessStr( Cmd_Argv( i ) );
Msg( "%d %016"PRIX64"\n", BloomFilter_Weight( value ), value ); Msg( "%d %016"PRIX64"\n", BloomFilter_Weight( value ), value );
@@ -115,6 +119,7 @@ static qboolean ID_VerifyHEX( const char *hex )
uint chars = 0; uint chars = 0;
char prev = 0; char prev = 0;
qboolean monotonic = true; // detect 11:22... qboolean monotonic = true; // detect 11:22...
int weight = 0;
while( *hex++ ) while( *hex++ )
{ {
@@ -137,7 +142,6 @@ static qboolean ID_VerifyHEX( const char *hex )
if( monotonic ) if( monotonic )
return false; return false;
int weight = 0;
while( chars ) while( chars )
{ {
if( chars & 1 ) if( chars & 1 )
@@ -166,7 +170,7 @@ static void ID_VerifyHEX_f( void )
static qboolean ID_ProcessCPUInfo( bloomfilter_t *value ) static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
{ {
int cpuinfofd = open( "/proc/cpuinfo", O_RDONLY ); int cpuinfofd = open( "/proc/cpuinfo", O_RDONLY );
char buffer[1024]; char buffer[1024], *pbuf, *pbuf2;
int ret; int ret;
if( cpuinfofd < 0 ) if( cpuinfofd < 0 )
@@ -185,12 +189,12 @@ static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
if( !ret ) if( !ret )
return false; return false;
char *pbuf = Q_stristr( buffer, "Serial" ); pbuf = Q_stristr( buffer, "Serial" );
if( !pbuf ) if( !pbuf )
return false; return false;
pbuf += 6; pbuf += 6;
char *pbuf2 = Q_strchrnul( pbuf, '\n' ); pbuf2 = Q_strchrnul( pbuf, '\n' );
*pbuf2 = 0; *pbuf2 = 0;
if( !ID_VerifyHEX( pbuf ) ) if( !ID_VerifyHEX( pbuf ) )
@@ -200,6 +204,86 @@ static qboolean ID_ProcessCPUInfo( bloomfilter_t *value )
return true; return true;
} }
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 );
// if NULL, it may be old kernel
if( pfile )
{
assignType = Q_atoi( (char*)pfile );
Mem_Free( pfile );
// check is MAC address is constant
if( assignType != 0 )
return false;
}
return true;
}
static int ID_ProcessNetDevices( bloomfilter_t *value )
{
const char *prefix = "/sys/class/net";
DIR *dir;
struct dirent *entry;
int count = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) && BloomFilter_Weight( *value ) < MAXBITS_GEN )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( !ID_ValidateNetDevice( entry->d_name ) )
continue;
count += ID_ProcessFile( value, va( "%s/%s/address", prefix, entry->d_name ) );
}
closedir( dir );
return count;
}
static int ID_CheckNetDevices( bloomfilter_t value )
{
const char *prefix = "/sys/class/net";
DIR *dir;
struct dirent *entry;
int count = 0;
bloomfilter_t filter = 0;
if( !( dir = opendir( prefix ) ) )
return 0;
while( ( entry = readdir( dir ) ) )
{
if( !Q_strcmp( entry->d_name, "." ) || !Q_strcmp( entry->d_name, ".." ) )
continue;
if( !ID_ValidateNetDevice( entry->d_name ) )
continue;
if( ID_ProcessFile( &filter, va( "%s/%s/address", prefix, entry->d_name ) ) )
count += ( value & filter ) == filter, filter = 0;
}
closedir( dir );
return count;
}
static void ID_TestCPUInfo_f( void ) static void ID_TestCPUInfo_f( void )
{ {
bloomfilter_t value = 0; bloomfilter_t value = 0;
@@ -210,6 +294,8 @@ static void ID_TestCPUInfo_f( void )
Msg( "Could not get serial\n" ); Msg( "Could not get serial\n" );
} }
#endif
static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path ) static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path )
{ {
int fd = open( path, O_RDONLY ); int fd = open( path, O_RDONLY );
@@ -239,6 +325,7 @@ static qboolean ID_ProcessFile( bloomfilter_t *value, const char *path )
return true; return true;
} }
#if !XASH_WIN32
static int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix ) static int ID_ProcessFiles( bloomfilter_t *value, const char *prefix, const char *postfix )
{ {
DIR *dir; DIR *dir;
@@ -281,94 +368,7 @@ static int ID_CheckFiles( bloomfilter_t value, const char *prefix, const char *p
closedir( dir ); closedir( dir );
return count; return count;
} }
#endif // XASH_LINUX #else
#if XASH_POSIX
#define MAX_NETDEVICES 16
static qboolean ID_IsReservedMAC( uint64_t mac )
{
byte first = ( mac >> 40 ) & 0xff;
// no address at all
if( mac == 0 )
return true;
// group and local bits of the first octet, see https://www.rfc-editor.org/rfc/rfc9542#section-2.1.1
if( FBitSet( first, 0x01 ) || FBitSet( first, 0x02 ))
return true;
// IANA OUI, see https://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml and https://www.rfc-editor.org/rfc/rfc9568#section-7.3
if(( mac >> 24 ) == 0x00005e )
return true;
return false;
}
static void ID_FormatMAC( char *out, size_t size, uint64_t mac )
{
// format exactly like /sys/class/net/ */address contents, so ids generated by older versions stay valid
Q_snprintf( out, size, "%02x:%02x:%02x:%02x:%02x:%02x\n",
(uint)(( mac >> 40 ) & 0xffu ),
(uint)(( mac >> 32 ) & 0xffu ),
(uint)(( mac >> 24 ) & 0xffu ),
(uint)(( mac >> 16 ) & 0xffu ),
(uint)(( mac >> 8 ) & 0xffu ),
(uint)( mac & 0xffu ));
}
static int ID_ProcessNetDevices( bloomfilter_t *value )
{
uint64_t macs[MAX_NETDEVICES];
int total = Posix_GetNetDeviceAddresses( macs, MAX_NETDEVICES );
int count = 0;
for( int i = 0; i < total && BloomFilter_Weight( *value ) < MAXBITS_GEN; i++ )
{
char buf[32];
if( ID_IsReservedMAC( macs[i] ))
continue;
ID_FormatMAC( buf, sizeof( buf ), macs[i] );
if( !ID_VerifyHEX( buf ))
continue;
*value |= BloomFilter_ProcessStr( buf );
count++;
}
return count;
}
static int ID_CheckNetDevices( bloomfilter_t value )
{
uint64_t macs[MAX_NETDEVICES];
int total = Posix_GetNetDeviceAddresses( macs, MAX_NETDEVICES );
int count = 0;
for( int i = 0; i < total; i++ )
{
char buf[32];
if( ID_IsReservedMAC( macs[i] ))
continue;
ID_FormatMAC( buf, sizeof( buf ), macs[i] );
if( !ID_VerifyHEX( buf ))
continue;
bloomfilter_t filter = BloomFilter_ProcessStr( buf );
count += ( value & filter ) == filter;
}
return count;
}
#endif // XASH_POSIX
#if XASH_WIN32
static int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData ) static int ID_GetKeyData( HKEY hRootKey, char *subKey, char *value, LPBYTE data, DWORD cbData )
{ {
HKEY hKey; HKEY hKey;
@@ -500,10 +500,12 @@ static int ID_CheckWMIC( bloomfilter_t value, const wchar_t *cmdline )
pbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header pbuf = COM_ParseFile( buffer, token, sizeof( token )); // Header
while(( pbuf = COM_ParseFile( pbuf, token, sizeof( token )))) while(( pbuf = COM_ParseFile( pbuf, token, sizeof( token ))))
{ {
bloomfilter_t filter;
if( !ID_VerifyHEX( token )) if( !ID_VerifyHEX( token ))
continue; continue;
bloomfilter_t filter = BloomFilter_ProcessStr( token ); filter = BloomFilter_ProcessStr( token );
count += ( filter & value ) == filter; count += ( filter & value ) == filter;
} }
@@ -542,17 +544,6 @@ static bloomfilter_t ID_GenerateRawId( void )
count += ID_ProcessCPUInfo( &value ); count += ID_ProcessCPUInfo( &value );
count += ID_ProcessFiles( &value, "/sys/block", "device/cid" ); count += ID_ProcessFiles( &value, "/sys/block", "device/cid" );
#endif
#if XASH_OSX
char buf[64];
if( Apple_GetSerialNumber( buf, sizeof( buf )))
{
value |= BloomFilter_ProcessStr( buf );
count++;
}
#endif
#if XASH_POSIX
count += ID_ProcessNetDevices( &value ); count += ID_ProcessNetDevices( &value );
#endif #endif
#if XASH_WIN32 #if XASH_WIN32
@@ -601,26 +592,12 @@ static uint ID_CheckRawId( bloomfilter_t filter )
} }
#endif // !XASH_ANDROID #endif // !XASH_ANDROID
count += ID_CheckNetDevices( filter );
count += ID_CheckFiles( filter, "/sys/block", "device/cid" ); count += ID_CheckFiles( filter, "/sys/block", "device/cid" );
if( ID_ProcessCPUInfo( &value ) ) if( ID_ProcessCPUInfo( &value ) )
count += (filter & value) == value; count += (filter & value) == value;
#endif #endif
#if XASH_OSX
char buf[64];
if( Apple_GetSerialNumber( buf, sizeof( buf )))
{
value = BloomFilter_ProcessStr( buf );
count += (filter & value) == value;
value = 0;
}
#endif
#if XASH_POSIX
count += ID_CheckNetDevices( filter );
#endif
#if XASH_WIN32 #if XASH_WIN32
count += ID_CheckWMIC( filter, L"wmic path win32_physicalmedia get SerialNumber" ); count += ID_CheckWMIC( filter, L"wmic path win32_physicalmedia get SerialNumber" );
count += ID_CheckWMIC( filter, L"wmic bios get serialnumber" ); count += ID_CheckWMIC( filter, L"wmic bios get serialnumber" );

View File

@@ -68,7 +68,6 @@ static CVAR_DEFINE_AUTO( joy_gyro_roll, "0.0", FCVAR_ARCHIVE | FCVAR_FILTERABLE,
static CVAR_DEFINE_AUTO( joy_gyro_pitch_deadzone, "0.5", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "gyroscope pitch axis deadzone (deg/s)" ); static CVAR_DEFINE_AUTO( joy_gyro_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_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_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" ); 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 // stores the latest instantaneous gyroscope rotation rates (in rad/s) from platform input
@@ -129,8 +128,9 @@ static void Joy_HatMotionEvent( int value )
{ JOY_HAT_LEFT, K_LEFTARROW }, { JOY_HAT_LEFT, K_LEFTARROW },
{ JOY_HAT_RIGHT, K_RIGHTARROW }, { JOY_HAT_RIGHT, K_RIGHTARROW },
}; };
int i;
for( int i = 0; i < ARRAYSIZE( keys ); i++ ) for( i = 0; i < ARRAYSIZE( keys ); i++ )
{ {
if( value & keys[i].mask ) if( value & keys[i].mask )
{ {
@@ -197,7 +197,7 @@ static int Joy_GetHatValueForAxis( const engineAxis_t engineAxis )
positive = JOY_HAT_RIGHT; positive = JOY_HAT_RIGHT;
break; break;
case JOY_AXIS_FWD: case JOY_AXIS_FWD:
threshold = joy_forward_key_threshold.value; threshold = joy_side_key_threshold.value;
negative = JOY_HAT_UP; negative = JOY_HAT_UP;
positive = JOY_HAT_DOWN; positive = JOY_HAT_DOWN;
break; break;
@@ -316,8 +316,9 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
if( FBitSet( joy_axis_binding.flags, FCVAR_CHANGED ) ) if( FBitSet( joy_axis_binding.flags, FCVAR_CHANGED ) )
{ {
const char *bind = joy_axis_binding.string; const char *bind = joy_axis_binding.string;
size_t i;
for( size_t i = 0; bind[i] && i < MAX_AXES; i++ ) for( i = 0; bind[i]; i++ )
{ {
switch( bind[i] ) switch( bind[i] )
{ {
@@ -339,7 +340,7 @@ void Joy_FinalizeMove( float *fw, float *side, float *dpitch, float *dyaw )
*dpitch += joy_pitch.value * (float)joyaxis[JOY_AXIS_PITCH].val/(float)SHRT_MAX * host.realframetime; *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; *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 ) if( joy_have_gyro.value && (int)joy_calibrated.value == JOY_CALIBRATED )
{ {
float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI ); float pitch_speed = joy_gyro_speed[0] * ( 180.0f / M_PI );
float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI ); float yaw_speed = joy_gyro_speed[1] * ( 180.0f / M_PI );
@@ -533,6 +534,8 @@ void Joy_DrawDebug( void )
} }
qboolean pressed = Key_IsDown( buttons[i] ); qboolean pressed = Key_IsDown( buttons[i] );
int btn_w = 0;
rgba_t color;
string name; string name;
Q_strncpy( name, Key_KeynumToString( buttons[i] ), sizeof( name )); Q_strncpy( name, Key_KeynumToString( buttons[i] ), sizeof( name ));
@@ -541,11 +544,8 @@ void Joy_DrawDebug( void )
if( p ) if( p )
*p = 0; *p = 0;
rgba_t color;
MakeRGBA( color, pressed ? 0 : 255, pressed ? 255 : 0, 0, 255 ); MakeRGBA( color, pressed ? 0 : 255, pressed ? 255 : 0, 0, 255 );
CL_DrawString( x, y, name, color, font, 0 ); CL_DrawString( x, y, name, color, font, 0 );
int btn_w = 0;
CL_DrawStringLen( font, name, &btn_w, NULL, 0 ); CL_DrawStringLen( font, name, &btn_w, NULL, 0 );
x += btn_w + 6; x += btn_w + 6;
} }
@@ -596,7 +596,6 @@ void Joy_Init( void )
Cvar_RegisterVariable( &joy_have_gyro ); Cvar_RegisterVariable( &joy_have_gyro );
Cvar_RegisterVariable( &joy_calibrated ); Cvar_RegisterVariable( &joy_calibrated );
Cvar_RegisterVariable( &joy_enable ); Cvar_RegisterVariable( &joy_enable );
Cvar_RegisterVariable( &joy_gyro_enable );
Cvar_RegisterVariable( &joy_gyro_pitch ); Cvar_RegisterVariable( &joy_gyro_pitch );
Cvar_RegisterVariable( &joy_gyro_yaw ); Cvar_RegisterVariable( &joy_gyro_yaw );

View File

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

View File

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

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