diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 08d91514..5e712f61 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -17,6 +17,21 @@ CHalfLife2 g_HL2; +namespace SourceHook +{ + template<> + int HashFunction(datamap_t * const &k) + { + return reinterpret_cast(k); + } + + template<> + int Compare(datamap_t * const &k1, datamap_t * const &k2) + { + return (k1-k2); + } +} + CHalfLife2::CHalfLife2() { m_pClasses = sm_trie_create(); @@ -36,6 +51,18 @@ CHalfLife2::~CHalfLife2() } m_Tables.clear(); + + THash::iterator h_iter; + for (h_iter=m_Maps.begin(); h_iter!=m_Maps.end(); h_iter++) + { + if (h_iter->val.trie) + { + sm_trie_destroy(h_iter->val.trie); + h_iter->val.trie = NULL; + } + } + + m_Maps.clear(); } CSharedEdictChangeInfo *g_pSharedChangeInfo = NULL; @@ -85,6 +112,31 @@ SendProp *UTIL_FindInSendTable(SendTable *pTable, const char *name) return NULL; } +typedescription_t *UTIL_FindInDataMap(datamap_t *pMap, const char *name) +{ + while (pMap) + { + for (int i=0; idataNumFields; i++) + { + if (strcmp(name, pMap->dataDesc[i].fieldName) == 0) + { + return &(pMap->dataDesc[i]); + } + if (pMap->dataDesc[i].td) + { + typedescription_t *_td; + if ((_td=UTIL_FindInDataMap(pMap->dataDesc[i].td, name)) != NULL) + { + return _td; + } + } + } + pMap = pMap->baseMap; + } + + return NULL; +} + ServerClass *CHalfLife2::FindServerClass(const char *classname) { DataTableInfo *pInfo = _FindServerClass(classname); @@ -111,6 +163,7 @@ DataTableInfo *CHalfLife2::_FindServerClass(const char *classname) pInfo->lookup = sm_trie_create(); pInfo->sc = sc; sm_trie_insert(m_pClasses, classname, pInfo); + m_Tables.push_back(pInfo); break; } sc = sc->m_pNext; @@ -133,7 +186,7 @@ SendProp *CHalfLife2::FindInSendTable(const char *classname, const char *offset) return NULL; } - SendProp *pProp; + SendProp *pProp = NULL; if (!sm_trie_retrieve(pInfo->lookup, offset, (void **)&pProp)) { if ((pProp = UTIL_FindInSendTable(pInfo->sc->m_pTable, offset)) != NULL) @@ -144,3 +197,23 @@ SendProp *CHalfLife2::FindInSendTable(const char *classname, const char *offset) return pProp; } + +typedescription_t *CHalfLife2::FindInDataMap(datamap_t *pMap, const char *offset) +{ + typedescription_t *td = NULL; + DataMapTrie &val = m_Maps[pMap]; + + if (!val.trie) + { + val.trie = sm_trie_create(); + } + if (!sm_trie_retrieve(val.trie, offset, (void **)&td)) + { + if ((td = UTIL_FindInDataMap(pMap, offset)) != NULL) + { + sm_trie_insert(val.trie, offset, td); + } + } + + return td; +} diff --git a/core/HalfLife2.h b/core/HalfLife2.h index dc3ec36b..0f7df3b2 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -16,10 +16,12 @@ #define _INCLUDE_SOURCEMOD_CHALFLIFE2_H_ #include +#include #include "sm_trie.h" #include "sm_globals.h" #include "dt_send.h" #include "server_class.h" +#include "datamap.h" using namespace SourceHook; @@ -29,6 +31,12 @@ struct DataTableInfo Trie *lookup; }; +struct DataMapTrie +{ + DataMapTrie() : trie(NULL) {} + Trie *trie; +}; + class CHalfLife2 : public SMGlobalClass { public: @@ -40,11 +48,13 @@ public: public: SendProp *FindInSendTable(const char *classname, const char *offset); ServerClass *FindServerClass(const char *classname); + typedescription_t *FindInDataMap(datamap_t *pMap, const char *offset); private: DataTableInfo *_FindServerClass(const char *classname); private: Trie *m_pClasses; List m_Tables; + THash m_Maps; }; extern CHalfLife2 g_HL2; diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 12569f78..8f4e9ca9 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -31,11 +31,15 @@ PlayerManager::PlayerManager() { m_AuthQueue = NULL; m_FirstPass = true; + + m_UserIdLookUp = new int[USHRT_MAX]; + memset(m_UserIdLookUp, 0, sizeof(int) * USHRT_MAX); } PlayerManager::~PlayerManager() { delete [] m_AuthQueue; + delete [] m_UserIdLookUp; } void PlayerManager::OnSourceModAllInitialized() @@ -212,6 +216,8 @@ bool PlayerManager::OnClientConnect(edict_t *pEntity, const char *pszName, const RETURN_META_VALUE(MRES_SUPERCEDE, false); } + m_UserIdLookUp[engine->GetPlayerUserId(pEntity)] = client; + return true; } @@ -353,6 +359,7 @@ void PlayerManager::OnClientDisconnect(edict_t *pEntity) } m_Players[client].Disconnect(); + m_UserIdLookUp[engine->GetPlayerUserId(pEntity)] = 0; } void PlayerManager::OnClientDisconnect_Post(edict_t *pEntity) @@ -425,6 +432,11 @@ int PlayerManager::GetNumPlayers() return m_PlayerCount; } +int PlayerManager::GetClientOfUserId(int userid) +{ + return (userid < 0 || userid > USHRT_MAX) ? 0 : m_UserIdLookUp[userid]; +} + void PlayerManager::AddClientListener(IClientListener *listener) { m_hooks.push_back(listener); diff --git a/core/PlayerManager.h b/core/PlayerManager.h index ac4ebb90..56811de2 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -95,6 +95,7 @@ public: //IPlayerManager IGamePlayer *GetGamePlayer(edict_t *pEdict); int GetMaxClients(); int GetNumPlayers(); + int GetClientOfUserId(int userid); public: inline int MaxClients() { @@ -117,6 +118,7 @@ private: IForward *m_clauth; IForward *m_onActivate; CPlayer *m_Players; + int *m_UserIdLookUp; int m_maxClients; int m_PlayerCount; bool m_FirstPass; diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index 3126cad1..78cab260 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -1,7 +1,7 @@ (&pThisPtr); + void **vtable = *reinterpret_cast(pThisPtr); + void *vfunc = vtable[offset]; + + union + { + datamap_t *(VEmptyClass::*mfpnew)(); +#ifndef PLATFORM_POSIX + void *addr; + } u; + u.addr = vfunc; +#else + struct + { + void *addr; + intptr_t adjustor; + } s; + } u; + u.s.addr = vfunc; + u.s.adjustor = 0; +#endif + + return (datamap_t *)(reinterpret_cast(this_ptr)->*u.mfpnew)(); +} + +inline datamap_t *CBaseEntity_GetDataDescMap(CBaseEntity *pEntity) +{ + int offset; + + if (!g_pGameConf->GetOffset("GetDataDescMap", &offset) || !offset) + { + return NULL; + } + + return VGetDataDescMap(pEntity, offset); +} + static cell_t GetMaxEntities(IPluginContext *pContext, const cell_t *params) { return gpGlobals->maxEntities; @@ -494,6 +535,33 @@ static cell_t FindSendPropOffs(IPluginContext *pContext, const cell_t *params) return pSend->GetOffset(); } +static cell_t FindDataMapOffs(IPluginContext *pContext, const cell_t *params) +{ + CBaseEntity *pEntity; + datamap_t *pMap; + typedescription_t *td; + char *offset; + edict_t *pEdict = GetEntity(params[1], &pEntity); + + if (!pEdict || !pEntity) + { + return pContext->ThrowNativeError("Entity %d is invalid", params[1]); + } + + if ((pMap=CBaseEntity_GetDataDescMap(pEntity)) == NULL) + { + return pContext->ThrowNativeError("Unable to retrieve GetDataDescMap offset"); + } + + pContext->LocalToString(params[2], &offset); + if ((td=g_HL2.FindInDataMap(pMap, offset)) == NULL) + { + return -1; + } + + return td->fieldOffset[TD_OFFSET_NORMAL]; +} + REGISTER_NATIVES(entityNatives) { {"ChangeEdictState", ChangeEdictState}, @@ -517,5 +585,6 @@ REGISTER_NATIVES(entityNatives) {"SetEntDataEnt", SetEntDataEnt}, {"SetEntDataFloat", SetEntDataFloat}, {"SetEntDataVector", SetEntDataVector}, + {"FindDataMapOffs", FindDataMapOffs}, {NULL, NULL} }; diff --git a/core/smn_halflife.cpp b/core/smn_halflife.cpp index e9a2bf5e..833dc0e0 100644 --- a/core/smn_halflife.cpp +++ b/core/smn_halflife.cpp @@ -16,6 +16,18 @@ #include "sourcemod.h" #include "sourcemm_api.h" #include "PlayerManager.h" +#include "HandleSys.h" + +IServerPluginCallbacks *g_VSP = NULL; + +class HalfLifeNatives : public SMGlobalClass +{ +public: //SMGlobalClass + void OnSourceModVSPReceived(IServerPluginCallbacks *iface) + { + g_VSP = iface; + } +}; static cell_t SetRandomSeed(IPluginContext *pContext, const cell_t *params) { @@ -229,6 +241,36 @@ static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t smn_CreateDialog(IPluginContext *pContext, const cell_t *params) +{ + KeyValues *pKV; + HandleError herr; + Handle_t hndl = static_cast(params[2]); + CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]); + + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid player", params[1]); + } + + if (!pPlayer->IsConnected()) + { + return pContext->ThrowNativeError("Player %d is not connected", params[1]); + } + + pKV = g_SourceMod.ReadKeyValuesHandle(hndl, &herr, true); + if (herr != HandleError_None) + { + return pContext->ThrowNativeError("Invalid key value handle %x (error %d)", hndl, herr); + } + + serverpluginhelpers->CreateMessage(pPlayer->GetEdict(), static_cast(params[3]), pKV, g_VSP); + + return 1; +} + +static HalfLifeNatives s_HalfLifeNatives; + REGISTER_NATIVES(halflifeNatives) { {"CreateFakeClient", CreateFakeClient}, @@ -252,5 +294,6 @@ REGISTER_NATIVES(halflifeNatives) {"PrecacheSound", PrecacheSound}, {"IsSoundPrecached", IsSoundPrecached}, {"FakeClientCommand", FakeClientCommand}, + {"CreateDialog", smn_CreateDialog}, {NULL, NULL}, }; diff --git a/core/smn_keyvalues.cpp b/core/smn_keyvalues.cpp index 745756f1..d0aae516 100644 --- a/core/smn_keyvalues.cpp +++ b/core/smn_keyvalues.cpp @@ -48,6 +48,33 @@ public: } }; +KeyValues *SourceModBase::ReadKeyValuesHandle(Handle_t hndl, HandleError *err, bool root) +{ + HandleError herr; + HandleSecurity sec; + KeyValueStack *pStk; + + sec.pOwner = NULL; + sec.pIdentity = g_pCoreIdent; + + if ((herr=g_HandleSys.ReadHandle(hndl, g_KeyValueType, &sec, (void **)&pStk)) + != HandleError_None) + { + if (err) + { + *err = herr; + } + return NULL; + } + + if (err) + { + *err = HandleError_None; + } + + return (root) ? pStk->pBase : pStk->pCurRoot.front(); +} + static cell_t smn_KvSetString(IPluginContext *pCtx, const cell_t *params) { Handle_t hndl = static_cast(params[1]); diff --git a/core/smn_player.cpp b/core/smn_player.cpp index 6f0a055b..042b21d8 100644 --- a/core/smn_player.cpp +++ b/core/smn_player.cpp @@ -15,6 +15,7 @@ #include "PlayerManager.h" #include "AdminCache.h" #include "sm_stringutil.h" +#include static cell_t sm_GetClientCount(IPluginContext *pCtx, const cell_t *params) { @@ -637,6 +638,182 @@ static cell_t GetHealth(IPluginContext *pContext, const cell_t *params) return pInfo->GetHealth(); } +static cell_t GetTimeConnected(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetTimeConnected()); +} + +static cell_t GetDataRate(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return pInfo->GetDataRate(); +} + +static cell_t IsTimingOut(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return pInfo->IsTimingOut() ? 1 : 0; +} + +static cell_t GetLatency(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetLatency(params[2])); +} + +static cell_t GetAvgLatency(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetAvgLatency(params[2])); +} + +static cell_t GetAvgLoss(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetAvgLoss(params[2])); +} + +static cell_t GetAvgChoke(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetAvgChoke(params[2])); +} + +static cell_t GetAvgData(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetAvgData(params[2])); +} + +static cell_t GetAvgPackets(IPluginContext *pContext, const cell_t *params) +{ + int client = params[1]; + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(client); + if (!pPlayer) + { + return pContext->ThrowNativeError("Player %d is not a valid client", client); + } else if (!pPlayer->IsInGame()) { + return pContext->ThrowNativeError("Player %d is not in game", client); + } else if (pPlayer->IsFakeClient()) { + return pContext->ThrowNativeError("Player %d is a bot", client); + } + + INetChannelInfo *pInfo = engine->GetPlayerNetInfo(client); + + return sp_ftoc(pInfo->GetAvgPackets(params[2])); +} + +static cell_t GetClientOfUserId(IPluginContext *pContext, const cell_t *params) +{ + return g_Players.GetClientOfUserId(params[1]); +} + REGISTER_NATIVES(playernatives) { {"AddUserFlags", AddUserFlags}, @@ -668,5 +845,15 @@ REGISTER_NATIVES(playernatives) {"GetClientWeapon", GetWeaponName}, {"GetClientModel", GetModelName}, {"GetClientHealth", GetHealth}, + {"GetTimeConnected", GetTimeConnected}, + {"GetDataRate", GetDataRate}, + {"IsTimingOut", IsTimingOut}, + {"GetLatency", GetLatency}, + {"GetAvgLatency", GetAvgLatency}, + {"GetAvgLoss", GetAvgLoss}, + {"GetAvgChoke", GetAvgChoke}, + {"GetAvgData", GetAvgData}, + {"GetAvgPackets", GetAvgPackets}, + {"GetClientOfUserId", GetClientOfUserId}, {NULL, NULL} }; diff --git a/core/sourcemod.h b/core/sourcemod.h index a2ca1f84..f7a80ac3 100644 --- a/core/sourcemod.h +++ b/core/sourcemod.h @@ -93,6 +93,7 @@ public: // ISourceMod IDataPack *CreateDataPack(); void FreeDataPack(IDataPack *pack); HandleType_t GetDataPackHandleType(bool readonly=false); + KeyValues *ReadKeyValuesHandle(Handle_t hndl, HandleError *err=NULL, bool root=false); private: /** * @brief Loading plugins diff --git a/plugins/include/clients.inc b/plugins/include/clients.inc index bfcfa590..f2f169d4 100644 --- a/plugins/include/clients.inc +++ b/plugins/include/clients.inc @@ -18,6 +18,13 @@ #endif #define _clients_included +enum NetFlow +{ + NetFlow_Outgoing = 0, /**< Outgoing traffic */ + NetFlow_Incoming, /**< Incoming traffic */ + NetFlow_Both /**< Incoming and outgoing traffic */ +}; + /** * Called on client connection. * @@ -380,3 +387,110 @@ native GetClientDeaths(client); * @error Invalid client index, client not in game, or no mod support. */ native GetClientFrags(client); + +/** + * Returns the client's send data rate in bytes/sec. + * + * @param client Player's index. + * @return Data rate. + * @error Invalid client index, client not in game, or fake client. + */ +native GetDataRate(client); + +/** + * Returns if a client is timing out + * + * @param client Player's index. + * @return True if client is timing out, false otherwise. + * @error Invalid client index, client not in game, or fake client. + */ +native bool:IsTimingOut(client); + +/** + * Returns the client's connection time in seconds. + * + * @param client Player's index. + * @return Connection time. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetTimeConnected(client); + +/** + * Returns the client's current latency (RTT), more accurate than GetAvgLatency but jittering. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Latency. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetLatency(client, NetFlow:flow); + +/** + * Returns the client's average packet latency in seconds. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Average latency. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetAvgLatency(client, NetFlow:flow); + +/** + * Returns the client's average packet loss, values go from 0 to 1 (for percentages). + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Average packet loss. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetAvgLoss(client, NetFlow:flow); + +/** + * Returns the client's average packet choke, values go from 0 to 1 (for percentages). + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Average packet choke. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetAvgChoke(client, NetFlow:flow); + +/** + * Returns the client's data flow in bytes/sec. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Data flow. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetAvgData(client, NetFlow:flow); + +/** + * Returns the client's average packet frequency in packets/sec. + * + * @param client Player's index. + * @param flow Traffic flowing direction. + * @return Packet frequency. + * @error Invalid client index, client not in game, or fake client. + */ +native Float:GetAvgPackets(client, NetFlow:flow); + +/** + * Translates an userid index to the real player index. + * + * @param userid Userid value. + * @return Client value. + * @error Returns 0 if invalid userid. + */ +native GetClientOfUserId(userid); + +/** + * Executes a client command on the server without being networked. + * + * @param client Index of the client. + * @param fmt Format of the client command. + * @param ... Format parameters + * @noreturn + * @error Invalid client index, or client not connected. + */ +native FakeClientCommand(client, const String:fmt[], any:...); diff --git a/plugins/include/entity.inc b/plugins/include/entity.inc index 986503ac..f2e0eced 100644 --- a/plugins/include/entity.inc +++ b/plugins/include/entity.inc @@ -289,6 +289,16 @@ native SetEntDataVector(entity, offset, const Float:vec[3], bool:changeState=fal */ native FindSendPropOffs(const String:cls[], const String:prop[]); +/** + * Given an entity, finds a datamap property offset. + * This information is cached for future calls. + * + * @param entity Entity index. + * @param prop Property name. + * @return An offset, or -1 on failure. + */ +native FindDataMapOffs(entity, const String:prop[]); + /** * Wrapper function for finding a send property for a particular entity. * @@ -320,11 +330,29 @@ stock GetEntSendPropOffs(ent, const String:prop[]) */ stock GetEntProp(entity, PropType:type, const String:prop[], size=4) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return GetEntData(entity, offs, size); } @@ -340,11 +368,29 @@ stock GetEntProp(entity, PropType:type, const String:prop[], size=4) */ stock SetEntProp(entity, PropType:type, const String:prop[], value, size=4) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return SetEntData(entity, offs, value, size, true); } @@ -359,11 +405,29 @@ stock SetEntProp(entity, PropType:type, const String:prop[], value, size=4) */ stock Float:GetEntPropFloat(entity, PropType:type, const String:prop[]) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return GetEntDataFloat(entity, offs); } @@ -379,11 +443,29 @@ stock Float:GetEntPropFloat(entity, PropType:type, const String:prop[]) */ stock SetEntPropFloat(entity, PropType:type, const String:prop[], Float:value) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return SetEntDataFloat(entity, offs, value, true); } @@ -398,11 +480,29 @@ stock SetEntPropFloat(entity, PropType:type, const String:prop[], Float:value) */ stock GetEntPropEnt(entity, PropType:type, const String:prop[]) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return GetEntDataEnt(entity, offs); } @@ -418,11 +518,29 @@ stock GetEntPropEnt(entity, PropType:type, const String:prop[]) */ stock SetEntPropEnt(entity, PropType:type, const String:prop[], other) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return SetEntDataEnt(entity, offs, other, true); } @@ -440,11 +558,29 @@ stock SetEntPropEnt(entity, PropType:type, const String:prop[], other) */ stock GetEntPropVector(entity, PropType:type, const String:prop[], Float:vec[3]) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return GetEntDataVector(entity, offs, vec); } @@ -462,10 +598,28 @@ stock GetEntPropVector(entity, PropType:type, const String:prop[], Float:vec[3]) */ stock SetEntPropVector(entity, PropType:type, const String:prop[], const Float:vec[3]) { - new offs = GetEntSendPropOffs(entity, prop); + new offs; + + switch (type) + { + case Prop_Send: + { + offs = GetEntSendPropOffs(entity, prop); + } + case Prop_Data: + { + offs = FindDataMapOffs(entity, prop); + } + default: + { + ThrowError("Invalid Property type %d", type); + } + } + if (offs == -1) { ThrowError("Property \"%s\" not found for entity %d", prop, entity); } + return SetEntDataVector(entity, offs, vec, true); } diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 4fca185f..e1949353 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -45,6 +45,14 @@ struct Plugin #include #include +enum DialogType +{ + DialogType_Msg = 0, /**< just an on screen message */ + DialogType_Menu, /**< an options menu */ + DialogType_Text, /**< a richtext dialog */ + DialogType_Entry /**< an entry box */ +}; + /** * Declare this as a struct in your plugin to expose its information. * Example: @@ -388,15 +396,15 @@ native bool:PrecacheSound(const String:sound[], bool:preload=false); native bool:IsSoundPrecached(const String:sound[]); /** - * Executes a client command on the server without being networked. + * Creates different types of ingame messages. * * @param client Index of the client. - * @param fmt Format of the client command. - * @param ... Format parameters + * @param kv KeyValues handle to set the menu keys and options. (Check iserverplugin.h for more information). + * @param type Message type to display ingame. * @noreturn * @error Invalid client index, or client not connected. */ -native FakeClientCommand(client, const String:fmt[], any:...); +native CreateDialog(client, Handle:kv, DialogType:type); #include #include diff --git a/public/IForwardSys.h b/public/IForwardSys.h index 478670aa..ab3d553b 100644 --- a/public/IForwardSys.h +++ b/public/IForwardSys.h @@ -229,13 +229,13 @@ namespace SourceMod virtual bool AddFunction(IPluginContext *ctx, funcid_t index) =0; /** - * @brief Removes a function from the call list. - * NOTE: Only removes one instance. - * - * @param ctx Context to use as a look-up. - * @param index Function id to add. - * @return Whether or not the function was removed. - */ + * @brief Removes a function from the call list. + * NOTE: Only removes one instance. + * + * @param ctx Context to use as a look-up. + * @param index Function id to add. + * @return Whether or not the function was removed. + */ virtual bool RemoveFunction(IPluginContext *ctx, funcid_t index) =0; }; diff --git a/public/IPlayerHelpers.h b/public/IPlayerHelpers.h index 1d120a9a..4fed6ef8 100644 --- a/public/IPlayerHelpers.h +++ b/public/IPlayerHelpers.h @@ -244,6 +244,14 @@ namespace SourceMod * @return Current number of connected clients. */ virtual int GetNumPlayers() =0; + + /** + * @brief Returns the client index by its userid. + * + * @param userid Userid of the client. + * @return Client index, or 0 if invalid userid passed. + */ + virtual int GetClientOfUserId(int userid) =0; }; } diff --git a/public/ISourceMod.h b/public/ISourceMod.h index d9e3b270..aca741d9 100644 --- a/public/ISourceMod.h +++ b/public/ISourceMod.h @@ -23,13 +23,18 @@ * @brief Defines miscellanious helper functions useful to extensions. */ -#include +#include #include #include #define SMINTERFACE_SOURCEMOD_NAME "ISourceMod" #define SMINTERFACE_SOURCEMOD_VERSION 1 +/** +* @brief Forward declaration of the KeyValues class. +*/ +class KeyValues; + namespace SourceMod { /** @@ -133,18 +138,29 @@ namespace SourceMod */ virtual void FreeDataPack(IDataPack *pack) =0; - /** - * @brief Returns the automated data pack handle type. - * - * The readonly data type is the parent of the writable type. - * Note that calling CloseHandle() on either type will release the data pack. - * The readonly type is inheritable, but due to limitations of the Handle System, - * the writable type is not. - * - * @param readonly If true, the readonly type will be returned. - * @return The Handle type for storing generic data packs. - */ + /** + * @brief Returns the automated data pack handle type. + * + * The readonly data type is the parent of the writable type. + * Note that calling CloseHandle() on either type will release the data pack. + * The readonly type is inheritable, but due to limitations of the Handle System, + * the writable type is not. + * + * @param readonly If true, the readonly type will be returned. + * @return The Handle type for storing generic data packs. + */ virtual HandleType_t GetDataPackHandleType(bool readonly=false) =0; + + /** + * @brief Retrieves a KeyValues pointer from a handle. + * + * @param hndl Handle_t from which to retrieve contents. + * @param err Optional address to store a possible handle error. + * @param root If true it will return the root KeyValues pointer for the whole structure. + * + * @return The KeyValues pointer, or NULL for any error encountered. + */ + virtual KeyValues *ReadKeyValuesHandle(Handle_t hndl, HandleError *err=NULL, bool root=false) =0; }; }