//place this file in your tf > scripts > vscripts folder //Cntrl + F, then put in "#" for documentation // function AddRingerThink(ent, Using_PopExt, cloak_duration, reuse_cloak_time) // { // ::MAX_WEAPONS <- 8 // //first, we need to check if the bot actually has the dead ringer // for (local i = 0; i < MAX_WEAPONS; i++) // { // local weapon = NetProps.GetPropEntityArray(ent, "m_hMyWeapons", i) // if (weapon == null) // { // continue // } // else // { // local index = NetProps.GetPropInt(weapon, "m_AttributeManager.m_Item.m_iItemDefinitionIndex") // if (index == 59) // { // //The Dead Ringer's internal index is 59. // //if index returns 59, do everything below // //first we need to ensure that the spy is holding the ringer out on spawn // //and also ensure the spy doesnt drop the ringer immediately // ent.PressAltFireButton(0.1) // ent.AddBotAttribute(8) // Attributes SuppressFire // ent.ValidateScriptScope() // local ent_scope = ent.GetScriptScope() // ent_scope.cloaktime <- cloak_duration // ent_scope.disallow_attack <- true // ent_scope.serverTime <- 0 // //these functions only one once every second // //always runs, determines how long we must remain cloaked // ent_scope.tickSpyCloak <- function() // { // if (cloaktime == null) return // local time = floor(Time()) // if (time != serverTime) { // Once every second // serverTime = time // if (cloaktime > 0) // { // cloaktime-- // } // if (cloaktime == 0) // { // disallow_attack = false // self.RemoveBotAttribute(8) // self.PressAltFireButton(0.1) // cloaktime = cloak_duration // } // } // } // //only runs if you have set "reuse_cloak_time" (arg 4) to a value other then -1 // ent_scope.ringer_reuse_time <- reuse_cloak_time // ent_scope.reuse_ringer <- function() // { // if (ringer_reuse_time == null || reuse_cloak_time == -1) return // local time = floor(Time()) // if (time != serverTime) { // Once every second // serverTime = time // if (ringer_reuse_time > 0) // { // ringer_reuse_time-- // } // if (ringer_reuse_time == 0) // { // disallow_attack = true // ringer_reuse_time = reuse_cloak_time // } // } // } // ::DeadRingerThink <- function() // { // //Because Spy bots have infinite cloak, we need to do a lot of script stuff to control // //when the Spy bot cloaks and decloaks. We could also just modify the cloak value internally // //by forcing a limited cloak, but i dont want to do that for simplicity // //if we are not currently alive, remove script from scope // if (NetProps.GetPropInt(self, "m_lifeState") != 0) // { // AddThinkToEnt(self, null) // NetProps.SetPropString(self, "m_iszScriptThinkFunction", "") // } // local cur_health = self.GetHealth() // local scope = self.GetScriptScope() // //If we lose health (i.e. attacked), we arent cloaked and reuse ringer isnt forcing us to not feign // //throw the ringer up. // if (self.GetMaxHealth() != cur_health && !self.InCond(4) && disallow_attack == true) // { // self.RemoveBotAttribute(8) // SuppressFire // self.PressAltFireButton(0.1) // self.AddBotAttribute(8) // } // //if we arent cloaked and reuse ringer hasnt made us not attack, tick down seconds // //as to how long until we can try a feign // if (disallow_attack == false && !self.InCond(4)) // { // reuse_ringer() // } // //if we are cloaked, tick how long until we must decloak // if (self.InCond(4)) // { // tickSpyCloak() // //self.AddCustomAttribute("voice pitch scale", 0, 2) // if you dont want spy bots to laugh while invisible, uncomment this // } // return -1 // this is required without pop extensions // } // //Must use delay // if (Using_PopExt == true) // { // EntFireByHandle(ent, "RunScriptCode", "PopExtUtil.AddThinkToEnt(self, `DeadRingerThink`)", 0.1, ent, ent) // } // else // { // EntFireByHandle(ent, "RunScriptCode", "AddThinkToEnt(self, `DeadRingerThink`)", 0.1, ent, ent) // } // } // } // } // } ::RingerNamespace <- { Cleanup = function() { // cleanup any persistent changes here // keep this at the end delete ::RingerNamespace try delete ::DeadRingerThink catch(e) return } // mandatory events OnGameEvent_recalculate_holidays = function(_) { if (GetRoundState() == 3) Cleanup() } OnGameEvent_mvm_wave_complete = function(_) { Cleanup() } // add stored variables or your own events here // // e.g. // myvar = 123 // // OnGameEvent_player_death = function(params) // { ... } AddRingerThink = function(ent, Using_PopExt, cloak_duration, reuse_cloak_time) { ::MAX_WEAPONS <- 8 //first, we need to check if the bot actually has the dead ringer for (local i = 0; i < MAX_WEAPONS; i++) { local weapon = NetProps.GetPropEntityArray(ent, "m_hMyWeapons", i) if (weapon == null) { continue } else { local index = NetProps.GetPropInt(weapon, "m_AttributeManager.m_Item.m_iItemDefinitionIndex") if (index == 59) { //The Dead Ringer's internal index is 59. //if index returns 59, do everything below //first we need to ensure that the spy is holding the ringer out on spawn //and also ensure the spy doesnt drop the ringer immediately ent.PressAltFireButton(0.1) ent.AddBotAttribute(8) // Attributes SuppressFire ent.ValidateScriptScope() local ent_scope = ent.GetScriptScope() ent_scope.cloaktime <- cloak_duration ent_scope.disallow_attack <- true ent_scope.serverTime <- 0 //these functions only one once every second //always runs, determines how long we must remain cloaked ent_scope.tickSpyCloak <- function() { if (cloaktime == null) return local time = floor(Time()) if (time != serverTime) { // Once every second serverTime = time if (cloaktime > 0) { cloaktime-- } if (cloaktime == 0) { disallow_attack = false self.RemoveBotAttribute(8) self.PressAltFireButton(0.1) cloaktime = cloak_duration } } } //only runs if you have set "reuse_cloak_time" (arg 4) to a value other then -1 ent_scope.ringer_reuse_time <- reuse_cloak_time ent_scope.reuse_ringer <- function() { if (ringer_reuse_time == null || reuse_cloak_time == -1) return local time = floor(Time()) if (time != serverTime) { // Once every second serverTime = time if (ringer_reuse_time > 0) { ringer_reuse_time-- } if (ringer_reuse_time == 0) { disallow_attack = true ringer_reuse_time = reuse_cloak_time } } } ::DeadRingerThink <- function() { //Because Spy bots have infinite cloak, we need to do a lot of script stuff to control //when the Spy bot cloaks and decloaks. We could also just modify the cloak value internally //by forcing a limited cloak, but i dont want to do that for simplicity //if we are not currently alive, remove script from scope if (NetProps.GetPropInt(self, "m_lifeState") != 0) { AddThinkToEnt(self, null) NetProps.SetPropString(self, "m_iszScriptThinkFunction", "") } local cur_health = self.GetHealth() local scope = self.GetScriptScope() //If we lose health (i.e. attacked), we arent cloaked and reuse ringer isnt forcing us to not feign //throw the ringer up. if (self.GetMaxHealth() != cur_health && !self.InCond(4) && disallow_attack == true) { self.RemoveBotAttribute(8) // SuppressFire self.PressAltFireButton(0.1) self.AddBotAttribute(8) } //if we arent cloaked and reuse ringer hasnt made us not attack, tick down seconds //as to how long until we can try a feign if (disallow_attack == false && !self.InCond(4)) { reuse_ringer() } //if we are cloaked, tick how long until we must decloak if (self.InCond(4)) { tickSpyCloak() //self.AddCustomAttribute("voice pitch scale", 0, 2) // if you dont want spy bots to laugh while invisible, uncomment this } return -1 // this is required without pop extensions } //Must use delay if (Using_PopExt == true) { EntFireByHandle(ent, "RunScriptCode", "PopExtUtil.AddThinkToEnt(self, `DeadRingerThink`)", 0.1, ent, ent) } else { EntFireByHandle(ent, "RunScriptCode", "AddThinkToEnt(self, `DeadRingerThink`)", 0.1, ent, ent) } } } } } OnGameEvent_post_inventory_application = function(params) { local player = GetPlayerFromUserID(params.userid) player.ValidateScriptScope() local playerscope = player.GetScriptScope() //# //So usually, we could do something like check if the bot has a tag, or some other condition //However, due to requirement of namespaces, we cant do this here. VScript moment. //This script will run on every bot that spawns if (player.IsBotOfType(1337)) { //arguements in order //arg 1 //player that think is applied to. //arg 2 //Is popextensions enabled? //arg 3 //Dead Ringer cloak time (Spy bot will drop ringer after this many seconds) //arg 4 //Dead Ringer re-cloak time (After the first feign, the Spy bot will use its ringer after this time.) AddRingerThink(player, false, 5, -1) } } }; //insert the function into the namespace so the namespace can read the function __CollectGameEventCallbacks(RingerNamespace) ////////////////////////// # EXAMPLE FOR POP /////////////////////////////////// // Wave // { // StartWaveOutput // { // Target wave_start_relay_ironman // Action Trigger // } // DoneOutput // { // Target wave_finished_relay // Action Trigger // } // InitWaveOutput // { // Target gamerules // Action RunScriptCode // Param " // // Load popextensions script // //IncludeScript(`popextensions_main.nut`, getroottable()) // IncludeScript(`spy_deadringer.nut`) // " // } // WaveSpawn // { // Name "a1" // Where spawnbot // TotalCount 55 // MaxActive 1 // SpawnCount 1 // WaitBeforeStarting 0 // WaitBetweenSpawns 0 // TotalCurrency 0 // TFBot // { // Class Spy // Item "The Dead Ringer" // //Attributes SuppressFire // } // } // }