------------------------------------------------------------------------------------------------------------- ------------------------------------------------- GLOBALS --------------------------------------------------- ------------------------------------------------------------------------------------------------------------- -- Not limited by midwave restrictions -- Pick any spell regardless of upgrade unlocks debug = false; precache.PrecacheScriptSound("Halloween.spell_lightning_cast"); precache.PrecacheScriptSound("Halloween.spell_overheal"); precache.PrecacheScriptSound("Halloween.spell_blastjump"); precache.PrecacheScriptSound("Halloween.spell_stealth"); precache.PrecacheScriptSound("Weapon_RPG.SingleCrit"); precache.PrecacheScriptSound("BaseExplosionEffect.Sound"); precache.PrecacheSound("ambient/fireball.wav"); precache.PrecacheSound("player/recharged.wav"); precache.PrecacheSound("misc/halloween/merasmus_appear.wav"); precache.PrecacheSound("ambient/energy/zap1.wav"); precache.PrecacheParticle("heavy_ring_of_fire"); precache.PrecacheParticle("mvm_hatch_destroy_smolderembers"); precache.PrecacheParticle("flaregun_energyfield_red"); precache.PrecacheParticle("dxhr_lightningball_glow3_red"); precache.PrecacheParticle("rd_robot_explosion_smoke_linger"); -- m_iAmmo TF_AMMO_DUMMY = 1; TF_AMMO_PRIMARY = 2; TF_AMMO_SECONDARY = 3; TF_AMMO_METAL = 4; TF_AMMO_GRENADES1 = 5; TF_AMMO_GRENADES2 = 6; TF_AMMO_GRENADES3 = 7; TF_AMMO_COUNT = 8; player_list = {}; midwave = false; tickrate = 66; common_spell_time = 5; rare_spell_time = 30; common_timer_value = common_spell_time; rare_timer_value = rare_spell_time; wizard_rng_rolls_custom_spells = true; WIZARD_NONE = 0; WIZARD_USE_MANA = 1; WIZARD_USE_ROLLS = 2; SPELL_TYPE_NONE = 0; SPELL_TYPE_COMMON = 1; SPELL_TYPE_RARE = 2; SPELL_CHOOSING = -2; SPELL_NONE = -1; SPELL_FIREBALL = 0; SPELL_BALLOBATS = 1; SPELL_HEALINGAURA = 2; SPELL_PUMPKINMIRV = 3; SPELL_SUPERJUMP = 4; SPELL_INVISIBILITY = 5; SPELL_TELEPORT = 6; SPELL_TESLABOLT = 7; SPELL_MINIFY = 8; SPELL_METEORSHOWER = 9; SPELL_SUMMONMONOCULUS = 10; SPELL_SUMMONSKELETONS = 11; SPELL_KARTBOXINGROCKET = 12; SPELL_KARTBASEJUMP = 13; SPELL_KARTOVERHEAL = 14; SPELL_KARTBOMBHEAD = 15; SPELL_CUSTOM_CROCKET = 16; SPELL_CUSTOM_GRAVITYBOMB = 17; spell_rng_common_chances = { [SPELL_FIREBALL] = { roll_chance=0.5, charge_chances = {0.2, 0.7, 0.07, 0.03} }, [SPELL_HEALINGAURA] = { roll_chance=0.2, charge_chances = {0.4, 0.5, 0.1, } }, [SPELL_PUMPKINMIRV] = { roll_chance=0.15, charge_chances = {0.7, 0.2, 0.1, } }, [SPELL_CUSTOM_CROCKET] = { roll_chance=0.15, charge_chances = {0.7, 0.2, 0.1, } }, }; spell_rng_rare_chances = { [SPELL_SUMMONSKELETONS] = { roll_chance=0.5, charge_chances = {0.9, 0.1,} }, [SPELL_SUMMONMONOCULUS] = { roll_chance=0.3, charge_chances = {0.9, 0.1,} }, [SPELL_METEORSHOWER] = { roll_chance=0.10, charge_chances = {1 } }, [SPELL_TESLABOLT] = { roll_chance=0.05, charge_chances = {1 } }, [SPELL_CUSTOM_GRAVITYBOMB] = { roll_chance=0.05, charge_chances = {1 } }, }; spell_projectile_class_map = { tf_projectile_spellfireball = SPELL_FIREBALL, tf_projectile_spellbats = SPELL_BALLOBATS, tf_projectile_spellmirv = SPELL_PUMPKINMIRV, tf_projectile_spelltransposeteleport = SPELL_TELEPORT, tf_projectile_lightningorb = SPELL_TESLABOLT, tf_projectile_spellmeteorshower = SPELL_METEORSHOWER, tf_projectile_spellspawnboss = SPELL_SUMMONMONOCULUS, tf_projectile_spellspawnhorde = SPELL_SUMMONSKELETONS, tf_projectile_spellkartorb = SPELL_KARTBOXINGROCKET, }; CLR_WHITE = {250, 245, 240}; CLR_RED = {250, 50, 0}; CLR_ORANGE = {250, 100, 50}; CLR_LIMEGREEN = {100, 250, 50}; CLR_BLUE = {50, 100, 250}; CLR_PURPLE = {150, 50, 175}; CLR_MAGENTA = {250, 50, 150}; UPGRADE_UPGRADE = 0; UPGRADE_APPLY = 1; UPGRADE_DOWNGRADE = 2; UPGRADE_RESTORE = 3; VALUE_INTEGER = 0; VALUE_FLOAT = 1; ------------------------------------------------------------------------------------------------------------- -------------------------------------------------- MISC ----------------------------------------------------- ------------------------------------------------------------------------------------------------------------- ------------- -- Math ------------- -- Round a number math.round = function(num, decimals) if (not decimals or decimals <= 0) then return math.floor(num + 0.5); else local mod = 10 ^ decimals; return math.floor((num * mod) + 0.5) / mod; end end -- Round to nearest divisible math.rounddiv = function(num, div) local remainder = num % div; if (remainder == 0) then return num; elseif (remainder < div / 2) then return num - remainder; else return num + div - remainder; end end -- Get the integer value for an rgb value math.rgbtoint = function(red, green, blue) return (red << 16) + (green << 8) + blue end ------------- -- CEntity ------------- -- Grab the CEntity table local _ = Entity("info_target", false, false); local CEntity = getmetatable(_); _:Remove(); ------------- -- Entity methods ------------- function IsValidPlayer(ent) return IsValid(ent) and ent:IsPlayer(); end function IsValidRealPlayer(ent) return IsValid(ent) and ent:IsRealPlayer(); end function IsValidAlivePlayer(ent) return IsValid(ent) and ent:IsPlayer() and ent:IsAlive(); end function IsValidAliveRealPlayer(ent) return IsValid(ent) and ent:IsRealPlayer() and ent:IsAlive(); end -- Set entity attributes CEntity.SetAttributeValues = function(self, attributes) if (IsValid(self)) then for attr, val in pairs(attributes) do self:SetAttributeValue(attr, val); end end end -- Play a particle system and parent it to the entity CEntity.PlayParentedParticle = function(self, particle, offset, remove_after, RemoveFunction) if (not IsValid(self)) then return; end particle = ents.CreateWithKeys("info_particle_system", { effect_name=particle, start_active=1, ["$modules"]="fakeparent", ["$positiononly"]=1, }, true, true) local entity_origin = self:GetAbsOrigin(); if (offset) then entity_origin = entity_origin + offset; end particle.m_vecOrigin = entity_origin; particle["$SetFakeParent"](particle, self); particle["Start"](particle); timer.Create(remove_after, function() pcall(particle["Remove"], particle); if (RemoveFunction) then pcall(RemoveFunction); end end, 1); return particle; end ------------- -- Player methods ------------- -- How many spells does this player have unlocked CEntity.CountUnlockedSpells = function(self) if (not IsValidRealPlayer(self)) then return; end local playerdata = player_list[self:GetUserId()].upgrades_spell_data; local count = 0; for spell, data in pairs(playerdata) do if (data._id) then count = count + 1; end end return count; end -- Get player eye angles CEntity.GetEyeAngles = function(self) if (IsValidPlayer(self)) then return Vector(self["m_angEyeAngles[0]"], self["m_angEyeAngles[1]"], 0); end end -- Get player eye position CEntity.GetEyePos = function(self) if (not IsValidPlayer(self)) then return; end local eyepos = self:GetAbsOrigin(); eyepos.z = eyepos.z + self["m_vecViewOffset[2]"]; return eyepos end -- Is this player midair? CEntity.IsMidair = function(self) if (IsValidAlivePlayer(self)) then return not (self.movetype == MOVETYPE_WALK and (self.m_fFlags & FL_ONGROUND ~= 0)); end end -- Is this player Harry Potter? CEntity.IsWizard = function(self) if (not IsValidRealPlayer(self)) then return false; end return (self.m_iTeamNum == TEAM_RED and self.m_iClass == TF_CLASS_ENGINEER and player_list[self:GetUserId()].wizard_type ~= WIZARD_NONE); end -- Concise version of CEntity.ShowHudText CEntity.ShowHudTextSimple = function(self, text, channel, x, y, clr, a, fadetime, holdtime) if (not IsValidPlayer(self)) then return; end alpha = alpha or 0; fadetime = fadetime or 0.25; holdtime = holdtime or 2; local r, g, b; if (clr and #clr == 3) then r, g, b = table.unpack(clr); else r, g, b = 255, 255, 255; end self:ShowHudText({ channel = channel, x = x, y = y, effect = 0, r1 = r, g1 = g, b1 = b, a1 = a, r2 = r, g2 = g, b2 = b, a2 = a, fadeinTime = fadetime, fadeoutTime = fadetime, holdTime = holdtime, fxTime = 0, }, text); end -- Roll a spell for this player table.RandomChance = false; -- need to reference this before it's defined CEntity.RollSpell = function(self, chancetable) if (not IsValidRealPlayer(self)) then return; end if (not chancetable or table.Count(chancetable) == 0) then return; end local spellbook = self:GetPlayerItemBySlot(LOADOUT_POSITION_ACTION); if (not spellbook or spellbook.m_iClassname ~= "tf_weapon_spellbook") then return; end local tbl = nil; -- Not allowed to roll custom spells if (not wizard_rng_rolls_custom_spells) then -- Strip custom spells from the chance table local sumchances = 0; tbl = {}; for spell, chances in pairs(chancetable) do if (not spell_data[spell].is_custom) then tbl[spell] = chances; sumchances = sumchances + chances.roll_chance; end end -- The chance sum isn't correct as a result of removing custom spells if (sumchances ~= 1) then -- How much does each entry need to change? local difference = math.abs(1 - sumchances); local increment = difference / table.Count(chancetable); -- Increment the table chances for spell, chances in pairs(tbl) do if (sumchances > 1) then chances.roll_chance = chances.roll_chance - increment; elseif (sumchances < 1) then chances.roll_chance = chances.roll_chance + increment; end end end -- Allowed to use custom spells else tbl = chancetable; end -- Choose a random spell local spell = table.RandomChance(tbl); local charges = table.RandomChance(tbl[spell].charge_chances); -- Get our the spellbook's current spell local current_spell = spellbook.m_iSelectedSpellIndex; if (spellbook._m_iCustomSelectedSpellIndex) then current_spell = spellbook._m_iCustomSelectedSpellIndex; end -- Only select a spell if no spell or common -> rare if (current_spell < 0 or spellbook.m_iSpellCharges == 0 or (spell_data[current_spell].spell_type == SPELL_TYPE_COMMON and spell_data[spell].spell_type == SPELL_TYPE_RARE)) then SelectSpell(spellbook, spell, charges, 2.5, false, true); end end ------------- -- Generic functions ------------- -- Get key with random chance from table of format: -- key : probability -- or -- key : {roll_chance=probability, charge_chances={[1]=probability}} -- see: spell_rng_*_chances for example table.RandomChance = function(t) local rand = math.random() local cumulativeProbability = 0 for key, item in pairs(t) do if (type(item) == "table") then cumulativeProbability = cumulativeProbability + item.roll_chance; else cumulativeProbability = cumulativeProbability + item; end if (rand <= cumulativeProbability) then return key; end end end -- Play a sound at a generic position function PlaySound(sound, position) local ent = ents.CreateWithKeys("info_target", {}, true, true); ent:SetAbsOrigin(position); ent:PlaySound(sound); ent:Remove(); end ------------------------------------------------------------------------------------------------------------- ------------------------------------------------- SPELLS ---------------------------------------------------- ------------------------------------------------------------------------------------------------------------- -- Fire a weapon mimic from a player's eye angles function FireCustomWeaponMimic(player, keyvalues, playerattributes, itemattributes, custommodel, customsound, customparticles) if (not IsValidRealPlayer(player)) then return; end local mimic = ents.CreateWithKeys("tf_point_weapon_mimic", keyvalues, true, true) mimic["$SetOwner"](mimic, player); local current_player_attributes = {}; -- Populate current_player_attributes with attributes to be modified from playerattributes if (playerattributes) then for attr, val in pairs(playerattributes) do local currentattr = player:GetAttributeValue(attr); if (not currentattr) then -- We need to know later that this attribute needs to be modified, so we can't set it to nil current_player_attributes[attr] = "\0"; else current_player_attributes[attr] = currentattr; end player:SetAttributeValue(attr, val); end end -- Modify mimic's weapon attributes if (itemattributes) then for index, attr in pairs(itemattributes) do mimic["$AddWeaponAttribute"](mimic, attr); end end -- Inherit owner eye angles local player_eye_angles = player:GetEyeAngles(); if (not player_eye_angles) then player_eye_angles = Vector(0, 0, 0); end mimic:SetAbsAngles(player_eye_angles); -- Inherit owner eye origin local player_eye_origin = player:GetEyePos(); mimic:SetAbsOrigin(player_eye_origin); -- Stuff to do when the mimic weapon fires function HandleMimicChanges(value, activator, caller) -- Add particle effects to the projectile local particleentities = {}; if (customparticles) then for index, particlename in pairs(customparticles) do local particle = ents.CreateWithKeys("info_particle_system", { effect_name=particlename, start_active=1, ["$modules"]="fakeparent", }, true, true); table.insert(particleentities, particle); local origin = activator:GetAbsOrigin(); particle.m_vecOrigin = origin; particle["$SetFakeParent"](particle, activator); particle["Start"](particle); end end -- Set the projectile's model if (custommodel) then activator["$SetModelSpecial"](activator, custommodel); end -- Kaboom activator:AddCallback(ON_REMOVE, function() -- Clean up particles for index, particle in pairs(particleentities) do particle:Remove(); end -- Revert player's attributes for attr, val in pairs(current_player_attributes) do if (val == "\0") then player:SetAttributeValue(attr, nil); else player:SetAttributeValue(attr, val); end end end); end mimic:AddOutput("$OnFire popscript,$HandleMimicChanges,,0,-1"); mimic["FireOnce"](mimic); if (customsound) then player:PlaySoundToSelf(customsound); end mimic:Remove(); end -- Spawn function for the Healing Aura spell function SpellHealingAuraSpawn(player, spellbook) if (not IsValidRealPlayer(player)) then return; end local entities = ents.FindInSphere(player:GetAbsOrigin(), 96); for index, ent in pairs(entities) do if (IsValidPlayer(ent) and ent.m_iTeamNum == player.m_iTeamNum) then ent:AddCond(TF_COND_INVULNERABLE, 2); ent:AddCond(TF_COND_HALLOWEEN_QUICK_HEAL, 6); end end player:PlaySoundToSelf("Halloween.spell_overheal"); end -- Spawn function for the Super Jump spell function SpellSuperjumpSpawn(player, spellbook) if (not IsValidRealPlayer(player)) then return; end local velocity = player.m_vecAbsVelocity; if (velocity.z < 0) then velocity.z = 0; end velocity.z = (velocity.z + 500) * 2; if (velocity.z > 1500) then velocity.z = 1500; end player.m_vecAbsVelocity = velocity; player:PlaySoundToSelf("Halloween.spell_blastjump"); end -- Spawn function for the Invisibility spell function SpellInvisibilitySpawn(player, spellbook) if (not IsValidRealPlayer(player)) then return; end player:AddCond(TF_COND_STEALTHED_USER_BUFF, 5); player:SetAttributeValue("dmg taken increased", 0.5); player:PlaySoundToSelf("Halloween.spell_stealth"); timer.Create(5, function() if (IsValid(player)) then return; end player:SetAttributeValue("dmg taken increased", 1); end, 1); end -- Spawn function for the Minify spell function SpellMinifySpawn(player, spellbook) if (not IsValidRealPlayer(player)) then return; end local userid = player:GetUserId(); local playerdata = player_list[userid]; if (playerdata.is_minified) then return; end playerdata.is_minified = true; local fire_rate = player:GetAttributeValue("fire rate bonus") or 1; local melee_res = player:GetAttributeValue("mult dmgtaken from melee") or 1; player["SetForcedTauntCam"](player, 1); player:AddCond(TF_COND_SPEED_BOOST, 8); player:SetAttributeValues({ ["fire rate bonus"] = 0.7, ["voice pitch scale"] = 1.25, ["mult dmgtaken from melee"] = 0.6, }); timer.Create(8, function() if (not IsValid(player)) then return; end player["SetForcedTauntCam"](player, 0); player:SetAttributeValues({ ["fire rate bonus"] = fire_rate, ["voice pitch scale"] = 1, ["mult dmgtaken from melee"] = melee_res, }); playerdata.is_minified = false; end, 1); end -- Fire function for the Crocket spell function SpellCustomCrocketFired(value, activator, caller) local firetime = CurTime(); local think_timer = nil; think_timer = timer.Create(0.015, function() if (IsValid(activator)) then -- Explode after a short period if (CurTime() >= firetime + 5) then local origin = activator:GetAbsOrigin(); util.ParticleEffect("rd_robot_explosion_smoke_linger", origin); activator:PlaySound("BaseExplosionEffect.Sound"); -- Damage enemy players in the blast radius local entities = ents.FindInSphere(origin, 146); for index, ent in pairs(entities) do if (IsValidAlivePlayer(ent) and ent.m_iTeamNum ~= activator.m_iTeamNum) then ent:TakeDamage({ Attacker = activator.m_hOwnerEntity, Inflictor = activator, Weapon = nil, Damage = 40, DamageType = DMG_GENERIC, DamageCustom = TF_DMG_CUSTOM_NONE, DamagePosition = origin, DamageForce = Vector(0,0,0), ReportedPosition = origin, }); end end activator:Remove(); return; end local player = activator.m_hOwnerEntity; if (not IsValidRealPlayer(player)) then return; end -- Owner doesn't have homing rockets upgrade, don't continue if (not player:GetAttributeValue("sticky detonate mode")) then return; end local player_eye_angles = player:GetEyeAngles(); if (not player_eye_angles) then player_eye_angles = Vector(90, 0, 0); end -- Fire trace from owner's eyes and set rocket's angles and velocity towards hit position if (not util.IsLagCompensationActive()) then util.StartLagCompensation(player) local traceresult = util.Trace({ start = player, endpos = nil, distance = 8192, angles = player_eye_angles, mask = MASK_SHOT, collisiongroup = COLLISION_GROUP_PLAYER, mins = Vector(0,0,0), maxs = Vector(0,0,0), filter = {player, activator}, }); util.FinishLagCompensation(player) if (traceresult.Hit and traceresult.HitPos) then local target_angles = (traceresult.HitPos - activator:GetAbsOrigin()):ToAngles(); activator:SetAbsAngles(target_angles); activator.m_vecAbsVelocity = target_angles:GetForward() * 1100; end end else -- Projectile is dead, stop thinking if (think_timer) then pcall(timer.Stop, think_timer); think_timer = nil; end end end, 0); end -- Spawn function for the Crocket spell function SpellCustomCrocketSpawn(player, spellbook) FireCustomWeaponMimic(player, { TeamNum = player.m_iTeamNum, ["$weaponname"] = "TF_WEAPON_ROCKETLAUNCHER", ["$OnFire"] = "popscript,$SpellCustomCrocketFired,,0,-1", Crits = true, }, nil, nil, nil, "Weapon_RPG.SingleCrit", nil); end -- Fire function for the Gravity Bomb spell function SpellCustomGravityBombFired(value, activator, caller) -- We need to access the projectile's owner's team after it's been removed local caster = activator.m_hOwnerEntity; local caster_team = caster.m_iTeamNum; activator:AddCallback(ON_REMOVE, function() local offset = Vector(0, 0, 70); activator:PlayParentedParticle("flaregun_energyfield_red", offset, 4); activator:PlayParentedParticle("dxhr_lightningball_parent_red", offset, 4); local epicenter = activator:GetAbsOrigin() + offset; -- Suck enemy players into epicenter for duration local entities = {}; timer.Create(0.015, function() entities = ents.FindInSphere(epicenter, 180); for index, ent in pairs(entities) do if (IsValidAlivePlayer(ent) and ent.m_iTeamNum ~= caster_team) then local player_origin = ent:GetAbsOrigin(); -- Ensure victim becomes airborne when at low velocity if (not ent:IsMidair()) then player_origin.z = player_origin.z + 32; ent:SetAbsOrigin(player_origin); end local velocity_dir = (epicenter - player_origin):ToAngles(); local distance = epicenter:Distance(player_origin); local player_velocity = velocity_dir:GetForward() * (distance ^ 1.25); ent.m_vecAbsVelocity = player_velocity; end end end, math.round(2.1 / 0.015)); -- Explode afterwards timer.Create(4.2, function() local origin = epicenter; origin.z = origin.z - 35; util.ParticleEffect("rd_robot_explosion_smoke_linger", origin); PlaySound("BaseExplosionEffect.Sound", origin); -- Damage enemy players in the blast radius for index, ent in pairs(entities) do if (IsValidAlivePlayer(ent) and ent.m_iTeamNum ~= caster_team) then ent:TakeDamage({ Attacker = caster, Inflictor = nil, Weapon = nil, Damage = 300, DamageType = DMG_GENERIC, DamageCustom = TF_DMG_CUSTOM_NONE, DamagePosition = origin, DamageForce = Vector(0,0,0), ReportedPosition = origin, }); local velocity_dir = Vector(math.random(-90, -45), math.random(-180, 180), 0); velocity_dir = velocity_dir:GetForward() * math.random(500, 1000); ent.m_vecAbsVelocity = velocity_dir; end end end, 1); end) end -- Spawn function for the Gravity Bomb spell function SpellCustomGravityBombSpawn(player, spellbook) FireCustomWeaponMimic(player, { TeamNum = player.m_iTeamNum, ["$weaponname"] = "TF_WEAPON_GRENADELAUNCHER", ["$OnFire"] = "popscript,$SpellCustomGravityBombFired,,0,-1", Crits = true, }, nil, { "Projectile speed decreased|0.7", }, "models/empty.mdl", "Halloween.spell_lightning_cast", {"flaregun_energyfield_red"}); end spell_data = { [SPELL_CHOOSING] = { name = "...", charges = 0, mana_cost = nil, roll_time = 0, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_NONE, }, [SPELL_NONE] = { name = "None", charges = 0, mana_cost = nil, roll_time = 0, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_NONE, }, [SPELL_FIREBALL] = { name = "Fireball", charges = 2, mana_cost = 300, roll_time = 2, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_BALLOBATS] = { name = "Ball O' Bats", charges = 2, mana_cost = 300, roll_time = 1, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_HEALINGAURA] = { name = "Healing Aura", charges = 2, mana_cost = 400, roll_time = 3, is_custom = true, fake_icon = SPELL_HEALINGAURA, SpawnFunction = SpellHealingAuraSpawn, spell_type = SPELL_TYPE_COMMON, }, [SPELL_PUMPKINMIRV] = { name = "Pumpkin MIRV", charges = 2, mana_cost = 300, roll_time = 2, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_SUPERJUMP] = { name = "Superjump", charges = 999, mana_cost = 100, roll_time = 1, is_custom = true, fake_icon = SPELL_SUPERJUMP, SpawnFunction = SpellSuperjumpSpawn, spell_type = SPELL_TYPE_COMMON, }, [SPELL_INVISIBILITY] = { name = "Invisibility", charges = 999, mana_cost = 200, roll_time = 1, is_custom = true, fake_icon = SPELL_INVISIBILITY, SpawnFunction = SpellInvisibilitySpawn, spell_type = SPELL_TYPE_COMMON, }, [SPELL_TELEPORT] = { name = "Teleport", charges = 2, mana_cost = 300, roll_time = 1, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_TESLABOLT] = { name = "Tesla Bolt", charges = 1, mana_cost = 2000, roll_time = 7, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_RARE, }, [SPELL_MINIFY] = { name = "Minify", charges = 2, mana_cost = 300, roll_time = 1, is_custom = true, fake_icon = SPELL_MINIFY, SpawnFunction = SpellMinifySpawn, spell_type = SPELL_TYPE_RARE, }, [SPELL_METEORSHOWER] = { name = "Meteor Shower", charges = 1, mana_cost = 2500, roll_time = 7, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_RARE, }, [SPELL_SUMMONMONOCULUS] = { name = "Summon Monoculus", charges = 1, mana_cost = 1250, roll_time = 5, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_RARE, }, [SPELL_SUMMONSKELETONS] = { name = "Summon Skeletons", charges = 1, mana_cost = 1250, roll_time = 5, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_RARE, }, [SPELL_KARTBOXINGROCKET] = { name = "Bumper Car Boxing Rocket", charges = 5, mana_cost = 450, roll_time = 3, is_custom = false, fake_icon = nil, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_KARTBASEJUMP] = { name = "Bumper Car Base Jump", charges = 999, mana_cost = 200, roll_time = 2, is_custom = true, fake_icon = SPELL_KARTBASEJUMP, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_KARTOVERHEAL] = { name = "Bumper Car Overheal", charges = 2, mana_cost = 600, roll_time = 3, is_custom = true, fake_icon = SPELL_KARTOVERHEAL, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_KARTBOMBHEAD] = { name = "Bumper Car Bomb Head", charges = 2, mana_cost = 500, roll_time = 4, is_custom = true, fake_icon = SPELL_KARTBOMBHEAD, SpawnFunction = nil, spell_type = SPELL_TYPE_COMMON, }, [SPELL_CUSTOM_CROCKET] = { name = "Crocket!", charges = 3, mana_cost = 500, roll_time = 3, is_custom = true, fake_icon = SPELL_FIREBALL, SpawnFunction = SpellCustomCrocketSpawn, spell_type = SPELL_TYPE_COMMON, }, [SPELL_CUSTOM_GRAVITYBOMB] = { name = "Gravity Bomb", charges = 1, mana_cost = 2500, roll_time = 7, is_custom = true, fake_icon = SPELL_TESLABOLT, SpawnFunction = SpellCustomGravityBombSpawn, spell_type = SPELL_TYPE_RARE, }, }; -- Retrieve appropriate spell data from either player spell upgrades data or spell_data function GetSpellData(player, spell, customspell, data, shouldround) local playerspelldata = player_list[player:GetUserId()].upgrades_spell_data; local playerspelltypedata = player_list[player:GetUserId()].upgrades_spelltype_data; local modvalue = nil; local returnvalue = nil; -- customspell takes priority over spell -- Custom spell has player upgrade data if (customspell and playerspelldata[customspell] and playerspelldata[customspell][data]) then -- Modify spell data with spell *type* data (COMMON, RARE) modvalue = playerspelltypedata[spell_data[customspell].spell_type][data] or 1; returnvalue = modvalue * playerspelldata[customspell][data]; if (shouldround) then returnvalue = math.round(returnvalue); end -- Custom spell has generic spell data elseif (customspell and spell_data[customspell] and spell_data[customspell][data]) then -- Modify spell data with spell *type* data (COMMON, RARE) modvalue = playerspelltypedata[spell_data[customspell].spell_type][data] or 1; returnvalue = modvalue * spell_data[customspell][data]; if (shouldround) then returnvalue = math.round(returnvalue); end -- Spell has player upgrade data elseif (spell and playerspelldata[spell] and playerspelldata[spell][data]) then -- Modify spell data with spell *type* data (COMMON, RARE) modvalue = playerspelltypedata[spell_data[spell].spell_type][data] or 1; returnvalue = modvalue * playerspelldata[spell][data]; if (shouldround) then returnvalue = math.round(returnvalue); end -- Spell has generic spell data elseif (spell_data[spell] and spell_data[spell][data]) then -- Modify spell data with spell *type* data (COMMON, RARE) modvalue = playerspelltypedata[spell_data[spell].spell_type][data] or 1; returnvalue = modvalue * spell_data[spell][data]; if (shouldround) then returnvalue = math.round(returnvalue); end end return returnvalue; end -- Select a spell for a player's spellbook function SelectSpell(spellbook, spell_index, charges, roll_time, override_same_spell, override_same_charges) local player = spellbook.m_hOwner; local userid = player:GetUserId(); local playerdata = player_list[userid]; -- If we're rolling at the moment, stop the timer if (playerdata.spell_roll_timer) then pcall(timer.Stop, player_list[userid].spell_roll_timer) playerdata.spell_roll_timer = nil; end if (not player:IsWizard()) then return; end -- Use this when checking whether to override, otherwise custom spells can't -- switch to fireball because that's technically what they are local current_spell = spellbook.m_iSelectedSpellIndex; if (spellbook._m_iCustomSelectedSpellIndex) then current_spell = spellbook._m_iCustomSelectedSpellIndex; end -- We already have that spell, don't do anything if ((not override_same_spell or override_same_spell == 0) and current_spell == spell_index and spellbook.m_iSpellCharges > 0) then return; end -- We already have that amount of charges, don't do anything if ((not override_same_charges or override_same_charges == 0) and current_spell >= 0 and spell_index >= 0 and spellbook.m_iSpellCharges == charges) then return; end -- Reset any previous custom spell spellbook:ResetFakeSendProp("m_iSelectedSpellIndex"); spellbook._m_iCustomSelectedSpellIndex = nil; -- No roll time or no charges if ( (not roll_time or roll_time <= 0) or (not charges or charges <= 0) ) then -- Custom spell if (spell_data[spell_index].is_custom) then spellbook.m_iSelectedSpellIndex = SPELL_FIREBALL; -- Custom spells use fireball so we can detect projectile spawn spellbook._m_iCustomSelectedSpellIndex = spell_index; spellbook:SetFakeSendProp("m_iSelectedSpellIndex", spell_data[spell_index].fake_icon); -- Regular spell else spellbook.m_iSelectedSpellIndex = spell_index; end if (charges and charges > 0) then spellbook.m_iSpellCharges = charges; end -- Roll time and charges else spellbook.m_iSelectedSpellIndex = SPELL_CHOOSING -- Rolling... -- When we're done rolling... playerdata.spell_roll_timer = timer.Create(roll_time, function() if (not IsValid(player)) then return; end if (not IsValid(spellbook)) then spellbook = player:GetPlayerItemBySlot(LOADOUT_POSITION_ACTION); if (not spellbook or spellbook.m_iClassname ~= "tf_weapon_spellbook") then goto cleanup; end end if (not player:IsWizard()) then spellbook.m_iSelectedSpellIndex = SPELL_NONE; goto cleanup; end -- Custom spell if (spell_data[spell_index].is_custom) then spellbook.m_iSelectedSpellIndex = SPELL_FIREBALL; -- Custom spells use fireball so we can detect projectile spawn spellbook._m_iCustomSelectedSpellIndex = spell_index; spellbook:SetFakeSendProp("m_iSelectedSpellIndex", spell_data[spell_index].fake_icon); -- Regular spell else spellbook.m_iSelectedSpellIndex = spell_index; end spellbook.m_iSpellCharges = charges; ::cleanup:: playerdata.spell_roll_timer = nil; end, 1); end end -- Give all RNG wizards a random spell of type spell_type function GiveWizardsSpell(spell_type) for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid); if (player:IsWizard() and playerdata.wizard_type == WIZARD_USE_ROLLS) then if (spell_type == SPELL_TYPE_COMMON) then player:RollSpell(spell_rng_common_chances); elseif (spell_type == SPELL_TYPE_RARE) then player:RollSpell(spell_rng_rare_chances); player:PlaySoundToSelf("misc/halloween/merasmus_appear.wav"); end end end end -- Give a player wizard items function GivePlayerWizardItems(player, wizard_type) player:WeaponStripSlot(LOADOUT_POSITION_PRIMARY); player:WeaponStripSlot(LOADOUT_POSITION_SECONDARY); player:WeaponStripSlot(LOADOUT_POSITION_BUILDING); player:WeaponStripSlot(LOADOUT_POSITION_UTILITY); player:WeaponStripSlot(LOADOUT_POSITION_PDA); player:WeaponStripSlot(LOADOUT_POSITION_PDA2); player:WeaponSwitchSlot(LOADOUT_POSITION_MELEE); if (wizard_type == WIZARD_USE_MANA) then player:RemoveItem(player.m_hActiveWeapon:GetItemName()); player:GiveItem("The Freedom Staff"); elseif (wizard_type == WIZARD_USE_ROLLS) then player.m_clrRender = math.rgbtoint(52, 116, 78); end player:GiveItem("TF_WEAPON_SPELLBOOK"); end ------------------------------------------------------------------------------------------------------------- ------------------------------------------------- MENUS ---------------------------------------------------- ------------------------------------------------------------------------------------------------------------- function OnMenuCancel(player, reason) player_list[player:GetUserId()].displaying_menu = nil; end function OnSpellMenuSelect(player, selectedIndex, value) local userid = player:GetUserId() value = tonumber(value); if (not player:IsWizard()) then return; end player_list[userid].displaying_menu = nil; if (not debug and not midwave) then player:Print(PRINT_TARGET_CENTER, "You can only select spells once the wave starts!"); return; end local spellbook = player:GetPlayerItemBySlot(LOADOUT_POSITION_ACTION); if (not spellbook or spellbook.m_iClassname ~= "tf_weapon_spellbook") then return; end -- We add a small delay to prevent frame perfect spell cast then spell switch which avoids -- paying mana for the spell just cast (SPELL_CHOOSING has a mana cost of 0) timer.Create(0.25, function() SelectSpell(spellbook, value, GetSpellData(player, nil, value, "charges", true), GetSpellData(player, nil, value, "roll_time", false), false, true); end, 1); end base_spell_menu = { timeout = 0, title = "Choose a Spell!", itemsPerPage = nil, flags = MENUFLAG_BUTTON_EXIT, onSelect = OnSpellMenuSelect, onCancel = OnMenuCancel, }; debug_spell_menu = { timeout = 0, title = "[DEBUG] Choose a Spell", itemsPerPage = nil, flags = MENUFLAG_BUTTON_EXIT, onSelect = OnSpellMenuSelect, onCancel = OnMenuCancel, }; -- Populate debug menu with all spells for spell, data in pairs(spell_data) do debug_spell_menu[spell+1] = {text=data.name, value=spell, disabled=false}; end -- Create a menu for a player based on their spell upgrade unlocks function CreateSpellMenuForPlayer(player) if (not IsValidRealPlayer(player)) then return; end local userid = player:GetUserId(); local new_menu = {}; -- Inherit from base spell menu for key, val in pairs(base_spell_menu) do new_menu[key] = val; end -- Populate with player's unlocked spells for spell, playerspelldata in pairs(player_list[userid].upgrades_spell_data) do if (playerspelldata._id and playerspelldata.name) then new_menu[playerspelldata._id] = {text=playerspelldata.name, value=spell, disabled=false}; elseif (playerspelldata._id) then new_menu[playerspelldata._id] = {text=spell_data[spell].name, value=spell, disabled=false}; end end return new_menu; end -- Clear the player's screen of hud text function DisplayClearHud(player) for i=2,5 do player:ShowHudTextSimple("", i, 0, 0, CLR_WHITE, 0, 0, 0.015); end end function DisplayManaWizardHud(player, spellbook, playerdata, current_mana_cost) -- Get our the spellbook's current spell local current_spell = spell_data[spellbook.m_iSelectedSpellIndex].name; if (spellbook._m_iCustomSelectedSpellIndex) then current_spell = spell_data[spellbook._m_iCustomSelectedSpellIndex].name; end if (playerdata.current_mana - current_mana_cost >= 0) then player:ShowHudTextSimple("Mana: "..playerdata.current_mana.." [+"..playerdata.mana_regen_rate.."/s]", 3, .78, .75, CLR_BLUE); else player:ShowHudTextSimple("Mana: "..playerdata.current_mana.." [+"..playerdata.mana_regen_rate.."/s]", 3, .78, .75, CLR_RED); end player:ShowHudTextSimple("Cost: "..current_mana_cost, 4, .78, .8, CLR_RED); player:ShowHudTextSimple("Press 'Reload' to select a spell!", 5, .8, .85, CLR_LIMEGREEN); end function DisplayRollsWizardHud(player, spellbook) -- Get our the spellbook's current spell name local current_spell = spell_data[spellbook.m_iSelectedSpellIndex].name; if (spellbook._m_iCustomSelectedSpellIndex) then current_spell = spell_data[spellbook._m_iCustomSelectedSpellIndex].name; end if (spellbook.m_iSpellCharges == 0 and current_spell ~= spell_data[SPELL_CHOOSING].name) then current_spell = "None"; end player:ShowHudTextSimple("Spell: "..current_spell, 3, .78, .7, CLR_WHITE); player:ShowHudTextSimple("Next Common: "..common_timer_value, 4, .78, .75, CLR_PURPLE); player:ShowHudTextSimple("Next Rare: "..rare_timer_value, 5, .78, .8, CLR_ORANGE); end ------------------------------------------------------------------------------------------------------------- ------------------------------------------------ CALLBACKS -------------------------------------------------- ------------------------------------------------------------------------------------------------------------- -- Called when a spell projectile spawns function OnSpellProjectileSpawned(entity) local player = entity.m_hOwnerEntity; if (not IsValidRealPlayer(player) or not player:IsWizard()) then return; end -- Projectiles spawned by Meteor Shower should be ignored if (entity.m_hLauncher == entity.m_hOwnerEntity and entity.m_iClassname == "tf_projectile_spellfireball") then return; end local userid = player:GetUserId(); local playerdata = player_list[userid]; local spellbook = player:GetPlayerItemBySlot(LOADOUT_POSITION_ACTION); if (not spellbook or spellbook.m_iClassname ~= "tf_weapon_spellbook") then return; end local spell = spellbook.m_iSelectedSpellIndex; local customspell = spellbook._m_iCustomSelectedSpellIndex; if (playerdata.wizard_type == WIZARD_USE_MANA) then -- Get the mana cost of this spell local current_mana_cost = 0; if (spell >= 0 or (customspell and customspell >= 0)) then local cost = GetSpellData(player, spell, customspell, "mana_cost", true); if (cost) then current_mana_cost = cost; end end -- Not enough mana for this spell if (playerdata.current_mana - current_mana_cost < 0) then local player_origin = player:GetAbsOrigin(); player:TakeDamage({ Attacker = player, Inflictor = entity, Weapon = player.m_hActiveWeapon, Damage = 25, DamageType = DMG_SHOCK, DamageCustom = TF_DMG_CUSTOM_NONE, DamagePosition = player_origin, DamageForce = Vector(0,0,0), ReportedPosition = player_origin, }); entity:Remove(); player:PlaySoundToSelf("Halloween.spell_lightning_cast"); player:Print(PRINT_TARGET_CENTER, "Not enough mana!"); return; end -- Valid spell cast playerdata.current_mana = playerdata.current_mana - current_mana_cost; end -- Call customspell spawn callback if available if (spellbook and customspell) then entity:Remove(); local spawnfunc = spell_data[customspell].SpawnFunction; if (spawnfunc) then spawnfunc(player, spellbook) end end end -- Called when a spell projectile is created function OnSpellProjectileCreated(entity, classname) entity:AddCallback(ON_SPAWN, OnSpellProjectileSpawned); end local badplayers = {}; -- Called every game tick (15ms) function OnGameTick() -- Fall back just in case OnPlayerDisconnected doesn't fire for some reason if (table.Count(badplayers) > 0) then for index, userid in pairs(badplayers) do player_list[userid] = nil; end badplayers = {}; end -- RNG Spells seconds timer if ((debug or midwave) and TickCount() % tickrate == 0) then common_timer_value = common_timer_value - 1 rare_timer_value = rare_timer_value - 1 -- We check rare first because it should take priority if their timers both land on the same second if (rare_timer_value == 0) then GiveWizardsSpell(SPELL_TYPE_RARE); rare_timer_value = rare_spell_time; -- Reset the common timer if we skipped over it executing if (common_timer_value == 0) then common_timer_value = common_spell_time; end elseif (common_timer_value == 0) then GiveWizardsSpell(SPELL_TYPE_COMMON); common_timer_value = common_spell_time; end end -- Loop through our human players for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid); -- Degenerate player handle, store and continue if (not IsValidRealPlayer(player)) then table.insert(badplayers, userid); goto continue; end -- Spectators don't need to be handled if (player.m_iTeamNum == TEAM_SPECTATOR) then goto continue; end if (player.m_iClass == TF_CLASS_ENGINEER) then if (not player:IsWizard()) then goto continue; end -- Mana regen if (TickCount() % tickrate == 0) then if (playerdata.current_mana + playerdata.mana_regen_rate <= playerdata.max_mana) then playerdata.current_mana = playerdata.current_mana + playerdata.mana_regen_rate; else playerdata.current_mana = playerdata.max_mana; end end local spellbook = player:GetPlayerItemBySlot(LOADOUT_POSITION_ACTION); if (not spellbook or spellbook.m_iClassname ~= "tf_weapon_spellbook") then goto continue; end local spell = spellbook.m_iSelectedSpellIndex; local customspell = spellbook._m_iCustomSelectedSpellIndex; local lastspell = customspell or spell or SPELL_NONE; -- Re-roll our last spell when we run out if ((debug or midwave) and not playerdata.spell_reroll_timer and playerdata.wizard_type == WIZARD_USE_MANA and lastspell >= 0 and spellbook.m_iSpellCharges == 0) then playerdata.spell_reroll_timer = timer.Create(0.5, function() if (not IsValid(spellbook) or not IsValidRealPlayer(player)) then if (playerdata and playerdata.spell_roll_timer) then playerdata.spell_reroll_timer = nil; end return; end -- Only reroll the spell if we aren't already choosing by the time this runs if (spellbook.m_iSelectedSpellIndex ~= SPELL_CHOOSING) then SelectSpell(spellbook, lastspell, GetSpellData(player, nil, lastspell, "charges", true), GetSpellData(player, nil, lastspell, "roll_time", false), false, true); end playerdata.spell_reroll_timer = nil; end, 1); end -- Get our spell's mana cost local current_mana_cost = GetSpellData(player, spell, customspell, "mana_cost", true); if (not current_mana_cost) then current_mana_cost = 0; end -- HUD display if (TickCount() % tickrate == 0) then if (playerdata.wizard_type == WIZARD_USE_MANA) then DisplayManaWizardHud(player, spellbook, playerdata, current_mana_cost); elseif (playerdata.wizard_type == WIZARD_USE_ROLLS) then DisplayRollsWizardHud(player, spellbook); end end end ::continue:: end end -- Called on player key press function OnPlayerKey(player, key) if (not IsValidAliveRealPlayer(player)) then return end; local userid = player:GetUserId(); local playerdata = player_list[userid]; local wep = player.m_hActiveWeapon; -- Reload if (key == IN_RELOAD) then if (player.m_iClass == TF_CLASS_ENGINEER) then if (not player:IsWizard() or playerdata.wizard_type ~= WIZARD_USE_MANA) then return; end if (not debug and player:CountUnlockedSpells() == 0) then player:Print(PRINT_TARGET_CENTER, "You haven't bought any spells from the Upgrades Station yet!"); return; end -- Display spell menu if (debug) then player:DisplayMenu(debug_spell_menu); playerdata.displaying_menu = debug_spell_menu; else local spellmenu = CreateSpellMenuForPlayer(player); player:DisplayMenu(spellmenu); playerdata.displaying_menu = spellmenu; end end end end -- Called when a player is given a fresh set of items (spawn, resupply, etc) function OnPlayerInventoryApplication(eventTable) local player = ents.GetPlayerByUserId(eventTable.userid); if (not IsValidRealPlayer(player)) then return; end local playerdata = player_list[eventTable.userid]; player.m_clrRender = math.rgbtoint(255, 255, 255); player:GiveItem("TF_WEAPON_SPELLBOOK"); if (player:IsWizard()) then GivePlayerWizardItems(player, player_list[eventTable.userid].wizard_type); end end -- Called on player spawn function OnPlayerSpawn(player) local userid = player:GetUserId(); local playerdata = player_list[userid]; -- Handle menu, hud, and wizard items player:HideMenu(playerdata.displaying_menu); DisplayClearHud(player); if (not player:IsWizard()) then player:ResetInventory(); else GivePlayerWizardItems(player, playerdata.wizard_type); end end -- Called before player takes damage function OnPlayerDamagedPre(player, damageinfo) local damagecustom = damageinfo.DamageCustom; local attacker = damageinfo.Attacker; local playerdata = player_list[player:GetUserId()]; -- Modify self damage in certain situations if (attacker == player) then -- Pumpkin MIRV spell if (damagecustom == TF_DMG_CUSTOM_SPELL_MIRV) then damageinfo.Damage = 0; return true; end end end -- Called on player connected to server function OnPlayerConnected(player) local userid = player:GetUserId(); if (player:IsRealPlayer()) then player_list[userid] = { displaying_menu = nil, -- What menu are we displaying currently? spell_roll_timer = nil, -- If we're rolling a spell as a mana wizard, the timer goes here base_mana = 1000, -- The base, unmodified mana for max_mana max_mana = 1000, -- Our max mana current_mana = 1000, -- Our current mana base_mana_regen_rate = 50, -- The base, unmodified regen rate for mana_regen_rate mana_regen_rate = 50, -- Our current mana regen rate per second wizard_type = WIZARD_NONE, -- What type of wizard are you Harry? upgrades_spelltype_data = {[SPELL_TYPE_COMMON]={mana_cost=1, roll_time=1}, -- Modifiers for spell upgrades [SPELL_TYPE_RARE]={mana_cost=1, roll_time=1} }, upgrades_spell_data = {}, -- Modified spell data from spell upgrades is_minified = false, -- Are we "minified"? spell_reroll_timer = nil, -- We used up all our spells, timer for rerolling goes here }; -- Populate player spell upgrades data for spell, data in pairs(spell_data) do player_list[userid].upgrades_spell_data[spell] = {}; end -- Callbacks player:AddCallback(ON_KEY_PRESSED, OnPlayerKey); player:AddCallback(ON_SPAWN, OnPlayerSpawn); player:AddCallback(ON_DAMAGE_RECEIVED_PRE, OnPlayerDamagedPre); -- Ensure player has no previous upgrades when connecting -- (Otherwise player data and upgrades become out of sync) player:RunScriptCode("activator.GrantOrRemoveAllUpgrades(true, false)", player); end end -- Called on player disconnected from server function OnPlayerDisconnected(player) local userid = player:GetUserId() local playerdata = player_list[userid]; -- Kill active timers if (player:IsRealPlayer() and playerdata) then pcall(timer.Stop, playerdata.spell_roll_timer); pcall(timer.Stop, playerdata.scout_tempent_timer); pcall(timer.Stop, playerdata.soldier_airborne_timer); pcall(timer.Stop, playerdata.pyro_aoeblast_timer); pcall(timer.Stop, playerdata.kinky_weighdown_timer); pcall(timer.Stop, playerdata.kinky_charge_timer); pcall(timer.Stop, playerdata.kinky_respawn_timer); pcall(timer.Stop, playerdata.demo_hatchet_cleaver_timer); pcall(timer.Stop, playerdata.demo_drunk_timer); pcall(timer.Stop, playerdata.heavy_botsignore_timer); player_list[userid] = nil; end end -- Called on mission wave initialization function OnWaveInit(wave) midwave = false; common_timer_value = common_spell_time; rare_timer_value = rare_spell_time; -- Loop through our human players for userid, playerdata in pairs(player_list) do local player = ents.GetPlayerByUserId(userid) player:HideMenu(playerdata.displaying_menu); local spellbook = player:GetPlayerItemBySlot(LOADOUT_POSITION_ACTION); if (spellbook and spellbook.m_iClassname == "tf_weapon_spellbook") then -- No spells during setup to prevent cheesing next wave SelectSpell(spellbook, SPELL_NONE, 0, 0, true, true); end end end -- Called on mission wave start function OnWaveStart(wave) midwave = true; end -- Add create callbacks for all the spell projectile entity classnames for ent, index in pairs(spell_projectile_class_map) do ents.AddCreateCallback(ent, OnSpellProjectileCreated); end AddEventCallback("post_inventory_application", OnPlayerInventoryApplication); ------------------------------------------------------------------------------------------------------------- ------------------------------------------------ UPGRADES --------------------------------------------------- ------------------------------------------------------------------------------------------------------------- function UpgradeWizardUseMana(value, activator, caller) local userid = activator:GetUserId(); if (not activator or not player_list[userid]) then return; end value = tonumber(value); if (value == UPGRADE_UPGRADE) then player_list[userid].wizard_type = WIZARD_USE_MANA; GivePlayerWizardItems(activator, player_list[userid].wizard_type); local melee = activator:GetPlayerItemBySlot(LOADOUT_POSITION_MELEE); melee:SetAttributeValue("damage bonus", nil); melee:SetAttributeValue("damage penalty", 0.5); elseif (value == UPGRADE_DOWNGRADE) then player_list[userid].wizard_type = WIZARD_NONE; activator:ResetInventory(); elseif (value == UPGRADE_RESTORE) then if (not activator:GetAttributeValue("zoom speed mod disabled") and not activator:GetAttributeValue("sniper no headshots")) then player_list[userid].wizard_type = WIZARD_NONE; activator:ResetInventory(); end end end function UpgradeWizardUseRolls(value, activator, caller) local userid = activator:GetUserId(); if (not activator or not player_list[userid]) then return; end value = tonumber(value); if (value == UPGRADE_UPGRADE) then player_list[userid].wizard_type = WIZARD_USE_ROLLS; GivePlayerWizardItems(activator, player_list[userid].wizard_type); elseif (value == UPGRADE_DOWNGRADE) then player_list[userid].wizard_type = WIZARD_NONE; activator:ResetInventory(); elseif (value == UPGRADE_RESTORE) then if (not activator:GetAttributeValue("sniper no headshots") and not activator:GetAttributeValue("zoom speed mod disabled")) then player_list[userid].wizard_type = WIZARD_NONE; activator:ResetInventory(); end end end function UpgradeWizardMaxMana(value, activator, caller) local userid = activator:GetUserId(); if (not activator or not player_list[userid]) then return; end value = tonumber(value); local basemana = player_list[userid].base_mana; local maxmana = player_list[userid].max_mana; local newmana = maxmana if (value == UPGRADE_UPGRADE) then newmana = player_list[userid].max_mana + (math.round(basemana * 1.25) - basemana); elseif (value == UPGRADE_DOWNGRADE) then newmana = basemana; elseif (value == UPGRADE_RESTORE) then local upgradeval = activator:GetAttributeValue("tag__summer2014"); if (not upgradeval) then newmana = basemana; elseif ((1 + upgradeval) * basemana ~= maxmana) then newmana = math.round((1 + upgradeval) * basemana); end end if (newmana < 0) then newmana = 0; end player_list[userid].max_mana = newmana; player_list[userid].current_mana = newmana; end function UpgradeWizardManaRegen(value, activator, caller) local userid = activator:GetUserId(); if (not activator or not player_list[userid]) then return; end value = tonumber(value); local baseregen = player_list[userid].base_mana_regen_rate; local newregen = player_list[userid].mana_regen_rate; if (value == UPGRADE_UPGRADE) then newregen = player_list[userid].mana_regen_rate + 5; elseif (value == UPGRADE_DOWNGRADE) then newregen = baseregen; elseif (value == UPGRADE_RESTORE) then local upgradeval = activator:GetAttributeValue("elevate to unusual if applicable"); if (not upgradeval) then newregen = baseregen; elseif (baseregen + upgradeval ~= newregen) then newregen = math.round(baseregen + upgradeval); end end if (newregen < 0) then newregen = 0; end player_list[userid].mana_regen_rate = newregen; end function HandleUpgradeUnlock(upgradetype, activator, spell, attribute) if (not activator or not player_list[activator:GetUserId()]) then return; end local userid = activator:GetUserId(); local playerspelldata = player_list[userid].upgrades_spell_data; upgradetype = tonumber(upgradetype); local newval = playerspelldata[spell]; if (upgradetype == UPGRADE_UPGRADE) then newval = {_id=activator:CountUnlockedSpells()+1} elseif (upgradetype == UPGRADE_DOWNGRADE) then newval = {}; elseif (upgradetype == UPGRADE_RESTORE) then local upgradeval = activator:GetAttributeValue(attribute); if (not upgradeval) then newval = {}; end end playerspelldata[spell] = newval; end function HandleUpgradeValue(upgradetype, activator, modvalue, spelltype, attribute, data) if (not activator or not player_list[activator:GetUserId()]) then return; end local userid = activator:GetUserId(); local playerspelltypedata = player_list[userid].upgrades_spelltype_data; upgradetype = tonumber(upgradetype); local newval = playerspelltypedata[spelltype][data]; if (upgradetype == UPGRADE_UPGRADE) then newval = newval - (1 - 1 * modvalue); elseif (upgradetype == UPGRADE_DOWNGRADE) then newval = 1; elseif (upgradetype == UPGRADE_RESTORE) then local upgradeval = activator:GetAttributeValue(attribute) if (not upgradeval) then newval = 1; elseif (1 * upgradeval ~= newval) then newval = math.round(1 * newval); end end playerspelltypedata[spelltype][data] = newval; return true; end function UpgradeSpellbookCommonsManaCost(value, activator, caller) HandleUpgradeValue(value, activator, 0.9, SPELL_TYPE_COMMON, "custom texture lo", "mana_cost"); end function UpgradeSpellbookCommonsRollTime(value, activator, caller) HandleUpgradeValue(value, activator, 0.9, SPELL_TYPE_COMMON, "cannot trade", "roll_time"); end function UpgradeSpellbookRaresManaCost(value, activator, caller) HandleUpgradeValue(value, activator, 0.9, SPELL_TYPE_RARE, "duel loser account id", "mana_cost"); end function UpgradeSpellbookRaresRollTime(value, activator, caller) HandleUpgradeValue(value, activator, 0.9, SPELL_TYPE_RARE, "event date", "roll_time"); end function UpgradeSpellbookUnlockFireball(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_FIREBALL, "DEPRECATED socketed item definition id DEPRECATED "); end function UpgradeSpellbookUnlockBallobats(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_BALLOBATS, "purchased"); end function UpgradeSpellbookUnlockHealingaura(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_HEALINGAURA, "gifter account id"); end function UpgradeSpellbookUnlockPumpkinmirv(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_PUMPKINMIRV, "referenced item id high"); end function UpgradeSpellbookUnlockSuperjump(value, activator, caller) if (not activator or not player_list[activator:GetUserId()]) then return; end HandleUpgradeUnlock(value, activator, SPELL_SUPERJUMP, "halloween item"); local upgradetype = tonumber(value); if (upgradetype == UPGRADE_UPGRADE) then activator:SetAttributeValue("cancel falling damage", 1); elseif (upgradetype == UPGRADE_DOWNGRADE) then activator:SetAttributeValue("cancel falling damage", 0); elseif (upgradetype == UPGRADE_RESTORE) then local upgradeval = activator:GetAttributeValue("halloween item"); if (not upgradeval) then activator:SetAttributeValue("cancel falling damage", 0); end end end function UpgradeSpellbookUnlockInvisibility(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_INVISIBILITY, "force level display"); end function UpgradeSpellbookUnlockTeleport(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_TELEPORT, "unique craft index"); end function UpgradeSpellbookUnlockTeslabolt(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_TESLABOLT, "unlimited quantity"); end function UpgradeSpellbookUnlockMinify(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_MINIFY, "strange part new counter ID"); end function UpgradeSpellbookUnlockMeteorshower(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_METEORSHOWER, "pyro year number"); end function UpgradeSpellbookUnlockSummonmonoculus(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_SUMMONMONOCULUS, "zombiezombiezombiezombie"); end function UpgradeSpellbookUnlockSummonmonskeletons(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_SUMMONSKELETONS, "strange restriction type 2"); end function UpgradeSpellbookUnlockCrocket(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_CUSTOM_CROCKET, "sniper no charge"); end function UpgradeSpellbookUnlockGravitybomb(value, activator, caller) HandleUpgradeUnlock(value, activator, SPELL_CUSTOM_GRAVITYBOMB, "strange restriction user value 1"); end