diff --git a/AMBuildScript b/AMBuildScript new file mode 100644 index 0000000..9855935 --- /dev/null +++ b/AMBuildScript @@ -0,0 +1,448 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os, sys + +# Simple extensions do not need to modify this file. + +class SDK(object): + def __init__(self, sdk, ext, aDef, name, platform, dir): + self.folder = 'hl2sdk-' + dir + self.envvar = sdk + self.ext = ext + self.code = aDef + self.define = name + self.platform = platform + self.name = dir + self.path = None # Actual path + +WinOnly = ['windows'] +WinLinux = ['windows', 'linux'] +WinLinuxMac = ['windows', 'linux', 'mac'] + +PossibleSDKs = { + 'episode1': SDK('HL2SDK', '1.ep1', '1', 'EPISODEONE', WinLinux, 'episode1'), + 'ep2': SDK('HL2SDKOB', '2.ep2', '3', 'ORANGEBOX', WinLinux, 'orangebox'), + 'css': SDK('HL2SDKCSS', '2.css', '6', 'CSS', WinLinuxMac, 'css'), + 'hl2dm': SDK('HL2SDKHL2DM', '2.hl2dm', '7', 'HL2DM', WinLinuxMac, 'hl2dm'), + 'dods': SDK('HL2SDKDODS', '2.dods', '8', 'DODS', WinLinuxMac, 'dods'), + 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'), + 'tf2': SDK('HL2SDKTF2', '2.tf2', '11', 'TF2', WinLinuxMac, 'tf2'), + 'l4d': SDK('HL2SDKL4D', '2.l4d', '12', 'LEFT4DEAD', WinLinuxMac, 'l4d'), + 'nucleardawn': SDK('HL2SDKND', '2.nd', '13', 'NUCLEARDAWN', WinLinuxMac, 'nucleardawn'), + 'l4d2': SDK('HL2SDKL4D2', '2.l4d2', '15', 'LEFT4DEAD2', WinLinuxMac, 'l4d2'), + 'darkm': SDK('HL2SDK-DARKM', '2.darkm', '2', 'DARKMESSIAH', WinOnly, 'darkm'), + 'swarm': SDK('HL2SDK-SWARM', '2.swarm', '16', 'ALIENSWARM', WinOnly, 'swarm'), + 'bgt': SDK('HL2SDK-BGT', '2.bgt', '4', 'BLOODYGOODTIME', WinOnly, 'bgt'), + 'eye': SDK('HL2SDK-EYE', '2.eye', '5', 'EYE', WinOnly, 'eye'), + 'csgo': SDK('HL2SDKCSGO', '2.csgo', '20', 'CSGO', WinLinuxMac, 'csgo'), + 'dota': SDK('HL2SDKDOTA', '2.dota', '21', 'DOTA', [], 'dota'), + 'portal2': SDK('HL2SDKPORTAL2', '2.portal2', '17', 'PORTAL2', [], 'portal2'), + 'blade': SDK('HL2SDKBLADE', '2.blade', '18', 'BLADE', WinLinux, 'blade'), + 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'), + 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'), + 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'), +} + +def ResolveEnvPath(env, folder): + if env in os.environ: + path = os.environ[env] + if os.path.isdir(path): + return path + return None + + head = os.getcwd() + oldhead = None + while head != None and head != oldhead: + path = os.path.join(head, folder) + if os.path.isdir(path): + return path + oldhead = head + head, tail = os.path.split(head) + + return None + +def Normalize(path): + return os.path.abspath(os.path.normpath(path)) + +class ExtensionConfig(object): + def __init__(self): + self.sdks = {} + self.binaries = [] + self.extensions = [] + self.generated_headers = None + self.mms_root = None + self.sm_root = None + + @property + def tag(self): + if builder.options.debug == '1': + return 'Debug' + return 'Release' + + def detectSDKs(self): + sdk_list = builder.options.sdks.split(',') + use_all = sdk_list[0] == 'all' + use_present = sdk_list[0] == 'present' + + for sdk_name in PossibleSDKs: + sdk = PossibleSDKs[sdk_name] + if builder.target_platform in sdk.platform: + if builder.options.hl2sdk_root: + sdk_path = os.path.join(builder.options.hl2sdk_root, sdk.folder) + else: + sdk_path = ResolveEnvPath(sdk.envvar, sdk.folder) + if sdk_path is None or not os.path.isdir(sdk_path): + if use_all or sdk_name in sdk_list: + raise Exception('Could not find a valid path for {0}'.format(sdk.envvar)) + continue + if use_all or use_present or sdk_name in sdk_list: + sdk.path = Normalize(sdk_path) + self.sdks[sdk_name] = sdk + + if len(self.sdks) < 1: + raise Exception('At least one SDK must be available.') + + if builder.options.sm_path: + self.sm_root = builder.options.sm_path + else: + self.sm_root = ResolveEnvPath('SOURCEMOD18', 'sourcemod-1.8') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central') + + if not self.sm_root or not os.path.isdir(self.sm_root): + raise Exception('Could not find a source copy of SourceMod') + self.sm_root = Normalize(self.sm_root) + + if builder.options.mms_path: + self.mms_root = builder.options.mms_path + else: + self.mms_root = ResolveEnvPath('MMSOURCE110', 'mmsource-1.10') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE', 'metamod-source') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central') + + if not self.mms_root or not os.path.isdir(self.mms_root): + raise Exception('Could not find a source copy of Metamod:Source') + self.mms_root = Normalize(self.mms_root) + + def configure(self): + cxx = builder.DetectCompilers() + + if cxx.like('gcc'): + self.configure_gcc(cxx) + elif cxx.vendor == 'msvc': + self.configure_msvc(cxx) + + # Optimizaiton + if builder.options.opt == '1': + cxx.defines += ['NDEBUG'] + + # Debugging + if builder.options.debug == '1': + cxx.defines += ['DEBUG', '_DEBUG'] + + # Platform-specifics + if builder.target_platform == 'linux': + self.configure_linux(cxx) + elif builder.target_platform == 'mac': + self.configure_mac(cxx) + elif builder.target_platform == 'windows': + self.configure_windows(cxx) + + # Finish up. + cxx.includes += [ + os.path.join(self.sm_root, 'public'), + ] + + def configure_gcc(self, cxx): + cxx.defines += [ + 'stricmp=strcasecmp', + '_stricmp=strcasecmp', + '_snprintf=snprintf', + '_vsnprintf=vsnprintf', + 'HAVE_STDINT_H', + 'GNUC', + ] + cxx.cflags += [ + '-pipe', + '-fno-strict-aliasing', + '-Wall', + '-Werror', + '-Wno-unused', + '-Wno-switch', + '-Wno-array-bounds', + '-msse', + '-m32', + '-fvisibility=hidden', + ] + cxx.cxxflags += [ + '-std=c++11', + '-fno-exceptions', + '-fno-threadsafe-statics', + '-Wno-non-virtual-dtor', + '-Wno-overloaded-virtual', + '-fvisibility-inlines-hidden', + ] + cxx.linkflags += ['-m32'] + + have_gcc = cxx.vendor == 'gcc' + have_clang = cxx.vendor == 'clang' + if cxx.version >= 'clang-3.6': + cxx.cxxflags += ['-Wno-inconsistent-missing-override'] + if have_clang or (cxx.version >= 'gcc-4.6'): + cxx.cflags += ['-Wno-narrowing'] + if have_clang or (cxx.version >= 'gcc-4.7'): + cxx.cxxflags += ['-Wno-delete-non-virtual-dtor'] + if cxx.version >= 'gcc-4.8': + cxx.cflags += ['-Wno-unused-result'] + + if have_clang: + cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch'] + if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4': + cxx.cxxflags += ['-Wno-deprecated-register'] + else: + cxx.cxxflags += ['-Wno-deprecated'] + cxx.cflags += ['-Wno-sometimes-uninitialized'] + + if have_gcc: + cxx.cflags += ['-mfpmath=sse'] + + if builder.options.opt == '1': + cxx.cflags += ['-O3'] + + def configure_msvc(self, cxx): + if builder.options.debug == '1': + cxx.cflags += ['/MTd'] + cxx.linkflags += ['/NODEFAULTLIB:libcmt'] + else: + cxx.cflags += ['/MT'] + cxx.defines += [ + '_CRT_SECURE_NO_DEPRECATE', + '_CRT_SECURE_NO_WARNINGS', + '_CRT_NONSTDC_NO_DEPRECATE', + '_ITERATOR_DEBUG_LEVEL=0', + ] + cxx.cflags += [ + '/W3', + ] + cxx.cxxflags += [ + '/EHsc', + '/GR-', + '/TP', + ] + cxx.linkflags += [ + '/MACHINE:X86', + 'kernel32.lib', + 'user32.lib', + 'gdi32.lib', + 'winspool.lib', + 'comdlg32.lib', + 'advapi32.lib', + 'shell32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'odbc32.lib', + 'odbccp32.lib', + ] + + if builder.options.opt == '1': + cxx.cflags += ['/Ox', '/Zo'] + cxx.linkflags += ['/OPT:ICF', '/OPT:REF'] + + if builder.options.debug == '1': + cxx.cflags += ['/Od', '/RTC1'] + + # This needs to be after our optimization flags which could otherwise disable it. + # Don't omit the frame pointer. + cxx.cflags += ['/Oy-'] + + def configure_linux(self, cxx): + cxx.defines += ['_LINUX', 'POSIX'] + cxx.linkflags += ['-Wl,--exclude-libs,ALL', '-lm'] + if cxx.vendor == 'gcc': + cxx.linkflags += ['-static-libgcc'] + elif cxx.vendor == 'clang': + cxx.linkflags += ['-lgcc_eh'] + + def configure_mac(self, cxx): + cxx.defines += ['OSX', '_OSX', 'POSIX'] + cxx.cflags += ['-mmacosx-version-min=10.5'] + cxx.linkflags += [ + '-mmacosx-version-min=10.5', + '-arch', 'i386', + '-lstdc++', + '-stdlib=libstdc++', + ] + cxx.cxxflags += ['-stdlib=libstdc++'] + + def configure_windows(self, cxx): + cxx.defines += ['WIN32', '_WINDOWS'] + + def ConfigureForExtension(self, context, compiler): + compiler.cxxincludes += [ + os.path.join(context.currentSourcePath), + os.path.join(context.currentSourcePath, 'sdk'), + os.path.join(self.sm_root, 'public'), + os.path.join(self.sm_root, 'public', 'extensions'), + os.path.join(self.sm_root, 'sourcepawn', 'include'), + os.path.join(self.sm_root, 'public', 'amtl', 'amtl'), + os.path.join(self.sm_root, 'public', 'amtl'), + ] + return compiler + + def ConfigureForHL2(self, binary, sdk): + compiler = binary.compiler + + if sdk.name == 'episode1': + mms_path = os.path.join(self.mms_root, 'core-legacy') + else: + mms_path = os.path.join(self.mms_root, 'core') + + compiler.cxxincludes += [ + os.path.join(mms_path), + os.path.join(mms_path, 'sourcehook'), + ] + + defines = ['SE_' + PossibleSDKs[i].define + '=' + PossibleSDKs[i].code for i in PossibleSDKs] + compiler.defines += defines + + paths = [ + ['public'], + ['public', 'engine'], + ['public', 'mathlib'], + ['public', 'vstdlib'], + ['public', 'tier0'], + ['public', 'tier1'] + ] + if sdk.name == 'episode1' or sdk.name == 'darkm': + paths.append(['public', 'dlls']) + paths.append(['game_shared']) + else: + paths.append(['public', 'game', 'server']) + paths.append(['public', 'toolframework']) + paths.append(['game', 'shared']) + paths.append(['common']) + + compiler.defines += ['SOURCE_ENGINE=' + sdk.code] + + if sdk.name in ['sdk2013', 'bms'] and compiler.like('gcc'): + # The 2013 SDK already has these in public/tier0/basetypes.h + compiler.defines.remove('stricmp=strcasecmp') + compiler.defines.remove('_stricmp=strcasecmp') + compiler.defines.remove('_snprintf=snprintf') + compiler.defines.remove('_vsnprintf=vsnprintf') + + if compiler.like('msvc'): + compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32'] + else: + compiler.defines += ['COMPILER_GCC'] + + # For everything after Swarm, this needs to be defined for entity networking + # to work properly with sendprop value changes. + if sdk.name in ['blade', 'insurgency', 'csgo', 'dota']: + compiler.defines += ['NETWORK_VARS_ENABLED'] + + if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2', 'dota']: + if builder.target_platform in ['linux', 'mac']: + compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE'] + + if sdk.name == 'csgo' and builder.target_platform == 'linux': + compiler.linkflags += ['-lstdc++'] + + for path in paths: + compiler.cxxincludes += [os.path.join(sdk.path, *path)] + + if builder.target_platform == 'linux': + if sdk.name == 'episode1': + lib_folder = os.path.join(sdk.path, 'linux_sdk') + elif sdk.name in ['sdk2013', 'bms']: + lib_folder = os.path.join(sdk.path, 'lib', 'public', 'linux32') + else: + lib_folder = os.path.join(sdk.path, 'lib', 'linux') + elif builder.target_platform == 'mac': + if sdk.name in ['sdk2013', 'bms']: + lib_folder = os.path.join(sdk.path, 'lib', 'public', 'osx32') + else: + lib_folder = os.path.join(sdk.path, 'lib', 'mac') + + if builder.target_platform in ['linux', 'mac']: + if sdk.name in ['sdk2013', 'bms']: + compiler.postlink += [ + compiler.Dep(os.path.join(lib_folder, 'tier1.a')), + compiler.Dep(os.path.join(lib_folder, 'mathlib.a')) + ] + else: + compiler.postlink += [ + compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a')), + compiler.Dep(os.path.join(lib_folder, 'mathlib_i486.a')) + ] + + if sdk.name in ['blade', 'insurgency', 'csgo', 'dota']: + compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))] + + dynamic_libs = [] + if builder.target_platform == 'linux': + if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency']: + dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so'] + elif sdk.name in ['l4d', 'blade', 'insurgency', 'csgo', 'dota']: + dynamic_libs = ['libtier0.so', 'libvstdlib.so'] + else: + dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so'] + elif builder.target_platform == 'mac': + compiler.linkflags.append('-liconv') + dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib'] + elif builder.target_platform == 'windows': + libs = ['tier0', 'tier1', 'vstdlib', 'mathlib'] + if sdk.name in ['swarm', 'blade', 'insurgency', 'csgo', 'dota']: + libs.append('interfaces') + for lib in libs: + lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib' + compiler.linkflags.append(compiler.Dep(lib_path)) + + for library in dynamic_libs: + source_path = os.path.join(lib_folder, library) + output_path = os.path.join(binary.localFolder, library) + + def make_linker(source_path, output_path): + def link(context, binary): + cmd_node, (output,) = context.AddSymlink(source_path, output_path) + return output + return link + + linker = make_linker(source_path, output_path) + compiler.linkflags[0:0] = [compiler.Dep(library, linker)] + + return binary + + def HL2Library(self, context, name, sdk): + binary = context.compiler.Library(name) + self.ConfigureForExtension(context, binary.compiler) + return self.ConfigureForHL2(binary, sdk) + + def HL2Project(self, context, name): + project = context.compiler.LibraryProject(name) + self.ConfigureForExtension(context, project.compiler) + return project + + def HL2Config(self, project, name, sdk): + binary = project.Configure(name, '{0} - {1}'.format(self.tag, sdk.name)) + return self.ConfigureForHL2(binary, sdk) + +Extension = ExtensionConfig() +Extension.detectSDKs() +Extension.configure() + +# Add additional buildscripts here +BuildScripts = [ + 'AMBuilder', +] + +if builder.backend == 'amb2': + BuildScripts += [ + 'PackageScript', + ] + +builder.RunBuildScripts(BuildScripts, { 'Extension': Extension}) diff --git a/AMBuilder b/AMBuilder new file mode 100644 index 0000000..0fa4b7d --- /dev/null +++ b/AMBuilder @@ -0,0 +1,35 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os, sys + +projectName = 'sourcetvmanager' + +# smsdk_ext.cpp will be automatically added later +sourceFiles = [ + 'extension.cpp', + 'natives.cpp', + 'forwards.cpp' +] + +############### +# Make sure to edit PackageScript, which copies your files to their appropriate locations +# Simple extensions do not need to modify past this point. + +project = Extension.HL2Project(builder, projectName + '.ext') + +if os.path.isfile(os.path.join(builder.currentSourcePath, 'sdk', 'smsdk_ext.cpp')): + # Use the copy included in the project + project.sources += [os.path.join('sdk', 'smsdk_ext.cpp')] +else: + # Use the copy included with SM 1.6 and newer + project.sources += [os.path.join(Extension.sm_root, 'public', 'smsdk_ext.cpp')] + +project.sources += sourceFiles + +for sdk_name in ['csgo']: + if sdk_name not in Extension.sdks: + continue + sdk = Extension.sdks[sdk_name] + + binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk) + +Extension.extensions = builder.Add(project) diff --git a/PackageScript b/PackageScript new file mode 100644 index 0000000..f768acb --- /dev/null +++ b/PackageScript @@ -0,0 +1,50 @@ +# vim: set ts=8 sts=2 sw=2 tw=99 et ft=python: +import os + +# This is where the files will be output to +# package is the default +builder.SetBuildFolder('package') + +# Add any folders you need to this list +folder_list = [ + 'addons/sourcemod/extensions', + 'addons/sourcemod/scripting/include', + 'addons/sourcemod/gamedata', + #'addons/sourcemod/configs', +] + +# Create the distribution folder hierarchy. +folder_map = {} +for folder in folder_list: + norm_folder = os.path.normpath(folder) + folder_map[folder] = builder.AddFolder(norm_folder) + +# Do all straight-up file copies from the source tree. +def CopyFiles(src, dest, files): + if not dest: + dest = src + dest_entry = folder_map[dest] + for source_file in files: + source_path = os.path.join(builder.sourcePath, src, source_file) + builder.AddCopy(source_path, dest_entry) + +# Include files +CopyFiles('', 'addons/sourcemod/scripting/include', + [ 'sourcetvmanager.inc', ] +) + +# GameData files +CopyFiles('', 'addons/sourcemod/gamedata', + [ 'sourcetvmanager.games.txt' ] +) + +# Config Files +#CopyFiles('configs', 'addons/sourcemod/configs', +# [ 'configfile.cfg', +# 'otherconfig.cfg, +# ] +#) + +# Copy binaries. +for cxx_task in Extension.extensions: + builder.AddCopy(cxx_task.binary, folder_map['addons/sourcemod/extensions']) diff --git a/configure.py b/configure.py new file mode 100644 index 0000000..57910e8 --- /dev/null +++ b/configure.py @@ -0,0 +1,23 @@ +# vim: set sts=2 ts=8 sw=2 tw=99 et: +import sys +from ambuild2 import run + +# Simple extensions do not need to modify this file. + +builder = run.PrepareBuild(sourcePath = sys.path[0]) + +builder.options.add_option('--hl2sdk-root', type=str, dest='hl2sdk_root', default=None, + help='Root search folder for HL2SDKs') +builder.options.add_option('--mms-path', type=str, dest='mms_path', default=None, + help='Path to Metamod:Source') +builder.options.add_option('--sm-path', type=str, dest='sm_path', default=None, + help='Path to SourceMod') +builder.options.add_option('--enable-debug', action='store_const', const='1', dest='debug', + help='Enable debugging symbols') +builder.options.add_option('--enable-optimize', action='store_const', const='1', dest='opt', + help='Enable optimization') +builder.options.add_option('-s', '--sdks', default='all', dest='sdks', + help='Build against specified SDKs; valid args are "all", "present", or ' + 'comma-delimited list of engine names (default: %default)') + +builder.Configure() diff --git a/extension.cpp b/extension.cpp new file mode 100644 index 0000000..bc02a78 --- /dev/null +++ b/extension.cpp @@ -0,0 +1,257 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SourceTV Manager Extension + * Copyright (C) 2004-2016 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#include "extension.h" +#include "forwards.h" + +IHLTVDirector *hltvdirector = nullptr; +IHLTVServer *hltvserver = nullptr; +IDemoRecorder *demorecorder = nullptr; +void *host_client = nullptr; +void *old_host_client = nullptr; +bool g_HostClientOverridden = false; + +IGameEventManager2 *gameevents = nullptr; + +IBinTools *bintools = nullptr; +ISDKTools *sdktools = nullptr; +IServer *iserver = nullptr; +IGameConfig *g_pGameConf = nullptr; + +SH_DECL_HOOK1_void(IHLTVDirector, AddHLTVServer, SH_NOATTRIB, 0, IHLTVServer *); +SH_DECL_HOOK1_void(IHLTVDirector, RemoveHLTVServer, SH_NOATTRIB, 0, IHLTVServer *); + +SH_DECL_HOOK1(IClient, ExecuteStringCommand, SH_NOATTRIB, 0, bool, const char *); + +/** + * @file extension.cpp + * @brief Implement extension code here. + */ + +SourceTVManager g_STVManager; /**< Global singleton for extension's main interface */ + +SMEXT_LINK(&g_STVManager); + +extern const sp_nativeinfo_t sourcetv_natives[]; + +bool SourceTVManager::SDK_OnLoad(char *error, size_t maxlength, bool late) +{ + sharesys->AddDependency(myself, "bintools.ext", true, true); + sharesys->AddDependency(myself, "sdktools.ext", true, true); + + char conf_error[255]; + if (!gameconfs->LoadGameConfigFile("sourcetvmanager.games", &g_pGameConf, conf_error, sizeof(conf_error))) + { + if (error) + { + snprintf(error, maxlength, "Could not read sourcetvmanager.games: %s", conf_error); + } + return false; + } + + // Get the host_client pointer + // This is used to fix a null pointer crash when executing fake commands on bots. + if (!g_pGameConf->GetAddress("host_client", &host_client) || !host_client) + { + smutils->LogError(myself, "Failed to find host_client pointer. Server might crash when executing commands on SourceTV bot."); + } + + sharesys->AddNatives(myself, sourcetv_natives); + sharesys->RegisterLibrary(myself, "sourcetvmanager"); + + return true; +} + +void SourceTVManager::SDK_OnAllLoaded() +{ + SH_ADD_HOOK(IHLTVDirector, AddHLTVServer, hltvdirector, SH_MEMBER(this, &SourceTVManager::OnAddHLTVServer_Post), true); + SH_ADD_HOOK(IHLTVDirector, RemoveHLTVServer, hltvdirector, SH_MEMBER(this, &SourceTVManager::OnRemoveHLTVServer_Post), true); + + SM_GET_LATE_IFACE(BINTOOLS, bintools); + SM_GET_LATE_IFACE(SDKTOOLS, sdktools); + + g_pSTVForwards.Init(); + + iserver = sdktools->GetIServer(); + if (!iserver) + smutils->LogError(myself, "Failed to get IServer interface from SDKTools. Some functions won't work."); + + if (hltvdirector->GetHLTVServerCount() > 0) + SelectSourceTVServer(hltvdirector->GetHLTVServer(0)); + + // Hook all the exisiting servers. + for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++) + { + g_pSTVForwards.HookRecorder(GetDemoRecorderPtr(hltvdirector->GetHLTVServer(i))); + } +} + +bool SourceTVManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) +{ + GET_V_IFACE_CURRENT(GetServerFactory, hltvdirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR); + GET_V_IFACE_CURRENT(GetEngineFactory, gameevents, IGameEventManager2, INTERFACEVERSION_GAMEEVENTSMANAGER2); + + return true; +} + +void SourceTVManager::SDK_OnUnload() +{ + SH_REMOVE_HOOK(IHLTVDirector, AddHLTVServer, hltvdirector, SH_MEMBER(this, &SourceTVManager::OnAddHLTVServer_Post), true); + SH_REMOVE_HOOK(IHLTVDirector, RemoveHLTVServer, hltvdirector, SH_MEMBER(this, &SourceTVManager::OnRemoveHLTVServer_Post), true); + + gameconfs->CloseGameConfigFile(g_pGameConf); + + // Unhook all the existing servers. + for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++) + { + g_pSTVForwards.UnhookRecorder(GetDemoRecorderPtr(hltvdirector->GetHLTVServer(i))); + } + g_pSTVForwards.Shutdown(); +} + +bool SourceTVManager::QueryRunning(char *error, size_t maxlength) +{ + SM_CHECK_IFACE(BINTOOLS, bintools); + SM_CHECK_IFACE(SDKTOOLS, sdktools); + + return true; +} + +void SourceTVManager::SelectSourceTVServer(IHLTVServer *hltv) +{ + // Need to unhook the old server first? + if (hltvserver != nullptr && iserver != nullptr) + { + IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot()); + if (pClient) + { + SH_REMOVE_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand), false); + SH_REMOVE_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand_Post), true); + } + } + + // Select the new server. + hltvserver = hltv; + + UpdateDemoRecorderPointer(); + + if (!hltvserver) + return; + + if (!iserver) + return; + IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot()); + if (!pClient) + return; + + SH_ADD_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand), false); + SH_ADD_HOOK(IClient, ExecuteStringCommand, pClient, SH_MEMBER(this, &SourceTVManager::OnHLTVBotExecuteStringCommand_Post), true); +} + +IDemoRecorder *SourceTVManager::GetDemoRecorderPtr(IHLTVServer *hltvserver) +{ + static int offset = -1; + if (offset == -1 && !g_pGameConf->GetOffset("CHLTVServer::m_DemoRecorder", &offset)) + { + smutils->LogError(myself, "Failed to get CHLTVServer::m_DemoRecorder offset."); + return nullptr; + } + + if (hltvserver) + return (IDemoRecorder *)((intptr_t)hltvserver + offset); + else + return nullptr; +} + +void SourceTVManager::UpdateDemoRecorderPointer() +{ + demorecorder = GetDemoRecorderPtr(hltvserver); +} + +void SourceTVManager::OnAddHLTVServer_Post(IHLTVServer *hltv) +{ + g_pSTVForwards.HookRecorder(GetDemoRecorderPtr(hltv)); + + // We already selected some SourceTV server. Keep it. + if (hltvserver != nullptr) + RETURN_META(MRES_IGNORED); + + // This is the first SourceTV server to be added. Hook it. + SelectSourceTVServer(hltv); +} + +void SourceTVManager::OnRemoveHLTVServer_Post(IHLTVServer *hltv) +{ + g_pSTVForwards.UnhookRecorder(GetDemoRecorderPtr(hltv)); + + // We got this SourceTV server selected. Now it's gone :( + if (hltvserver == hltv) + { + // Is there another one available? Try to keep us operable. + if (hltvdirector->GetHLTVServerCount() > 0) + SelectSourceTVServer(hltvdirector->GetHLTVServer(0)); + else + SelectSourceTVServer(nullptr); + } +} + +bool SourceTVManager::OnHLTVBotExecuteStringCommand(const char *s) +{ + if (!hltvserver || !iserver || !host_client) + RETURN_META_VALUE(MRES_IGNORED, 0); + + if (*(void **)host_client) + RETURN_META_VALUE(MRES_IGNORED, 0); + + IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot()); + if (!pClient) + RETURN_META_VALUE(MRES_IGNORED, 0); + + // The IClient vtable is +4 from the CBaseClient vtable due to multiple inheritance. + void *pGameClient = (void *)((intptr_t)pClient - 4); + + old_host_client = *(void **)host_client; + *(void **)host_client = pGameClient; + g_HostClientOverridden = true; + + RETURN_META_VALUE(MRES_IGNORED, 0); +} + +bool SourceTVManager::OnHLTVBotExecuteStringCommand_Post(const char *s) +{ + if (!host_client || !g_HostClientOverridden) + RETURN_META_VALUE(MRES_IGNORED, 0); + + *(void **)host_client = old_host_client; + g_HostClientOverridden = false; + RETURN_META_VALUE(MRES_IGNORED, 0); +} + diff --git a/extension.h b/extension.h new file mode 100644 index 0000000..fe4689b --- /dev/null +++ b/extension.h @@ -0,0 +1,152 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ + +/** + * @file extension.h + * @brief Sample extension code header. + */ + +#include "smsdk_ext.h" +#include +#include +#include "ihltvdirector.h" +#include "ihltv.h" +#include "iserver.h" +#include "ihltvdemorecorder.h" +#include "igameevents.h" + +/** + * @brief Sample implementation of the SDK Extension. + * Note: Uncomment one of the pre-defined virtual functions in order to use it. + */ +class SourceTVManager : public SDKExtension +{ +public: + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload(); + + /** + * @brief This is called once all known extensions have been loaded. + * Note: It is is a good idea to add natives here, if any are provided. + */ + virtual void SDK_OnAllLoaded(); + + /** + * @brief Called when the pause state is changed. + */ + //virtual void SDK_OnPauseChange(bool paused); + + /** + * @brief this is called when Core wants to know if your extension is working. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @return True if working, false otherwise. + */ + virtual bool QueryRunning(char *error, size_t maxlength); +public: +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late); + + /** + * @brief Called when Metamod is detaching, after the extension version is called. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); +#endif + +public: + void SelectSourceTVServer(IHLTVServer *hltv); + IDemoRecorder *GetDemoRecorderPtr(IHLTVServer *hltvserver); + +private: + void OnAddHLTVServer_Post(IHLTVServer *hltv); + void OnRemoveHLTVServer_Post(IHLTVServer *hltv); + bool OnHLTVBotSendNetMsg_Post(INetMessage &msg, bool bForceReliable, bool bVoice); + bool OnHLTVBotExecuteStringCommand(const char *s); + bool OnHLTVBotExecuteStringCommand_Post(const char *s); + +private: + void UpdateDemoRecorderPointer(); +}; + +/* Interfaces from SourceMod */ +extern SourceTVManager g_STVManager; +extern IBinTools *bintools; +extern ISDKTools *sdktools; +extern IGameConfig *g_pGameConf; + +extern IServer *iserver; +extern IGameEventManager2 *gameevents; + +extern IHLTVDirector *hltvdirector; +extern IHLTVServer *hltvserver; +extern IDemoRecorder *demorecorder; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/forwards.cpp b/forwards.cpp new file mode 100644 index 0000000..c3b7cc3 --- /dev/null +++ b/forwards.cpp @@ -0,0 +1,94 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod SourceTV Manager Extension +* Copyright (C) 2004-2016 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#include "extension.h" +#include "forwards.h" + +CForwardManager g_pSTVForwards; + +SH_DECL_HOOK2_void(IDemoRecorder, StartRecording, SH_NOATTRIB, 0, const char *, bool) +SH_DECL_HOOK1_void(IDemoRecorder, StopRecording, SH_NOATTRIB, 0, CGameInfo const *) + +void CForwardManager::Init() +{ + m_StartRecordingFwd = forwards->CreateForward("SourceTV_OnStartRecording", ET_Ignore, 3, NULL, Param_Cell, Param_String, Param_Cell); + m_StopRecordingFwd = forwards->CreateForward("SourceTV_OnStopRecording", ET_Ignore, 3, NULL, Param_Cell, Param_String, Param_Cell); +} + +void CForwardManager::Shutdown() +{ + forwards->ReleaseForward(m_StartRecordingFwd); + forwards->ReleaseForward(m_StopRecordingFwd); +} + +void CForwardManager::HookRecorder(IDemoRecorder *recorder) +{ + SH_ADD_HOOK(IDemoRecorder, StartRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStartRecording_Post), false); + SH_ADD_HOOK(IDemoRecorder, StopRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStopRecording_Post), false); +} + +void CForwardManager::UnhookRecorder(IDemoRecorder *recorder) +{ + SH_REMOVE_HOOK(IDemoRecorder, StartRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStartRecording_Post), false); + SH_REMOVE_HOOK(IDemoRecorder, StopRecording, recorder, SH_MEMBER(this, &CForwardManager::OnStopRecording_Post), false); +} + +void CForwardManager::OnStartRecording_Post(const char *filename, bool bContinuously) +{ + if (m_StartRecordingFwd->GetFunctionCount() == 0) + RETURN_META(MRES_IGNORED); + + m_StartRecordingFwd->PushCell(0); // TODO: Get current hltvserver index + m_StartRecordingFwd->PushString(filename); + m_StartRecordingFwd->PushCell(bContinuously); + m_StartRecordingFwd->Execute(); + + RETURN_META(MRES_IGNORED); +} + +void CForwardManager::OnStopRecording_Post(CGameInfo const *info) +{ + if (m_StopRecordingFwd->GetFunctionCount() == 0) + RETURN_META(MRES_IGNORED); + + IDemoRecorder *recorder = META_IFACEPTR(IDemoRecorder); + if (!recorder->IsRecording()) + RETURN_META(MRES_IGNORED); + + char *pDemoFile = (char *)recorder->GetDemoFile(); + + m_StopRecordingFwd->PushCell(0); // TODO: Get current hltvserver index + m_StopRecordingFwd->PushString(pDemoFile); + m_StopRecordingFwd->PushCell(recorder->GetRecordingTick()); + m_StopRecordingFwd->Execute(); + + RETURN_META(MRES_IGNORED); +} \ No newline at end of file diff --git a/forwards.h b/forwards.h new file mode 100644 index 0000000..3c984e6 --- /dev/null +++ b/forwards.h @@ -0,0 +1,59 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod Sample Extension +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_FORWARDS_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_FORWARDS_H_ + +#include "extension.h" + +class CGameInfo; + +class CForwardManager +{ +public: + void Init(); + void Shutdown(); + + void HookRecorder(IDemoRecorder *recorder); + void UnhookRecorder(IDemoRecorder *recorder); + +private: + void OnStartRecording_Post(const char *filename, bool bContinuously); + void OnStopRecording_Post(CGameInfo const *info); + +private: + IForward *m_StartRecordingFwd; + IForward *m_StopRecordingFwd; +}; + +extern CForwardManager g_pSTVForwards; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_FORWARDS_H_ \ No newline at end of file diff --git a/ihltvdemorecorder.h b/ihltvdemorecorder.h new file mode 100644 index 0000000..f9d11d5 --- /dev/null +++ b/ihltvdemorecorder.h @@ -0,0 +1,33 @@ +#ifndef _INCLUDE_DEMORECORDER_H +#define _INCLUDE_DEMORECORDER_H + + +class CDemoFile; +class bf_read; +class ServerClass; + +class IDemoRecorder +{ +public: + + virtual CDemoFile *GetDemoFile() = 0; + virtual int GetRecordingTick(void) = 0; + + virtual void StartRecording(const char *filename, bool bContinuously) = 0; + virtual void SetSignonState(int state) = 0; + virtual bool IsRecording(void) = 0; + virtual void PauseRecording(void) = 0; + virtual void ResumeRecording(void) = 0; + virtual void StopRecording(CGameInfo const *info) = 0; + + virtual void RecordCommand(const char *cmdstring) = 0; + virtual void RecordUserInput(int cmdnumber) = 0; + virtual void RecordMessages(bf_read &data, int bits) = 0; + virtual void RecordPacket(void) = 0; + virtual void RecordServerClasses(ServerClass *pClasses) = 0; + virtual void RecordStringTables(void); + virtual void RecordCustomData(int, void const *, unsigned int); + + virtual void ResetDemoInterpolation(void) = 0; +}; +#endif \ No newline at end of file diff --git a/natives.cpp b/natives.cpp new file mode 100644 index 0000000..8046b11 --- /dev/null +++ b/natives.cpp @@ -0,0 +1,492 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod SourceTV Manager Extension +* Copyright (C) 2004-2016 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#include "extension.h" + +extern const sp_nativeinfo_t sourcetv_natives[]; + +// native SourceTV_GetHLTVServerCount(); +static cell_t Native_GetHLTVServerCount(IPluginContext *pContext, const cell_t *params) +{ + return hltvdirector->GetHLTVServerCount(); +} + +// native SourceTV_SelectHLTVServer(); +static cell_t Native_SelectHLTVServer(IPluginContext *pContext, const cell_t *params) +{ + if (params[1] < 0 || params[1] >= hltvdirector->GetHLTVServerCount()) + { + pContext->ReportError("Invalid HLTV server instance number (%d).", params[1]); + return 0; + } + g_STVManager.SelectSourceTVServer(hltvdirector->GetHLTVServer(params[1])); + + return 0; +} + +// native SourceTV_GetSelectedHLTVServer(); +static cell_t Native_GetSelectedHLTVServer(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return -1; + + for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++) + { + if (hltvserver == hltvdirector->GetHLTVServer(i)) + return i; + } + + // We should have found it in the above loop :S + hltvserver = nullptr; + return -1; +} + +// native SourceTV_GetBotIndex(); +static cell_t Native_GetBotIndex(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + return hltvserver->GetHLTVSlot() + 1; +} + +// native bool:SourceTV_GetLocalStats(&proxies, &slots, &specs); +static cell_t Native_GetLocalStats(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + int proxies, slots, specs; + hltvserver->GetLocalStats(proxies, slots, specs); + + cell_t *plProxies, *plSlots, *plSpecs; + pContext->LocalToPhysAddr(params[1], &plProxies); + pContext->LocalToPhysAddr(params[2], &plSlots); + pContext->LocalToPhysAddr(params[3], &plSpecs); + + *plProxies = proxies; + *plSlots = slots; + *plSpecs = specs; + return 1; +} + +// native SourceTV_GetBroadcastTick(); +static cell_t Native_GetBroadcastTick(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return -1; + + return hltvdirector->GetDirectorTick(); +} + +// native Float:SourceTV_GetDelay(); +static cell_t Native_GetDelay(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return -1; + + return sp_ftoc(hltvdirector->GetDelay()); +} + +// native bool:SourceTV_BroadcastHintMessage(const String:format[], any:...); +static cell_t Native_BroadcastHintMessage(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + char buffer[1024]; + size_t len; + { + DetectExceptions eh(pContext); + len = smutils->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) + return 0; + } + + IGameEvent *msg = gameevents->CreateEvent("hltv_message", true); + if (!msg) + return 0; + + msg->SetString("text", buffer); + hltvserver->BroadcastEvent(msg); + gameevents->FreeEvent(msg); + + return 1; +} + +// native bool:SourceTV_BroadcastConsoleMessage(const String:format[], any:...); +static cell_t Native_BroadcastConsoleMessage(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + static ICallWrapper *pBroadcastPrintf = nullptr; + + if (!pBroadcastPrintf) + { + int offset = -1; + if (!g_pGameConf->GetOffset("BroadcastPrintf", &offset) || offset == -1) + { + pContext->ReportError("Failed to get CBaseServer::BroadcastPrintf offset."); + return 0; + } + + PassInfo pass[3]; + pass[0].flags = PASSFLAG_BYVAL; + pass[0].type = PassType_Basic; + pass[0].size = sizeof(void *); + pass[1].flags = PASSFLAG_BYVAL; + pass[1].type = PassType_Basic; + pass[1].size = sizeof(char *); + pass[2].flags = PASSFLAG_BYVAL; + pass[2].type = PassType_Basic; + pass[2].size = sizeof(char *); + + void *iserver = (void *)hltvserver->GetBaseServer(); + void **vtable = *(void ***)iserver; + void *func = vtable[offset]; + + pBroadcastPrintf = bintools->CreateCall(func, CallConv_Cdecl, NULL, pass, 3); + } + + char buffer[1024]; + size_t len; + { + DetectExceptions eh(pContext); + len = smutils->FormatString(buffer, sizeof(buffer) - 2, pContext, params, 1); + if (eh.HasException()) + return 0; + } + + buffer[len++] = '\n'; + buffer[len] = '\0'; + + if (pBroadcastPrintf) + { + unsigned char vstk[sizeof(void *) + sizeof(char *) * 2]; + unsigned char *vptr = vstk; + + *(void **)vptr = (void *)hltvserver->GetBaseServer(); + vptr += sizeof(void *); + *(char **)vptr = "%s"; + vptr += sizeof(char *); + *(char **)vptr = buffer; + + pBroadcastPrintf->Execute(vstk, NULL); + } + + return 1; +} + +// native SourceTV_GetViewEntity(); +static cell_t Native_GetViewEntity(IPluginContext *pContext, const cell_t *params) +{ + return hltvdirector->GetPVSEntity(); +} + +// native SourceTV_GetViewCoordinates(); +static cell_t Native_GetViewCoordinates(IPluginContext *pContext, const cell_t *params) +{ + Vector pvs = hltvdirector->GetPVSOrigin(); + + cell_t *addr; + pContext->LocalToPhysAddr(params[1], &addr); + addr[0] = sp_ftoc(pvs.x); + addr[1] = sp_ftoc(pvs.y); + addr[2] = sp_ftoc(pvs.z); + return 0; +} + +// native bool:SourceTV_IsRecording(); +static cell_t Native_IsRecording(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + return hltvserver->IsRecording(); +} + +// Checks in COM_IsValidPath in the engine +static bool IsValidPath(const char *path) +{ + return strlen(path) > 0 && !strstr(path, "\\\\") && !strstr(path, ":") && !strstr(path, "..") && !strstr(path, "\n") && !strstr(path, "\r"); +} + +// native bool:SourceTV_StartRecording(const String:sFilename[]); +static cell_t Native_StartRecording(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + // SourceTV is not active. + if (!hltvserver->GetBaseServer()->IsActive()) + return 0; + + // Only SourceTV Master can record demos instantly + if (!hltvserver->IsMasterProxy()) + return 0; + + // already recording + if (hltvserver->IsRecording()) + return 0; + + char *pFile; + pContext->LocalToString(params[1], &pFile); + + // Invalid path. + if (!IsValidPath(pFile)) + return 0; + + // Make sure there is a '.dem' suffix + char pPath[PLATFORM_MAX_PATH]; + size_t len = strlen(pFile); + const char *ext = libsys->GetFileExtension(pFile); + if (!ext || stricmp(ext, "dem") != 0) + ext = ".dem"; + else + ext = ""; + smutils->Format(pPath, sizeof(pPath), "%s%s", pFile, ext); + + if (hltvdirector->GetHLTVServerCount() > 1) + { + for (int i = 0; i < hltvdirector->GetHLTVServerCount(); i++) + { + IHLTVServer *otherserver = hltvdirector->GetHLTVServer(i); + if (!otherserver->IsRecording()) + continue; + + // Cannot record. another SourceTV is currently recording into that file. + if (!stricmp(pPath, otherserver->GetRecordingDemoFilename())) + return 0; + } + } + + demorecorder->StartRecording(pPath, false); + + return 1; +} + +// native bool:SourceTV_StopRecording(); +static cell_t Native_StopRecording(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + if (!hltvserver->IsRecording()) + return 0; + + hltvserver->StopRecording(NULL); + + // TODO: Stop recording on all other active hltvservers (tv_stoprecord in csgo does this) + + return 1; +} + +// native bool:SourceTV_GetDemoFileName(String:sFilename[], maxlen); +static cell_t Native_GetDemoFileName(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + if (!hltvserver->IsRecording()) + return 0; + + char *pDemoFile = hltvserver->GetRecordingDemoFilename(); + if (!pDemoFile) + return 0; + + pContext->StringToLocalUTF8(params[1], params[2], pDemoFile, NULL); + + return 1; +} + +// native SourceTV_GetRecordingTick(); +static cell_t Native_GetRecordingTick(IPluginContext *pContext, const cell_t *params) +{ + if (demorecorder == nullptr) + return -1; + + if (!demorecorder->IsRecording()) + return -1; + + return demorecorder->GetRecordingTick(); +} + +// native bool:SourceTV_PrintToDemoConsole(const String:format[], any:...); +static cell_t Native_PrintToDemoConsole(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + if (!iserver) + return 0; + IClient *pClient = iserver->GetClient(hltvserver->GetHLTVSlot()); + if (!pClient) + return 0; + + char buffer[1024]; + size_t len; + { + DetectExceptions eh(pContext); + len = smutils->FormatString(buffer, sizeof(buffer), pContext, params, 1); + if (eh.HasException()) + return 0; + } + + pClient->ClientPrintf("%s", buffer); + + return 1; +} + + + +// native SourceTV_GetSpectatorCount(); +static cell_t Native_GetSpectatorCount(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return -1; + + return hltvserver->GetBaseServer()->GetNumClients(); +} + +// native SourceTV_GetMaxClients(); +static cell_t Native_GetMaxClients(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return -1; + + return hltvserver->GetBaseServer()->GetMaxClients(); +} + +// native SourceTV_GetClientCount(); +static cell_t Native_GetClientCount(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return -1; + + return hltvserver->GetBaseServer()->GetClientCount(); +} + +// native bool:SourceTV_IsClientConnected(client); +static cell_t Native_IsClientConnected(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + cell_t client = params[1]; + if (client < 1 || client > hltvserver->GetBaseServer()->GetClientCount()) + { + pContext->ReportError("Invalid spectator client index %d.", client); + return 0; + } + + IClient *pClient = hltvserver->GetBaseServer()->GetClient(client - 1); + return pClient->IsConnected(); +} + +// native SourceTV_GetSpectatorName(client, String:name[], maxlen); +static cell_t Native_GetSpectatorName(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + cell_t client = params[1]; + if (client < 1 || client > hltvserver->GetBaseServer()->GetClientCount()) + { + pContext->ReportError("Invalid spectator client index %d.", client); + return 0; + } + + IClient *pClient = hltvserver->GetBaseServer()->GetClient(client - 1); + if (!pClient || !pClient->IsConnected()) + { + pContext->ReportError("Client %d is not connected.", client); + return 0; + } + + pContext->StringToLocalUTF8(params[2], static_cast(params[3]), pClient->GetClientName(), NULL); + return 0; +} + +// native SourceTV_KickClient(client, const String:sReason[]); +static cell_t Native_KickClient(IPluginContext *pContext, const cell_t *params) +{ + if (hltvserver == nullptr) + return 0; + + cell_t client = params[1]; + if (client < 1 || client > hltvserver->GetBaseServer()->GetClientCount()) + { + pContext->ReportError("Invalid spectator client index %d.", client); + return 0; + } + + IClient *pClient = hltvserver->GetBaseServer()->GetClient(client - 1); + if (!pClient || !pClient->IsConnected()) + { + pContext->ReportError("Client %d is not connected.", client); + return 0; + } + + char *pReason; + pContext->LocalToString(params[2], &pReason); + + hltvserver->GetBaseServer()->DisconnectClient(pClient, pReason); + return 0; +} + +const sp_nativeinfo_t sourcetv_natives[] = +{ + { "SourceTV_GetHLTVServerCount", Native_GetHLTVServerCount }, + { "SourceTV_SelectHLTVServer", Native_SelectHLTVServer }, + { "SourceTV_GetSelectedHLTVServer", Native_GetSelectedHLTVServer }, + { "SourceTV_GetBotIndex", Native_GetBotIndex }, + { "SourceTV_GetLocalStats", Native_GetLocalStats }, + { "SourceTV_GetBroadcastTick", Native_GetBroadcastTick }, + { "SourceTV_GetDelay", Native_GetDelay }, + { "SourceTV_BroadcastHintMessage", Native_BroadcastHintMessage }, + { "SourceTV_BroadcastConsoleMessage", Native_BroadcastConsoleMessage }, + { "SourceTV_GetViewEntity", Native_GetViewEntity }, + { "SourceTV_GetViewCoordinates", Native_GetViewCoordinates }, + { "SourceTV_StartRecording", Native_StartRecording }, + { "SourceTV_StopRecording", Native_StopRecording }, + { "SourceTV_IsRecording", Native_IsRecording }, + { "SourceTV_GetDemoFileName", Native_GetDemoFileName }, + { "SourceTV_GetRecordingTick", Native_GetRecordingTick }, + { "SourceTV_PrintToDemoConsole", Native_PrintToDemoConsole }, + { "SourceTV_GetSpectatorCount", Native_GetSpectatorCount }, + { "SourceTV_GetMaxClients", Native_GetMaxClients }, + { "SourceTV_GetClientCount", Native_GetClientCount }, + { "SourceTV_IsClientConnected", Native_IsClientConnected }, + { "SourceTV_GetSpectatorName", Native_GetSpectatorName }, + { "SourceTV_KickClient", Native_KickClient }, + { NULL, NULL }, +}; diff --git a/smsdk_config.h b/smsdk_config.h new file mode 100644 index 0000000..7ff3527 --- /dev/null +++ b/smsdk_config.h @@ -0,0 +1,81 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod Sample Extension + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ + +/** + * @file smsdk_config.h + * @brief Contains macros for configuring basic extension information. + */ + +/* Basic information exposed publicly */ +#define SMEXT_CONF_NAME "SourceTV Manager" +#define SMEXT_CONF_DESCRIPTION "Interface to interact with the SourceTV server." +#define SMEXT_CONF_VERSION "1.0.0.0" +#define SMEXT_CONF_AUTHOR "Peace-Maker" +#define SMEXT_CONF_URL "http://www.wcfan.de/" +#define SMEXT_CONF_LOGTAG "STVM" +#define SMEXT_CONF_LICENSE "GPL" +#define SMEXT_CONF_DATESTRING __DATE__ + +/** + * @brief Exposes plugin's main interface. + */ +#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name; + +/** + * @brief Sets whether or not this plugin required Metamod. + * NOTE: Uncomment to enable, comment to disable. + */ +#define SMEXT_CONF_METAMOD + +/** Enable interfaces you want to use here by uncommenting lines */ +#define SMEXT_ENABLE_FORWARDSYS +//#define SMEXT_ENABLE_HANDLESYS +//#define SMEXT_ENABLE_PLAYERHELPERS +//#define SMEXT_ENABLE_DBMANAGER +#define SMEXT_ENABLE_GAMECONF +//#define SMEXT_ENABLE_MEMUTILS +//#define SMEXT_ENABLE_GAMEHELPERS +//#define SMEXT_ENABLE_TIMERSYS +//#define SMEXT_ENABLE_THREADER +#define SMEXT_ENABLE_LIBSYS +//#define SMEXT_ENABLE_MENUS +//#define SMEXT_ENABLE_ADTFACTORY +//#define SMEXT_ENABLE_PLUGINSYS +//#define SMEXT_ENABLE_ADMINSYS +//#define SMEXT_ENABLE_TEXTPARSERS +//#define SMEXT_ENABLE_USERMSGS +//#define SMEXT_ENABLE_TRANSLATOR +//#define SMEXT_ENABLE_ROOTCONSOLEMENU + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/sourcetv_test.sp b/sourcetv_test.sp new file mode 100644 index 0000000..1aa5d8c --- /dev/null +++ b/sourcetv_test.sp @@ -0,0 +1,250 @@ +#undef REQUIRE_EXTENSIONS +#include "sourcetvmanager" + +public OnPluginStart() +{ + RegConsoleCmd("sm_servercount", Cmd_GetServerCount); + RegConsoleCmd("sm_selectserver", Cmd_SelectServer); + RegConsoleCmd("sm_getselectedserver", Cmd_GetSelectedServer); + RegConsoleCmd("sm_getbotindex", Cmd_GetBotIndex); + RegConsoleCmd("sm_localstats", Cmd_Localstats); + RegConsoleCmd("sm_getdelay", Cmd_GetDelay); + RegConsoleCmd("sm_spectators", Cmd_Spectators); + RegConsoleCmd("sm_spechintmsg", Cmd_SendHintMessage); + RegConsoleCmd("sm_specmsg", Cmd_SendMessage); + RegConsoleCmd("sm_startrecording", Cmd_StartRecording); + RegConsoleCmd("sm_stoprecording", Cmd_StopRecording); + RegConsoleCmd("sm_isrecording", Cmd_IsRecording); + RegConsoleCmd("sm_demofile", Cmd_GetDemoFileName); + RegConsoleCmd("sm_recordtick", Cmd_GetRecordTick); + RegConsoleCmd("sm_specstatus", Cmd_SpecStatus); + RegConsoleCmd("sm_democonsole", Cmd_PrintDemoConsole); + RegConsoleCmd("sm_botcmd", Cmd_ExecuteStringCommand); + RegConsoleCmd("sm_speckick", Cmd_KickClient); +} + +public SourceTV_OnStartRecording(hltvinstance, const String:filename[], bool:bContinuously) +{ + PrintToServer("Started recording sourcetv #%d demo to %s (continuosly %d)", hltvinstance, filename, bContinuously); +} + +public SourceTV_OnStopRecording(hltvinstance, const String:filename[], recordingtick) +{ + PrintToServer("Stopped recording sourcetv #%d demo to %s (%d ticks)", hltvinstance, filename, recordingtick); +} + +public Action:Cmd_GetServerCount(client, args) +{ + ReplyToCommand(client, "SourceTV server count: %d", SourceTV_GetHLTVServerCount()); + return Plugin_Handled; +} + +public Action:Cmd_SelectServer(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_selectserver "); + return Plugin_Handled; + } + + new String:sArg[12]; + GetCmdArg(1, sArg, sizeof(sArg)); + new iInstance = StringToInt(sArg); + + SourceTV_SelectHLTVServer(iInstance); + ReplyToCommand(client, "SourceTV selecting server: %d", iInstance); + return Plugin_Handled; +} + +public Action:Cmd_GetSelectedServer(client, args) +{ + ReplyToCommand(client, "SourceTV selected server: %d", SourceTV_GetSelectedHLTVServer()); + return Plugin_Handled; +} + +public Action:Cmd_GetBotIndex(client, args) +{ + ReplyToCommand(client, "SourceTV bot index: %d", SourceTV_GetBotIndex()); + return Plugin_Handled; +} + +public Action:Cmd_Localstats(client, args) +{ + new proxies, slots, specs; + if (!SourceTV_GetLocalStats(proxies, slots, specs)) + { + ReplyToCommand(client, "SourceTV local stats: no server selected :("); + return Plugin_Handled; + } + ReplyToCommand(client, "SourceTV local stats: proxies %d - slots %d - specs %d", proxies, slots, specs); + return Plugin_Handled; +} + +public Action:Cmd_GetDelay(client, args) +{ + ReplyToCommand(client, "SourceTV delay: %f", SourceTV_GetDelay()); + return Plugin_Handled; +} + +public Action:Cmd_Spectators(client, args) +{ + ReplyToCommand(client, "SourceTV spectator count: %d/%d", SourceTV_GetSpectatorCount(), SourceTV_GetClientCount()); + new String:sName[64]; + for (new i=1;i<=SourceTV_GetClientCount();i++) + { + if (!SourceTV_IsClientConnected(i)) + continue; + + SourceTV_GetSpectatorName(i, sName, sizeof(sName)); + ReplyToCommand(client, "Client %d: %s", i, sName); + } + return Plugin_Handled; +} + +public Action:Cmd_SendHintMessage(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_spechintmsg "); + return Plugin_Handled; + } + + new String:sMsg[1024]; + GetCmdArgString(sMsg, sizeof(sMsg)); + StripQuotes(sMsg); + + new bool:bSent = SourceTV_BroadcastHintMessage("%s", sMsg); + ReplyToCommand(client, "SourceTV sending hint message (success %d): %s", bSent, sMsg); + return Plugin_Handled; +} + +public Action:Cmd_SendMessage(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_specmsg "); + return Plugin_Handled; + } + + new String:sMsg[1024]; + GetCmdArgString(sMsg, sizeof(sMsg)); + StripQuotes(sMsg); + + new bool:bSent = SourceTV_BroadcastConsoleMessage("%s", sMsg); + ReplyToCommand(client, "SourceTV sending console message (success %d): %s", bSent, sMsg); + return Plugin_Handled; +} + +public Action:Cmd_StartRecording(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_startrecording "); + return Plugin_Handled; + } + + new String:sFilename[PLATFORM_MAX_PATH]; + GetCmdArgString(sFilename, sizeof(sFilename)); + StripQuotes(sFilename); + + if (SourceTV_StartRecording(sFilename)) + { + SourceTV_GetDemoFileName(sFilename, sizeof(sFilename)); + ReplyToCommand(client, "SourceTV started recording to: %s", sFilename); + } + else + ReplyToCommand(client, "SourceTV failed to start recording to: %s", sFilename); + return Plugin_Handled; +} + +public Action:Cmd_StopRecording(client, args) +{ + ReplyToCommand(client, "SourceTV stopped recording %d", SourceTV_StopRecording()); + return Plugin_Handled; +} + +public Action:Cmd_IsRecording(client, args) +{ + ReplyToCommand(client, "SourceTV is recording: %d", SourceTV_IsRecording()); + return Plugin_Handled; +} + +public Action:Cmd_GetDemoFileName(client, args) +{ + new String:sFileName[PLATFORM_MAX_PATH]; + ReplyToCommand(client, "SourceTV demo file name (%d): %s", SourceTV_GetDemoFileName(sFileName, sizeof(sFileName)), sFileName); + return Plugin_Handled; +} + +public Action:Cmd_GetRecordTick(client, args) +{ + ReplyToCommand(client, "SourceTV recording tick: %d", SourceTV_GetRecordingTick()); + return Plugin_Handled; +} + +public Action:Cmd_SpecStatus(client, args) +{ + new iSourceTV = SourceTV_GetBotIndex(); + if (!iSourceTV) + return Plugin_Handled; + FakeClientCommand(iSourceTV, "status"); + ReplyToCommand(client, "Sent status bot console."); + return Plugin_Handled; +} + +public Action:Cmd_PrintDemoConsole(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_democonsole "); + return Plugin_Handled; + } + + new String:sMsg[1024]; + GetCmdArgString(sMsg, sizeof(sMsg)); + StripQuotes(sMsg); + + new bool:bSent = SourceTV_PrintToDemoConsole("%s", sMsg); + ReplyToCommand(client, "SourceTV printing to demo console (success %d): %s", bSent, sMsg); + return Plugin_Handled; +} + +public Action:Cmd_ExecuteStringCommand(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_botcmd "); + return Plugin_Handled; + } + + new String:sCmd[1024]; + GetCmdArgString(sCmd, sizeof(sCmd)); + StripQuotes(sCmd); + + new iSourceTV = SourceTV_GetBotIndex(); + if (!iSourceTV) + return Plugin_Handled; + FakeClientCommand(iSourceTV, sCmd); + ReplyToCommand(client, "SourceTV executing command on bot: %s", sCmd); + return Plugin_Handled; +} + +public Action:Cmd_KickClient(client, args) +{ + if (args < 1) + { + ReplyToCommand(client, "Usage: sm_speckick "); + return Plugin_Handled; + } + + new String:sIndex[16], String:sMsg[1024]; + GetCmdArg(1, sIndex, sizeof(sIndex)); + StripQuotes(sIndex); + GetCmdArg(2, sMsg, sizeof(sMsg)); + StripQuotes(sMsg); + + new iTarget = StringToInt(sIndex); + SourceTV_KickClient(iTarget, sMsg); + ReplyToCommand(client, "SourceTV kicking spectator %d with reason %s", iTarget, sMsg); + return Plugin_Handled; +} \ No newline at end of file diff --git a/sourcetvmanager.games.txt b/sourcetvmanager.games.txt new file mode 100644 index 0000000..52724d9 --- /dev/null +++ b/sourcetvmanager.games.txt @@ -0,0 +1,46 @@ +"Games" +{ + "csgo" + { + "Addresses" + { + "host_client" + { + "windows" + { + "signature" "host_client" + "read" "58" + } + "linux" + { + "signature" "host_client" + } + } + } + "Offsets" + { + "BroadcastPrintf" + { + "windows" "39" + "linux" "40" + "mac" "40" + } + + "CHLTVServer::m_DemoRecorder" + { + "windows" "19600" + "linux" "20496" + } + } + "Signatures" + { + "host_client" + { + "library" "engine" + "linux" "@host_client" + // ping(CCommand const&) "Client ping times:\n" + "windows" "\x55\x8B\xEC\x83\xE4\xC0\x83\xEC\x34\x83\x3D\x2A\x2A\x2A\x2A\x01\x53\x56\x57\x75\x2A" + } + } + } +} \ No newline at end of file diff --git a/sourcetvmanager.inc b/sourcetvmanager.inc new file mode 100644 index 0000000..c07f846 --- /dev/null +++ b/sourcetvmanager.inc @@ -0,0 +1,81 @@ +#if defined _stvmngr_included + #endinput +#endif +#define _stvmngr_included + +native SourceTV_GetHLTVServerCount(); +native SourceTV_SelectHLTVServer(instance); +native SourceTV_GetSelectedHLTVServer(); +native SourceTV_GetBotIndex(); +native bool:SourceTV_GetLocalStats(&proxies, &slots, &specs); +native SourceTV_GetBroadcastTick(); +native Float:SourceTV_GetDelay(); +native bool:SourceTV_BroadcastHintMessage(const String:format[], any:...); +native bool:SourceTV_BroadcastConsoleMessage(const String:format[], any:...); +// get current view entity (PVS), 0 if coords are used +native SourceTV_GetViewEntity(); +// get current PVS origin +native SourceTV_GetViewCoordinates(Float:view[3]); + +native bool:SourceTV_StartRecording(const String:sFilename[]); +native bool:SourceTV_StopRecording(); +native bool:SourceTV_IsRecording(); +native bool:SourceTV_GetDemoFileName(String:sFilename[], maxlen); +native SourceTV_GetRecordingTick(); +native bool:SourceTV_PrintToDemoConsole(const String:format[], any:...); + +native SourceTV_GetSpectatorCount(); +native SourceTV_GetMaxClients(); +native SourceTV_GetClientCount(); +native bool:SourceTV_IsClientConnected(client); +native SourceTV_GetSpectatorName(client, String:name[], maxlen); +native SourceTV_KickClient(client, const String:sReason[]); + +forward SourceTV_OnStartRecording(hltvinstance, const String:filename[], bool:bContinuously); +forward SourceTV_OnStopRecording(hltvinstance, const String:filename[], recordingtick); + +/** + * Do not edit below this line! + */ +public Extension:__ext_stvmngr = +{ + name = "SourceTV Manager", + file = "sourcetvmanager.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_stvmngr_SetNTVOptional() +{ + MarkNativeAsOptional("SourceTV_GetHLTVServerCount"); + MarkNativeAsOptional("SourceTV_SelectHLTVServer"); + MarkNativeAsOptional("SourceTV_GetSelectedHLTVServer"); + MarkNativeAsOptional("SourceTV_GetBotIndex"); + MarkNativeAsOptional("SourceTV_GetLocalStats"); + MarkNativeAsOptional("SourceTV_GetBroadcastTick"); + MarkNativeAsOptional("SourceTV_GetDelay"); + MarkNativeAsOptional("SourceTV_SendHintMessage"); + MarkNativeAsOptional("SourceTV_BroadcastConsoleMessage"); + MarkNativeAsOptional("SourceTV_StartRecording"); + MarkNativeAsOptional("SourceTV_StopRecording"); + MarkNativeAsOptional("SourceTV_IsRecording"); + MarkNativeAsOptional("SourceTV_GetDemoFileName"); + MarkNativeAsOptional("SourceTV_GetRecordingTick"); + MarkNativeAsOptional("SourceTV_PrintBotConsole"); + MarkNativeAsOptional("SourceTV_GetSpectatorCount"); + MarkNativeAsOptional("SourceTV_GetMaxClients"); + MarkNativeAsOptional("SourceTV_GetClientCount"); + MarkNativeAsOptional("SourceTV_IsClientConnected"); + MarkNativeAsOptional("SourceTV_GetSpectatorName"); + MarkNativeAsOptional("SourceTV_KickClient"); +} +#endif