// Script work by fellen (https://steamcommunity.com/profiles/76561198055313095). // Spell usage information for a given bot. SPELL_UNKNOWN <- -2 SPELL_EMPTY <- -1 SPELL_FIREBALL <- 0 SPELL_BATS <- 1 SPELL_OVERHEAL <- 2 SPELL_PUMPKIN <- 3 SPELL_SUPERJUMP <- 4 SPELL_STEALTH <- 5 SPELL_TELEPORT <- 6 SPELL_ENERGYORB <- 7 SPELL_MINIFY <- 8 SPELL_METEOR <- 9 SPELL_MONOCULOUS <- 10 SPELL_SKELETON <- 11 class SpellbookInstance { spellbook = null scope = null bot = null BotAI = null VCD = "" SpellInfo = null NextChargeTime = -1.0 Charges = 0 Casts = 0 static MAX_CHARGES = 2147483647 static LAUGH_DELAY = 4.0 static DefaultParams = { Type = SPELL_EMPTY, Cooldown = 0.0, MaxCharges = 0, IfSeeTarget = true, IfHealthBelow = 0, SwingMelee = false Limit = 0 } static VCDs = [ [ // SPELL_FIREBALL "", "scenes/player/scout/low/6264.vcd", "scenes/player/sniper/low/6524.vcd", "scenes/player/soldier/low/6199.vcd", "scenes/player/demoman/low/6069.vcd", "scenes/player/medic/low/6589.vcd", "scenes/player/heavy/low/6394.vcd", "scenes/player/pyro/low/8478.vcd", "scenes/player/spy/low/6134.vcd", "scenes/player/engineer/low/6329.vcd" ], [ // SPELL_BATS "", "scenes/player/scout/low/6254.vcd", "scenes/player/sniper/low/6514.vcd", "scenes/player/soldier/low/6189.vcd", "scenes/player/demoman/low/6059.vcd", "scenes/player/medic/low/6579.vcd", "scenes/player/heavy/low/6384.vcd", "scenes/player/pyro/low/6438.vcd", "scenes/player/spy/low/6124.vcd", "scenes/player/engineer/low/6319.vcd" ], [], // SPELL_OVERHEAL [ // SPELL_PUMPKIN "", "scenes/player/scout/low/6263.vcd", "scenes/player/sniper/low/6523.vcd", "scenes/player/soldier/low/6198.vcd", "scenes/player/demoman/low/6068.vcd", "scenes/player/medic/low/6588.vcd", "scenes/player/heavy/low/6393.vcd", "scenes/player/pyro/low/6435.vcd", "scenes/player/spy/low/6133.vcd", "scenes/player/engineer/low/6328.vcd" ], [], // SPELL_SUPERJUMP [], // SPELL_STEALTH [], // SPELL_TELEPORT [], // SPELL_ENERGYORB [], // SPELL_MINIFY [ // SPELL_METEOR "", "scenes/player/scout/low/6291.vcd", "scenes/player/sniper/low/6551.vcd", "scenes/player/soldier/low/6226.vcd", "scenes/player/demoman/low/6096.vcd", "scenes/player/medic/low/6616.vcd", "scenes/player/heavy/low/6421.vcd", "scenes/player/pyro/low/6440.vcd", "scenes/player/spy/low/6161.vcd", "scenes/player/engineer/low/6356.vcd" ], [ // SPELL_MONOCULOUS "", "scenes/player/scout/low/6296.vcd", "scenes/player/sniper/low/6556.vcd", "scenes/player/soldier/low/6231.vcd", "scenes/player/demoman/low/6101.vcd", // There are two VCDs for this one. Not included as not required for our uses. "scenes/player/medic/low/6621.vcd", "scenes/player/heavy/low/6426.vcd", "scenes/player/pyro/low/8480.vcd", "scenes/player/spy/low/6166.vcd", "scenes/player/engineer/low/6361.vcd" ], [ // SPELL_SKELETON "", "scenes/player/scout/low/6293.vcd", "scenes/player/sniper/low/6553.vcd", "scenes/player/soldier/low/6228.vcd", "scenes/player/demoman/low/6098.vcd", "scenes/player/medic/low/6618.vcd", "scenes/player/heavy/low/6423.vcd", "scenes/player/pyro/low/6435.vcd", "scenes/player/spy/low/6163.vcd", "scenes/player/engineer/low/6358.vcd" ] ] function constructor(spellbook_handle, params) { spellbook = spellbook_handle NetProps.SetPropInt(spellbook, "m_iSpellCharges", MAX_CHARGES) bot = spellbook.GetOwner() BotAI = bot.GetScriptScope().BotAI SpellInfo = params DemonicDominance.Bots.FillTagDefaults(SpellInfo, DefaultParams) VCD = GetSpellVCD() Charges = SpellInfo.MaxCharges scope = spellbook.GetScriptScope() scope.SpellList.push(this) scope.NextLaughTime <- -1.0 } function CancelBotVoiceline() { // Don't cancel if we're using a non-bot model. if (bot.GetModelName().find("bots") == null) { scope.NextLaughTime = FLT_MAX return } for (local scene; scene = Entities.FindByClassname(scene, "instanced_scripted_scene");) { if (NetProps.GetPropEntity(scene, "m_hOwner") != bot) continue if (NetProps.GetPropString(scene, "m_szInstanceFilename") != VCD) continue scene.Kill() break } } function GetSpellVCD() { if (!(SpellInfo.Type in VCDs)) return "" if (!(bot.GetPlayerClass() in VCDs[SpellInfo.Type])) return "" return VCDs[SpellInfo.Type][bot.GetPlayerClass()] } function ProcessChargeGain() { if (NextChargeTime > Time()) return NextChargeTime = Time() + SpellInfo.Cooldown Charges++ } function CastSpellIfAble() { if (Charges < SpellInfo.MaxCharges) ProcessChargeGain() if (SpellInfo.IfHealthBelow && bot.GetHealth() > SpellInfo.IfHealthBelow) return if (Charges <= 0) return // This gets added too late and the bot will try (and fail) to cast in spawn. //if (bot.InCond(TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED)) // return local lastarea = bot.GetLastKnownArea() if (lastarea && lastarea.HasAttributeTF(TF_NAV_SPAWN_ROOM_BLUE)) return if (SpellInfo.IfSeeTarget && !BotAI.ThreatInLOS()) return NetProps.SetPropInt(spellbook, "m_iSelectedSpellIndex", SpellInfo.Type) spellbook.PrimaryAttack() Charges-- Casts++ // Stop spell voiceline, as there's no robo version for these. CancelBotVoiceline() // Use LaughEvil as a replacement voiceline. if (scope.NextLaughTime < Time()) { DemonicDominance.Bots.LaughEvil(bot) scope.NextLaughTime = Time() + LAUGH_DELAY } // Bots don't have a casting animation but we can fake it by swinging their melee. if (SpellInfo.SwingMelee && bot.GetActiveWeapon().IsMeleeWeapon()) bot.GetActiveWeapon().PrimaryAttack() if (SpellInfo.Limit && Casts >= SpellInfo.Limit) scope.SpellList.remove(scope.SpellList.find(this)) } } function SetupSpellBot(bot) { local spellbook = GetBotSpellbook(bot) local botscope = bot.GetScriptScope() if (!("BotAI" in botscope)) botscope.BotAI <- DemonicDominance.BotAI(bot) local scope = spellbook.GetScriptScope() scope.UpdateFrequency <- botscope.BotAI.UpdateFrequency scope.SpellList <- [] botscope.OnDeathEnts.push(spellbook) scope.SpellThink <- function() { foreach (spell in SpellList) spell.CastSpellIfAble() return UpdateFrequency } DemonicDominance.AddContextThink(spellbook, "SpellThink") } function GetBotSpellbook(bot) { local spellbook = null for (local i = 0; i < MAX_WEAPONS; ++i) { local weapon = NetProps.GetPropEntityArray(bot, "m_hMyWeapons", i) if (!weapon || weapon.GetClassname() != "tf_weapon_spellbook") continue spellbook = weapon } if (!spellbook) { printl("Could not find spellbook on \"Spell\" tagged bot.") return null } if (!spellbook.GetScriptScope()) { spellbook.ValidateScriptScope() NetProps.SetPropBool(spellbook, "m_bForcePurgeFixedupStrings", true) } return spellbook } function ParseSpellBot(bot, tag) { local spellbook = GetBotSpellbook(bot) local scope = spellbook.GetScriptScope() if (!("SpellList" in scope)) SetupSpellBot(bot) compilestring("TagStore=" + tag.slice(5))() scope.SpellList.push( SpellbookInstance(spellbook, TagStore) ) }