diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index ac1fc427..b6f37ca5 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -90,6 +90,7 @@ PlayerManager::PlayerManager() { m_AuthQueue = NULL; m_FirstPass = false; + m_maxClients = 0; m_UserIdLookUp = new int[USHRT_MAX+1]; memset(m_UserIdLookUp, 0, sizeof(int) * (USHRT_MAX+1)); diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index c3efbe2c..3c5985a0 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -1424,6 +1424,10 @@ RelativePath="..\smn_handles.cpp" > + + diff --git a/core/sm_globals.h b/core/sm_globals.h index b761fabf..42fa6b15 100644 --- a/core/sm_globals.h +++ b/core/sm_globals.h @@ -85,12 +85,19 @@ public: } /** - * @brief Called after all global classes have initialized + * @brief Called after all global classes have been started up */ virtual void OnSourceModAllInitialized() { } + /** + * @brief Called after all global classes have initialized + */ + virtual void OnSourceModAllInitialized_Post() + { + } + /** * @brief Called when SourceMod is shutting down */ diff --git a/core/smn_hudtext.cpp b/core/smn_hudtext.cpp new file mode 100644 index 00000000..50d1923a --- /dev/null +++ b/core/smn_hudtext.cpp @@ -0,0 +1,475 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2007 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 "sm_globals.h" +#include "GameConfigs.h" +#include "UserMessages.h" +#include "TimerSys.h" +#include "PlayerManager.h" +#include "HandleSys.h" + +#define MAX_HUD_CHANNELS 6 + +int g_HudMsgNum = -1; + +struct hud_syncobj_t +{ + int *player_channels; +}; + +struct player_chaninfo_t +{ + double chan_times[MAX_HUD_CHANNELS]; + hud_syncobj_t *chan_syncobjs[MAX_HUD_CHANNELS]; +}; + +struct hud_text_parms +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +}; + +class HudMsgHelpers : + public SMGlobalClass, + public IHandleTypeDispatch, + public IClientListener +{ +public: + bool IsSupported() + { + return (g_HudMsgNum != -1); + } + + virtual void OnSourceModAllInitialized_Post() + { + const char *key; + + key = g_pGameConf->GetKeyValue("HudTextMsg"); + if (key != NULL) + { + g_HudMsgNum = g_UserMsgs.GetMessageIndex(key); + } + + if (!IsSupported()) + { + m_hHudSyncObj = 0; + m_PlayerHuds = NULL; + return; + } + + m_PlayerHuds = new player_chaninfo_t[256+1]; + m_hHudSyncObj = g_HandleSys.CreateType("HudSyncObj", this, 0, NULL, NULL, g_pCoreIdent, NULL); + + g_Players.AddClientListener(this); + } + + virtual void OnSourceModShutdown() + { + if (!IsSupported()) + { + return; + } + + delete [] m_PlayerHuds; + g_HandleSys.RemoveType(m_hHudSyncObj, g_pCoreIdent); + + g_Players.RemoveClientListener(this); + } + + virtual void OnHandleDestroy(HandleType_t type, void *object) + { + hud_syncobj_t *obj = (hud_syncobj_t *)object; + + delete [] obj->player_channels; + delete obj; + } + + virtual bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize) + { + *pSize = sizeof(unsigned int) * g_Players.GetMaxClients(); + return true; + } + + virtual void OnClientConnected(int client) + { + player_chaninfo_t *player; + + player = &m_PlayerHuds[client]; + memset(player->chan_syncobjs, 0, sizeof(player->chan_syncobjs)); + memset(player->chan_times, 0, sizeof(player->chan_times)); + } + + Handle_t CreateHudSyncObj(IdentityToken_t *pIdent) + { + Handle_t hndl; + int max_clients; + HandleError err; + hud_syncobj_t *obj; + HandleSecurity sec; + + if ((max_clients = g_Players.MaxClients()) == 0) + { + max_clients = 256+1; + } + + obj = new hud_syncobj_t; + obj->player_channels = new int[max_clients]; + + memset(obj->player_channels, 0, sizeof(int) * max_clients); + + sec = HandleSecurity(pIdent, g_pCoreIdent); + + if ((hndl = g_HandleSys.CreateHandleEx(m_hHudSyncObj, obj, &sec, NULL, &err)) + == BAD_HANDLE) + { + delete [] obj->player_channels; + delete obj; + } + + return hndl; + } + + HandleError ReadHudSyncObj(Handle_t hndl, + IdentityToken_t *pOwner, + hud_syncobj_t **pObj) + { + HandleSecurity sec(pOwner, g_pCoreIdent); + return g_HandleSys.ReadHandle(hndl, m_hHudSyncObj, &sec, (void **)pObj); + } + + unsigned int AutoSelectChannel(unsigned int client) + { + int last_channel; + player_chaninfo_t *player; + + player = &m_PlayerHuds[client]; + + last_channel = 0; + for (unsigned int i = 1; i < MAX_HUD_CHANNELS; i++) + { + if (player->chan_times[i] < player->chan_times[last_channel]) + { + last_channel = i; + } + } + + ManualSelectChannel(client, last_channel); + + return last_channel; + } + + int TryReuseLastChannel(unsigned int client, hud_syncobj_t *obj) + { + int last_channel; + player_chaninfo_t *player; + + player = &m_PlayerHuds[client]; + + /* First, see if we can re-use the previous channel. */ + last_channel = obj->player_channels[client]; + + if (player->chan_syncobjs[last_channel] == obj) + { + player->chan_times[last_channel] = *g_pUniversalTime; + return last_channel; + } + + return -1; + } + + int AutoSelectChannel(unsigned int client, hud_syncobj_t *obj) + { + int last_channel; + player_chaninfo_t *player; + + player = &m_PlayerHuds[client]; + + /* First, see if we can re-use the previous channel. */ + last_channel = obj->player_channels[client]; + + if (player->chan_syncobjs[last_channel] == obj) + { + player->chan_times[last_channel] = *g_pUniversalTime; + return last_channel; + } + + last_channel = 0; + for (unsigned int i = 1; i < MAX_HUD_CHANNELS; i++) + { + if (player->chan_times[i] < player->chan_times[last_channel]) + { + last_channel = i; + } + } + + obj->player_channels[client] = last_channel; + player->chan_syncobjs[last_channel] = obj; + player->chan_times[last_channel] = *g_pUniversalTime; + + return last_channel; + } + + int ManualSelectChannel(unsigned int client, int channel) + { + player_chaninfo_t *player; + + player = &m_PlayerHuds[client]; + player->chan_times[channel] = *g_pUniversalTime; + player->chan_syncobjs[channel] = NULL; + + return channel; + } +private: + HandleType_t m_hHudSyncObj; + player_chaninfo_t *m_PlayerHuds; +} s_HudMsgHelpers; + +hud_text_parms g_hud_params; + +static cell_t CreateHudSynchronizer(IPluginContext *pContext, const cell_t *params) +{ + return s_HudMsgHelpers.CreateHudSyncObj(pContext->GetIdentity()); +} + +static cell_t SetHudTextParams(IPluginContext *pContext, const cell_t *params) +{ + g_hud_params.x = sp_ctof(params[1]); + g_hud_params.y = sp_ctof(params[2]); + g_hud_params.holdTime = sp_ctof(params[3]); + g_hud_params.r1 = static_cast(params[4]); + g_hud_params.g1 = static_cast(params[5]); + g_hud_params.b1 = static_cast(params[6]); + g_hud_params.a1 = static_cast(params[7]); + g_hud_params.effect = params[8]; + g_hud_params.fxTime = sp_ctof(params[9]); + g_hud_params.fadeinTime = sp_ctof(params[10]); + g_hud_params.fadeoutTime = sp_ctof(params[11]); + g_hud_params.r2 = 255; + g_hud_params.g2 = 255; + g_hud_params.b2 = 250; + g_hud_params.a2 = 0; + + return 1; +} + +static cell_t SetHudTextParamsEx(IPluginContext *pContext, const cell_t *params) +{ + cell_t *color1, *color2; + + pContext->LocalToPhysAddr(params[4], &color1); + pContext->LocalToPhysAddr(params[5], &color2); + + g_hud_params.x = sp_ctof(params[1]); + g_hud_params.y = sp_ctof(params[2]); + g_hud_params.holdTime = sp_ctof(params[3]); + g_hud_params.r1 = static_cast(color1[0]); + g_hud_params.g1 = static_cast(color1[1]); + g_hud_params.b1 = static_cast(color1[2]); + g_hud_params.a1 = static_cast(color1[3]); + g_hud_params.effect = params[6]; + g_hud_params.fxTime = sp_ctof(params[7]); + g_hud_params.fadeinTime = sp_ctof(params[8]); + g_hud_params.fadeoutTime = sp_ctof(params[9]); + g_hud_params.r2 = static_cast(color2[0]); + g_hud_params.g2 = static_cast(color2[1]); + g_hud_params.b2 = static_cast(color2[2]); + g_hud_params.a2 = static_cast(color2[3]); + + return 1; +} + +void UTIL_SendHudText(int client, const hud_text_parms &textparms, const char *pMessage) +{ + bf_write *bf; + cell_t players[1]; + + players[0] = client; + + bf = g_UserMsgs.StartMessage(g_HudMsgNum, players, 1, 0); + bf->WriteByte(textparms.channel & 0xFF ); + bf->WriteFloat(textparms.x); + bf->WriteFloat(textparms.y); + bf->WriteByte(textparms.r1); + bf->WriteByte(textparms.g1); + bf->WriteByte(textparms.b1); + bf->WriteByte(textparms.a1); + bf->WriteByte(textparms.r2); + bf->WriteByte(textparms.g2); + bf->WriteByte(textparms.b2); + bf->WriteByte(textparms.a2); + bf->WriteByte(textparms.effect); + bf->WriteFloat(textparms.fadeinTime); + bf->WriteFloat(textparms.fadeoutTime); + bf->WriteFloat(textparms.holdTime); + bf->WriteFloat(textparms.fxTime); + bf->WriteString(pMessage); + g_UserMsgs.EndMessage(); +} + +static cell_t ShowSyncHudText(IPluginContext *pContext, const cell_t *params) +{ + int client; + Handle_t err; + CPlayer *pPlayer; + hud_syncobj_t *obj; + char message_buffer[255-36]; + + if (!s_HudMsgHelpers.IsSupported()) + { + return -1; + } + + if ((err = s_HudMsgHelpers.ReadHudSyncObj(params[2], pContext->GetIdentity(), &obj)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[2], err); + } + + client = params[1]; + if ((pPlayer = g_Players.GetPlayerByIndex(client)) == NULL) + { + return pContext->ThrowNativeError("Invalid client index %d", client); + } + else if (!pPlayer->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in-game", client); + } + + g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3); + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + + g_hud_params.channel = s_HudMsgHelpers.AutoSelectChannel(client, obj); + UTIL_SendHudText(client, g_hud_params, message_buffer); + + return 1; +} + +static cell_t ClearSyncHud(IPluginContext *pContext, const cell_t *params) +{ + int client; + int channel; + Handle_t err; + CPlayer *pPlayer; + hud_syncobj_t *obj; + + if (!s_HudMsgHelpers.IsSupported()) + { + return -1; + } + + if ((err = s_HudMsgHelpers.ReadHudSyncObj(params[2], pContext->GetIdentity(), &obj)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[2], err); + } + + client = params[1]; + if ((pPlayer = g_Players.GetPlayerByIndex(client)) == NULL) + { + return pContext->ThrowNativeError("Invalid client index %d", client); + } + else if (!pPlayer->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in-game", client); + } + + if ((channel = s_HudMsgHelpers.TryReuseLastChannel(client, obj)) == -1) + { + return -1; + } + + g_hud_params.channel = channel; + UTIL_SendHudText(client, g_hud_params, ""); + + return g_hud_params.channel; +} + +static cell_t ShowHudText(IPluginContext *pContext, const cell_t *params) +{ + int client; + CPlayer *pPlayer; + char message_buffer[255-36]; + + if (!s_HudMsgHelpers.IsSupported()) + { + return -1; + } + + client = params[1]; + if ((pPlayer = g_Players.GetPlayerByIndex(client)) == NULL) + { + return pContext->ThrowNativeError("Invalid client index %d", client); + } + else if (!pPlayer->IsInGame()) + { + return pContext->ThrowNativeError("Client %d is not in-game", client); + } + + g_SourceMod.FormatString(message_buffer, sizeof(message_buffer), pContext, params, 3); + if (pContext->GetContext()->n_err != SP_ERROR_NONE) + { + return 0; + } + + if (params[2] == -1) + { + g_hud_params.channel = s_HudMsgHelpers.AutoSelectChannel(client); + } + else + { + g_hud_params.channel = params[2] % MAX_HUD_CHANNELS; + s_HudMsgHelpers.ManualSelectChannel(client, g_hud_params.channel); + } + + UTIL_SendHudText(client, g_hud_params, message_buffer); + + return g_hud_params.channel; +} + +REGISTER_NATIVES(hudNatives) +{ + {"ClearSyncHud", ClearSyncHud}, + {"CreateHudSynchronizer", CreateHudSynchronizer}, + {"SetHudTextParams", SetHudTextParams}, + {"SetHudTextParamsEx", SetHudTextParamsEx}, + {"ShowHudText", ShowHudText}, + {"ShowSyncHudText", ShowSyncHudText}, + {NULL, NULL}, +}; diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index 5a44e775..695e9933 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -302,6 +302,14 @@ void SourceModBase::StartSourceMod(bool late) pBase = pBase->m_pGlobalClassNext; } + /* Notify! */ + pBase = SMGlobalClass::head; + while (pBase) + { + pBase->OnSourceModAllInitialized_Post(); + pBase = pBase->m_pGlobalClassNext; + } + /* Add us now... */ g_ShareSys.AddInterface(NULL, this); diff --git a/gamedata/core.games.txt b/gamedata/core.games.txt index f598813a..2d91a614 100644 --- a/gamedata/core.games.txt +++ b/gamedata/core.games.txt @@ -63,6 +63,24 @@ "HudRadioMenuMsg" "ShowMenu" } } + + /** + * Which games support HudMsg? + */ + "#default" + { + "#supported" + { + "game" "hl2mp" + "game" "sourceforts" + "game" "tf" + } + + "Keys" + { + "HudTextMsg" "HudMsg" + } + } /** * Which games use an extra byte in the HintText diff --git a/plugins/include/halflife.inc b/plugins/include/halflife.inc index c9f1fbba..35fd7f74 100644 --- a/plugins/include/halflife.inc +++ b/plugins/include/halflife.inc @@ -353,21 +353,162 @@ stock PrintHintTextToAll(const String:format[], any:...) * Shows a VGUI panel to a specific client. * * @param client Client index. - * @param name Panel type name (Check viewport_panel_names.h to see a list of some panel names). - * @param Kv KeyValues handle with all the data for the panel setup (Depends on the panel type and may be unused). + * @param name Panel type name (Check viewport_panel_names.h to see a list of + * some panel names). + * @param Kv KeyValues handle with all the data for the panel setup (Depends + * on the panel type and may be unused). * @param show True to show the panel, or false to remove it from the client screen. * @noreturn * @error If the client is not connected an error will be thrown. */ native ShowVGUIPanel(client, const String:name[], Handle:Kv=INVALID_HANDLE, bool:show=true); +/** + * Creates a HUD synchronization object. This object is used to automatically assign and + * re-use channels for a set of messages. + * + * The HUD has a hardcoded number of channels (usually 6) for displaying + * text. You can use any channel for any area of the screen. Text on + * different channels can overlap, but text on the same channel will + * erase the old text first. This overlapping and overwriting gets problematic. + * + * A HUD synchronization object automatically selects channels for you based on + * the following heuristics: + * - If channel X was last used by the object, and hasn't been modified again, + * channel X gets re-used. + * - Otherwise, a new channel is chosen based on the least-recently-used channel. + * + * This ensures that if you display text on a sync object, that the previous text + * displayed on it will always be cleared first. This is because your new text + * will either overwrite the old text on the same channel, or because another + * channel has already erased your text. + * + * Note that messages can still overlap if they are on different synchronization + * objects, or they are displayed to manual channels. + * + * These are particularly useful for displaying repeating or refreshing HUD text, in + * addition to displaying multiple message sets in one area of the screen (for example, + * center-say messages that may pop up randomly that you don't want to overlap each + * other). + * + * @return New HUD synchronization object. + * The Handle can be closed with CloseHandle(). + * If HUD text is not supported on this mod, then + * INVALID_HANDLE is returned. + */ +native Handle:CreateHudSynchronizer(); + +/** + * Sets the HUD parameters for drawing text. These parameters are stored + * globally, although nothing other than this function and SetHudTextParamsEx + * modify them. + * + * You must call this function before drawing text. If you are drawing + * text to multiple clients, you can set the parameters once, since + * they won't be modified. However, as soon as you pass control back + * to other plugins, you must reset the parameters next time you draw. + * + * @param x x coordinate, from 0 to 1. -1.0 is the center. + * @param y y coordinate, from 0 to 1. -1.0 is the center. + * @param holdTime Number of seconds to hold the text. + * @param r Red color value. + * @param g Green color value. + * @param b Blue color value. + * @param a Alpha transparency value. + * @param effect 0/1 causes the text to fade in and fade out. + * 2 causes the text to flash[?]. + * @param fxTime Duration of chosen effect (may not apply to all effects). + * @param fadeIn Number of seconds to spend fading in. + * @param fadeOut Number of seconds to spend fading out. + * @noreturn + */ +native SetHudTextParams(Float:x, Float:y, Float:holdTime, r, g, b, a, effect = 0, + Float:fxTime=6.0, Float:fadeIn=0.1, Float:fadeOut=0.2); + +/** + * Sets the HUD parameters for drawing text. These parameters are stored + * globally, although nothing other than this function and SetHudTextParams + * modify them. + * + * This is the same as SetHudTextParams(), except it lets you set the alternate + * color for when effects require it. + * + * @param x x coordinate, from 0 to 1. -1.0 is the center. + * @param y y coordinate, from 0 to 1. -1.0 is the center. + * @param holdTime Number of seconds to hold the text. + * @param color1 First color set, array values being [red, green, blue, alpha] + * @param color2 Second color set, array values being [red, green, blue, alpha] + * @param effect 0/1 causes the text to fade in and fade out. + * 2 causes the text to flash[?]. + * @param fxTime Duration of chosen effect (may not apply to all effects). + * @param fadeIn Number of seconds to spend fading in. + * @param fadeOut Number of seconds to spend fading out. + * @noreturn + */ +native SetHudTextParamsEx(Float:x, Float:y, Float:holdTime, color1[4], + color2[4]={255,255,255,0}, effect = 0, Float:fxTime=6.0, + Float:fadeIn=0.1, Float:fadeOut=0.2); + +/** + * Shows a synchronized HUD message to a client. + * + * As of this writing, only TF, HL2MP, and SourceForts support HUD Text. + * + * @param client Client index to send the message to. + * @param sync Synchronization object. + * @param message Message text or formatting rules. + * @param ... Message formatting parameters. + * @return -1 on failure, anything else on success. + * This function fails if the mod does not support it. + * @error Client not in-game, or sync object not valid. + */ +native ShowSyncHudText(client, Handle:sync, const String:message[], any:...); + +/** + * Clears the text on a synchronized HUD channel. + * + * This is not the same as sending "" because it guarantees that it won't + * overwrite text on another channel. For example, consider the scenario: + * + * 1. Your synchronized message goes to channel 3. + * 2. Someone else's non-synchronized message goes to channel 3. + * + * If you were to simply send "" on your synchronized message, + * then someone else's text could be overwritten. + * + * @param client Client index to send the message to. + * @param sync Synchronization object. + * @noreturn + * @error Client not in-game, or sync object not valid. + */ +native ClearSyncHud(client, Handle:sync); + +/** + * Shows a HUD message to a client on the given channel. + * + * As of this writing, only TF, HL2MP, and SourceForts support HUD Text. + * + * @param client Client index to send the message to. + * @param channel A channel number. + * If -1, then a channel will automatically be selected + * based on the least-recently-used channel. If the + * channel is any other number, it will be modulo'd with + * the channel count to get a final channel number. + * @param message Message text or formatting rules. + * @param ... Message formatting parameters. + * @return -1 on failure (lack of mod support). + * Any other return value is the channel number that was + * used to render the text. + */ +native ShowHudText(client, channel, const String:message[], any:...); + /** * Shows a MOTD panel to a specific client. * * @param client Client index. * @param title Title of the panel (printed on the top border of the window). * @param msg Contents of the panel, it can be treated as an url, filename or plain text - * depending on the type parameter (WARNING: msg has to be 192 bytes maximum!) + * depending on the type parameter (WARNING: msg has to be 192 bytes maximum!) * @param type Determines the way to treat the message body of the panel. * @noreturn * @error If the client is not connected an error will be thrown.