/* * Author: Needles * https://steamcommunity.com/profiles/76561198026257137/ */ printl("*** HANDLER/PROXIMITY_REVIVE"); ::ProximityRevive <- {}; ::ProximityRevive.SCOPE_KEY <- UniqueString(); ::ProximityRevive.MODEL_REANIMATOR_PATH <- "models/props_mvm/mvm_revive_tombstone.mdl"; ::ProximityRevive.SPRITE_HEALTH_PATH <- "needles/sprites/health.vmt"; ::ProximityRevive.SOUND_REVIVE_PATH <- "mvm/mvm_revive.wav"; PrecacheModel(::ProximityRevive.MODEL_REANIMATOR_PATH); PrecacheModel(::ProximityRevive.SPRITE_HEALTH_PATH); PrecacheSound(::ProximityRevive.SOUND_REVIVE_PATH); ::ProximityRevive.reviveDistance <- 256.0; ::ProximityRevive.reviveRate <- ::Const.MEDIGUN_HEAL_RATE * 0.25; // HP per second ::ProximityRevive.safetyTime <- 2.5; ::ProximityRevive.rateList <- [ 1.0, 0.5, 0.25, 0.125 ]; ::ProximityRevive.bonusFactor <- 2.0; ::ProximityRevive.disguiseFactor <- 0.5; local ProximityReanimatorHandler = class extends ::Handler.BaseHandler { gui_progressBar = null; userCount = null; userMap = null; health = null; constructor() { base.constructor(); gui_progressBar = null; health = 0.0; userCount = 0; userMap = {}; } function StartRevive(player) { if (player in userMap) { return; } local ent_particleSystem = SpawnEntityFromTable("info_particle_system", { origin = player.GetOrigin() + Vector(0.0, 0.0, 48.0), angles = player.GetAbsAngles(), effect_name = "medicgun_beam_red", start_active = true, }); NetProps.SetPropEntityArray(ent_particleSystem, "m_hControlPointEnts", self, 0); EntFireByHandle(ent_particleSystem, "SetParent", "!activator", -1.0, player, null); AddDependency(ent_particleSystem); userMap[player] <- ent_particleSystem; userCount ++; ::Debug.Print("*** PROXIMITY_REVIVE - Started revive " + player + " : " + self + " : " + userCount); } function StopRevive(player) { if (!(player in userMap)) { return; } if (userMap[player].IsValid()) { userMap[player].Destroy(); } userMap.rawdelete(player); userCount --; ::Debug.Print("*** PROXIMITY_REVIVE - Stopped revive " + player + " : " + self + " : " + userCount); } function RespawnOwner() { local respawnPosition = self.GetOrigin(); local respawnAngles = QAngle(0.0, 0.0, 0.0); foreach(player, ent_particleSystem in userMap) { if (ent_particleSystem.IsValid()) { ent_particleSystem.Destroy(); } respawnPosition = player.GetOrigin(); respawnAngles = player.GetAbsAngles(); } if (::Players.IsPlayerValid(activator) == true) { local reviveData = ::ProximityRevive.Validate(activator); if (reviveData != null) { reviveData.dontResetOnSpawn = true; reviveData.isSpawnFromRevive = true; reviveData.timeOfRevive = Time(); } ::SpawnContext.SetUniqueSpawnPoint(activator, respawnPosition, respawnAngles, 1); activator.ForceRespawn(); activator.Taunt(4, 167); // Resurrected voiceline... maybe? activator.EmitSound(::ProximityRevive.SOUND_REVIVE_PATH); activator.AddCondEx(Constants.ETFCond.TF_COND_INVULNERABLE_CARD_EFFECT, ::ProximityRevive.safetyTime, null); } } function OnEvent_PlayerSpawn(params) { // Owner is alive? Then time for me to leave! if (GetPlayerFromUserID(params.userid) == activator) { foreach(player, ent_particleSystem in userMap) { if (ent_particleSystem.IsValid()) { ent_particleSystem.Destroy(); } } } } function OnEvent_Tick(params) { if (!self || !self.IsValid()) { return; } // Sometimes the reanimator doesn't immediately initialize its health values if (self.GetMaxHealth() == 0) { return; } for (local i = 1; i <= ::Players.Max(); i++) { local player = PlayerInstanceFromIndex(i); if (player == null) continue; if ( ::Players.IsPlayerValid(player) // Valid && ::Players.IsPlayerAlive(player) // Alive && player != activator // Not the owner of the reanimator && player.GetTeam() == activator.GetTeam() // Same team && player.InCond(Constants.ETFCond.TF_COND_STEALTHED) == false // Not cloaked && (self.GetOrigin() - player.GetOrigin()).LengthSqr() <= ::ProximityRevive.reviveDistance * ::ProximityRevive.reviveDistance ) { StartRevive(player); } else { StopRevive(player); } } /* local rateMultiplier = 2.0 * userCount.tofloat() / (userCount.tofloat() + 1.0); health += ::ProximityRevive.reviveRate * rateMultiplier * params.time; self.SetHealth(health); */ // Medic ubercharge and canteens will increase the revive rate local head = 0; local sortedBonusList = []; foreach (player, _ in userMap) { if (player.GetDisguiseTeam() > Constants.ETFTeam.TEAM_SPECTATOR && player.GetDisguiseTeam() != activator.GetTeam()) { sortedBonusList.append((::Players.IsUbered(player) || ::Players.IsUsingCanteen(player)) ? ::ProximityRevive.disguiseFactor * ::ProximityRevive.bonusFactor : ::ProximityRevive.disguiseFactor); continue; } if (::Players.IsUbered(player) || ::Players.IsUsingCanteen(player)) { sortedBonusList.insert(0, ::ProximityRevive.bonusFactor); head++; continue; } sortedBonusList.insert(head, 1.0); } local totalRate = 0.0; for (local i = 0; i < sortedBonusList.len(); i++) { local bonus = sortedBonusList[i]; totalRate += ::ProximityRevive.rateList[::MathN.Min(i, ::ProximityRevive.rateList.len()-1)] * bonus; } health += ::ProximityRevive.reviveRate * totalRate * params.time; self.SetHealth(health); if (health >= self.GetMaxHealth()) { RespawnOwner(); } gui_progressBar.SetPercentage(health/self.GetMaxHealth()); } }; ::Handler.RegisterHandler("proximity_reanimator", @() ProximityReanimatorHandler()); function ProximityRevive::InitReanimator(ent_reanimator) { ::Debug.Print("*** PROXIMITY_REVIVE - Initializing reanimator " + ent_reanimator); // Stop medics from healing the reanimator ent_reanimator.SetSolid(Constants.ESolidType.SOLID_NONE); // Health indicator local ent_progressBar = SpawnEntityFromTable("env_glow", { scale = 0.3, origin = ent_reanimator.GetOrigin() + Vector(0.0, 0.0, 96.0), angles = ent_reanimator.GetAbsAngles, model = ::ProximityRevive.SPRITE_HEALTH_PATH, framerate = 0, frame = 0, rendermode = 1, spawnflags = 1, }); ::Gui.GuiSprite(ent_progressBar); local gui_progressBar = ::Gui.GuiProgressBar(ent_progressBar, ::AnimatedSprite.GetAnimationData(::ProximityRevive.SPRITE_HEALTH_PATH), 0.0); EntFireByHandle(ent_progressBar, "SetParent", "!activator", -1.0, ent_reanimator, null); local handler_proximityReanimator = ::Handler.AddHandler(ent_reanimator, "proximity_reanimator", NetProps.GetPropEntity(ent_reanimator, "m_hOwner"), -1.0, {gui_progressBar = gui_progressBar}); EntFireByHandle(ent_reanimator, "AddOutput", "targetname " + UniqueString(), -1.0, ent_reanimator, null); ::Utils.DoOnEndOfFrame(function() {::Highlight.AddHighlight(ent_reanimator, ::Color.GetTeamColor(ent_reanimator.GetTeam()))}); // @TODO - Silly workaround for delayed targetname application... Maybe AddHighlight should automatically delay application if no targetname? return handler_proximityReanimator; } function ProximityRevive::Validate(entity) { if (entity == null || entity.IsValid() == false || entity.ValidateScriptScope() == false) return null; if (::ProximityRevive.SCOPE_KEY in entity.GetScriptScope()) return entity.GetScriptScope()[::ProximityRevive.SCOPE_KEY]; local reviveData = { dontResetOnSpawn = false, // @TODO - This is dumb. Another solution would be setting isSpawnFromRevive AFTER spawn events have been fired, then whatever happens in spawn events could be delegated to an end-of-frame call. isSpawnFromRevive = false, timeOfRevive = -1.0, }; entity.GetScriptScope()[::ProximityRevive.SCOPE_KEY] <- reviveData; return reviveData; } ::Events.GetGlobalEvent(EVENT.PLAYER_SPAWN).AddListener( function(params) { local player = GetPlayerFromUserID(params.userid); local reviveData = ::ProximityRevive.Validate(player); if (reviveData != null) { if (reviveData.dontResetOnSpawn == true) reviveData.dontResetOnSpawn = false; else reviveData.isSpawnFromRevive = false; } } ); ::Events.GetGlobalEvent(EVENT.PLAYER_DEATH_POST).AddListener( function(params) { local latestReanimator = null; local ent_reanimator = null; while (ent_reanimator = Entities.FindByClassname(ent_reanimator, "entity_revive_marker")) { if (NetProps.GetPropEntity(ent_reanimator, "m_hOwner") == params.player) { //if (latestReanimator == null || ::Utils.ValidateCreationTime(ent_reanimator) > ::Utils.ValidateCreationTime(latestReanimator)) // latestReanimator = ent_reanimator; ::ProximityRevive.InitReanimator(ent_reanimator); } } //if (latestReanimator != null) // ::ProximityRevive.InitReanimator(latestReanimator); } );