#pragma semicolon 1 #pragma newdecls required //#define DEBUG #define PLUGIN_VERSION "1.0" #include #include #include #include #include "jutils.inc" #include "l4d_survivor_identity_fix.inc" static ArrayList LasersUsed; static ConVar hLaserNotice, hFinaleTimer, hFFNotice, hMPGamemode; static int iFinaleStartTime, botDropMeleeWeapon[MAXPLAYERS+1]; static float OUT_OF_BOUNDS[3] = {0.0, -1000.0, 0.0}; //TODO: Drop melee on death AND clear on respawn by defib. //TODO: Auto increase abm_minplayers based on highest player count public Plugin myinfo = { name = "L4D2 Misc Tools", author = "Includes: Notice on laser use, Timer for gauntlet runs", description = "jackzmc", version = PLUGIN_VERSION, url = "" }; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { MarkNativeAsOptional("IdentityFix_SetPlayerModel"); return APLRes_Success; } public void OnPluginStart() { EngineVersion g_Game = GetEngineVersion(); if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { SetFailState("This plugin is for L4D/L4D2 only."); } LoadTranslations("common.phrases"); hLaserNotice = CreateConVar("sm_laser_use_notice", "1.0", "Enable notification of a laser box being used", FCVAR_NONE, true, 0.0, true, 1.0); hFinaleTimer = CreateConVar("sm_time_finale", "0.0", "Record the time it takes to complete finale. 0 -> OFF, 1 -> Gauntlets Only, 2 -> All finales", FCVAR_NONE, true, 0.0, true, 2.0); hFFNotice = CreateConVar("sm_ff_notice", "0.0", "Notify players if a FF occurs. 0 -> Disabled, 1 -> In chat, 2 -> In Hint text", FCVAR_NONE, true, 0.0, true, 2.0); hMPGamemode = FindConVar("mp_gamemode"); hFFNotice.AddChangeHook(CVC_FFNotice); if(hFFNotice.IntValue > 0) { HookEvent("player_hurt", Event_PlayerHurt); } LasersUsed = new ArrayList(1, 0); HookEvent("player_use", Event_PlayerUse); HookEvent("round_end", Event_RoundEnd); HookEvent("gauntlet_finale_start", Event_GauntletStart); HookEvent("finale_start", Event_FinaleStart); HookEvent("finale_vehicle_leaving", Event_FinaleEnd); HookEvent("player_bot_replace", Event_BotPlayerSwap); HookEvent("bot_player_replace", Event_BotPlayerSwap); AutoExecConfig(true, "l4d2_tools"); for(int client = 1; client < MaxClients; client++) { if(IsClientConnected(client) && IsClientInGame(client) && GetClientTeam(client) == 2) { if(IsFakeClient(client)) { SDKHook(client, SDKHook_WeaponDrop, Event_OnWeaponDrop); } } } HookUserMessage(GetUserMessageId("VGUIMenu"), VGUIMenu, true); RegAdminCmd("sm_model", Command_SetClientModel, ADMFLAG_ROOT); RegAdminCmd("sm_respawn_all", Command_RespawnAll, ADMFLAG_CHEATS, "Makes all dead players respawn in a closet"); RegConsoleCmd("sm_pmodels", Command_ListClientModels, "Lists all player's models"); } public void CVC_FFNotice(ConVar convar, const char[] oldValue, const char[] newValue) { if(convar.IntValue > 0) { HookEvent("player_hurt", Event_PlayerHurt); }else { UnhookEvent("player_hurt", Event_PlayerHurt); } } public Action Event_RoundEnd(Event event, const char[] name, bool dontBroadcast) { LasersUsed.Clear(); } public Action Command_RespawnAll(int client, int args) { L4D_CreateRescuableSurvivors(); } //TODO: Implement idle bot support public Action Command_ListClientModels(int client, int args) { char model[64]; for(int i = 1; i <= MaxClients; i++) { if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) { GetClientModel(i, model, sizeof(model)); ReplyToCommand(client, "%N's model: %s", i, model); } } } public Action Command_SetClientModel(int client, int args) { if(args < 1) { ReplyToCommand(client, "Usage: sm_model [keep]"); }else{ char arg1[32], arg2[16], arg3[8]; GetCmdArg(1, arg1, sizeof(arg1)); GetCmdArg(2, arg2, sizeof(arg2)); GetCmdArg(3, arg3, sizeof(arg3)); char modelPath[64]; int modelID = GetSurvivorId(arg2); if(modelID == -1) { ReplyToCommand(client, "Invalid survivor type entered. Case-sensitive, full name required."); return Plugin_Handled; } GetSurvivorModel(modelID, modelPath, sizeof(modelPath)); char target_name[MAX_TARGET_LENGTH]; int target_list[MAXPLAYERS], target_count; bool tn_is_ml; if ((target_count = ProcessTargetString( arg1, client, target_list, MAXPLAYERS, COMMAND_FILTER_CONNECTED, target_name, sizeof(target_name), tn_is_ml)) <= 0) { /* This function replies to the admin with a failure message */ ReplyToTargetError(client, target_count); return Plugin_Handled; } int target; for (int i = 0; i < target_count; i++) { int team = GetClientTeam(target_list[i]); target = target_list[i]; /*if(team == 1) { int bot = GetIdleBot(target); if(bot > -1) { target = bot; team = 2; } else { ReplyToCommand(client, "Player %N is spectating and is not idle.", target); return Plugin_Handled; } }*/ bool keepModel = StrEqual(arg3, "keep", false); if(IsClientConnected(target) && IsClientInGame(target) && IsClientInGame(target) && IsPlayerAlive(target) && team == 2 || team == 3) { SetEntProp(target, Prop_Send, "m_survivorCharacter", modelID); SetEntityModel(target, modelPath); if (IsFakeClient(target)) { char name[32]; GetSurvivorName(target, name, sizeof(name)); SetClientInfo(target, "name", name); } UpdatePlayerIdentity(target, view_as(modelID), keepModel); int weapon = GetEntPropEnt(client, Prop_Data, "m_hActiveWeapon"); if( weapon != -1 ) { DataPack pack = new DataPack(); pack.WriteCell(GetClientUserId(target)); pack.WriteCell(EntIndexToEntRef(weapon)); // Save last held weapon to switch back CreateTimer(0.1, Timer_RequipWeapon, pack); for( int slot = 0; slot <= 4; slot++ ) { weapon = GetPlayerWeaponSlot(target, slot); if( weapon != -1 ) { pack.WriteCell(EntIndexToEntRef(weapon)); SDKHooks_DropWeapon(target, weapon, NULL_VECTOR, NULL_VECTOR); } } } } } } return Plugin_Handled; } public Action Timer_RequipWeapon(Handle hdl, DataPack pack) { pack.Reset(); int client = GetClientOfUserId(pack.ReadCell()); if(client == 0) return; int activeWeapon = pack.ReadCell(); int weapon; while( pack.IsReadable() ) { weapon = pack.ReadCell(); if( EntRefToEntIndex(weapon) != INVALID_ENT_REFERENCE ) { EquipPlayerWeapon(client, weapon); } } if( EntRefToEntIndex(activeWeapon) != INVALID_ENT_REFERENCE ) { SetEntPropEnt(client, Prop_Data, "m_hActiveWeapon", activeWeapon); } } public Action Event_BotPlayerSwap(Event event, const char[] name, bool dontBroadcast) { int bot = GetClientOfUserId(event.GetInt("bot")); if(StrEqual(name, "player_bot_replace")) { //Bot replaced player SDKHook(bot, SDKHook_WeaponDrop, Event_OnWeaponDrop); }else{ //Player replaced a bot int client = GetClientOfUserId(event.GetInt("player")); if(botDropMeleeWeapon[bot] > 0) { int meleeOwnerEnt = GetEntPropEnt(botDropMeleeWeapon[bot], Prop_Send, "m_hOwnerEntity"); if(meleeOwnerEnt == -1) { EquipPlayerWeapon(client, botDropMeleeWeapon[bot]); botDropMeleeWeapon[bot] = -1; }else{ PrintToChat(client, "Could not give back your melee weapon, %N has it instead.", meleeOwnerEnt); } } SDKUnhook(bot, SDKHook_WeaponDrop, Event_OnWeaponDrop); } } public Action VGUIMenu(UserMsg msg_id, Handle bf, const int[] players, int playersNum, bool reliable, bool init) { char buffer[5]; BfReadString(bf, buffer, sizeof(buffer)); return StrEqual(buffer, "info") ? Plugin_Handled : Plugin_Continue; } //TODO: Might have to actually check for the bot they control, or possibly the bot will call this itself. public void OnClientDisconnect(int client) { if(IsClientConnected(client) && IsClientInGame(client) && botDropMeleeWeapon[client] > -1) { float pos[3]; GetClientAbsOrigin(client, pos); TeleportEntity(botDropMeleeWeapon[client], pos, NULL_VECTOR, NULL_VECTOR); botDropMeleeWeapon[client] = -1; } } public void OnMapStart() { HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); } public Action Event_OnWeaponDrop(int client, int weapon) { if(!IsValidEntity(weapon)) return Plugin_Continue; char wpn[32]; GetEdictClassname(weapon, wpn, sizeof(wpn)); if(IsFakeClient(client) && StrEqual(wpn, "weapon_melee") && GetEntProp(client, Prop_Send, "m_humanSpectatorUserID") > 0) { #if defined DEBUG 0 PrintToServer("Bot %N dropped melee weapon %s", client, wpn); #endif RequestFrame(Frame_HideEntity, weapon); botDropMeleeWeapon[client] = weapon; } return Plugin_Continue; } public void Frame_HideEntity(int entity) { TeleportEntity(entity, OUT_OF_BOUNDS, NULL_VECTOR, NULL_VECTOR); } public void EntityOutput_OnStartTouchSaferoom(const char[] output, int caller, int client, float time) { if(client > 0 && client <= MaxClients && IsValidClient(client) && GetClientTeam(client) == 2) { if(botDropMeleeWeapon[client] > 0) { PrintToServer("Giving melee weapon back to %N", client); float pos[3]; GetClientAbsOrigin(client, pos); TeleportEntity(botDropMeleeWeapon[client], pos, NULL_VECTOR, NULL_VECTOR); EquipPlayerWeapon(client, botDropMeleeWeapon[client]); botDropMeleeWeapon[client] = -1; } char currentGamemode[16]; hMPGamemode.GetString(currentGamemode, sizeof(currentGamemode)); if(StrEqual(currentGamemode, "tankrun", false)) { if(!IsFakeClient(client)) { CreateTimer(1.0, Timer_TPBots, client, TIMER_FLAG_NO_MAPCHANGE); } } } } public Action Timer_TPBots(Handle timer, int user) { float pos[3]; GetClientAbsOrigin(user, pos); for(int i = 1; i < MaxClients + 1; i++) { if(IsClientConnected(i) && IsClientInGame(i) && IsFakeClient(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i)) { TeleportEntity(i, pos, NULL_VECTOR, NULL_VECTOR); L4D2_RunScript("CommandABot({cmd=1,bot=GetPlayerFromUserID(%i),pos=Vector(%f,%f,%f)})", GetClientUserId(i), pos[0], pos[1], pos[2]); } } } //laserNotice public void Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast) { if(hFFNotice.IntValue > 0) { int victim = GetClientOfUserId(event.GetInt("userid")); int attacker = GetClientOfUserId(event.GetInt("attacker")); int dmg = event.GetInt("dmg_health"); if(dmg > 0) { if(attacker > 0 && !IsFakeClient(attacker) && attacker != victim) { if(GetClientTeam(attacker) == 2 && GetClientTeam(victim) == 2) { if(hFFNotice.IntValue == 1) { PrintHintTextToAll("%N has done %d HP of friendly fire damage to %N", attacker, dmg, victim); }else{ PrintToChatAll("%N has done %d HP of friendly fire damage to %N", attacker, dmg, victim); } } } } } } public void Event_PlayerUse(Event event, const char[] name, bool dontBroadcast) { if(hLaserNotice.BoolValue) { char entity_name[32]; int player_id = GetClientOfUserId(event.GetInt("userid")); int target_id = event.GetInt("targetid"); GetEntityClassname(target_id, entity_name, sizeof(entity_name)); if(StrEqual(entity_name,"upgrade_laser_sight")) { if(LasersUsed.FindValue(target_id) == -1) { LasersUsed.Push(target_id); PrintToChatAll("%N picked up laser sights", player_id); } } } } //finaletimer public void Event_GauntletStart(Event event, const char[] name, bool dontBroadcast) { if(hFinaleTimer.IntValue > 0) { iFinaleStartTime = GetTime(); } } public void Event_FinaleStart(Event event, const char[] name, bool dontBroadcast) { if(hFinaleTimer.IntValue == 2) { iFinaleStartTime = GetTime(); } } public void Event_FinaleEnd(Event event, const char[] name, bool dontBroadcast) { if(hFinaleTimer.IntValue != 0) { if(iFinaleStartTime != 0) { int difference = GetTime() - iFinaleStartTime; char time[32]; FormatSeconds(difference, time, sizeof(time)); PrintToChatAll("Finale took %s to complete", time); iFinaleStartTime = 0; } } } public void Event_CarAlarmTriggered(Event event, const char[] name, bool dontBroadcast) { int userID = GetClientOfUserId(event.GetInt("userid")); PrintToChatAll("%N activated a car alarm!", userID); } /** * Prints human readable duration from milliseconds * * @param ms The duration in milliseconds * @param str The char array to use for text * @param strSize The size of the string */ stock void FormatSeconds(int raw_sec, char[] str, int strSize) { int hours = raw_sec / 3600; int minutes = (raw_sec -(3600*hours))/60; int seconds = (raw_sec -(3600*hours)-(minutes*60)); if(hours >= 1) { Format(str, strSize, "%d hours, %d.%d minutes", hours, minutes, seconds); }else if(minutes >= 1) { Format(str, strSize, "%d minutes and %d seconds", minutes, seconds); }else { Format(str, strSize, "%d seconds", seconds); } } stock int GetAnyValidClient() { for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i) && !IsFakeClient(i)) { return i; } } return -1; } stock int GetRealClient(int client) { if(IsFakeClient(client)) { int realPlayer = GetClientOfUserId(GetEntProp(client, Prop_Send, "m_humanSpectatorUserID")); return realPlayer > 0 ? realPlayer : -1; }else{ return client; } } stock int GetIdleBot(int client) { for(int i = 1; i <= MaxClients; i++ ) { if(IsClientConnected(i) && HasEntProp(i, Prop_Send, "m_humanSpectatorUserID")) { int realPlayer = GetClientOfUserId(GetEntProp(i, Prop_Send, "m_humanSpectatorUserID")); if(realPlayer == client) { return i; } } } return -1; }