#include #include #include #include #include #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; }