//Category Five VScript //by Braindawg //thanks to various members of the community for the help //BIG THINGS TO FIX: // Demoknight loadouts don't work, the GiveWeapons function needs to be re-written to allow tf_wearable items (shield/booties) // Sniper loadouts that use backpacks are also affected //wave 2 unfinished //func_nav_prerequisites just, don't work... PrecacheSound("buttons/button9.wav") PrecacheSound("items/ammocrate_open.wav") PrecacheSound("items/ammocrate_close.wav") PrecacheSound("ambient/levels/citadel/zapper_loop2.wav") PrecacheScriptSound("engy_taunt_killertime_1_button") //bomb beep PrecacheScriptSound("Hud.Hint") PrecacheScriptSound("Weapon_GasCan.Throw") PrecacheScriptSound("Weapon_GasCan.Draw") PrecacheModel("buildables/spawn_room/spawn_turret.mdl") //pauling sounds PrecacheScriptSound("plng_contract_complete_allclass") PrecacheScriptSound("plng_contract_fail_allclass") foreach (c in __ctable) { PrecacheScriptSound(format("plng_give_contract_%s", c)) PrecacheModel(format("models/bots/%s/bot_%s_gibby.mdl", c, c)) PrecacheModel(format("models/bots/%s/bot_%s_boss_gibby.mdl", c, c)) } //can't use hl2 soundscripts // PrecacheScriptSound("Buttons.snd9") // PrecacheScriptSound("AmmoCrate.Open") // PrecacheScriptSound("AmmoCrate.Close") local timeractive = false local popname = "" local bomb local boxspots = {} const BUTTON_COOLDOWN_TIME = 3 //const TIMER_MINUTES = 1.0 // CONST.TIMER_INTERVAL = (TIMER_MINUTES / 60) / 255 const TIMER_INTERVAL = 0.2352941176470588 // ((1/60) / 255) const BOMBTICK_INTERVAL = 1.66 const BUTTON_RADIUS = 96 const PICKUP_RADIUS = 64 const WAVE_START_COUNTDOWN = 5.0 const MAX_RESPAWN_CRYSTALS_ACTIVE = 10 const MAX_GASCANS_ACTIVE = 25 ::activefogcontroller <- "mist" ::generatorcounter <- 3 ::crystalsactive <- false ::bossunlocked <- false ::__bombdeployed <- false //kill extra edicts //we do this here instead of changing the map incase someone wants to use this map for a normal mvm mission for some reason ::Optimize <- function() { EntFire("arrows_path*", "Kill") EntFire("bot_hint*", "Kill") EntFire("func_nobuild", "Kill") EntFire("particle_storm_rain1*", "Kill") EntFire("phys_bone_follower", "Kill") // for (local i = 1; i <= MAX_EDICTS; i++) // { // local entity = EntIndexToHScript(i) // if (entity == null) continue // // printl(entity.GetClassname()) // //CUtlRBTree Overflow crash // // SetPropBool(entity, "m_bForcePurgeFixedupStrings", true) // //kill unused bombs (6 edicts) // // if (entity.GetClassname() == "item_teamflag" && (entity.GetName() != "bomb1_timed" || entity.GetName() != "bomb3_timed")) entity.Kill(); // //kill ropes // //ropes take up a lot of edicts (46), but are used to point players to objectives // // else if (entity.GetClassname() == "move_rope" || entity.GetClassname() == "keyframe_rope") entity.Kill(); // //kill bomb arrows (33 edicts) if (startswith(entity.GetName(), "arrows_path")) entity.Kill() // //kill engi hints (no engi bots, 18 edicts) else if (startswith(entity.GetClassname(), "bot_hint*")) entity.Kill() // //kill nobuilds (no busters, 36 edicts) else if (entity.GetClassname() == "func_nobuild") entity.Kill() // //kill unused rain effects (20 edicts) else if (startswith(entity.GetName(), "particle_storm_rain1")) entity.Kill() // //like 160 edicts lol else if (entity.GetClassname() == "phys_bone_follower") entity.Kill() // //wave specific // // if (__wavenum == 1) // // { // // //kill rainstorm particles on w1 (70 edicts!) // // if (startswith(entity.GetName(), "rain_angledstorm")) entity.Kill(); // // //kill more particles // // else if (startswith(entity.GetName(), "drip_storm")) entity.Kill(); // // } // } return } ::DrainAmmo <- function(player) { local buttons = NetProps.GetPropInt(self, "m_nButtons") local wep = player.GetActiveWeapon() if (wep == null || wep.IsMeleeWeapon()) return wep.ValidateScriptScope() local scope = wep.GetScriptScope() if (!("nextattack" in scope)) { scope.nextattack <- -1 scope.lastattack <- -1 } local classname = wep.GetClassname() local itemid = GetItemIndex(wep) local sequence = wep.GetSequence() // These weapons have clips but either function fine or don't need to be handled if (classname == "tf_weapon_particle_cannon" || classname == "tf_weapon_raygun" || classname == "tf_weapon_flaregun_revenge" || classname == "tf_weapon_drg_pomson" || classname == "tf_weapon_medigun") return local clip = wep.Clip1() if (clip > -1) { if (!("lastclip" in scope)) scope.lastclip <- clip // We reloaded if (clip > scope.lastclip) { local difference = clip - scope.lastclip if (self.GetPlayerClass() == TF_CLASS_SPY && classname == "tf_weapon_revolver") { local maxammo = GetPropIntArray(self, "m_iAmmo", SLOT_SECONDARY + 1) SetPropIntArray(self, "m_iAmmo", maxammo - difference, SLOT_SECONDARY + 1) } else { local maxammo = GetPropIntArray(self, "m_iAmmo", wep.GetSlot() + 1) SetPropIntArray(self, "m_iAmmo", maxammo - difference, wep.GetSlot() + 1) } } scope.lastclip <- clip } else { if (classname == "tf_weapon_rocketlauncher_fireball") { local recharge = GetPropFloat(player, "m_Shared.m_flItemChargeMeter") if (recharge == 0) { local cost = (sequence == 13) ? 2 : 1 local maxammo = GetPropIntArray(self, "m_iAmmo", wep.GetSlot() + 1) if (maxammo - cost > -1) SetPropIntArray(self, "m_iAmmo", maxammo - cost, wep.GetSlot() + 1) } } else if (classname == "tf_weapon_flamethrower") { if (sequence == 12) return // Weapon deploy if (Time() < scope.nextattack) return local maxammo = GetPropIntArray(self, "m_iAmmo", wep.GetSlot() + 1) // The airblast sequence lasts 0.25s too long so we check against it here if (buttons & IN_ATTACK && sequence != 13) { if (maxammo - 1 > -1) { SetPropIntArray(self, "m_iAmmo", maxammo - 1, wep.GetSlot() + 1) scope.nextattack <- Time() + 0.105 } } else if (buttons & IN_ATTACK2) { local cost = 20 if (itemid == ID_BACKBURNER || itemid == ID_FESTIVE_BACKBURNER_2014) // Backburner cost = 50 else if (itemid == ID_DEGREASER) // Degreaser cost = 25 if (maxammo - cost > -1) { SetPropIntArray(self, "m_iAmmo", maxammo - cost, wep.GetSlot() + 1) scope.nextattack <- Time() + 0.75 } } } else if (classname == "tf_weapon_flaregun") { local nextattack = GetPropFloat(wep, "m_flNextPrimaryAttack") if (Time() < nextattack) return local maxammo = GetPropIntArray(self, "m_iAmmo", wep.GetSlot() + 1) if (buttons & IN_ATTACK) { if (maxammo - 1 > -1) SetPropIntArray(self, "m_iAmmo", maxammo - 1, wep.GetSlot() + 1) } } else if (classname == "tf_weapon_minigun") { local nextattack = GetPropFloat(wep, "m_flNextPrimaryAttack") if (Time() < nextattack) return local maxammo = GetPropIntArray(self, "m_iAmmo", wep.GetSlot() + 1) if (sequence == 21) { if (maxammo - 1 > -1) SetPropIntArray(self, "m_iAmmo", maxammo - 1, wep.GetSlot() + 1) } else if (sequence == 25) { if (Time() < scope.nextattack) return if (itemid != ID_HUO_LONG_HEATMAKER && itemid != ID_GENUINE_HUO_LONG_HEATMAKER) return if (maxammo - 1 > -1) { SetPropIntArray(self, "m_iAmmo", maxammo - 1, wep.GetSlot() + 1) scope.nextattack <- Time() + 0.25 } } } else if (classname == "tf_weapon_mechanical_arm") { // Reset hack SetPropIntArray(self, "m_iAmmo", 0, TF_AMMO_GRENADES1) SetPropInt(wep, "m_iPrimaryAmmoType", 3) local nextattack1 = GetPropFloat(wep, "m_flNextPrimaryAttack") local nextattack2 = GetPropFloat(wep, "m_flNextSecondaryAttack") local maxmetal = GetPropIntArray(self, "m_iAmmo", TF_AMMO_METAL) if (buttons & IN_ATTACK) { if (Time() < nextattack1) return if (maxmetal - 5 > -1) { SetPropIntArray(self, "m_iAmmo", maxmetal - 5, TF_AMMO_METAL) // This prevents an exploit where you M1 and M2 in rapid succession to evade the 65 orb cost SetPropFloat(wep, "m_flNextSecondaryAttack", Time() + 0.25) } } else if (buttons & IN_ATTACK2) { if (Time() < nextattack2) return if (maxmetal - 65 > -1) { // Hack to get around the game blocking SecondaryAttack below 65 metal SetPropIntArray(self, "m_iAmmo", INT_MAX, TF_AMMO_GRENADES1) SetPropInt(wep, "m_iPrimaryAmmoType", TF_AMMO_GRENADES1) SetPropIntArray(self, "m_iAmmo", maxmetal - 65, TF_AMMO_METAL) } } } else if (itemid == ID_WIDOWMAKER) { // Widowmaker local nextattack = GetPropFloat(wep, "m_flNextPrimaryAttack") if (Time() < nextattack) return local maxmetal = GetPropIntArray(self, "m_iAmmo", TF_AMMO_METAL) if (buttons & IN_ATTACK) { if (maxmetal - 30 > -1) SetPropIntArray(self, "m_iAmmo", maxmetal - 30, TF_AMMO_METAL) } } else if (classname == "tf_weapon_sniperrifle" || itemid == ID_BAZAAR_BARGAIN || itemid == ID_CLASSIC) { local lastfire = GetPropFloat(wep, "m_flLastFireTime") if (scope.lastattack == lastfire) return scope.lastattack <- lastfire local maxammo = GetPropIntArray(self, "m_iAmmo", wep.GetSlot() + 1) if (scope.lastattack > 0 && scope.lastattack < Time()) { if (maxammo - 1 > -1) SetPropIntArray(self, "m_iAmmo", maxammo - 1, wep.GetSlot() + 1) } } } } local ffloopsound = "ambient/energy/force_field_loop1.wav" PrecacheSound(ffloopsound) local gascantext = SpawnEntityFromTable("game_text", { targetname = "__gascantext" message = "Gas Cans: 0/3" effect = 0 spawnflags = 1 color = "255 254 255" color2 = "255 254 255" holdtime = 1 fadeout = 0.01 fadein = 0.01 channel = 6 x = 0.7 y = 0.7 }) ::__waveended <- false //per-player think ::ReverseTeams <- function() { local player = self local scope = player.GetScriptScope() DrainAmmo(player) ButtonThink(player) if (__wavenum == 2 && __wavestart && !scope.paulingspeaking) EntFireByHandle(gascantext, "Display", "", -1, player, player) //move extra players to spectator //extra players are not appended to playerarray if (player in playertable) if (player.GetTeam() != TF_TEAM_PVE_INVADERS && !__waveended) ChangePlayerTeamMvM(player, TF_TEAM_PVE_INVADERS) else if (!(player in playertable) && player.GetTeam() != TEAM_SPECTATOR) { ClientPrint(player, HUD_PRINTCENTER, "Max players reached, you have been moved to spectator.") ChangePlayerTeamMvM(player, TEAM_SPECTATOR) } if (!__wavestart && IsPlayerDead(player)) player.ForceRespawn() // if (IsSigmod()) //use the world money counter for per-player currency since we don't ever drop money // EntFireByHandle(player, "$SetClientProp$m_nMvMWorldMoney", player.GetCurrency().tostring(), -1, null, null) __wavestart ? player.RemoveCustomAttribute("no_attack") : player.AddCustomAttribute("no_attack", 1, -1) // if (player.GetTeam() == TEAM_SPECTATOR && playerarray.len() > CFIVE_MAXPLAYERS) // ChangePlayerTeamMvM(player, TF_TEAM_PVE_DEFENDERS); //we can't strip toolbox and give it back because GiveWeapon toolbox crashes (???) //disallow switching to block "build" console commands if (player.GetPlayerClass() == TF_CLASS_ENGINEER && GetItemIndex(player.GetActiveWeapon()) == ID_BUILDER && GetWeaponInSlot(player, SLOT_PDA) == null) player.Weapon_Switch(GetWeaponInSlot(player, SLOT_MELEE)) //remove sniper/wrangler laser //deleting env_laserdot doesn't stop wrangler laser // local dots = [] for (local dot; dot = FindByClassname(dot, "env_sniperdot");) EntFireByHandle(dot, "Kill", "", -1, null, null) for (local dot; dot = FindByClassname(dot, "env_laserdot");) EntFireByHandle(dot, "Kill", "", -1, null, null) // for (local i = dots.len(); i >= 0; i--) EntFireByHandle(dots[i], "Kill", "", -1, null, null) // dots.clear() //ammo/health packs //this doesn't work for money, credit team is red so even if we pick it up we can't use it. //manually add currency to all blu players on pickup instead for (local packs; packs = FindByClassnameWithin(packs, "item_*", player.GetLocalOrigin(), PICKUP_RADIUS);) if (!HasEffect(packs, EF_NODRAW) && packs.GetClassname() != "item_teamflag" && !startswith(packs.GetClassname(), "item_currency")) GivePackReverse(player, packs) //forcefield humming // if (FindByClassnameWithin(null, "func_forcefield", player.GetOrigin(), BUTTON_RADIUS * 1.25) != null && !scope.fflooping) for (local forcefield; forcefield = FindByClassnameWithin(null, "func_forcefield", player.GetOrigin(), BUTTON_RADIUS * 1.5);) { if (HasEffect(forcefield, EF_NODRAW) || GetPropString(forcefield, "m_iName") == "undergroundforcefield") return -1 if (!scope.fflooping) EmitSoundEx({ sound_name = ffloopsound channel = CHAN_STREAM volume = 0.2 sound_level = 65 flag = SND_NOFLAGS entity = player filter_type = RECIPIENT_FILTER_SINGLE_PLAYER }) scope.fflooping = true return -1 } if (scope.fflooping) { EmitSoundEx({ sound_name = "misc/null.wav" channel = CHAN_STREAM volume = 0.000001 flag = SND_STOP entity = player filter_type = RECIPIENT_FILTER_SINGLE_PLAYER }) scope.fflooping = false } // foreach(k, v in player.GetScriptScope().weapontable) // printl(k+" : "+v[0]) return -1 } //global think local cooldown = 0 ::ReverseTeamsEx <- function() { //make sure the middle stays blocked, check a random nav square if (__wavenum != 3 && !NavMesh.GetNavAreaByID(3).IsBlocked(TF_TEAM_PVE_DEFENDERS, true)) BlockNavs() // EntFire("entity_revive_marker", "Kill") //pre-round if (!__wavestart) { local roundtime = GetPropFloat(__gamerules, "m_flRestartRoundTime") local starttime = Time() + WAVE_START_COUNTDOWN local ready = 0 foreach (bot, userid in bottable) try { if (bot.GetTeam() != TEAM_SPECTATOR) bot.ForceChangeTeam(TEAM_SPECTATOR, false) } catch(err) printl(err) if (roundtime > starttime) { for (local i = 0; i < GetPropArraySize(__gamerules, "m_bPlayerReady"); i++) { if (!GetPropBoolArray(__gamerules, "m_bPlayerReady", i)) continue ready++ // printl(ready +" : "+ CountAllPlayers()) if (ready >= CountAllPlayers() || (roundtime <= 12.0)) SetPropFloat(__gamerules, "m_flRestartRoundTime", starttime) } } return REDUCED_THINK_INTERVAL //nothing in pre-round needs to think very fast } return -1 } ::RespawnThink <- function() { local respawntext = crystalsactive ? -1 : 0.0 foreach(player, userid in playertable) try if (IsPlayerDead(player)) SetPropFloatArray(__playermanager, "m_flNextRespawnTime", respawntext, player.entindex()) catch(err) break return -1 } __playermanager.ValidateScriptScope() __playermanager.GetScriptScope().RespawnThink <- RespawnThink AddThinkToEnt(__playermanager, "RespawnThink") local hudtime = 2 local hudcooldown = 0 //button pressing stuff + weapon boxes //highlight nearby important items ::ButtonThink <- function(player) { local scope = player.GetScriptScope() for (local button; button = FindByClassnameWithin(button, "func_rot_button", player.GetOrigin(), BUTTON_RADIUS); ) { // printl(GetPropVector(button, "m_vecPosition1") + " : " + GetPropVector(button, "m_vecPosition2") + " : " + GetPropVector(button, "m_vecAngle1") + " : " + GetPropVector(button, "m_vecAngle")) // SetPropVector(button, "m_vecFinalAngle", Vector()) SetPropVector(button, "m_vecAngle", Vector()) if (GetPropBool(button, "m_bLocked")) continue //hint when we get close local glow if (hudcooldown < Time()) { if (!scope.hinted) EmitSoundOnClient("Hud.Hint", player) ShowHudHint(player, "Press%use_action_slot_item%", 2) local parent = button.GetMoveParent() if (parent == null) parent = FindByClassnameNearest("prop_dynamic", button.GetOrigin(), BUTTON_RADIUS) //button isn't parented, find nearest prop_dynamic and glow that glow = ShowModelToPlayer(player, [parent.GetModelName(), parent.GetSkin()], parent.GetOrigin(), parent.GetAbsAngles(), 3.0) SetPropInt(glow, "m_nRenderMode", 1) glow.SetModelScale(glow.GetModelScale() + 0.01, -1) SetEntityColor(glow, 0, 0, 0, 0) SetPropBool(glow, "m_bGlowEnabled", true) hudcooldown = Time() + hudtime scope.hinted = true continue } if (player.IsUsingActionSlot() && scope.cooldowntime < Time()) // if (InButton(player, IN_RELOAD) && scope.cooldowntime < Time()) { //press the button EntFireByHandle(button, "Use", "", -1, player, player) scope.hinted = false if (glow != null) glow.Kill() //manually set it here just in case SetPropBool(player, "m_bUsingActionSlot", false) //set cooldown time scope.cooldowntime = Time() + BUTTON_COOLDOWN_TIME //gunbox button if (startswith(button.GetName(), "PT_SWITCHGUNBUTTON")) { button.EmitSound("items/ammocrate_open.wav") //can't use hl2 soundscripts // EmitSoundOnClient("AmmoCrate.Open", player) EntFireByHandle(button, "RunScriptCode", "self.EmitSound(`items/ammocrate_close.wav`)", 3, null, null) StripEverythingExcept(player, SLOT_MELEE) EntFireByHandle(player, "RunScriptCode", "AwardWeapons(self, self.GetPlayerClass())", 0.1, null, null) return -1 } //default button sound if (!GetPropBool(button, "m_bLocked")) button.EmitSound("buttons/button9.wav") //can't use hl2 soundscripts // EmitSoundOnClient("Buttons.snd9", player) } } return -1 } ::MarketGardenerThink <- function() { local bot = self try { for (local p; p = FindByClassnameWithin(p, "player", bot.GetOrigin(), 128);) if (p.GetTeam() == TF_TEAM_PVE_INVADERS) { local melee = GetWeaponInSlot(self, SLOT_MELEE) bot.Weapon_Switch(melee) bot.AddCustomAttribute("disable weapon switch", 1, 2) melee.ReapplyProvision() } } catch(err) { SetPropString(bot, "m_iszScriptThinkFunction", "") return } } ::WaveStart <- function() { // SetPropInt(gamerules, "m_iRoundState", ROUNDSTATE_SUDDENDEATH); //change respawn text, breaks bot spawns foreach (player, userid in playertable) { if (player == null) continue player.RemoveCustomAttribute("no_attack") player.RemoveCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) } // ceiling turret fixes DoEntFire("PT_SWITCHBUTTON2", "Lock", "", -1, null, null) DoEntFire("obj_*", "RunScriptCode", "SetPropInt(self, `m_lifeState`, 1)", -1, null, null) //this works DoEntFire("obj_sentrygun", "RunScriptCode", "self.SetModel(`models/buildables/spawn_room/spawn_turret.mdl`)", -1, null, null) DoEntFire("obj_sentrygun", "RunScriptCode", "self.SetSequence(self.LookupSequence(`ACT_OBJ_IDLE`))", -1, null, null) DoEntFire("obj_sentrygun", "Disable", "", 0.1, null, null) DoEntFire("nav_interface", "RecomputeBlockers", "", -1, null, null) local rock = FindByName(null, "PT_ROCKBREAKABLE") local wind = FindByClassname(null, "env_wind") function AddWind() { AddOutput(wind, "OnGustStart", "windshuffle", "PickRandomShuffle", "", -1, -1) AddOutput(wind, "OnGustStart", "shakeit2", "StartShake", "", -1, -1) AddOutput(wind, "OnGustEnd", "shakeit2", "StopShake", "", -1, -1) } AddOutput(rock, "OnBreak", "gamerules", "CallScriptFunction", "AddWind", -1, -1) DoEntFire("env_soundscape*", "RunScriptCode", "RemoveOutputAll(self, `OnPlay`)", -1, null, null) DoEntFire("player", "SetFogController" , "" , 1 , null , null) switch(__wavenum) { case 1: // make rock breakable DoEntFire("PT_ROCK*", "SetDamageFilter", "filter_blu_team", -1, null, null) DoEntFire("PT_GENERATORBUTTON*", "Lock", "", -1, null, null) DoEntFire("PT_SWITCHBUTTON*", "Lock", "", -1, null, null) break case 2: DoEntFire("gamerules", "CallScriptFunction", "SpawnGasCans", -1, null, null) DoEntFire("restartgens", "AddOutput", "OnTrigger worldspawn:CallScriptFunction:GenLogic:0:-1", -1, null, null) for (local button; button = FindByName(button, "PT_GENERATORBUTTO*");) { button.GetScriptScope().DepositGasCan <- DepositGasCan AddThinkToEnt(button, "DepositGasCan") } break case 3: PaulingAnnouncement("Anyone alive in there?|PAUSE 5|I'm completely blind, no idea what's coming.|I detected something big in the area before the storm hit|See if that upgrade station works still") DoEntFire("gamerules", "RunScriptCode", "ShowAnnotation(`Get to the upgrade station`, 15, Vector(307, -3165, 34))", 17, null, null) // ShowAnnotation("Get to the upgrade station", 15, Vector(307, -3165, 34)) DoEntFire("midblockerbarrel*", "Kill", "", -1, null, null) DoEntFire("PT_GENERATOR*", "Kill", "", -1, null, null) // DoEntFire("spawntele", "Kill", "", 0.1, null, null) SpawnEntityFromTable("logic_relay", { targetname = "ffdestroy_relay", "OnSpawn#1": "!self,Trigger,,1,-1" "OnTrigger#1": "midforcefieldRunScriptCodeCreateSpark(Vector(2097.551270, -1195.632690, -59.96868)),0-1" "OnTrigger#2": "midforcefield,Disable,,0,-1" "OnTrigger#3": "midforcefield,Enable,,0.3,-1" "OnTrigger#7": "ffdestroy_relay2,Trigger,,0,-1" }) SpawnEntityFromTable("logic_relay", { targetname = "ffdestroy_relay2", "OnTrigger#1": "ffdestroy_relay,Trigger,,"+RandomFloat(0.5, 2)+",-1" }) DoEntFire("ffdestroy_relay*", "Kill", "", 12, null, null) DoEntFire("midforcefield", "Kill", "", 12, null, null) DoEntFire("tunnelforcefield*", "Kill", "", 12, null, null) DoEntFire("tunnelforcefield*", "Kill", "", 12, null, null) DoEntFire("skybox_terrain", "Enable", "", -1, null, null) DoEntFire("door_upgrade", "Kill", "", -1, null, null) DoEntFire("fourthspawnhatch", "Enable", "", -1, null, null) DoEntFire("red_spawnp*", "Enable", "", -1, null, null) DoEntFire("spawntele", "Kill", "", -1, null, null) DoEntFire("spawntele1", "Kill", "", -1, null, null) DoEntFire("func_upgradestation", "Enable", "", 5, null, null) RemoveOutput(FindByName(null, "bomb_deploy_relay"), "OnTrigger", "win_bots", "RoundWin", "") EntFire("bomb_deploy_relay", "AddOutput", "OnTrigger !self:RunScriptCode:__bombdeployed=true:0:-1") DoEntFire("bomb_kill_linger", "Kill", "", -1, null, null) BlockNavs(true) break } } ::PaulingAnnouncement <- function(str = "PLACEHOLDER", announceflag = PAULING_GENERIC, player = null) { local playerfilter = 1 if (player != null) { playerfilter = 0 player.GetScriptScope().lastannouncement = str player.GetScriptScope().paulingspeaking = true //failsafe EntFireByHandle(player, "RunScriptCode", "self.GetScriptScope().paulingspeaking <- false", 20, null, null) } else foreach (player, userid in playertable) { player.GetScriptScope().lastannouncement = str player.GetScriptScope().paulingspeaking = true EntFireByHandle(player, "RunScriptCode", "self.GetScriptScope().paulingspeaking <- false", 20, null, null) } local txtent = SpawnEntityFromTable("game_text", { effect = 2 spawnflags = playerfilter color = "255 254 255" color2 = "255 254 255" fxtime = 0.02 // holdtime = 5 fadeout = 0.01 fadein = 0.01 channel = 3 // generator 3 overwrites this. x = 0.3 y = 0.645 }) SetPropBool(txtent, "m_bForcePurgeFixedupStrings", true) SetTargetname(txtent, format("pauling%d", txtent.entindex())) local maxlength = 64 local genericpauling = [ ["vo/pauling/plng_give_contract_soldier_01.mp3", -0.42], ["vo/pauling/plng_give_contract_demo_09.mp3", -0.5], ["vo/pauling/plng_give_contract_scout_05.mp3", -0.5], ["vo/pauling/plng_give_contract_pyro_06.mp3", -0.5], ["vo/pauling/plng_give_contract_heavy_01.mp3", -0.3], ["vo/pauling/plng_give_contract_heavy_02.mp3", -0.4], ["vo/pauling/plng_give_contract_heavy_03.mp3", -0.29], ["vo/pauling/plng_give_contract_heavy_07.mp3", -0.3], ["vo/pauling/plng_give_contract_heavy_10.mp3", -0.3], // ["vo/pauling/plng_give_contract_medic_02.mp3", -0.45], ["vo/pauling/plng_give_contract_medic_03.mp3", -0.65], // ["vo/pauling/plng_give_contract_medic_04.mp3", -0.5], ["vo/pauling/plng_give_contract_medic_07.mp3", -0.27], // ["vo/pauling/plng_give_contract_medic_10.mp3", -0.5], //confidential // ["vo/pauling/plng_give_contract_medic_13.mp3", -0.5], //messy ["vo/pauling/plng_give_contract_sniper_06.mp3", -0.5], //take a look ] switch(announceflag) { case PAULING_GAG: break //-1, nothing case PAULING_GENERIC: //0 local r = RandomInt(0, (genericpauling.len() - 1)) player == null ? EmitSoundEx({ sound_name = genericpauling[r][0], flags = 16, delay = genericpauling[r][1], time = 0.02 }) : EmitSoundEx({ sound_name = genericpauling[r][0], flags = 16, delay = genericpauling[r][1], time = 0.02 , entity = player, filter_type = RECIPIENT_FILTER_SINGLE_PLAYER}) break case PAULING_SUCCESS: //1 DoEntFire("gamerules", "PlayVOBlue", "plng_contract_complete_allclass", -1, null, null) break case PAULING_FAILURE: //2 DoEntFire("gamerules", "PlayVOBlue", "plng_contract_fail_allclass", -1, null, null) break } local strarray = [] //avoid needing to do a ton of function calls for multiple announcements. local newlines = split(str, "|") foreach (i, n in newlines) { //print remaining string to the next announcement box if we go over the max length. //we should split this at spaces instead, so we don't get words chopped off half-way or something silly while (n.len() > maxlength) { local part = n.slice(0, maxlength) strarray.append(part+"\n") printl(n) n = n.slice(maxlength) } // Append the remaining part of the string if (str.len() > 0) { strarray.append(n) } } local textcooldown = 0 local i = -1 function PaulingTextThink() { if (textcooldown > Time()) return i++ if (i == strarray.len()) { SetPropString(txtent, "m_iszScriptThinkFunction", "") DoEntFire("!activator", "SetScriptOverlayMaterial", "", -1, player, player) // foreach (player, userid in playertable) DoEntFire("command", "Command", "r_screenoverlay vgui/pauling_text", -1, player, player) SetPropString(txtent, "m_iszMessage", "") EntFireByHandle(txtent, "Display", "", -1, player, player) EntFireByHandle(txtent, "Kill", "", 0.1, null, null) if (player) player.GetScriptScope().paulingspeaking = false return } local s = strarray[i] //make text display slightly longer depending on string length local delaybetweendisplays = s.len() / 10 if (delaybetweendisplays < 2) delaybetweendisplays = 2 else if (delaybetweendisplays > 12) delaybetweendisplays = 12 //allow for pauses in the announcement if (startswith(s, "PAUSE")) { local pause = split(s, " ")[1].tofloat() DoEntFire("player", "SetScriptOverlayMaterial", "", -1, player, player) SetPropString(txtent, "m_iszMessage", "") SetPropInt(txtent, "m_textParms.holdTime", pause) txtent.KeyValueFromInt("holdtime", pause) EntFireByHandle(txtent, "Display", "", -1, player, player) textcooldown = Time() + pause return REDUCED_THINK_INTERVAL } //shits fucked function calculate_x(string) { local len = string.len().tofloat() // local t = 1 - (len.tofloat() / maxlength) local t = 1.0 - (len / 48.0) local x = 1.0 * (1.0 - t) x = (1.0 - (x / 3.0)) / 2.1 //clamp final output if (x < 0.28) x = 0.28 else if (x > 0.4) x = 0.4 return x } SetPropFloat(txtent, "m_textParms.x", calculate_x(s)) txtent.KeyValueFromFloat("x", calculate_x(s)) SetPropString(txtent, "m_iszMessage", s) SetPropInt(txtent, "m_textParms.holdTime", delaybetweendisplays) txtent.KeyValueFromInt("holdtime", delaybetweendisplays) EntFireByHandle(txtent, "Display", "", -1, player, player) // ClientPrint(null, HUD_PRINTTALK, "\x07D500FFMiss Pauling\x07FBECCB: "+s); //too saturated ClientPrint(player, HUD_PRINTTALK, format("%sMiss Pauling%s: %s", COLOR_PAULING, COLOR_DEFAULT, s)) local target = "player" if (player != null) target = "!activator" DoEntFire(target, "SetScriptOverlayMaterial", "vgui/pauling_text", -1, player, player) DoEntFire(target, "SetScriptOverlayMaterial", "", delaybetweendisplays, player, player) textcooldown = Time() + delaybetweendisplays return REDUCED_THINK_INTERVAL } txtent.ValidateScriptScope() txtent.GetScriptScope().PaulingTextThink <- PaulingTextThink AddThinkToEnt(txtent, "PaulingTextThink") } ::GivePackReverse <- function(player, pack) { local classname = pack.GetClassname() local refill = false //AMMO PACKS if (startswith(classname, "item_ammopack_")) { local wep = player.GetActiveWeapon() local scope = player.GetScriptScope() local ammo = scope.maxammo //engi metal if (player.GetPlayerClass() == TF_CLASS_ENGINEER) ammo.append(scope.maxmetal) for (local i = 0; i < ammo.len(); i++) { local ammoslot = GetAmmoInSlot(player, i) if (ammoslot >= ammo[i]) continue refill = true if (endswith(classname, "_small")) SetReserveAmmo(wep, (ammo[i] * 0.2) + ammoslot, i) else if (endswith(classname, "_medium")) SetReserveAmmo(wep, (ammo[i] * 0.5) + ammoslot, i) else if (endswith(classname, "_full")) SetReserveAmmo(wep, ammo[i], i) } //check to make sure we don't have more ammo than expected for (local i = 0; i < ammo.len(); i++) if (GetPropIntArray(player, "m_iAmmo", i+1) > ammo[i]) EntFireByHandle(player, "RunScriptCode", "SetReserveAmmo(self, "+ammo[i]+","+i+")", -1, null, null) if (refill) { EmitSoundOnClient("AmmoPack.Touch", player) pack.SetOrigin(pack.GetOrigin() - Vector(0, 0, 1000)) EntFireByHandle(pack, "Disable", "", -1, null, null) EntFireByHandle(pack, "RunScriptCode", "self.SetOrigin(self.GetOrigin() + Vector(0, 0, 1000))", 9.9, null, null) EntFireByHandle(pack, "Enable", "", 10, null, null) } //HEALTH KITS } if (startswith(classname, "item_healthkit_")) { local hp = player.GetHealth() local maxhp = player.GetMaxHealth() if (hp >= maxhp) return EmitSoundOnClient("HealthKit.Touch", player) local hpamount = 0 local multiplier = 0 if (endswith(classname, "_small")) multiplier = 0.2 else if (endswith(classname, "_medium")) multiplier = 0.5 else if (endswith(classname, "_full")) multiplier = 1.0 hpamount = (maxhp * multiplier) + hp if (hpamount > maxhp) hpamount = maxhp player.ExtinguishPlayerBurning() SendGlobalGameEvent("player_healed", { patient = GetPlayerUserID(player), healer = 0, amount = hp > maxhp * multiplier ? maxhp - hp : maxhp * multiplier }) SendGlobalGameEvent("player_healonhit", { entindex = player.entindex(), weapon_def_index = 65535, amount = hp > maxhp * multiplier ? maxhp - hp : maxhp * multiplier }) player.SetHealth(hpamount) pack.SetOrigin(pack.GetOrigin() - Vector(0, 0, 2000)) EntFireByHandle(pack, "Disable", "", -1, null, null) EntFireByHandle(pack, "RunScriptCode", "self.SetOrigin(self.GetOrigin() + Vector(0, 0, 2000))", 9.9, null, null) EntFireByHandle(pack, "Enable", "", 10, null, null) } } ::Annotations <- function(annotationcase) { switch(annotationcase) { case 1: ShowAnnotation("Break this!", -1, Vector(1202, -4364, 321), 65432) DoEntFire("PT_ROCK*", "AddOutput", "OnBreak !self:RunScriptCode:HideAnnotation(65432):0:1", -1, null, null) break case 2: ShowAnnotation("Look for weapon cases", -1, Vector(387, -3978, -76), 1234) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(1234)", 10.0, null, null) PaulingAnnouncement("Hey it's pauling, can you guys hear me?|PAUSE 5|Alright good, you're still alive.|RED was doing some secret experiments in this area|We haven't heard from anyone in about a week|Look for weapons and expect the worst.|(type .pauling in chat to see the last announcement)") break case 3: ShowAnnotation("Find a way to clear the path", -1, Vector(608, -1953, 80), 2345) DoEntFire("PT_SWITCHBUTTON", "AddOutput", "OnPressed !self:RunScriptCode:HideAnnotation(2345):0:-1", -1, null, null) DoEntFire("truckexplode", "AddOutput", "OnDroppedFlag !self:RunScriptCode:HideAnnotation(2345):0:-1", -1, null, null) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(2345)", 20.0, null, null) break case 4: ShowAnnotation("Disable power to the forcefield", -1, Vector(2914, -2051, -48), 5432) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(5432)", 10.0, null, null) break case 5: DoEntFire("gamerules", "RunScriptCode", "ShowAnnotation(`Grab a bomb from spawn to blow up the barricade!`, -1, Vector(946, -5264, -120), 321)", 10.0, null, null) DoEntFire("item_teamflag", "AddOutput", "OnPickup !self:RunScriptCode:HideAnnotation(321):0:-1", -1, null, null) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(321)", 20.0, null, null) DoEntFire("gamerules", "RunScriptCode", "ShowAnnotation(`Grab a bomb from spawn to blow up the barricade!`, -1, Vector(946, -5264, -120), 321)", 30.0, null, null) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(321)", 40.0, null, null) PaulingAnnouncement("Picking up a lot of movement near REDs base|There might be leftover explosives|Look around for something to clear that path.") break case 6: ShowAnnotation("Get to the courtyard!", -1, Vector(716, -251, -10), 654) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(654)", 10.0, null, null) PaulingAnnouncement("This doesn't look good, RED is nowhere to be seen|Stock up while you can|I'm seeing more of those things nearby") break case 7: ShowAnnotation("Make your way into REDs base!", -1, Vector(-3108, -588, 85), 555) DoEntFire("soundscape_spawn", "AddOutput", "OnPlay !self:RunScriptCode:HideAnnotation(555):0:-1", -1, null, null) break case 8: ShowAnnotation("A storm is coming! Enable the generators to prepare", -1, Vector(-2000, -332, 83), 1337) DoEntFire("PT_GENERATORBUTTON", "AddOutput", "OnPressed !self:RunScriptCode:HideAnnotation(1337):0:-1", -1, null, null) PaulingAnnouncement("Heads up, really bad storm is approaching|Start those generators before you lose power|There should be some gas cans nearby.") break case 9: ShowAnnotation("Take Shelter In the Cave!", -1, Vector(2299.142090, -768.861206, 137.216324), 5432) DoEntFire("gamerules", "RunScriptCode", "HideAnnotation(5432)", 16.0, null, null) DoEntFire("gascan*", "Kill", "", -1, null, null) break case 10: ShowAnnotation("GENERATOR DISABLED!", 10, Vector(-1992, -346, 86), 111) ShowAnnotation("GENERATOR DISABLED!", 10, Vector(2144, -811, -25), 112) ShowAnnotation("GENERATOR DISABLED!", 10, Vector(-441, -3234, 92), 113) DoEntFire("PT_GENERATORBUTTON", "AddOutput", "OnPressed !self:RunScriptCode:HideAnnotation(111):0:-1", 1, null, null) DoEntFire("PT_GENERATORBUTTON2", "AddOutput", "OnPressed !self:RunScriptCode:HideAnnotation(112):0:-1", 1, null, null) DoEntFire("PT_GENERATORBUTTON3", "AddOutput", "OnPressed !self:RunScriptCode:HideAnnotation(113):0:-1", 1, null, null) break case 11: PaulingAnnouncement("It looks like RED was working on something big underground|You'll need to blast your way in|Grab another bomb, there should be more in that tunnel.") DoEntFire("item_teamflag", "AddOutput", "OnPickup !self:RunScriptCode:HideAnnotation(222):0:-1", -1, null, null) DoEntFire("item_teamflag", "AddOutput", "OnDrop !self:RunScriptCode:HideAnnotation(222):0:-1", -1, null, null) DoEntFire("item_teamflag", "AddOutput", "OnPickup !self:RunScriptCode:HideAnnotation(2222):0:-1", -1, null, null) DoEntFire("item_teamflag", "AddOutput", "OnDrop !self:RunScriptCode:HideAnnotation(2222):0:-1", -1, null, null) DoEntFire("gamerules", "RunScriptCode", "ShowAnnotation(`Grab a bomb from spawn to blow up the hatch!`, -1, Vector(946, -5264, -120), 222)", 10.0, null, null) DoEntFire("gamerules", "RunScriptCode", "ShowAnnotation(`Hatch`, -1, Vector(-2078, -562, 72), 2222)", 10.0, null, null) break case 12: ShowAnnotation("Deploy the bomb!", 10, FindByName(null, "bomb_deploy_relay").GetOrigin(), 113) PaulingAnnouncement("Alright, drop a bomb in the hatch|No idea what to expect, be prepared") break case 13: for (local i = 1; i < 5; i++) { local beamprop = FindByName(null, format("beamprop%d", i)) ShowAnnotation("Break the emitters!", -1, beamprop.GetOrigin(), i) SetPropBool(beamprop, "m_bGlowEnabled", true) } break case 14: ShowAnnotation("Enable the power", -1, Vector(-2826.048584, 2018.988770, -2971.968750), 69) DoEntFire("pt_powerbutton", "AddOutput", "OnPressed !self:RunScriptCode:HideAnnotation(69):0:-1", -1, null, null) } } ::SetTurretState <- function(state) { const STATE_RETRACT = 0 const STATE_DEPLOY = 1 const STATE_DESTROYED = 2 //set sentry owner function SetTurretOwner(turret, player) { _SetOwner(turret, player) SetPropEntity(turret, "m_hBuilder", player) turret.SetTeam(player.GetTeam()) //hidden bonus engineer activated turrets are not nerfed if (player.GetPlayerClass() == TF_CLASS_ENGINEER) return; player.AddCustomAttribute("engy sentry fire rate increased", 2, 20) player.AddCustomAttribute("engy sentry damage bonus", 0.85, 20) } for (local turret; turret = FindByName(turret, "PT_TURRETS*");) { if (turret == null) continue switch(state) { case STATE_RETRACT: EntFireByHandle(turret, "Disable", "", -1, null, null) turret.ResetSequence(turret.LookupSequence("ACT_OBJ_DISMANTLING")) EntFireByHandle(turret, "RunScriptCode", "self.ResetSequence(self.LookupSequence(`ACT_OBJ_IDLE`))", 3, null, null) EntFireByHandle(turret, "Disable", "", 3.01, null, null) break case STATE_DEPLOY: turret.ResetSequence(turret.LookupSequence("ACT_OBJ_ASSEMBLING")) EntFireByHandle(turret, "RunScriptCode", "self.ResetSequence(self.LookupSequence(`ACT_OBJ_RUNNING`))", 2.5, null, null) EntFireByHandle(turret, "Show", "", 2.5, null, null) SetTurretOwner(turret, activator) break case STATE_DESTROYED: DoEntFire("PT_SWITCHBUTTON4", "Kill", "", -1, null, null) EntFireByHandle(turret, "Disable", "", -1, null, null) turret.ResetSequence(turret.LookupSequence("ACT_OBJ_DETERIORATING")) EntFireByHandle(turret, "RunScriptCode", "self.ResetSequence(self.LookupSequence(`ACT_OBJ_IDLE`))", 3, null, null) EntFireByHandle(turret, "Disable", "", 3.01, null, null) turret.KeyValueFromString("targetname", "") // EntFireByHandle(turret, "RemoveHealth", "9999", 3.02, null, null) break } } } //block navs here since func_door is apparently unreliable ::BlockNavs <- function(unblock = false) { local GetNavAreaByID = NavMesh.GetNavAreaByID.bindenv(::NavMesh) //old nav, regenerated 6/11/24 // local navarray = [ // 30, // 40, // 5571, // 3, // 4624, // 65, // 5511, // 5512, // 248, // 307, // 889, // 4679, // 672, // 847, // 5729, // 5228, // 5227, // 5371, // 5372, // 4604, // 1020, // 16, // 17, // 204 // ] local navs = {} navs.rawset(0, GetNavAreaByID(293)) GetNavAreasInRadius(Vector(1453.681763, -962.355225, 68.031319), 300, navs) local navstayblocked = {} GetNavAreasInRadius(Vector(668.164673, -1255.695801, 76.031319), 200, navstayblocked) foreach(k, v in navstayblocked) navs[k] <- v // if (__wavenum != 3) // foreach(nav in __navareas) // if (nav.GetZ(nav.GetCenter()) < -2000.0) // navarray.append(nav.GetID()) // else // foreach(nav in __navareas) // if (nav.GetZ(nav.GetCenter()) > -2000.0) // navarray.append(nav.GetID()) // GetNavAreaByID(5358).UnblockArea(); //was blocked by func_brush, this brush no longer intersects nav. // GetNavAreaByID(81).UnblockArea(); //was blocked by func_brush, this brush no longer intersects nav. // GetNavAreaByID(128).UnblockArea(); //was blocked by func_brush, this brush no longer intersects nav. // GetNavAreaByID(246).UnblockArea(); //was blocked by func_brush, this brush no longer intersects nav. if (unblock) { foreach (_, nav in navs) if (nav.IsBlocked(TF_TEAM_PVE_DEFENDERS, true) && !(nav in navstayblocked)) nav.UnblockArea() foreach(_, nav in navstayblocked) if (!nav.IsBlocked(TF_TEAM_PVE_DEFENDERS, true)) nav.MarkAsBlocked(TF_TEAM_PVE_DEFENDERS) return } //block foreach (_, nav in navs) if (!nav.IsBlocked(TF_TEAM_PVE_DEFENDERS, true)) nav.MarkAsBlocked(TF_TEAM_PVE_DEFENDERS) //lazy way to block nav around gun boxes // for (local boxes; boxes = FindByName(boxes, "PT_SWITCHGUNBUTTON2*");) // { // boxspots.rawset(boxes, boxes.GetOrigin()); // AddOutput(boxes, "OnPressed", "gamerules", "CallScriptFunction", "CheckNavNearGunboxes", -1, 3); // } } // ::W3NavSwap <- function() { foreach (nav in __navareas) nav.GetZ(nav.GetCenter()) > -2000.0 ? nav.MarkAsBlocked(TF_TEAM_PVE_DEFENDERS) : nav.UnblockArea() } // ::CheckNavNearGunboxes <- function() // { // foreach (box, org in boxspots) // { // if (box.IsValid()) // NavMesh.GetNearestNavArea(org, 256, false, true).MarkAsBlocked(TF_TEAM_PVE_DEFENDERS) // else // { // NavMesh.GetNearestNavArea(org, 256, false, true).UnblockArea() // boxspots.rawdelete(box) // } // } // } //this function fires on wave init ::CFive <- function() { Optimize() __resource.ValidateScriptScope() __gamerules.ValidateScriptScope() __mvmlogic.ValidateScriptScope() __worldspawn.ValidateScriptScope() __mvmlogic.GetScriptScope().FFCountdown <- FFCountdown __worldspawn.GetScriptScope().ReverseTeamsEx <- ReverseTeamsEx AddThinkToEnt(__worldspawn, "ReverseTeamsEx") DoEntFire("PT_SWITCH*", "Lock", "", -1, null, null) DoEntFire("PT_SWITCHGUNBUTTON*", "Unlock", "", 0.015, null, null) DoEntFire("player", "SetScriptOverlayMaterial", "", -1, null, null) DoEntFire("PT_ROCK*", "SetDamageFilter", "filter_red_team", -1, null, null) DoEntFire("mist*", "SetFarZ", "9999", 1, null, null) local capturezone = FindByClassname(null, "func_capturezone") bomb = FindByName(bomb, "bomb1_timed") //save pop name for later if (popname == "") popname = GetPropString(__resource, "m_iszMvMPopfileName") // popname = split(GetPropString(__resource, "m_iszMvMPopfileName"), "/")[2] // AddOutput(__startrelay, "OnTrigger", "!self", "CallScriptFunction", "WaveStart", 0 , 1); //moved to cfive_events SetPropString(__resource, "m_iszMvMPopfileName", "Downpour: Category Five") //force mobber behavior SetConvar("tf_bot_escort_range", INT_MAX) SetConvar("tf_bot_flag_escort_range", INT_MAX) SetConvar("tf_bot_flag_escort_max_count", 0) SetConvar("tf_mvm_bot_flag_carrier_interval_to_1st_upgrade", INT_MAX) //stop bomb upgrade sounds/effects SetConvar("tf_bot_ammo_search_range", 0) //don't look for ammo packs //remove bomb slow SetConvar("tf_mvm_bot_flag_carrier_movement_penalty", 1.0) //remove buybacks SetConvar("tf_mvm_buybacks_method", 1) SetConvar("tf_mvm_buybacks_per_wave", 0) //temporarily block ready-up //we count players in 0ry_application, ready-up can be done with macros before spawning and will mess things up. SetConvar("tf_mvm_min_players_to_start", 999) DoEntFire("gamerules", "RunScriptCode", "SetConvar(`tf_mvm_min_players_to_start`, 1)", 3.5, null, null) SetPropInt(__resource, "m_iBossHealthPercentageByte", 0) //disable boss bar SetPropInt(__resource, "m_flMvMBaseBombUpgradeTime", INT_MAX) //stop bomb upgrade sounds/effects //block holiday ropes SetPropBool(__gamerules, "m_bRopesHolidayLightsAllowed", false) //stop crystals DoEntFire("spawncrystals", "CancelPending", "", 0.1, null, null) //soundscape stuff for (local soundscape; soundscape = FindByClassname(soundscape, "env_soundscape*");) RemoveOutput(soundscape, "OnPlay", "!self", "CallScriptFunction", "StormSlow") DoEntFire("env_soundscape", "RunScriptCode", "RemoveOutput(self, `OnPlay`, `!self`, `CallScriptFunction`, `StormSlow`)", -1, null, null) DoEntFire("beamcc", "Disable", "", -1, null, null) function RemoveWind() { RemoveOutput(wind, "OnGustStart", "windshuffle", "PickRandomShuffle", "") RemoveOutput(wind, "OnGustStart", "shakeit2", "StartShake", "") RemoveOutput(wind, "OnGustEnd", "shakeit2", "StopShake", "") } local winbots = FindByClassname(null, "win_bots") AddOutput(winbots, "OnRoundWin", "!self", "CallScriptFunction", "RemoveWind", -1, -1) //wave-specific switch(__wavenum) { case 1: DoEntFire("player", "RunScriptCode", "self.SetCurrency(0)", -1, null, null) // DoEntFire("gamerules", "CallScriptFunction", "GetValidCrystalSpots", 1, null, null) break case 2: DoEntFire("spawntele", "Disable", "", -1, null, null) DoEntFire("spawntele1", "Enable", "", 0.1, null, null) DoEntFire("player", "RunScriptCode", "self.SetCurrency(1000)", -1, null, null) RemoveOutputAll(FindByName(null, "PT_SWITCHBUTTON2"), "OnPressed") break case 3: DoEntFire("obj_sentrygun", "Kill", "", -1, null, null) DoEntFire("mineblocker", "Kill", "", -1, null, null) DoEntFire("spawntele", "Disable", "", -1, null, null) DoEntFire("spawntele1", "Disable", "", -1, null, null) DoEntFire("spawntele2", "Enable", "", -1, null, null) DoEntFire("hinttrigger", "Kill", "", -1, null, null) // DoEntFire("playerspawnred", "Disable", "", -1, null, null) // DoEntFire("boss_fight_dude", "Disable", "", -1, null, null) DoEntFire("bomb_deploy_relay", "AddOutput", "OnTrigger boss_fight_dude:Enable::0:-1", -1, null, null) DoEntFire("truck*", "Kill", "", -1, null, null) DoEntFire("explodecapture", "Kill", "", -1, null, null) // DoEntFire("beamfx*", "AddOutput", "OnTrigger !self:CallScriptFunction:Wave3CCEffect:0:-1", 0.015, null, null) DoEntFire("player", "RunScriptCode", "if (!IsPlayerABot(self)) self.ForceRespawn(); if (!IsPlayerABot(self)) { self.SetOrigin(Vector(3520.062012, -1186.839966, -57.603889)); self.SetAbsAngles(QAngle(0, 180, 0)) }; self.AddCurrency(2000); if (self.GetCurrency() > 3000) self.SetCurrency(3000);", 1, null, null) DoEntFire("player", "RunScriptCode", "if (!IsPlayerABot(self)) self.ForceRespawn(); if (!IsPlayerABot(self)) { self.SetOrigin(Vector(3520.062012, -1186.839966, -57.603889)); self.SetAbsAngles(QAngle(0, 180, 0)) }; self.AddCurrency(2000); if (self.GetCurrency() > 3000) self.SetCurrency(3000);", 1.1, null, null) // DoEntFire(string target,s string action, string value, float delay, handle activator, handle caller) local beamtarget = FindByName(null, "boss_beam_target") local beameffects = [ "self.AddCondEx(TF_COND_CRITBOOSTED_USER_BUFF, 1.0, null)", "self.AddCondEx(TF_COND_RUNE_KING, 1.0, null)", "self.AddCondEx(TF_COND_DEFENSEBUFF, 1.0, null)", "self.AddCondEx(TF_COND_SPEED_BOOST, 1.0, null)", ] local i = 0 local j = 0 for (local beam; beam = FindByClassname(beam, "env_laser");) { local triggerOrigin = beam.GetOrigin() local dispenserDummy = SpawnEntityFromTable("dispenser_touch_trigger", { targetname = format("dispenser_trigger%d", j) spawnflags = 1 StartDisabled = 1 origin = triggerOrigin - Vector(0, 0, 300) }) dispenserDummy.AcceptInput("SetParent", "!activator", beam, beam) local cartDispenser = SpawnEntityFromTable("mapobj_cart_dispenser", { targetname = format("cart_dispenser%d", j) origin = triggerOrigin spawnflags = 4 TeamNum = 3 StartDisabled = 1 touch_trigger = format("dispenser_trigger%d", j) }) cartDispenser.AcceptInput("SetParent", "!activator", beam, beam) j++ dispenserDummy.SetSize(Vector(-500, -500, -500), Vector(500, 500, 500)) dispenserDummy.SetSolid(SOLID_BBOX) beam.ValidateScriptScope() beam.GetScriptScope().BossBeamThink <- function() { local boss = beamtarget.GetMoveParent() if (boss && (boss.GetOrigin() - GetPropVector(self, "m_vecEndPos")).Length() < 150.0) EntFireByHandle(boss, "RunScriptCode", beameffects[i], -1, null, null) // if (boss) print(GetPropVector(self, "m_vecEndPos")) i++ if (i == beameffects.len()) i = 0 return -1 } AddThinkToEnt(beam, "BossBeamThink") } break } if (MAX_CLIENTS < 64) ClientPrint(null, HUD_PRINTTALK, format("%sMAXPLAYERS IS TOO LOW! SET MAXPLAYERS TO 64!\nMAXPLAYERS IS TOO LOW! SET MAXPLAYERS TO 64!\nMAXPLAYERS IS TOO LOW! SET MAXPLAYERS TO 64!", COLOR_RED)) } ::CFiveEnd <- function() { //map rotation breaks on victory if this isn't set back to default SetPropString(__resource, "m_iszMvMPopfileName", popname) } //this thing is fucked //for some reason lower values are stronger and > 1 values are weaker ::Wave3LightglowEffect <- function() { self.ValidateScriptScope() self.GetScriptScope().LightglowThink <- function() { if (GetPropInt(self, "m_clrRender") >= -128 && GetPropInt(self, "m_clrRender") <= 128) { SetPropString(self, "m_iszScriptThinkFunction", "") delete self.GetScriptScope().LightglowThink self.Kill() return } local color = GetEntityColor(self) SetEntityColor(self, color.r - 5, color.g - 5, color.b - 5, color.a - 5) } // EntFireByHandle(self, "RunScriptCode", "AddThinkToEnt(self, `BeamselfThink`)", 2, null, null) AddThinkToEnt(self, "LightglowThink") } ::StunBoss <- function(boss = null) { local stun = SpawnEntityFromTable("trigger_stun", { targetname = "__boss_stun" stun_type = 1 stun_duration = 999 move_speed_reduction = 1.0 trigger_delay = 0 StartDisabled = 0 spawnflags = 1 }) stun.SetSolid(2) stun.SetSize(Vector(), Vector(1, 1, 1)) if (!boss) boss = FindByName(null, "boss_beam_target").GetMoveParent() // null activator on trigger_stun = CRASH! if (!boss) return boss.RemoveCond(TF_COND_PHASE) EntFireByHandle(stun, "EndTouch", "", 0.1, boss, boss) EntFireByHandle(boss, "RunScriptCode", "self.GetActiveWeapon().EnableDraw()", 1, null, null) } ::BeginCount <- function() { SetPropInt(__monsterresource, "m_iBossHealthPercentageByte", 255) timeractive = true AddThinkToEnt(__mvmlogic, "FFCountdown") } ::FFCountdown <- function() { local bossbar = GetPropInt(__monsterresource, "m_iBossHealthPercentageByte") if (bossbar < 1) { timeractive = false AddThinkToEnt(__mvmlogic, null) return TIMER_INTERVAL } if (timeractive) { SetPropInt(__monsterresource, "m_iBossHealthPercentageByte", bossbar - 1) // foreach (k, v in this) // printl(k+" : "+v) // printl(this) return TIMER_INTERVAL } return TIMER_INTERVAL } ::BombTimer <- function() { //move this to map file AddOutput(bomb, "OnReturn", "tunnelforcefieldblu", "Kill", "", 0, -1) AddOutput(bomb, "OnReturn", "!self", "RunScriptCode", "AddThinkToEnt(self , null)", 0, -1) // bomb.EmitSound("vo/taunts/engy/taunt_engineer_lounge_button_press.mp3") EmitSoundOn("engy_taunt_killertime_1_button", bomb) return BOMBTICK_INTERVAL } ::StartBomb <- function() { self.ValidateScriptScope() self.GetScriptScope().BombTimer <- BombTimer AddThinkToEnt(self, "BombTimer") } ::CrystalBuff <- function(crystalactivator) { foreach (player, userid in playertable) { if (player == null) continue if (IsPlayerDead(player) && player.GetTeam() == TF_TEAM_PVE_INVADERS) { player.ForceRespawn() player.SetOrigin(crystalactivator.GetOrigin()) ClientPrint(null, HUD_PRINTCENTER , "DEAD PLAYERS REVIVED!") ClientPrint(null, HUD_PRINTTALK , format("%sDEAD PLAYERS REVIVED!%s", COLOR_NAVY_BLUE, COLOR_END)) EntFireByHandle(player, "SetFogController", format("%s", activefogcontroller), -1, null, null) EntFireByHandle(player, "RunScriptCode", "StripEverythingExcept(self, SLOT_MELEE, false, true)", -1, null, null) EntFireByHandle(player, "RunScriptCode", "GiveLastKnownLoadout(self)", 0.1, null, null) EntFireByHandle(player, "RunScriptCode", "self.AddCondEx(TF_COND_INVULNERABLE_CARD_EFFECT, 3.0, null)", 0.1, null, null) } player.AddCondEx(TF_COND_HALLOWEEN_QUICK_HEAL, 6.0, null) player.AddCondEx(TF_COND_SPEED_BOOST, 6.0, null) player.AddCondEx(TF_COND_OFFENSEBUFF, 6.0, null) } } ::GetValidCrystalSpots <- function() { if (!__wavestart) return; local spots = []; local minareasize = 1500; foreach (_, area in __navareas) //area 104 = behind middle forcefield, doesn't open until w3 if (!area.IsBlocked(TF_TEAM_PVE_DEFENDERS, true) && area.GetSizeX() * area.GetSizeY() > minareasize && ((__wavenum != 3 && area.GetZ(self.GetOrigin()) > -2000 && bossunlocked) || (__wavenum == 3 && area.GetZ(self.GetOrigin()) < -2000))) spots.append(area.GetCenter() + Vector(0, 0, 50)); for (local numcrystals = 0; numcrystals <= MAX_RESPAWN_CRYSTALS_ACTIVE; numcrystals++) { local spot = spots[RandomInt(0, spots.len() - 1)] try { SpawnEntityGroupFromTable({ [0] = { func_rotating = { message = "hl1/ambience/labdrone2.wav", vscripts = "ent_additions", volume = 8, responsecontext = "-1 -1 -1 1 1 1", // vscripts = "ent_additions", targetname = format("crystal_spin%d", numcrystals), spawnflags = 65, solidbsp = 0, rendermode = 10, rendercolor = "255 255 255", renderamt = 255, maxspeed = 48, fanfriction = 20, origin = spot, } }, [1] = { trigger_multiple = { targetname = format("crystaltrigger%d",numcrystals), spawnflags = 1, parentname = format("crystal_spin%d", numcrystals), filtername = "filter_blu_team", responsecontext = "-20 -20 -20 20 20 20", vscripts = "ent_additions", origin = spot, "OnStartTouchAll#1": "!activatorRunScriptCodeCrystalBuff(self)0.15-1", "OnStartTouchAll#2": "gamerulesPlayVOnpc/roller/mine/rmine_explode_shock1.wav0-1", "OnStartTouchAll#3": "PT_SHAKEStartShake0-1", "OnStartTouchAll#4": "spawntele1AddOutputtarget !self0-1", "OnStartTouchAll#5": "afkslayDisable0-1", "OnStartTouchAll#6": "spawntele1Enable0.1-1", "OnStartTouchAll#7": "lighttrigger*Kill0-1", "OnStartTouchAll#8": "crystal*Kill0-1", "OnStartTouchAll#9": "crystallight*Kill1-1", "OnStartTouchAll#11": "crystal_spin*Stop0-1", "OnStartTouchAll#12": "crystal_spin*Kill1-1", "OnStartTouchAll#13": "afkslayEnable3-1", "OnStartTouchAll#14": "gamerulesRunScriptCodeif (crystalsactive == false) DoEntFire(`spawncrystals`,`Trigger`,``,-1,null,null)60-1", "OnStartTouchAll#15": "!selfKillHierarchy60.2-1", "OnStartTouchAll#16": "spawntele1AddOutputtarget afkslay5-1", "OnStartTouchAll#17": "!selfRunScriptCodecrystalsactive = false0-1", "OnStartTouchAll#18": "gamerulesRunScriptCodeClientPrintSafe(null, `^1337ADRespawn crystals spawning soon...^`)40-1", "OnStartTouchAll#19": "gamerulesCallScriptFunctionGetValidCrystalSpots60-1", } }, [2] = { trigger_multiple = { targetname = format("lighttrigger%d",numcrystals), spawnflags = 1, parentname = format("crystal_spin%d", numcrystals), filtername = "filter_blu_team", responsecontext = "-500 -500 -500 500 500 500", vscripts = "ent_additions", origin = spot, "OnStartTouchAll#1": "crystallightTurnOn0-1", "OnEndTouchAll#2": "crystallightTurnOff0-1", } }, [3] = { tf_glow = { targetname = format("crystalglow%d",numcrystals), parentname = format("crystal%d", numcrystals), target = format("crystal%d", numcrystals), Mode = 2, origin = spot, GlowColor = "0 78 255 255" } }, [4] = { prop_dynamic = { targetname = format("crystal%d", numcrystals), solid = 6, renderfx = 15, rendercolor = "255 255 255", renderamt = 255, physdamagescale = 1.0, parentname = format("crystal_spin%d", numcrystals), modelscale = 1.3, model = "models/props_moonbase/moon_gravel_crystal_blue.mdl", MinAnimTime = 5, MaxAnimTime = 10, fadescale = 1.0, fademindist = -1.0, origin = spot, angles = QAngle(45, 0, 0) } }, [5] = { light_dynamic = { targetname = format("crystallight%d",numcrystals), target = format("crystal%d", numcrystals), style = 2, // vscripts = "ent_additions", spotlight_radius = 128, spawnflags = 17, pitch = -90.0, parentname = format("crystal_spin%d", numcrystals), distance = 128, brightness = 6, origin = spot, _light = "0 78 255", _inner_cone = 0, _cone = 0 } }, }) } catch(err) { printl(err) } } return spots; } //knock 15 seconds off crystal regen time ::ReduceCrystalTime <- function() { for (local trigger; trigger = FindByName(trigger, "crystaltrigger*");) { RemoveOutput(trigger, "OnStartTouchAll", "spawncrystals", "Trigger", ""); RemoveOutput(trigger, "OnStartTouchAll", "!self", "KillHierarchy", ""); EntFireByHandle(trigger, "AddOutput", "OnStartTouchAll spawncrystals:Trigger::25.0:-1", -1, null, null) EntFireByHandle(trigger, "AddOutput", "OnStartTouchAll !self:KillHierarchy::25.2:-1", -1, null, null) } } ::SpawnGasCans <- function() { local spots = []; local minareasize = 1500; foreach (_, area in __navareas) //area 104 = behind middle forcefield, doesn't open until w3 if (area != NavMesh.GetNavAreaByID(104) && !area.IsBlocked(TF_TEAM_PVE_DEFENDERS, true) && area.GetSizeX() * area.GetSizeY() > minareasize && area.GetZ(self.GetOrigin()) > -2000) spots.append(area.GetCenter() + Vector(0, 0, 50)); foreach (player, userid in playertable) EntFireByHandle(gascantext, "Display", "", -1, player, player) for (local numgascans = 0; numgascans <= MAX_GASCANS_ACTIVE; numgascans++) { local spot = spots[RandomInt(0, spots.len() - 1)] try { SpawnEntityGroupFromTable({ [0] = { func_rotating = { responsecontext = "-1 -1 -1 1 1 1", vscripts = "ent_additions", targetname = format("gascan_spin%d",numgascans), spawnflags = 65, solidbsp = 0, rendermode = 10, rendercolor = "255 255 255", renderamt = 255, maxspeed = 48, fanfriction = 20, origin = spot, } }, [1] = { prop_dynamic = { targetname = format("gascan%d",numgascans), solid = 0, rendercolor = "255 255 255", renderamt = 255, physdamagescale = 1.0, parentname = format("gascan_spin%d",numgascans), model = "models/weapons/c_models/c_gascan/c_gascan.mdl", vscripts = "ent_additions", // responsecontext = "rotate_speed = 50" MinAnimTime = 5, MaxAnimTime = 10, fadescale = 1.0, fademindist = -1.0, origin = spot, } }, [2] = { trigger_multiple = { targetname = format("gascan_trigger%d", numgascans), spawnflags = 1, parentname = format("gascan%d",numgascans), responsecontext = "-20 -20 -20 20 20 20", vscripts = "ent_additions", origin = spot, "OnStartTouchAll#1": format("!activatorRunScriptCodePickupGasCan(self, `gascan_spin%d`)-1-1", numgascans), } }, [3] = { tf_glow = { targetname = format("gascan_glow",numgascans), parentname = format("gascan%d",numgascans), target = format("gascan%d",numgascans), Mode = 2, origin = spot, GlowColor = "3 128 0 255" } }, }) } catch(err) printl(err) } return spots; } ::PickupGasCan <- function(player, gascan) { local scope = player.GetScriptScope() if (player.IsBotOfType(1337) || ("numgascans" in scope && scope.numgascans >= 3)) return // ClientPrint(player, 4, "Picked up a gas can!") EmitSoundOnClient("Weapon_GasCan.Throw", player) "numgascans" in scope ? scope.numgascans++ : scope.numgascans <- 1 FindByName(null, gascan).Kill() SetPropString(gascantext, "m_iszMessage", format("Gas Cans: %d/3", scope.numgascans)) EntFireByHandle(gascantext, "Display", "", -1, player, player) } ::DepositGasCan <- function() { local name = GetPropString(self, "m_iName") for (local player; player = FindByClassnameWithin(player, "player", self.GetOrigin(), 128);) { local scope = player.GetScriptScope() if ("numgascans" in scope && scope.numgascans > 0) foreach (bot, userid in bottable) { if ((bot.HasBotTag("generator1") && name == "PT_GENERATORBUTTON") || (bot.HasBotTag("generator2") && name == "PT_GENERATORBUTTON2") || (bot.HasBotTag("generator3") && name == "PT_GENERATORBUTTON3")) { bot.SetHealth(bot.GetHealth() + 40) scope.numgascans-- EmitSoundOn("Weapon_GasCan.Throw", player) SetPropString(gascantext, "m_iszMessage", format("Gas Cans: %d/3", scope.numgascans)) EntFireByHandle(gascantext, "Display", "", -1, player, player) } } } return 1 } SetPropBool(gascantext, "m_bForcePurgeFixedupStrings", true) ::StunBots <- function(duration = 15) { local trigger_stun = SpawnEntityFromTable("trigger_stun", { targetname = "stun", stun_type = 1, stun_duration = duration, move_speed_reduction = 1, trigger_delay = 0, StartDisabled = 0, spawnflags = 1, solid = 2 }); foreach (bot, userid in bottable) { if (IsPlayerDead(bot)) continue // bot.RemoveCond(TF_COND_PHASE) bot.AddCondEx(TF_COND_SAPPED, duration, null) EntFireByHandle(trigger_stun, "EndTouch", "", -1, bot, bot); EntFireByHandle(bot, "RunScriptCode", "self.GetActiveWeapon().EnableDraw()", duration+0.5, null, null) if (__wavenum != 3) return bot.AddCustomAttribute("health drain", -1000.0, duration) } EntFireByHandle(trigger_stun, "Kill", "", 1.0, null, null) if (__wavenum == 3) { EntFire("boss_fight_support", "Enable") EntFire("boss_fight_support", "Disable", "", duration) } // foreach (bot, userid in bottable) (StunPlayer(bot, 15, 1, 0, 1)); } //probably need to update these ::WavebarHack <- function() { SetPropIntArray(__resource, "m_nMannVsMachineWaveClassFlags", 0, 011) SetPropIntArray(__resource, "m_nMannVsMachineWaveClassFlags", 0, 004) } ::RestartGens <- function() { for (local gen; gen = FindByName(gen, "PT_GENERATORBUTTON*"); ) { RemoveOutputAll(gen, "OnPressed"); AddThinkToEnt(gen, null) } } ::GenLogic <- function() { for (local i = 1, buttonname = "", button; i < 4; i++) { foreach (bot, userid in bottable) { if (bot.HasBotTag("generator"+i)) { bot.AddCustomAttribute("health regen", -1, -1) bot.RemoveCustomAttribute("health drain") bot.KeyValueFromString("targetname", "genbot"+i); SetPropString(bot, "m_iName", "genbot"+i); buttonname = i == 1 ? "PT_GENERATORBUTTON" : "PT_GENERATORBUTTON"+i; } } button = FindByName(null, buttonname); AddOutput(button, "OnPressed", "genbot"+i, "RunScriptCode", "self.RemoveCustomAttribute(`health drain`)", 1, -1); AddOutput(button, "OnPressed", "genbot"+i, "RunScriptCode", "self.AddCustomAttribute(`health regen`, -1, -1)", 1.1, -1); AddOutput(button, "OnPressed", "!self", "RunScriptCode", "AddThinkToEnt(self, `DepositGasCan`)", -1, -1); } } ::StormSlow <- function() { if (__wavenum != 2) return; foreach (player, userid in playertable) { if (player == null) continue; if (InSoundscapeIndex(player, 34)) { // ScreenFade(player, 2, 8, 12, 150, 1, 0.1, 2) // EntFireByHandle(player, "RunScriptCode", "ScreenFade(self, 2, 8, 12, 150, 1, 1, 1)", 1, null, null) if (GetStr("sv_skyname") != "sky_downpour_heavy_storm") SetSkyboxTexture("sky_downpour_heavy_storm"); // player.AddCond(TF_COND_STUNNED); //boot icon // player.AddCustomAttribute("major move speed bonus", 0.75, 0); DoEntFire("mist2", "SetFarZ", "9999", -1, null, null) EntFireByHandle(player, "SetFogController" , "mist2" , 0.1 , null , null) DoEntFire("mist2", "SetFarZ", "800", 2, null, null) SetPropBool(player, "m_bGlowEnabled", false) continue; } SetPropBool(player, "m_bGlowEnabled", true); // player.RemoveCond(TF_COND_STUNNED); // player.RemoveCustomAttribute("major move speed bonus"); EntFireByHandle(player, "SetFogController" , "mist" , 0 , null , null) } } // ::ColorCorrectionThink <- function(player) // { // local scope = player.GetScriptScope(); // if (IsPlayerABot(player)) return; // local cc = FindByName(null, format("__cc%d", GetPlayerUserID(player))) // if (cc == null) // { // cc = SpawnEntityFromTable("color_correction", { // targetname = format("__cc%d", GetPlayerUserID(player)), // filename = "materials/colorcorrection/downpour.raw", // origin = player.GetOrigin() // spawnflags = 2, // maxweight = 1.0, // minfalloff = 1, // maxfalloff = 1, // }) // SetPropFloat(cc, "m_flCurWeight", 0.0) // } // if (!("cc" in scope) || ("cc" in scope && !scope.cc.IsValid())) scope.cc <- cc // function CCThink() // { // if (player == null) // { // scope.cc.Kill() // return // } // scope.cc.SetOrigin(player.GetOrigin()) // if (InSoundscapeIndex(player, 34)) // { // if (GetPropFloat(scope.cc, "m_flCurWeight") >= 0.4) return -1 // SetPropFloat(scope.cc, "m_flCurWeight", GetPropFloat(scope.cc, "m_flCurWeight") + 0.01) // return -1 // } // else // { // if (GetPropFloat(scope.cc, "m_flCurWeight") < 0.01) return // SetPropFloat(scope.cc, "m_flCurWeight", GetPropFloat(scope.cc, "m_flCurWeight") - 0.01) // return -1 // } // } // if (cc.GetScriptThinkFunc() != "CCThink") // { // cc.ValidateScriptScope() // cc.GetScriptScope().CCThink <- CCThink // AddThinkToEnt(scope.cc, "CCThink") // } // } //sets the gibby models for bots //not all of these models may exist yet, as it's an ongoing project by trigger_hurt //soldier/demo/heavy/pyro common and giant models have been packed into the map, others will need to be provided separately. ::SetModels <- function(player, classindex) { if (!IsPlayerABot(player)) return; local cstring = __ctable[classindex - 1]; local common = format("models/bots/%s/bot_%s_gibby.mdl", cstring, cstring) local giant = format("models/bots/%s_boss/bot_%s_boss_gibby.mdl", cstring, cstring) player.IsMiniBoss() ? EntFireByHandle(player, "SetCustomModelWithClassAnimations", giant, -1, null, null) : EntFireByHandle(player, "SetCustomModelWithClassAnimations", common, -1, null, null) } //set players back to red when the game ends or else the next wave won't load ::EndWaveChangeTeam <- function() { if (!__wavestart) return; __waveended = true //move to red foreach (player, userid in playertable) { if (player == null) continue; ChangePlayerTeamMvM(player, TF_TEAM_PVE_DEFENDERS); } function ClearWave() { //kill all bots foreach (bot, userid in bottable) { if (IsPlayerDead(bot) || bot.GetTeam() == TEAM_SPECTATOR) continue; bot.TakeDamage(INT_MAX, DMG_GENERIC, null) } } __startrelay.ValidateScriptScope() __startrelay.GetScriptScope().ClearWave <- ClearWave AddThinkToEnt(__startrelay, "ClearWave") }