From e51143d05acaf8048403f1c8291542d4ed62476a Mon Sep 17 00:00:00 2001 From: Peace-Maker Date: Mon, 29 Feb 2016 09:59:09 +0100 Subject: [PATCH] Initial commit of project files CSGO only yet --- AMBuildScript | 448 ++++++++++++++++++++++++++++++++++ AMBuilder | 35 +++ PackageScript | 50 ++++ configure.py | 23 ++ extension.cpp | 257 ++++++++++++++++++++ extension.h | 152 ++++++++++++ forwards.cpp | 94 ++++++++ forwards.h | 59 +++++ ihltvdemorecorder.h | 33 +++ natives.cpp | 492 ++++++++++++++++++++++++++++++++++++++ smsdk_config.h | 81 +++++++ sourcetv_test.sp | 250 +++++++++++++++++++ sourcetvmanager.games.txt | 46 ++++ sourcetvmanager.inc | 81 +++++++ 14 files changed, 2101 insertions(+) create mode 100644 AMBuildScript create mode 100644 AMBuilder create mode 100644 PackageScript create mode 100644 configure.py create mode 100644 extension.cpp create mode 100644 extension.h create mode 100644 forwards.cpp create mode 100644 forwards.h create mode 100644 ihltvdemorecorder.h create mode 100644 natives.cpp create mode 100644 smsdk_config.h create mode 100644 sourcetv_test.sp create mode 100644 sourcetvmanager.games.txt create mode 100644 sourcetvmanager.inc 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