local PlayersWithCallbacks = {} local function removeCallbacks(player, callbacks) if not IsValid(player) then return end for _, callbackId in pairs(callbacks) do player:RemoveCallback(callbackId) end end local function getEyeAngles(player) local pitch = player["m_angEyeAngles[0]"] local yaw = player["m_angEyeAngles[1]"] return Vector(pitch, yaw, 0) end local BOTS_VARIANTS = { scout = { Display = "Raptor", Class = "Scout", Model = "models/player/scout.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 175, SummonCount = 1, Items = { "Wild Whip", "Remorseless Raptor", "Unarmed Combat", "Fowl Fists" }, DefaultAttributes = { ["Move speed bonus"] = 1.15, ["Fire rate bonus"] = 0.6, ["damage penalty"] = 0.7142, }, Tiers = { [1] = { Display = "Raptor", Class = "Scout", Model = "models/player/scout.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 175, SummonCount = 1, Items = { "Wild Whip", "Remorseless Raptor", "Unarmed Combat", "Fowl Fists" }, Attributes = { ["Move speed bonus"] = 1.15, ["Fire rate bonus"] = 0.6, ["damage penalty"] = 0.7142, }, }, [2] = { Display = "Raptor", Class = "Scout", Model = "models/player/scout.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 175, SummonCount = 1, Items = { "Wild Whip", "Remorseless Raptor", "Unarmed Combat", "Fowl Fists" }, Attributes = { ["Move speed bonus"] = 1.15, ["Fire rate bonus"] = 0.6, ["damage penalty"] = 1, ["add cond on hit"] = 30, ["add cond on hit duration"] = 0.25, }, }, [3] = { Display = "Raptor", Class = "Scout", Model = "models/player/scout.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 175, SummonCount = 2, Items = { "Wild Whip", "Remorseless Raptor", "Unarmed Combat", "Fowl Fists" }, Attributes = { ["Move speed bonus"] = 1.15, ["Fire rate bonus"] = 0.6, ["damage penalty"] = 1, ["add cond on hit"] = 30, ["add cond on hit duration"] = 0.25, }, }, [4] = { Display = "Raptor", Class = "Scout", Model = "models/player/scout.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 275, SummonCount = 2, Items = { "Wild Whip", "Remorseless Raptor", "Unarmed Combat", "Fowl Fists" }, Attributes = { ["Move speed bonus"] = 1.3, ["Fire rate bonus"] = 0.6, ["damage penalty"] = 1, ["mult crit dmg"] = 1.5, ["add cond on hit"] = 30, ["add cond on hit duration"] = 0.25, }, }, [5] = { Display = "Raptor", Class = "Scout", Model = "models/player/scout.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 275, SummonCount = 3, Items = { "Wild Whip", "Remorseless Raptor", "Unarmed Combat", "Fowl Fists" }, Attributes = { ["Move speed bonus"] = 1.3, ["Fire rate bonus"] = 0.6, ["damage penalty"] = 1, ["mult crit dmg"] = 1.5, ["add cond on hit"] = 30, ["add cond on hit duration"] = 0.25, }, }, }, }, soldier = { Display = "Raccoon", Class = "Soldier", Model = "models/player/soldier.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 300, SummonCount = 1, Items = { "Racc Mann", "Loaf Loafers", "Bone-Cut Belt", "Bread Biter" }, ItemAttributes = { ["Racc Mann"] = { ["set item tint RGB"] = 8519682, }, ["TF_WEAPON_SHOVEL"] = { ["is invisible"] = 1 }, }, DefaultAttributes = { ["crit mod disabled"] = 0, ["Bleeding duration"] = 4, ["mult bleeding dmg"] = 2, ["Move speed bonus"] = 1.25, }, Tiers = { [1] = { Display = "Raccoon", Class = "Soldier", Model = "models/player/soldier.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 300, SummonCount = 1, Items = { "Racc Mann", "Loaf Loafers", "Bone-Cut Belt", "Bread Biter" }, ItemAttributes = { ["Racc Mann"] = { ["set item tint RGB"] = 8519682, }, ["TF_WEAPON_SHOVEL"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["Bleeding duration"] = 4, ["mult bleeding dmg"] = 2, ["Move speed bonus"] = 1.25, }, }, [2] = { Display = "Raccoon", Class = "Soldier", Model = "models/player/soldier.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 300, SummonCount = 1, Items = { "Racc Mann", "Loaf Loafers", "Bone-Cut Belt", "Bread Biter" }, ItemAttributes = { ["Racc Mann"] = { ["set item tint RGB"] = 8519682, }, ["TF_WEAPON_SHOVEL"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["Bleeding duration"] = 4, ["mult bleeding dmg"] = 2, ["mult bleeding delay"] = 0.5, ["Move speed bonus"] = 1.25, }, }, [3] = { Display = "Raccoon", Class = "Soldier", Model = "models/player/soldier.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 300, SummonCount = 1, Items = { "Racc Mann", "Loaf Loafers", "Bone-Cut Belt", "Bread Biter", "Warrior's Spirit" }, ItemAttributes = { ["Racc Mann"] = { ["set item tint RGB"] = 8519682, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, }, Attributes = { ["crit mod disabled"] = 0, ["Bleeding duration"] = 4, ["mult bleeding dmg"] = 3, ["mult bleeding delay"] = 0.5, ["Move speed bonus"] = 1.25, ["fire rate bonus"] = 0.7, }, }, [4] = { Display = "Raccoon", Class = "Soldier", Model = "models/player/soldier.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 400, SummonCount = 1, Items = { "Racc Mann", "Loaf Loafers", "Bone-Cut Belt", "Bread Biter", "Warrior's Spirit", "Exquisite Rack" }, ItemAttributes = { ["Racc Mann"] = { ["set item tint RGB"] = 8519682, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, }, Attributes = { ["crit mod disabled"] = 0, ["Bleeding duration"] = 4, ["mult bleeding dmg"] = 3, ["mult bleeding delay"] = 0.5, ["Move speed bonus"] = 1.34, ["health regen"] = 35, ["fire rate bonus"] = 0.7, }, }, [5] = { Display = "Raccoon", Class = "Soldier", Model = "models/player/soldier.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 400, SummonCount = 1, Items = { "Racc Mann", "Loaf Loafers", "Bone-Cut Belt", "Bread Biter", "The Bread Bite", "Exquisite Rack" }, ItemAttributes = { ["Racc Mann"] = { ["set item tint RGB"] = 8519682, }, ["The Bread Bite"] = { ["mod_maxhealth_drain_rate"] = 0, ["mult_player_movespeed_active"] = 1, ["damage bonus"] = 1.3, }, }, Attributes = { ["crit mod disabled"] = 0, ["Bleeding duration"] = 4, ["mult bleeding dmg"] = 4, ["mult bleeding delay"] = 0.33, ["Move speed bonus"] = 1.34, ["health regen"] = 35, ["fire rate bonus"] = 0.7, }, }, }, }, heavyweapons = { Display = "Bear", Class = "Heavyweapons", Model = "models/player/heavy.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 700, SummonCount = 1, Items = { "Yeti_Arms", "Yeti_Legs", "Warrior's Spirit", "Misha's Maw", "The Chargin' Targe" }, ItemAttributes = { ["Misha's Maw"] = { ["item style override"] = 1, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, ["The Chargin' Targe"] = { ["is invisible"] = 1, ["charge time increased"] = 0.5, ["mult charging move speed"] = 0.6, }, }, DefaultAttributes = { ["crit mod disabled"] = 0, ["move speed bonus"] = 0.8695, ["damage bonus"] = 3, ["fire rate penalty"] = 1.8, ["attack not cancel charge"] = 1, }, Tiers = { [1] = { Display = "Bear", Class = "Heavyweapons", Model = "models/player/heavy.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 700, SummonCount = 1, Items = { "Yeti_Arms", "Yeti_Legs", "Warrior's Spirit", "Misha's Maw", "The Chargin' Targe" }, ItemAttributes = { ["Misha's Maw"] = { ["item style override"] = 1, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, ["The Chargin' Targe"] = { ["is invisible"] = 1, ["charge time increased"] = 0.5, ["mult charging move speed"] = 0.6, }, }, Attributes = { ["crit mod disabled"] = 0, ["move speed bonus"] = 0.8695, ["damage bonus"] = 3, ["fire rate penalty"] = 1.8, ["attack not cancel charge"] = 1, }, }, [2] = { Display = "Bear", Class = "Heavyweapons", Model = "models/player/heavy.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 950, SummonCount = 1, Items = { "Yeti_Arms", "Yeti_Legs", "Warrior's Spirit", "Misha's Maw", "The Chargin' Targe" }, ItemAttributes = { ["Misha's Maw"] = { ["item style override"] = 1, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, ["The Chargin' Targe"] = { ["is invisible"] = 1, ["charge time increased"] = 0.5, ["mult charging move speed"] = 0.6, }, }, Attributes = { ["crit mod disabled"] = 0, ["move speed bonus"] = 0.8695, ["damage bonus"] = 5.25, ["fire rate penalty"] = 1.8, ["attack not cancel charge"] = 1, --["damage penalty"] = 1.5, }, }, [3] = { Display = "Bear", Class = "Heavyweapons", Model = "models/player/heavy.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 950, SummonCount = 1, Items = { "Yeti_Arms", "Yeti_Legs", "Warrior's Spirit", "Misha's Maw", "The Chargin' Targe" }, ItemAttributes = { ["Misha's Maw"] = { ["item style override"] = 1, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, ["The Chargin' Targe"] = { ["is invisible"] = 1, ["charge time increased"] = 0.5, ["mult charging move speed"] = 0.6, ["charge impact damage increased"] = 3, }, }, Attributes = { ["crit mod disabled"] = 0, ["move speed bonus"] = 0.8695, ["damage bonus"] = 5.25, ["fire rate penalty"] = 1.4, ["attack not cancel charge"] = 1, --["damage penalty"] = 1.5, }, }, [4] = { Display = "Bear", Class = "Heavyweapons", Model = "models/player/heavy.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 950, SummonCount = 1, Items = { "Yeti_Arms", "Yeti_Legs", "Warrior's Spirit", "Misha's Maw", "The Chargin' Targe" }, ItemAttributes = { ["Misha's Maw"] = { ["item style override"] = 1, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, ["The Chargin' Targe"] = { ["is invisible"] = 1, ["charge time increased"] = 0.5, ["mult charging move speed"] = 0.8, ["charge impact damage increased"] = 3, }, }, Attributes = { ["crit mod disabled"] = 0, ["move speed bonus"] = 1, ["damage bonus"] = 6.58, ["fire rate penalty"] = 1.4, ["attack not cancel charge"] = 1, --["damage penalty"] = 1.88, }, }, [5] = { Display = "Bear", Class = "Heavyweapons", Model = "models/player/heavy.mdl", Action = "Mobber", Scale = 1, HealthIncrease = 1300, SummonCount = 1, Items = { "Yeti_Arms", "Yeti_Legs", "Warrior's Spirit", "Misha's Maw", "The Chargin' Targe" }, ItemAttributes = { ["Misha's Maw"] = { ["item style override"] = 1, }, ["Warrior's Spirit"] = { ["dmg taken increased"] = 1, ["heal on kill"] = 0, }, ["The Chargin' Targe"] = { ["is invisible"] = 1, ["charge time increased"] = 0.5, ["mult charging move speed"] = 0.8, ["charge impact damage increased"] = 3, }, }, Attributes = { ["crit mod disabled"] = 0, ["move speed bonus"] = 1, ["damage bonus"] = 6.58, ["fire rate penalty"] = 1.4, ["attack not cancel charge"] = 1, --["damage penalty"] = 1.88, ["minicritboost on kill"] = 3, }, }, }, }, sniper = { Display = "Crocodile", Class = "Sniper", Model = "models/player/sniper.mdl", Action = "Passive", Scale = 1, HealthIncrease = 325, SummonCount = 1, Conds = { TF_COND_STEALTHED_USER_BUFF }, Items = { "Crocodile Mun-Dee", "Crocodile Dandy", "Scopers Scales" }, ItemAttributes = { ["TF_WEAPON_CLUB"] = { ["is invisible"] = 1, }, }, DefaultAttributes = { ["crit mod disabled"] = 0, ["dmg from melee increased"] = 0.75, ["dmg taken from crit reduced"] = 0.5, ["damage bonus"] = 5, ["fire rate penalty"] = 3, ["move speed penalty"] = 0.65, }, Tiers = { [1] = { Display = "Crocodile", Class = "Sniper", Model = "models/player/sniper.mdl", Action = "Passive", Scale = 1, HealthIncrease = 325, SummonCount = 1, Conds = { TF_COND_STEALTHED_USER_BUFF }, Items = { "Crocodile Mun-Dee", "Crocodile Dandy", "Scopers Scales" }, ItemAttributes = { ["TF_WEAPON_CLUB"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["dmg from melee increased"] = 0.75, ["dmg taken from crit reduced"] = 0.5, ["damage bonus"] = 5, ["fire rate penalty"] = 3, ["move speed penalty"] = 0.65, }, }, [2] = { Display = "Crocodile", Class = "Sniper", Model = "models/player/sniper.mdl", Action = "Passive", Scale = 1, HealthIncrease = 325, SummonCount = 1, CloakAfterAttack = true, Conds = { TF_COND_STEALTHED_USER_BUFF }, Items = { "Crocodile Mun-Dee", "Crocodile Dandy", "Scopers Scales" }, ItemAttributes = { ["TF_WEAPON_CLUB"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["dmg from melee increased"] = 0.75, ["dmg taken from crit reduced"] = 0.5, ["damage bonus"] = 5, ["fire rate penalty"] = 3, ["move speed penalty"] = 0.65, }, }, [3] = { Display = "Crocodile", Class = "Sniper", Model = "models/player/sniper.mdl", Action = "Passive", Scale = 1, HealthIncrease = 325, SummonCount = 1, CloakAfterAttack = true, Conds = { TF_COND_STEALTHED_USER_BUFF }, Items = { "Crocodile Mun-Dee", "Crocodile Dandy", "Scopers Scales", "Darwin's Danger Shield" }, ItemAttributes = { ["TF_WEAPON_CLUB"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["dmg from ranged reduced"] = 0.75, ["dmg from melee increased"] = 0.5, ["dmg taken from crit reduced"] = 0.5, ["damage bonus"] = 5, ["fire rate penalty"] = 3, ["move speed penalty"] = 0.65, }, }, [4] = { Display = "Crocodile", Class = "Sniper", Model = "models/player/sniper.mdl", Action = "Passive", Scale = 1, HealthIncrease = 325, SummonCount = 1, CloakAfterAttack = true, Conds = { TF_COND_STEALTHED_USER_BUFF }, Items = { "Crocodile Mun-Dee", "Crocodile Dandy", "Scopers Scales", "Darwin's Danger Shield" }, ItemAttributes = { ["TF_WEAPON_CLUB"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["dmg from ranged reduced"] = 0.75, ["dmg from melee increased"] = 0.5, ["dmg taken from crit reduced"] = 0.5, ["melee cleave attack"] = 1, ["damage bonus"] = 5, ["fire rate penalty"] = 2.5, ["move speed penalty"] = 0.65, }, }, [5] = { Display = "Crocodile", Class = "Sniper", Model = "models/player/sniper.mdl", Action = "Passive", Scale = 1, HealthIncrease = 325, SummonCount = 1, CloakAfterAttack = true, Conds = { TF_COND_STEALTHED_USER_BUFF }, Items = { "Crocodile Mun-Dee", "Crocodile Dandy", "Scopers Scales", "Darwin's Danger Shield" }, ItemAttributes = { ["TF_WEAPON_CLUB"] = { ["is invisible"] = 1, }, }, Attributes = { ["crit mod disabled"] = 0, ["dmg from ranged reduced"] = 0.75, ["dmg from melee increased"] = 0.5, ["dmg taken from crit reduced"] = 0.5, ["melee cleave attack"] = 1, ["damage bonus"] = 5, ["fire rate penalty"] = 2.5, ["move speed penalty"] = 0.65, ["melee range multiplier"] = 2, ["melee bounds multiplier"] = 1.75, ["fire input on hit"] = "!self^$Slowdown^0.65|2", }, }, }, }, } local SPEED_BOOST_DISTANCE = 400 local BOTS_ATTRIBUTES = { ["collect currency on kill"] = 1, ["mult charge turn control"] = 0.0001337, } local REPLICATE_CONDS = { TF_COND_CRITBOOSTED_USER_BUFF, TF_COND_INVULNERABLE_USER_BUFF, TF_COND_MINICRITBOOSTED_ON_KILL, TF_COND_OFFENSEBUFF, TF_COND_ENERGY_BUFF, } local SENTRY_FIRERATE_REPLICATE_ATTR = "halloween fire rate bonus" local SENTRY_FIRERATE_REPLICATE_MULT = 1 local SENTRY_DAMAGE_REPLICATE_ATTR = "CARD: damage bonus" local SENTRY_DAMAGE_REPLICATE_MULT = 1 local BOT_SETUP_VSCRIPT = "activator.SetDifficulty(3); activator.SetMaxVisionRangeOverride(100000); activator.AddBotAttribute(16)" local BOT_DISABLE_VISION_VSCRIPT = "activator.SetDifficulty(0); activator.SetMaxVisionRangeOverride(0.1)" local BOT_ENABLE_VISION_VSCRIPT = "activator.SetDifficulty(3); activator.SetMaxVisionRangeOverride(100000)" local BOT_SET_WEPRESTRICTION_VSCRIPT = "activator.AddWeaponRestriction(2)" local BOT_CLEAR_RESTRICTIONS_VSCRIPT = "activator.ClearAllWeaponRestrictions()" --additional functionality local BOT_HOLD_FIRE = "activator.AddBotAttribute(2048)" local BOT_ALWAYS_FIRE = "activator.AddBotAttribute(8192)" local BOT_SPAWN_FULL_CHARGE = "activator.AddBotAttribute(256)" local BOT_ATTACK_VSCRIPT = "activator.PressFireButton(0.01)" --made shorter local BOT_CHARGE_VSCRIPT = "activator.PressAltFireButton(0.1)" local activeBots = {} -- bots alive local activeBuiltBots = {} -- bot built by player local activeBuiltBotsOwner = {} local lingeringBuiltBots = {} local inWave = false local PACK_ITEMS = { "item_currencypack_small", "item_currencypack_medium", "item_currencypack_large", "item_currencypack_custom", } -- delete cash dropped by bots that were built by players -- due to inheriting TFBot's currency count local function removeCashSafe(pack) pack:SetAbsOrigin(Vector(0, -100000, 0)) local objectiveResource = ents.FindByClass("tf_objective_resource") local moneyBefore = objectiveResource.m_nMvMWorldMoney pack:Remove() local moneyAfter = objectiveResource.m_nMvMWorldMoney local packPrice = moneyBefore - moneyAfter -- print("price: "..tostring(packPrice)) local mvmStats = ents.FindByClass("tf_mann_vs_machine_stats") local vscript = 'NetProps.SetPropInt(activator, "%s.nCreditsDropped", NetProps.GetPropInt(activator, "%s.nCreditsDropped") - %s)' local curWave = "m_currentWaveStats" mvmStats:RunScriptCode(vscript:format(curWave, curWave, packPrice), mvmStats, mvmStats) end for _, packName in pairs(PACK_ITEMS) do ents.AddCreateCallback(packName, function(pack) local disablePickUp = pack:AddCallback(ON_SHOULD_COLLIDE, function() return false end) timer.Simple(0, function() -- failsafe for a glitch where spamming rebuild can very rarely drop cash if not inWave then removeCashSafe(pack) return end local handle = pack.m_hOwnerEntity:GetHandleIndex() if not lingeringBuiltBots[handle] then pack:RemoveCallback(disablePickUp) return end removeCashSafe(pack) lingeringBuiltBots[handle] = nil end) end) end function OnWaveInit() inWave = false for _, bot in pairs(ents.GetAllPlayers()) do if bot:IsBot() and bot.SummonedBot then bot:Suicide() bot.m_iTeamNum = 1 local botHandle = bot:GetHandleIndex() for name, _ in pairs(bot:GetAllAttributeValues()) do bot:SetAttributeValue(name, nil) end activeBots[botHandle] = nil activeBuiltBotsOwner[botHandle] = nil bot:ResetFakeSendProp("m_iTeamNum") bot.m_iTeamNum = 1 bot.m_bGlowEnabled = 0 bot.SummonedBot = false pcall( timer.Stop, bot.logicLoop ) end end activeBuiltBots = {} end function OnWaveStart() inWave = true -- bots spawned in prewave are put to spectate/gray team so they don't take up slot for _, bot in pairs(ents.GetAllPlayers()) do if bot:IsBot() and bot.SummonedBot then bot:ResetFakeSendProp("m_iTeamNum") bot.m_iTeamNum = 2 bot:SetFakeSendProp("m_iTeamNum", 2) bot.m_iTeamNum = 2 bot:RunScriptCode( "self.ForceChangeTeam( 2, true )", bot ) -- bot:RemoveCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) bot:SetAttributeValue("not solid to players", nil) bot:SetAttributeValue("ignored by enemy sentries", nil) bot:SetAttributeValue("ignored by bots", nil) bot:SetAttributeValue("damage bonus HIDDEN", nil) end end end local function checkOnHit(parent, damageinfo) local attacker = damageinfo.Attacker if not attacker then return end local handle = attacker:GetHandleIndex() local owner = activeBuiltBotsOwner[handle] if not owner then return end if parent == owner then return end damageinfo.Attacker = owner return true end ents.AddCreateCallback("tank_boss", function(tank) tank:AddCallback(ON_DAMAGE_RECEIVED_PRE, function(_, damageinfo) return checkOnHit(tank, damageinfo) end) end) -- convert damage dealt by bots to owner -- and nullify damage taken by built bot during prewave local function addGlobalCallbacks(player) player:AddCallback(ON_DAMAGE_RECEIVED_PRE, function(_, damageinfo) local isBot = activeBots[player:GetHandleIndex()] if isBot and not inWave then -- nullify attacks in prewave damageinfo.Damage = 0 damageinfo.Inflictor = nil damageinfo.Weapon = nil return true end return checkOnHit(player, damageinfo) end) -- failsafe for spawning bot in setup player:AddCallback(ON_SPAWN, function() if player:IsRealPlayer() then return end timer.Simple(0.1, function() if inWave then return end local owner = activeBuiltBots[player:GetHandleIndex()] if not owner then return end if player.m_iTeamNum == owner.m_iTeamNum then player:Suicide() end end) end) end AddEventCallback("player_spawn", function(eventTable) --print("Added callbacks") local spawnedPlayer = ents.GetPlayerByUserId(eventTable.userid) --print(spawnedPlayer) local revisedTable = {} local spawnedPlayerInTable = false for _, player in pairs(PlayersWithCallbacks) do --if IsValid(player) then -- table.insert(revisedTable, player) --end if player == spawnedPlayer then spawnedPlayerInTable = true end end --if #revisedTable ~= #PlayersWithCallbacks then -- PlayersWithCallbacks = {} -- print("A player was invalid, rebuilding") -- for _, player in pairs(revisedTable) do -- table.insert(PlayersWithCallbacks, player) -- end --end if spawnedPlayerInTable == false then -- print("Added spawning player to table and applied callbacks") addGlobalCallbacks(spawnedPlayer) table.insert(PlayersWithCallbacks, spawnedPlayer) end end) local function applyName(bot, name, owner) local displayName = name .. " (" .. owner.m_szNetname .. ")" bot.m_szNetname = displayName bot:SetFakeClientConVar("name", displayName) end local function applyUniversalData(bot, data) if data.Model then bot:SetCustomModelWithClassAnimations(data.Model) end if data.Scale then local vscript = ("activator.SetScaleOverride(%s)"):format(tostring(data.Scale)) bot:RunScriptCode(vscript, bot) end --apply the correct bitflags when these are set to true within a bot template if data.HoldFireUntilFullReload then bot:RunScriptCode(BOT_HOLD_FIRE, bot) end if data.SpawnWithFullCharge then bot:RunScriptCode(BOT_SPAWN_FULL_CHARGE, bot) end if data.AlwaysFireWeapon then bot:RunScriptCode(BOT_ALWAYS_FIRE, bot) end if data.Conds then for _, id in pairs(data.Conds) do bot:AddCond(id) end end if data.Items then for _, itemName in pairs(data.Items) do bot:GiveItem(itemName) end end if data.ItemAttributes then for item, attributes in pairs(data.ItemAttributes) do local weapon = bot:GetPlayerItemByName( item ) --print(item, weapon) if weapon then for name, value in pairs(attributes) do weapon:SetAttributeValue(name, value) --print("added " .. name, value) end end end end if data.Attributes then for name, value in pairs(data.Attributes) do bot:SetAttributeValue(name, value) end end if data.MiniBoss then bot.m_bIsMiniBoss = 1 else bot.m_bIsMiniBoss = 0 end if data.Display then applyName(bot, data.Display, activeBuiltBotsOwner[bot:GetHandleIndex()]) end end local function applyMaxHealthUpgrade(owner, bot) local secondary = owner:GetPlayerItemBySlot(LOADOUT_POSITION_SECONDARY) local healthBonusMult = secondary:GetAttributeValue("engy building health bonus") or 1 -- each upgrade gives extra health based on spawned health local extraHealth = bot.m_iMaxHealth * (healthBonusMult - 1) local curMaxHealthBuff = bot:GetAttributeValue("hidden maxhealth non buffed") or 0 bot:SetAttributeValue("hidden maxhealth non buffed", curMaxHealthBuff + extraHealth) end local function applyDefaultData(owner, bot, class) -- print( class ) -- PrintTable( BOTS_VARIANTS ) local data = BOTS_VARIANTS[class] if data.DefaultAttributes then for name, value in pairs(data.DefaultAttributes) do bot:SetAttributeValue(name, value) end end bot:SwitchClassInPlace(data.Class) -- remove potential lingering health --out of place if statement here to correct a bug where this lingering health remover makes it impossible to add added health to default templates, --which is an issue that didn't manifest in any other version of the script due to all default templates being regular classes bot:SetAttributeValue("hidden maxhealth non buffed", nil) if bot:GetAttributeValue("hidden maxhealth non buffed") ~= data.HealthIncrease then bot:SetAttributeValue("hidden maxhealth non buffed", data.HealthIncrease) end local str = "switch_action %s" bot:BotCommand( str:format( data.Action ) ) applyUniversalData(bot, data) applyMaxHealthUpgrade(owner, bot) end local function applyTierData(bot, data) applyUniversalData(bot, data) if data.HealthIncrease then bot:SetAttributeValue("hidden maxhealth non buffed", data.HealthIncrease) end if data.Conds then for _, id in pairs(data.Conds) do print( id ) bot:AddCond(id) end end bot:RefillAmmo() end local function removePreviousTier(bot, class, previousTier) local data = BOTS_VARIANTS[class].Tiers[previousTier] if data.Conds then for _, id in pairs(data.Conds) do bot:RemoveCond(id) end end if data.Items then for _, itemName in pairs(data.Items) do bot:RemoveItem(itemName) end end if data.Attributes then for name, _ in pairs(data.Attributes) do bot:SetAttributeValue(name, nil) end end end local function getCurBotTier(owner) --these functions are brilliant, you effectively use useless attributes as variables for your script return owner:GetPlayerItemBySlot(1):GetAttributeValue("throwable fire speed") or 1 end local function applyBotTier(owner, bot, class, tier) if tier > 0 then removePreviousTier(bot, class, tier - 1) end local tierData = BOTS_VARIANTS[class].Tiers[tier] applyTierData(bot, tierData) applyMaxHealthUpgrade(owner, bot) end local function setupBot(bot, owner, handle, steak) local callbacks = {} local botHandle = bot:GetHandleIndex() applyName(bot, "", owner) if IsValid(steak) == false then bot:Suicide() end bot.m_iTeamNum = owner.m_iTeamNum bot.m_iszClassIcon = "" -- don't remove from wave on death for name, value in pairs(BOTS_ATTRIBUTES) do bot:SetAttributeValue(name, value) end owner.BuiltBotHandle = tostring(botHandle) activeBots[botHandle] = true activeBuiltBots[handle] = bot lingeringBuiltBots[botHandle] = true activeBuiltBotsOwner[botHandle] = owner -- callbacks.shouldCollide = bot:AddCallback(ON_SHOULD_COLLIDE, function(other, cause) -- if cause == ON_SHOULD_COLLIDE_CAUSE_FIRE_WEAPON then -- return -- end -- if not other:IsPlayer() then -- return -- end -- return false -- end) callbacks.died = bot:AddCallback(ON_DEATH, function() -- attributes applied to bot spawned through script are not cleared automatically on death for name, _ in pairs(bot:GetAllAttributeValues()) do bot:SetAttributeValue(name, nil) end owner.BuiltBotHandle = false activeBots[botHandle] = nil activeBuiltBots[handle] = nil activeBuiltBotsOwner[botHandle] = nil bot:ResetFakeSendProp("m_iTeamNum") bot.m_iTeamNum = 1 bot.m_bGlowEnabled = 0 bot.SummonedBot = false pcall( timer.Stop, bot.logicLoop ) removeCallbacks(bot, callbacks) if IsValid(steak) then steak:Remove() end end) timer.Simple( 0.1, function() if IsValid( bot:GetPlayerItemBySlot(2) ) then callbacks.attack = bot:GetPlayerItemBySlot(2):AddCallback( ON_FIRE_WEAPON_PRE, function( ent ) bot.TimeSinceLastAttack = CurTime() + 3 end) end return callbacks end) end local function findFreeBot() local chosen for _, bot in pairs(ents.GetAllPlayers()) do if not bot:IsRealPlayer() and not bot:IsAlive() and (bot.m_iTeamNum == 1 or bot.m_iTeamNum == 0) and bot:GetPlayerName() ~= "Demo-Bot" then chosen = bot break end end return chosen end botType = "scout" --declared here as an anti crash mechanism, the exact second I removed it and did some solo testing the server imploded function TierPurchaseScout(tier, activator) tier = tier + 1 --print("purchasing tier", tier) -- activator.BotTier = tier local botHandle = activator.BuiltBotHandle local bot = botHandle and Entity(tonumber(botHandle)) if tier <= 1 then if bot then bot:Suicide() end return end if not bot then return end botType = "scout" --added to deal with a hard reference to soldier in the original script applyBotTier(activator, bot, "scout", tier) end print( "Waagaawoowoo ride" ) local function BotTimer() for _, bot in pairs ( ents.GetAllPlayers() ) do if bot:IsBot() and ( bot.m_iTeamNum ~= 3 ) and ( bot.SummonedBot ) then local data = BOTS_VARIANTS[botType] local str = "switch_action %s" bot:BotCommand( str:format( data.Action ) ) end end timer.Simple( 5, function() BotTimer() end) end BotTimer() function HealSummonedBot( _, activator, caller ) if caller:IsBot() and caller.SummonedBot then local healing_amount = 100 * ( ( activator:GetPlayerItemBySlot(2):GetAttributeValue( "throwable healing" ) or 0 ) + 1 ) local MaxHealth = caller.m_iMaxHealth + caller:GetAttributeValueByClass("add_maxhealth_nonbuffed", 0) + caller:GetAttributeValueByClass("add_maxhealth", 0) local fakeEventTable = { patient = caller:GetUserId(), healer = activator:GetUserId(), amount = healing_amount } --scoreboard credit. FireEvent("player_healed", fakeEventTable) caller:PlaySoundToSelf("HealthKit.Touch") caller.m_iHealth = math.min( caller.m_iHealth + healing_amount, MaxHealth ) end end function BeansThrown( bean ) if not IsValid( bean ) then return end local owner = bean.m_hOwnerEntity if ( not owner ) or ( owner == nil ) or ( not owner:IsValid() ) or ( not owner:IsPlayer() ) then return end if not IsValid( owner:GetPlayerItemBySlot(1) ) then return end if owner:GetPlayerItemBySlot(1):GetItemName() ~= "Beans" then return end bean:SetModel( "models/weapons/c_models/c_canned_ration/c_ration_plate.mdl" ) bean:SetModelOverride( "models/weapons/c_models/c_canned_ration/c_ration_plate.mdl" ) -- bean.CallBack = bean:AddCallback( ON_START_TOUCH, function( ent, other, hitPos, hitNormal ) --unused -- print( other ) -- if other:IsPlayer() then -- bean:RemoveCallback( ON_START_TOUCH, bean.CallBack ) -- if other:IsRealPlayer() and ( ent:GetClassname() == "item_healthkit_medium" ) then -- end -- end -- end) end function SandwichThrown( steak ) if not IsValid( steak ) then return end --if inWave then return end -- find steak owner -- print( steak.m_hOwnerEntity ) local owner = steak.m_hOwnerEntity if ( not owner ) or ( owner == nil ) or ( not owner:IsValid() ) or ( not owner:IsPlayer() ) then return end if not IsValid( owner:GetPlayerItemBySlot(1) ) then return end if owner:GetPlayerItemBySlot(1):GetItemName() ~= "Beast Bait" then return end local ownerSecondary = owner:GetPlayerItemBySlot(LOADOUT_POSITION_SECONDARY) if ownerSecondary:GetAttributeValue("cloak consume rate decreased") == 3 then botType = "soldier" elseif ownerSecondary:GetAttributeValue("cloak consume rate decreased") == 4 then botType = "heavyweapons" elseif ownerSecondary:GetAttributeValue("cloak consume rate decreased") == 5 then botType = "sniper" elseif ownerSecondary:GetAttributeValue("cloak consume rate decreased") == 2 then botType = "scout" else owner:Print(PRINT_TARGET_CENTER, "NO BEAST SELECTED") return end --this code is very inefficient local botTier = getCurBotTier(owner) --print( botType, botTier ) --print( BOTS_VARIANTS[botType].Tiers[botTier].SummonCount ) --PrintTable( BOTS_VARIANTS[botType].Tiers[botTier] ) for i = 1, BOTS_VARIANTS[botType].Tiers[botTier].SummonCount do if not owner.SummonedBots then owner.SummonedBots = 1 else owner.SummonedBots = owner.SummonedBots + 1 end local handle = owner:GetHandleIndex() local origin = steak:GetAbsOrigin() -- print(origin) local botSpawn = findFreeBot() if not botSpawn then owner:Print(PRINT_TARGET_CENTER, "GLOBAL BOT LIMIT REACHED") return end local data = BOTS_VARIANTS[botType] local str = "switch_action %s" botSpawn.SummonedBot = true botSpawn.TimeSinceLastAttack = CurTime() local callbacks = setupBot(botSpawn, owner, handle, steak) timer.Simple(0, function() -- print( "timer" ) if not IsValid(steak) then --print("newBuilding was destroyed before logic could happen") botSpawn:Suicide() return end botSpawn:SetAbsOrigin(origin) botSpawn:SwitchClassInPlace( data.Class ) --maybe running it twice will fix the problem? I don't know botSpawn:SwitchClassInPlace( data.Class ) if botTier and botTier > 1 then --print("applying tier", botTier) applyBotTier(owner, botSpawn, botType, botTier) else applyDefaultData(owner, botSpawn, botType) end botSpawn:RunScriptCode(BOT_SETUP_VSCRIPT, botSpawn, botSpawn) botSpawn:RunScriptCode((BOT_SET_WEPRESTRICTION_VSCRIPT), botSpawn, botSpawn) botSpawn:RunScriptCode(BOT_CLEAR_RESTRICTIONS_VSCRIPT, botSpawn, botSpawn) botSpawn:WeaponSwitchSlot(2) botSpawn:AddCond(TF_COND_CANNOT_SWITCH_FROM_MELEE) util.ParticleEffect( "teleportedin_red", origin ) botSpawn.m_bGlowEnabled = 1 if not inWave then botSpawn:SetFakeSendProp("m_iTeamNum", 2) botSpawn.m_iTeamNum = 1 -- botSpawn:AddCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED) botSpawn:SetAttributeValue("not solid to players", 1) botSpawn:SetAttributeValue("ignored by enemy sentries", 1) botSpawn:SetAttributeValue("ignored by bots", 1) botSpawn:SetAttributeValue("damage bonus HIDDEN", 0.0001) end steak:Disable() steak:Remove() timer.Simple( 1, function() -- print( str:format( data.Action ) ) botSpawn:BotCommand( str:format( data.Action ) ) end) timer.Simple( 2, function() -- print( str:format( data.Action ) ) botSpawn:BotCommand( str:format( data.Action ) ) end) timer.Simple( 3, function() -- print( str:format( data.Action ) ) botSpawn:BotCommand( str:format( data.Action ) ) end) botSpawn:ResetFakeSendProp("m_iTeamNum") lingeringBuiltBots[handle] = nil -- bot behavior -- default behavior is always following you botSpawn.logicLoop = timer.Create(0.2, function() -- print( "timer loop" ) if ( not owner:GetPlayerItemBySlot(1) ) or ( owner:GetPlayerItemBySlot(1):GetItemName() ~= "Beast Bait" ) then botSpawn:Suicide() end if ( ( botSpawn.m_iTeamNum == 3 ) or ( not botSpawn:IsAlive() ) ) then -- print( "stopping timer" ) pcall( timer.Stop, botSpawn.logicLoop ) return end if botSpawn.m_hActiveWeapon ~= botSpawn:GetPlayerItemBySlot(2) then botSpawn:WeaponSwitchSlot(2) botSpawn:WeaponStripSlot(0) end botSpawn:BotCommand( str:format( data.Action ) ) if botSpawn.m_flChargeMeter >= 100 then if botSpawn:GetPlayerItemBySlot(1) then if botSpawn:GetPlayerItemBySlot(1):GetItemName() == "The Splendid Screen" or botSpawn:GetPlayerItemBySlot(1):GetItemName() == "The Chargin' Targe" then botSpawn:RunScriptCode(BOT_CHARGE_VSCRIPT, botSpawn) end end else if not botSpawn:InCond( TF_COND_SHIELD_CHARGE ) then botSpawn.OldAngles = getEyeAngles( botSpawn ) else botSpawn:SnapEyeAngles( botSpawn.OldAngles or getEyeAngles( botSpawn ) ) --prevent turning when charging end end if BOTS_VARIANTS[botType].Tiers[botTier].CloakAfterAttack then if CurTime() >= botSpawn.TimeSinceLastAttack then botSpawn:AddCond( TF_COND_STEALTHED_USER_BUFF ) end end if botSpawn:GetPlayerItemBySlot(1) then if botSpawn:GetPlayerItemBySlot(1):GetItemName() == "The Buff Banner" or botSpawn:GetPlayerItemBySlot(1):GetItemName() == "The Concheror" then local bannerActive local secondary = botSpawn:GetPlayerItemBySlot(1) if botSpawn.m_flRageMeter == 0 then bannerActive = false end if botSpawn.m_flRageMeter > 100 then botSpawn.m_flRageMeter = 100 end if botSpawn.m_flRageMeter == 100 then if botSpawn:GetPlayerItemBySlot(0) then botSpawn:WeaponSwitchSlot(1) secondary:SetAttributeValue("disable weapon switch", 1) end botSpawn:RunScriptCode("activator.PressFireButton(0.01)", botSpawn) else if botSpawn:InCond(29) or botSpawn:InCond(16) or botSpawn:InCond(26) then secondary:SetAttributeValue("disable weapon switch", 0) botSpawn:WeaponSwitchSlot(2) bannerActive = true end end end end if owner:InCond(TF_COND_TAUNTING) then botSpawn:SetAttributeValue("cannot taunt", nil) botSpawn["$Taunt"](botSpawn) botSpawn:SetAttributeValue("cannot taunt", 1) end for _, cond in pairs(REPLICATE_CONDS) do if owner:InCond(cond) then botSpawn:AddCond(cond, 0.25, owner) end end local secondary = owner:GetPlayerItemBySlot(LOADOUT_POSITION_SECONDARY) local ownerFireRateUpgrade = secondary:GetAttributeValue("sentry fire rate") local ownerDamageUpgrade = secondary:GetAttributeValue("engy sentry damage bonus") if ownerFireRateUpgrade then botSpawn:SetAttributeValue( SENTRY_FIRERATE_REPLICATE_ATTR, ownerFireRateUpgrade * SENTRY_FIRERATE_REPLICATE_MULT ) else botSpawn:SetAttributeValue(SENTRY_FIRERATE_REPLICATE_ATTR, nil) end if ownerDamageUpgrade then botSpawn:SetAttributeValue( SENTRY_DAMAGE_REPLICATE_ATTR, ownerDamageUpgrade * SENTRY_DAMAGE_REPLICATE_MULT ) else botSpawn:SetAttributeValue(SENTRY_DAMAGE_REPLICATE_ATTR, nil) end end, 0) end) end end ents.AddCreateCallback("item_healthkit_medium", function( ent, classname ) timer.Simple( 2.5, function() SandwichThrown( ent ) end) timer.Simple( 0, function() BeansThrown( ent ) end) end)