Add l4d2_rollback plugin

This commit is contained in:
Jackzie 2021-04-25 15:58:36 -05:00
parent c482a1bbfb
commit ac18fc7a10
No known key found for this signature in database
GPG key ID: 1E834FE36520537A
3 changed files with 258 additions and 1 deletions

View file

@ -28,6 +28,7 @@ Useful things:
* [l4d2_population_control](#l4d2_population_control)
* [l4d2_extrafinaletanks](#l4d2_extrafinaletanks)
* [globalbans](#globalbans)
* [l4d2_rollback](#l4d2_rollback)
### Modified Others
* [200IQBots_FlyYouFools](#200IQBots_FlyYouFools)
@ -250,4 +251,17 @@ This plugin will automatically spawn an extra amount of tanks (determined by `l4
This plugin will store bans in a database and read from it on connect. This allows you to easily have bans global between servers.
It will automatically intercept any ban that calls OnBanIdentity or OnBanClient (so sm_ban will work normally)
* **Convars:**
* `sm_hKickOnDBFailure <0/1>` - Should the plugin kick players if it cannot connect to the database?
* `sm_hKickOnDBFailure <0/1>` - Should the plugin kick players if it cannot connect to the database?
### l4d2_rollback
An idea that you can either manually or have events (friendly fire, new player joining) trigger saving all the player's states. Then if say, a troll comes and kills you and/or incaps your team, you can just quick restore to exactly the point you were at with the same items, health, etc.
Currently **in development.**
Currently auto triggers:
1. On any recent friendly fire (only triggers once per 100 game ticks)
2. Any new player joins (only triggers once per 100 game ticks)
* **Commands:**
* `sm_sstate` - Initiates a manual save of all player's states
* `sm_rstate <player(s)>` - Restores the selected player's state. @all for all

BIN
plugins/l4d2_rollback.smx Normal file

Binary file not shown.

243
scripting/l4d2_rollback.sp Normal file
View file

@ -0,0 +1,243 @@
#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#define LAST_FF_TIME_THRESHOLD 100.0
#define LAST_PLAYER_JOIN_THRESHOLD 120.0
#include <sourcemod>
#include <sdktools>
#include <jutils>
#include <left4dhooks>
static Handle hRoundRespawn;
public Plugin myinfo =
{
name = "L4D2 Rollback",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = ""
};
/*
Allows you to rollback to state,
auto recorded at: player join, or FF event
*/
enum struct PlayerState {
int incapState; //0 -> Not incapped, # -> # of incap
bool isAlive;
bool hasKit;
char pillSlotItem[32];
int permHealth;
float tempHealth;
float position[3];
float angles[3];
}
static PlayerState[MAXPLAYERS+1] playerStates;
static bool isHealing[MAXPLAYERS+1]; //Is player healing (self, or other)
static ConVar hMaxIncapCount, hDecayRate;
static float ZERO_VECTOR[3] = {0.0, 0.0, 0.0}, lastDamageTime, lastSpawnTime;
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D/L4D2 only.");
}
Handle hGameConf = LoadGameConfigFile("left4dhooks.l4d2");
if (hGameConf != INVALID_HANDLE) {
StartPrepSDKCall(SDKCall_Player);
PrepSDKCall_SetFromConf(hGameConf, SDKConf_Signature, "RoundRespawn");
hRoundRespawn = EndPrepSDKCall();
if (hRoundRespawn == INVALID_HANDLE) SetFailState("L4D2_Rollback: RoundRespawn Signature broken");
} else {
SetFailState("Could not find gamedata: l4d2_rollback.txt.");
}
hMaxIncapCount = FindConVar("survivor_max_incapacitated_count");
hDecayRate = FindConVar("pain_pills_decay_rate");
HookEvent("heal_begin", Event_HealBegin);
HookEvent("heal_end", Event_HealStop);
HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
HookEvent("player_hurt", Event_PlayerHurt);
RegAdminCmd("sm_sstate", Command_SaveGlobalState, ADMFLAG_ROOT, "Saves all players state");
RegAdminCmd("sm_rstate", Command_RestoreState, ADMFLAG_ROOT, "Restores a certain player's state");
}
// /////////////////////////////////////////////////////////////////////////////
// COMMANDS
// /////////////////////////////////////////////////////////////////////////////
public Action Command_SaveGlobalState(int client, int args) {
RecordGlobalState();
ReplyToCommand(client, "Saved global state.");
}
public Action Command_RestoreState(int client, int args) {
if(args < 1) {
ReplyToCommand(client, "Usage: sm_srestore <player>");
}else{
char arg1[32];
GetCmdArg(1, arg1, sizeof(arg1));
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;
}
for (int i = 0; i < target_count; i++) {
int target = target_list[i];
if(IsClientConnected(target) && IsClientInGame(target) && GetClientTeam(target) == 2) {
RestoreState(target);
//ReplyToCommand(client, "Restored %N's state", target);
}
}
}
return Plugin_Handled;
}
// /////////////////////////////////////////////////////////////////////////////
// EVENTS
// /////////////////////////////////////////////////////////////////////////////
public Action Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
float time = GetGameTime();
if(time - lastSpawnTime >= LAST_PLAYER_JOIN_THRESHOLD) {
RecordGlobalState();
PrintToConsoleAll("[Rollback] Saving global state.");
lastSpawnTime = time;
}
}
void OnClientDisconnect(int client) {
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
playerStates[i].incapState = 0;//TODO: get incap state
playerStates[i].wasKilled = false;
players[i].pillSlotItem[0] = '\0';
playerStates[i].hasKit = false;
playerStates[i].prePermHealth = 0;
//TODO: record temp health
}
}
}
public Action Event_PlayerHurt(Event event, const char[] name, bool dontBroadcast) {
float currentTime = GetGameTime();
if(currentTime - lastDamageTime >= LAST_FF_TIME_THRESHOLD) {
int client = GetClientOfUserId(event.GetInt("userid"));
int attackerID = event.GetInt("attacker");
int damage = event.GetInt("dmg_health");
PrintToChatAll("PLAYER_HURT | V %N | A #%d | DMG %d", client, attackerID, damage);
if(client && GetClientTeam(client) == 2 && attackerID > 0 && damage > 0) {
int attacker = GetClientOfUserId(attackerID);
if(GetClientTeam(attacker) == 2) {
lastDamageTime = GetGameTime();
RecordGlobalState();
PrintToConsoleAll("[Rollback] Saving global state due to FF damage");
}
}
}
}
public Action Event_HealBegin(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
isHealing[client] = true;
}
public Action Event_HealStop(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
isHealing[client] = false;
}
// /////////////////////////////////////////////////////////////////////////////
// METHODS
// /////////////////////////////////////////////////////////////////////////////
void RecordGlobalState() {
char item[32];
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2) {
playerStates[i].incapState = GetEntProp(i, Prop_Send, "m_currentReviveCount");
playerStates[i].isAlive = IsPlayerAlive(i);
GetClientWeaponName(i, 3, item, sizeof(item));
playerStates[i].hasKit = StrEqual(item, "weapon_first_aid_kit");
GetClientWeaponName(i, 4, playerStates[i].pillSlotItem, 32);
playerStates[i].permHealth = GetClientHealth(i);
playerStates[i].tempHealth = GetClientHealthBuffer(i);
GetClientAbsOrigin(i, playerStates[i].position);
GetClientAbsAngles(i, playerStates[i].angles);
}
}
}
void RestoreState(int client) {
char item[32];
bool isIncapped = GetEntProp(client, Prop_Send, "m_isIncapacitated") == 1;
bool respawned = false;
if(!IsPlayerAlive(client) && playerStates[client].isAlive) {
SDKCall(hRoundRespawn, client);
RequestFrame(Frame_Teleport, client);
respawned = true;
}else if(isIncapped) {
CheatCommand(client, "give", "health", "");
TeleportEntity(client, playerStates[client].position, playerStates[client].angles, ZERO_VECTOR);
}
SetEntProp(client, Prop_Send, "m_currentReviveCount", playerStates[client].incapState);
SetEntProp(client, Prop_Send, "m_bIsOnThirdStrike", playerStates[client].incapState >= hMaxIncapCount.IntValue);
SetEntProp(client, Prop_Send, "m_isGoingToDie", playerStates[client].incapState >= hMaxIncapCount.IntValue);
if(!respawned) {
GetClientWeaponName(client, 3, item, sizeof(item));
if(playerStates[client].hasKit && !StrEqual(item, "weapon_first_aid_kit") && !isHealing[client]) {
CheatCommand(client, "give", "first_aid_kit", "");
}
GetClientWeaponName(client, 4, item, sizeof(item));
if(!StrEqual(playerStates[client].pillSlotItem, item)) {
CheatCommand(client, "give", item, "");
}
}
SetEntProp(client, Prop_Send, "m_iHealth", playerStates[client].permHealth);
SetEntPropFloat(client, Prop_Send, "m_healthBuffer", playerStates[client].tempHealth);
SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", GetGameTime());
}
float GetClientHealthBuffer(int client, float defaultVal=0.0) {
// https://forums.alliedmods.net/showpost.php?p=1365630&postcount=1
static float healthBuffer, healthBufferTime, tempHealth;
healthBuffer = GetEntPropFloat(client, Prop_Send, "m_healthBuffer");
healthBufferTime = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime");
tempHealth = healthBuffer - (healthBufferTime / (1.0 / hDecayRate.FloatValue));
return tempHealth < 0.0 ? defaultVal : tempHealth;
}
public void Frame_Teleport(int client) {
TeleportEntity(client, playerStates[client].position, playerStates[client].angles, ZERO_VECTOR);
}