PrecacheModel("models/bots/bot_worker/bot_worker_powercore.mdl") PrecacheSound("misc/null.wav") //if("DynamicReanimators" in getroottable()) return ::ExplanationPrinted <- {} ::DynamicReanimators <- { // CONFIGURABLES // basic healPower = 0.3 // 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 = false // 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 // 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.25 // 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() } numRevives = {} deathCount = 0 OnGameEvent_mvm_begin_wave = function(_) { numRevives = {} deathCount = 0 } // OnScriptHook_OnTakeDamage = function(params) { // __DumpScope(0, params) // } OnGameEvent_npc_hurt = function(params) { local ent = EntIndexToHScript(params.entindex) if(ent.GetClassname() == "base_boss" && ent.GetName().find("ReanimatorBB") != null) { 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)) { //check if killed by world hazard local inflictor = EntIndexToHScript(params.inflictor_entindex) ClientPrint(null, 2, player.tostring() + "killed by" + inflictor.tostring() + ". wep: " + params.weapon) if (params.weapon = "world" && inflictor.GetClassname() != "player") { //&& inflictor.GetClassname().find("trigger_hurt") != null if(respawnIfHazardDeath) { EntFire("!activator", "RunScriptCode", "DynamicReanimators.ManualRespawnPlayer(self)", 2, player) } } else { EntFire("!activator", "RunScriptCode", "DynamicReanimators.SpawnReanimator(self)", 0.2, player) } } } 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) } if(printExplanationWhenPlayerJoins) { local networkId = NetProps.GetPropString(player, "m_szNetworkIDString") if(!(networkId in ExplanationPrinted)) { PrintExplanation(player) ExplanationPrinted[networkId] <- true } } } PrintExplanation = function(player = null) { ClientPrint(player ,3,"\x07FEEF07This mission uses Dynamic Reanimators: Revive your allies by attacking the reanimator device. Closer attacks are more effective") } 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,4,"no revive marker found for " + player.tostring()) local trigger_hurt = Entities.FindByClassnameNearest("trigger_hurt", player.GetOrigin(), 30) if(respawnIfHazardDeath && trigger_hurt) ClientPrint(null,3,trigger_hurt.tostring()) 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()) 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!", 8, 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 } 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) { local playerscope = deadplayer.GetScriptScope() if("reanimator" in playerscope) { local reanimator = playerscope.reanimator local healAmt = amount //scale heal amount by distance local _maxDist = maxDist local wep = healer.GetActiveWeapon() if(wep != null) { if(wep.GetClassname().find("minigun")!=null) { healAmt *= 0.5 } if(wep.GetClassname().find("tf_weapon_sniperrifle")!=null) { _maxDist += 500 } } 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.GetHealth() > reanimator.GetMaxHealth()) { EntFireByHandle(playerscope.reanimatorHitbox, "kill", null, 0.0, null, null) EntFireByHandle(deadplayer, "RunScriptCode", "DynamicReanimators.ManualRespawnPlayer(self, activator)", 0.3, healer, deadplayer) return } } //TODO: prompt owner to cancel revive } ManualRespawnPlayer = function(player, reviver = null) { 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(IsValidPlayer(reviver)) player.Teleport(true, reviver.GetOrigin(),false,QAngle(0,0,0),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() 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 // } // } // })