1c6d91b818
Thanks to Popoklopsi for making the change and sharing it with the community. https://forums.alliedmods.net/showpost.php?p=1879655&postcount=10 Also adding the source for all the super logs plugins so it is all in one place. The complied version in the scripting folder are built with sourcemod 1.5.3 on windows.Pushing the new version of the in game plug-in that has the CS:GO Protobuf changes. Thanks to Popoklopsi for making the change and sharing it with the community. https://forums.alliedmods.net/showpost.php?p=1879655&postcount=10 Also adding the source for all the super logs plugins so it is all in one place. The complied version in the scripting folder are built with sourcemod 1.5.3 on windows.init push
1512 lines
47 KiB
SourcePawn
1512 lines
47 KiB
SourcePawn
/*
|
|
* HLstatsX Community Edition - SourceMod plugin to generate advanced weapon logging
|
|
* http://www.hlxcommunity.com
|
|
* Copyright (C) 2009-2010 Nicholas Hastings (psychonic)
|
|
* Copyright (C) 2010 Thomas "CmptrWz" Berezansky
|
|
* Copyright (C) 2007-2008 TTS Oetzel & Goerz GmbH
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#pragma semicolon 1
|
|
|
|
#include <sourcemod>
|
|
#include <tf2_stocks> // NOTE: Gives us tf2 AND sdktools
|
|
#include <loghelper> // http://forums.alliedmods.net/showthread.php?t=100084
|
|
#undef REQUIRE_EXTENSIONS
|
|
#include <sdkhooks> // http://forums.alliedmods.net/showthread.php?t=106748
|
|
#define REQUIRE_EXTENSIONS
|
|
|
|
#define VERSION "2.0.32"
|
|
#define NAME "SuperLogs: TF2"
|
|
|
|
#define UNLOCKABLE_BIT (1<<30)
|
|
#define MAX_LOG_WEAPONS 28
|
|
#define MAX_WEAPON_LEN 29
|
|
#define MAX_BULLET_WEAPONS 14
|
|
#define MAX_UNLOCKABLE_WEAPONS 6
|
|
#define MAX_LOADOUT_SLOTS 8
|
|
#define WEAPON_PREFIX_LENGTH 10
|
|
#define WEAPON_FULL_LENGTH (WEAPON_PREFIX_LENGTH + MAX_WEAPON_LEN)
|
|
#define TELEPORT_AGAIN_TIME 10.0
|
|
#define OBJ_DISPENSER 0
|
|
#define OBJ_TELEPORTER_ENTRANCE 1
|
|
#define OBJ_TELEPORTER_EXIT 2
|
|
#define OBJ_SENTRYGUN 3
|
|
#define OBJ_ATTACHMENT_SAPPER 4
|
|
#define OBJ_SENTRYGUN_MINI 20
|
|
#define ITEMINDEX_DEMOSHIELD 131
|
|
#define ITEMINDEX_GUNBOATS 133
|
|
#define JUMP_NONE 0
|
|
#define JUMP_ROCKET_START 1
|
|
#define JUMP_ROCKET 2
|
|
#define JUMP_STICKY 3
|
|
#define LOG_SHOTS 0
|
|
#define LOG_HITS 1
|
|
#define LOG_KILLS 2
|
|
#define LOG_HEADSHOTS 3
|
|
#define LOG_TEAMKILLS 4
|
|
#define LOG_DAMAGE 5
|
|
#define LOG_DEATHS 6
|
|
#define LUNCHBOX_CHOCOLATE 159
|
|
#define LUNCHBOX_STEAK 311
|
|
|
|
public Plugin:myinfo = {
|
|
name = NAME,
|
|
author = "Thomas \"CmptrWz\" Berezansky & psychonic",
|
|
description = "Advanced logging for TF2. Generates auxilary logging for use with log parsers such as HLstatsX and Psychostats",
|
|
version = VERSION,
|
|
url = "http://www.hlxcommunity.com"
|
|
};
|
|
|
|
// Convars we need to monitor
|
|
new Handle:cvar_crits; // tf_weapon_criticals - Needs to be on for weapon stats
|
|
|
|
// Our convars
|
|
new Handle:cvar_actions;
|
|
new Handle:cvar_teleports;
|
|
new Handle:cvar_teleports_again;
|
|
new Handle:cvar_headshots;
|
|
new Handle:cvar_backstabs;
|
|
new Handle:cvar_sandvich;
|
|
new Handle:cvar_fire;
|
|
new Handle:cvar_wstats;
|
|
new Handle:cvar_heals;
|
|
new Handle:cvar_rolelogfix;
|
|
new Handle:cvar_objlogfix;
|
|
|
|
// Booleans to keep track of them
|
|
new bool:b_actions;
|
|
new bool:b_teleports;
|
|
new bool:b_teleports_again;
|
|
new bool:b_headshots;
|
|
new bool:b_backstabs;
|
|
new bool:b_sandvich;
|
|
new bool:b_fire;
|
|
new bool:b_wstats;
|
|
new bool:b_heals;
|
|
new bool:b_rolelogfix;
|
|
new bool:b_objlogfix;
|
|
|
|
// Monitoring outside libraries/cvars
|
|
new bool:b_sdkhookloaded = false;
|
|
|
|
// Weapon trie
|
|
new Handle:h_weapontrie;
|
|
// Weapon Stats
|
|
new weaponStats[MAXPLAYERS+1][MAX_LOG_WEAPONS][7];
|
|
new nextHurt[MAXPLAYERS+1] = {-1, ...};
|
|
// Loadout Info
|
|
new playerLoadout[MAXPLAYERS+1][MAX_LOADOUT_SLOTS][2];
|
|
new bool:playerLoadoutUpdated[MAXPLAYERS+1];
|
|
new Handle:itemsKv;
|
|
new Handle:slotsTrie;
|
|
// Stunball id (so we aren't looking it up in gameframe)
|
|
new stunBallId = -1;
|
|
// Stacks for "object destroyed at spawn"
|
|
new Handle:h_objList[MAXPLAYERS+1];
|
|
// Time storage for the same
|
|
new Float:f_objRemoved[MAXPLAYERS+1];
|
|
// Stunball Stack
|
|
new Handle:h_stunBalls;
|
|
// Wearables Stack
|
|
new Handle:h_wearables;
|
|
|
|
// Teleporter Stat-Padding Fix: Keep track of last use of teleporter
|
|
new Float:f_lastTeleport[MAXPLAYERS+1][MAXPLAYERS+1];
|
|
// Heals
|
|
new healPoints[MAXPLAYERS+1];
|
|
// Rocket/Sticky Jump Status
|
|
new jumpStatus[MAXPLAYERS+1];
|
|
// Last dalokohs eaten
|
|
new Float:dalokohs[MAXPLAYERS+1];
|
|
|
|
// Last known class of players
|
|
// Likely less intensive to keep track of this for sound hooks
|
|
new TFClassType:playerClass[MAXPLAYERS+1];
|
|
|
|
// Is player carrying a building
|
|
new bool:g_bCarryingObject[MAXPLAYERS+1] = {false,...};
|
|
new g_iCarryingOffs = -1;
|
|
new bool:g_bBlockLog = false;
|
|
|
|
// Bullet Weapons Variable
|
|
new bulletWeapons = 0;
|
|
|
|
// Arrays
|
|
// ONLY RULE OF THIS WEAPON LIST:
|
|
// Unlockable variants of a weapon go AFTER the original variant
|
|
// This will need to be tweaked if they make a second alt version of a weapon we track
|
|
new const String:weaponList[MAX_LOG_WEAPONS][MAX_WEAPON_LEN] = {
|
|
"ball",
|
|
"flaregun",
|
|
"minigun",
|
|
"natascha",
|
|
"pistol",
|
|
"pistol_scout",
|
|
"revolver",
|
|
"ambassador",
|
|
"scattergun",
|
|
"force_a_nature",
|
|
"shotgun_hwg",
|
|
"shotgun_primary",
|
|
"shotgun_pyro",
|
|
"shotgun_soldier",
|
|
"smg",
|
|
"sniperrifle",
|
|
"syringegun_medic",
|
|
"blutsauger",
|
|
"tf_projectile_arrow",
|
|
"tf_projectile_pipe",
|
|
"tf_projectile_pipe_remote",
|
|
"sticky_resistance",
|
|
"tf_projectile_rocket",
|
|
"rocketlauncher_directhit",
|
|
"deflect_rocket",
|
|
"deflect_promode",
|
|
"deflect_flare",
|
|
"deflect_arrow"
|
|
};
|
|
// This list has none of the above rules
|
|
new const String:weaponBullet[MAX_BULLET_WEAPONS][MAX_WEAPON_LEN] = {
|
|
"ambassador",
|
|
"force_a_nature",
|
|
"minigun",
|
|
"natascha",
|
|
"pistol",
|
|
"pistol_scout",
|
|
"revolver",
|
|
"scattergun",
|
|
"shotgun_hwg",
|
|
"shotgun_primary",
|
|
"shotgun_pyro",
|
|
"shotgun_soldier",
|
|
"smg",
|
|
"sniperrifle"
|
|
};
|
|
// This list is the list of weapons with an alternate in the next slot in the first list
|
|
new const String:weaponUnlockables[MAX_UNLOCKABLE_WEAPONS][MAX_WEAPON_LEN] = {
|
|
"minigun",
|
|
"revolver",
|
|
"scattergun",
|
|
"syringegun_medic",
|
|
"tf_projectile_pipe_remote",
|
|
"tf_projectile_rocket"
|
|
};
|
|
|
|
public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
|
|
{
|
|
MarkNativeAsOptional("SDKHook"); // This needs to be marked optional for a number of reasons
|
|
return APLRes_Success;
|
|
}
|
|
|
|
public OnPluginStart()
|
|
{
|
|
CreateConVar("superlogs_tf_version", VERSION, NAME, FCVAR_PLUGIN|FCVAR_NOTIFY);
|
|
|
|
cvar_crits = FindConVar("tf_weapon_criticals");
|
|
cvar_actions = CreateConVar("superlogs_actions", "1", "Enable logging of most player actions, such as \"stun\" (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_teleports = CreateConVar("superlogs_teleports", "1", "Enable logging of teleports (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_teleports_again = CreateConVar("superlogs_teleports_again", "1", "Repeated use of same teleporter in 10 seconds adds _again to event (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_headshots = CreateConVar("superlogs_headshots", "0", "Enable logging of headshot player action (default off)", 0, true, 0.0, true, 1.0);
|
|
cvar_backstabs = CreateConVar("superlogs_backstabs", "1", "Enable logging of backstab player action (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_sandvich = CreateConVar("superlogs_sandvich", "1", "Enable logging of sandvich eating (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_fire = CreateConVar("superlogs_fire", "1", "Enable logging of fiery arrows as a separate weapon from regular arrows (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_wstats = CreateConVar("superlogs_wstats", "1", "Enable logging of weapon stats (default on, only works when tf_weapon_criticals is 1)", 0, true, 0.0, true, 1.0);
|
|
cvar_heals = CreateConVar("superlogs_heals", "1", "Enable logging of healpoints upon death (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_rolelogfix = CreateConVar("superlogs_rolelogfix", "1", "Enable logging of healpoints upon death (default on)", 0, true, 0.0, true, 1.0);
|
|
cvar_objlogfix = CreateConVar("superlogs_objlogfix", "1", "Enable logging of owner object destruction on team/class change (default on)", 0, true, 0.0, true, 1.0);
|
|
|
|
HookConVarChange(cvar_crits,OnConVarStatsChange);
|
|
HookConVarChange(cvar_actions,OnConVarActionsChange);
|
|
HookConVarChange(cvar_teleports,OnConVarTeleportsChange);
|
|
HookConVarChange(cvar_teleports_again,OnConVarTeleportsAgainChange);
|
|
HookConVarChange(cvar_headshots,OnConVarHeadshotsChange);
|
|
HookConVarChange(cvar_backstabs,OnConVarBackstabsChange);
|
|
HookConVarChange(cvar_sandvich,OnConVarSandvichChange);
|
|
HookConVarChange(cvar_fire,OnConVarFireChange);
|
|
HookConVarChange(cvar_wstats,OnConVarStatsChange);
|
|
HookConVarChange(cvar_heals,OnConVarHealsChange);
|
|
HookConVarChange(cvar_rolelogfix,OnConVarRolelogfixChange);
|
|
HookConVarChange(cvar_objlogfix,OnConVarObjlogfixChange);
|
|
|
|
h_stunBalls = CreateStack();
|
|
h_wearables = CreateStack();
|
|
itemsKv = CreateKeyValues("items_game");
|
|
if(FileToKeyValues(itemsKv, "scripts/items/items_game.txt"))
|
|
KvJumpToKey(itemsKv, "items");
|
|
slotsTrie = CreateTrie();
|
|
SetTrieValue(slotsTrie, "primary", 0);
|
|
SetTrieValue(slotsTrie, "secondary", 1);
|
|
SetTrieValue(slotsTrie, "melee", 2);
|
|
SetTrieValue(slotsTrie, "pda", 3);
|
|
SetTrieValue(slotsTrie, "pda2", 4);
|
|
SetTrieValue(slotsTrie, "building", 5);
|
|
SetTrieValue(slotsTrie, "head", 6);
|
|
SetTrieValue(slotsTrie, "misc", 7);
|
|
|
|
// Populate the Weapon Trie
|
|
// Creates it too, technically
|
|
PopulateWeaponTrie();
|
|
|
|
// Populate stacks
|
|
for(new i = 0; i <= MAXPLAYERS; i++)
|
|
h_objList[i] = CreateStack();
|
|
|
|
// Hook Events
|
|
HookEvent("player_death", Event_PlayerDeath);
|
|
HookEvent("player_death", Event_PlayerDeathPre, EventHookMode_Pre);
|
|
HookEvent("object_destroyed", Event_ObjectDestroyed);
|
|
HookEvent("object_destroyed", Event_ObjectDestroyedPre, EventHookMode_Pre);
|
|
HookEvent("player_builtobject", Event_PlayerBuiltObject);
|
|
HookEvent("player_builtobject", Event_PlayerBuiltObjectPre, EventHookMode_Pre);
|
|
HookEvent("player_hurt", Event_PlayerHurt);
|
|
HookEvent("player_spawn", Event_PlayerSpawn);
|
|
HookEvent("player_disconnect", Event_PlayerDisconnect, EventHookMode_Pre);
|
|
HookEvent("post_inventory_application", Event_PostInventoryApplication);
|
|
|
|
HookEvent("arena_win_panel", Event_WinPanel);
|
|
HookEvent("teamplay_win_panel", Event_WinPanel);
|
|
|
|
g_iCarryingOffs = FindSendPropInfo("CTFPlayer", "m_bCarryingObject");
|
|
|
|
//AutoExecConfig(); // Create/request load of config file
|
|
AutoExecConfig(false); // Dont auto-make, but load if you find it.
|
|
|
|
CreateTimer(1.0, LogMap);
|
|
|
|
AddGameLogHook(OnGameLog);
|
|
}
|
|
|
|
public OnMapStart()
|
|
{
|
|
GetTeams(); // Loghelper says to put this here. Who am I to argue?
|
|
}
|
|
|
|
public OnConfigsExecuted()
|
|
{
|
|
OnConVarStatsChange(cvar_wstats, "", "");
|
|
OnConVarActionsChange(cvar_actions, "", "");
|
|
OnConVarTeleportsChange(cvar_teleports, "", "");
|
|
OnConVarTeleportsAgainChange(cvar_teleports_again, "", "");
|
|
OnConVarHeadshotsChange(cvar_headshots, "", "");
|
|
OnConVarBackstabsChange(cvar_backstabs, "", "");
|
|
OnConVarSandvichChange(cvar_sandvich, "", "");
|
|
OnConVarFireChange(cvar_fire, "", "");
|
|
OnConVarHealsChange(cvar_heals, "", "");
|
|
OnConVarRolelogfixChange(cvar_rolelogfix, "", "");
|
|
OnConVarObjlogfixChange(cvar_objlogfix, "", "");
|
|
}
|
|
|
|
public Action:TF2_CalcIsAttackCritical(attacker, weapon, String:weaponname[], &bool:result)
|
|
{
|
|
if(b_wstats && attacker > 0 && attacker <= MaxClients)
|
|
{
|
|
new weapon_index = GetWeaponIndex(weaponname[WEAPON_PREFIX_LENGTH], attacker);
|
|
if(weapon_index != -1)
|
|
{
|
|
weaponStats[attacker][weapon_index][LOG_SHOTS]++;
|
|
if((1<<weapon_index) & bulletWeapons)
|
|
nextHurt[attacker] = weapon_index;
|
|
}
|
|
}
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public OnAllPluginsLoaded()
|
|
{
|
|
b_sdkhookloaded = GetExtensionFileStatus("sdkhooks.ext") == 1;
|
|
if (b_sdkhookloaded)
|
|
HookAllClients();
|
|
}
|
|
|
|
public OnEntityCreated(entity, const String:className[])
|
|
{
|
|
if(b_wstats && StrEqual(className, "tf_projectile_stun_ball"))
|
|
PushStackCell(h_stunBalls, EntIndexToEntRef(entity));
|
|
else if(StrEqual(className, "tf_wearable_item_demoshield") || StrEqual(className, "tf_wearable_item"))
|
|
{
|
|
PushStackCell(h_wearables, EntIndexToEntRef(entity));
|
|
}
|
|
}
|
|
|
|
public Action:OnGameLog(const String:message[])
|
|
{
|
|
if (g_bBlockLog)
|
|
return Plugin_Handled;
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Action:OnTakeDamage(victim, &attacker, &inflictor, &Float:damage, &damagetype)
|
|
{
|
|
if(b_actions && attacker > 0 && attacker <= MaxClients && attacker != victim && inflictor > MaxClients && damage > 0.0 && IsValidEntity(inflictor) && (GetEntityFlags(victim) & (FL_ONGROUND | FL_INWATER)) == 0)
|
|
{
|
|
decl String:weapon[WEAPON_FULL_LENGTH];
|
|
GetEdictClassname(inflictor, weapon, sizeof(weapon));
|
|
if(weapon[3] == 'p' && weapon[4] == 'r') // Eliminate pumpkin bomb with the r
|
|
{
|
|
switch(weapon[14])
|
|
{
|
|
case 'r':
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_rocket");
|
|
if(jumpStatus[attacker] == JUMP_ROCKET)
|
|
LogPlayerEvent(attacker, "triggered", "air2airshot_rocket");
|
|
}
|
|
case 'p':
|
|
{
|
|
if(weapon[18] != 0)
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_sticky");
|
|
if(jumpStatus[attacker] == JUMP_STICKY)
|
|
LogPlayerEvent(attacker, "triggered", "air2airshot_sticky");
|
|
}
|
|
else
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_pipebomb");
|
|
if(jumpStatus[attacker] == JUMP_STICKY)
|
|
LogPlayerEvent(attacker, "triggered", "air2airshot_pipebomb");
|
|
}
|
|
}
|
|
case 'a':
|
|
LogPlayerEvent(attacker, "triggered", "airshot_arrow");
|
|
case 'f':
|
|
if(damage > 10.0)
|
|
LogPlayerEvent(attacker, "triggered", "airshot_flare");
|
|
}
|
|
}
|
|
}
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public OnTakeDamage_Post(victim, attacker, inflictor, Float:damage, damagetype)
|
|
{
|
|
if(b_wstats && attacker > 0 && attacker <= MaxClients)
|
|
{
|
|
new weapon_index = -1;
|
|
new idamage = RoundFloat(damage);
|
|
decl String:weapon[WEAPON_FULL_LENGTH];
|
|
if (inflictor <= MaxClients) // Inflictor is a player
|
|
{
|
|
if (damagetype & DMG_BURN)
|
|
return;
|
|
if(inflictor == attacker && damagetype & 1 && damage == 1000.0) // Telefrag
|
|
return;
|
|
GetClientWeapon(attacker, weapon, sizeof(weapon));
|
|
weapon_index = GetWeaponIndex(weapon[WEAPON_PREFIX_LENGTH], attacker);
|
|
}
|
|
else if (IsValidEdict(inflictor))
|
|
{
|
|
GetEdictClassname(inflictor, weapon, sizeof(weapon));
|
|
if (weapon[WEAPON_PREFIX_LENGTH] == 'g')
|
|
return; // grenadelauncher, but the projectile does damage, not the weapon. So this must be Charge N Targe.
|
|
else if(weapon[3] == 'p')
|
|
{
|
|
weapon_index = GetWeaponIndex(weapon, attacker, inflictor);
|
|
}
|
|
else
|
|
{
|
|
// Baseballs are funky.
|
|
// Inflictor is the BAT
|
|
// But the melee Crush damage (and the nevergib I don't check here) aren't set
|
|
// Still has CLUB damage though, and on a melee strike the inflictor is the PLAYER, not the weapon
|
|
// Just in case either way, forcing it to get the index of "ball"
|
|
if(!(damagetype & DMG_CRUSH) && (damagetype & DMG_CLUB) && StrEqual(weapon, "tf_weapon_bat_wood"))
|
|
weapon_index = GetWeaponIndex("ball", attacker);
|
|
else
|
|
weapon_index = GetWeaponIndex(weapon[WEAPON_PREFIX_LENGTH], attacker);
|
|
}
|
|
}
|
|
if(b_wstats && weapon_index > -1)
|
|
{
|
|
weaponStats[attacker][weapon_index][LOG_DAMAGE] += idamage;
|
|
weaponStats[attacker][weapon_index][LOG_HITS]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public OnGameFrame()
|
|
{
|
|
new entity;
|
|
new owner;
|
|
new itemindex, slot;
|
|
decl String:tempstring[15];
|
|
if(stunBallId > -1)
|
|
{
|
|
while(PopStackCell(h_stunBalls, entity))
|
|
{
|
|
if(IsValidEntity(entity))
|
|
{
|
|
owner = GetEntPropEnt(entity, Prop_Send, "m_hThrower");
|
|
if(owner > 0 && owner <= MaxClients)
|
|
weaponStats[owner][stunBallId][LOG_SHOTS]++;
|
|
}
|
|
}
|
|
}
|
|
while(PopStackCell(h_wearables, entity))
|
|
{
|
|
if(IsValidEntity(entity))
|
|
{
|
|
owner = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
|
|
if(owner > 0 && owner <= MaxClients)
|
|
{
|
|
itemindex = GetEntProp(entity, Prop_Send, "m_iItemDefinitionIndex");
|
|
Format(tempstring, sizeof(tempstring), "%d", itemindex);
|
|
if(KvJumpToKey(itemsKv, tempstring))
|
|
{
|
|
KvGetString(itemsKv, "item_slot", tempstring, sizeof(tempstring));
|
|
if(GetTrieValue(slotsTrie, tempstring, slot))
|
|
{
|
|
if(slot == 0 && playerClass[owner] == TFClass_DemoMan)
|
|
slot++;
|
|
if(playerLoadout[owner][slot][0] != itemindex)
|
|
{
|
|
playerLoadout[owner][slot][0] = itemindex;
|
|
playerLoadoutUpdated[owner] = true;
|
|
}
|
|
playerLoadout[owner][slot][1] = entity;
|
|
}
|
|
KvGoBack(itemsKv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
new cnt = GetClientCount();
|
|
for (new i = 1; i <= cnt; i++)
|
|
{
|
|
if (IsClientInGame(i) && GetEntData(i, g_iCarryingOffs, 1))
|
|
g_bCarryingObject[i] = true;
|
|
}
|
|
}
|
|
|
|
public OnClientPutInServer(client)
|
|
{
|
|
if (b_sdkhookloaded)
|
|
{
|
|
SDKHook(client, SDKHook_OnTakeDamagePost, OnTakeDamage_Post);
|
|
SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamage);
|
|
}
|
|
playerLoadoutUpdated[client] = true;
|
|
|
|
g_bCarryingObject[client] = false;
|
|
|
|
for(new i = 0; i <= MaxClients; i++)
|
|
{
|
|
// Clear both "we built" (client first) and "we used" (i first)
|
|
f_lastTeleport[client][i] = 0.0;
|
|
f_lastTeleport[i][client] = 0.0;
|
|
}
|
|
f_objRemoved[client] = 0.0;
|
|
playerClass[client] = TFClass_Unknown;
|
|
healPoints[client] = 0;
|
|
for(new i = 0; i < MAX_LOADOUT_SLOTS; i++)
|
|
playerLoadout[client][i] = {-1, -1};
|
|
ResetWeaponStats(client);
|
|
}
|
|
|
|
public Action:Event_PlayerDisconnect(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
if(client > 0 && IsClientInGame(client))
|
|
{
|
|
if(b_wstats) DumpWeaponStats(client);
|
|
if(b_heals) DumpHeals(client, " (disconnect)");
|
|
}
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
HookAllClients()
|
|
{
|
|
for (new i = 1; i <= MaxClients; i++)
|
|
if (IsClientInGame(i))
|
|
{
|
|
SDKHook(i, SDKHook_OnTakeDamagePost, OnTakeDamage_Post);
|
|
SDKHook(i, SDKHook_OnTakeDamage, OnTakeDamage);
|
|
}
|
|
}
|
|
|
|
public Action:Event_PlayerChangeclassPre(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
// Stop log entry!
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Event_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new Float:time = GetGameTime();
|
|
new userid = GetEventInt(event, "userid");
|
|
new client = GetClientOfUserId(userid);
|
|
new TFClassType:spawnClass = TFClassType:GetEventInt(event, "class");
|
|
jumpStatus[client] = JUMP_NONE; // Play it safe
|
|
if(b_wstats) DumpWeaponStats(client); // Changed class without death, dump and reset stats
|
|
//if(b_wstats) ResetWeaponStats(client);
|
|
if(b_heals) DumpHeals(client, " (spawn)");
|
|
if(time == f_objRemoved[client])
|
|
{
|
|
decl String:owner[96];
|
|
decl String:player_authid[32];
|
|
decl String:objname[24];
|
|
if (!GetClientAuthString(client, player_authid, sizeof(player_authid)))
|
|
strcopy(player_authid, sizeof(player_authid), "UNKNOWN");
|
|
Format(owner, sizeof(owner), "\"%N<%d><%s><%s>\"", client, userid, player_authid, g_team_list[GetClientTeam(client)]);
|
|
new objecttype;
|
|
while(PopStackCell(h_objList[client], objecttype))
|
|
{
|
|
switch(objecttype)
|
|
{
|
|
case OBJ_DISPENSER:
|
|
objname = "OBJ_DISPENSER";
|
|
case OBJ_TELEPORTER_ENTRANCE:
|
|
objname = "OBJ_TELEPORTER_ENTRANCE";
|
|
case OBJ_TELEPORTER_EXIT:
|
|
objname = "OBJ_TELEPORTER_EXIT";
|
|
case OBJ_SENTRYGUN:
|
|
objname = "OBJ_SENTRYGUN";
|
|
case OBJ_ATTACHMENT_SAPPER:
|
|
objname = "OBJ_ATTACHMENT_SAPPER";
|
|
case OBJ_SENTRYGUN_MINI:
|
|
objname = "OBJ_SENTRYGUN_MINI";
|
|
default:
|
|
continue;
|
|
}
|
|
LogToGame("%s triggered \"killedobject\" (object \"%s\") (weapon \"pda_engineer\") (objectowner %s) (spawn)", owner, objname, owner);
|
|
}
|
|
}
|
|
if(b_rolelogfix && playerClass[client] != spawnClass)
|
|
{
|
|
switch(spawnClass)
|
|
{
|
|
case TFClass_Scout:
|
|
LogRoleChange(client, "scout");
|
|
case TFClass_Sniper:
|
|
LogRoleChange(client, "sniper");
|
|
case TFClass_Soldier:
|
|
LogRoleChange(client, "soldier");
|
|
case TFClass_DemoMan:
|
|
LogRoleChange(client, "demoman");
|
|
case TFClass_Medic:
|
|
LogRoleChange(client, "medic");
|
|
case TFClass_Heavy:
|
|
LogRoleChange(client, "heavyweapons");
|
|
case TFClass_Pyro:
|
|
LogRoleChange(client, "pyro");
|
|
case TFClass_Spy:
|
|
LogRoleChange(client, "spy");
|
|
case TFClass_Engineer:
|
|
LogRoleChange(client, "engineer");
|
|
default:
|
|
LogRoleChange(client, "unknown");
|
|
}
|
|
}
|
|
playerClass[client] = spawnClass;
|
|
dalokohs[client] = -30.0;
|
|
}
|
|
|
|
public Event_ObjectRemoved(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new Float:time = GetGameTime();
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
if(time != f_objRemoved[client])
|
|
{
|
|
f_objRemoved[client] = time;
|
|
while(PopStack(h_objList[client]))
|
|
continue;
|
|
}
|
|
new objtype = GetEventInt(event, "objecttype");
|
|
new objindex = GetEventInt(event, "index");
|
|
if (IsValidEdict(objindex) && GetEntProp(GetEventInt(event, "index"), Prop_Send, "m_bMiniBuilding", 1))
|
|
{
|
|
objtype = OBJ_SENTRYGUN_MINI;
|
|
}
|
|
PushStackCell(h_objList[client], objtype);
|
|
}
|
|
|
|
public Event_PlayerStealsandvich(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
LogPlyrPlyrEvent(GetClientOfUserId(GetEventInt(event, "target")), GetClientOfUserId(GetEventInt(event, "owner")), "triggered", "steal_sandvich", true);
|
|
}
|
|
|
|
public Event_PlayerStunned(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new String: properties[33];
|
|
new stunner = GetClientOfUserId(GetEventInt(event, "stunner"));
|
|
if(stunner > 0) // Stunner == 0 would be map stun (ghost/trigger), natascha stun (slowdown), taunt kill stun (medic, sniper)
|
|
{
|
|
new victim = GetClientOfUserId(GetEventInt(event, "victim"));
|
|
if(GetEventBool(event, "victim_capping")) StrCat(properties, sizeof(properties), " (victim_capping)");
|
|
if(GetEventBool(event, "big_stun")) StrCat(properties, sizeof(properties), " (big_stun)");
|
|
LogPlyrPlyrEvent(stunner, victim, "triggered", "stun", true);
|
|
if((GetEntityFlags(victim) & (FL_ONGROUND | FL_INWATER)) == 0)
|
|
LogPlayerEvent(stunner, "triggered", "airshot_stun");
|
|
}
|
|
}
|
|
|
|
public Event_PlayerTeleported(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new builderid = GetClientOfUserId(GetEventInt(event, "builderid"));
|
|
new userid = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
new Float:curTime = GetGameTime();
|
|
if(b_teleports_again && f_lastTeleport[builderid][userid] > curTime - TELEPORT_AGAIN_TIME)
|
|
{
|
|
if(userid == builderid)
|
|
{
|
|
LogPlayerEvent(userid, "triggered", "teleport_self_again");
|
|
}
|
|
else
|
|
{
|
|
LogPlayerEvent(builderid, "triggered", "teleport_again");
|
|
LogPlayerEvent(userid, "triggered", "teleport_used_again");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(userid == builderid)
|
|
{
|
|
LogPlayerEvent(userid, "triggered", "teleport_self");
|
|
}
|
|
else
|
|
{
|
|
LogPlayerEvent(builderid, "triggered", "teleport");
|
|
LogPlayerEvent(userid, "triggered", "teleport_used");
|
|
}
|
|
}
|
|
f_lastTeleport[builderid][userid] = curTime;
|
|
}
|
|
|
|
public Event_DeployBuffBanner(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
LogPlayerEvent(GetClientOfUserId(GetEventInt(event, "buff_owner")), "triggered", "buff_deployed");
|
|
}
|
|
|
|
public Event_MedicDefended(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
LogPlayerEvent(GetClientOfUserId(GetEventInt(event, "userid")), "triggered", "defended_medic");
|
|
}
|
|
|
|
public Event_PlayerEscortScore(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
LogPlayerEvent(GetEventInt(event, "player"), "triggered", "escort_score");
|
|
}
|
|
|
|
public Event_MedicDeath(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
healPoints[client] = GetEntProp(client, Prop_Send, "m_iHealPoints");
|
|
}
|
|
|
|
public Event_PlayerHurt(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
// NOTE: The weaponid in this event is the weapon the player is HOLDING as of the event happening
|
|
// Thus, only if we don't have SDK Hooks do we use it for airshot detection.
|
|
if(!b_sdkhookloaded)
|
|
{
|
|
new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
|
|
if(b_wstats)
|
|
{
|
|
if(nextHurt[attacker] > -1)
|
|
weaponStats[attacker][nextHurt[attacker]][LOG_HITS]++;
|
|
nextHurt[attacker] = -1;
|
|
}
|
|
if(b_actions)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
if(client != attacker && client > 0 && client <= MaxClients && IsClientInGame(client)
|
|
&& IsPlayerAlive(client) && (GetEntityFlags(client) & (FL_ONGROUND | FL_INWATER)) == 0)
|
|
{
|
|
switch(GetEventInt(event, "weaponid"))
|
|
{
|
|
case TF_WEAPON_ROCKETLAUNCHER, TF_WEAPON_DIRECTHIT:
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_rocket");
|
|
if(jumpStatus[attacker] == JUMP_ROCKET)
|
|
LogPlayerEvent(attacker, "triggered", "air2airshot_rocket");
|
|
}
|
|
case TF_WEAPON_GRENADELAUNCHER:
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_pipebomb");
|
|
if(jumpStatus[attacker] == JUMP_STICKY)
|
|
LogPlayerEvent(attacker, "triggered", "air2airshot_pipebomb");
|
|
}
|
|
case TF_WEAPON_PIPEBOMBLAUNCHER:
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_sticky");
|
|
if(jumpStatus[attacker] == JUMP_STICKY)
|
|
LogPlayerEvent(attacker, "triggered", "air2airshot_sticky");
|
|
}
|
|
case TF_WEAPON_FLAREGUN:
|
|
{
|
|
if(GetEventInt(event, "damageamount") > 10)
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_flare");
|
|
}
|
|
}
|
|
case TF_WEAPON_COMPOUND_BOW:
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "airshot_arrow");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public Action:Event_ObjectDestroyedPre(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
if (GetEntProp(GetEventInt(event, "index"), Prop_Send, "m_bMiniBuilding", 1))
|
|
{
|
|
g_bBlockLog = true;
|
|
}
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Event_ObjectDestroyed(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
if (g_bBlockLog)
|
|
{
|
|
g_bBlockLog = false;
|
|
decl String:weapon[64];
|
|
decl String:team[64];
|
|
decl String:auth[32];
|
|
decl String:properties[255];
|
|
GetEventString(event, "weapon", weapon, sizeof(weapon));
|
|
new victimuid = GetEventInt(event, "userid");
|
|
new victim = GetClientOfUserId(victimuid);
|
|
if (victim == 0 || !IsClientInGame(victim))
|
|
return;
|
|
GetClientAuthString(victim, auth, sizeof(auth));
|
|
GetTeamName(GetClientTeam(victim), team, sizeof(team));
|
|
Format(properties, sizeof(properties), " (object \"OBJ_SENTRYGUN_MINI\") (weapon \"%s\") (objectowner \"%N<%d><%s><%s>\")", weapon, victim, victimuid, auth, team);
|
|
LogPlayerEvent(GetClientOfUserId(GetEventInt(event, "attacker")), "triggered", "killedobject", true, properties);
|
|
}
|
|
}
|
|
|
|
public Action:Event_PlayerBuiltObjectPre(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
if (g_bCarryingObject[GetClientOfUserId(GetEventInt(event, "userid"))] || GetEntProp(GetEventInt(event, "index"), Prop_Send, "m_bMiniBuilding", 1))
|
|
{
|
|
g_bBlockLog = true;
|
|
}
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Event_PlayerBuiltObject(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
|
|
if (g_bBlockLog)
|
|
{
|
|
g_bBlockLog = false;
|
|
if (!g_bCarryingObject[client] && GetEntProp(GetEventInt(event, "index"), Prop_Send, "m_bMiniBuilding", 1))
|
|
{
|
|
LogPlayerEvent(client, "triggered", "builtobject", true, " (object \"OBJ_SENTRYGUN_MINI\")");
|
|
}
|
|
}
|
|
|
|
g_bCarryingObject[client] = false;
|
|
}
|
|
|
|
public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new death_flags = GetEventInt(event, "death_flags");
|
|
if((death_flags & TF_DEATHFLAG_DEADRINGER) == TF_DEATHFLAG_DEADRINGER) // Not a dead ringer death?
|
|
{
|
|
return;
|
|
}
|
|
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
|
|
new customkill = GetEventInt(event, "customkill");
|
|
new bits = GetEventInt(event, "damagebits");
|
|
if(b_heals && playerClass[client] != TFClass_Medic) // medic_death event handles this for dead medics
|
|
DumpHeals(client);
|
|
jumpStatus[client] = JUMP_NONE; // Not jumping
|
|
g_bCarryingObject[client] = false;
|
|
if(b_actions)
|
|
{
|
|
if(attacker == client && customkill == TF_CUSTOM_SUICIDE)
|
|
LogPlayerEvent(client, "triggered", "force_suicide");
|
|
else
|
|
{
|
|
switch(jumpStatus[client])
|
|
{
|
|
case 2:
|
|
{
|
|
LogPlayerEvent(client, "triggered", "rocket_failjump");
|
|
if(attacker > 0 && attacker != client)
|
|
LogPlayerEvent(attacker, "triggered", "rocket_jumper_kill");
|
|
}
|
|
case 3:
|
|
{
|
|
LogPlayerEvent(client, "triggered", "sticky_failjump");
|
|
if(attacker > 0 && attacker != client)
|
|
LogPlayerEvent(attacker, "triggered", "sticky_jumper_kill");
|
|
}
|
|
}
|
|
if(bits & DMG_DROWN)
|
|
{
|
|
LogPlayerEvent(client, "triggered", "drowned");
|
|
}
|
|
else if(attacker != client)
|
|
{
|
|
switch(jumpStatus[attacker]) // Don't need to check attacker != 0 here as world will never rocket/sticky jump
|
|
{
|
|
case 2:
|
|
LogPlayerEvent(attacker, "triggered", "rocket_jump_kill");
|
|
case 3:
|
|
LogPlayerEvent(attacker, "triggered", "sticky_jump_kill");
|
|
}
|
|
if ((bits & DMG_ACID) && attacker > 0 && customkill != TF_CUSTOM_HEADSHOT)
|
|
LogPlayerEvent(attacker, "triggered", "crit_kill");
|
|
else if((death_flags & TF_DEATHFLAG_FIRSTBLOOD) == TF_DEATHFLAG_FIRSTBLOOD)
|
|
LogPlayerEvent(attacker, "triggered", "first_blood");
|
|
if (customkill == TF_CUSTOM_HEADSHOT && client > 0 && client <= MaxClients
|
|
&& IsClientInGame(client) && (GetEntityFlags(client) & (FL_ONGROUND | FL_INWATER)) == 0)
|
|
LogPlayerEvent(attacker, "triggered", "airshot_headshot");
|
|
}
|
|
}
|
|
}
|
|
if(b_wstats && client > 0 && attacker > 0 && attacker <= MaxClients)
|
|
{
|
|
decl String:weaponlogname[MAX_WEAPON_LEN];
|
|
GetEventString(event, "weapon_logclassname", weaponlogname, sizeof(weaponlogname));
|
|
new weapon_index = GetWeaponIndex(weaponlogname, attacker);
|
|
if(weapon_index != -1)
|
|
{
|
|
weaponStats[attacker][weapon_index][LOG_KILLS]++;
|
|
if(customkill == TF_CUSTOM_HEADSHOT)
|
|
weaponStats[attacker][weapon_index][LOG_HEADSHOTS]++;
|
|
weaponStats[client][weapon_index][LOG_DEATHS]++;
|
|
if(GetClientTeam(client) == GetClientTeam(attacker))
|
|
weaponStats[attacker][weapon_index][LOG_TEAMKILLS]++;
|
|
}
|
|
DumpWeaponStats(client);
|
|
}
|
|
}
|
|
|
|
public Action:Event_PlayerDeathPre(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
|
|
new victim = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
new customkill = GetEventInt(event, "customkill");
|
|
new inflictor = GetEventInt(event, "inflictor_entindex");
|
|
if (!IsValidEdict(inflictor))
|
|
{
|
|
inflictor = 0;
|
|
}
|
|
|
|
switch (customkill)
|
|
{
|
|
case TF_CUSTOM_HEADSHOT:
|
|
if(b_headshots)
|
|
{
|
|
LogPlyrPlyrEvent(attacker, victim, "triggered", "headshot");
|
|
}
|
|
case TF_CUSTOM_BACKSTAB:
|
|
if(b_backstabs)
|
|
{
|
|
LogPlyrPlyrEvent(attacker, victim, "triggered", "backstab");
|
|
}
|
|
case TF_CUSTOM_BURNING_ARROW, TF_CUSTOM_FLYINGBURN:
|
|
if(b_fire)
|
|
{
|
|
decl String:logweapon[64];
|
|
GetEventString(event, "weapon_logclassname", logweapon, sizeof(logweapon));
|
|
if(logweapon[0] != 'd') // No changing reflects - was 'r' but it is deflects
|
|
{
|
|
SetEventString(event, "weapon_logclassname", "tf_projectile_arrow_fire");
|
|
}
|
|
}
|
|
case TF_CUSTOM_TAUNT_UBERSLICE:
|
|
{
|
|
if(GetEventInt(event, "weaponid") == TF_WEAPON_BONESAW)
|
|
{
|
|
SetEventString(event, "weapon_logclassname", "taunt_medic");
|
|
|
|
// Might as well fix the kill icon, too, as long as we're here
|
|
// Courtesy of FlaminSarge
|
|
SetEventString(event, "weapon", "taunt_medic");
|
|
}
|
|
}
|
|
|
|
case TF_CUSTOM_DECAPITATION_BOSS:
|
|
{
|
|
LogPlayerEvent(attacker, "triggered", "killed_by_horseman", true);
|
|
}
|
|
}
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Event_WinPanel(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
if(b_actions)
|
|
{
|
|
LogPlayerEvent(GetEventInt(event, "player_1"), "triggered", "mvp1");
|
|
LogPlayerEvent(GetEventInt(event, "player_2"), "triggered", "mvp2");
|
|
LogPlayerEvent(GetEventInt(event, "player_3"), "triggered", "mvp3");
|
|
}
|
|
if(b_wstats)
|
|
DumpAllWeaponStats();
|
|
}
|
|
|
|
public Event_RocketJump(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
new status = jumpStatus[client];
|
|
if(status == JUMP_ROCKET_START) // Taunt kills trigger one event, rocket jumps two
|
|
{
|
|
jumpStatus[client] = JUMP_ROCKET;
|
|
LogPlayerEvent(client, "triggered", "rocket_jump");
|
|
}
|
|
else if(status != JUMP_ROCKET)
|
|
jumpStatus[client] = JUMP_ROCKET_START;
|
|
}
|
|
|
|
public Event_StickyJump(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
if(jumpStatus[client] != JUMP_STICKY)
|
|
{
|
|
jumpStatus[client] = JUMP_STICKY;
|
|
LogPlayerEvent(client, "triggered", "sticky_jump");
|
|
}
|
|
}
|
|
|
|
public Event_JumpLanded(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
jumpStatus[GetClientOfUserId(GetEventInt(event, "userid"))] = JUMP_NONE;
|
|
}
|
|
|
|
public Event_ObjectDeflected(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
new client = GetClientOfUserId(GetEventInt(event, "userid"));
|
|
new owner = GetClientOfUserId(GetEventInt(event, "ownerid"));
|
|
switch(GetEventInt(event, "weaponid"))
|
|
{
|
|
case TF_WEAPON_NONE:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "airblast_player", true);
|
|
}
|
|
case TF_WEAPON_ROCKETLAUNCHER:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_rocket", true);
|
|
if(b_wstats && b_sdkhookloaded)
|
|
{
|
|
new weapon_index = GetWeaponIndex("deflect_rocket");
|
|
if(weapon_index > -1)
|
|
weaponStats[client][weapon_index][LOG_SHOTS]++;
|
|
}
|
|
}
|
|
case TF_WEAPON_GRENADE_DEMOMAN:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_pipebomb", true);
|
|
if(b_wstats && b_sdkhookloaded)
|
|
{
|
|
new weapon_index = GetWeaponIndex("deflect_promode");
|
|
if(weapon_index > -1)
|
|
weaponStats[client][weapon_index][LOG_SHOTS]++;
|
|
}
|
|
}
|
|
case TF_WEAPON_FLAREGUN:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_flare", true);
|
|
if(b_wstats && b_sdkhookloaded)
|
|
{
|
|
new weapon_index = GetWeaponIndex("deflect_flare");
|
|
if(weapon_index > -1)
|
|
weaponStats[client][weapon_index][LOG_SHOTS]++;
|
|
}
|
|
}
|
|
case TF_WEAPON_JAR:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_jarate", true);
|
|
}
|
|
case TF_WEAPON_COMPOUND_BOW:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_arrow", true);
|
|
if(b_wstats && b_sdkhookloaded)
|
|
{
|
|
new weapon_index = GetWeaponIndex("deflect_arrow");
|
|
if(weapon_index > -1)
|
|
weaponStats[client][weapon_index][LOG_SHOTS]++;
|
|
}
|
|
}
|
|
case TF_WEAPON_DIRECTHIT:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_rocket_dh", true);
|
|
if(b_wstats && b_sdkhookloaded)
|
|
{
|
|
new weapon_index = GetWeaponIndex("deflect_rocket");
|
|
if(weapon_index > -1)
|
|
weaponStats[client][weapon_index][LOG_SHOTS]++;
|
|
}
|
|
}
|
|
case TF_WEAPON_GRENADE_STUNBALL:
|
|
{
|
|
LogPlyrPlyrEvent(client, owner, "triggered", "deflected_baseball", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Event_PostInventoryApplication(Handle:event, const String:name[], bool:dontBroadcast)
|
|
{
|
|
CreateTimer(0.2, CheckPlayerLoadout, GetEventInt(event, "userid"));
|
|
}
|
|
|
|
public Action:CheckPlayerLoadout(Handle:timer, any:userid)
|
|
{
|
|
new client = GetClientOfUserId(userid);
|
|
if (client == 0 || !IsClientInGame(client))
|
|
{
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
new ent = -1;
|
|
new bool:newLoadout = false;
|
|
new TFClassType:pClass = playerClass[client];
|
|
for(new checkslot = 0; checkslot <=5; checkslot++)
|
|
{
|
|
if(playerLoadout[client][checkslot][1] != 0 && IsValidEntity(playerLoadout[client][checkslot][1]))
|
|
{
|
|
continue;
|
|
}
|
|
ent = GetPlayerWeaponSlot(client, checkslot);
|
|
if(ent == -1)
|
|
{
|
|
// Nothing in slot?
|
|
if(b_sdkhookloaded && checkslot < 3 && (pClass == TFClass_Soldier || pClass == TFClass_DemoMan)) // Maybe gunboats? Or charge n targe?
|
|
{
|
|
playerLoadout[client][checkslot][1] = -1;
|
|
continue;
|
|
}
|
|
if(playerLoadout[client][checkslot][0] == -1)
|
|
continue;
|
|
playerLoadout[client][checkslot] = {-1, -1};
|
|
newLoadout = true;
|
|
}
|
|
else
|
|
{
|
|
new itemindex = GetEntProp(ent, Prop_Send, "m_iItemDefinitionIndex");
|
|
if(playerLoadout[client][checkslot][0] != itemindex)
|
|
{
|
|
playerLoadout[client][checkslot][0] = itemindex;
|
|
newLoadout = true;
|
|
}
|
|
playerLoadout[client][checkslot][1] = EntIndexToEntRef(ent);
|
|
}
|
|
}
|
|
if(b_sdkhookloaded)
|
|
{
|
|
if(newLoadout) // Just in case we already updated it due to a hat spawning or being a new client
|
|
playerLoadoutUpdated[client] = true;
|
|
CreateTimer(0.2, LogWeaponLoadout, userid);
|
|
return Plugin_Stop;
|
|
}
|
|
if (newLoadout)
|
|
LogWeaponLoadout(INVALID_HANDLE, userid);
|
|
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
public Action:LogWeaponLoadout(Handle:timer, any:userid)
|
|
{
|
|
new client = GetClientOfUserId(userid);
|
|
if (client > 0 && IsClientInGame(client))
|
|
{
|
|
for (new i = 0; i < MAX_LOADOUT_SLOTS; i++)
|
|
{
|
|
if(playerLoadout[client][i][0] != -1 && !IsValidEntity(playerLoadout[client][i][1]) || playerLoadout[client][i][1] == 0)
|
|
{
|
|
playerLoadout[client][i] = {-1, -1};
|
|
playerLoadoutUpdated[client] = true;
|
|
}
|
|
}
|
|
if (playerLoadoutUpdated[client] == false)
|
|
return Plugin_Stop;
|
|
playerLoadoutUpdated[client] = false;
|
|
|
|
decl String:logString[255];
|
|
Format(logString, sizeof(logString), " (primary \"%d\") (secondary \"%d\") (melee \"%d\") (pda \"%d\") (pda2 \"%d\") (building \"%d\") (head \"%d\") (misc \"%d\")", playerLoadout[client][0][0], playerLoadout[client][1][0], playerLoadout[client][2][0], playerLoadout[client][3][0], playerLoadout[client][4][0], playerLoadout[client][5][0], playerLoadout[client][6][0], playerLoadout[client][7][0]);
|
|
|
|
LogPlayerEvent(client, "triggered", "player_loadout", _, logString);
|
|
}
|
|
return Plugin_Stop;
|
|
}
|
|
|
|
public Action:Event_PlayerJarated(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
|
|
{
|
|
new client = BfReadByte(bf);
|
|
new victim = BfReadByte(bf);
|
|
|
|
if (!victim || !IsClientInGame(victim))
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
|
|
if (TF2_IsPlayerInCondition(victim, TFCond_Jarated))
|
|
{
|
|
LogPlyrPlyrEvent(client, victim, "triggered", "jarate", true);
|
|
}
|
|
else if (TF2_IsPlayerInCondition(victim, TFCond_Milked))
|
|
{
|
|
LogPlyrPlyrEvent(client, victim, "triggered", "madmilk", true);
|
|
}
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Action:Event_PlayerShieldBlocked(UserMsg:msg_id, Handle:bf, const players[], playersNum, bool:reliable, bool:init)
|
|
{
|
|
new victim = BfReadByte(bf);
|
|
new client = BfReadByte(bf);
|
|
|
|
LogPlyrPlyrEvent(client, victim, "triggered", "shield_blocked", true);
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
// Modified Octo's method a bit to try and reduce checking of sound strings
|
|
public Action:SoundHook(clients[64], &numClients, String:sample[PLATFORM_MAX_PATH], &entity, &channel, &Float:volume, &level, &pitch, &flags)
|
|
{
|
|
if(entity <= MaxClients && clients[0] == entity && playerClass[entity] == TFClass_Heavy && StrEqual(sample,"vo/SandwichEat09.wav"))
|
|
{
|
|
switch(playerLoadout[entity][1][0])
|
|
{
|
|
case LUNCHBOX_CHOCOLATE:
|
|
{
|
|
LogPlayerEvent(entity, "triggered", "dalokohs");
|
|
new Float:time = GetGameTime();
|
|
if(time - dalokohs[entity] > 30)
|
|
LogPlayerEvent(entity, "triggered", "dalokohs_healthboost");
|
|
dalokohs[entity] = time;
|
|
if(GetClientHealth(entity) < 350)
|
|
LogPlayerEvent(entity, "triggered", "dalokohs_healself");
|
|
}
|
|
case LUNCHBOX_STEAK:
|
|
{
|
|
LogPlayerEvent(entity, "triggered", "steak");
|
|
}
|
|
default:
|
|
{
|
|
LogPlayerEvent(entity, "triggered", "sandvich");
|
|
if(GetClientHealth(entity) < 300)
|
|
LogPlayerEvent(entity, "triggered", "sandvich_healself");
|
|
}
|
|
}
|
|
}
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Action:LogMap(Handle:timer)
|
|
{
|
|
// Called 1 second after OnPluginStart since srcds does not log the first map loaded. Idea from Stormtrooper's "mapfix.sp" for psychostats
|
|
LogMapLoad();
|
|
}
|
|
|
|
PopulateWeaponTrie()
|
|
{
|
|
// Create a Trie
|
|
h_weapontrie = CreateTrie();
|
|
|
|
// Initial populate
|
|
for(new i = 0; i < MAX_LOG_WEAPONS; i++)
|
|
SetTrieValue(h_weapontrie, weaponList[i], i);
|
|
|
|
// Figure out the Bullet Weapon ids (based on the list of bullet weapons)
|
|
new index;
|
|
bulletWeapons = 0;
|
|
for(new i = 0; i < MAX_BULLET_WEAPONS; i++)
|
|
if(GetTrieValue(h_weapontrie, weaponBullet[i], index))
|
|
bulletWeapons |= (1<<index);
|
|
|
|
// Flag unlockable enabled weapons as such
|
|
for(new i = 0; i < MAX_UNLOCKABLE_WEAPONS; i++)
|
|
if(GetTrieValue(h_weapontrie, weaponUnlockables[i], index))
|
|
SetTrieValue(h_weapontrie, weaponUnlockables[i], index | UNLOCKABLE_BIT);
|
|
|
|
// Aux entries, based on the position of other entries in the list
|
|
// This is last so that we inherit unlockable weapon flags.
|
|
// Note: As of this writing, none of the below have unlockable variants anyway.
|
|
if(GetTrieValue(h_weapontrie, "tf_projectile_arrow", index))
|
|
{
|
|
SetTrieValue(h_weapontrie, "compound_bow", index);
|
|
SetTrieValue(h_weapontrie, "tf_projectile_arrow_fire", index);
|
|
}
|
|
if(GetTrieValue(h_weapontrie, "tf_projectile_rocket", index))
|
|
SetTrieValue(h_weapontrie, "rocketlauncher", index);
|
|
if(GetTrieValue(h_weapontrie, "rocketlauncher_directhit", index))
|
|
SetTrieValue(h_weapontrie, "tf_projectile_rocket_dh", index);
|
|
if(GetTrieValue(h_weapontrie, "tf_projectile_pipe", index))
|
|
SetTrieValue(h_weapontrie, "grenadelauncher", index);
|
|
if(GetTrieValue(h_weapontrie, "tf_projectile_pipe_remote", index))
|
|
SetTrieValue(h_weapontrie, "pipebomblauncher", index);
|
|
if(GetTrieValue(h_weapontrie, "sticky_resistance", index))
|
|
SetTrieValue(h_weapontrie, "tf_projectile_pipe_remote_sr", index);
|
|
if(GetTrieValue(h_weapontrie, "flaregun", index))
|
|
SetTrieValue(h_weapontrie, "tf_projectile_flare", index);
|
|
if(GetTrieValue(h_weapontrie, "ball", index))
|
|
{
|
|
SetTrieValue(h_weapontrie, "tf_projectile_stun_ball", index);
|
|
stunBallId = index;
|
|
}
|
|
}
|
|
|
|
GetWeaponIndex(const String:weaponname[], client = 0, weapon = -1)
|
|
{
|
|
new index;
|
|
new bool:unlockable;
|
|
new reflectindex = -1;
|
|
|
|
if (strlen(weaponname) < 15)
|
|
return -1;
|
|
|
|
if(GetTrieValue(h_weapontrie, weaponname, index))
|
|
{
|
|
if(index & UNLOCKABLE_BIT)
|
|
{
|
|
index &= ~UNLOCKABLE_BIT;
|
|
unlockable = true;
|
|
|
|
}
|
|
if(weaponname[3] == 'p' && weapon > -1) // Projectile?
|
|
{
|
|
if(client == GetEntProp(weapon, Prop_Send, "m_iDeflected"))
|
|
{
|
|
switch(weaponname[14])
|
|
{
|
|
case 'a':
|
|
reflectindex = GetWeaponIndex("deflect_arrow");
|
|
case 'f':
|
|
reflectindex = GetWeaponIndex("deflect_flare");
|
|
case 'p':
|
|
if(weaponname[19] == 0) // we aren't a _remote
|
|
reflectindex = GetWeaponIndex("deflect_promode");
|
|
case 'r':
|
|
reflectindex = GetWeaponIndex("deflect_rocket");
|
|
}
|
|
}
|
|
}
|
|
if(reflectindex > -1)
|
|
return reflectindex;
|
|
if(unlockable && client > 0)
|
|
{
|
|
new slot = 0;
|
|
if(playerClass[client] == TFClass_DemoMan)
|
|
slot = 1;
|
|
new itemindex = playerLoadout[client][slot][0];
|
|
switch(itemindex) // Hell of a lot easier than a set of ifs. <_<
|
|
{
|
|
case 36, 41, 45, 61, 127, 130:
|
|
index++;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
DumpHeals(client, String:addProp[] = "")
|
|
{
|
|
new curHeals = GetEntProp(client, Prop_Send, "m_iHealPoints");
|
|
new lifeHeals = curHeals - healPoints[client];
|
|
if(lifeHeals > 0)
|
|
{
|
|
decl String:szProperties[32];
|
|
Format(szProperties, sizeof(szProperties), " (healing \"%d\")%s", lifeHeals, addProp);
|
|
LogPlayerEvent(client, "triggered", "healed", _, szProperties);
|
|
}
|
|
healPoints[client] = curHeals;
|
|
}
|
|
|
|
DumpAllWeaponStats()
|
|
{
|
|
for(new i = 1; i <= MaxClients; i++)
|
|
DumpWeaponStats(i);
|
|
}
|
|
|
|
DumpWeaponStats(client)
|
|
{
|
|
if(IsClientInGame(client))
|
|
{
|
|
decl String:player_authid[64];
|
|
if(!GetClientAuthString(client, player_authid, sizeof(player_authid)))
|
|
strcopy(player_authid, sizeof(player_authid), "UNKNOWN");
|
|
new player_team = GetClientTeam(client);
|
|
new player_userid = GetClientUserId(client);
|
|
for (new i = 0; i < MAX_LOG_WEAPONS; i++)
|
|
if(weaponStats[client][i][LOG_SHOTS] > 0 || weaponStats[client][i][LOG_DEATHS] > 0)
|
|
LogToGame("\"%N<%d><%s><%s>\" triggered \"weaponstats\" (weapon \"%s\") (shots \"%d\") (hits \"%d\") (kills \"%d\") (headshots \"%d\") (tks \"%d\") (damage \"%d\") (deaths \"%d\")", client, player_userid, player_authid, g_team_list[player_team], weaponList[i], weaponStats[client][i][LOG_SHOTS], weaponStats[client][i][LOG_HITS], weaponStats[client][i][LOG_KILLS], weaponStats[client][i][LOG_HEADSHOTS], weaponStats[client][i][LOG_TEAMKILLS], weaponStats[client][i][LOG_DAMAGE], weaponStats[client][i][LOG_DEATHS]);
|
|
}
|
|
ResetWeaponStats(client);
|
|
}
|
|
|
|
ResetWeaponStats(client)
|
|
{
|
|
for(new i = 0; i < MAX_LOG_WEAPONS; i++)
|
|
weaponStats[client][i] = {0,0,0,0,0,0,0};
|
|
}
|
|
|
|
public OnConVarActionsChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_actions);
|
|
if(newval != b_actions)
|
|
{
|
|
if(newval)
|
|
{
|
|
HookEvent("player_escort_score", Event_PlayerEscortScore);
|
|
HookEvent("player_stealsandvich", Event_PlayerStealsandvich);
|
|
HookEvent("player_stunned", Event_PlayerStunned);
|
|
HookEvent("deploy_buff_banner", Event_DeployBuffBanner);
|
|
HookEvent("medic_defended", Event_MedicDefended);
|
|
HookEvent("rocket_jump", Event_RocketJump);
|
|
HookEvent("rocket_jump_landed", Event_JumpLanded);
|
|
HookEvent("sticky_jump", Event_StickyJump);
|
|
HookEvent("sticky_jump_landed", Event_JumpLanded);
|
|
HookEvent("object_deflected", Event_ObjectDeflected);
|
|
HookUserMessage(GetUserMessageId("PlayerJarated"), Event_PlayerJarated);
|
|
HookUserMessage(GetUserMessageId("PlayerShieldBlocked"), Event_PlayerShieldBlocked);
|
|
}
|
|
else
|
|
{
|
|
UnhookEvent("player_escort_score", Event_PlayerEscortScore);
|
|
UnhookEvent("player_stealsandvich", Event_PlayerStealsandvich);
|
|
UnhookEvent("player_stunned", Event_PlayerStunned);
|
|
UnhookEvent("deploy_buff_banner", Event_DeployBuffBanner);
|
|
UnhookEvent("medic_defended", Event_MedicDefended);
|
|
UnhookEvent("rocket_jump", Event_RocketJump);
|
|
UnhookEvent("rocket_jump_landed", Event_JumpLanded);
|
|
UnhookEvent("sticky_jump", Event_StickyJump);
|
|
UnhookEvent("sticky_jump_landed", Event_JumpLanded);
|
|
UnhookEvent("object_deflected", Event_ObjectDeflected);
|
|
UnhookUserMessage(GetUserMessageId("PlayerJarated"), Event_PlayerJarated);
|
|
UnhookUserMessage(GetUserMessageId("PlayerShieldBlocked"), Event_PlayerShieldBlocked);
|
|
for(new i = 1; i <= MaxClients; i++)
|
|
jumpStatus[i] = 0;
|
|
}
|
|
b_actions = newval;
|
|
}
|
|
}
|
|
|
|
|
|
public OnConVarTeleportsChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_teleports);
|
|
if(newval != b_teleports)
|
|
{
|
|
if(newval)
|
|
HookEvent("player_teleported", Event_PlayerTeleported);
|
|
else
|
|
UnhookEvent("player_teleported", Event_PlayerTeleported);
|
|
b_teleports = newval;
|
|
}
|
|
}
|
|
|
|
public OnConVarTeleportsAgainChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
b_teleports_again = GetConVarBool(cvar_teleports_again);
|
|
}
|
|
|
|
public OnConVarHeadshotsChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
b_headshots = GetConVarBool(cvar_headshots);
|
|
}
|
|
|
|
public OnConVarBackstabsChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
b_backstabs = GetConVarBool(cvar_backstabs);
|
|
}
|
|
|
|
public OnConVarSandvichChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_sandvich);
|
|
if(newval != b_sandvich)
|
|
{
|
|
if(newval)
|
|
AddNormalSoundHook(SoundHook);
|
|
else
|
|
RemoveNormalSoundHook(SoundHook);
|
|
b_sandvich = newval;
|
|
}
|
|
}
|
|
|
|
public OnConVarFireChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
b_fire = GetConVarBool(cvar_fire);
|
|
}
|
|
|
|
public OnConVarStatsChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_crits) && GetConVarBool(cvar_wstats);
|
|
if(newval != b_wstats)
|
|
{
|
|
if(!newval)
|
|
{
|
|
DumpAllWeaponStats();
|
|
}
|
|
b_wstats = newval;
|
|
}
|
|
}
|
|
|
|
public OnConVarHealsChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_heals);
|
|
if(newval != b_heals)
|
|
{
|
|
if(newval)
|
|
{
|
|
HookEvent("medic_death", Event_MedicDeath);
|
|
for(new i = 1; i <= MaxClients; i++)
|
|
if(IsClientInGame(i))
|
|
healPoints[i] = GetEntProp(i, Prop_Send, "m_iHealPoints");
|
|
}
|
|
else
|
|
{
|
|
UnhookEvent("medic_death", Event_MedicDeath);
|
|
}
|
|
b_heals = newval;
|
|
}
|
|
}
|
|
|
|
public OnConVarRolelogfixChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_rolelogfix);
|
|
if(newval != b_rolelogfix)
|
|
{
|
|
if(newval)
|
|
HookEvent("player_changeclass", Event_PlayerChangeclassPre, EventHookMode_Pre);
|
|
else
|
|
UnhookEvent("player_changeclass", Event_PlayerChangeclassPre, EventHookMode_Pre);
|
|
b_rolelogfix = newval;
|
|
}
|
|
}
|
|
|
|
public OnConVarObjlogfixChange(Handle:cvar, const String:oldVal[], const String:newVal[])
|
|
{
|
|
new bool:newval = GetConVarBool(cvar_objlogfix);
|
|
if(newval != b_objlogfix)
|
|
{
|
|
if(newval)
|
|
HookEvent("object_removed", Event_ObjectRemoved);
|
|
else
|
|
UnhookEvent("object_removed", Event_ObjectRemoved);
|
|
b_objlogfix = newval;
|
|
}
|
|
} |