sm-plugins/VScripts/scripting/VScript_ze_stardust_battleground.sp

501 lines
15 KiB
SourcePawn

#include <sourcemod>
#include <sdktools>
#include <cstrike>
#include <multicolors>
#include <zombiereloaded>
#pragma semicolon 1
#pragma newdecls required
#define PARACHUTE_MDL "models/mark2580/pubg/carepackage/parachutecare.mdl"
ConVar g_cvParachuteFallSpeed;
ConVar g_cvParachuteLinear;
ConVar g_cvParachuteDecrease;
ConVar g_cvDelayPlaneLanded;
Handle g_hTimerTriggersHook = INVALID_HANDLE;
Handle g_hTimerPlaneDropTrigger = INVALID_HANDLE;
Handle g_hPlaneDoorOpen = INVALID_HANDLE;
Handle g_hTimerPlaneEndPath = INVALID_HANDLE;
int g_iVelocity = -1;
int g_iParachuteEntity[MAXPLAYERS+1];
int g_iTriggerTeleportID = 0;
int g_iTriggerTemplateID = 0;
int g_iTriggerMultipleID = 0;
int g_iPlaneEndPathID = 0;
bool g_bFallSpeed[MAXPLAYERS +1] = {false, ...};
bool g_bInUse[MAXPLAYERS+1] = {false, ...};
bool g_bHasParachuteModel[MAXPLAYERS+1] = {false, ...};
bool g_bRoundStarted = false;
bool g_bPlayerJumpedFormPlane[MAXPLAYERS+1] = {false, ...};
bool g_bPlayerLanded[MAXPLAYERS+1] = {false, ...};
bool g_bParaLinearFallSpeed = false;
float g_fFallSpeed = 100.0;
float g_fDecrease = 0.0;
float g_fAfterExplodeDelay = 45.0;
// Based on SWAT_88's plugin from SRCDSLAB
public Plugin myinfo =
{
name = "VScript - ze_stardust_battleground",
author = ".Rushaway & SWAT_88",
description = "Give a single parachute to player for jumping out of the plane.",
version = "1.0.0",
url = "https://github.com/srcdslab/sm-plugin-Parachute"
};
public void OnPluginStart()
{
g_cvParachuteLinear = CreateConVar("sm_stardust_parachute_linear", "1", "0: disables linear fallspeed - 1: enables it");
g_cvParachuteFallSpeed = CreateConVar("sm_stardust_parachute_fallspeed", "135", "Speed of the fall when you use the parachute");
g_cvParachuteDecrease = CreateConVar("sm_stardust_parachute_decrease", "75", "0: dont use Realistic velocity-decrease - x: sets the velocity-decrease");
g_cvDelayPlaneLanded = CreateConVar("sm_stardust_parachute_delay", "45", "Remove parachute after x seconds of the plane exploded");
g_iVelocity = FindSendPropInfo("CBasePlayer", "m_vecVelocity[0]");
g_bParaLinearFallSpeed = g_cvParachuteLinear.BoolValue;
g_fFallSpeed = g_cvParachuteFallSpeed.FloatValue;
g_fDecrease = g_cvParachuteDecrease.FloatValue;
g_fAfterExplodeDelay = g_cvDelayPlaneLanded.FloatValue;
HookConVarChange(g_cvParachuteLinear, OnConVarChanged);
HookConVarChange(g_cvParachuteFallSpeed, OnConVarChanged);
HookConVarChange(g_cvParachuteDecrease, OnConVarChanged);
HookConVarChange(g_cvDelayPlaneLanded, OnConVarChanged);
AutoExecConfig(true);
}
public void OnMapStart()
{
VerifyMap(false);
}
public void VerifyMap(bool bForceUnload)
{
char sCurMap[256];
GetCurrentMap(sCurMap, sizeof(sCurMap));
bool bValidMap = strncmp(sCurMap, "ze_stardust_battleground", 24, false) == 0;
LogMessage("bValidMap %d", bValidMap);
if (bForceUnload || !bValidMap || g_iVelocity == -1)
{
if (bValidMap && g_iVelocity == -1)
SetFailState("Failed to find m_vecVelocity[0] for CBasePlayer");
//better to let the server control loading and unloading than the plugin itself.
//having many plugins unload/load on mapchange can cause very slow mapchanges.
}
else
{
// Is it needed if the model is already present in the map?
// PrecacheModel("models/mark2580/pubg/carepackage");
// AddFileToDownloadsTable(Line);
HookEvent("player_death", Event_PlayerDeath);
HookEvent("round_start", Event_OnRoundStart);
HookEvent("round_end", Event_OnRoundEnd);
}
}
public void OnMapEnd()
{
EndRoundCleanUp();
g_hTimerTriggersHook = INVALID_HANDLE;
g_hTimerPlaneDropTrigger = INVALID_HANDLE;
g_hPlaneDoorOpen = INVALID_HANDLE;
g_hTimerPlaneEndPath = INVALID_HANDLE;
UnhookEvent("player_death", Event_PlayerDeath);
UnhookEvent("round_start", Event_OnRoundStart);
UnhookEvent("round_end", Event_OnRoundEnd);
// Force plugin to unload
VerifyMap(true);
}
public void OnConVarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
{
if (convar == g_cvParachuteLinear)
g_bParaLinearFallSpeed = g_cvParachuteLinear.BoolValue;
else if (convar == g_cvParachuteFallSpeed)
g_fFallSpeed = g_cvParachuteFallSpeed.FloatValue;
else if (convar == g_cvParachuteDecrease)
g_fDecrease = g_cvParachuteDecrease.FloatValue;
else if (convar == g_cvDelayPlaneLanded)
g_fAfterExplodeDelay = g_cvDelayPlaneLanded.FloatValue;
}
public void OnClientDisconnect(int client)
{
StopParachute(client);
}
public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast)
{
int client = GetClientOfUserId(event.GetInt("userid"));
StopParachute(client);
}
public void Event_OnRoundStart(Event event, const char[] name, bool dontBroadcast)
{
g_bRoundStarted = false;
// We use 5.0 seconds to let the map initialize the entities and dont stress the server
g_hTimerTriggersHook = CreateTimer(5.0, Timer_HookTriggers, _, TIMER_FLAG_NO_MAPCHANGE);
}
public void Event_OnRoundEnd(Event event, const char[] name, bool dontBroadcast)
{
EndRoundCleanUp();
}
public Action ZR_OnClientInfect(int &client, int &attacker, bool &motherInfect, bool &respawnOverride, bool &respawn)
{
MarkPlayerAsLanded(client);
StopParachute(client);
return Plugin_Stop;
}
// We need to use OnGameFrame instead of OnPlayerRunCmd for more accurate parachute position rendering
public void OnGameFrame()
{
if (!g_bRoundStarted)
return;
for (int client = 1; client <= MaxClients; client++)
{
if (IsClientInGame(client) && !g_bPlayerLanded[client] && IsPlayerAlive(client))
{
if (GetClientButtons(client) & IN_USE)
{
if (!g_bInUse[client])
{
g_bInUse[client] = true;
g_bFallSpeed[client] = false;
StartParachute(client, true);
}
// else
// StartParachute(client, false);
TeleportParachute(client);
}
else
{
// Player released the key, parachute will be released
if (g_bInUse[client])
{
MarkPlayerAsLanded(client);
StopParachute(client);
CPrintToChat(client, "{pink}[VScripts] {white}You released your parachute.");
}
}
CheckClientLocation(client);
}
}
}
stock void StartParachute(int client, bool bOpen)
{
float fVelocity[3];
GetEntDataVector(client, g_iVelocity, fVelocity);
float fFallSpeed = g_fFallSpeed * (-1.0);
if (fVelocity[2] >= fFallSpeed)
g_bFallSpeed[client] = true;
if (fVelocity[2] < 0.0)
{
if ((g_bParaLinearFallSpeed && g_bFallSpeed[client]) || g_fDecrease == 0.0)
fVelocity[2] = fFallSpeed;
else
fVelocity[2] = fVelocity[2] + g_fDecrease;
TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, fVelocity);
SetEntDataVector(client, g_iVelocity, fVelocity);
SetEntityGravity(client, 0.1);
if (bOpen)
OpenParachute(client);
}
}
stock void OpenParachute(int client)
{
g_iParachuteEntity[client] = CreateEntityByName("prop_dynamic");
if (!g_iParachuteEntity[client])
return;
DispatchKeyValue(g_iParachuteEntity[client], "model", PARACHUTE_MDL);
DispatchKeyValue(g_iParachuteEntity[client], "DefaultAnim", "default");
DispatchKeyValue(g_iParachuteEntity[client], "solid", "0");
DispatchKeyValue(g_iParachuteEntity[client], "spawnflags", "256");
DispatchKeyValue(g_iParachuteEntity[client], "rendermode", "1");
DispatchKeyValue(g_iParachuteEntity[client], "rendercolor", "255 255 255");
DispatchKeyValue(g_iParachuteEntity[client], "renderamt", "255");
// Original packed model is big++ if we dont resize it, players views will be blocked when they jump off from the plane
DispatchKeyValue(g_iParachuteEntity[client], "modelscale", "0.7");
DispatchKeyValue(g_iParachuteEntity[client], "disablereceiveshadows", "1");
DispatchKeyValue(g_iParachuteEntity[client], "disableshadows", "1");
SetEntityMoveType(g_iParachuteEntity[client], MOVETYPE_NOCLIP);
DispatchSpawn(g_iParachuteEntity[client]);
g_bHasParachuteModel[client] = true;
TeleportParachute(client);
}
stock void TeleportParachute(int client)
{
if (HasValidParachute(client))
{
float fClient_Origin[3], fClient_Angles[3], fParachute_Angles[3];
GetClientAbsOrigin(client, fClient_Origin);
// Adjust the position of the parachute model
fClient_Origin[2] += 60.0;
GetClientAbsAngles(client, fClient_Angles);
fParachute_Angles[1] = fClient_Angles[1];
TeleportEntity(g_iParachuteEntity[client], fClient_Origin, fParachute_Angles, NULL_VECTOR);
}
}
stock void StopParachute(int client)
{
SetEntityGravity(client, 1.0);
DeleteParachute(client);
g_bInUse[client] = false;
g_bFallSpeed[client] = false;
}
stock void DeleteParachute(int client)
{
if (HasValidParachute(client))
{
AcceptEntityInput(g_iParachuteEntity[client], "Kill");
g_bHasParachuteModel[client] = false;
}
}
stock void CheckClientLocation(int client)
{
float fSpeed[3];
GetEntDataVector(client, g_iVelocity, fSpeed);
int iFlag = GetEntityFlags(client);
if (fSpeed[2] >= 0 || iFlag & FL_ONGROUND)
{
StopParachute(client);
if (g_bPlayerJumpedFormPlane[client] && iFlag & FL_ONGROUND)
{
MarkPlayerAsLanded(client);
CPrintToChat(client, "{pink}[VScripts] {white}You have landed.");
}
}
}
stock void EndRoundCleanUp()
{
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i))
StopParachute(i);
}
if (g_hTimerTriggersHook != INVALID_HANDLE && CloseHandle(g_hTimerTriggersHook))
g_hTimerTriggersHook = INVALID_HANDLE;
if (g_hTimerPlaneDropTrigger != INVALID_HANDLE && CloseHandle(g_hTimerPlaneDropTrigger))
g_hTimerPlaneDropTrigger = INVALID_HANDLE;
if (g_hPlaneDoorOpen != INVALID_HANDLE && CloseHandle(g_hPlaneDoorOpen))
g_hPlaneDoorOpen = INVALID_HANDLE;
if (g_hTimerPlaneEndPath != INVALID_HANDLE && CloseHandle(g_hTimerPlaneEndPath))
g_hTimerPlaneEndPath = INVALID_HANDLE;
g_iTriggerTemplateID = 0;
g_iTriggerTeleportID = 0;
g_iTriggerMultipleID = 0;
g_iPlaneEndPathID = 0;
VerifyStartHookedEntity();
VerifyPlaneJumpHookedEntity();
}
/* TIMERS TO HOOKS ENTITIES */
public Action Timer_HookTriggers(Handle timer)
{
g_hTimerTriggersHook = INVALID_HANDLE;
// Credits
char sBuffer[64];
Handle hPlugin = GetMyHandle();
GetPluginInfo(hPlugin, PlInfo_Author, sBuffer, sizeof(sBuffer));
CPrintToChatAll("{pink}[VScripts] {white}Map using VScripts made by %s", sBuffer);
delete hPlugin;
// Plante template is spawned
g_iTriggerTemplateID = FindEntityByTargetname(0, "player_drop", "point_template");
if (g_iTriggerTemplateID != 0)
HookSingleEntityOutput(g_iTriggerTemplateID, "OnUser1", CallBack_OnPlaneTemplateSpawn);
// Humans getting teleported inside the plane
g_iTriggerTeleportID = FindEntityByTargetname(0, "teleport_spawn_ct", "trigger_teleport");
if (g_iTriggerTeleportID != 0)
HookSingleEntityOutput(g_iTriggerTeleportID, "OnUser1", CallBack_OnPlayerTeleportInPlane);
if (!VerifyStartHookedEntity())
CPrintToChatAll("{pink}[VScripts] {red}An error was caught. No parachute this round.");
return Plugin_Stop;
}
public Action Timer_HookPlaneDropTrigger(Handle timer)
{
g_hTimerPlaneDropTrigger = INVALID_HANDLE;
g_iTriggerMultipleID = FindEntityByTargetname(0, "plane_player_drop", "trigger_multiple");
if (g_iTriggerMultipleID != 0)
HookSingleEntityOutput(g_iTriggerMultipleID, "OnEndTouch", CallBack_OnPlayerJumpedFromPlane);
g_iPlaneEndPathID = FindEntityByTargetname(0, "player_drop_path02", "path_track");
if (g_iPlaneEndPathID != 0)
HookSingleEntityOutput(g_iPlaneEndPathID, "OnPass", CallBack_OnPlaneEndPath);
if (!VerifyPlaneJumpHookedEntity())
CPrintToChatAll("{pink}[VScripts] {red}An error was caught. No parachute this round.");
return Plugin_Stop;
}
public Action Timer_PlaneHasExploded(Handle timer)
{
g_hTimerPlaneEndPath = INVALID_HANDLE;
g_bRoundStarted = false;
for (int i = 1; i <= MaxClients; i++)
{
if (IsClientInGame(i) && !g_bPlayerLanded[i])
{
g_bPlayerLanded[i] = true;
StopParachute(i);
CPrintToChat(i, "{pink}[VScripts] {white}You parachute have a problem, prepare for impact!");
}
}
return Plugin_Stop;
}
public Action Timer_OnPlayerTeleportInPlane(Handle timer)
{
g_hPlaneDoorOpen = INVALID_HANDLE;
g_bRoundStarted = true;
for (int i = 1; i <= MaxClients; i++)
{
if (!IsClientInGame(i))
continue;
if (GetClientTeam(i) != CS_TEAM_CT)
continue;
InitPlayerToJumpFromPlane(i);
}
return Plugin_Stop;
}
/* HOOKS ENTITY CALLBACKS */
stock void CallBack_OnPlaneTemplateSpawn(const char[] output, int caller, int activator, float delay)
{
// "OnUser1" "player_drop_prop,FireUser1,,5,1"
// So we add a delay of 0.1 seconds to hook the players dropping from the plane trigger
g_hTimerPlaneDropTrigger = CreateTimer(5.1, Timer_HookPlaneDropTrigger, _, TIMER_FLAG_NO_MAPCHANGE);
}
stock void CallBack_OnPlayerTeleportInPlane(const char[] output, int caller, int activator, float delay)
{
g_hPlaneDoorOpen = CreateTimer(8.0, Timer_OnPlayerTeleportInPlane, _, TIMER_FLAG_NO_MAPCHANGE);
}
stock void CallBack_OnPlayerJumpedFromPlane(const char[] output, int caller, int activator, float delay)
{
g_bPlayerJumpedFormPlane[activator] = true;
}
stock void CallBack_OnPlaneEndPath(const char[] output, int caller, int activator, float delay)
{
// Plane reached the end of the path_track - Some players may still in the air with parachute
// Give them 45 seconds to land
if (g_fAfterExplodeDelay <= 0.0)
g_fAfterExplodeDelay = 45.0;
g_hTimerPlaneEndPath = CreateTimer(g_fAfterExplodeDelay, Timer_PlaneHasExploded, _, TIMER_FLAG_NO_MAPCHANGE);
}
/* UTILS */
stock bool VerifyStartHookedEntity()
{
if (g_iTriggerTeleportID != 0)
return true;
UnhookSingleEntityOutput(g_iTriggerTemplateID, "OnUser1", CallBack_OnPlaneTemplateSpawn);
UnhookSingleEntityOutput(g_iTriggerTeleportID, "OnUser1", CallBack_OnPlayerTeleportInPlane);
return false;
}
stock bool VerifyPlaneJumpHookedEntity()
{
if (g_iTriggerMultipleID != 0 || g_iPlaneEndPathID != 0)
return true;
UnhookSingleEntityOutput(g_iTriggerMultipleID, "OnEndTouch", CallBack_OnPlayerJumpedFromPlane);
UnhookSingleEntityOutput(g_iPlaneEndPathID, "OnPass", CallBack_OnPlaneEndPath);
return false;
}
stock bool HasValidParachute(int client)
{
return g_bHasParachuteModel[client] && IsValidEntity(g_iParachuteEntity[client]);
}
stock void InitPlayerToJumpFromPlane(int client)
{
g_bPlayerLanded[client] = false;
g_bPlayerJumpedFormPlane[client] = false;
}
stock void MarkPlayerAsLanded(int client)
{
g_bPlayerLanded[client] = true;
}
public int FindEntityByTargetname(int entity, const char[] sTargetname, const char[] sClassname)
{
if(sTargetname[0] == '#') // HammerID
{
int HammerID = StringToInt(sTargetname[1]);
while((entity = FindEntityByClassname(entity, sClassname)) != 0)
{
if(GetEntProp(entity, Prop_Data, "m_iHammerID") == HammerID)
return entity;
}
}
else // Targetname
{
int Wildcard = FindCharInString(sTargetname, '*');
char sTargetnameBuf[64];
while((entity = FindEntityByClassname(entity, sClassname)) != 0)
{
if(GetEntPropString(entity, Prop_Data, "m_iName", sTargetnameBuf, sizeof(sTargetnameBuf)) <= 0)
continue;
if(strncmp(sTargetnameBuf, sTargetname, Wildcard) == 0)
return entity;
}
}
return 0;
}