From 4521f80e5a0881ecb4d21f35730ee9c7bbf576f0 Mon Sep 17 00:00:00 2001 From: Nicholas Hastings Date: Wed, 6 Jul 2011 17:37:35 -0400 Subject: [PATCH] Added ability in SDKTools to get/set prop values on gamerules class (bug 4983, r=fyren). --- extensions/sdktools/AMBuilder | 1 + extensions/sdktools/Makefile | 2 +- extensions/sdktools/extension.cpp | 5 + extensions/sdktools/gamerulesnatives.cpp | 609 ++++++++++++++++++ extensions/sdktools/gamerulesnatives.h | 44 ++ extensions/sdktools/msvc10/sdktools.vcxproj | 2 + .../sdktools/msvc10/sdktools.vcxproj.filters | 6 + extensions/sdktools/vglobals.cpp | 37 ++ extensions/sdktools/vglobals.h | 2 + extensions/sdktools/vhelpers.cpp | 38 +- gamedata/sdktools.games/engine.l4d.txt | 2 + gamedata/sdktools.games/engine.l4d2.txt | 2 + gamedata/sdktools.games/game.alienswarm.txt | 5 + gamedata/sdktools.games/game.cstrike.txt | 2 + gamedata/sdktools.games/game.dinodday.txt | 5 + gamedata/sdktools.games/game.dod.txt | 5 + gamedata/sdktools.games/game.tf.txt | 5 + plugins/include/sdktools.inc | 1 + plugins/include/sdktools_gamerules.inc | 194 ++++++ plugins/testsuite/gamerules-props.sp | 129 ++++ 20 files changed, 1058 insertions(+), 38 deletions(-) create mode 100644 extensions/sdktools/gamerulesnatives.cpp create mode 100644 extensions/sdktools/gamerulesnatives.h create mode 100644 plugins/include/sdktools_gamerules.inc create mode 100644 plugins/testsuite/gamerules-props.sp diff --git a/extensions/sdktools/AMBuilder b/extensions/sdktools/AMBuilder index a5f1f52d..6affcad6 100644 --- a/extensions/sdktools/AMBuilder +++ b/extensions/sdktools/AMBuilder @@ -39,6 +39,7 @@ for i in SM.sdkInfo: 'voice.cpp', 'vsound.cpp', 'hooks.cpp', + 'gamerulesnatives.cpp', 'vstringtable.cpp', 'CDetour/detours.cpp', 'sdk/smsdk_ext.cpp', diff --git a/extensions/sdktools/Makefile b/extensions/sdktools/Makefile index 3f86af0b..fa74e69e 100644 --- a/extensions/sdktools/Makefile +++ b/extensions/sdktools/Makefile @@ -22,7 +22,7 @@ USEMETA = true OBJECTS = sdk/smsdk_ext.cpp extension.cpp vdecoder.cpp vcallbuilder.cpp vcaller.cpp \ vnatives.cpp vsound.cpp tenatives.cpp trnatives.cpp tempents.cpp vstringtable.cpp \ vhelpers.cpp vglobals.cpp voice.cpp inputnatives.cpp teamnatives.cpp output.cpp \ - outputnatives.cpp hooks.cpp CDetour/detours.cpp asm/asm.c + outputnatives.cpp hooks.cpp gamerulesnatives.cpp CDetour/detours.cpp asm/asm.c ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### diff --git a/extensions/sdktools/extension.cpp b/extensions/sdktools/extension.cpp index 1d89fb0e..b05b633a 100644 --- a/extensions/sdktools/extension.cpp +++ b/extensions/sdktools/extension.cpp @@ -40,6 +40,7 @@ #include "vsound.h" #include "output.h" #include "hooks.h" +#include "gamerulesnatives.h" #include /** @@ -84,6 +85,7 @@ extern sp_nativeinfo_t g_StringTableNatives[]; extern sp_nativeinfo_t g_VoiceNatives[]; extern sp_nativeinfo_t g_EntInputNatives[]; extern sp_nativeinfo_t g_TeamNatives[]; +extern sp_nativeinfo_t g_GameRulesNatives[]; static void InitSDKToolsAPI(); @@ -107,6 +109,7 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->AddNatives(myself, g_EntInputNatives); sharesys->AddNatives(myself, g_TeamNatives); sharesys->AddNatives(myself, g_EntOutputNatives); + sharesys->AddNatives(myself, g_GameRulesNatives); SM_GET_IFACE(GAMEHELPERS, g_pGameHelpers); @@ -154,6 +157,8 @@ bool SDKTools::SDK_OnLoad(char *error, size_t maxlength, bool late) GetIServer(); + GameRulesNativesInit(); + InitSDKToolsAPI(); return true; diff --git a/extensions/sdktools/gamerulesnatives.cpp b/extensions/sdktools/gamerulesnatives.cpp new file mode 100644 index 00000000..23346313 --- /dev/null +++ b/extensions/sdktools/gamerulesnatives.cpp @@ -0,0 +1,609 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2011 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 "gamerulesnatives.h" +#include "vglobals.h" + +const char *g_szGameRulesProxy; + +void GameRulesNativesInit() +{ + g_szGameRulesProxy = g_pGameConf->GetKeyValue("GameRulesProxy"); +} + +static CBaseEntity *FindEntityByNetClass(int start, const char *classname) +{ + int maxEntities = gpGlobals->maxEntities; + for (int i = start; i < maxEntities; i++) + { + edict_t *current = gamehelpers->EdictOfIndex(i); + if (current == NULL) + continue; + + IServerNetworkable *network = current->GetNetworkable(); + if (network == NULL) + continue; + + ServerClass *sClass = network->GetServerClass(); + const char *name = sClass->GetName(); + + if (!strcmp(name, classname)) + return gamehelpers->ReferenceToEntity(gamehelpers->IndexOfEdict(current)); + } + + return NULL; +} + +static CBaseEntity* GetGameRulesProxyEnt() +{ + static cell_t proxyEntRef = -1; + CBaseEntity *pProxy; + if (proxyEntRef == -1 || (pProxy = gamehelpers->ReferenceToEntity(proxyEntRef)) == NULL) + { + pProxy = FindEntityByNetClass(playerhelpers->GetMaxClients(), g_szGameRulesProxy); + proxyEntRef = gamehelpers->EntityToReference(pProxy); + } + + return pProxy; +} + +enum PropFieldType +{ + PropField_Unsupported, /**< The type is unsupported. */ + PropField_Integer, /**< Valid for SendProp and Data fields */ + PropField_Float, /**< Valid for SendProp and Data fields */ + PropField_Entity, /**< Valid for Data fields only (SendProp shows as int) */ + PropField_Vector, /**< Valid for SendProp and Data fields */ + PropField_String, /**< Valid for SendProp and Data fields */ + PropField_String_T, /**< Valid for Data fields. Read only! */ +}; + +#define FIND_PROP_SEND(type, type_name) \ + sm_sendprop_info_t info;\ + if (!gamehelpers->FindSendPropInfo(g_szGameRulesProxy, prop, &info)) \ + { \ + return pContext->ThrowNativeError("Property \"%s\" not found on the gamerules proxy", prop); \ + } \ + \ + offset = info.actual_offset; \ + bit_count = info.prop->m_nBits; \ + \ + switch (info.prop->GetType()) \ + { \ + case type: \ + { \ + if (element > 0) \ + { \ + return pContext->ThrowNativeError("SendProp %s is not an array. Element %d is invalid.", \ + prop, \ + element); \ + } \ + break; \ + } \ + case DPT_DataTable: \ + { \ + SendProp *pProp; \ + FIND_PROP_SEND_IN_SENDTABLE(info, pProp, element, type, type_name); \ + \ + offset += pProp->GetOffset(); \ + bit_count = pProp->m_nBits; \ + break; \ + } \ + default: \ + { \ + return pContext->ThrowNativeError("SendProp %s type is not " type_name " (%d != %d)", \ + prop, \ + info.prop->GetType(), \ + type); \ + } \ + } \ + +#define FIND_PROP_SEND_IN_SENDTABLE(info, pProp, element, type, type_name) \ + SendTable *pTable = info.prop->GetDataTable(); \ + if (!pTable) \ + { \ + return pContext->ThrowNativeError("Error looking up DataTable for prop %s", \ + prop); \ + } \ + \ + int elementCount = pTable->GetNumProps(); \ + if (element >= elementCount) \ + { \ + return pContext->ThrowNativeError("Element %d is out of bounds (Prop %s has %d elements).", \ + element, \ + prop, \ + elementCount); \ + } \ + \ + pProp = pTable->GetProp(element); \ + if (pProp->GetType() != type) \ + { \ + return pContext->ThrowNativeError("SendProp %s type is not " type_name " ([%d,%d] != %d)", \ + prop, \ + info.prop->GetType(), \ + info.prop->m_nBits, \ + type); \ + } + +static cell_t GameRules_GetProp(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[3]; + int offset; + int bit_count; + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + int elementCount = 1; + + FIND_PROP_SEND(DPT_Int, "integer"); + if (bit_count < 1) + { + bit_count = params[2] * 8; + } + + void *pGameRules = *g_pGameRules; + + if (bit_count >= 17) + { + return *(int32_t *)((intptr_t)pGameRules + offset); + } + else if (bit_count >= 9) + { + return *(int16_t *)((intptr_t)pGameRules + offset); + } + else if (bit_count >= 2) + { + return *(int8_t *)((intptr_t)pGameRules + offset); + } + else + { + return *(bool *)((intptr_t)pGameRules + offset) ? 1 : 0; + } + + return -1; +} + +static cell_t GameRules_SetProp(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[4]; + int offset; + int bit_count; + + bool sendChange = true; + if (params[5] == 0) + sendChange = false; + + CBaseEntity *pProxy = NULL; + if (sendChange && ((pProxy = GetGameRulesProxyEnt()) == NULL)) + return pContext->ThrowNativeError("Couldn't find gamerules proxy entity"); + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed"); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Int, "integer"); + + void *pGameRules = *g_pGameRules; + + if (bit_count < 1) + { + bit_count = params[3] * 8; + } + + if (bit_count >= 17) + { + *(int32_t *)((intptr_t)pGameRules + offset) = params[2]; + if (sendChange) + { + *(int32_t *)((intptr_t)pProxy + offset) = params[2]; + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + } + else if (bit_count >= 9) + { + *(int16_t *)((intptr_t)pGameRules + offset) = (int16_t)params[2]; + if (sendChange) + { + *(int16_t *)((intptr_t)pProxy + offset) = (int16_t)params[2]; + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + } + else if (bit_count >= 2) + { + *(int8_t *)((intptr_t)pGameRules + offset) = (int8_t)params[2]; + if (sendChange) + { + *(int8_t *)((intptr_t)pProxy + offset) = (int8_t)params[2]; + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + } + else + { + *(bool *)((intptr_t)pGameRules + offset) = (params[2] == 0) ? false : true; + if (sendChange) + { + *(bool *)((intptr_t)pProxy + offset) = (params[2] == 0) ? false : true; + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + } + + return 0; +} + +static cell_t GameRules_GetPropFloat(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[2]; + int offset; + int bit_count; + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Float, "float"); + + void *pGameRules = *g_pGameRules; + + float val = *(float *)((intptr_t)pGameRules + offset); + + return sp_ftoc(val); +} + +static cell_t GameRules_SetPropFloat(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[3]; + int offset; + int bit_count; + + bool sendChange = true; + if (params[4] == 0) + sendChange = false; + + CBaseEntity *pProxy = NULL; + if (sendChange && ((pProxy = GetGameRulesProxyEnt()) == NULL)) + return pContext->ThrowNativeError("Couldn't find gamerules proxy entity."); + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Float, "float"); + + void *pGameRules = *g_pGameRules; + float newVal = sp_ctof(params[2]); + + *(float *)((intptr_t)pGameRules + offset) = newVal; + + if (sendChange) + { + *(float *)((intptr_t)pProxy + offset) = newVal; + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + + return 0; +} + +static cell_t GameRules_GetPropEnt(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[2]; + int offset; + int bit_count; + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Int, "Integer"); + + void *pGameRules = *g_pGameRules; + + CBaseHandle &hndl = *(CBaseHandle *)((intptr_t)pGameRules + offset); + + int ref = gamehelpers->IndexToReference(hndl.GetEntryIndex()); + return gamehelpers->ReferenceToBCompatRef(ref); +} + +static cell_t GameRules_SetPropEnt(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[3]; + int offset; + int bit_count; + + bool sendChange = true; + if (params[4] == 0) + sendChange = false; + + CBaseEntity *pProxy = NULL; + if (sendChange && ((pProxy = GetGameRulesProxyEnt()) == NULL)) + return pContext->ThrowNativeError("Couldn't find gamerules proxy entity."); + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Int, "integer"); + + void *pGameRules = *g_pGameRules; + + CBaseHandle &hndl = *(CBaseHandle *)((intptr_t)pGameRules + offset); + CBaseEntity *pOther; + + if (params[2] == -1) + { + hndl.Set(NULL); + } + else + { + pOther = gamehelpers->ReferenceToEntity(params[2]); + + if (!pOther) + { + return pContext->ThrowNativeError("Entity %d (%d) is invalid", gamehelpers->ReferenceToIndex(params[4]), params[4]); + } + + IHandleEntity *pHandleEnt = (IHandleEntity *)pOther; + hndl.Set(pHandleEnt); + } + + if (sendChange) + { + CBaseHandle &hndl = *(CBaseHandle *)((intptr_t)pProxy + offset); + if (params[2] == -1) + { + hndl.Set(NULL); + } + else + { + IHandleEntity *pHandleEnt = (IHandleEntity *)pOther; + hndl.Set(pHandleEnt); + } + + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + + return 0; +} + +static cell_t GameRules_GetPropVector(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[3]; + int offset; + int bit_count; + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Vector, "vector"); + + void *pGameRules = *g_pGameRules; + + Vector *v = (Vector *)((intptr_t)pGameRules + offset); + + cell_t *vec; + pContext->LocalToPhysAddr(params[2], &vec); + + vec[0] = sp_ftoc(v->x); + vec[1] = sp_ftoc(v->y); + vec[2] = sp_ftoc(v->z); + + return 1; +} + +static cell_t GameRules_SetPropVector(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int element = params[3]; + int offset; + int bit_count; + + bool sendChange = true; + if (params[4] == 0) + sendChange = false; + + CBaseEntity *pProxy = NULL; + if (sendChange && ((pProxy = GetGameRulesProxyEnt()) == NULL)) + return pContext->ThrowNativeError("Couldn't find gamerules proxy entity."); + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + FIND_PROP_SEND(DPT_Vector, "vector"); + + void *pGameRules = *g_pGameRules; + + Vector *v = (Vector *)((intptr_t)pGameRules + offset); + + cell_t *vec; + pContext->LocalToPhysAddr(params[2], &vec); + + v->x = sp_ctof(vec[0]); + v->y = sp_ctof(vec[1]); + v->z = sp_ctof(vec[2]); + + if (sendChange) + { + v = (Vector *)((intptr_t)g_pGameRules + offset); + v->x = sp_ctof(vec[0]); + v->y = sp_ctof(vec[1]); + v->z = sp_ctof(vec[2]); + + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + + return 1; +} + +static cell_t GameRules_GetPropString(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int offset; + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + sm_sendprop_info_t info; + if (!gamehelpers->FindSendPropInfo(g_szGameRulesProxy, prop, &info)) + { + return pContext->ThrowNativeError("Property \"%s\" not found on the gamerules proxy", prop); + } + + offset = info.actual_offset; + + if (info.prop->GetType() != DPT_String) + { + return pContext->ThrowNativeError("SendProp %s type is not a string (%d != %d)", + prop, + info.prop->GetType(), + DPT_String); + } + + void *pGameRules = *g_pGameRules; + + size_t len; + const char *src; + + src = (char *)((intptr_t)pGameRules + offset); + + pContext->StringToLocalUTF8(params[2], params[3], src, &len); + + return len; +} + +// From sm_stringutil +inline int strncopy(char *dest, const char *src, size_t count) +{ + if (!count) + { + return 0; + } + + char *start = dest; + while ((*src) && (--count)) + { + *dest++ = *src++; + } + *dest = '\0'; + + return (dest - start); +} +// + +static cell_t GameRules_SetPropString(IPluginContext *pContext, const cell_t *params) +{ + char *prop; + int offset; + int maxlen; + + bool sendChange = true; + if (params[3] == 0) + sendChange = false; + + CBaseEntity *pProxy = NULL; + if (sendChange && ((pProxy = GetGameRulesProxyEnt()) == NULL)) + return pContext->ThrowNativeError("Couldn't find gamerules proxy entity."); + + if (!g_pGameRules || !g_szGameRulesProxy || !strcmp(g_szGameRulesProxy, "")) + return pContext->ThrowNativeError("Gamerules lookup failed."); + + pContext->LocalToString(params[1], &prop); + + sm_sendprop_info_t info; + if (!gamehelpers->FindSendPropInfo(g_szGameRulesProxy, prop, &info)) + { + return pContext->ThrowNativeError("Property \"%s\" not found on the gamerules proxy", prop); + } + + offset = info.actual_offset; + + if (info.prop->GetType() != DPT_String) + { + return pContext->ThrowNativeError("SendProp %s type is not a string (%d != %d)", + prop, + info.prop->GetType(), + DPT_String); + } + + void *pGameRules = *g_pGameRules; + maxlen = DT_MAX_STRING_BUFFERSIZE; + + char *src; + char *dest = (char *)((intptr_t)pGameRules + offset); + + pContext->LocalToString(params[2], &src); + size_t len = strncopy(dest, src, maxlen); + + if (sendChange) + { + dest = (char *)((intptr_t)pProxy + offset); + strncopy(dest, src, maxlen); + gamehelpers->SetEdictStateChanged(gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy)), offset); + } + + return len; +} + +sp_nativeinfo_t g_GameRulesNatives[] = +{ + {"GameRules_GetProp", GameRules_GetProp}, + {"GameRules_SetProp", GameRules_SetProp}, + {"GameRules_GetPropFloat", GameRules_GetPropFloat}, + {"GameRules_SetPropFloat", GameRules_SetPropFloat}, + {"GameRules_GetPropEnt", GameRules_GetPropEnt}, + {"GameRules_SetPropEnt", GameRules_SetPropEnt}, + {"GameRules_GetPropVector", GameRules_GetPropVector}, + {"GameRules_SetPropVector", GameRules_SetPropVector}, + {"GameRules_GetPropString", GameRules_GetPropString}, + {"GameRules_SetPropString", GameRules_SetPropString}, + {NULL, NULL}, +}; + diff --git a/extensions/sdktools/gamerulesnatives.h b/extensions/sdktools/gamerulesnatives.h new file mode 100644 index 00000000..d465e2a1 --- /dev/null +++ b/extensions/sdktools/gamerulesnatives.h @@ -0,0 +1,44 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod SDKTools Extension + * Copyright (C) 2004-2011 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_GRNATIVES_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_GRNATIVES_H_ + +/** + * @file gamerulesnatives.h + * @brief SDK Tools extension gamerules natives header. + */ + +void GameRulesNativesInit(); + +extern sp_nativeinfo_t g_GameRulesNatives[]; + +#endif //_INCLUDE_SOURCEMOD_EXTENSION_GRNATIVES_H_ diff --git a/extensions/sdktools/msvc10/sdktools.vcxproj b/extensions/sdktools/msvc10/sdktools.vcxproj index a0e6f277..40da1978 100644 --- a/extensions/sdktools/msvc10/sdktools.vcxproj +++ b/extensions/sdktools/msvc10/sdktools.vcxproj @@ -841,6 +841,7 @@ + @@ -863,6 +864,7 @@ + diff --git a/extensions/sdktools/msvc10/sdktools.vcxproj.filters b/extensions/sdktools/msvc10/sdktools.vcxproj.filters index b53f280a..04d8107a 100644 --- a/extensions/sdktools/msvc10/sdktools.vcxproj.filters +++ b/extensions/sdktools/msvc10/sdktools.vcxproj.filters @@ -81,6 +81,9 @@ SourceMod SDK + + Source Files + @@ -125,5 +128,8 @@ SourceMod SDK + + Header Files + \ No newline at end of file diff --git a/extensions/sdktools/vglobals.cpp b/extensions/sdktools/vglobals.cpp index c72ddff3..5bdae558 100644 --- a/extensions/sdktools/vglobals.cpp +++ b/extensions/sdktools/vglobals.cpp @@ -182,3 +182,40 @@ void GetIServer() iserver = reinterpret_cast(addr); } #endif + +const char *GetDTTypeName(int type) +{ + switch (type) + { + case DPT_Int: + { + return "integer"; + } + case DPT_Float: + { + return "float"; + } + case DPT_Vector: + { + return "vector"; + } + case DPT_String: + { + return "string"; + } + case DPT_Array: + { + return "array"; + } + case DPT_DataTable: + { + return "datatable"; + } + default: + { + return NULL; + } + } + + return NULL; +} diff --git a/extensions/sdktools/vglobals.h b/extensions/sdktools/vglobals.h index d3783089..1fc817ee 100644 --- a/extensions/sdktools/vglobals.h +++ b/extensions/sdktools/vglobals.h @@ -38,4 +38,6 @@ extern void *g_EntList; void InitializeValveGlobals(); void GetIServer(); +const char *GetDTTypeName(int type); + #endif // _INCLUDE_SDKTOOLS_VGLOBALS_H_ diff --git a/extensions/sdktools/vhelpers.cpp b/extensions/sdktools/vhelpers.cpp index 30016ebd..03a43d74 100644 --- a/extensions/sdktools/vhelpers.cpp +++ b/extensions/sdktools/vhelpers.cpp @@ -32,6 +32,7 @@ #include "extension.h" #include "util.h" #include "vhelpers.h" +#include "vglobals.h" CallHelper s_Teleport; CallHelper s_GetVelocity; @@ -278,43 +279,6 @@ void ShutdownHelpers() s_EyeAngles.Shutdown(); } -const char *GetDTTypeName(int type) -{ - switch (type) - { - case DPT_Int: - { - return "integer"; - } - case DPT_Float: - { - return "float"; - } - case DPT_Vector: - { - return "vector"; - } - case DPT_String: - { - return "string"; - } - case DPT_Array: - { - return "array"; - } - case DPT_DataTable: - { - return "datatable"; - } - default: - { - return NULL; - } - } - - return NULL; -} - void UTIL_DrawSendTable_XML(FILE *fp, SendTable *pTable, int space_count) { char spaces[255]; diff --git a/gamedata/sdktools.games/engine.l4d.txt b/gamedata/sdktools.games/engine.l4d.txt index 362e1f70..2607929d 100644 --- a/gamedata/sdktools.games/engine.l4d.txt +++ b/gamedata/sdktools.games/engine.l4d.txt @@ -19,6 +19,8 @@ "SlapSoundCount" "2" "SlapSound1" "player/damage1.wav" "SlapSound2" "player/damage2.wav" + + "GameRulesProxy" "CTerrorGameRulesProxy" } } diff --git a/gamedata/sdktools.games/engine.l4d2.txt b/gamedata/sdktools.games/engine.l4d2.txt index 57388f5e..9fd1ef64 100644 --- a/gamedata/sdktools.games/engine.l4d2.txt +++ b/gamedata/sdktools.games/engine.l4d2.txt @@ -19,6 +19,8 @@ "SlapSoundCount" "2" "SlapSound1" "player/damage1.wav" "SlapSound2" "player/damage2.wav" + + "GameRulesProxy" "CTerrorGameRulesProxy" } } diff --git a/gamedata/sdktools.games/game.alienswarm.txt b/gamedata/sdktools.games/game.alienswarm.txt index fdc32667..9be83258 100644 --- a/gamedata/sdktools.games/game.alienswarm.txt +++ b/gamedata/sdktools.games/game.alienswarm.txt @@ -42,5 +42,10 @@ "windows" "442" } } + + "Keys" + { + "GameRulesProxy" "CAlienSwarmProxy" + } } } \ No newline at end of file diff --git a/gamedata/sdktools.games/game.cstrike.txt b/gamedata/sdktools.games/game.cstrike.txt index 0c4a8ba5..a8d1edb1 100644 --- a/gamedata/sdktools.games/game.cstrike.txt +++ b/gamedata/sdktools.games/game.cstrike.txt @@ -20,6 +20,8 @@ "SlapSound1" "player/damage1.wav" "SlapSound2" "player/damage2.wav" "SlapSound3" "player/damage3.wav" + + "GameRulesProxy" "CCSGameRulesProxy" } "Offsets" diff --git a/gamedata/sdktools.games/game.dinodday.txt b/gamedata/sdktools.games/game.dinodday.txt index 62bd5c18..422a428c 100644 --- a/gamedata/sdktools.games/game.dinodday.txt +++ b/gamedata/sdktools.games/game.dinodday.txt @@ -62,5 +62,10 @@ "windows" "442" } } + + "Keys" + { + "GameRulesProxy" "CDDDGameRulesProxy" + } } } \ No newline at end of file diff --git a/gamedata/sdktools.games/game.dod.txt b/gamedata/sdktools.games/game.dod.txt index edbc1e99..faf2200b 100644 --- a/gamedata/sdktools.games/game.dod.txt +++ b/gamedata/sdktools.games/game.dod.txt @@ -101,5 +101,10 @@ "mac" "408" } } + + "Keys" + { + "GameRulesProxy" "CDODGameRulesProxy" + } } } diff --git a/gamedata/sdktools.games/game.tf.txt b/gamedata/sdktools.games/game.tf.txt index 2f604be0..1f3dddba 100644 --- a/gamedata/sdktools.games/game.tf.txt +++ b/gamedata/sdktools.games/game.tf.txt @@ -101,5 +101,10 @@ "mac" "408" } } + + "Keys" + { + "GameRulesProxy" "CTFGameRulesProxy" + } } } diff --git a/plugins/include/sdktools.inc b/plugins/include/sdktools.inc index 6e7d7322..2125a99c 100644 --- a/plugins/include/sdktools.inc +++ b/plugins/include/sdktools.inc @@ -49,6 +49,7 @@ #include #include #include +#include enum SDKCallType { diff --git a/plugins/include/sdktools_gamerules.inc b/plugins/include/sdktools_gamerules.inc new file mode 100644 index 00000000..590d6f82 --- /dev/null +++ b/plugins/include/sdktools_gamerules.inc @@ -0,0 +1,194 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2011 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * 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$ + */ + +#if defined _sdktools_gamerules_included + #endinput +#endif +#define _sdktools_gamerules_included + +enum RoundState { + // initialize the game, create teams + RoundState_Init, + + //Before players have joined the game. Periodically checks to see if enough players are ready + //to start a game. Also reverts to this when there are no active players + RoundState_Pregame, + + //The game is about to start, wait a bit and spawn everyone + RoundState_StartGame, + + //All players are respawned, frozen in place + RoundState_Preround, + + //Round is on, playing normally + RoundState_RoundRunning, + + //Someone has won the round + RoundState_TeamWin, + + //Noone has won, manually restart the game, reset scores + RoundState_Restart, + + //Noone has won, restart the game + RoundState_Stalemate, + + //Game is over, showing the scoreboard etc + RoundState_GameOver, + + //Game is over, doing bonus round stuff + RoundState_Bonus, +}; + +/** + * Retrieves an integer value from a property of the gamerules entity. + * + * @param prop Property name. + * @param size Number of bytes to read (valid values are 1, 2, or 4). + * This value is auto-detected, and the size parameter is + * only used as a fallback in case detection fails. + * @param element Element # (starting from 0) if property is an array. + * @return Value at the given property offset. + * @error Not supported. + */ +native GameRules_GetProp(const String:prop[], size=4, element=0); + +/** + * Sets an integer value for a property of the gamerules entity. + * + * @param prop Property name. + * @param value Value to set. + * @param size Number of bytes to write (valid values are 1, 2, or 4). + * This value is auto-detected, and the size parameter is + * only used as a fallback in case detection fails. + * @param element Element # (starting from 0) if property is an array. + * @error Not supported. + * @noreturn + */ +native GameRules_SetProp(const String:prop[], any:value, size=4, element=0, bool:changeState=false); + +/** + * Retrieves a float value from a property of the gamerules entity. + * + * @param prop Property name. + * @param element Element # (starting from 0) if property is an array. + * @return Value at the given property offset. + * @error Not supported. + */ +native Float:GameRules_GetPropFloat(const String:prop[], element=0); + +/** + * Sets a float value for a property of the gamerules entity. + * + * @param prop Property name. + * @param value Value to set. + * @param element Element # (starting from 0) if property is an array. + * @noreturn + * @error Not supported. + */ +native GameRules_SetPropFloat(const String:prop[], Float:value, element=0, bool:changeState=false); + +/** + * Retrieves a entity index from a property of the gamerules entity. + * + * @param prop Property name. + * @param element Element # (starting from 0) if property is an array. + * @return Entity index at the given property. + * If there is no entity, or the entity is not valid, + * then -1 is returned. + * @error Not supported. + */ +native GameRules_GetPropEnt(const String:prop[], element=0); + +/** + * Sets an entity index for a property of the gamerules entity. + * + * @param prop Property name. + * @param other Entity index to set, or -1 to unset. + * @param element Element # (starting from 0) if property is an array. + * @noreturn + * @error Not supported. + */ +native GameRules_SetPropEnt(const String:prop[], other, element=0, bool:changeState=false); + +/** + * Retrieves a vector of floats from the gamerules entity, given a named network property. + * + * @param prop Property name. + * @param vec Vector buffer to store data in. + * @param element Element # (starting from 0) if property is an array. + * @noreturn + * @error Not supported. + */ +native GameRules_GetPropVector(const String:prop[], Float:vec[3], element=0); + +/** + * Sets a vector of floats in the gamerules entity, given a named network property. + * + * @param prop Property name. + * @param vec Vector to set. + * @param element Element # (starting from 0) if property is an array. + * @noreturn + * @error Not supported. + */ +native GameRules_SetPropVector(const String:prop[], const Float:vec[3], element=0, bool:changeState=false); + +/** + * Gets a gamerules property as a string. + * + * @param prop Property to use. + * @param buffer Destination string buffer. + * @param maxlen Maximum length of output string buffer. + * @return Number of non-null bytes written. + * @error Not supported. + */ +native GameRules_GetPropString(const String:prop[], String:buffer[], maxlen); + +/** + * Sets a gamerules property as a string. + * + * @param prop Property to use. + * @param buffer String to set. + * @return Number of non-null bytes written. + * @error Not supported. + */ +native GameRules_SetPropString(const String:prop[], const String:buffer[], bool:changeState=false); + +/** + * Gets the current round state. + * + * @return Round state. + * @error Game doesn't support round state. + */ +stock RoundState:GameRules_GetRoundState() +{ + return RoundState:GameRules_GetProp("m_iRoundState"); +} diff --git a/plugins/testsuite/gamerules-props.sp b/plugins/testsuite/gamerules-props.sp new file mode 100644 index 00000000..bff0346c --- /dev/null +++ b/plugins/testsuite/gamerules-props.sp @@ -0,0 +1,129 @@ +#include +#include + +enum Game { Game_TF, Game_SWARM } +new Game:g_Game; + +public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +{ + decl String:gamedir[64]; + GetGameFolderName(gamedir, sizeof(gamedir)); + if (!strcmp(gamedir, "tf")) + { + g_Game = Game_TF; + return APLRes_Success; + } + + if (!strcmp(gamedir, "swarm")) + { + g_Game = Game_SWARM; + return APLRes_Success; + } + + strcopy(error, err_max, "These tests are only supported on TF2 and Alien Swarm"); + return APLRes_Failure; +} + +public OnPluginStart() +{ + if (g_Game == Game_TF) + { + RegConsoleCmd("gr_getstate", gr_getstate); + RegConsoleCmd("gr_ttr", gr_ttr); + RegConsoleCmd("gr_tbs", gr_tbs); + RegConsoleCmd("gr_nextspawn", gr_nextspawn); + RegConsoleCmd("gr_settime", gr_settime); + RegConsoleCmd("gr_getgoal", gr_getgoal); + RegConsoleCmd("gr_setgoal", gr_setgoal); + } + else if (g_Game == Game_SWARM) + { + HookEvent("entity_killed", entity_killed); + RegConsoleCmd("gr_getstimmer", gr_getstimmer); + } +} + +public Action:gr_getstate(client, argc) +{ + ReplyToCommand(client, "Round state is %d", GameRules_GetRoundState()); + return Plugin_Handled; +} + +stock Float:GameRules_GetTimeUntilRndReset() +{ + new Float:flRestartTime = GameRules_GetPropFloat("m_flRestartRoundTime"); + if (flRestartTime == -1.0) + return flRestartTime; + + return flRestartTime - GetGameTime(); +} + +public Action:gr_ttr(client, argc) +{ + ReplyToCommand(client, "Time til restart is %.2f", GameRules_GetTimeUntilRndReset()); + return Plugin_Handled; +} + +public Action:gr_tbs(client, argc) +{ + ReplyToCommand(client, "Time between spawns for team2 is %.2f", GameRules_GetPropFloat("m_TeamRespawnWaveTimes", 2)); + return Plugin_Handled; +} + +public Action:gr_nextspawn(client, argc) +{ + ReplyToCommand(client, "Next spawn for team2 is %.2f", GameRules_GetPropFloat("m_flNextRespawnWave", 2)); + return Plugin_Handled; +} + +public Action:gr_settime(client, argc) +{ + GameRules_SetPropFloat("m_TeamRespawnWaveTimes", 2.0, 2, true); + return Plugin_Handled; +} + +public Action:gr_getgoal(client, argc) +{ + decl String:goal[64]; + GameRules_GetPropString("m_pszTeamGoalStringRed", goal, sizeof(goal)); + ReplyToCommand(client, "Red goal string is \"%s\"", goal); + return Plugin_Handled; +} + +public Action:gr_setgoal(client, argc) +{ + GameRules_SetPropString("m_pszTeamGoalStringRed", "urururur", true); + + decl String:goal[64]; + GameRules_GetPropString("m_pszTeamGoalStringRed", goal, sizeof(goal)); + ReplyToCommand(client, "Red goal string is \"%s\"", goal); + return Plugin_Handled; +} + +//.. DIE +public entity_killed(Handle:event, const String:name[], bool:dontBroadcast) +{ + new ent = GetEventInt(event, "entindex_killed"); + if (!IsValidEdict(ent)) + return; + + decl String:classname[64]; + GetEdictClassname(ent, classname, sizeof(classname)); + + if (!!strcmp(classname, "asw_marine")) + return; + + decl Float:deathvec[3]; + GameRules_GetPropVector("m_vMarineDeathPos", deathvec); + PrintToChatAll("Death vec is %.3f %.3f %.3f", deathvec[0], deathvec[1], deathvec[2]); +} + +// you can manually induce this with asw_PermaStim (needs sv_cheats) +public Action:gr_getstimmer(client, argc) +{ + ReplyToCommand(client, "%N (%.2f/%.2f)", + GameRules_GetPropEnt("m_hStartStimPlayer"), + GameRules_GetPropFloat("m_flStimStartTime"), + GameRules_GetPropFloat("m_flStimEndTime")); + return Plugin_Handled; +}