//behavior tags IncludeScript("popextensions/botbehavior", getroottable()) try IncludeScript(format("%s_tags.nut", split(split(__popname, "/")[2], ".")[0]), getroottable()) catch(e) ::__tagarray <- [] PopExtUtil.PlayerManager.ValidateScriptScope() local popext_funcs = { popext_addcond = function(bot, args) { if (args.len() == 1) { if (args[0].tointeger() == 43) { bot.ForceChangeTeam(TF_TEAM_PVE_DEFENDERS, true) // PopExtTags.DeathHookTable.MoveToSpec <- function (params) { // if (!IsPlayerABot(bot)) return // EntFirebyHandle(bot, "RunScriptCode", "self.ForceChangeTeam(TEAM_SPECTATOR, true)", 3, null, null) // } } else bot.AddCond(args[0].tointeger()) } else if (args.len() >= 2) bot.AddCondEx(args[0].tointeger(), args[1].tointeger(), null) } popext_reprogrammed = function(bot, args) { bot.ForceChangeTeam(TF_TEAM_PVE_DEFENDERS, true) // PopExtTags.DeathHookTable.MoveToSpec <- function (params) { // if (!IsPlayerABot(bot)) return // EntFireByHandle(bot, "RunScriptCode", "self.ForceChangeTeam(TEAM_SPECTATOR, true)", 3, null, null) // } } // popext_reprogrammed_neutral = function(bot, args) { // bot.ForceChangeTeam(TEAM_UNASSIGNED, true) // } popext_altfire = function(bot, args) { if (args.len() == 1) bot.PressAltFireButton(INT_MAX) else if (args.len() >= 2) bot.PressAltFireButton(args[1].tointeger()) } popext_usehumanmodel = function(bot, args) { bot.SetCustomModelWithClassAnimations(format("models/player/%s.mdl", PopExtUtil.Classes[bot.GetPlayerClass()])) } popext_usecustommodel = function(bot, args) { if (!IsModelPrecached(args[0])) PrecacheModel(args[0]) bot.SetCustomModelWithClassAnimations(args[0]) } popext_usehumananims = function(bot, args) { local class_string = PopExtUtil.Classes[bot.GetPlayerClass()] bot.SetCustomModelWithClassAnimations(format("models/player/%s.mdl", class_string)) PopExtUtil.PlayerRobotModel(bot, format("models/bots/%s/bot_%s.mdl", class_string, class_string)) } popext_alwaysglow = function(bot, args) { SetPropBool(bot, "m_bGlowEnabled", true) } popext_stripslot = function(bot, args) { if (args.len() == 1) args.append(-1) local slot = args[1].tointeger() if (slot == -1) slot = player.GetActiveWeapon().GetSlot() PopExtUtil.GetItemInSlot(player, slot).Kill() } popext_fireweapon = function(bot, args) { local args_len = args.len() local button = args[0].tointeger() local cooldown = (args_len > 1) ? args[1].tointeger() : 3 local duration = (args_len > 2) ? args[2].tointeger() : 1.0 local delay = (args_len > 3) ? args[3].tointeger() : 0 local repeats = (args_len > 4) ? args[4].tointeger() : INT_MAX local ifhealthbelow = (args_len > 5) ? args[5].tointeger() : INT_MAX local ifseetarget = (args_len > 6) ? args[6].tointeger() : 1 local maxrepeats = 0 local cooldowntime = Time() + cooldown local delaytime = Time() + delay bot.GetScriptScope().PlayerThinkTable.FireWeaponThink <- function() { if ((maxrepeats) >= repeats) { delete bot.GetScriptScope().PlayerThinkTable.FireWeaponThink return } if (Time() < delaytime || (Time() < cooldowntime) || bot.GetHealth() > ifhealthbelow || bot.HasBotAttribute(SUPPRESS_FIRE)) return maxrepeats++ PopExtUtil.PressButton(bot, button) cooldowntime = Time() + cooldown } } popext_weaponswitch = function(bot, args) { local args_len = args.len() local slot = args[0].tointeger() local cooldown = (args_len > 1) ? args[1].tointeger() : 3 local duration = (args_len > 2) ? args[2].tointeger() : 5 local delay = (args_len > 3) ? args[3].tointeger() : 3 local repeats = (args_len > 4) ? args[4].tointeger() : INT_MAX local ifhealthbelow = (args_len > 5) ? args[5].tointeger() : INT_MAX local ifseetarget = (args_len > 6) ? args[6].tointeger() : 1 local maxrepeats = 0 local cooldowntime = Time() + cooldown local delaytime = Time() + delay bot.GetScriptScope().PlayerThinkTable.WeaponSwitchThink <- function() { if ((maxrepeats) >= repeats) { delete bot.GetScriptScope().PlayerThinkTable.WeaponSwitchThink return } if (Time() < delaytime || (Time() < cooldowntime) || bot.GetHealth() > ifhealthbelow) return maxrepeats++ bot.Weapon_Switch(PopExtUtil.GetItemInSlot(bot, slot)) bot.AddCustomAttribute("disable weapon switch", 1, duration) EntFireByHandle(bot, "RunScriptCode","self.RemoveCustomAttribute(`disable weapon switch`)", duration, null, null) EntFireByHandle(bot, "RunScriptCode", format("self.Weapon_Switch(PopExtUtil.GetItemInSlot(self, %d))", slot), duration+SINGLE_TICK, null, null) cooldowntime = Time() + cooldown } } popext_spell = function(bot, args) { local args_len = args.len() local type = args[0].tointeger() local cooldown = args[1].tointeger() local delay = (args_len > 2) ? args[2].tointeger() : 3 local repeats = (args_len > 3) ? args[3].tointeger() : INT_MAX local ifhealthbelow = (args_len > 4) ? args[4].tointeger() : INT_MAX local charges = (args_len > 5) ? args[5].tointeger() : 1 local ifseetarget = (args_len > 6) ? args[6].tointeger() : 1 local spellbook = PopExtUtil.GetItemInSlot(bot, SLOT_PDA) //equip a spellbook if the bot doesn't have one if (spellbook == null) { local book = Entities.CreateByClassname("tf_weapon_spellbook") SetPropInt(book, STRING_NETPROP_ITEMDEF, ID_BASIC_SPELLBOOK) SetPropBool(book, "m_AttributeManager.m_Item.m_bInitialized", true) SetPropBool(book, "m_bValidatedAttachedEntity", true) SetPropEntityArray(bot, "m_hMyWeapons", book, book.GetSlot()) book.SetTeam(bot.GetTeam()) DispatchSpawn(book) bot.Weapon_Equip(book) //try again next think return } local cooldowntime = Time() + cooldown local delaytime = Time() + delay local maxrepeats = 0 bot.GetScriptScope().PlayerThinkTable.SpellThink <- function() { if ((maxrepeats) >= repeats) { delete bot.GetScriptScope().PlayerThinkTable.SpellThink return } if (Time() < delaytime || (Time() < cooldowntime) || bot.GetHealth() > ifhealthbelow) return maxrepeats++ SetPropInt(spellbook, "m_iSelectedSpellIndex", type) SetPropInt(spellbook, "m_iSpellCharges", charges) try { bot.Weapon_Switch(spellbook) spellbook.AddAttribute("disable weapon switch", 1, 1) // duration doesn't work here? spellbook.ReapplyProvision() } catch(e) printl("can't find spellbook!") EntFireByHandle(spellbook, "RunScriptCode", "self.RemoveAttribute(`disable weapon switch`)", 1, null, null) EntFireByHandle(spellbook, "RunScriptCode", "self.ReapplyProvision()", 1, null, null) cooldowntime = Time() + cooldown } } popext_spawntemplate = function(bot, args) { SpawnTemplates.SpawnTemplate(PointTemplates[args[0]], bot, bot.GetOrigin(), bot.GetLocalAngles()) } popext_forceromevision = function(bot, args) { //kill the existing romevision EntFireByHandle(bot, "RunScriptCode", @" local killrome = [] if (self.IsBotOfType(1337)) for (local child = self.FirstMoveChild(); child != null; child = child.NextMovePeer()) if (child.GetClassname() == `tf_wearable` && startswith(child.GetModelName(), `models/workshop/player/items/`+PopExtUtil.Classes[self.GetPlayerClass()]+`/tw`)) killrome.append(child) for (local i = killrome.len() - 1; i >= 0; i--) killrome[i].Kill() local cosmetics = PopExtUtil.ROMEVISION_MODELS[self.GetPlayerClass()] if (self.GetModelName() == `models/bots/demo/bot_sentry_buster.mdl`) { PopExtUtil.CreatePlayerWearable(self, PopExtUtil.ROMEVISION_MODELS[self.GetPlayerClass()][2]) return } foreach (cosmetic in cosmetics) { local wearable = PopExtUtil.CreatePlayerWearable(self, cosmetic) SetPropString(wearable, `m_iName`, `__bot_romevision_model`) } ", -1, null, null) } popext_customattr = function(bot, args) { local args_len = args.len() if (args_len == 2) CustomAttributes.AddAttr(bot, args[0], args[1]) else if (args_len == 3) CustomAttributes.AddAttr(bot, args[0], args[1], args[2]) } popext_ringoffire = function(bot, args) { local args_len = args.len() local damage = (args_len > 0) ? args[0].tofloat() : 7.5 local interval = (args_len > 1) ? args[1].tofloat() : 0.5 local radius = (args_len > 2) ? args[2].tofloat() : 135.0 local cooldown = Time() + interval bot.GetScriptScope().PlayerThinkTable.RingOfFireThink <- function() { if (Time() < cooldown) return local origin = bot.GetOrigin() local angles = bot.GetAngles() DispatchParticleEffect("heavy_ring_of_fire", origin, angles) for (local player; player = FindByClassnameWithin(player, "player", origin, radius);) { if (player.GetTeam() == bot.GetTeam() || !PopExtUtil.IsAlive(player)) continue player.TakeDamage(damage, DMG_BURN, bot) PopExtUtil.Ignite(player) } cooldown = Time() + interval } } popext_meleeai = function(bot, args) { local visionoverride = bot.GetMaxVisionRangeOverride() == -1 ? INT_MAX : bot.GetMaxVisionRangeOverride() bot.GetScriptScope().PlayerThinkTable.MeleeAIThink <- function() { local threat = FindThreat(visionoverride) if (threat == null || threat.IsFullyInvisible() || threat.IsStealthed()) return LookAt(threat.EyePosition(), 50, 50) } } popext_weaponresist = function(bot, args) { local weapon = args[0] local amount = args[1].tofloat() PopExtTags.TakeDamageTable.WeaponResistTakeDamage <- function(params) { local player = params.const_entity if (!player.IsPlayer() || params.attacker == null || params.weapon == null || !PopExtUtil.HasItemInLoadout(player, params.weapon)) return if (params.damage * amount < player.GetHealth()) params.damage *= amount } } popext_setskin = function(bot, args) { SetPropBool(bot, "m_bForcedSkin", true) SetPropInt(bot, "m_nForcedSkin", args[0].tointeger()) PopExtTags.DeathHookTable.ResetSkin <- function(params) { local b = GetPlayerFromUserID(params.userid) if (b == bot) { SetPropBool(bot, "m_bForcedSkin", true) SetPropInt(bot, "m_nForcedSkin", 1) } } PopExtTags.TeamSwitchTable.ResetSkin <- function(params) { local b = GetPlayerFromUserID(params.userid) if (b == bot && params.team == TEAM_SPECTATOR) { SetPropBool(bot, "m_bForcedSkin", true) SetPropInt(bot, "m_nForcedSkin", 1) } } } popext_doubledonk = function(bot, args) { bot.GetScriptScope().PlayerThinkTable.DoubleDonker <- function() { local distance = GetThreatDistanceSqr() printl("holdtime: " + (2 * exp(-distance / 10) + 0.5)) } } popext_dispenseroverride = function(bot, args) { if (args.len() == 0) args.append(1) //sentry override by default local alwaysfire = bot.HasBotAttribute(ALWAYS_FIRE_WEAPON) //force deploy dispenser when leaving spawn and kill it immediately if (!alwaysfire && args[0].tointeger() == 1) bot.PressFireButton(INT_MAX) bot.GetScriptScope().PlayerThinkTable.DispenserBuildThink <- function() { //start forcing primary attack when near hint local hint = FindByClassnameWithin(null, "bot_hint*", bot.GetOrigin(), 16) if (hint && !alwaysfire) bot.PressFireButton(0.0) } bot.GetScriptScope().BuiltObjectTable.DispenserBuildOverride <- function(params) { local obj = params.object //dispenser built, stop force firing if (!alwaysfire) bot.PressFireButton(0.0) if ((args[0].tointeger() == 1 && obj == OBJ_SENTRYGUN) || (args[0].tointeger() == 2 && obj == OBJ_TELEPORTER)) { if (obj == OBJ_SENTRYGUN) bot.AddCustomAttribute("engy sentry radius increased", FLT_SMALL, -1) bot.AddCustomAttribute("upgrade rate decrease", 8, -1) local building = EntIndexToHScript(params.index) if (obj != OBJ_DISPENSER) { building.ValidateScriptScope() building.GetScriptScope().CheckBuiltThink <- function() { if (GetPropBool(building, "m_bBuilding")) return EntFireByHandle(building, "Disable", "", -1, null, null) delete building.GetScriptScope().CheckBuiltThink } AddThinkToEnt(building, "CheckBuiltThink") } //kill the first alwaysfire built dispenser when leaving spawn local hint = FindByClassnameWithin(null, "bot_hint*", building.GetOrigin(), 16) if (!hint) { building.Kill() return } //hide the building building.SetModelScale(0.01, 0.0) SetPropInt(building, "m_nRenderMode", kRenderTransColor) SetPropInt(building, "m_clrRender", 0) building.SetHealth(INT_MAX) building.SetSolid(SOLID_NONE) PopExtUtil.SetTargetname(building, format("building%d", building.entindex())) //create a dispenser local dispenser = CreateByClassname("obj_dispenser") SetPropEntity(dispenser, "m_hBuilder", bot) PopExtUtil.SetTargetname(dispenser, format("dispenser%d", dispenser.entindex())) dispenser.SetTeam(bot.GetTeam()) dispenser.SetSkin(bot.GetSkin()) dispenser.DispatchSpawn() //post-spawn stuff // SetPropInt(dispenser, "m_iHighestUpgradeLevel", 2) //doesn't work local builder = PopExtUtil.GetItemInSlot(bot, SLOT_PDA) local builtobj = GetPropEntity(builder, "m_hObjectBeingBuilt") SetPropInt(builder, "m_iObjectType", 0) SetPropInt(builder, "m_iBuildState", 2) // if (builtobj && builtobj.GetClassname() != "obj_dispenser") builtobj.Kill() SetPropEntity(builder, "m_hObjectBeingBuilt", dispenser) //makes dispenser a null reference bot.Weapon_Switch(builder) builder.PrimaryAttack() //m_hObjectBeingBuilt messes with our dispenser reference, do radius check to grab it again for (local d; d = FindByClassnameWithin(d, "obj_dispenser", building.GetOrigin(), 128);) if (GetPropEntity(d, "m_hBuilder") == bot) dispenser = d dispenser.SetLocalOrigin(building.GetLocalOrigin()) dispenser.SetLocalAngles(building.GetLocalAngles()) AddOutput(dispenser, "OnDestroyed", building.GetName(), "Kill", "", -1, -1) //kill it to avoid showing up in killfeed AddOutput(building, "OnDestroyed", dispenser.GetName(), "Destroy", "", -1, -1) //always destroy the dispenser } } } popext_giveweapon = function(bot, args) { local weapon = Entities.CreateByClassname(args[0]) SetPropInt(weapon, STRING_NETPROP_ITEMDEF, args[1].tointeger()) SetPropBool(weapon, "m_AttributeManager.m_Item.m_bInitialized", true) SetPropBool(weapon, "m_bValidatedAttachedEntity", true) weapon.SetTeam(bot.GetTeam()) Entities.DispatchSpawn(weapon) PopExtUtil.GetItemInSlot(bot, weapon.GetSlot()).Destroy() bot.Weapon_Equip(weapon) return weapon } popext_usebestweapon = function(bot, args) { bot.GetScriptScope().PlayerThinkTable.BestWeaponThink <- function() { switch(bot.GetPlayerClass()) { case 1: //TF_CLASS_SCOUT //scout and pyro's UseBestWeapon is inverted //switch them to secondaries, then back to primary when enemies are close if (bot.GetActiveWeapon() != PopExtUtil.GetItemInSlot(bot, SLOT_SECONDARY)) bot.Weapon_Switch(PopExtUtil.GetItemInSlot(bot, SLOT_SECONDARY)) for (local p; p = Entities.FindByClassnameWithin(p, "player", bot.GetOrigin(), 500);) { if (p.GetTeam() == bot.GetTeam()) continue local primary = PopExtUtil.GetItemInSlot(bot, SLOT_PRIMARY) bot.Weapon_Switch(primary) primary.AddAttribute("disable weapon switch", 1, 1) primary.ReapplyProvision() } break case 2: //TF_CLASS_SNIPER for (local p; p = Entities.FindByClassnameWithin(p, "player", bot.GetOrigin(), 750);) { if (p.GetTeam() == bot.GetTeam() || bot.GetActiveWeapon().GetSlot() == 2) continue //potentially not break sniper ai local secondary = PopExtUtil.GetItemInSlot(bot, SLOT_SECONDARY) bot.Weapon_Switch(secondary) secondary.AddAttribute("disable weapon switch", 1, 1) secondary.ReapplyProvision() } break case 3: //TF_CLASS_SOLDIER for (local p; p = Entities.FindByClassnameWithin(p, "player", bot.GetOrigin(), 500);) { if (p.GetTeam() == bot.GetTeam() || bot.GetActiveWeapon().Clip1() != 0) continue local secondary = PopExtUtil.GetItemInSlot(bot, SLOT_SECONDARY) bot.Weapon_Switch(secondary) secondary.AddAttribute("disable weapon switch", 1, 2) secondary.ReapplyProvision() } break case 7: //TF_CLASS_PYRO //scout and pyro's UseBestWeapon is inverted //switch them to secondaries, then back to primary when enemies are close //TODO: check if we're targetting a soldier with a simple raycaster, or wait for more bot functions to be exposed if (bot.GetActiveWeapon() != PopExtUtil.GetItemInSlot(bot, SLOT_SECONDARY)) bot.Weapon_Switch(PopExtUtil.GetItemInSlot(bot, SLOT_SECONDARY)) for (local p; p = Entities.FindByClassnameWithin(p, "player", bot.GetOrigin(), 500);) { if (p.GetTeam() == bot.GetTeam()) continue local primary = PopExtUtil.GetItemInSlot(bot, SLOT_PRIMARY) bot.Weapon_Switch(primary) primary.AddAttribute("disable weapon switch", 1, 1) primary.ReapplyProvision() } break } } } popext_homingprojectile = function(bot, args) { // Tag homingprojectile |turnpower|speedmult|ignoreStealthedSpies|ignoreDisguisedSpies local args_len = args.len() local turn_power = (args_len > 0) ? args[0].tofloat() : 0.75 local speed_mult = (args_len > 1) ? args[1].tofloat() : 1.0 local ignoreStealthedSpies = (args_len > 2) ? args[2].tointeger() : 1 local ignoreDisguisedSpies = (args_len > 3) ? args[3].tointeger() : 1 bot.GetScriptScope().PlayerThinkTable.HomingProjectileScanner <- function() { for (local projectile; projectile = Entities.FindByClassname(projectile, "tf_projectile_*");) { if (projectile.GetOwner() != bot || !Homing.IsValidProjectile(projectile, PopExtUtil.HomingProjectiles)) continue // Any other parameters needed by the projectile thinker can be set here Homing.AttachProjectileThinker(projectile, speed_mult, turn_power, ignoreDisguisedSpies, ignoreStealthedSpies) } } PopExtTags.TakeDamageTable.HomingTakeDamage <- function(params) { if (!params.const_entity.IsPlayer()) return local classname = params.inflictor.GetClassname() if (classname != "tf_projectile_flare" && classname != "tf_projectile_energy_ring") return EntFireByHandle(params.inflictor, "Kill", null, 0.5, null, null) } } popext_rocketcustomtrail = function (bot, args) { bot.GetScriptScope().PlayerThinkTable.ProjectileTrailThink <- function() { for (local projectile; projectile = FindByClassname(projectile, "tf_projectile_*");) { if (projectile.GetEFlags() & EFL_NO_ROTORWASH_PUSH || GetPropEntity(projectile, "m_hOwnerEntity") != bot) continue if (args.len() > 1) EntFireByHandle(projectile, "DispatchEffect", "ParticleEffectStop", -1, null, null) // EntFireByHandle(projectile, "RunScriptCode", format("DispatchParticleEffect(`%s`, self.GetOrigin(), self.GetAngles())", args[0]), SINGLE_TICK, null, null) local particle = SpawnEntityFromTable("trigger_particle", { particle_name = args[0], attachment_type = 1, // PATTACH_ABSORIGIN_FOLLOW, spawnflags = 64 // allow everything }); EntFireByHandle(particle, "StartTouch", "!activator", -1, projectile, projectile); EntFireByHandle(particle, "Kill", "", -1, null, null); projectile.AddEFlags(EFL_NO_ROTORWASH_PUSH) } } } popext_customweaponmodel = function(bot, args) { bot.GetActiveWeapon().SetModelSimple(args[0]) } popext_spawnhere = function(bot, args) { local org = split(args[0], " ") bot.Teleport(true, Vector(org[0].tofloat(), org[1].tofloat(), org[2].tofloat()), true, QAngle(), true, bot.GetAbsVelocity()) if (args.len() < 2) return local spawnubertime = args[1].tofloat() bot.AddCondEx(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED, spawnubertime, null) } popext_improvedairblast = function (bot, args) { bot.GetScriptScope().PlayerThinkTable.ImprovedAirblastThink <- function() { for (local projectile; projectile = FindByClassname(projectile, "tf_projectile_*");) { if (projectile.GetTeam() == bot.GetTeam() || !Homing.IsValidProjectile(projectile, PopExtUtil.DeflectableProjectiles)) continue if (GetThreatDistanceSqr(projectile) <= 67000 && IsVisible(projectile)) { switch (botLevel) { case 1: // Basic Airblast, only deflect if in FOV if (!IsInFieldOfView(projectile)) return break case 2: // Advanced Airblast, deflect regardless of FOV LookAt(projectile.GetOrigin(), INT_MAX, INT_MAX) break case 3: // Expert Airblast, deflect regardless of FOV back to Sender local owner = projectile.GetOwner() if (owner != null) { local owner_head = owner.GetAttachmentOrigin(owner.LookupAttachment("head")) LookAt(owner_head, INT_MAX, INT_MAX) } break } bot.PressAltFireButton(0.0) } } } } /* valid attachment points for most playermodels: - head - eyes - righteye/lefteye - foot_L/_R - back_upper/lower - hand_L/R - partyhat - doublejumpfx (scout) - eyeglow_L/R - weapon_bone - weapon_bone_2/3/4 - effect_hand_R - flag - prop_bone - prop_bone_1/2/3/4/5/6 */ popext_aimat = function(bot, args) { bot.GetScriptScope().PlayerThinkTable.AimAtThink <- function() { foreach (player in PopExtUtil.HumanArray) { if (bot.IsInFieldOfView(player)) { LookAt(player.GetAttachmentOrigin(player.LookupAttachment(args[0]))) break } } } } popext_addcondonhit = function(bot, args) { // Tag addcondonhit |cond|duration|threshold|crit // Leave Duration blank for infinite duration // Leave Threshold blank to apply effect on any hit local args_len = args.len() local cond = args[0].tointeger() local duration = (args_len >= 2) ? args[1].tofloat() : -1.0 local dmgthreshold = (args_len >= 3) ? args[2].tofloat() : 0.0 local critOnly = (args_len >= 4) ? args[3].tointeger() : 0 // Add the new variables to the bot's scope local bot_scope = bot.GetScriptScope() bot_scope.CondOnHit = true bot_scope.CondOnHitVal = cond bot_scope.CondOnHitDur = duration bot_scope.CondOnHitDmgThres = dmgthreshold bot_scope.CondOnHitCritOnly = critOnly PopExtTags.TakeDamageTable.AddCondOnHitTakeDamage <- function(params) { if (!params.const_entity.IsPlayer()) return local victim = params.const_entity local attacker = params.attacker if (attacker != null && victim != attacker) { local attacker_scope = attacker.GetScriptScope() if (!attacker_scope.CondOnHit) return local hurt_damage = params.damage local victim_health = victim.GetHealth() - hurt_damage local isCrit = params.crit if (victim_health <= 0) return if (attacker_scope.CondOnHitCritOnly == 1 && !isCrit) return if ((attacker_scope.CondOnHitCritOnly == 1 && isCrit) || (attacker_scope.CondOnHitDmgThres == 0.0 || hurt_damage >= attacker_scope.CondOnHitDmgThres)) victim.AddCondEx(attacker_scope.CondOnHitVal, attacker_scope.CondOnHitDur, null) } } } popext_dropweapon = function(bot, args) { bot.GetScriptScope().DeathHookTable.DropWeaponDeath <- function(params) { printl("dropping weapon") local slot = (args.len() > 0) ? args[0].tointeger() : -1 local wep = (slot == -1) ? bot.GetActiveWeapon() : PopExtUtil.GetItemInSlot(bot, slot) if (wep == null) return local itemid = PopExtUtil.GetItemIndex(wep) local wearable = CreateByClassname("tf_wearable") SetPropBool(wearable, "m_AttributeManager.m_Item.m_bInitialized", true) SetPropInt(wearable, STRING_NETPROP_ITEMDEF, itemid) wearable.DispatchSpawn(); local modelname = wearable.GetModelName() wearable.Destroy() local droppedweapon = CreateByClassname("tf_dropped_weapon") SetPropInt(droppedweapon, "m_Item.m_iItemDefinitionIndex", itemid) SetPropInt(droppedweapon, "m_Item.m_iEntityLevel", 5) SetPropInt(droppedweapon, "m_Item.m_iEntityQuality", 6) SetPropBool(droppedweapon, "m_Item.m_bInitialized", true) droppedweapon.SetModelSimple(modelname) droppedweapon.SetOrigin(bot.GetOrigin()) droppedweapon.DispatchSpawn() // Store attributes in scope, when it gets picked up add the attributes to the real weapon } } } ::Homing <- { // Modify the AttachProjectileThinker function to accept projectile speed adjustment if needed function AttachProjectileThinker(projectile, speed_mult, turn_power, ignoreDisguisedSpies = true, ignoreStealthedSpies = true) { projectile.ValidateScriptScope() local projectile_scope = projectile.GetScriptScope() if (!("speedmultiplied" in projectile_scope)) projectile_scope.speedmultiplied <- false local projectile_speed = projectile.GetAbsVelocity().Norm() if (!projectile_scope.speedmultiplied) { projectile_speed *= speed_mult projectile_scope.speedmultiplied = true } // printl("speed: " + projectile_speed) projectile_scope.turn_power <- turn_power projectile_scope.projectile_speed <- projectile_speed projectile_scope.ignoreDisguisedSpies <- ignoreDisguisedSpies projectile_scope.ignoreStealthedSpies <- ignoreStealthedSpies //this should be added in globalfixes.nut but sometimes this code tries to run before the table is created if (!("ProjectileThinkTable" in projectile_scope)) projectile_scope.ProjectileThinkTable <- {} projectile_scope.ProjectileThinkTable.HomingProjectileThink <- Homing.HomingProjectileThink } function HomingProjectileThink() { local new_target = Homing.SelectVictim(self) if (new_target != null && Homing.IsLookingAt(self, new_target)) Homing.FaceTowards(new_target, self, projectile_speed) } function SelectVictim(projectile) { local target local min_distance = 32768.0 foreach (player in PopExtUtil.HumanArray) { local distance = (projectile.GetOrigin() - player.GetOrigin()).Length() if (IsValidTarget(player, distance, min_distance, projectile)) { target = player min_distance = distance } } return target } function IsValidTarget(victim, distance, min_distance, projectile) { local projectile_scope = projectile.GetScriptScope() // Early out if basic conditions aren't met if (distance > min_distance || victim.GetTeam() == projectile.GetTeam() || !PopExtUtil.IsAlive(victim)) { return false } // Check for conditions based on the projectile's configuration if (victim.IsPlayer()) { if (victim.InCond(TF_COND_HALLOWEEN_GHOST_MODE)) { return false } // Check for stealth and disguise conditions if not ignored if (!projectile_scope.ignoreStealthedSpies && (victim.IsStealthed() || victim.IsFullyInvisible())) { return false } if (!projectile_scope.ignoreDisguisedSpies && victim.GetDisguiseTarget() != null) { return false } } return true } function FaceTowards(new_target, projectile, projectile_speed) { local scope = projectile.GetScriptScope() local desired_dir = new_target.EyePosition() - projectile.GetOrigin() desired_dir.Norm() local current_dir = projectile.GetForwardVector() local new_dir = current_dir + (desired_dir - current_dir) * scope.turn_power // printl("Dir: " + new_dir) new_dir.Norm() local move_ang = PopExtUtil.VectorAngles(new_dir) local projectile_velocity = move_ang.Forward() * projectile_speed projectile.SetAbsVelocity(projectile_velocity) projectile.SetLocalAngles(move_ang) } function IsLookingAt(projectile, new_target) { local target_origin = new_target.GetOrigin() local projectile_owner = projectile.GetOwner() local projectile_owner_pos = projectile_owner.EyePosition() if (TraceLine(projectile_owner_pos, target_origin, projectile_owner)) { local direction = (target_origin - projectile_owner.EyePosition()) direction.Norm() local product = projectile_owner.EyeAngles().Forward().Dot(direction) if (product > 0.6) return true } return false } function IsValidProjectile(projectile, table) { if (projectile.GetClassname() in table) return true return false } } // ::GetBotBehaviorFromTags <- function(bot) // { // local tags = {} // local scope = bot.GetScriptScope() // bot.GetAllBotTags(tags) // if (tags.len() == 0) return // foreach (tag in tags) // { // local args = split(tag, "|") // if (args.len() == 0) continue // local func = args.remove(0) // if (func in popext_funcs) // popext_funcs[func](bot, args) // } // function PopExt_BotThinks() // { // local scope = self.GetScriptScope() // if (scope.PlayerThinkTable.len() < 1) return // foreach (_, func in scope.PlayerThinkTable) // func(self) // } // AddThinkToEnt(bot, "PopExt_BotThinks") // } ::PopExtTags <- { TakeDamageTable = {} DeathHookTable = {} TeamSwitchTable = {} function AI_BotSpawn(bot) { local scope = bot.GetScriptScope() scope.bot <- AI_Bot(bot) foreach(tag in __tagarray) { if (bot.HasBotTag(tag)) { local args = split(tag, "|") local func = args.remove(0) if (func in popext_funcs) popext_funcs[func](bot, args) } } //bot.AddBotAttribute(1024) // IGNORE_ENEMIES } function BotThink() { bot.OnUpdate() return -1 } function OnScriptHook_OnTakeDamage(params) { local scope = params.attacker.GetScriptScope() foreach (_, func in PopExtTags.TakeDamageTable) { func(params) } } function OnGameEvent_player_team(params) { local bot = GetPlayerFromUserID(params.userid) if (params.team == TEAM_SPECTATOR) AddThinkToEnt(bot, null) foreach (_, func in PopExtTags.TeamSwitchTable) func(params) } function OnGameEvent_player_death(params) { local bot = GetPlayerFromUserID(params.userid) if (!bot.IsBotOfType(1337)) return local scope = bot.GetScriptScope() bot.ClearAllBotTags() foreach (_, func in PopExtTags.DeathHookTable) func(params) AddThinkToEnt(bot, null) } function OnGameEvent_teamplay_round_start(params) { foreach (bot in PopExtUtil.BotArray) if (bot.GetTeam() == TF_TEAM_PVE_DEFENDERS) bot.ForceChangeTeam(TEAM_SPECTATOR, true) } } __CollectGameEventCallbacks(PopExtTags)