mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-06 02:53:21 +00:00
Add l4d2_rollback plugin
This commit is contained in:
parent
c482a1bbfb
commit
ac18fc7a10
3 changed files with 258 additions and 1 deletions
14
README.md
14
README.md
|
@ -28,6 +28,7 @@ Useful things:
|
||||||
* [l4d2_population_control](#l4d2_population_control)
|
* [l4d2_population_control](#l4d2_population_control)
|
||||||
* [l4d2_extrafinaletanks](#l4d2_extrafinaletanks)
|
* [l4d2_extrafinaletanks](#l4d2_extrafinaletanks)
|
||||||
* [globalbans](#globalbans)
|
* [globalbans](#globalbans)
|
||||||
|
* [l4d2_rollback](#l4d2_rollback)
|
||||||
|
|
||||||
### Modified Others
|
### Modified Others
|
||||||
* [200IQBots_FlyYouFools](#200IQBots_FlyYouFools)
|
* [200IQBots_FlyYouFools](#200IQBots_FlyYouFools)
|
||||||
|
@ -251,3 +252,16 @@ This plugin will store bans in a database and read from it on connect. This allo
|
||||||
It will automatically intercept any ban that calls OnBanIdentity or OnBanClient (so sm_ban will work normally)
|
It will automatically intercept any ban that calls OnBanIdentity or OnBanClient (so sm_ban will work normally)
|
||||||
* **Convars:**
|
* **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
BIN
plugins/l4d2_rollback.smx
Normal file
Binary file not shown.
243
scripting/l4d2_rollback.sp
Normal file
243
scripting/l4d2_rollback.sp
Normal 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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue