mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-09 13:03:20 +00:00
Update rollback to working state
This commit is contained in:
parent
3daf334f30
commit
bf98c08272
2 changed files with 160 additions and 58 deletions
Binary file not shown.
|
@ -4,6 +4,7 @@
|
|||
//#define DEBUG
|
||||
|
||||
#define PLUGIN_VERSION "1.0"
|
||||
#define MAXIMUM_STAGES_STORED 3 //The maximum amount of ages to store
|
||||
#define LAST_FF_TIME_THRESHOLD 100.0
|
||||
#define LAST_PLAYER_JOIN_THRESHOLD 120.0
|
||||
|
||||
|
@ -18,20 +19,15 @@ public Plugin myinfo =
|
|||
{
|
||||
name = "L4D2 Rollback",
|
||||
author = "jackzmc",
|
||||
description = "",
|
||||
description = "System to save and rollback to states in the game, usually pre-troll",
|
||||
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 kitSlotItem[32];
|
||||
char pillSlotItem[32];
|
||||
|
||||
int permHealth;
|
||||
|
@ -44,10 +40,11 @@ enum struct PlayerState {
|
|||
int timeRecorded;
|
||||
}
|
||||
|
||||
static PlayerState[MAXPLAYERS+1] playerStates;
|
||||
static PlayerState[MAXIMUM_STAGES_STORED][MAXPLAYERS+1] playerStatesList; //Newest -> Oldest
|
||||
|
||||
static bool isHealing[MAXPLAYERS+1]; //Is player healing (self, or other)
|
||||
static bool isHealing[MAXPLAYERS+1], bMapStarted; //Is player healing (self, or other)
|
||||
static ConVar hMaxIncapCount, hDecayRate;
|
||||
static Handle hAutoSaveTimer;
|
||||
|
||||
static float ZERO_VECTOR[3] = {0.0, 0.0, 0.0}, lastDamageTime, lastSpawnTime;
|
||||
|
||||
|
@ -75,10 +72,12 @@ public void OnPluginStart() {
|
|||
|
||||
HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
|
||||
HookEvent("player_hurt", Event_PlayerHurt);
|
||||
HookEvent("revive_begin", Event_ReviveBegin);
|
||||
|
||||
RegAdminCmd("sm_save", Command_SaveGlobalState, ADMFLAG_ROOT, "Saves all players state");
|
||||
RegAdminCmd("sm_state", Command_ViewStateInfo, ADMFLAG_ROOT, "Views the current state info");
|
||||
RegAdminCmd("sm_restore", Command_RestoreState, ADMFLAG_ROOT, "Restores a certain player's state");
|
||||
|
||||
RegAdminCmd("sm_sstate", Command_SaveGlobalState, ADMFLAG_ROOT, "Saves all players state");
|
||||
RegAdminCmd("sm_istate", Command_ViewStateInfo, ADMFLAG_ROOT, "Views the current state info");
|
||||
RegAdminCmd("sm_rstate", Command_RestoreState, ADMFLAG_ROOT, "Restores a certain player's state");
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -87,25 +86,37 @@ public void OnPluginStart() {
|
|||
|
||||
public Action Command_SaveGlobalState(int client, int args) {
|
||||
RecordGlobalState("MANUAL");
|
||||
ReplyToCommand(client, "Saved global state");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
public Action Command_ViewStateInfo(int client, int args) {
|
||||
ReplyToCommand(client, "---== Recorded Player States ==---");
|
||||
int index = 0;
|
||||
int time = GetTime();
|
||||
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(playerStates[i].timeRecorded > 0) {
|
||||
int minutes = RoundToNearest((time - playerStates[i].timeRecorded) / 1000.0 / 60.0);
|
||||
ReplyToCommand(client, "%d. %16.16N - %20s - %-3d min. ago", ++index, i, playerStates[i].recordType, minutes);
|
||||
int time = GetTime(), index;
|
||||
for(int state = 0; state < MAXIMUM_STAGES_STORED; state++) {
|
||||
if(state == 0 || playerStatesList[state][0].timeRecorded > 0)
|
||||
ReplyToCommand(client, "---== Recorded Player States ==--- [Age: %d]", state);
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(playerStatesList[state][i].timeRecorded > 0) {
|
||||
int minutes = RoundToNearest((time - playerStatesList[state][i].timeRecorded) / 60.0);
|
||||
ReplyToCommand(client, "%2.d. %-16.16N | %-16.20s | %3d min. ago", ++index, i, playerStatesList[state][i].recordType, minutes);
|
||||
}
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
return Plugin_Handled;
|
||||
}
|
||||
public Action Command_RestoreState(int client, int args) {
|
||||
if(args < 1) {
|
||||
ReplyToCommand(client, "Usage: sm_srestore <player>");
|
||||
ReplyToCommand(client, "Usage: sm_restore <player(s)> [age=0]");
|
||||
}else{
|
||||
char arg1[32];
|
||||
char arg1[32], arg2[4];
|
||||
GetCmdArg(1, arg1, sizeof(arg1));
|
||||
GetCmdArg(2, arg2, sizeof(arg2));
|
||||
|
||||
int index = StringToInt(arg2);
|
||||
if(index > MAXIMUM_STAGES_STORED) {
|
||||
ReplyToCommand(client, "Age is above maximum amount of ages saved of %d.", MAXIMUM_STAGES_STORED);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
char target_name[MAX_TARGET_LENGTH];
|
||||
int target_list[MAXPLAYERS], target_count;
|
||||
|
@ -128,7 +139,11 @@ public Action Command_RestoreState(int client, int args) {
|
|||
int target = target_list[i];
|
||||
if(IsClientConnected(target) && IsClientInGame(target) && GetClientTeam(target) == 2) {
|
||||
RestoreState(target);
|
||||
//ReplyToCommand(client, "Restored %N's state", target);
|
||||
if(playerStatesList[index][target].timeRecorded == 0) {
|
||||
ReplyToCommand(client, "%N does not have a state, using bare state.");
|
||||
}else{
|
||||
ReplyToCommand(client, "Restored %N's state to age %d", target, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +154,17 @@ public Action Command_RestoreState(int client, int args) {
|
|||
// EVENTS
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void OnMapStart() {
|
||||
CreateTimer(120.0, Timer_AutoRecord, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
|
||||
}
|
||||
|
||||
public void OnClientPutInServer(int client) {
|
||||
if(bMapStarted) {
|
||||
RecordGlobalState("MAP_START");
|
||||
bMapStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
public Action Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
//Ignore admins
|
||||
|
@ -151,15 +177,10 @@ public Action Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBr
|
|||
}
|
||||
}
|
||||
|
||||
void OnClientDisconnect(int client) {
|
||||
public 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
|
||||
if(IsClientConnected(i) && IsClientInGame(i) && GetClientTeam(i) == 2 && !IsFakeClient(i)) {
|
||||
ResetStates(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,64 +211,134 @@ public Action Event_HealStop(Event event, const char[] name, bool dontBroadcast)
|
|||
isHealing[client] = false;
|
||||
}
|
||||
|
||||
public Action Event_ReviveBegin(Event event, const char[] name, bool dontBroadcast) {
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
int subject = GetClientOfUserId(event.GetInt("subject"));
|
||||
if(client && subject) {
|
||||
AdminId revived = GetUserAdmin(subject);
|
||||
if(revived == INVALID_ADMIN_ID && !IsFakeClient(subject)) {
|
||||
RecordGlobalState("REVIVED_NON_ADMIN");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void FTT_OnClientMarked(int troll, int marker) {
|
||||
RecordGlobalState("FTT_MARKED");
|
||||
}
|
||||
|
||||
//TODO: hook revive_begin (non-admins only?)
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// TIMERS
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
public Action Timer_AutoRecord(Handle h) {
|
||||
RecordGlobalState("AUTO_TIMER", 50000);
|
||||
}
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// METHODS
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
void RecordGlobalState(const char[] type) {
|
||||
char item[32];
|
||||
void RecordGlobalState(const char[] type, int skipTime = 0) {
|
||||
//TODO: Move playerStatesList[0][0-32] -> playerStatesList[1][0-32] -> playerStatesList[2][0-32]
|
||||
int time = GetTime();
|
||||
for(int i = MAXIMUM_STAGES_STORED - 2; i >= 0; i--) {
|
||||
TransferArray(i, i+1);
|
||||
}
|
||||
|
||||
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);
|
||||
//If skipTime set, do not record if last recording was <= skipTime ms ago
|
||||
if(skipTime > 0 && playerStatesList[0][i].timeRecorded > 0 && time - playerStatesList[0][i].timeRecorded <= skipTime) continue;
|
||||
|
||||
playerStates[i].permHealth = GetClientHealth(i);
|
||||
playerStates[i].tempHealth = GetClientHealthBuffer(i);
|
||||
playerStatesList[0][i].incapState = GetEntProp(i, Prop_Send, "m_currentReviveCount");
|
||||
playerStatesList[0][i].isAlive = IsPlayerAlive(i);
|
||||
GetClientWeaponName(i, 3, playerStatesList[0][i].kitSlotItem, 32);
|
||||
GetClientWeaponName(i, 4, playerStatesList[0][i].pillSlotItem, 32);
|
||||
|
||||
GetClientAbsOrigin(i, playerStates[i].position);
|
||||
GetClientAbsAngles(i, playerStates[i].angles);
|
||||
playerStatesList[0][i].permHealth = GetClientHealth(i);
|
||||
playerStatesList[0][i].tempHealth = GetClientHealthBuffer(i);
|
||||
|
||||
strcopy(playerStates[i].recordType, 32, type);
|
||||
playerStates[i].timeRecorded = GetTime();
|
||||
GetClientAbsOrigin(i, playerStatesList[0][i].position);
|
||||
GetClientAbsAngles(i, playerStatesList[0][i].angles);
|
||||
|
||||
strcopy(playerStatesList[0][i].recordType, 32, type);
|
||||
playerStatesList[0][i].timeRecorded = time;
|
||||
}
|
||||
playerStatesList[0][0].timeRecorded = time;
|
||||
}
|
||||
PrintToConsoleAll("[Rollback] Recorded all player states for: %s", type);
|
||||
}
|
||||
|
||||
void RestoreState(int client) {
|
||||
void TransferArray(int oldIndex, int newIndex) {
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
playerStatesList[newIndex][i].incapState = playerStatesList[oldIndex][i].incapState;
|
||||
playerStatesList[newIndex][i].isAlive = playerStatesList[oldIndex][i].isAlive;
|
||||
strcopy(playerStatesList[newIndex][i].kitSlotItem, 32, playerStatesList[oldIndex][i].kitSlotItem);
|
||||
strcopy(playerStatesList[newIndex][i].pillSlotItem, 32, playerStatesList[oldIndex][i].pillSlotItem);
|
||||
|
||||
playerStatesList[newIndex][i].permHealth = playerStatesList[oldIndex][i].permHealth;
|
||||
playerStatesList[newIndex][i].tempHealth = playerStatesList[oldIndex][i].tempHealth;
|
||||
|
||||
playerStatesList[newIndex][i].position = playerStatesList[oldIndex][i].position;
|
||||
playerStatesList[newIndex][i].angles = playerStatesList[oldIndex][i].angles;
|
||||
|
||||
strcopy(playerStatesList[newIndex][i].recordType, 32, playerStatesList[oldIndex][i].recordType);
|
||||
playerStatesList[newIndex][i].timeRecorded = playerStatesList[oldIndex][i].timeRecorded;
|
||||
}
|
||||
playerStatesList[newIndex][0].timeRecorded = playerStatesList[oldIndex][0].timeRecorded;
|
||||
}
|
||||
|
||||
void RestoreState(int client, int index = 0) {
|
||||
char item[32];
|
||||
bool isIncapped = GetEntProp(client, Prop_Send, "m_isIncapacitated") == 1;
|
||||
|
||||
bool respawned = false;
|
||||
if(!IsPlayerAlive(client) && playerStates[client].isAlive) {
|
||||
if(!IsPlayerAlive(client) && playerStatesList[index][client].isAlive) {
|
||||
SDKCall(hRoundRespawn, client);
|
||||
RequestFrame(Frame_Teleport, client);
|
||||
DataPack pack = CreateDataPack();
|
||||
pack.WriteCell(GetClientUserId(client));
|
||||
for(int i = 0; i < 3; i++) {
|
||||
pack.WriteFloat(playerStatesList[index][client].position[i]);
|
||||
}
|
||||
for(int i = 0; i < 3; i++) {
|
||||
pack.WriteFloat(playerStatesList[index][client].angles[i]);
|
||||
}
|
||||
CreateDataTimer(0.1, Timer_Teleport, pack);
|
||||
respawned = true;
|
||||
}else if(isIncapped) {
|
||||
CheatCommand(client, "give", "health", "");
|
||||
TeleportEntity(client, playerStates[client].position, playerStates[client].angles, ZERO_VECTOR);
|
||||
TeleportEntity(client, playerStatesList[index][client].position, playerStatesList[index][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);
|
||||
SetEntProp(client, Prop_Send, "m_currentReviveCount", playerStatesList[index][client].incapState);
|
||||
SetEntProp(client, Prop_Send, "m_bIsOnThirdStrike", playerStatesList[index][client].incapState >= hMaxIncapCount.IntValue);
|
||||
SetEntProp(client, Prop_Send, "m_isGoingToDie", playerStatesList[index][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", "");
|
||||
if(!StrEqual(playerStatesList[index][client].pillSlotItem, item) && !isHealing[client]) {
|
||||
CheatCommand(client, "give", item, "");
|
||||
}
|
||||
GetClientWeaponName(client, 4, item, sizeof(item));
|
||||
if(!StrEqual(playerStates[client].pillSlotItem, item)) {
|
||||
if(!StrEqual(playerStatesList[index][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);
|
||||
SetEntProp(client, Prop_Send, "m_iHealth", playerStatesList[index][client].permHealth > 0 ? playerStatesList[index][client].permHealth : 10);
|
||||
SetEntPropFloat(client, Prop_Send, "m_healthBuffer", playerStatesList[index][client].tempHealth);
|
||||
SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", GetGameTime());
|
||||
}
|
||||
|
||||
void ResetStates(int client) {
|
||||
for(int stage = 0; stage < MAXIMUM_STAGES_STORED; stage++) {
|
||||
playerStatesList[stage][client].incapState = 0;//TODO: get incap state
|
||||
playerStatesList[stage][client].pillSlotItem[0] = '\0';
|
||||
playerStatesList[stage][client].kitSlotItem[0] = '\0';
|
||||
playerStatesList[stage][client].permHealth = 0;
|
||||
playerStatesList[stage][client].tempHealth = 0.0;
|
||||
playerStatesList[stage][client].timeRecorded = 0;
|
||||
playerStatesList[stage][client].recordType[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
float GetClientHealthBuffer(int client, float defaultVal=0.0) {
|
||||
// https://forums.alliedmods.net/showpost.php?p=1365630&postcount=1
|
||||
static float healthBuffer, healthBufferTime, tempHealth;
|
||||
|
@ -257,6 +348,17 @@ float GetClientHealthBuffer(int client, float defaultVal=0.0) {
|
|||
return tempHealth < 0.0 ? defaultVal : tempHealth;
|
||||
}
|
||||
|
||||
public void Frame_Teleport(int client) {
|
||||
TeleportEntity(client, playerStates[client].position, playerStates[client].angles, ZERO_VECTOR);
|
||||
public Action Timer_Teleport(Handle handle, DataPack pack) {
|
||||
pack.Reset();
|
||||
int client = pack.ReadCell();
|
||||
float position[3], angles[3];
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
position[i] = pack.ReadFloat();
|
||||
}
|
||||
for(int i = 0; i < 3; i++) {
|
||||
angles[i] = pack.ReadFloat();
|
||||
}
|
||||
|
||||
TeleportEntity(client, position, angles, ZERO_VECTOR);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue