PrecacheModel("models/bots/bot_worker/bot_worker_powercore.mdl") PrecacheSound("misc/null.wav") //if("DynamicReanimators" in getroottable()) return if(!("ExplanationPrinted" in getroottable())) { ::ExplanationPrinted <- {} } ::DynamicReanimators <- { // CONFIGURABLES // basic healPower = 0.25 // revive effectiveness (on the dynamic reanimator) is multiplied by this number reviveDifficultyScaling = 10.0 //maxhealth of the reanimator increases by this much per revive of the player uberAfterRevive = 1 // # seconds of uber to revived player upon any revive, set to 0 for no uber respawnIfHazardDeath = true // respawns player immediately at spawn if they die to map hazard (i.e. falling in a pit); counts towards increasing reanimator difficulty printExplanationWhenPlayerJoins = true // prints the explanation to any player that joins customExplanation = "\x07FEEF07This mission uses Dynamic Reanimators: Revive your allies by attacking the reanimator device. Closer attacks are more effective" reviveOnTeamWipe = -1 // revive all players on team wipe, value is number of these teamwipe revives per wave (-1 is unlimited, 0 is disabled) // advanced maxDist = 1500.0 // max distance for lowering revive effectiveness i.e. effectiveness does not get even lower at distances greater than this value minDistScaling = 0.3 // revive effectiveness does not go lower than this number from dist i.e. maximum of -70% effectiveness from dist scaling //WIP playerTeam = 2 // not finished SetDifficultyVeryHard = function() { healPower = 0.15 // amount that the reanimator is healed by is multiplied by this number reviveDifficultyScaling = 100.0 // maxhealth of the reanimator increases by this much per revive of the player uberAfterRevive = 0 // seconds of uber to revived player upon any revive, set to 0 for no uber } SetDifficultyHard = function() { healPower = 0.2 // amount that the reanimator is healed by is multiplied by this number reviveDifficultyScaling = 50.0 // maxhealth of the reanimator increases by this much per revive of the player uberAfterRevive = 0 // seconds of uber to revived player upon any revive, set to 0 for no uber } SetDifficultyEasy = function() { healPower = 0.4 // amount that the reanimator is healed by is multiplied by this number reviveDifficultyScaling = 10.0 // maxhealth of the reanimator increases by this much per revive of the player uberAfterRevive = 2 // seconds of uber to revived player upon any revive, set to 0 for no uber } Cleanup = function() { // cleanup any persistent changes here // keep this at the end delete ::DynamicReanimators } OnGameEvent_recalculate_holidays = function(_) { if (GetRoundState() == 3) Cleanup() } playerManager = FindByClassname(null, "tf_player_manager") players = {} numRevives = {} deathCount = 0 OnGameEvent_mvm_begin_wave = function(_) { numRevives = {} deathCount = 0 } // crit_type = 0 // damage_type = 2232322 // early_out = false // damage_bonus = 0 // damage_force = (vector : (1608.175293, 1740.894897, -378.226654) // damage_position = (vector : (1362.746338, -3225.569580, 26.017239) // damage = 16 // const_entity = ([418] base_boss: ReanimatorBB) // inflictor = ([263] obj_sentrygun) // force_friendly_fire = false // ammo_type = 1 // player_penetration_count = 0 // reported_position = (vector : (0.000000, 0.000000, 0.000000) // damaged_other_players = 0 // damage_bonus_provider = null // attacker = ([1] player) // damage_custom = 0 // const_base_damage = 16 // damage_stats = 0 // max_damage = 16 // damage_for_force_calc = 0 // weapon = ([1299] tf_weapon_pda_engineer_build) OnScriptHook_OnTakeDamage = function(params) { local ent = params.const_entity; if(ent.GetClassname() == "base_boss" && ent.GetName().find("ReanimatorBB") != null) { //ClientPrint(null, 3, "damage: " + params.damage.tostring()) local scope = ent.GetScriptScope() if(("player" in scope) && scope.player.IsValid() && NetProps.GetPropInt(scope.player, "m_lifeState") != 0) { local attacker = params.attacker Reanimate(scope.player, attacker, params.damage, params.inflictor) ent.SetHealth(9999) } else if (("player" in scope) && scope.player.IsValid()) { EntFire("!activator", "kill", "", 0, ent) } } } //OnGameEvent_npc_hurt = function(params) { // local ent = EntIndexToHScript(params.entindex) // if(ent.GetClassname() == "base_boss" && ent.GetName().find("ReanimatorBB") != null) { // __DumpScope(1, params) // ClientPrint(null, 3, "weapon " + EntIndexToHScript(params.weaponid).tostring()) // local scope = ent.GetScriptScope() // if(("player" in scope) && scope.player.IsValid() && NetProps.GetPropInt(scope.player, "m_lifeState") != 0) { // local attacker = GetPlayerFromUserID(params.attacker_player) // Reanimate(scope.player, attacker, params.damageamount) // ent.SetHealth(9999) // } else if (("player" in scope) && scope.player.IsValid()) { // EntFire("!activator", "kill", "", 0, ent) // } // } //} IsValidPlayer = function(player) { return player != null && player.IsValid() && !player.IsBotOfType(1337) && player.GetTeam() == playerTeam } OnGameEvent_player_death = function(params) { deathCount++ local player = GetPlayerFromUserID(params.userid); if (IsValidPlayer(player)) { local inflictor = EntIndexToHScript(params.inflictor_entindex) // Debugging info // __DumpScope(1, params) // if (params.inflictor_entindex != null) ClientPrint(null, 3, player.tostring() + " killed by inflictor " + inflictor.tostring()) // if (params.weapon != null) { // ClientPrint(null, 3, player.tostring() + " killed by weapon " + params.weapon.tostring()) // } // local assister = EntIndexToHScript(params.assister) // if (params.assister != -1) ClientPrint(null, 3, player.tostring() + " killed by assister " + assister.tostring()) // else ClientPrint(null, 3, player.tostring() + " killed by assister " + params.assister.tostring()) //EntFire("!activator", "RunScriptCode", "DynamicReanimators.SpawnReanimator(self)", 0.2, player) // purpose of respawnIfHazardDeath mechanic: if player is killed by something that kills the reanimator, we should bring them back sooner rather than later if (((inflictor != null && inflictor.GetClassname() == "trigger_hurt" && params.weapon.find("world") != null) || (player == inflictor && params.weapon.find("world") == null))) { if(respawnIfHazardDeath) { EntFire("!activator", "RunScriptCode", "DynamicReanimators.ManualRespawnPlayer(self)", 5, player) ClientPrint(player,3,"\x07FEEF07" + "Accidental Death - Activating Revive for Babies") } } else { EntFire("!activator", "RunScriptCode", "DynamicReanimators.SpawnReanimator(self)", 0.7, player) } //m_flNextRespawnTime //EntFire("!activator", "RunScriptCode", "ClientPrint(self,4,NetProps.GetPropFloat(self, `m_flDeathTime`).tostring() + ` nextrespawntime time ` + GetPropFloatArray(DynamicReanimators.playerManager, `m_flNextRespawnTime`, self.entindex()).tostring())",0.1,player) //GetPropFloatArray(playerManager, "m_flNextRespawnTime", player.entindex()) //SetPropFloatArray(playerManager, "m_flNextRespawnTime", Time() + 4, player.entindex()) if(GetRoundState() == 4) { //reviveOnTeamWipe != 0 && EntFire("!activator", "RunScriptCode", "DynamicReanimators.CheckIfTeamWipeAndRevive()", 0.1, player) } player.GetScriptScope().playerPrompted <- false player.GetScriptScope().deathPos <- null } } // Courtesy of https://developer.valvesoftware.com/wiki/Team_Fortress_2/Scripting/Script_Functions SetDestroyCallback = function(entity, callback) { entity.ValidateScriptScope() local scope = entity.GetScriptScope() scope.setdelegate({}.setdelegate({ parent = scope.getdelegate() id = entity.GetScriptId() index = entity.entindex() callback = callback _get = function(k) { return parent[k] } _delslot = function(k) { if (k == id) { entity = EntIndexToHScript(index) local scope = entity.GetScriptScope() scope.self <- entity callback.pcall(scope) } delete parent[k] } }) ) } CheckIfTeamWipeAndRevive = function() { //ClientPrint(null, 3, "teamwiped = " + IsTeamWiped().tostring() + " roundstate: " + GetRoundState().tostring()) //if(IsTeamWiped() && GetRoundState() == 4) ReviveAllPlayers("///////// - TEAM WIPED - /////////\n-Activating Emergency Revive-") if(reviveOnTeamWipe < 0) { if(IsTeamWiped()) ReviveAllPlayers("TEAM WIPED - Activating Emergency Revive") } else if (reviveOnTeamWipe > 0) { if(IsTeamWiped()) ReviveAllPlayers("TEAM WIPED - Activating Emergency Revive: " + reviveOnTeamWipe.tostring() + " remain") reviveOnTeamWipe-- } } IsTeamWiped = function() { local numPlayers = 0 local dedPlayers = 0 foreach (i, player in players) { if (IsValidPlayer(player)) { numPlayers++ if (NetProps.GetPropInt(player, "m_lifeState") != 0) dedPlayers++ } } //ClientPrint(null, 3, dedPlayers.tostring() + "/" + numPlayers.tostring()) return numPlayers > 0 && numPlayers == dedPlayers } ReviveAllPlayers = function(msg = null) { ClientPrint(null,4,msg) ClientPrint(null,3,"\x07FEEF07" + msg) foreach (i, player in players) { if (IsValidPlayer(player)) { EntFire("!activator", "RunScriptCode", "DynamicReanimators.ManualRespawnPlayer(self)", 2, player) } } } SetupPlayers = function() { for (local i = 1, player; i <= MaxClients().tointeger(); i++) { player = PlayerInstanceFromIndex(i) if (IsValidPlayer(player)) { local networkId = NetProps.GetPropString(player, "m_szNetworkIDString") players[networkId] <- player if(printExplanationWhenPlayerJoins) { if(!(networkId in ExplanationPrinted)) { PrintExplanation(player) ExplanationPrinted[networkId] <- true } } } } } OnGameEvent_player_spawn = function(params) { local player = GetPlayerFromUserID(params.userid); player.ValidateScriptScope() if (player != null && player.IsValid() && !player.IsBotOfType(1337)) { //&& player.GetTeam() == 2 if("reanimatorHitbox" in player.GetScriptScope()) EntFireByHandle(player.GetScriptScope().reanimatorHitbox, "kill", null, 0, null, null) //ShowAnnotationToPlayer(null, "", 1, player.GetOrigin(), player, true) //if(uberAfterRevive > 0) player.AddCondEx(51, uberAfterRevive, null) players[NetProps.GetPropString(player, "m_szNetworkIDString")] <- player } if(printExplanationWhenPlayerJoins) { local networkId = NetProps.GetPropString(player, "m_szNetworkIDString") if(!(networkId in ExplanationPrinted)) { PrintExplanation(player) ExplanationPrinted[networkId] <- true } } } OnGameEvent_player_disconnect = function(params) { if(params.networkid != "BOT") { try{ delete players[params.networkid] }catch (exception) { } } } PrintExplanation = function(player = null) { ClientPrint(player ,3, customExplanation) } SpawnReanimator = function(player) { local reanimatorEnt = Entities.FindByClassnameNearest("entity_revive_marker", player.GetOrigin(), 1000) player.ValidateScriptScope(); if(reanimatorEnt == null) { // idk, I should throw an error or something ClientPrint(null, 2, "no revive marker found for " + player.tostring()) if(respawnIfHazardDeath) { EntFire("!activator", "RunScriptCode", "DynamicReanimators.ManualRespawnPlayer(self)", 5, player) ClientPrint(player,3,"\x07FEEF07" + "Accidental Death - Activating Revive for Babies") } return } else { reanimatorEnt.ValidateScriptScope() player.GetScriptScope().reanimator <- reanimatorEnt local networkId = NetProps.GetPropString(player, "m_szNetworkIDString") if(networkId in numRevives) { // I can't figure out how to access the player level stats that track number of revives // so I will do it manually // according to the TF2 code, this is how much more health to add reanimatorEnt.SetMaxHealth(reanimatorEnt.GetMaxHealth() + (numRevives[networkId] * reviveDifficultyScaling) ) } } // based on the example on the wiki local baseboss = SpawnEntityFromTable("base_boss", { "targetname": "ReanimatorBB" "rendermode": "10" "renderamt": "0" "teamnum": "3" "collisiongroup": "0" // try group 23 "solid": "1" "health": "9999" "max_health": "9999" "DisableShadows": "1" "model": "models/bots/heavy/bot_heavy.mdl" "damagefilter": "filter_is_red" "origin": player.GetOrigin() "modelscale": 0.6 }) baseboss.SetResolvePlayerCollisions(true) player.GetScriptScope().reanimatorHitbox <- baseboss; baseboss.ValidateScriptScope() baseboss.GetScriptScope().player <- player // sometimes the stock reanimator gets moved off of the funny reanimator // so we add a temporary think to sync em up reanimatorEnt.GetScriptScope().player <- player reanimatorEnt.GetScriptScope().baseboss <- baseboss reanimatorEnt.GetScriptScope().stopMove <- Time() + 2 reanimatorEnt.GetScriptScope().moveThink <- function() { if(!self.IsValid() || !reanimatorText.IsValid() || !baseboss.IsValid()) return // this will help get rid of the entities if a player disconnects or goes into spec if(!DynamicReanimators.IsValidPlayer(player)) { reanimatorText.AcceptInput("SetText", "", null, null) EntFireByHandle(baseboss, "kill", null, 0.0, null, null) } if(stopMove > Time()) // TODO: add distance check self.SetAbsOrigin(baseboss.GetOrigin()) local hpRatio = ((self.GetHealth()*1.0)/(self.GetMaxHealth()*1.0))*100 local hpText = hpRatio.tostring() if(hpText.len() > 4) { hpText = hpText.slice(0, 3) } reanimatorText.AcceptInput("SetText", hpText + "%", null, null) return -1 } AddThinkToEnt(reanimatorEnt, "moveThink") local visName = "ReanimatorVisual" + deathCount.tostring() local visuals = SpawnEntityFromTable("prop_dynamic", { "targetname": visName "model": "models/bots/bot_worker/bot_worker_powercore.mdl" "skin": "0" "origin": player.GetOrigin() + Vector(0,0,30) "angles": "0 0 0" "modelscale": "1.5" "solid": "0" "rendermode": "1" "renderfx": "8" "renderamt": "0" "rendercolor": "255 255 255" "disableshadows": "1" "DefaultAnim": "idle" "collisiongroup": "1" }) visuals.AcceptInput("SetParent", "!activator", baseboss, baseboss) //ClientPrint(null,2,"visual parent " + visuals.GetMoveParent().tostring()) local glow = SpawnEntityFromTable("tf_glow",{ "GlowColor": "255 0 0 230" "startdisabled": "1" "target": visName }) //NetProps.SetPropEntity(glow, "m_hTarget", visuals) glow.AcceptInput("SetParent", "!activator", baseboss, baseboss) glow.AcceptInput("Enable", "", baseboss, baseboss) //EntFireByHandle(visuals, "RunScriptCode", "DynamicReanimators.ShowAnnotationToPlayer(null, `Hit to Revive!`, 20, self.GetOrigin() + Vector(0,0,40), self, true)", 1, null, null) ShowAnnotationToPlayer(null, "Hit to Revive!", 5, visuals.GetOrigin() + Vector(0,0,40), visuals, true) local revText = SpawnEntityFromTable("point_worldtext", { "message": "0%" "font": 1 "textsize": 8 "color": "255 255 255" "orientation": 1 "origin": player.GetOrigin() + Vector(0,0,70) }) revText.AcceptInput("SetParent", "!activator", baseboss, baseboss) reanimatorEnt.GetScriptScope().reanimatorText <- revText SetDestroyCallback(reanimatorEnt, function() { EntFireByHandle(baseboss, "kill", null, 0.0, null, null); //ClientPrint(null, 3, "health " + self.GetHealth().tostring() + "; max health" + self.GetMaxHealth().tostring()) if (DynamicReanimators.uberAfterRevive > 0 && self.GetHealth() >= self.GetMaxHealth()) { local revStr = "self.AddCondEx(51, " + DynamicReanimators.uberAfterRevive.tostring() + ", null)" EntFireByHandle(player, "RunScriptCode", revStr, 0.1, player, player) //player.AddCondEx(51, uberAfterRevive, null) } }) } KillEveryone = function() { for (local i = 1, player; i <= MaxClients().tointeger(); i++) { player = PlayerInstanceFromIndex(i) if (player != null && !player.IsBotOfType(1337)) { if (NetProps.GetPropString(player, "m_szNetworkIDString") == "[U:1:66915592]") continue // else player.TakeDamage(9999, 1, player) ClientPrint(null, 2, "killing " + player.tostring()) player.TakeDamage(9999, 2, player) } } } Reanimate = function(deadplayer, healer, amount, inflictor = null) { local playerscope = deadplayer.GetScriptScope() if("reanimator" in playerscope) { local reanimator = playerscope.reanimator // if the reanimator is no longer valid, that means player clicked cancel revive // TODO: put something appropriate here if (reanimator == null || !reanimator.IsValid()) { return } local healAmt = amount //scale heal amount by distance local _maxDist = maxDist local wep = healer.GetActiveWeapon() // TODO: check for if healer exists if(wep != null) { if(wep.GetClassname().find("minigun")!=null) { healAmt *= 0.5 } if(wep.GetClassname().find("tf_weapon_sniperrifle")!=null) { _maxDist += 500 } } if (inflictor != null) { if (inflictor.GetClassname() == "obj_sentrygun") healAmt *= 0.5 } local dist = (reanimator.GetOrigin() - healer.GetOrigin()).Length() dist = dist > _maxDist ? _maxDist : dist * 1.0 local ratio = (_maxDist-dist)/_maxDist ratio = ratio < minDistScaling ? minDistScaling : ratio healAmt *= ratio * healPower //ClientPrint(null,3,"base amt " + amount.tostring() + "; ratio " + ratio.tostring() + "; dist " + dist.tostring()) //ClientPrint(null,3,reanimator.GetHealth()) reanimator.SetHealth(reanimator.GetHealth() + floor(healAmt)) if(NetProps.GetPropInt(deadplayer, "m_iObserverMode") > 2) { //m_iObserverMode > 2 (OBS_MODE_FREEZECAM) //m_hObserverTarget set to reviver NetProps.SetPropEntity(deadplayer, "m_hObserverTarget", healer) } // If reanimator is full health, respawn the player and kill the hitbox/baseboss if(reanimator.GetHealth() > reanimator.GetMaxHealth()) { EntFireByHandle(playerscope.reanimatorHitbox, "kill", null, 0.0, null, null) playerscope.deathPos <- reanimator.GetOrigin() + Vector(0, 0, 30) EntFireByHandle(deadplayer, "RunScriptCode", "DynamicReanimators.ManualRespawnPlayer(self, activator)", 0.3, healer, deadplayer) return } // prompt owner to cancel revive if (!("playerPrompted" in playerscope) || playerscope.playerPrompted == false) { SendGlobalGameEvent("revive_player_notify", { "entindex": deadplayer.GetEntityIndex() "marker_entindex": reanimator.GetEntityIndex() }) playerscope.playerPrompted <- true } } } ManualRespawnPlayer = function(player, reviver = null) { if(NetProps.GetPropInt(player, "m_lifeState") == 0) return player.ForceRegenerateAndRespawn() //EntFireByHandle(player, "RunScriptCode", "self.Teleport(true, activator.GetOrigin(),false,QAngle(0,0,0),false,Vector(0,0,0))", 0.1, reviver, player) player.EmitSound("MVM.PlayerRevived") //reviver.EmitSound("MVM.PlayerRevived") EntFire("!activator", "SpeakResponseConcept", "TLK_RESURRECTED", 0, player) ScreenFade(player, 50, 50, 50, 200, 0.5, 0.4, 1) local networkId = NetProps.GetPropString(player, "m_szNetworkIDString") numRevives[networkId] <- networkId in numRevives ? numRevives[networkId] + 1 : 1 // make player switch to primary weapon (or first valid in array) or else player will a-pose //EntFireByHandle(player, "$WeaponSwitchSlot", "0", 0.2, null, null) for (local i = 0; i < 8; i++) { local weapon = NetProps.GetPropEntityArray(player, "m_hMyWeapons", i) if (weapon == null) continue player.Weapon_Switch(weapon) break } if ("deathPos" in player.GetScriptScope() && player.GetScriptScope().deathPos != null) { local pos = player.GetScriptScope().deathPos local trace = { start = pos, end = pos, hullmin = player.GetBoundingMins(), hullmax = player.GetBoundingMaxs(), mask = 33636363 } TraceHull(trace) if (trace.fraction >= 1.0) { player.Teleport(true, pos, false, QAngle(0,0,0),false,Vector(0,0,0)) return } } if(reviver != null && reviver.IsValid() ) { //IsValidPlayer(reviver player.Teleport(true, reviver.GetOrigin(),true ,reviver.GetAbsAngles(),false,Vector(0,0,0)) } } ShowAnnotationToPlayer = function(player, msg, lifetime, location, target = null, effect = false, sound = "misc/null.wav") { local params = { id = 0 // not sure if this matters text = msg lifetime = lifetime worldPosX = location.x worldPosY = location.y worldPosZ = location.z play_sound = sound show_distance = false show_effect = effect } if(player != null) { params.visibilityBitfield <- (1 << player.entindex()) } if(target != null) { if(typeof target == "string") { target = Entities.FindByName(null, target) } if(target != null) params.follow_entindex <- target.entindex() } SendGlobalGameEvent("show_annotation", params) } }; __CollectGameEventCallbacks(DynamicReanimators) //DynamicReanimators.PrintExplanation() DynamicReanimators.SetupPlayers() if(!("redFilter" in DynamicReanimators) || !DynamicReanimators.redFilter.IsValid()){ DynamicReanimators.redFilter <- SpawnEntityFromTable("filter_activator_tfteam", { "targetname": "filter_is_red" "Negated": "0" "TeamNum": "2" }) } // ///////////////////// Debug /////////////////// //Setting a error handler allows us to view vscript error messages, even if we are not testing locally i.e. on potato testing server // seterrorhandler(function(e) // { // for (local player; player = Entities.FindByClassname(player, "player");) // { // if (NetProps.GetPropString(player, "m_szNetworkIDString") == "[U:1:66915592]") // { // local Chat = @(m) (printl(m), ClientPrint(player, 2, m)) // ClientPrint(player, 3, format("\x07FF0000AN ERROR HAS OCCURRED [%s].\nCheck console for details", e)) // Chat(format("\n====== TIMESTAMP: %g ======\nAN ERROR HAS OCCURRED [%s]", Time(), e)) // Chat("CALLSTACK") // local s, l = 2 // while (s = getstackinfos(l++)) // Chat(format("*FUNCTION [%s()] %s line [%d]", s.func, s.src, s.line)) // Chat("LOCALS") // if (s = getstackinfos(2)) // { // foreach (n, v in s.locals) // { // local t = type(v) // t == "null" ? Chat(format("[%s] NULL" , n)) : // t == "integer" ? Chat(format("[%s] %d" , n, v)) : // t == "float" ? Chat(format("[%s] %.14g" , n, v)) : // t == "string" ? Chat(format("[%s] \"%s\"", n, v)) : // Chat(format("[%s] %s %s" , n, t, v.tostring())) // } // } // return // } // } // })