//Utility Script //By Braindawg //Note: certain functions like CreateSpark and StunPlayer spawn entities that exist permanently after being spawned once. //This is to avoid constantly deleting and respawning the same entity multiple times if we use them in rapid succession. //using every single function here that relies on spawning an entity will maybe add 5 edicts to the map const SHREDDER_ENABLE = false; //basic edict overflow prevention, deletes decorations and eventually cosmetics const SHREDDER_THRESHOLD = 0.85; //max edict threshold, final resulting value is (MAX_EDICTS - MAX_CLIENTS) * SHREDDER_THRESHOLD. const SHREDDER_INTERVAL = 0.33; //shredder think interval in seconds (don't set this to -1, iterates through every edict every think) //pauling response flags ::PAULING_GAG <- -1; //no voiceline ::PAULING_GENERIC <- 0; //generic "hey it's pauling" lines ::PAULING_SUCCESS <- 1; //nice work/good job/etc ::PAULING_FAILURE <- 2; //contract incomplete IncludeScript("itemdef_constants.nut") ::ROOT <- getroottable(); //fold existing constants ::CONST <- getconsttable(); local ConVars = {} if (!("ConstantNamingConvention" in ::CONST)) // make sure folding is only done once { foreach (a,b in Constants) foreach (k,v in b) if (v == null) ::CONST[k] <- 0; //replace incorrect null constants with 0 else ::CONST[k] <- v; } //fold netprop entity and entityoutput functions into root table //don't just go folding everything, there are shared function names (notably in NavMesh) //you can fold these functions into local scope, rather than a root table if need be. foreach (k, v in ::NetProps.getclass()) if (k != "IsValid") ROOT[k] <- ::NetProps[k].bindenv(::NetProps); foreach (k, v in ::Entities.getclass()) if (k != "IsValid") ROOT[k] <- ::Entities[k].bindenv(::Entities); foreach (k, v in ::EntityOutputs.getclass()) if (k != "IsValid") ROOT[k] <- ::EntityOutputs[k].bindenv(::EntityOutputs); foreach (k, v in ::Convars.getclass()) if (k != "IsValid") ROOT[k] <- ::Convars[k].bindenv(::Convars); //allow defining constants with expressions CONST.setdelegate({ _newslot = @(k ,v) compilestring("const " + k + "=" + (typeof(v) == "string" ? ("\"" + v + "\"") : v))() }); CONST.MAX_CLIENTS <- MaxClients().tointeger(); //spell constants const SPELL_ROLLING = -2; const SPELL_EMPTY = -1; const SPELL_FIREBALL = 0; const SPELL_BATS = 1; const SPELL_OVERHEAL = 2; const SPELL_PUMPKIN = 3; const SPELL_SUPERJUMP = 4; const SPELL_STEALTH = 5; const SPELL_TELEPORT = 6; const SPELL_ENERGYORB = 7; const SPELL_MINIFY = 8; const SPELL_METEOR = 9; const SPELL_MONOCULOUS = 10; const SPELL_SKELETON = 11; const SPELL_BUMPER_BOXING_ROCKET = 12; const SPELL_BUMPER_PARACHUTE = 13; const SPELL_BUMPER_OVERHEAL = 14; const SPELL_BUMPER_BOMBHEAD = 15; const SPELL_COUNT = 16; //clientprint chat colors CONST.COLOR_END <- "\x07"; CONST.COLOR_BASE <- "\x07FBECCB"; CONST.COLOR_BLUE <- "\x07FF3F3F"; CONST.COLOR_RED <- "\x07FF3F3F"; CONST.COLOR_SPECTATOR <- "\x07CCCCCC"; CONST.COLOR_PAULING <- "\x07AD5AFF" CONST.COLOR_NAVY_BLUE <- "\x071337AD"; CONST.COLOR_DEEP_RED <- "\x07FF0000"; CONST.COLOR_DEFAULT <- "\x07FBECCB"; //wep slot constants const SLOT_PRIMARY = 0; const SLOT_SECONDARY = 1; const SLOT_MELEE = 2; const SLOT_UTILITY = 3; const SLOT_BUILDING = 4; const SLOT_PDA = 5; const SLOT_PDA2 = 6; const SLOT_COUNT = 7; //cosmetic slot constants (UNTESTED) const LOADOUT_POSITION_HEAD = 8 const LOADOUT_POSITION_MISC = 9 const LOADOUT_POSITION_ACTION = 10 const LOADOUT_POSITION_MISC2 = 11 //taunt slot constants (UNTESTED) const LOADOUT_POSITION_TAUNT = 12 const LOADOUT_POSITION_TAUNT2 = 13 const LOADOUT_POSITION_TAUNT3 = 14 const LOADOUT_POSITION_TAUNT4 = 15 const LOADOUT_POSITION_TAUNT5 = 16 const LOADOUT_POSITION_TAUNT6 = 17 const LOADOUT_POSITION_TAUNT7 = 18 const LOADOUT_POSITION_TAUNT8 = 19 //m_bloodColor constants const DONT_BLEED = -1; const BLOOD_COLOR_RED = 0; const BLOOD_COLOR_YELLOW = 1; const BLOOD_COLOR_GREEN = 2; const BLOOD_COLOR_MECH = 3; //m_iTeleportType constants const TTYPE_NONE = 0 const TTYPE_ENTRANCE = 1; const TTYPE_EXIT = 2; //tf_gamerules m_iRoundState const GR_STATE_BONUS = 9; const GR_STATE_BETWEEN_RNDS = 10; //m_lifeState constants const LIFE_ALIVE = 0; // alive const LIFE_DYING = 1; // playing death animation or still falling off of a ledge waiting to hit ground const LIFE_DEAD = 2; // dead. lying still. const LIFE_RESPAWNABLE = 3; const LIFE_DISCARDBODY = 4; //emitsoundex flags const SND_NOFLAGS = 0 const SND_CHANGE_VOL = 1 const SND_CHANGE_PITCH = 2 const SND_STOP = 4 const SND_SPAWNING = 8 const SND_DELAY = 16 const SND_STOP_LOOPING = 32 const SND_SPEAKER = 64 const SND_SHOULDPAUSE = 128 const SND_IGNORE_PHONEMES = 256 const SND_IGNORE_NAME = 512 const SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL = 1024 //DMG type bits, less confusing than shit like DMG_AIRBOAT or DMG_SLOWBURN const DMG_USE_HITLOCATIONS = 33554432 //DMG_AIRBOAT const DMG_HALF_FALLOFF = 262144 //DMG_RADIATION const DMG_CRITICAL = 1048576 //DMG_ACID const DMG_RADIUS_MAX = 1024 //DMG_ENERGYBEAM const DMG_IGNITE = 16777216 //DMG_PLASMA const DMG_USEDISTANCEMOD = 2097152 //DMG_SLOWBURN const DMG_NOCLOSEDISTANCEMOD = 131072 //DMG_POISON const DMG_FROM_OTHER_SAPPER = 16777216 //same as DMG_IGNITE const DMG_MELEE = 134217728 //DMG_BLAST_SURFACE const DMG_DONT_COUNT_DAMAGE_TOWARDS_CRIT_RATE = 67108864 //DMG_DISSOLVE //bot behavior flags //only useful for bot_generator const TFBOT_IGNORE_ENEMY_SCOUTS = 1 const TFBOT_IGNORE_ENEMY_SOLDIERS = 2 const TFBOT_IGNORE_ENEMY_PYROS = 4 const TFBOT_IGNORE_ENEMY_DEMOMEN = 8 const TFBOT_IGNORE_ENEMY_HEAVIES = 16 const TFBOT_IGNORE_ENEMY_MEDICS = 32 const TFBOT_IGNORE_ENEMY_ENGINEERS = 64 const TFBOT_IGNORE_ENEMY_SNIPERS = 128 const TFBOT_IGNORE_ENEMY_SPIES = 256 const TFBOT_IGNORE_ENEMY_SENTRY_GUNS = 512 const TFBOT_IGNORE_SCENARIO_GOALS = 1024 //emitsoundex channels const CHAN_REPLACE = -1 const CHAN_AUTO = 0 const CHAN_WEAPON = 1 const CHAN_VOICE = 2 const CHAN_ITEM = 3 const CHAN_BODY = 4 const CHAN_STREAM = 5 const CHAN_STATIC = 6 const CHAN_VOICE2 = 7 const CHAN_VOICE_BASE = 8 const CHAN_USER_BASE = 136 //ammo const TF_AMMO_PRIMARY = 1 const TF_AMMO_SECONDARY = 2 const TF_AMMO_METAL = 3 const TF_AMMO_GRENADES1 = 4 const TF_AMMO_GRENADES2 = 5 const TF_AMMO_GRENADES3 = 6 const TF_AMMO_COUNT = 7 //trigger entity spawnflags const SF_TRIGGER_ALLOW_CLIENTS = 1 const SF_TRIGGER_ALLOW_NPCS = 2 const SF_TRIGGER_ALLOW_PUSHABLES = 4 const SF_TRIGGER_ALLOW_PHYSICS = 8 const SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS = 16 const SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES = 32 const SF_TRIGGER_ALLOW_ALL = 64 const SF_TRIG_PUSH_ONCE = 128 const SF_TRIG_PUSH_AFFECT_PLAYER_ON_LADDER = 256 const SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES = 512 const SF_TRIG_TOUCH_DEBRIS = 1024 const SF_TRIGGER_ONLY_NPCS_IN_VEHICLES = 2048 const SF_TRIGGER_DISALLOW_BOTS = 4096 //max ammo values const MAXAMMO_BASE_SCOUT_PRIMARY = 32 const MAXAMMO_BASE_SCOUT_SECONDARY = 36 const MAXAMMO_BASE_SOLDIER_PRIMARY = 20 const MAXAMMO_BASE_SOLDIER_JUMPER = 60 const MAXAMMO_BASE_SOLDIER_SECONDARY = 32 const MAXAMMO_BASE_PYRO_PRIMARY = 200 const MAXAMMO_BASE_PYRO_DRAGONS_FURY = 40 const MAXAMMO_BASE_PYRO_SECONDARY = 32 const MAXAMMO_BASE_PYRO_FLARE = 32 const MAXAMMO_BASE_DEMO_PRIMARY = 16 const MAXAMMO_BASE_DEMO_SECONDARY = 24 const MAXAMMO_BASE_DEMO_SCOTRES_JUMPER = 36 const MAXAMMO_BASE_HEAVY_PRIMARY = 200 const MAXAMMO_BASE_HEAVY_SECONDARY = 32 const MAXAMMO_BASE_ENGI_PRIMARY = 32 const MAXAMMO_BASE_ENGI_RESCUE_RANGER = 16 const MAXAMMO_BASE_ENGI_SECONDARY = 200 const MAXAMMO_BASE_MEDIC_PRIMARY = 150 const MAXAMMO_BASE_MEDIC_CROSSBOW = 38 const MAXAMMO_BASE_SNIPER_PRIMARY = 25 const MAXAMMO_BASE_SNIPER_BOW = 12 const MAXAMMO_BASE_SNIPER_SECONDARY = 75 const MAXAMMO_BASE_SPY_PRIMARY = 24 //soundscape constants for InSoundscapeIndex const SOUNDSCAPE_SAWMILL_DEEPINSIDE = 30 const SOUNDSCAPE_SAWMILL_INSIDE = 31 const SOUNDSCAPE_SAWMILL_OUTSIDE = 34 const SOUNDSCAPE_NIGHTFALL_CAVERN = 70 const REDUCED_THINK_INTERVAL = 0.33; //random useful constants const FLT_SMALL = 0.0000001; const FLT_MIN = 1.175494e-38; const FLT_MAX = 3.402823466e+38; const INT_MIN = -2147483648; const INT_MAX = 2147483647; ::__ctable <- ["scout", "sniper", "soldier", "demo", "medic", "heavy", "pyro", "spy", "engineer"]; local armindex = []; foreach (arm in __ctable) if (arm == "soldier") armindex.append(PrecacheModel("models/weapons/c_models/c_soldier_arms_rocketless.mdl")); else armindex.append(PrecacheModel(format("models/weapons/c_models/c_%s_arms.mdl", arm))); ::__navareas <- {}; NavMesh.GetAllAreas(__navareas); //check a global variable instead of accessing a netprop every time to check if we are between waves. ::__wavestart <- false; // ::__wavenum <- 1; // function OnGameEvent_mvm_wave_complete(params) { __wavestart = false; wavenum++; } ::__UtilEvents <- { function OnGameEvent_mvm_wave_complete(params) { __wavestart = false; } function OnGameEvent_mvm_reset_stats(params) { __wavestart = false; } function OnGameEvent_mvm_wave_failed(params) { __wavestart = false; } function OnGameEvent_mvm_begin_wave(params) { __wavestart = true; } //reorganize m_hMyWeapons by weapon slot // function OnGameEvent_post_inventory_application(params) { // local player = GetPlayerFromUserID(params.userid) // local weapons = []; // for (local i = SLOT_COUNT, weapon; i >= 0; i--) // { // weapon = GetWeaponInSlot(player, i); // if (player == null || weapon == null || GetWeaponInSlot(player, i) == weapon.GetSlot()) continue; // weapons.append(weapon); // weapon.Kill(); // } // for (local i = SLOT_COUNT; i >= 0; i--) // SetWeaponInSlot(player, weapons[i], weapons[i].GetSlot()); // } }; __CollectGameEventCallbacks(__UtilEvents); ::__wavenum <- GetPropInt(FindByClassname(null, "tf_objective_resource"), "m_nMannVsMachineWaveCount"); //useful ents ::__gamerules <- FindByClassname(null, "tf_gamerules"); ::__resource <- FindByClassname(null, "tf_objective_resource"); ::__monsterresource <- FindByClassname(null, "monster_resource"); ::__mvmlogic <- FindByClassname(null, "tf_logic_mann_vs_machine"); ::__playermanager <- FindByClassname(null, "tf_player_manager"); ::__worldspawn <- FindByClassname(null, "worldspawn"); ::__startrelay <- FindByName(null, "wave_start_relay"); ::__finishrelay <- FindByName(null, "wave_finished_relay"); //spawn a point_clientcommand ::__clientcommand <- CreateByClassname("point_clientcommand"); DispatchSpawn(__clientcommand); ::GetPlayerUserID <- function(player) { return GetPropIntArray(__playermanager, "m_iUserID", player.entindex()); } ::IsMITM <- function() { return GetStr("sm_mitm_version") ? true : false } //TODO: check something else instead of cvar, this will blast console with errors if used in a think func ::IsSigmod <- function() { return GetStr("sig_color_console") ? true : false } ::IsLinuxServer <- function() { return RAND_MAX != 32767 ? true: false} ::StripWeapon <- function(player, slot = -1) { if (slot == -1) slot = player.GetActiveWeapon().GetSlot() for (local i = 0; i < SLOT_COUNT; i++) { local weapon = GetWeaponInSlot(player, i); if (weapon == null || weapon.GetSlot() != slot) continue; weapon.Kill(); break; } } ::ShowMessage <- function(message) { ClientPrint(null, 4, message); ClientPrint(null, 3, message) } local itemids = [] // slocal itemids = [ID_DARWINS_DANGER_SHIELD, ID_COZY_CAMPER, ID_ALI_BABAS_WEE_BOOTIES, ID_BOOTLEGGER, ID_BASE_JUMPER, ID_GUNBOATS, ID_MANTREADS] //strip everything except for the given slot ::StripEverythingExcept <- function(player, slot, keepbuffitems = false, keepengitoolbox = true) { for (local i = 0; i < SLOT_COUNT; i++) { // if (player.GetPlayerClass() < 8 && i > SLOT_UTILITY) break; //don't need to strip pda slots for non spy/engi local weapon = GetPropEntityArray(player, "m_hMyWeapons", i); if (weapon == null) continue; //don't strip classes with buff items, medic/spy, or utility slot items (spellbook canteen etc). MvM compatibility try { if (keepbuffitems && ( player.GetPlayerClass() == TF_CLASS_SCOUT || player.GetPlayerClass() == TF_CLASS_SOLDIER || player.GetPlayerClass() == TF_CLASS_MEDIC || player.GetPlayerClass() == TF_CLASS_SPY )) continue; } catch(err) {printl("KEEPBUFFITEMS FAILED!")} if (keepengitoolbox && (GetItemIndex(weapon) == ID_BUILDER)) continue; if (weapon.GetSlot() == slot) player.Weapon_Switch(weapon) else weapon.Destroy(); } for (local weapon = player.FirstMoveChild(); weapon != null; weapon = weapon.NextMovePeer()) if (weapon.GetClassname() == "tf_wearable" && itemids.find(GetItemIndex(weapon)) != null) { weapon.Destroy(); break; } } ::GetAllEnts <- function() { local entdata = { "entlist": [], "numents": 0 }; for (local i = MAX_CLIENTS, ent; i <= MAX_EDICTS; i++) { if (ent = EntIndexToHScript(i)) { entdata.numents++; entdata.entlist.append(ent) } } return entdata } //sets m_hOwnerEntity and m_hOwner to the same value ::_SetOwner <- function(ent, owner) { if (ent.GetOwner() != null && GetPropEntity(ent, "m_hOwnerEntity") != null && ent.GetOwner() != GetPropEntity(ent, "m_hOwnerEntity")) { ClientPrint(null, 3, "m_hOwnerEntity is "+GetPropEntity(ent, "m_hOwnerEntity")+" but m_hOwner is "+ent.GetOwner()) ClientPrint(null, 3, "m_hOwnerEntity is "+GetPropEntity(ent, "m_hOwnerEntity")+" but m_hOwner is "+ent.GetOwner()) ClientPrint(null, 3, "m_hOwnerEntity is "+GetPropEntity(ent, "m_hOwnerEntity")+" but m_hOwner is "+ent.GetOwner()) ClientPrint(null, 3, "m_hOwnerEntity is "+GetPropEntity(ent, "m_hOwnerEntity")+" but m_hOwner is "+ent.GetOwner()) ClientPrint(null, 3, "m_hOwnerEntity is "+GetPropEntity(ent, "m_hOwnerEntity")+" but m_hOwner is "+ent.GetOwner()) } ent.SetOwner(owner); SetPropEntity(ent, "m_hOwnerEntity", owner); } ::OnGround <-function(player) { return GetPropEntity(player, "m_hGroundEntity") != null } //this sucks just copy RunWithDelay from vsh // ::Delay <- function(func, params = "", delay = -1, ent = gamerules) // { // if (params == "") // EntFireByHandle(ent, "CallScriptFunction", format("%s", func), delay, null, null); // else // EntFireByHandle(ent, "RunScriptCode", format("%s(%s)", func, params), delay, null, null); // } //use better annotations // ::ShowAnnotation <- function(text = "This is an annotation", lifetime = 10, xyz = [0, 0, 0], id = 0, sound = "misc/null.wav", distance = true, effect = true) // { // SendGlobalGameEvent("show_annotation", { // text = text // lifetime = lifetime // worldPosX = xyz[0] // worldPosY = xyz[1] // worldPosZ = xyz[2] // id = id // play_sound = sound // show_distance = distance // show_effect = effect // }) // } ::FadeEmitSoundEx <- function(sound, seconds, ent, fadein = false, startingvol = 1.0) { local d = SpawnEntityFromTable("info_teleport_destination", {}) local sv = 0 fadein ? sv = 1-startingvol : sv = startingvol local decrement = startingvol / (seconds / 0.015) function FadeThink() { EmitSoundEx({sound_name = sound, flags = 1, entity = ent, volume = sv}) !fadein ? sv -= decrement : sv += decrement // This stops the thinking. if ((!fadein && sv <= 0.0) || (fadein && sv >= 1.0)) { EmitSoundEx({sound_name = sound, flags = 4, entity = ent}) self.Kill() } return -1 } d.ValidateScriptScope() d.GetScriptScope().FadeThink <- FadeThink AddThinkToEnt(d, "FadeThink") } ::ShowAnnotation <- function(text = "This is an annotation", lifetime = 10, pos = Vector(), id = 0, sound = "misc/null.wav", distance = true, effect = true, index = 0, visbit = 0) { SendGlobalGameEvent("show_annotation", { text = text lifetime = lifetime worldPosX = pos.x worldPosY = pos.y worldPosZ = pos.z id = id play_sound = sound show_distance = distance show_effect = effect follow_entindex = index visibilityBitfield = visbit }) } //the hide_annotation event is listen server only, this works instead ::HideAnnotation <- function(id) { ShowAnnotation("", FLT_SMALL, Vector(), id = id) } ::ViewControl <- function(player = null, ent = Entities.FindByClassname(null, "worldspawn"), org = Vector(), duration = 3.0) { local viewcontrol = SpawnEntityFromTable("point_viewcontrol", { targetname = "__viewcontrol" target = ent spawnflags = 68 //interruptable by player + freezes player origin = org wait = duration }) function ViewControlEnable() { local player = activator if (player == null) for (local i = 1, player; i <= MaxClients().tointeger(); i++); if (player == PlayerInstanceFromIndex(i) && !player.IsBotOfType(1337)) EntFireByHandle(self, "Enable", "", -1, player, player); } function ViewControlDisable() { local player = activator local lifestate = GetPropInt(player, "m_lifeState"); EntFireByHandle(viewcontrol, "Enable", "", -1, player, player); if (lifestate != 0) EntFireByHandle(player, "RunScriptCode", "NetProps.SetPropInt(self, `m_lifeState`, 0)", -1, player, player); EntFireByHandle(viewcontrol, "Disable", "", -1, player, player); EntFireByHandle(player, "RunScriptCode", "NetProps.SetPropInt(self, `m_lifeState`, "+lifestate+")", -1, player, player); if (player == null) for (local i = 1, player; i <= MaxClients().tointeger(); i++); { if (player == PlayerInstanceFromIndex(i) && !player.IsBotOfType(1337)) { lifestate = GetPropInt(player, "m_lifeState"); EntFireByHandle(viewcontrol, "Enable", "", -1, player, player); if (lifestate != 0) EntFireByHandle(player, "RunScriptCode", "NetProps.SetPropInt(self, `m_lifeState`, 0)", -1, player, player); EntFireByHandle(viewcontrol, "Disable", "", -1, player, player); EntFireByHandle(player, "RunScriptCode", "NetProps.SetPropInt(self, `m_lifeState`, "+lifestate+")", -1, player, player); } } } viewcontrol.ValidateScriptScope(); viewcontrol.GetScriptScope().InputEnable <- ViewControlEnable; viewcontrol.GetScriptScope().Inputenable <- ViewControlEnable; viewcontrol.GetScriptScope().InputDisable <- ViewControlDisable; viewcontrol.GetScriptScope().Inputdisable <- ViewControlDisable; EntFireByHandle(viewcontrol, "Disable", "", duration, null, null) } ::StripRomevision <- function(bot) { if (bot == null || bot.HasBotTag("bot_rome")) return; for (local child = bot.FirstMoveChild(); child != null; child = child.NextMovePeer()) if (child.GetClassname() == "tf_wearable" && startswith(child.GetModelName(), "tw_")) EntFireByHandle(child, "Kill", "", -1, null, null); } //this needs to be redone to deal with GiveWeapon related things better local j = 0 ::RefillAmmo <- function(player, givewep = false) { if (!givewep) { local h = player.GetHealth(); player.Regenerate(true); player.SetHealth(h); return; } local oldwep = player.GetActiveWeapon(); local oldslot = SLOT_PRIMARY; if (oldwep != null) oldslot = oldwep.GetSlot(); local t = {}; j++; for (local i = 0; i < SLOT_COUNT; i++) { local w = GetWeaponInSlot(player, i); if (w == null) continue; t.rawset(GetItemIndex(w), [w.GetClassname(), w.GetModelName()]); // printl(w + " : " + w.GetSlot()) } //regenerate and reset HP so we only get ammo refill //this will wipe out all GiveWeapon-created weapons local h = player.GetHealth(); player.Regenerate(true); player.SetHealth(h); StripEverythingExcept(player, -1); local loadout; if (player.GetPlayerClass() > 7) //spy and engi loadout = array(7); else loadout = array(3); local i = 0; foreach (k, v in t) { local classname = v[0], index = k, model = v[1]; // printl(format("%d : %s : %s", k, classname, model)); try { // if (classname == oldwep.GetClassname()) continue; loadout[i] = ([index, classname, model]); i++; } catch(err) {printl(err) } } i = 0; foreach (l in loadout) { if (l == null) continue; GiveWeapon(player, l[1], l[0].tointeger(), l[2]); } // printl("a" + l); SwitchWeaponSlot(player, oldslot) } //weapon_switch only takes an entity handle ::SwitchWeaponSlot <- function(player, slot) { EntFireByHandle(__clientcommand, "Command", format("slot%d", slot + 1), -1, player, player); } ::GetWeaponInSlot <- function(player, slot) { return GetPropEntityArray(player, "m_hMyWeapons", slot) } ::SetWeaponInSlot <- function(player, weapon, slot) { SetPropEntityArray(player, "m_hMyWeapons", weapon, slot) } //returns an array of max ammo values for each slot //do not use this, use the ammo constants instead ::GetMaxAmmo <- function(player) { local maxammo = array(SLOT_COUNT), oldammo = array(SLOT_COUNT), oldclip = array(SLOT_COUNT) local chargemeter = -1.0 for (local i = 0; i < SLOT_COUNT; i++) { oldammo[i] = GetPropIntArray(player, "m_iAmmo", i); local wep = GetWeaponInSlot(player, i); if (wep == null) continue; oldclip[i] = wep.Clip1(); chargemeter = GetPropFloat(wep, "m_flEffectBarRegenTime"); } RefillAmmo(player, true); for (local i = 0; i < SLOT_COUNT; i++) { maxammo[i] = GetPropIntArray(player, "m_iAmmo", i); SetPropIntArray(player, "m_iAmmo", oldammo[i], i); local wep = GetWeaponInSlot(player, i); if (chargemeter > 0) SetPropFloat(wep, "m_flEffectBarRegenTime", chargemeter); if (wep == null || oldclip[i] == null || oldclip[i] == -1) continue; wep.SetClip1(oldclip[i]); } return maxammo; } ::HasEffect <- function(ent, value) { return GetPropInt(ent, "m_fEffects") == value ? true : false } ::IsPlayerDead <- function(player) { return GetPropInt(player, "m_lifeState") != LIFE_ALIVE ? true : false } for (local i = 0; i < 10; i++) PrecacheSound(format("ambient/energy/spark%d.wav", i)); for (local i = 0; i < 10; i++) PrecacheSound(format("ambient/energy/zap%d.wav", i)); ::CreateSpark <- function(org, sound = true, mag = 8, trail = 2) { if (FindByName(null, "__spark") == null) ::__spark <- SpawnEntityFromTable("env_spark", { targetname = "__spark", spawnflags = 128, magnitude = mag, TrailLength = trail }) __spark.SetOrigin(org) EntFireByHandle(__spark, "SparkOnce", "", -1, null, null); // if (sound) EmitAmbientSoundOn(format("ambient/energy/zap%d.wav", RandomInt(1,9)), 10.0, 2, 1.0, __spark) if (sound) RandomInt(1, 2) == 1 ? __spark.EmitSound(format("ambient/energy/spark%d.wav", RandomInt(1,6))) : __spark.EmitSound(format("ambient/energy/zap%d.wav", RandomInt(1,9))); } ::DeadMedigunFix <- function() { local mediguns = [29, 35, 211, 411, 663, 796, 805, 885, 894, 903, 912, 961, 970, 998, 15008, 15010, 15025, 15039, 15050, 15078, 15097, 15121, 15122, 15123, 15145, 15146]; for (local droppedweapon; droppedweapon = FindByClassname(droppedweapon, "tf_dropped_weapon");) { foreach (m in mediguns) { if (GetItemIndex(droppedweapon) != m) continue; EntFireByHandle(droppedweapon, "SetBodyGroup", "1", -1, null, null); break; } } } ::InUpgradeZone <- function(player) { return GetPropBool(player, "m_Shared.m_bInUpgradeZone"); } ::InButton <- function(player, button) { return (GetPropInt(player, "m_nButtons") & button) } ::PressButton <- function(player, button) { SetPropInt(player, "m_afButtonForced") | button; SetPropInt(player, "m_nButtons") | button } // ::SetColorCorrection <- function(player = null, raw = "materials/colorcorrection/downpour.raw", soundscape = 34) // { // if (player == null) return; // local scope = player.GetScriptScope(); // local ccactive = false; // if (!(ccactive in scope)) scope.ccactive <- ccactive; // if (InSoundscapeIndex(player, soundscape) && !scope.ccactive) // { // local cc = FindByName(null, format("cc%d", player.entindex())); // // EntFireByHandle(__clientcommand, "Command", "r_screenoverlay colorcorrection/downpour", -1, player, player); // if (cc == null) // cc = SpawnEntityFromTable("color_correction", { // // origin = player.GetOrigin() + Vector(0, 0, 40) // targetname = format("cc%d", player.entindex()), // filename = raw, // spawnflags = 2, // maxweight = 0.4, // minfalloff = 0, // maxfalloff = 1000, // fadeInDuration = 3, // fadeOutDuration = 3, // StartDisabled = 1, // }) // SetPropFloat(cc, "m_flCurWeight", 0.4) // cc.SetOrigin(player.GetOrigin()); // EntFireByHandle(cc, "Enable", "", -1, null, null); // // cc.SetOrigin(player.GetOrigin() + Vector(0, 0, 1000)); // // EntFireByHandle(cc, "SetParent", "!activator", -1, player, player); // // printl(ccorg); // // function MoveToPlayer() // // { // // local ccorg = cc.GetOrigin(); // // local porg = player.GetOrigin(); // // // SetPropString(cc, "m_iszScriptThinkFunction", ""); return; // // // printl(ccorg.z); // // if (ccorg.z > porg.z) // // { // // cc.SetOrigin(ccorg - Vector(0, 0, 20)); // // return -1; // // } // // if (ccorg != porg) cc.SetOrigin(porg) // // } // // cc.ValidateScriptScope(); // // cc.GetScriptScope().MoveToPlayer <- MoveToPlayer; // // AddThinkToEnt(cc, "MoveToPlayer"); // } // else if (!InSoundscapeIndex(player, 34)) // { // local cc = FindByName(null, format("cc%d", player.entindex())); // if (cc == null) return; // // function MoveFromPlayer() // // { // // local ccorg = cc.GetOrigin(); // // local porg = player.GetOrigin(); // // // printl(ccorg.z); // // if (ccorg.z < porg.z + 1000) cc.SetOrigin(ccorg + Vector(0, 0, 20)); // // // if (ccorg != porg) cc.SetOrigin(porg) // // return -1; // // } // // cc.ValidateScriptScope(); // // cc.GetScriptScope().MoveFromPlayer <- MoveFromPlayer; // // AddThinkToEnt(cc, "MoveFromPlayer"); // DoEntFire(format("cc%d", player.entindex()), "Disable", "", 1, null, null); // // DoEntFire(format("cc%d", player.entindex()), "Kill", "", 1, null, null); // // EntFireByHandle(__clientcommand, "Command", "r_screenoverlay none", -1, player, player); // scope.ccactive = false; // } // } ::SetReserveAmmo <- function(wep, amount = 999, slot = -1, nounderflow = true) { //allow for just passing a single player variable to set primary + secondary reserve ammo to 999 if (wep != null && wep.IsPlayer() && slot == -1) { if (nounderflow && amount < 0) amount = 0; SetPropIntArray(wep, "m_iAmmo", amount, SLOT_PRIMARY); SetPropIntArray(wep, "m_iAmmo", amount, SLOT_SECONDARY); return; } local player; if (wep == null) return; if (wep.IsPlayer()) { player = wep; wep = wep.GetActiveWeapon() } else player = wep.GetOwner(); if (wep == null) return; if (slot == -1) slot = wep.GetSlot(); if (nounderflow && amount < 0) amount = 0; SetPropIntArray(player, "m_iAmmo", amount, slot+1); } ::GetReserveMetal <- function(player) { return GetPropIntArray(player, "m_iAmmo", 3); } ::SetReserveMetal <- function(player, amount, nounderflow = true) { SetPropIntArray(player, "m_iAmmo", amount, 3); if (nounderflow && GetReserveMetal(player) < 0) EntFireByHandle(player, "RunScriptCode", "SetPropIntArray(self, `m_iAmmo`, 0, 3)", -1, null, null); } ::GetAmmoInSlot <- function(player, slot) { return GetPropIntArray(player, "m_iAmmo", slot+1); } ::GetPrimaryAmmo <- function(player) { return GetPropIntArray(player, "m_iAmmo", 0); } ::GetSecondaryAmmo <- function(player) { return GetPropIntArray(player, "m_iAmmo", 1); } ::AddAttributeToLoadout <- function (player, attribute, value, duration = -1) { for (local i = 0; i < SLOT_COUNT; i++) { local wep = GetWeaponInSlot(player, i); if (wep == null) continue; wep.AddAttribute(attribute, value, duration); wep.ReapplyProvision(); } } ::SetEffect <- function(ent, value) { SetPropInt(ent, "m_fEffects", value); } ::ObjSniperNoAltFire <- function() { SetPropInt(self, "m_nButtons", GetPropInt(self, "m_nButtons") & ~IN_ATTACK2) } //don't do this on players, only used to not block bot primary fire. local carrierkilled = false; ::KillRomeCarrierAddon <- function() { if (carrierkilled) return; for (local props; props = FindByClassname(props, "prop_dynamic");) { if (!endswith(props.GetModelName(), "twcarrier_addon.mdl")) continue; props.Kill(); carrierkilled = true; break; } } ::StunPlayer <- function(player, duration = 5, type = 1, delay = 0, speedreduce = 0.5) { //spawn one trigger_stun and leave it around for a few seconds for iterating through multiple players. No point spawning a new trigger_stun every time. local stunent = FindByName(null, "__utilstun") != null ? true : false; if (!stunent) { ::__utilstun <- SpawnEntityFromTable("trigger_stun", { targetname = "__utilstun", stun_type = type, stun_duration = duration, move_speed_reduction = speedreduce, trigger_delay = delay, StartDisabled = 0, spawnflags = 1, "OnStunPlayer#1": "!self,Kill,,2,-1" }); __utilstun.SetSolid(2) __utilstun.SetSize(Vector(-1, -1, -1), Vector()) } EntFireByHandle(__utilstun, "EndTouch", "", -1, player, player); } ::ShowHudHint <- function(player, text = "PLACEHOLDER", duration = 5) { local hudhint = FindByName(null, "__hudhint") != null ? true : false; local flags = (player == null) ? 1 : 0; if (!hudhint) ::__hudhint <- SpawnEntityFromTable("env_hudhint", { targetname = "__hudhint", spawnflags = flags, message = text }) // SetPropString(__hudhint, "m_iszMessage", text); __hudhint.KeyValueFromString("message", text); EntFireByHandle(__hudhint, "ShowHudHint", "", -1, player, player); EntFireByHandle(__hudhint, "HideHudHint", "", duration, player, player); } //print colored text within hammer ::ClientPrintSafe <- function(player, text = "", escape = "^") { //replace ^ with \x07 at run-time //just use the normal print function if there's no escape character if (!startswith(text, escape)) { ClientPrint(player, 3, text); return; } //split text at the escape character local splittext = split(text, escape); //remove 0-length strings for (local i = splittext.len() - 1; i >= 0; i--) if (splittext[i].len() < 1) splittext.remove(i); //format into new string local formatted = ""; foreach (i, t in splittext) formatted += format("\x07%s", t); //print formatted string ClientPrint(player, 3, formatted); } //TODO: set m_iObjectType on toolboxes when given back //we just don't strip toolboxes for now const TF_CLASS_HEAVYWEAPONS = 6; const TF_CLASS_ENGINEER = 9; ::GiveWeapon <- function(player, classname, itemid, model = null, forceslot = -1, vm = null, stripall = false, refill = false) { if (classname == "tf_wearable" || classname == "tf_wearable_demoshield" || classname == "tf_wearable_razorback") { local wearable = CreateByClassname(classname); SetPropInt(wearable, "m_nModelIndex", PrecacheModel(model)); SetPropInt(wearable, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", itemid); SetPropBool(wearable, "m_bValidatedAttachedEntity", true); SetPropBool(wearable, "m_AttributeManager.m_Item.m_bInitialized", true); _SetOwner(wearable, player); DispatchSpawn(wearable); EntFireByHandle(wearable, "SetParent", "!activator", -1, player, player); SetPropInt(wearable, "m_fEffects", 129); //EF_BONEMERGE = EF_BONEMERGE_FASTCULL //this doesn't work, demo shields must be faked in vscript if (classname == "tf_wearable_demoshield") SetWeaponInSlot(player, wearable, SLOT_SECONDARY); return wearable; // player.Weapon_Equip(wearable); } else { if (refill) { local h = player.GetHealth(); player.Regenerate(true); player.SetHealth(h); } if (itemid == ID_BUILDER) return; local weapon = CreateByClassname(classname); function SetFakeWeapons() { switch(itemid) { case ID_WINGER: if (player.GetPlayerClass() != TF_CLASS_SCOUT) { itemid = ID_ENGINEERS_PISTOL; vm = "models/workshop/weapons/c_models/c_winger_pistol/c_winger_pistol.mdl" weapon.AddAttribute("damage bonus", 1.15, -1); weapon.AddAttribute("clip size penalty", 0.4, -1); weapon.AddAttribute("jump height from weapon", 1.25, -1); weapon.ReapplyProvision(); } break; case ID_PRETTY_BOYS_POCKET_PISTOL: if (player.GetPlayerClass() != TF_CLASS_SCOUT) { itemid = ID_ENGINEERS_PISTOL; vm = "models/workshop/weapons/c_models/c_pep_pistol/c_pep_pistol.mdl" weapon.AddAttribute("provide on active", 1 -1); weapon.AddAttribute("heal on hit for rapidfire", 3, -1); weapon.AddAttribute("fire rate bonus", 0.85, -1); weapon.AddAttribute("clip size penalty", 0.75, -1); weapon.ReapplyProvision(); } break; case ID_RESERVE_SHOOTER: local rsmodel = "models/workshop/weapons/c_models/c_reserve_shooter/c_reserve_shooter.mdl" switch (player.GetPlayerClass()) { case TF_CLASS_HEAVYWEAPONS: itemid = ID_HEAVYS_SHOTGUN; vm = rsmodel weapon.AddAttribute("clip size penalty", 0.66, -1); weapon.AddAttribute("mod mini-crit airborne", 1, -1); weapon.AddAttribute("single wep deploy time increased", 0.8, -1); weapon.AddAttribute("special taunt", 1, -1); weapon.ReapplyProvision(); break; case TF_CLASS_ENGINEER: itemid = ID_ENGINEERS_SHOTGUN; vm = rsmodel weapon.AddAttribute("clip size penalty", 0.66, -1); weapon.AddAttribute("mod mini-crit airborne", 1, -1); weapon.AddAttribute("single wep deploy time increased", 0.8, -1); weapon.AddAttribute("special taunt", 1, -1); weapon.ReapplyProvision(); break; } break; case ID_FAMILY_BUSINESS: local fbmodel = "models/workshop/weapons/c_models/c_russian_riot/c_russian_riot.mdl" switch (player.GetPlayerClass()) { case TF_CLASS_SOLDIER: itemid = ID_SOLDIERS_SHOTGUN; vm = fbmodel weapon.AddAttribute("damage penalty", 0.85, -1); weapon.AddAttribute("clip size bonus", 1.33, -1); weapon.AddAttribute("fire rate bonus", 0.85, -1); weapon.ReapplyProvision(); break; case TF_CLASS_PYRO: itemid = ID_PYROS_SHOTGUN; vm = fbmodel weapon.AddAttribute("damage penalty", 0.85, -1); weapon.AddAttribute("clip size bonus", 1.33, -1); weapon.AddAttribute("fire rate bonus", 0.85, -1); weapon.ReapplyProvision(); break; case TF_CLASS_ENGINEER: itemid = ID_ENGINEERS_SHOTGUN; vm = fbmodel weapon.AddAttribute("damage penalty", 0.85, -1); weapon.AddAttribute("clip size bonus", 1.33, -1); weapon.AddAttribute("fire rate bonus", 0.85, -1); weapon.ReapplyProvision(); break; } break; } } // if (itemid == ID_WINGER || itemid == ID_PRETTY_BOYS_POCKET_PISTOL || itemid == ID_RESERVE_SHOOTER || itemid == ID_FAMILY_BUSINESS) SetFakeWeapons(); SetPropInt(weapon, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", itemid); SetPropBool(weapon, "m_AttributeManager.m_Item.m_bInitialized", true); SetPropBool(weapon, "m_bValidatedAttachedEntity", true); if (itemid == ID_PHLOGISTINATOR || itemid == ID_HITMANS_HEATMAKER || classname == "tf_weapon_buff_item") player.SetRageMeter(0.0); if (itemid == ID_BABY_FACES_BLASTER || itemid == ID_SODA_POPPER) player.SetScoutHypeMeter(0.0); if (startswith(classname, "tf_weapon_lunchbox") || startswith(classname, "tf_weapon_jar") || classname == "tf_weapon_cleaver") SetPropInt(player, "m.Shared.m_flEnergyDrinkMeter", 0.0); weapon.SetTeam(player.GetTeam()); DispatchSpawn(weapon); local wepslot = weapon.GetSlot(); player.Weapon_Equip(weapon); // printl("vm: "+GetPropInt(weapon, "m_iViewModelIndex")) // printl("wm: "+GetPropInt(weapon, "m_iWorldModelIndex")) // printl("test: "+PrecacheModel("models/workshop/weapons/c_models/c_russian_riot/c_russian_riot.mdl")) // printl(weapon.GetModelName()) // player.Weapon_Switch(weapon); if (vm != null) { local vmindex = GetModelIndex(vm) weapon.SetModelSimple(vm); weapon.SetCustomViewModelModelIndex(vmindex); SetPropInt(weapon, "m_iViewModelIndex", vmindex); local scope = player.GetScriptScope(); if ("viewmodel" in scope && scope.viewmodel.IsValid()) scope.viewmodel.Kill(); local viewmodel = CreateByClassname("tf_wearable_vm"); SetPropInt(viewmodel, "m_nModelIndex", armindex[player.GetPlayerClass() - 1]); SetPropEntity(weapon, "m_hExtraWearableViewModel", viewmodel); viewmodel.SetTeam(player.GetTeam()); DispatchSpawn(viewmodel); player.EquipWearableViewModel(viewmodel); scope.viewmodel <- viewmodel; } if (stripall) StripEverythingExcept(player, weapon.GetSlot(), false); return weapon; } } ::SetEntityColor <- function(entity, r, g, b, a) { local color = (r) | (g << 8) | (b << 16) | (a << 24); SetPropInt(entity, "m_clrRender", color); } ::GetEntityColor <- function(entity) { local color = GetPropInt(entity, "m_clrRender"); local clr = {} clr.r <- color & 0xFF; clr.g <- (color >> 8) & 0xFF; clr.b <- (color >> 16) & 0xFF; clr.a <- (color >> 24) & 0xFF; return clr; } const EFL_NO_THINK_FUNCTION = 4194304 const SOLID_NONE = 0 //this still technically transmits the entity to every player, but only renders to one //TransmitEntityToPlayer below actually transmits the entity to only one person ::ShowModelToPlayer <- function(player, model = ["models/player/heavy.mdl", 0], pos = Vector(), ang = QAngle(), duration = 9999.0) { PrecacheModel(model[0]) local proxy_entity = CreateByClassname("obj_teleporter"); // not using SpawnEntityFromTable as that creates spawning noises proxy_entity.SetAbsOrigin(pos); proxy_entity.SetAbsAngles(ang); DispatchSpawn(proxy_entity); proxy_entity.SetModel(model[0]); proxy_entity.SetSkin(model[1]); proxy_entity.AddEFlags(EFL_NO_THINK_FUNCTION); // EFL_NO_THINK_FUNCTION prevents the entity from disappearing proxy_entity.SetSolid(SOLID_NONE); SetPropBool(proxy_entity, "m_bPlacing", true); SetPropInt(proxy_entity, "m_fObjectFlags", 2); // sets "attachment" flag, prevents entity being snapped to player feet // m_hBuilder is the player who the entity will be networked to only SetPropEntity(proxy_entity, "m_hBuilder", player); EntFireByHandle(proxy_entity, "Kill", "", duration, player, player); return proxy_entity; } //abuses PVS caching quirk, trick by ficool. //Isolate player in a room outside of everyone else's PVS and this entity will be transmitted to that player only until they disconnect. ::TransmitEntityToPlayer <- function(player, entity = ["",{}], org = Vector()) { local scope = player.GetScriptScope(); local transmittedents = [] if (!(transmittedents in scope)) scope.transmittedents <- transmittedents; local ent = CreateByClassname(entity[0]); printl(org) SetPropInt(ent, "m_nTransmitStateOwnedCounter", 1); foreach (k,v in entity[1]) ent.KeyValueFromString(k, v) DispatchSpawn(ent); ent.SetOrigin(org); local o = split(org.ToKVString(), " "); local s = split(player.GetOrigin().ToKVString(), " "); foreach(i, a in o) o[i] = a.tointeger(); foreach(i, a in s) s[i] = a.tointeger(); EntFireByHandle(player, "RunScriptCode", format("self.SetOrigin(Vector(%d, %d, %d));", o[0], o[1], o[2]), -1, null, null); EntFireByHandle(player, "RunScriptCode", format("self.SetOrigin(Vector(%d, %d, %d));", s[0], s[1], s[2]), 2, null, null); return ent; } ::GetItemIndex <- function(item) { return GetPropInt(item, "m_AttributeManager.m_Item.m_iItemDefinitionIndex") } ::SetItemIndex <- function(item, index) { SetPropInt(item, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", index)} ::GetViewmodelEntity <- function(player) { return GetPropEntity(player, "m_hViewModel"); } ::HasItem <- function(player, item, index) { local t = false; for (local child = player.FirstMoveChild(); child != null; child = child.NextMovePeer()) { if (child.GetClassname() == item && GetItemIndex(child) == index) { t = true; break; } } return t; } //TODO: expand this for other cases of items stretching //hardcoded to vaccinator right now ::NoStretching <- function(player) { for (local child = player.FirstMoveChild(); child != null; child = child.NextMovePeer()) if (player.GetPlayerClass() == TF_CLASS_MEDIC && child.GetClassname() == "tf_weapon_medigun" && GetItemIndex(child) == ID_VACCINATOR) GetPropEntity(child, "m_hExtraWearable").Kill(); } ::GetPlayerName <- function(player) { return GetPropString(player, "m_szNetname") } ::SetPlayerName <- function(player, name) { SetPropString(player, "m_szNetname", name) } ::SetTargetname <- function (ent, name) { SetPropString(ent, "m_iName", name) } ::GetPlayerSteamID <- function(player) { return GetPropString(player, "m_szNetworkIDString") } ::GetHammerID <- function(ent) { return GetPropInt(ent, "m_iHammerID") } ::GetSpawnFlags <- function(ent) { return GetPropInt(self, "m_spawnflags") } //we can't read a list of bot tags to iterate through, so we have to do this ugly mess of hijacking a string we DO have access to (the bot name) //then we can just split the string and send to those values to script functions. //CONDS MUST GO FIRST ::AddBehaviorFromName <- function(bot) { if (!bot.HasBotTag("addcond")) return; local name = GetPlayerName(bot); // local splitchars = ["^", "|"] local splitchars = ["^"] foreach (char in splitchars) { //no separator found if (split(name, char).len() < 2) continue; local splitname = split(name, char); foreach (c in splitname) { //always skip the first element if (c == splitname[0]) continue; //ADDCOND if (char != "^") continue; bot.AddCond(c.tointeger()); } } //conds must come first so the name is set correctly local finalname = split(name, "^"); SetPlayerName(bot, splitname[0]) } ::ChangePlayerTeamMvM <- function(player, teamnum = 3) { SetPropBool(FindByClassname(null, "tf_gamerules"), "m_bPlayingMannVsMachine", false); player.ForceChangeTeam(teamnum, false); SetPropBool(FindByClassname(null, "tf_gamerules"), "m_bPlayingMannVsMachine", true); } ::ForceChangeClass <- function(player, classindex = 1) { player.SetPlayerClass(classindex); SetPropInt(player, "m_Shared.m_iDesiredPlayerClass", classindex); player.ForceRegenerateAndRespawn(); } ::InSoundscapeIndex <- function(player, index) { // printf("Soundscape Index: %d \t", GetPropInt(player, "m_Local.m_audio.soundscapeIndex")) //uncomment to print soundscape indexes to console, enable "developer 1" to match with the name of the soundscape return GetPropInt(player, "m_Local.m_audio.soundscapeIndex") == index ? true : false } ::GetEntityOutputs <- function(ent, output, printout = false) { local t = {}, o = []; for (local i = 0; i < GetNumElements(ent, output); i++) { GetOutputTable(ent, output , t, i) o.append(t) } if (printout) { foreach(i, a in o) { local table = "{ " foreach(k, v in a) table += (k+" : "+v+",\n") printl(table+" }") } } return o; } ::RemoveOutputAll <- function(ent, output) { local outputs = []; for (local i = GetNumElements(ent, output); i >= 0; i--) { local t = {}; GetOutputTable(ent, output, t, i); outputs.append(t); } foreach (o in outputs) foreach(_ in o) RemoveOutput(ent, output, o.target, o.input, o.parameter); } ::SetConvar <- function(convar, value, hideChatMessage = true) { //save original values to restore later if (!(convar in ConVars)) ConVars[convar] <- Convars.GetStr(convar); function SetCvar() { Convars.SetValue(convar, value) } if (Convars.GetStr(convar) != value) SetCvar() } //evil wizardry ::ChangeLevel <- function() { local intermission = SpawnEntityFromTable("point_intermission", {}); Convars.SetValue("mp_chattime", 0); EntFireByHandle(intermission, "Activate", "", -1, null, null); } //edict overflow prevention. CONST.MAX_EDICTS <- Constants.Server.MAX_EDICTS; local EntWarning = function(text) { ClientPrint(null, 3, format("\x07FF0000 NEARING EDICT LIMIT!!! %s" text)); } ::EntShredder <- function(printout = false, aggressive = true) { local numents = 0; for (local i = MAX_CLIENTS; i <= MAX_EDICTS; i++) { if (EntIndexToHScript(i) == null) continue; numents++; local entity = EntIndexToHScript(i); if (numents < (maxedicts - MAX_CLIENTS) * SHREDDER_THRESHOLD) continue; //delete all info_particle_systems flagged as weather //delete all ropes EntWarning("REMOVING DECORATIONS!"); if (endswith(entity.GetClassname(), "_rope") || (entity.GetClassname() == "info_particle_system" && GetPropBool(entity, "m_bWeatherEffect"))); entity.Kill(); //delete all physics props and dynamic lights EntWarning("REMOVING DECORATIONS!"); if (startswith(entity.GetClassname(), "prop_physics") || entity.GetClassname() == "light_dynamic"); entity.Kill(); //NUCELAR OPTIONS BELOW if (!aggressive) continue; //delete all cosmetics EntWarning("REMOVING COSMETICS!"); if (entity.GetClassname() == "tf_wearable") entity.Kill(); EntWarning("REMOVING INSTANCED_SCRIPTED_SCENES!") if (entity.GetClassname() == "instanced_scripted_scene") entity.Kill(); } if (printout) printf("Non-player Entities: %d\n", numents); return SHREDDER_INTERVAL; } if (SHREDDER_ENABLE) { local dummy = CreateByClassname("info_target"); DispatchSpawn(dummy); dummy.ValidateScriptScope(); dummy.GetScriptScope().EntShredder <- EntShredder; AddThinkToEnt(dummy, "EntShredder"); }