//vscript by stardustspy //do not use without my credit PLEASE!!!!!!!!! //requires rafmod //TODO // Fold things into root so the script can access them anywhere ::ROOT <- getroottable() ::MAX_CLIENTS <- MaxClients().tointeger() // get all players that can spawn in a server if (!("ConstantNamingConvention" in ROOT)) foreach(a, b in Constants) foreach(k, v in b) ROOT[k] <- v != null ? v : 0 foreach(k, v in ::NetProps.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::NetProps[k].bindenv(::NetProps) foreach(k, v in ::Entities.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::Entities[k].bindenv(::Entities) foreach(k, v in ::EntityOutputs.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::EntityOutputs[k].bindenv(::EntityOutputs) foreach(k, v in ::NavMesh.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::NavMesh[k].bindenv(::NavMesh) //team ::TEAM_SPECTATOR <- 1 ::TEAM_RED <- 2 ::TEAM_BLU <- 3 ::TF_NAV_SPAWN_ROOM_RED <- 2 ::TF_NAV_SPAWN_ROOM_BLUE <- 4 ::TF_BOT_FAKE_CLIENT <- 1337 ::MAX_COUNT_WEAPON_EQUPPED <- 8 ::PI <- 3.14159265359 ::gamerules <- Entities.FindByClassname(null, "tf_gamerules") ::player_manager <- Entities.FindByClassname(null, "tf_player_manager") ::WORLD_SPAWN <- Entities.FindByClassname(null, "worldspawn") ::MAX_COUNT_PLAYERS <- MaxClients().tointeger() ::MASK_VISIBLE_AND_NPCS <- 33579137 //input ::PRIMARY_FIRE <- Constants.FButtons.IN_ATTACK ::SECONDARY_FIRE <- Constants.FButtons.IN_ATTACK2 ::SPECIAL_FIRE <- Constants.FButtons.IN_ATTACK3 ::RELOAD <- Constants.FButtons.IN_RELOAD ::JUMP <- Constants.FButtons.IN_JUMP //damage types ::TF_DMG_CRITICAL <- 1048576 //special dmg type ::TF_DMG_CUSTOM_HEADSHOT <- 1 ::TF_DMG_CUSTOM_BACKSTAB <- 2 ///mask ::SOLID_NONE <- 0 ::CONTENTS_SOLID <- 1 ::CONTENTS_WINDOW <- 2 ::CONTENTS_GRATE <- 8 ::CONTENTS_MOVEABLE <- 16384 ::CONTENTS_PLAYERCLIP <- 65536 ::MASK_NPCWORLDSTATIC <- 131083 ::MASK_PLAYERSOLID <- 33636363 ::MASK_NPCSOLID <- 33701899 ::MASK_SOLID <- 33570827 ::MASK_SHOT <- 1174421507 ::MASK_SHOT_HULL <- 100679691 //emitsoundex ::SND_NOFLAGS <- 0 ::SND_CHANGE_VOL <- 1 ::SND_CHANGE_PITCH <- 2 ::SND_STOP <- 4 ::SND_SPAWNING <- 8 ::SND_DELAY <- 16 ::SND_STOP_LOOPING <- 32 ::SND_SPEAKER <- 64 ::SND_SHOULDPAUSE <- 128 ::SND_IGNORE_PHONEMES <- 256 ::SND_IGNORE_NAME <- 512 ::SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL <- 1024 //classes ::Scout <- 1 ::Soldier <- 3 ::Pyro <- 7 ::Demoman <- 4 ::Heavy <- 6 ::Engineer <- 9 ::Medic <- 5 ::Sniper <- 2 ::Spy <- 8 const STRING_NETPROP_ITEMDEF = "m_AttributeManager.m_Item.m_iItemDefinitionIndex" const SLOT_COUNT = 7 const EFL_USER = 1048576 // EFL_IS_BEING_LIFTED_BY_BARNACLE const EFL_USER2 = 1073741824 //EFL_NO_PHYSCANNON_INTERACTION const SINGLE_TICK = 0.015 const DMG_NO_BULLET_FALLOFF = 2097152 PrecacheSound("ambient/energy/weld1.wav") PrecacheSound("ambient/energy/weld2.wav") PrecacheSound("player/recharged.wav") PrecacheSound("npc/assassin/ball_zap1.wav") PrecacheSound("weapons/drg_wrench_teleport.wav") PrecacheSound("misc/halloween/spell_lightning_ball_cast.wav") PrecacheSound("misc/halloween/spell_meteor_impact.wav") PrecacheSound("misc/halloween/spell_spawn_boss.wav") PrecacheSound("player/recharged.wav") PrecacheSound("ui/mm_medal_bronze.wav") PrecacheSound("ui/mm_medal_click_rare.wav") PrecacheSound("ui/mm_medal_click.wav") PrecacheSound("ui/mm_medal_click_rank_unknown.wav") PrecacheSound("ui/mm_medal_none.wav") PrecacheSound("weapons/stickybomblauncher_charge_up.wav") //a lot of thse local boons = [ //scout //35% faster reload { player_class = Scout, tier = 1, name = "Reload Training" text = "+35% faster reload rate" attrib_info = [ {attrib = "faster reload rate", value = 0.65, source = -1, default_value = 1, func_associated = null} ] func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); //attrib, attrib_value, player, slot, remove_attrib, apply_immediately PerksScript.ParseAttributeArray("faster reload rate", 0.65, player, -1, false, true, 1) } }, //30% jump height { player_class = Scout, tier = 1, name = "Jumping Jeremy" text = "+30% increased jump height" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("increased jump height", 1.3, player, -1, false, true, 1) } }, //speed { player_class = Scout, tier = 1, name = "Robot Hunter" text = "+5 seconds speed boost on kill \n +10% move speed bonus." func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("speed_boost_on_kill", 5, player, -1, false, true, 0) PerksScript.ParseAttributeArray("move speed bonus", 1.1, player, -1, false, true, 1) } }, //healing { player_class = Scout, tier = 1, name = "Life Support" text = "+4 Health regen, +25% increased healing recieved." func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("healing received bonus", 1.25, player, -1, false, true, 1) PerksScript.ParseAttributeArray("health regen", 4, player, -1, false, true, 0) } }, //Extra Ammo { player_class = Scout, tier = 1, name = "Battle Belt" text = "+50% increased Secondary and Primary Ammo. \n +2 consumables." func = function(player) { player.ValidateScriptScope() local scope = player.GetScriptScope() local primary = scope.GetItemInSlot(player, 0) local secondary = scope.GetItemInSlot(player, 1) PerksScript.ParseAttributeArray("maxammo primary increased", 1.5, player, -1, false, true, 1) PerksScript.ParseAttributeArray("maxammo secondary increased", 1.5, player, -1, false, true, 1) PerksScript.ParseAttributeArray("maxammo grenades1 increased", 3, player, -1, false, true, 1) if (secondary.GetClassname() != "tf_weapon_lunchbox_drink") { NetProps.SetPropIntArray(player, "m_iAmmo", 3, 5); } scope.consumable_recharge <- 0 scope.recharge_cleaver_table <- [] ::AmmoMaxCleaverDrink <- function() { local primary = scope.GetItemInSlot(player, 0) local secondary = scope.GetItemInSlot(player, 1) local primary_ammo_cur = NetProps.GetPropIntArray(self, "m_iAmmo", 5) // cleaver local recharge_meter = NetProps.GetPropFloat(secondary, "m_flEffectBarRegenTime") NetProps.SetPropFloat(secondary, "m_flEffectBarRegenTime", 0) local cur_time = Time() local wep_rechargables = { "tf_weapon_cleaver" : 1, "tf_weapon_jar_milk" : 1 } // Debugging: Print current time and ammo printl("Current Time: " + cur_time); printl("Current Cleaver Ammo: " + primary_ammo_cur); // Check if the secondary weapon is rechargeable and ammo is below 3 if (secondary.GetClassname() in wep_rechargables && primary_ammo_cur < 3 && secondary.GetClassname() != "tf_weapon_lunchbox_drink") { // Track recharge times for all cleavers, including the first one if (primary_ammo_cur < 3 && recharge_cleaver_table.len() < (3 - primary_ammo_cur)) { // Store the time the cleaver should recharge based on recharge_meter recharge_cleaver_table.append(recharge_meter); // Adding cur_time for proper timing } } // Check current time against stored cleaver recharge times for (local i = recharge_cleaver_table.len() - 1; i >= 0; i--) { printl("Recharge Time: " + recharge_cleaver_table[i]); if (recharge_cleaver_table[i] <= cur_time) { // Increment cleaver ammo if the current time is past the recharge time if (primary_ammo_cur < 3) { // Update the ammo count using NetProps NetProps.SetPropIntArray(self, "m_iAmmo", primary_ammo_cur + 1, 5); printl("Added Cleaver: New Ammo Count: " + primary_ammo_cur); } // Remove the used time from the table recharge_cleaver_table.remove(i); self.EmitSound("player/recharged.wav") } } printl("Final Ammo: " + primary_ammo_cur); } scope.ThinkTable_Player.AmmoMaxCleaverDrink <- AmmoMaxCleaverDrink } }, //Dodge damage bonus { player_class = Scout, tier = 1, name = "Draft Dodger" text = "+25% Damage bonus after \n not recieving damage for a while" func = function(player) { ::DodgerDamageBonus <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.DodgerDamageBonus <- DodgerDamageBonus } }, //damage bonus + fire rate bonus { player_class = Scout, tier = 2, name = "Quick Strong Strikes" text = "+25% fire rate bonus +15% damage bonus" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("damage bonus", 1.15, player, -1, false, true, 1) PerksScript.ParseAttributeArray("fire rate bonus", 0.75, player, -1, false, true, 1) } }, //bigger clip size { player_class = Scout, tier = 2, name = "Big Barrel Chambers" text = "+50% Clip size bonus" func = function(player) { player.ValidateScriptScope() local scope = player.GetScriptScope() PerksScript.ParseAttributeArray("clip size bonus", 1.5, player, 0, false, true, 1) PerksScript.ParseAttributeArray("clip size bonus", 1.5, player, 1, false, true, 1) } }, //bonus vs. cond'd enemies { player_class = Scout, tier = 2, name = "Big Deal" text = "+Secondaries and Melees deal x2 damage \n vs. enemies under effects such as banners, crits, jarate, bleed" func = function(player) { ::DamageBonusSecondaryMelee <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.DamageBonusSecondaryMelee <- DamageBonusSecondaryMelee } }, //medkit { player_class = Scout, tier = 2, name = "Medkit Menace" text = "+On Kill: Drop a small health kit." func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("drop health pack on kill", 1, player, -1, false, true, 0) } }, //accuracy { player_class = Scout, tier = 2, name = "Tight Flanks" text = "+45% more accuracy, +Minicrit from Behind." func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("closerange backattack minicrits", 1, player, -1, false, true, 0) PerksScript.ParseAttributeArray("weapon spread bonus", 0.55, player, -1, false, true, 1) } }, //trade off { player_class = Scout, tier = 2, name = "Titanium Bullets" text = "+75% more bullets, but you fire 25% slower." func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("fire rate penalty", 1.25, player, -1, false, true, 1) PerksScript.ParseAttributeArray("bullets per shot bonus", 1.75, player, -1, false, true, 1) } }, //soda popper mechanics { player_class = Scout, tier = 3, text = "+Gain Soda Popper's `Hype` meter" name = "Pop Off" func = function(player) { ::PopperMeter <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.PopperMeter <- PopperMeter } }, //baby face mechanics { player_class = Scout, tier = 3, name = "Running Mann" text = "+Gain Baby Face Blaster's `Boost` meter." func = function(player) { ::BFBMeter <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.BFBMeter <- BFBMeter } }, //refill clip on kill { player_class = Scout, tier = 4, name = "Kill and Conqueror" text = "+On Kill: Refill all clips and secondary meters." func = function(player) { ::RefillOnKill <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.RefillOnKill <- RefillOnKill } }, //poison double jump { player_class = Scout, tier = 4, name = "Posion Double Jump" text = "+When double jumping, create an area \n around you that poisons enemies. Stacks up to 3 times per enemy." func = function(player) { ::PoisonDoubleJump <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.PoisonDoubleJump <- PoisonDoubleJump } }, //more hit = better hit { player_class = Scout, tier = 4, name = "Domino Effect" text = "+Consecutive hits with Primary and Secondary \n grant +5% damage bonus per hit. Max 50% damage bonus." func = function(player) { ::ConsecutiveHits <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.ConsecutiveHits <- ConsecutiveHits } }, //medikit crit booster { player_class = Scout, tier = 4, name = "Adrenaline Junkie" text = "+Collecting health packs grants Crits at full health. \n Duration depends on pack type." func = function(player) { ::HealthPackCritboost <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.HealthPackCritboost <- HealthPackCritboost } }, //snowball { player_class = Scout, tier = 4, name = "Boston Butcher" text = "+On every 3rd Kill: Grant +20% fire rate and reload rate bonus. \n Stacks 4 times." func = function(player) { ::KillstreakBonusEffects <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.KillstreakBonusEffects <- KillstreakBonusEffects } }, //snowball { player_class = Scout, tier = 4, name = "Like Father, Like Son..." text = "+After not taking damage or attacking for a while, \n become cloaked." func = function(player) { ::CloakScout <- function() { printl(self) } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.CloakScout <- CloakScout } }, //SOLDIER //rocket spec //rocket jump dmg bonus { player_class = Soldier, tier = 1, name = "Air Bombardment" text = "+20% damage bonus while rocket jumping" func = function(player) { ::RocketJumpDMGBonus <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.RocketJumpDMGBonus <- RocketJumpDMGBonus } }, //black box { player_class = Soldier, tier = 1, name = "Vampire Doe" text = "+On hit: Gain +15 HP" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("heal on hit for slowfire", 15, player, -1, false, true, 0) } }, //fast projectiles { player_class = Soldier, tier = 1, name = "Speeding Projectiles" text = "+40% faster projectile speed" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("Projectile speed increased", 1.4, player, -1, false, true, 1) } }, //rocket jump dmg bonus { player_class = Soldier, tier = 1, name = "Quick Switch" text = "+50% faster switch speed" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("deploy time decreased", 0.5, player, -1, false, true, 1) } }, //rocket jump dmg bonus { player_class = Soldier, tier = 1, name = "Secondary Options" text = "+25% bullets per shot, +25% Rage duration" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("increase buff duration", 1.25, player, -1, false, true, 1) PerksScript.ParseAttributeArray("bullets per shot bonus", 1.25, player, -1, false, true, 1) } }, //damage health scaling { player_class = Soldier, tier = 1, name = "Brass Bringdown" text = "+Increased damage with lower health, up to +30%" func = function(player) { ::DamageLowerHealth <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.DamageLowerHealth <- DamageLowerHealth } }, //rocket spec { player_class = Soldier, tier = 2, name = "Enhanced Soldier" text = "+Rocket Specialist +10% faster firing speed +10% faster reload" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("faster reload rate", 0.9, player, -1, false, true, 1) PerksScript.ParseAttributeArray("fire rate bonus", 0.9, player, -1, false, true, 1) PerksScript.ParseAttributeArray("rocket specialist", 1, player, -1, false, true, 0) } }, //rocket jump dmg reduction { player_class = Soldier, tier = 2, name = "Iron Boots" text = "+65% reduced damage when rocket jumping. +No fall damage." func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("cancel falling damage", 1, player, -1, false, true, 0) PerksScript.ParseAttributeArray("rocket jump damage reduction", 0.45, player, -1, false, true, 1) } }, //rocket jump dmg reduction { player_class = Soldier, tier = 2, name = "Spammer Mode" text = "+3 clip size and +50% fire rate bonus, \n but you have -40% max primary ammo" func = function(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); PerksScript.ParseAttributeArray("fire rate bonus", 0.5, player, -1, false, true, 1) PerksScript.ParseAttributeArray("maxammo primary reduced", 0.6, player, -1, false, true, 1) PerksScript.ParseAttributeArray("clip size upgrade atomic", 3, player, 0, false, true, 0) } }, //defiant { player_class = Soldier, tier = 3, name = "Danger Defiance" text = "+50% damage bonus for every currently active negative effect." func = function(player) { ::DefiantPassive <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.DefiantPassive <- DefiantPassive } }, //poison rocket { player_class = Soldier, tier = 4, name = "Deadly Aroma" text = "+Rockets deal constant damage to targets near them." func = function(player) { ::RocketDamageAura <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.RocketDamageAura <- RocketDamageAura } }, //rocket homing { player_class = Soldier, tier = 4, name = "Homing Rockets" text = "+Rockets now have homing." func = function(player) { ::HomingRocket <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.HomingRocket <- HomingRocket } }, //rocket split small rockets { player_class = Soldier, tier = 4, name = "Firecracker Launcher" text = "+Rockets split into smaller rockets on \n impact for 20% of the initial rockets damage." func = function(player) { ::SplittingRocket <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.SplittingRocket <- SplittingRocket } }, //PYRO //DEMO //black hole charge { player_class = Demoman, tier = 4, name = "Black Hole Charge" text = "+While charging, enemies are pulled toward the user. \n Invulnerable during and sometime after charging." func = function(player) { ::BlackHoleCharge <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.BlackHoleCharge <- BlackHoleCharge } }, //rocket split small rockets { player_class = Demoman, tier = 4, name = "Splitting Launcher" text = "+Fire 2 additional grenades" func = function(player) { ::PillShotgun <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.PillShotgun <- PillShotgun } }, //heavy //sandvich poison { player_class = Heavy, tier = 3, name = "Corrosive Sandvich" text = "+Sandvich poisons nearby robots when dropped. \n While dealing damage, the user heals for 25% of the damage." func = function(player) { ::VeryEvilSandvich <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.VeryEvilSandvich <- VeryEvilSandvich } }, //engineer //summoner { player_class = Engineer, tier = 4, name = "Reconstruction" text = "+Press Special Attack key (Mouse3) to \n summon the last 4 bots you destroyed." func = function(player) { player.ValidateScriptScope() local scope = player.GetScriptScope() function CreateBot(player) { //when this function is called, set everything up here player.ValidateScriptScope() local scope = player.GetScriptScope() ::MobberThink <- function() { local origin = self.GetOrigin() printl(self) local vision_range = self.GetMaxVisionRangeOverride() for (local threat; threat = FindByClassnameWithin(threat, "player", origin, vision_range);) { if (threat.GetTeam() != self.GetTeam() && threat.GetTeam() != TEAM_SPECTATOR && bot_threats.find(threat) != null) { bot_threats.append(threat) } } PrintTable(bot_threats) } scope.bot_threats <- [] scope.DoPrintTable <- DoPrintTable scope.PrintTable <- PrintTable scope.ThinkTable_Bot.MobberThink <- MobberThink } ::SummonerEngineer <- function() { local buttons = NetProps.GetPropInt(self, "m_nButtons"); local buttons_changed = buttons_last ^ buttons; local buttons_pressed = buttons_changed & buttons; local buttons_released = buttons_changed & (~buttons); local origin = self.GetOrigin() local eye_angles = self.EyeAngles() function RespawnSpectatingBots(player, classindex) { player.SetPlayerClass(classindex); SetPropInt(player, "m_Shared.m_iDesiredPlayerClass", classindex); player.ForceRegenerateAndRespawn(); } if (buttons_pressed & IN_ATTACK3) { printl("summoned bots") for (local minion; minion = FindByClassname(minion, "player");) { if (SummonedBotsCount < 4 && minion.GetTeam() == TEAM_SPECTATOR) { RespawnSpectatingBots(minion, 1) local get_class_string = ["", "scout", "sniper", "soldier", "demo", "medic", "heavy", "pyro", "spy", "engineer", "civilian"] local get_cur_class = get_class_string[minion.GetPlayerClass()] local class_model_string = format("models/bots/%s/bot_%s.mdl", get_cur_class, get_cur_class) PrecacheModel(class_model_string) minion.SetCustomModelWithClassAnimations(class_model_string) //set up the bot PerksScript.PlayerCleanup(minion) minion.SetTeam(self.GetTeam()) minion.Teleport(true, origin, true, eye_angles, false, Vector()) minion.SetDifficulty(3) minion.SetMaxVisionRangeOverride(2000) CreateBot(minion) //data stuff SummonedBotsCount++ SummonedBotsTable.append(minion) PrintTable(SummonedBotsTable) } } } } scope.SummonedBotsTable <- [] scope.SummonedBotsCount <- 0 scope.CreateBot <- CreateBot scope.ThinkTable_Player.SummonerEngineer <- SummonerEngineer } }, //spy //sapper damage { player_class = Spy, tier = 2, name = "Battery Drain" text = "+Sapped robots health are drained" func = function(player) { ::SapperLifeDrain <- function() { } player.ValidateScriptScope() local scope = player.GetScriptScope() scope.ThinkTable_Player.SapperLifeDrain <- SapperLifeDrain } }, ] ::PerksScript <- { GetItemInSlot = function(player, slot) { local item for (local i = 0; i < SLOT_COUNT; i++) { local wep = NetProps.GetPropEntityArray(player, "m_hMyWeapons", i) if ( wep == null || wep.GetSlot() != slot) continue item = wep break } return item } DispatchParticleEffectEx = function(name, origin, angles, delay_start, delay_end, ent_attachment, debug) { PrecacheEntityFromTable({ classname = "info_particle_system", effect_name = name }) if (delay_start < delay_end && debug == true) { printl("WARNING: Start delay is slower then end delay! Particle may not spawn!") } local particle = SpawnEntityFromTable("info_particle_system", { effect_name = name, targetname = "temporary" origin = origin angles = angles }) if (ent_attachment != null) { NetProps.SetPropEntityArray(particle, "m_hControlPointEnts", ent_attachment, 0); } if (delay_start == -1) { particle.AcceptInput("Start", "", null, null) } else { EntFireByHandle(particle, "Start", "", delay_start, null, null); } EntFireByHandle(particle, "Stop", "", delay_end, null, null); EntFireByHandle(particle, "Kill", "", delay_end + SINGLE_TICK, null, null); } DisplayCustomUpgrades = function() { // Stop flashing EntFireByHandle(self, "RunScriptCode", "NetProps.SetPropBool(self, `m_Shared.m_bInUpgradeZone`, false);", -1, null, null); ///////////// /////////////GENERAL ///////////// local buttons = NetProps.GetPropInt(self, "m_nButtons"); local buttons_changed = buttons_last ^ buttons; local buttons_pressed = buttons_changed & buttons; local buttons_released = buttons_changed & (~buttons); local player_origin = self.GetOrigin(); local input_options = [ {input = PRIMARY_FIRE, option = 1}, {input = SECONDARY_FIRE, option = 2}, {input = RELOAD, option = 3}, {input = SPECIAL_FIRE, option = 4}, ] function isWithinUpgradeStation() { for (local station; station = Entities.FindByClassname(station, "func_upgradestation");) { local mins = station.GetBoundingMins(); local maxs = station.GetBoundingMaxs(); local stationOrigin = station.GetOrigin(); if (player_origin.x >= (stationOrigin.x + mins.x) && player_origin.x <= (stationOrigin.x + maxs.x) && player_origin.y >= (stationOrigin.y + mins.y) && player_origin.y <= (stationOrigin.y + maxs.y) && player_origin.z >= (stationOrigin.z + mins.z) && player_origin.z <= (stationOrigin.z + maxs.z)) { return true; // Player is inside the station } } return false; // Default to false if not within any upgrade station } // Stop flashing EntFireByHandle(self, "RunScriptCode", "NetProps.SetPropBool(self, `m_Shared.m_bInUpgradeZone`, false);", -1, null, null); check_stations = isWithinUpgradeStation(); if (check_reroll == false && check_stations == true) { //self.SetScriptOverlayMaterial("hud/panel_scalable_red") check_reroll = true //(MinTier, MaxTier, RollChance, GuaranteeMinTiers, GuaranteeMaxTiers) RollForTalent(4, 4, 0, 0, 0) } if (entered_upgrade_station == false) { entered_upgrade_station = true } //picking upgrades if (check_stations == true) { self.AddCustomAttribute("no_attack", 1, -1) self.AddCustomAttribute("no_jump", 1, -1) upgr_text_1.AcceptInput("Display", null, self, self) upgr_text_2.AcceptInput("Display", null, self, self) upgr_text_3.AcceptInput("Display", null, self, self) upgr_text_4.AcceptInput("Display", null, self, self) local upgrade_texts = [ { text = upgr_text_1, y_value = 0.3 }, { text = upgr_text_2, y_value = 0.3 }, { text = upgr_text_3, y_value = 0.6 }, { text = upgr_text_4, y_value = 0.6 } ]; // Variable to lock in the selected talent for removal if (locked_remove_index == -1) // Start with no locked talent { locked_remove_index = remove_talent_index; // Default it to the current talent index } local head_origin = self.GetAttachmentOrigin(self.LookupAttachment("head")) if (display_annotation == false) { display_annotation = true SendGlobalGameEvent("show_annotation", { text = "Skill Points: "+talent_points lifetime = -1 worldPosX = head_origin.x worldPosY = head_origin.y worldPosZ = head_origin.z id = 1 play_sound = "misc/null.wav" show_distance = false show_effect = false visibilityBitfield = 1 }) } else { SendGlobalGameEvent("hide_annotation", { id = 1 }) } ///////////// /////////////REMOVING TALENTS ///////////// if (talent_delete_text != null) { ClientPrint(self, 4, "Hold Action key to delete: "+talent_delete_text+". Press Jump to cycle current talents. Current talant delete points: "+remove_points) } else { ClientPrint(self, 4, "WARNING: After picking a talent, you CANNOT change classes! If you want to delete a talent, press jump.") } if (buttons_pressed & JUMP) // Check if the jump button is pressed for selecting talent { printl("Jump button pressed"); if (current_talents.len() > 0) { // Get the current talent based on the remove_talent_index local current_talent = current_talents[remove_talent_index]; printl("Selected talent: " + current_talent.name); // Lock the current selection for removal locked_remove_index = remove_talent_index; // Update the index and loop back if needed remove_talent_index = (remove_talent_index + 1) % current_talents.len(); talent_delete_text = current_talent.name printl("Locked talent for removal: " + current_talents[locked_remove_index].name); } else { printl("No talents available to select."); locked_remove_index = -1; // No valid talents to remove } } // Check for removing talents if (NetProps.GetPropBool(self, "m_bUsingActionSlot") == true && remove_points > 0) { // Ensure a valid talent is locked for removal if (locked_remove_index >= 0 && locked_remove_index < current_talents.len()) { // Increment the removal meter meter_talent_removal++; // Print meter progress printl("Talent removal meter: " + meter_talent_removal); ClientPrint(self, 4, "Removing talent: " + meter_talent_removal+"/200") // if (bRefundSoundCharge == false) // { // bRefundSoundCharge = true // self.EmitSound("weapons/stickybomblauncher_charge_up.wav") // } // When the meter reaches the threshold, proceed with removal if (meter_talent_removal >= 200) { // Get the locked talent to remove local talent_to_remove = current_talents[locked_remove_index]; // Increment talent points talent_points++; remove_points-- self.EmitSound("ui/mm_medal_none.wav"); // if (bRefundSoundCharge == true) // { // bRefundSoundCharge = false // self.StopSound("weapons/stickybomblauncher_charge_up.wav") // } // Remove the attributes here foreach (key, entry in talent_to_remove) { if (key == "attrib_info") { // Now 'entry' contains the value of 'attrib_info', which is expected to be an array foreach (data in entry) // Iterate through each attribute entry in attrib_info { // Access properties of each attrib entry printl("Attrib: " + data.attrib + ", Value: " + data.value + ", Source: " + data.source); // You can also access other properties if they exist, e.g., attrib.func_associated ParseAttributeArray(data.attrib, data.value, self, data.source, true, true, data.default_value) } } } display_annotation = false printl("Removing talent: " + talent_to_remove.name); // Remove the locked talent from the list current_talents.remove(locked_remove_index); // Reset the meter after removal meter_talent_removal = 0; // Unlock the selection (no longer valid after removal) locked_remove_index = -1; // Ensure the index doesn't go out of bounds after removal if (current_talents.len() == 0) { // If no talents are left, reset the index printl("All talents removed."); remove_talent_index = 0; } else { // If the remove_talent_index exceeds available talents, reset if (remove_talent_index >= current_talents.len()) { remove_talent_index = 0; } } } } else { printl("Invalid locked talent index: " + locked_remove_index); } } else { bRefundSoundCharge = false //self.StopSound("weapons/stickybomblauncher_charge_up.wav") meter_talent_removal = 0; } printl(remove_points) ///////////// /////////////GENERAL ///////////// foreach (key in input_options) { if (buttons_pressed & key.input) // Check if the button is pressed { // Check if there are talent points available if (talent_points <= 0) { self.EmitSound("ui/mm_medal_click_rank_unknown.wav"); continue; // Skip if no talent points } // Use key.input to get the talent index and the associated talent option local talent_index = key.input; // This should correspond to the input key local talent = key.option; // Assuming talent is the 1-based index for talents // Adjust for zero-based indexing local talent_zero_based = talent - 1; // Retrieve the text and y_value for the current talent local y_text_pos = upgrade_texts[talent_zero_based].y_value; local upgr_text = upgrade_texts[talent_zero_based].text; printl("Y text position: " + y_text_pos); // Check the current stage of selection if (select_talent_stage == 0) // No talent currently selected { // Store the initial input and increment the stage initial_input = talent_index; prev_talent = talent_zero_based; // Store zero-based index select_talent_stage++; // Increment the stage to indicate a talent is selected printl("Pressed once: " + initial_input); // Log the initial input // Ensure safe access to upgrade_texts and picked_talents if (talent_zero_based < upgrade_texts.len() && talent_zero_based < picked_talents.len() && picked_talents[talent_zero_based] != null) { // Set message to show picked talent upgrade_texts[talent_zero_based].text.KeyValueFromString("message", picked_talents[talent_zero_based].text); self.EmitSound("ui/mm_medal_click.wav"); // Move the upgrade text upward to indicate selection upgr_text.AcceptInput("SetPosY", (y_text_pos - 0.1).tostring(), self, self); upgr_text.AcceptInput("Display", null, self, self); } else { printl("Error: Invalid or removed talent during selection: " + talent_zero_based); continue; // Skip this iteration if the talent is invalid } } else if (select_talent_stage == 1) // A talent is currently selected { if (talent_index == initial_input) // Same button pressed { printl("Same button pressed twice: " + talent_index); // Log action for same button // Check for safe access to picked talents if (prev_talent < picked_talents.len() && picked_talents[prev_talent] != null) { talent_points--; SendGlobalGameEvent("hide_annotation", { id = 1 }) display_annotation = false current_talents.append(picked_talents[prev_talent]); PrintTable(current_talents); picked_talents[prev_talent].func(self); // Call the associated function upgrade_texts[prev_talent].text.KeyValueFromString("message", "Bought: " + picked_talents[prev_talent].name); // Remove the talent from picked_talents after it is used picked_talents[prev_talent] = null; self.EmitSound("ui/mm_medal_click_rare.wav"); // Move the upgrade text back to its original position upgr_text.AcceptInput("SetPosY", (y_text_pos + 0.1).tostring(), self, self); // should move text upgr_text.AcceptInput("Display", null, self, self); } else { printl("Error: Invalid or already purchased talent: " + prev_talent); continue; // Skip this iteration if the talent is invalid } // Reset state select_talent_stage = 0; initial_input = null; prev_talent = null; } else // Different button pressed { printl("Different button pressed: " + talent_index); // Log action for different button local input_labels = [ "PRIMARY ATTACK KEY: ", "SECONDARY ATTACK KEY: ", "RELOAD ATTACK KEY: ", "SPECIAL ATTACK KEY: " ]; // Revert the previously selected talent text and position if (prev_talent < upgrade_texts.len() && prev_talent < picked_talents.len() && picked_talents[prev_talent] != null) { if (prev_talent < input_labels.len() && prev_talent < picked_talents.len() && picked_talents[prev_talent] != null) { upgrade_texts[prev_talent].text.KeyValueFromString("message", input_labels[prev_talent] + picked_talents[prev_talent].name); } //upgrade_texts[prev_talent].text.KeyValueFromString("message", picked_talents[prev_talent].name); upgrade_texts[prev_talent].text.AcceptInput("SetPosY", (upgrade_texts[prev_talent].y_value + 0.1).tostring(), self, self); upgrade_texts[prev_talent].text.AcceptInput("Display", null, self, self); } // Handle the new selection // Move the new selected text upward upgr_text.AcceptInput("SetPosY", (y_text_pos - 0.1).tostring(), self, self); upgr_text.AcceptInput("Display", null, self, self); // Update state for the new talent prev_talent = talent_zero_based; initial_input = null; // Reset initial input for new selection select_talent_stage = 0; self.EmitSound("ui/mm_medal_bronze.wav"); } } } } } else { self.RemoveCustomAttribute("no_attack") self.RemoveCustomAttribute("no_jump") display_annotation = false //dont need to run for some reason // upgr_text_1.AcceptInput("Disable", null, self, self) // upgr_text_2.AcceptInput("Disable", null, self, self) // upgr_text_3.AcceptInput("Disable", null, self, self) // upgr_text_4.AcceptInput("Disable", null, self, self) } buttons_last = buttons; } PlayerCleanup = function(player) { player.ValidateScriptScope() local scope = player.GetScriptScope() function SilentKillPlayer(player) { NetProps.SetPropInt(player, "m_iObserverLastMode", 5) local team = player.GetTeam() NetProps.SetPropInt(player, "m_iTeamNum", 1) player.DispatchSpawn() NetProps.SetPropInt(player, "m_iTeamNum", TEAM_SPECTATOR) } if (!("ThinkTable_Player" in scope)) { return } local ThinksCleanup = [ {thinkname = "SummonerEngineer", relavent_table = "SummonedBotsTable"} ] foreach (entry in ThinksCleanup) { local name = entry.thinkname // Check if the think name exists in ThinkTable_Player if (name in scope.ThinkTable_Player) { // Retrieve the table name and use it to access the relevant table in the scope local table_name = entry.relavent_table local relevant_table = scope[table_name] // Check if the table exists before printing if (typeof(relevant_table) != "null") { for (local i = relevant_table.len() - 1; i >= 0; i--) { local record = relevant_table[i]; printl(record) if (record.IsPlayer()) { printl("we found a player") PrintTable(relevant_table) SilentKillPlayer(record); // Execute any required action on the player PerksScript.PlayerCleanup(record) relevant_table.remove(i); // Remove the item at index `i` PrintTable(relevant_table) } } } } } } Cleanup = function() { for (local player; player = FindByClassname(player, "player");) { PlayerCleanup(player) } // keep this at the end delete ::PerksScript } // mandatory events OnGameEvent_recalculate_holidays = function(_) { if (GetRoundState() == 3) { Cleanup() } } OnGameEvent_mvm_wave_complete = function(_) { Cleanup() } /////////////////////////////////////////////////////things ParseAttributeArray = function(attrib, attrib_value, player, wep_slot, removeAttrib, reapply_attribs, attrib_value_default) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); local wep = (wep_slot != -1) ? scope.GetItemInSlot(player, wep_slot) : null; local source = (wep != null) ? wep : player; local attrib_found = false; // Process the attributes in the player's scope and check if attrib exists if (attrib != null && attrib_value != null && wep_slot != null) { // Initialize a counter to track the index local index = 0; foreach (attrib_apply in scope.attributes_to_apply) { // Check if the attribute name and source match if (attrib_apply.name == attrib && attrib_apply.source == source) { attrib_found = true; local current_value = attrib_apply.value; // Skip processing if the current and new values are the same if (current_value == attrib_value && !removeAttrib) { printl("No changes needed for " + attrib + ", values match for source."); break; } // Adjust the value based on removeAttrib flag if (removeAttrib) { printl("Removing modifier from " + attrib + "."); // Remove the entry using the current index scope.attributes_to_apply.remove(index); // Remove entry from array // Remove from the source if (source == player) { player.RemoveCustomAttribute(attrib); } else { source.RemoveCustomAttribute(attrib); } } else { printl("Applying modifier to " + attrib + "."); attrib_apply.value += attrib_value; // Apply normally } break; // Exit loop once the attribute is found and updated } index++; // Increment the index counter } // If the attribute was not found for the specific source, add it if (!attrib_found && !removeAttrib) // Only add if not removing { printl("Attribute " + attrib + " not found for source, adding new entry."); scope.attributes_to_apply.append( { name = attrib, value = attrib_value, // Start with attrib_value as initial value default_var = attrib_value_default, source = source // Store the source (player or weapon) }); } } // If reapply_attribs is true, reapply all attributes for their respective sources if (reapply_attribs) { printl("Reapplying all attributes."); foreach (attribute in scope.attributes_to_apply) { local value = attribute.value; local name = attribute.name; local attribute_source = attribute.source; local script_code; if (!removeAttrib) { // Format the command for adding an attribute if (attribute_source == player) { script_code = format("self.AddCustomAttribute('%s', %f, -1);", name, value); } else { script_code = format("attribute_source.AddAttribute('%s', %f, -1);", name, value); } } else // When removing { // Format the command for removing an attribute if (attribute_source == player) { script_code = format("self.RemoveCustomAttribute('%s');", name); } else { script_code = format("attribute_source.RemoveCustomAttribute('%s');", name); } } // Execute the formatted script code using EntFireByHandle EntFireByHandle(gamerules, "RunScriptCode", script_code, 0.1, null, null); } } } PlayerSpawn = function(player) { player.ValidateScriptScope() local scope = player.GetScriptScope() // Clear or create the appropriate ThinkTable if (!("ThinkTable_Player" in scope)) { //printl("Created think table") scope.ThinkTable_Player <- {} } // Create new ones here scope.self <- player scope.attribs_list <- {} ::Thinks_Player <- function() { foreach (name, func in ThinkTable_Player) { func.call(scope); } return -1 } // Add the Think function to the player AddThinkToEnt(player, "Thinks_Player") scope.upgr_text_1 <- SpawnEntityFromTable("game_text", { message = "Custom message here", channel = 2, fadein = 0, fadeout = 0, holdtime = 1.0, spawnflags = 0 x = 0.2, // centered on screen y = 0.3, // centered on screen effect = 0, // no fade color = "255 255 255" }); scope.upgr_text_2 <- SpawnEntityFromTable("game_text", { message = "Custom message here", channel = 3, fadein = 0, fadeout = 0, holdtime = 1.0, spawnflags = 0 x = 0.6, y = 0.3, effect = 0, // no fade color = "255 255 255" }); scope.upgr_text_3 <- SpawnEntityFromTable("game_text", { message = "Custom message here", channel = 4, fadein = 0, fadeout = 0, holdtime = 1.0, spawnflags = 0 x = 0.6, // centered on screen y = 0.6, // centered on screen effect = 0, // no fade color = "255 255 255" }); scope.upgr_text_4 <- SpawnEntityFromTable("game_text", { message = "Custom message here", channel = 1, fadein = 0, fadeout = 0, holdtime = 1.0, spawnflags = 0 x = 0.2, // centered on screen y = 0.6, // centered on screen effect = 0, // no fade color = "255 255 255" }); scope.sprite <- null scope.bRefundSoundCharge <- false scope.upgrade_angle_enter <- null scope.lookedAtButton <- null scope.buttons_last <- 0 scope.select_talent_stage <- 0 scope.remove_talent_index <- 0 scope.meter_talent_removal <- 0 scope.locked_remove_index <- -1 scope.check_stations <- false scope.display_annotation <- false scope.check_reroll <- false scope.entered_upgrade_station <- false scope.talent_delete_text <- null scope.picked_talents <- [] scope.boons <- boons scope.initial_input <- null scope.prev_talent <- null scope.current_talents <- [] scope.attributes_to_apply <- [] scope.talent_points <- 1 /// players get +1 when the reroll func is called scope.remove_points <- 1 scope.PrintTable <- PrintTable scope.DoPrintTable <- DoPrintTable scope.GetItemInSlot <- GetItemInSlot scope.RollForTalent <- RollForTalent scope.ParseAttributeArray <- ParseAttributeArray scope.ThinkTable_Player.DisplayCustomUpgrades <- DisplayCustomUpgrades // do stuff when player "spawns" here } PrintTable = function(table) { if (table == null) return; this.DoPrintTable(table, 0) } DoPrintTable = function(table, indent) { local line = "" for (local i = 0; i < indent; i++) { line += " " } line += typeof table == "array" ? "[" : "{"; ClientPrint(null, 2, line) indent += 2 foreach(k, v in table) { line = "" for (local i = 0; i < indent; i++) { line += " " } line += k.tostring() line += " = " if (typeof v == "table" || typeof v == "array") { ClientPrint(null, 2, line) this.DoPrintTable(v, indent) } else { try { line += v.tostring() } catch (e) { line += typeof v } ClientPrint(null, 2, line) } } indent -= 2 line = "" for (local i = 0; i < indent; i++) { line += " " } line += typeof table == "array" ? "]" : "}"; ClientPrint(null, 2, line) } CalculateCritFactor = function(attacker, victim) { if (victim.GetClassname() != "player") { victim = null } function IsMiniCritAttacker(entity) { local CritArray = [16, 19, 31] foreach(crit in CritArray) { if (entity.InCond(crit)) { return true } } return false } function IsMiniCritVictim(entity) { if (victim == null) return false local CritArray = [24, 30] foreach(crit in CritArray) { if (entity.InCond(crit)) { return true } } return false } function IsCritAttacker(entity) { local CritArray = [11, 34, 37, 39, 40, 44, 56] foreach(crit in CritArray) { if (entity.InCond(crit)) { return true } } return false } // Initialize the damage factor to 1.0 local factor = 1.0; // Check if the attacker is a crit attacker local isCritAttacker = IsCritAttacker(attacker); // Check if the attacker is a mini-crit attacker local isMiniCritAttacker = IsMiniCritAttacker(attacker); // Check if the victim is a mini-crit victim local isMiniCritVictim = IsMiniCritVictim(victim); // Determine the factor based on crit and mini-crit conditions if (isCritAttacker && isMiniCritVictim) { // If the attacker is a crit attacker and the victim is a mini-crit victim, crit overrides factor = 3.0; } else if (isCritAttacker) { // If the attacker is a crit attacker, crit factor factor = 3.0; } else if (isMiniCritAttacker) { // If the attacker is a mini-crit attacker, mini-crit factor factor = 1.35; } return factor; } RollForTalent = function(MinTier, MaxTier, RollChance, GuaranteeMinTiers, GuaranteeMaxTiers) { // Empty the picked_talents array if it already has talents if (picked_talents.len() > 0) { picked_talents.clear(); // Clear the array to reroll talents printl("Rerolled talents: cleared previous picks."); } talent_points++; local tier_options = []; local max_tier_options = []; // To store talents from the max tier local min_tier_options = []; // To store talents from the min tier // An array of upgrade text objects for easy access local upgrade_texts = [upgr_text_1, upgr_text_2, upgr_text_3, upgr_text_4]; local color_tiers = { "1" : "20 168 211", // blue "2" : "20 211 20", // green "3" : "211 133 20", // yellow "4" : "86 20 211" // purp }; // Gather talents based on player class and tiers, excluding already acquired talents foreach (talent in boons) { if (self.GetPlayerClass() != talent.player_class) { continue; } // Ensure the talent is not already picked if (talent in current_talents) { printl("Skipped already acquired talent: " + talent.name); continue; // Skip if the talent is already acquired } // Collect talents for the minimum and maximum tiers if (talent.tier == MinTier) { printl("Added to min tier pool: " + talent.name); min_tier_options.append(talent); // Collect min tier options tier_options.append(talent); // Add to general tier options as well } else if (talent.tier == MaxTier) { printl("Added to max tier pool: " + talent.name); max_tier_options.append(talent); // Collect max tier options tier_options.append(talent); // Add to general tier options as well } } // Ensure guaranteed min tier talents are picked first local guaranteed_min_count = (GuaranteeMinTiers < min_tier_options.len()) ? GuaranteeMinTiers : min_tier_options.len(); for (local i = 0; i < guaranteed_min_count; i++) { local random_index = RandomInt(0, min_tier_options.len() - 1); local option = min_tier_options[random_index]; picked_talents.append(option); printl("Guaranteed min tier picked skill: " + option.name); // Set the upgrade text for guaranteed picks if (i < upgrade_texts.len()) { upgrade_texts[i].KeyValueFromString("message", option.name); // Set the color based on the talent's tier local tier_color = color_tiers[option.tier.tostring()]; // Get the color for the specific tier upgrade_texts[i].KeyValueFromString("color", tier_color); } // Remove guaranteed option from tier_options and min_tier_options tier_options.remove(tier_options.find(option)); // Remove from tier_options if it exists min_tier_options.remove(random_index); // Remove from min tier options } // Ensure guaranteed max tier talents are picked next local guaranteed_max_count = (GuaranteeMaxTiers < max_tier_options.len()) ? GuaranteeMaxTiers : max_tier_options.len(); for (local i = 0; i < guaranteed_max_count; i++) { local random_index = RandomInt(0, max_tier_options.len() - 1); local option = max_tier_options[random_index]; picked_talents.append(option); printl("Guaranteed max tier picked skill: " + option.name); // Set the upgrade text for guaranteed picks if ((guaranteed_min_count + i) < upgrade_texts.len()) { upgrade_texts[guaranteed_min_count + i].KeyValueFromString("message", option.name); // Set the color based on the talent's tier local tier_color = color_tiers[option.tier.tostring()]; // Get the color for the specific tier upgrade_texts[guaranteed_min_count + i].KeyValueFromString("color", tier_color); } // Remove guaranteed option from tier_options and max_tier_options tier_options.remove(tier_options.find(option)); // Remove from tier_options if it exists max_tier_options.remove(random_index); // Remove from max tier options } // Now fill the rest of the picks local total_options = tier_options.len(); for (local i = picked_talents.len(); i < 4; ) { if (total_options <= 0) break; // Prevent out-of-bounds access local random_index = RandomInt(0, total_options - 1); local option = tier_options[random_index]; local input_labels = [ "PRIMARY ATTACK KEY: ", "SECONDARY ATTACK KEY: ", "RELOAD ATTACK KEY: ", "SPECIAL ATTACK KEY: " ]; // Ensure the skill is unique and not in current_talents if (!(option in picked_talents) && !(option in current_talents)) { picked_talents.append(option); printl("Picked skill: " + option.name); // Set the upgrade text corresponding to the index if (i < upgrade_texts.len()) { if (i < input_labels.len()) { // Assign the corresponding input label and talent name upgrade_texts[i].KeyValueFromString("message", input_labels[i] + option.name); } // Set the color based on the talent's tier local tier_color = color_tiers[option.tier.tostring()]; // Get the color for the specific tier upgrade_texts[i].KeyValueFromString("color", tier_color); // Increment the index after a successful update i++; } // Remove from tier_options and update total_options tier_options.remove(random_index); // Remove from tier_options total_options = tier_options.len(); // Update total_options after removal } } } //(MinTier, MaxTier, RollChance, GuaranteeMinTiers, GuaranteeMaxTiers) RerollTalentsAll = function(Min, Max, Chance, MinGurantee, MaxGurantee) { for (local player; player = Entities.FindByClassname(player, "player");) { if (player.GetTeam() != TEAM_RED) { continue } player.ValidateScriptScope() local scope = player.GetScriptScope() //spits out harmless error try { scope.RollForTalent(Min, Max, Chance, MinGurantee, MaxGurantee) } catch (e) { return } } } // OnGameEvent_mvm_begin_wave = function(_) // { // RerollTalentsAll(1, 2, 0.25, 0, 0) // } OnGameEvent_player_death = function(params) { local victim = GetPlayerFromUserID(params.userid) victim.ValidateScriptScope() local scope = victim.GetScriptScope() } OnGameEvent_post_inventory_application = function(params) { local player = GetPlayerFromUserID(params.userid) player.ValidateScriptScope() local scope = player.GetScriptScope() if (player.IsBotOfType(TF_BOT_FAKE_CLIENT)) { EntFireByHandle(gamerules, "RunScriptCode", "PerksScript.BotSpawn(activator)", 0.2, player, player) return } if (player && player.GetTeam() == TEAM_RED && !("ThinkTable_Player" in scope)) { //PlayerSpawn(player) EntFireByHandle(gamerules, "RunScriptCode", "PerksScript.PlayerSpawn(activator)", 0.2, player, player) } else if (player.GetTeam() == TEAM_RED) { printl("running reapply") EntFireByHandle(gamerules, "RunScriptCode", "PerksScript.ParseAttributeArray(null, null, activator, null, false, true, null)", 0.2, player, player) } } OnGameEvent_player_spawn = function(params) { local player = GetPlayerFromUserID(params.userid) player.ValidateScriptScope() local scope = player.GetScriptScope() if (player.IsBotOfType(TF_BOT_FAKE_CLIENT)) { EntFireByHandle(gamerules, "RunScriptCode", "PerksScript.BotSpawn(activator)", 0.1, player, player) return } if (player && player.GetTeam() == TEAM_RED && !("ThinkTable_Player" in scope)) { //PlayerSpawn(player) EntFireByHandle(gamerules, "RunScriptCode", "PerksScript.PlayerSpawn(activator)", 0.2, player, player) } } OnScriptHook_OnTakeDamage = function(params) { local victim = params.const_entity local weapon = params.weapon local attacker = params.attacker local entity = params.inflictor local damage = params.damage local dmgtype = params.damage_type local dmg_special = params.damage_stats local weapon_id = NetProps.GetPropInt(weapon, STRING_NETPROP_ITEMDEF) } }; //spoof player spawn for (local i = 1, player; i <= MaxClients(); i++) { if (player = PlayerInstanceFromIndex(i), player && player.GetTeam() == 2) { PerksScript.PlayerSpawn(player) } } __CollectGameEventCallbacks(PerksScript)