/* finish spell implementation melee functionality frostshot needs some more oomph to it clean up wearable logic implement WIZARD_RAND finish special vanilla weapon functionality create system for custom weapons for custom weapons and rand wizard, choose through chat commands for now review and cleanup port to an old winterbridge mission and playtest work on the menu system for custom upgrades and weapons Fireball - copy of valve spell Frost Shot - basically same as fireball but slows enemies, does less damage, and has an arc Chain Lightning - trace a bolt of lightning to crosshair, if it hits chain to nearby enemies (limit per chain and total chains) Pumpkin MIRV - copy of valve spell Meteor Shower - copy of valve spell Reanimate - copy of valve spell (summon skeletons) Black Hole - lob an arced projectile that shatters on impact and spawns a blackhole, sucking small enemies in and damaging periodically - before dealing large damage in a radius and exploding them a short distance upward Tornado - basically a very slow rocket but with one of those tornado particles, - throws any small enemies in its path away and stuns them for 5 seconds */ ::ROOT <- getroottable(); if (!("ConstantNamingConvention" in ROOT)) { foreach(a, b in Constants) foreach(k, v in b) ROOT[k] <- v != null ? v : 0; } foreach(k, v in ::NetProps.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::NetProps[k].bindenv(::NetProps); foreach(k, v in ::Entities.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::Entities[k].bindenv(::Entities); foreach(k, v in ::EntityOutputs.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::EntityOutputs[k].bindenv(::EntityOutputs); foreach(k, v in ::NavMesh.getclass()) if (k != "IsValid" && !(k in ROOT)) ROOT[k] <- ::NavMesh[k].bindenv(::NavMesh); // Weapon slots const SLOT_PRIMARY = 0; const SLOT_SECONDARY = 1; const SLOT_MELEE = 2; const SLOT_UTILITY = 3; const SLOT_BUILDING = 4; const SLOT_PDA = 5; const SLOT_PDA2 = 6; const SLOT_COUNT = 7; const WIZARD_NONE = 0; const WIZARD_RAND = 1; const WIZARD_MANA = 2; const SPELL_FIREBALL = 0; ::VectorAngles <- function(forward) { local yaw, pitch if ( forward.y == 0.0 && forward.x == 0.0 ) { yaw = 0.0 if (forward.z > 0.0) pitch = 270.0 else pitch = 90.0 } else { yaw = (atan2(forward.y, forward.x) * 180.0 / Pi) if (yaw < 0.0) yaw += 360.0 pitch = (atan2(-forward.z, forward.Length2D()) * 180.0 / Pi) if (pitch < 0.0) pitch += 360.0 } return QAngle(pitch, yaw, 0.0) } ::DisableViewcontrolSafe <- function(player, viewcontrol) { EntFireByHandle(player, "RunScriptCode", "self.GetScriptScope().__ls<-GetPropInt(self,`m_lifeState`);SetPropInt(self,`m_lifeState`,0)", -1, player, player) EntFireByHandle(viewcontrol, "RunScriptCode", "SetPropEntity(self, `m_hPlayer`, activator)", -1, player, player) EntFireByHandle(viewcontrol, "Disable", null, -1, player, player) EntFireByHandle(player, "RunScriptCode", "SetPropInt(self,`m_lifeState`,self.GetScriptScope().__ls);SetPropInt(self,`m_takedamage`,2)", -1, player, player) } ::VectorsEqual <- function(v1, v2, tolerance=0.05) { foreach (k, v in v1) if (fabs(v - v2[k]) > tolerance) return false; return true; } ::degtorad <- function(deg) return deg * (PI / 180); ::radtodeg <- function(rad) return rad * (180 / PI); ::Ravenous <- { function CleanupPlayerWearables(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); if (!("extra_wearables" in scope)) return; foreach (wearable in scope.extra_wearables) if (wearable.IsValid()) EntFireByHandle(wearable, "Kill", "", -1, null, null); delete scope.extra_wearables; }, function CleanupWearables() { DoEntFire("__extra_wearable", "Kill", "", -1, null, null); for (local i = 1; i <= MAX_CLIENTS; ++i) { local player = PlayerInstanceFromIndex(i); if (!player) continue; CleanupPlayerWearables(player); } }, function Cleanup(init=false) { if (::Ravenous) { CleanupWearables(); for (local i = 1; i <= MAX_CLIENTS; ++i) { local player = PlayerInstanceFromIndex(i); if (!player) continue; SetEntityColor(player, 255, 255, 255, 255); } if (!init) delete ::Ravenous; } }, OnGameEvent_recalculate_holidays = function(_) { if (GetRoundState() == 3) Cleanup() }, OnGameEvent_mvm_wave_complete = function(_) { Cleanup() }, SCRIPT_ENTITY = FindByName(null, "__ravenous_entity"), GAMERULES = FindByClassname(null, "tf_gamerules"), MAX_CLIENTS = MaxClients().tointeger(), STRING_NETPROP_ITEMDEF = "m_AttributeManager.m_Item.m_iItemDefinitionIndex", CLASSES = ["", "scout", "sniper", "soldier", "demo", "medic", "heavy", "pyro", "spy", "engineer", "civilian"], NPCS = ["headless_hatman", "eyeball_boss", "merasmus", "tf_zombie"], BAR = "█", DISPLAY_TICKRATE = 1.0, WaveStarted = false, FirstSpawn = false, DefaultPlayerScope = { previous_currency = null, sodarevertattrs = {}, loadout = null, wearable = null, // todo give player extra_wearables as well instead of this nextloadoutcheck = 0, buttons_last = 0, sodachecktime = 0, rjumpnexttime = 0, rjumping = false, animseqs = null, seqfinishtime = null, currentseq = 0, wizard_type = WIZARD_MANA, // todo for testing, change to WIZARD_NONE nexthudtime = 0, max_mana = 1000, mana = 1000, mana_regen = 50, selected_spell = 0, spell_charges = 0, nextspellcasttime = 0, spell_regen_ticks = 0, reset_spell_regen = false, hand_particle = null, spell_weapon = null, choosing_spell = false, chosen_spell = null, viewcontrol = null, lastangles = null, cursorx = 0, cursory = 0, teleporter_proxy = null, }, SpellData = [ { name = "Fireball", max_charges = 2, regen_duration = 4, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 300, is_rare = false, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_rocketlauncher", weapon_id = 18, secondary_attack = false, weapon_attributes = {"damage bonus":2}, dmgcustom = TF_DMG_CUSTOM_SPELL_FIREBALL, hand_particle = "buildingdamage_fire3", projectile_particle = "spell_fireball_small_red", explode_particle = null, cast_vo = "vo/engineer_sf13_spell_bombhead01.mp3", deploy_sound = "player/taunt_fire.wav", cast_sound = "misc/halloween/spell_fireball_cast.wav", explode_sound = "misc/halloween/spell_fireball_impact.wav", oncast = null, // @(owner) onthink = null, // @(self) onexplode = null, // @(self) overlay = "vgui/spell_overlay_fireball.vmt", }, { name = "Frostshot", max_charges = 4, regen_duration = 3, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 200, is_rare = false, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_grenadelauncher", weapon_id = 19, secondary_attack = false, weapon_attributes = {"sticky air burst mode":2}, dmgcustom = TF_DMG_CUSTOM_SPELL_FIREBALL, hand_particle = "weapon_unusual_cool_powerjack", projectile_particle = "spell_fireball_small_trail_blue", explode_particle = "xms_snowburst", cast_vo = "vo/engineer_sf13_spell_generic04.mp3", deploy_sound = "weapons/icicle_freeze_victim_01.wav", cast_sound = "misc/halloween/spell_fireball_cast.wav", explode_sound = "misc/jingle_bells/jingle_bells_nm_03.wav", oncast = null, onthink = null, onexplode = function(self) { local origin = self.GetOrigin(); local owner = self.GetOwner(); if (!owner) return; origin.z += 32; for (local player; player = FindByClassnameWithin(player, "player", origin, 128);) { if (!player || !player.IsAlive() || player.GetTeam() == self.GetTeam() || player.IsMiniBoss()) continue; player.StunPlayer(4.0, 0.9, 1, owner); player.EmitSound("weapons/icicle_freeze_victim_01.wav"); // todo might need to put this outside loop player.TakeDamageCustom(owner, owner, null, Vector(), Vector(), 50, DMG_BLAST, TF_DMG_CUSTOM_SPELL_FIREBALL); Ravenous.SetEntityColor(player, 30, 70, 200, 255); EntFireByHandle(player, "RunScriptCode", "Ravenous.SetEntityColor(self, 255, 255, 255, 255)", 4.0, null, null); } }, overlay = "vgui/spell_overlay_frostshot.vmt", }, { name = "Lightning Surge", max_charges = 1, regen_duration = 5, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 400, is_rare = false, animseqs = [["spell_draw", 0.25, 3], ["spell_fire", 0.5, 2]], weapon_class = "tf_weapon_cleaver", // Doesn't have viewpunch like GL, weapon_id = 812, secondary_attack=false, weapon_attributes = {}, hand_particle = "unusual_electric_parent_white", projectile_particle = null, explode_particle = null, cast_vo = "vo/engineer_sf13_spell_teleport_self01.mp3", deploy_sound = "ambient/energy/zap1.wav", cast_sound = null, explode_sound = null, oncast = function(owner) { local eyepos = owner.EyePosition(); local trace = { start = eyepos, end = eyepos + owner.EyeAngles().Forward() * 8192, ignore = owner, }; TraceLineEx(trace); if (!("enthit" in trace) || !trace.enthit.IsPlayer() || trace.enthit.GetTeam() == owner.GetTeam() || trace.enthit.GetTeam() == TEAM_SPECTATOR) { owner.ValidateScriptScope(); local scope = owner.GetScriptScope(); scope.mana += mana_cost / 2; owner.EmitSound("weapons/barret_arm_fizzle.wav"); return; } local ent = trace.enthit; local players = {[ent]=ent}; local lastplayer = ent; for (local i = 0; i < 4; ++i) { local origin = lastplayer.GetOrigin(); local victim = null; for (local player; player = FindByClassnameWithin(player, "player", origin, 256.0);) { if (!player || !player.IsAlive() || player.GetTeam() == owner.GetTeam() || player.IsMiniBoss()) continue; if (player in players) continue; players[player] <- player; victim = player; lastplayer = victim; break; } if (!victim) break; } if (ent.IsMiniBoss()) delete players[ent]; foreach (victim in players) if (victim) victim.TakeDamageCustom(owner, owner, null, Vector(), Vector(), victim.GetHealth() * 4, DMG_ACID, TF_DMG_CUSTOM_SPELL_LIGHTNING); }, onthink = function(self) { self.Destroy(); }, onexplode = null, overlay = "vgui/spell_overlay_lightning.vmt", }, { name = "Pumpkin MIRV", max_charges = 1, regen_duration = 4, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 300, is_rare = false, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_grenadelauncher", weapon_id = 19, secondary_attack = false, weapon_attributes = {"sticky air burst mode":2, "damage penalty":0.001}, dmgcustom = TF_DMG_CUSTOM_SPELL_FIREBALL, hand_particle = "unusual_cubancigar_glow", projectile_particle = "spell_fireball_small_red", explode_particle = null, cast_vo = "vo/engineer_sf13_spell_devil_bargain01.mp3", deploy_sound = "player/taunt_fire.wav", cast_sound = "misc/halloween/spell_mirv_cast.wav", explode_sound = "misc/halloween/spell_mirv_explode_primary.wav", oncast = null, onthink = null, onexplode = function(self) { local origin = self.GetOrigin(); local owner = self.GetOwner(); if (!owner) return; local radius = 128; local offsets = [[-1, 0], [-0.5, 0.9], [0.5, 0.9], [1, 0], [0.5, -0.9], [-0.5, -0.9]]; foreach (xy in offsets) { local offset = origin + Vector(radius * xy[0], radius * xy[1], 64); local tracehull = { start = offset, end = offset + Vector(0, 0, -256), hullmin = Vector(-16, -16, -16), hullmax = Vector(16, 16, 16), ignore = owner, mask = 100679691, }; TraceHull(tracehull); if (("startsolid" in tracehull && tracehull.startsolid) || !("enthit" in tracehull)) continue; local o = tracehull.pos + Vector(0, 0, -16); local ent = SpawnEntityFromTable("tf_generic_bomb", { origin = o, model = "models/props_halloween/pumpkin_explode_teamcolor.mdl", modelscale = 0.85, explode_particle = "pumpkin_explode", sound = format("items/pumpkin_explode%d.wav", RandomInt(1, 3)), skin = 1, team = 2, damage = 150, radius = 128.0, health = 1.0, friendlyfire = 1, }); DispatchSpawn(ent); ent.SetCollisionGroup(2); } }, overlay = "vgui/spell_overlay_pumpkinmirv.vmt", }, { name = "Reanimate", max_charges = 2, regen_duration = 5, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 500, is_rare = true, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_grenadelauncher", weapon_id = 19, secondary_attack = false, weapon_attributes = {"sticky air burst mode":2}, dmgcustom = TF_DMG_CUSTOM_SPELL_FIREBALL, hand_particle = "unusual_devilish_headshadow_red", projectile_particle = "spell_teleport_red", explode_particle = null, cast_vo = "vo/engineer_sf13_spell_zombie_horde01.mp3", deploy_sound = "ambient/hell/hell_rumbles_03.wav", cast_sound = "misc/halloween/spell_skeleton_horde_cast.wav", explode_sound = "misc/halloween/spell_skeleton_horde_rise.wav", oncast = null, onthink = null, onexplode = function(self) { local origin = self.GetOrigin(); local owner = self.GetOwner(); if (!owner) return; local area = GetNavArea(origin, 128.0); if (!area) { owner.ValidateScriptScope(); local scope = owner.GetScriptScope(); scope.mana += mana_cost; return; } local sizex = area.GetSizeX(); local sizey = area.GetSizeY(); local size = sizex * sizey; local override = null; if (size > 150000) override = 0; else if (size < 20000 || sizex <= 50 || sizey <= 50) override = 1; for (local i = 0; i < 3; ++i) { local spawn = null; if (override == 0) spawn = area.GetCenter(); else if (override == 1) { local adj = area.GetAdjacentArea(i, 0); if (adj) spawn = adj.GetCenter(); else spawn = area.GetCenter(); } else spawn = area.FindRandomSpot(); local zombie = SpawnEntityFromTable("tf_zombie", { origin = spawn, }); zombie.SetTeam(2); // do this below in script think for the little ones zombie.SetSkin(0); zombie.SetSolid(0); zombie.SetOwner(self.GetOwner()); EntFireByHandle(zombie, "Kill", "", 10, null, null); //todo implement zombie limits } }, overlay = "vgui/spell_overlay_reanimate.vmt", }, { name = "Meteor Shower", max_charges = 1, regen_duration = 5, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 700, is_rare = true, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_grenadelauncher", weapon_id = 19, secondary_attack = false, weapon_attributes = {"sticky air burst mode":2}, dmgcustom = TF_DMG_CUSTOM_SPELL_METEOR, hand_particle = "buildingdamage_fire3", projectile_particle = "spell_fireball_small_red", explode_particle = null, cast_vo = "vo/engineer_sf13_spell_super_speed01.mp3", deploy_sound = "player/taunt_fire.wav", cast_sound = "misc/halloween/spell_meteor_cast.wav", explode_sound = "misc/halloween/spell_meteor_impact.wav", oncast = null, onthink = null, onexplode = function(self) { local origin = self.GetOrigin(); origin.z += 256.0; local owner = self.GetOwner(); if (!owner) return; for (local i = 0; i < 15; ++i) { local o = origin + Vector(RandomInt(-64, 64), RandomInt(-64, 64), 0); local a = QAngle(90, 0, 0) + QAngle(RandomInt(-20, 20), RandomInt(-45, 45), RandomInt(-90, 90)); EntFireByHandle(owner, "RunScriptCode", format(@"Ravenous.CastSpell(self, Vector(%f, %f, %f), QAngle(%f, %f, %f), 0, { dmgcustom = TF_DMG_CUSTOM_SPELL_METEOR, })", o.x, o.y, o.z, a.x, a.y, a.z), i * 0.25, null, null); } }, overlay = "vgui/spell_overlay_meteorshower.vmt", }, { name = "Tornado", max_charges = 1, regen_duration = 5, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 700, is_rare = true, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_grenadelauncher", weapon_id = 19, secondary_attack = false, weapon_attributes = {"sticky air burst mode":2}, dmgcustom = TF_DMG_CUSTOM_SPELL_BLASTJUMP, hand_particle = "unusual_icetornado_purple_tornado", projectile_particle = "spell_teleport_black", explode_particle = null, cast_vo = "vo/engineer_sf13_spell_earthquake01.mp3", deploy_sound = "ambient_mp3/wind_gust1.mp3", cast_sound = "misc/halloween/spell_fireball_cast.wav", explode_sound = null, oncast = null, onthink = null, onexplode = function(self) { local origin = self.GetOrigin(); local owner = self.GetOwner(); if (!owner) return; local radius = 32; local offsets = [[-1, 0], [-0.5, 0.9], [0.5, 0.9], [1, 0], [0.5, -0.9], [-0.5, -0.9]]; local particles = []; foreach (xy in offsets) { local offset = origin + Vector(radius * xy[0], radius * xy[1], 32); local trace = { start = offset, end = offset + Vector(0, 0, -256), ignore = owner, mask = 100679691, }; TraceLineEx(trace); if (("startsolid" in trace && trace.startsolid) || !("enthit" in trace)) continue; local o = trace.pos; local particle = SpawnEntityFromTable("info_particle_system", { start_active = true, effect_name = "utaunt_tornado_parent_black" }); particle.SetAbsOrigin(o); particles.append(particle); } if (particles.len()) { foreach (particle in particles) EntFireByHandle(particle, "Kill", "", 10, null, null); local p = particles[0]; p.ValidateScriptScope(); p.GetScriptScope().Think <- function() { for (local player; player = FindByClassnameWithin(player, "player", origin, 192);) { if (!player || !player.IsAlive() || player.GetTeam() == owner.GetTeam() || player.IsMiniBoss()) continue; player.TakeDamageCustom(owner, owner, null, Vector(), Vector(), 50, DMG_SLASH, TF_DMG_CUSTOM_SPELL_BLASTJUMP); SetPropEntity(player, "m_hGroundEntity", null); local newpos = player.GetOrigin() + Vector(RandomInt(-64, 64), RandomInt(-64, 64), RandomInt(128, 256)); player.SetAbsVelocity((newpos - player.GetOrigin()) * 4); } }; AddThinkToEnt(p, "Think"); } }, overlay = "vgui/spell_overlay_tornado.vmt", }, { name = "Black Hole", max_charges = 1, regen_duration = 5, // Number of display ticks based on DISPLAY_TICKRATE mana_cost = 700, is_rare = true, animseqs = [["spell_draw", 0.4, 2], ["spell_fire", 0.7, 1.0]], weapon_class = "tf_weapon_grenadelauncher", weapon_id = 19, secondary_attack = false, weapon_attributes = {"sticky air burst mode":2}, dmgcustom = TF_DMG_CUSTOM_SPELL_FIREBALL, hand_particle = "unusual_eyeboss_vortex", // todo these particle dont really match projectile_particle = "spell_teleport_black", explode_particle = null, cast_vo = "vo/engineer_sf13_spell_lightning_bolt01.mp3", deploy_sound = "misc/halloween/spell_teleport.wav", cast_sound = "misc/halloween/spell_fireball_cast.wav", explode_sound = null, oncast = null, onthink = null, onexplode = function(self) { local origin = self.GetOrigin(); origin.z += 8; local owner = self.GetOwner(); if (!owner) return; local particle = SpawnEntityFromTable("info_particle_system", { start_active = true, effect_name = "utaunt_portalswirl_purple_parent" }); particle.SetAbsOrigin(origin); EntFireByHandle(particle, "Kill", "", 10, null, null); particle.ValidateScriptScope(); local scope = particle.GetScriptScope(); scope.nextdamagetick <- Time(); scope.Think <- function() { for (local player; player = FindByClassnameWithin(player, "player", origin, 192);) { if (!player || !player.IsAlive() || player.GetTeam() == owner.GetTeam() || player.IsMiniBoss()) continue; if (Time() >= nextdamagetick) player.TakeDamageCustom(owner, owner, null, Vector(), Vector(), 20, DMG_SLASH, TF_DMG_CUSTOM_SPELL_LIGHTNING); SetPropEntity(player, "m_hGroundEntity", null); local vec = origin - player.GetOrigin(); local dist = vec.Length(); vec.Norm(); player.SetAbsVelocity(vec * pow(dist, 1.25)); } if (Time() >= nextdamagetick) nextdamagetick = Time() + 0.5; return -1; }; AddThinkToEnt(particle, "Think"); Ravenous.SetDestroyCallback(particle, function() { DispatchParticleEffect("rd_robot_explosion_smoke_linger", origin, Vector(1, 0, 0)); for (local player; player = FindByClassnameWithin(player, "player", origin, 192);) { if (!player || !player.IsAlive() || player.GetTeam() == owner.GetTeam()) continue; player.TakeDamageCustom(owner, owner, null, Vector(), Vector(), 300, DMG_BLAST, TF_DMG_CUSTOM_SPELL_LIGHTNING); } }); }, overlay = "vgui/spell_overlay_blackhole.vmt", }, ], ItemWhitelistEnabled = true, ItemWhitelist = { //"tf_weapon_rocketlauncher" : null, // DEBUG DEBUG DEBUG "tf_wearable" : null, "saxxy" : null, "tf_weapon_lunchbox_drink" : null, "tf_weapon_jar_milk" : null, "tf_weapon_cleaver" : null, "tf_weapon_bat" : null, "tf_weapon_bat_wood" : null, "tf_weapon_bat_fish" : null, "tf_weapon_bat_giftwrap" : null, "tf_weapon_buff_item" : null, "tf_weapon_parachute" : null, "tf_weapon_parachute_primary" : null, "tf_weapon_parachute_secondary" : null, "tf_weapon_shovel" : null, "tf_weapon_katana" : null, "tf_weapon_jar_gas" : null, "tf_weapon_rocketpack" : null, "tf_weapon_fireaxe" : null, "tf_weapon_breakable_sign" : null, "tf_weapon_slap" : null, "tf_weapon_cannon" : null, "tf_wearable_demoshield" : null, "tf_weapon_bottle" : null, "tf_weapon_sword" : null, "tf_weapon_stickbomb" : null, "tf_weapon_lunchbox" : null, "tf_weapon_fists" : null, "tf_weapon_wrench" : null, "tf_weapon_robot_arm" : null, "tf_weapon_crossbow" : null, "tf_weapon_syringegun_medic" : null, "tf_weapon_bonesaw" : null, "tf_weapon_compound_bow" : null, "tf_wearable_razorback" : null, "tf_weapon_jar" : null, "tf_weapon_club" : null, "tf_weapon_knife" : null, "tf_weapon_pda_spy" : null, "tf_weapon_invis" : null, }, SkipWeapons = { "tf_weapon_spellbook": null, "tf_weapon_parachute" : null, "tf_weapon_parachute_primary" : null, "tf_weapon_parachute_secondary" : null, "tf_wearable_demoshield" : null, "tf_wearable_razorback" : null, }, WearableWeapons = { [133] = null, // Gunboats [444] = null, // Mantreads [405] = null, // Booties [608] = null, // Bootlegger [131] = null, // Chargin' Targe [406] = null, // Splendid Screen [1099] = null, // Tide Turner [1144] = null, // Festive Targe [57] = null, // Razorback [231] = null, // Danger Shield [642] = null, // Cozy Camper }, function SwitchToFirstAvailableWeapon(player) { if (!player || !player.IsValid()) return; for (local i = 0; i < SLOT_COUNT; ++i) { local wep = GetPropEntityArray(player, "m_hMyWeapons", i); if (!wep || !wep.IsValid()) continue; local cls = wep.GetClassname().tolower(); if (cls in SkipWeapons) continue; player.Weapon_Switch(wep); return; } }, ItemAttributes = { ////////////////////////////////////////// SCOUT ////////////////////////////////////////////// // Flying Guillotine "tf_weapon_cleaver" : { "dmg pierces resists absorbs": 1, "customattr|takedamage|mult bleeding dmg": 0.0, "customattr|takedamage|mult dmg vs giants": 3.0, // One-shots non-giants }, // Bat "tf_weapon_bat" : { "minicrits become crits": 1, "damage bonus": 1.5, }, // Holy Mackerel "tf_weapon_bat_fish" : { "minicrits become crits": 1, "damage bonus": 1.5, }, // Sandman "tf_weapon_bat_wood" : { // * No attack speed upgrade "damage bonus": 1.5, "max health additive penalty": 0, "customattr|takedamage|stun on hit": 1.0, }, // Wrap Assassin "tf_weapon_bat_giftwrap" : { // * No attack speed upgrade "damage penalty": 1, "damage bonus": 1.2, "effect bar recharge rate increased": 0.25, "customattr|takedamage|mult bleeding dmg": 6.0, }, // Candy Cane [317] = { "damage bonus": 1.3, "drop health pack on kill": 0, "dmg taken from blast increased": 1, "max health additive bonus": 75, "heal on hit for rapidfire": 10, }, // Boston Basher [325] = { "damage bonus": 1.62, "fire rate penalty": 1.5, "bleeding duration" : 8, "customattr|takedamage|mult bleeding dmg": 2.0, // Hitting enemies cleanses self bleed }, // Three Rune Blade [452] = { "damage bonus": 1.62, "fire rate penalty": 1.5, "bleeding duration" : 8, "customattr|takedamage|mult bleeding dmg": 2.0, }, // Sun on a Stick [349] = { "crit vs burning players": 1, "damage penalty": 1, // Ignite enemy for 1 second }, // Fan o' War [355] = { "damage penalty": 1, }, // Atomizer [450] = { "damage bonus": 1.3, "dmg penalty vs players": 1, "minicrits become crits": 1, }, ////////////////////////////////////////// SOLDIER ///////////////////////////////////////////// // Mantreads [444] = { "max health additive bonus": 50, "cancel falling damage": 1, "move speed bonus": 1.1667, // 280 (Demo speed) }, // Gunboats [133] = { "dmg from ranged reduced": 0.85, // 25% less damage taken from melee "fire retardant": 1, "damage force reduction": 0.001, }, // Shovel "tf_weapon_shovel" : { "damage bonus": 1.3, }, // Escape Plan [775] = { "provide on active": 1, "move speed bonus": 1.25, "speed_boost_on_hit_enemy": 3, "mod shovel speed boost": 0, "self mark for death": 0, "reduced_healing_from_medics": 1, }, // Equalizer [128] = { "damage bonus": 2, "fire rate penalty": 1.3, }, // Pain Train [154] = { "dmg taken from bullets increased": 1, "max health additive bonus": 25, "bleeding duration": 5, "customattr|takedamage|mult bleeding dmg": 2.0, }, // Half Zatoichi [357] = { // todo: make sure you cant glitch this value higher "critboost on kill": 1, "honorbound": 0, }, // Disciplinary Action [447] = { "dmg pierces resists absorbs": 1, // Force laugh on hit }, // Market Gardener [416] = { "no crit boost": 1, "customattr|takedamage|mult crit dmg": 2.0, }, //////////////////////////////////////////// PYRO ////////////////////////////////////////////// "tf_weapon_rocketpack" : { "switch from wep deploy time decreased": 0.25, }, // Fireaxe "tf_weapon_fireaxe" : { "bleeding duration" : 5, }, // Hot Hand "tf_weapon_slap" : { "crit from behind": 1, "damage penalty": 1, "speed_boost_on_hit_enemy": 3, "speed buff ally": 1 }, // Volcano Fragment [348] = { "damage penalty": 1, }, // Homewrecker [153] = { "damage penalty vs players": 1, "fire rate penalty": 1.5, "customattr|takedamage|stun on hit": 2.0, }, // Maul [466] = { "damage penalty vs players": 1, "fire rate penalty": 1.5, "customattr|takedamage|stun on hit": 2.0, }, // Axtinguisher [38] = { "damage penalty": 1, }, // Powerjack [214] = { "dmg taken increased": 0.85, "speed_boost_on_hit_enemy": 1, "max health additive bonus": 25, "damage penalty": 0.8, }, //////////////////////////////////////////// DEMO ////////////////////////////////////////////// "tf_weapon_bottle" : { "max health additive bonus": 25, "bleeding duration": 2, "customattr|takedamage|mult bleeding dmg": 2.0, "customattr|takedamage|mult crit dmg": 0.75, }, // Claidheamohmor [327] = { "fire rate bonus": 0.9, "bleeding duration": 5, "customattr|takedamage|mult bleeding dmg": 3.0, "melee range multiplier": 2, "max health additive bonus": 10, }, // Persian Persuader [404] = { "max health additive bonus": 25, }, [172] = { "customattr|takedamage|mult crit dmg": 0.75, }, // Caber "tf_weapon_stickbomb" : { "fire rate penalty": 1.5, "cancel falling damage": 1, "self dmg push force decreased": 0.01, // Infinite bombs }, /////////////////////////////////////////// HEAVY ////////////////////////////////////////////// // Sandvich "tf_weapon_lunchbox" : { "provide on active": 1, "gesture speed increase": 1.75, }, // Fists "tf_weapon_fists" : { "damage bonus": 1.3, }, // Fists of Steel [331] = { "dmg taken increased": 0.75, "dmg from melee increased": 1, }, // Eviction Notice [426] = { "damage penalty": 0.7, "bleeding duration": 4, "mod_maxhealth_drain_rate": 0, "speed_boost_on_hit": 0, "speed_boost_on_kill": 1, "max health additive bonus": 50, }, // Warrior's Spirit [426] = { "damage bonus": 1.75, "crits_become_minicrits": 1, }, // Holiday Punch [656] = { "crit does no damage": 0, "damage penalty": 0.25, }, // GRU [239] = { "dmg taken increased": 0.85, "mod_maxhealth_drain_rate": 0, "speed_boost_on_hit": 1, }, // Festive GRU [1084] = { "dmg taken increased": 0.85, "mod_maxhealth_drain_rate": 0, "speed_boost_on_hit": 1, }, // KGB [43] = { "critboost on kill": 1, }, ////////////////////////////////////////// ENGINEER //////////////////////////////////////////// // Wrench "tf_weapon_wrench" : { "critboost on kill": 1, "damage penalty" : 0.8, }, // Gunslinger "tf_weapon_robot_arm" : { "max health additive bonus": 75, "damage bonus": 1.25, }, // Southern Hospitality [155] = { "damage bonus": 1.5, "customattr|takedamage|mult bleeding dmg": 2.0, "dmg taken from fire increased": 1, "crit mod disabled": 1, }, // Jag [329] = { "damage penalty": 1, "damage bonus": 1.5, "fire rate bonus": 0.8, "move speed bonus": 1.2, }, // Eureka Effect [589] = { "damage bonus": 1, "customattr|takedamage|stun on hit": 1.0, }, /////////////////////////////////////////// MEDIC ////////////////////////////////////////////// // Syringe Gun "tf_weapon_syringegun_medic" : { "damage bonus": 2, }, // Overdose [412] = { "damage penalty": 1, "damage bonus": 1.35, "move speed bonus": 1.3, "fire rate bonus": 0.5, }, // Blutsauger [36] = { "damage penalty": 1, // todo do something here }, // Bonesaw "tf_weapon_bonesaw" : { "fire rate bonus": 0.8, "health regen": 10, }, // Ubersaw [37] = { "dmg taken increased": 1.15, "fire rate penalty": 1.3, "damage bonus": 1.75, "add uber charge on hit": 0, }, // Vita saw [173] = { "bleeding duration": 10, "max health additive penalty": 0, "max health additive bonus": 25, }, // Amputator [304] = { "max health additive bonus": 50, "dmg taken increased": 0.8, }, // Solemn Vow [413] = { "single wep deploy time increased": 0.4, }, /////////////////////////////////////////// SNIPER ///////////////////////////////////////////// // Razorback "tf_wearable_razorback" : { "max health additive bonus": 25, "dmg taken increased": 0.8, }, // Kukri "tf_weapon_club" : { "damage bonus": 1.3, }, // Tribalmans Shiv [171] = { "damage penalty": 1, "customattr|takedamage|mult bleeding dmg": 2.0, }, // Shahanshah [401] = { "bleeding duration": 2, }, //////////////////////////////////////////// SPY /////////////////////////////////////////////// // todo "tf_weapon_invis" : { "max health additive bonus": 25, }, [60] = { "max health additive bonus": 50, }, // Knife "tf_weapon_knife" : { "closerange backattack minicrits": 1, "bleeding duration": 4, "damage bonus": 1.5, "customattr|takedamage|mult dmg vs giants": 0.6666, // 100% "customattr|takedamage|mult dmg vs tanks": 0.6666, // 100% "customattr|takedamage|mult bleeding dmg": 2, }, // Spycicle [649] = { "closerange backattack minicrits": 1, "customattr|takedamage|mult dmg vs tanks": 3.5, // 250% "melts in fire": 0, "slow enemy on hit major": 1, }, // YER [225] = { "closerange backattack minicrits": 1, "bleeding duration": 8, "customattr|takedamage|mult bleeding dmg": 4.0, "damage bonus": 2, "customattr|takedamage|mult dmg vs giants": 0.375, // 75% "customattr|takedamage|mult dmg vs tanks": 0.5, // 100% "set icicle knife mode": 0, "disguise on backstab": 0, "mod_disguise_consumes_cloak": 0, }, // Wanga Prick [574] = { "closerange backattack minicrits": 1, "bleeding duration": 8, "customattr|takedamage|mult bleeding dmg": 4.0, "damage bonus": 2, "customattr|takedamage|mult dmg vs giants": 0.375, // 75% "customattr|takedamage|mult dmg vs tanks": 0.5, // 100% "set icicle knife mode": 0, "disguise on backstab": 0, "mod_disguise_consumes_cloak": 0, }, // Kunai [356] = { "closerange backattack minicrits": 1, "max health additive penalty": 0, "heal on hit for rapidfire": 10, }, // Big Earner [461] = { "closerange backattack minicrits": 1, "damage penalty": 0.75, "customattr|takedamage|mult dmg vs giants": 1.6666, // 125% "max health additive penalty": -25, }, }, PlayerAttributes = { [TF_CLASS_SOLDIER] = { "increase buff duration": 2, "mod rage on hit bonus": 25, "mod soldier buff range": 1.5, }, }, DisallowedUpgrades = { [TF_CLASS_SCOUT] = { // Milk [222] = [ {name = "effect bar recharge rate increased", cost = 250, def = 0}, {name = "applies snare effect", cost = 200, def = 0} ] }, [TF_CLASS_DEMOMAN] = { all = [ {name = "critboost on kill", cost = 350, def = 0} ] }, [TF_CLASS_MEDIC] = { all = [ {name = "mad milk syringes", cost = 200, def = 0} ] }, [TF_CLASS_SNIPER] = { // Jarate [58] = [ {name = "effect bar recharge rate increased", cost = 250, def = 0}, {name = "applies snare effect", cost = 200, def = 0} ] }, [TF_CLASS_SPY] = { all = [ {name = "critboost on kill", cost = 350, def = 0} ] } }, function DisallowedUpgradesCheckItem(player, item) { local tfclass = player.GetPlayerClass(); if (!(tfclass in DisallowedUpgrades)) return; local id = GetPropInt(item, STRING_NETPROP_ITEMDEF); foreach (itemid, list in DisallowedUpgrades[tfclass]) { if (itemid != "all" && itemid != GetPropInt(item, STRING_NETPROP_ITEMDEF)) continue; foreach (info in list) { local attr = item.GetAttribute(info.name, info.def); if (attr == info.def) continue; // Zatoichi, ignore if critboost is 1 if (info.name == "critboost on kill" && id == 357) if (attr <= 1) continue; local sound_tfclass = ((tfclass == 4) ? "demoman" : CLASSES[tfclass]); EmitSoundClient(player, format("vo/%s_no0%d.mp3", sound_tfclass, RandomInt(1, 3))); ClientPrint(player, 4, format("Upgrade '%s' is not allowed in this mission", info.name)); item.RemoveAttribute(info.name); // Set back to 1 if Zatoichi crit goes above 1 if (info.name == "critboost on kill" && id == 357) item.AddAttribute(info.name, 1.0, -1); player.ValidateScriptScope(); local scope = player.GetScriptScope(); if (scope.previous_currency - player.GetCurrency() == info.cost) player.AddCurrency(info.cost); } } }, HealthAttributes = { "max health additive bonus" : null, "max health additive penalty": null, "SET BONUS: max health additive bonus": null, "hidden maxhealth non buffed": null, }, function SetAttributes(target, attrs) { if (!target || !target.IsValid()) return; if (!(target instanceof CEconEntity || target instanceof CTFPlayer || target instanceof CTFBot)) return; local addattribute = ( (target instanceof CEconEntity) ? CEconEntity.AddAttribute : CTFPlayer.AddCustomAttribute ).bindenv(target); foreach (attr, val in attrs) { if (startswith(attr, "customattr")) { local args = split(attr, "|"); if (!args.len()) continue; target.ValidateScriptScope(); local scope = target.GetScriptScope(); if (!("customattrs" in scope)) scope.customattrs <- {}; if (!(args[1] in scope.customattrs)) scope.customattrs[args[1]] <- {}; scope.customattrs[args[1]][args[2]] <- val; } else { addattribute(attr, val, -1); if (target instanceof CEconEntity) target.ReapplyProvision(); if (attr in HealthAttributes) { local p = (target instanceof CEconEntity) ? target.GetOwner() : target; if (p.IsPlayer()) p.SetHealth(p.GetMaxHealth()); } } } } function ApplyAttributes(player) { if (!player || !player.IsPlayer()) return; // ItemAttributes local loadout = GetPlayerLoadout(player); foreach (item in loadout) { local cls = item.GetClassname(); local id = GetPropInt(item, STRING_NETPROP_ITEMDEF); if (id in ItemAttributes) SetAttributes(item, ItemAttributes[id]); else if (cls in ItemAttributes) SetAttributes(item, ItemAttributes[cls]); } // PlayerAttributes local tfclass = player.GetPlayerClass(); if (tfclass in PlayerAttributes) SetAttributes(player, PlayerAttributes[tfclass]); }, function GetPlayerLoadout(player) { local loadout = []; for (local i = 0; i < SLOT_COUNT; ++i) { local wpn = GetPropEntityArray(player, "m_hMyWeapons", i); if ( wpn == null) continue loadout.append(wpn); } for (local child = player.FirstMoveChild(); child != null; child = child.NextMovePeer()) { local id = GetPropInt(child, STRING_NETPROP_ITEMDEF); if (id in WearableWeapons) loadout.append(child); } return loadout; }, function PlayerHasItem(player, idorclass) { player.ValidateScriptScope(); local loadout = player.GetScriptScope().loadout; foreach (item in loadout) { if (!item || !item.IsValid()) continue; if (idorclass == GetPropInt(item, STRING_NETPROP_ITEMDEF) || idorclass == item.GetClassname()) { return true; } } return false; }, function GetItemInSlot(player, slot) { for (local i = 0; i < SLOT_COUNT; ++i) { local wep = GetPropEntityArray(player, "m_hMyWeapons", i); if ( wep == null || wep.GetSlot() != slot) continue; return wep; } }, function GetPlayerReadyCount() { if (WaveStarted) return 0; local ready = 0; local array_size = GetPropArraySize(GAMERULES, "m_bPlayerReady"); for (local i = 0; i < array_size; ++i) if (GetPropBoolArray(GAMERULES, "m_bPlayerReady", i)) ++ready; return ready; }, function SetParentLocal(child, parent, attachment = null) { SetPropEntity(child, "m_hMovePeer", parent.FirstMoveChild()); SetPropEntity(parent, "m_hMoveChild", child); SetPropEntity(child, "m_hMoveParent", parent); local origPos = child.GetLocalOrigin(); child.SetLocalOrigin(origPos + Vector(0, 0, 1)); child.SetLocalOrigin(origPos); local origAngles = child.GetLocalAngles(); child.SetLocalAngles(origAngles + QAngle(0, 0, 1)); child.SetLocalAngles(origAngles); local origVel = child.GetVelocity(); child.SetAbsVelocity(origVel + Vector(0, 0, 1)); child.SetAbsVelocity(origVel); EntFireByHandle(child, "SetParent", "!activator", 0, parent, parent); if (attachment != null) { SetPropEntity(child, "m_iParentAttachment", parent.LookupAttachment(attachment)); EntFireByHandle(child, "SetParentAttachmentMaintainOffset", attachment, 0, parent, parent); } }, function CreateWearable(player, model, bonemerge = true, attachment = null) { local model_index = GetModelIndex(model); if (model_index == -1) model_index = PrecacheModel(model); local wearable = CreateByClassname("tf_wearable"); SetPropString(wearable, "m_iName", "__extra_wearable"); SetPropInt(wearable, "m_nModelIndex", model_index); wearable.SetSkin(player.GetTeam()); wearable.SetTeam(player.GetTeam()); wearable.SetSolidFlags(4); wearable.SetCollisionGroup(11); SetPropBool(wearable, "m_bValidatedAttachedEntity", true); SetPropBool(wearable, "m_AttributeManager.m_Item.m_bInitialized", true); SetPropInt(wearable, "m_AttributeManager.m_Item.m_iEntityQuality", 0); SetPropInt(wearable, "m_AttributeManager.m_Item.m_iEntityLevel", 1); SetPropInt(wearable, "m_AttributeManager.m_Item.m_iItemIDLow", 2048); SetPropInt(wearable, "m_AttributeManager.m_Item.m_iItemIDHigh", 0); wearable.SetOwner(player); DispatchSpawn(wearable); SetPropInt(wearable, "m_fEffects", bonemerge ? EF_BONEMERGE|EF_BONEMERGE_FASTCULL : 0); SetParentLocal(wearable, player, attachment); player.ValidateScriptScope() local scope = player.GetScriptScope() if (!("extra_wearables" in scope)) scope.extra_wearables <- [wearable]; else scope.extra_wearables.append(wearable); return wearable }, function Ignite(player, duration = 10.0, damage = 1) { local utilignite = FindByName(null, "__utilignite"); if (utilignite == null) { utilignite = SpawnEntityFromTable("trigger_ignite", { targetname = "__utilignite", burn_duration = duration, damage = damage, spawnflags = 1, }); } EntFireByHandle(utilignite, "StartTouch", "", -1, player, player); EntFireByHandle(utilignite, "EndTouch", "", 0.015, player, player); }, function SetEntityColor(entity, r, g, b, a) { local color = (r) | (g << 8) | (b << 16) | (a << 24); NetProps.SetPropInt(entity, "m_clrRender", color); }, // todo weapon attributes (e.g. damage, fire rate) dont get applied when you attack then swap to secondary? // todo see about making a generalized solution for temporary attributes on player / weapons BonkAttributes = { "no double jump": 1, "health regen": 20, "max health additive bonus": 75, "voice pitch scale": 0.75, "move speed penalty": 0.9, "move speed bonus": 1, "damage force reduction": 0.5, "hand scale": 1.5, }, ColaAttributes = { "voice pitch scale": 1.25, "move speed bonus": 1.5, "increased jump height": 1.8, "cancel falling damage": 1, "dmg taken from crit reduced": 0.001, }, function ApplySodaEffects(player, revert=false) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); if (revert) { player.SetForcedTauntCam(0); SetEntityColor(player, 255, 255, 255, 255); local defaults = scope.sodarevertattrs; if (defaults) foreach (attr, val in defaults) player.AddCustomAttribute(attr, val, -1); scope.sodarevertattrs = {}; return; } local table = null; local defaults = {}; local wpn = GetItemInSlot(player, SLOT_SECONDARY); local id = (wpn) ? GetPropInt(wpn, STRING_NETPROP_ITEMDEF) : null; if (id == 163) { table = ColaAttributes; } else { table = BonkAttributes; player.RemoveCond(TF_COND_PHASE); SetEntityColor(player, 52, 116, 78, 255); } player.SetForcedTauntCam(1); if (!scope.sodarevertattrs.len()) { foreach (attr, val in table) { local def = 1.0; if (attr in {"no double jump":null,"health regen":null,"max health additive bonus":null,"cancel falling damage":null}) def = 0.0; scope.sodarevertattrs[attr] <- player.GetCustomAttribute(attr, def); } } foreach (attr, val in table) player.AddCustomAttribute(attr, val, -1); } function PlayerDrinkSoda(player) { EntFireByHandle(player, "RunScriptCode", "DispatchParticleEffect(`utaunt_lightning_bolt`, self.GetOrigin() + Vector(0, 0, 64), Vector(0, -1, 0))", 0, null, null); player.EmitSound("ambient/energy/zap1.wav"); EntFireByHandle(player, "RunScriptCode", "Ravenous.ApplySodaEffects(self)", 0.5, null, null); EntFireByHandle(player, "RunScriptCode", "Ravenous.ApplySodaEffects(self, true)", 8.5, null, null); } function EmitSoundClient(player, sound) { EmitSoundEx({ sound_name = sound, channel = 2, entity = player, filter_type = 4, }); }, function PlaySound(sound, position) { local ent = SpawnEntityFromTable("info_target", {}); ent.SetAbsOrigin(position); ent.EmitSound(sound); ent.Destroy(); }, function PrecacheParticle(name) { PrecacheEntityFromTable({ classname = "info_particle_system", effect_name = name }); }, function SetDestroyCallback(entity, callback) { entity.ValidateScriptScope(); local scope = entity.GetScriptScope(); scope.setdelegate({}.setdelegate({ parent = scope.getdelegate() id = entity.GetScriptId() index = entity.entindex() callback = callback _get = function(k) { return parent[k]; } _delslot = function(k) { if (k == id) { entity = EntIndexToHScript(index); local scope = entity.GetScriptScope(); scope.self <- entity; callback.pcall(scope); } delete parent[k]; } }) ); }, function GetNumberOffset(string) { local offset = 0; foreach (ch in string) { switch (ch) { case '1': case '6': case '9': offset -= 0.002; break; default: offset -= 0.003; } } return offset; }, function MultiplyString(string, times) { local out = ""; for (local i = 0; i < times; ++i) out += string; return out; }, function GetProgressBar(percentage, length) { if (length < 1) return ""; local frac = 1.0 / length; local times = floor(percentage / frac); return MultiplyString(BAR, times); }, function DisplayWizardHud(player) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); local spelldata = SpellData[scope.selected_spell]; local color1; local color2; local bar1; local bar2; if (scope.wizard_type == WIZARD_RAND) { color1 = "173 149 102"; color2 = "153 102 175"; //bar1 bar2 } else if (scope.wizard_type == WIZARD_MANA) { color1 = (spelldata.mana_cost > scope.mana) ? "219 67 61" : "35 65 185"; color2 = "55 160 55"; bar1 = GetProgressBar(scope.mana.tofloat() / scope.max_mana, 10); bar2 = GetProgressBar(scope.spell_regen_ticks.tofloat() / spelldata.regen_duration, 10); } else return; local color3 = (scope.spell_charges > 0) ? "225 223 196" : "219 67 61"; local text = SpawnEntityFromTable("game_text", { targetname = "test", channel = 1, color = color1, effect = 0, fadein = 0.1, fadeout = 0.5, fxtime = 0, message = bar1, holdtime = 2, spawnflags = 0, x = 0.7537, y = 0.7420, }); text.AcceptInput("Display", "", player, player); text.AcceptInput("Kill", "", null, null); local text2 = SpawnEntityFromTable("game_text", { targetname = "test", channel = 2, color = color2, effect = 0, fadein = 0.1, fadeout = 0.5, fxtime = 0, message = bar2, holdtime = 2, spawnflags = 0, x = 0.7537, y = 0.7760, }); text2.AcceptInput("Display", "", player, player); text2.AcceptInput("Kill", "", null, null); local num = scope.spell_charges.tostring(); local x = 0.725 + GetNumberOffset(num); local text3 = SpawnEntityFromTable("game_text", { targetname = "test", channel = 3, color = color3, effect = 0, fadein = 0.1, fadeout = 0.5, fxtime = 0, message = num, holdtime = 2, spawnflags = 0, x = x, y = 0.825, }); text3.AcceptInput("Display", "", player, player); text3.AcceptInput("Kill", "", null, null); player.SetScriptOverlayMaterial(spelldata.overlay); }, // Used to get rid of hand particle for wizard engie function ResetPlayerViewmodel(player, resetcharges=true, spawnparticle=true) { local wpn = player.GetActiveWeapon(); local time = Time() + 0.75; SetPropFloat(wpn, "m_flNextPrimaryAttack", time); SetPropFloat(wpn, "m_flTimeWeaponIdle", time); local viewmodel = GetPropEntity(player, "m_hViewModel"); local sequence; if (viewmodel && viewmodel.IsValid()) { sequence = viewmodel.GetSequence(); viewmodel.Destroy(); } viewmodel = CreateByClassname("tf_viewmodel"); local model = format("models/weapons/c_models/c_%s_arms.mdl", CLASSES[player.GetPlayerClass()]); DispatchSpawn(viewmodel); SetPropEntity(viewmodel, "m_hOwnerEntity", player); SetPropEntity(viewmodel, "m_hOwner", player); SetPropEntity(player, "m_hViewModel", viewmodel); SetPropInt(viewmodel, "m_nViewModelIndex", PrecacheModel(model)); viewmodel.SetModel(model); SetParentLocal(viewmodel, player); SetPropInt(viewmodel, "m_fEffects", EF_BONEMERGE|EF_BONEMERGE_FASTCULL) viewmodel.SetCycle(1); viewmodel.SetPlaybackRate(1.0); if (!sequence) sequence = viewmodel.LookupSequence("gun_idle"); viewmodel.ResetSequence(sequence); player.ValidateScriptScope(); local scope = player.GetScriptScope(); local spelldata = SpellData[scope.selected_spell]; if (resetcharges) { scope.spell_charges = (spelldata.is_rare) ? 0 : 1; scope.spell_regen_ticks = (spelldata.max_charges == 1 && !spelldata.is_rare) ? spelldata.regen_duration : 0; } if (!spawnparticle) return; if (spelldata.deploy_sound) player.EmitSound(spelldata.deploy_sound); if (scope.hand_particle && scope.hand_particle.IsValid()) scope.hand_particle.AcceptInput("Kill", "", null, null); scope.hand_particle <- SpawnEntityFromTable("trigger_particle", { particle_name = SpellData[scope.selected_spell].hand_particle, attachment_type = 4, // PATTACH_ABSORIGIN_FOLLOW, attachment_name = "effect_hand_R", spawnflags = 64 // allow everything }); EntFireByHandle(scope.hand_particle, "StartTouch", "!activator", 0.1, viewmodel, viewmodel); }, function ExitSpellMenu(player, reset=null, spawnparticle=true) { player.ValidateScriptScope(); local scope = player.GetScriptScope(); local spelldata = SpellData[scope.selected_spell]; scope.choosing_spell = false; scope.lastangles = null; if (scope.viewcontrol) { DisableViewcontrolSafe(player, scope.viewcontrol); scope.viewcontrol.Destroy(); scope.viewcontrol = null; } player.SetScriptOverlayMaterial(spelldata.overlay); player.RemoveHudHideFlags(HIDEHUD_ALL); SetPropBool(player, "localdata.m_Local.m_bDrawViewmodel", true); if (reset == null) reset = (scope.chosen_spell != null && scope.selected_spell != scope.chosen_spell); if (reset) scope.selected_spell = scope.chosen_spell; scope.chosen_spell = null; Ravenous.ResetPlayerViewmodel(player, reset, spawnparticle); }, function CastSpell(player, origin, angles, spell_index, changes=null) { local original_pos = player.GetOrigin(); local original_ang = player.EyeAngles(); player.SetAbsOrigin(origin - player.GetClassEyeHeight()); player.SnapEyeAngles(angles); player.ValidateScriptScope(); local scope = player.GetScriptScope(); local spelldata = {}; // Allow changes param to override spelldata local sd = SpellData[spell_index]; foreach (k, v in sd) spelldata[k] <- v; if (changes) foreach (k, v in changes) spelldata[k] <- v; local wep = CreateByClassname(spelldata.weapon_class); SetPropInt(wep, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", spelldata.weapon_id); SetPropBool(wep, "m_AttributeManager.m_Item.m_bInitialized", true); SetPropEntity(wep, "m_hOwner", player) DispatchSpawn(wep); wep.KeyValueFromString("classname", "__spell_weapon"); if (spelldata.weapon_attributes) SetAttributes(wep, spelldata.weapon_attributes); wep.ReapplyProvision(); wep.SetClip1(-1); SetPropFloat(wep, "m_flNextPrimaryAttack", 0); SetPropFloat(wep, "m_flNextSecondaryAttack", 0); if (spelldata.secondary_attack) wep.SecondaryAttack(); else wep.PrimaryAttack(); if (spelldata.cast_sound) PlaySound(spelldata.cast_sound, player.GetOrigin()); if (spelldata.oncast) spelldata.oncast(player); wep.ValidateScriptScope(); local wpn_scope = wep.GetScriptScope(); wpn_scope.spell_index <- spell_index; wpn_scope.changes <- changes; player.SetAbsOrigin(original_pos); player.SnapEyeAngles(original_ang); return wep; }, function OnGameEvent_mvm_wave_complete(params) { WaveStarted = false }, function OnGameEvent_mvm_wave_failed(params) { WaveStarted = false }, function OnGameEvent_mvm_begin_wave(params) { WaveStarted = true }, function OnGameEvent_mvm_reset_stats(params) { WaveStarted = true }, //used for manually jumping waves function OnGameEvent_teamplay_round_start(params) { CleanupWearables(); // todo, is this necessary }, // todo do i need player_disconnect to cleanup wearables? // todo does this even do anything? function OnGameEvent_player_spawn(params) { local player = GetPlayerFromUserID(params.userid) if (!player) return; player.ValidateScriptScope(); local scope = player.GetScriptScope(); if (!("extra_wearables" in scope)) return; foreach (wearable in scope.extra_wearables) if (wearable.IsValid()) EntFireByHandle(wearable, "Kill", "", -1, null, null); delete scope.extra_wearables; }, function OnGameEvent_player_death(params) { local player = GetPlayerFromUserID(params.userid); player.ValidateScriptScope(); local scope = player.GetScriptScope(); CleanupPlayerWearables(player); if (player.IsBotOfType(TF_BOT_TYPE)) { for (local money; money = FindByClassname(money, "item_currencypack*");) money.SetAbsVelocity(Vector(1, 1, 1)); } }, function OnGameEvent_post_inventory_application(params) { local player = GetPlayerFromUserID(params.userid) if (!player) return; local time = Time(); player.SetForcedTauntCam(0); player.RemoveCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); SetEntityColor(player, 255, 255, 255, 255); player.RemoveHudHideFlags(HIDEHUD_ALL); SetPropBool(player, "localdata.m_Local.m_bDrawViewmodel", true); CleanupPlayerWearables(player); player.ValidateScriptScope(); local scope = player.GetScriptScope(); if (player.IsBotOfType(TF_BOT_TYPE)) { // Make them a zombie local cls = CLASSES[player.GetPlayerClass()]; EntFireByHandle(player, "RunScriptCode", format("self.SetCustomModelWithClassAnimations(`models/player/%s.mdl`)", cls), -1, null, null); CreateWearable(player, format("models/player/items/%s/%s_zombie.mdl", cls, cls)) SetPropBool(player, "m_bForcedSkin", true) SetPropInt(player, "m_nForcedSkin", player.GetSkin() + 4) } else { if (!("buttons_last" in scope)) foreach (k, v in DefaultPlayerScope) scope[k] <- v; if (scope.previous_currency == null) scope.previous_currency = player.GetCurrency(); scope.loadout = GetPlayerLoadout(player); scope.nextloadoutcheck = time; scope.nexthudtime = time; scope.nextspellcasttime = time; scope.mana = scope.max_mana; scope.spell_charges = 0; scope.spell_regen_ticks = 0; scope.animseqs = null; scope.currentseq = 0; scope.seqfinishtime = null; if (scope.selected_spell == null) scope.selected_spell = 0; if (scope.hand_particle && scope.hand_particle.IsValid()) scope.hand_particle.AcceptInput("Kill", "", null, null); for (local i = 0; i < SLOT_COUNT; ++i) { local wep = GetPropEntityArray(player, "m_hMyWeapons", i); if (!wep || !wep.IsValid()) continue; local cls = wep.GetClassname().tolower(); if (!(cls in ItemWhitelist)) wep.Kill(); } player.SetScriptOverlayMaterial(""); local tfclass = player.GetPlayerClass(); if (tfclass == TF_CLASS_ENGINEER) { local melee = GetItemInSlot(player, SLOT_MELEE); melee.Destroy(); // giveweapon local item = CreateByClassname("tf_weapon_robot_arm"); SetPropInt(item, "m_AttributeManager.m_Item.m_iItemDefinitionIndex", 142); SetPropBool(item, "m_AttributeManager.m_Item.m_bInitialized", true); SetPropBool(item, "m_bValidatedAttachedEntity", true); item.SetTeam(player.GetTeam()); DispatchSpawn(item); item.KeyValueFromString("classname", "__wizardglove"); // Prevent ItemAttributes getting applied player.Weapon_Equip(item); scope.wearable <- CreateWearable(player, "models/player/engineer.mdl"); SetPropInt(player, "m_nRenderMode", kRenderTransColor) SetPropInt(player, "m_clrRender", 0) item.SetCustomViewModel("models/weapons/c_models/c_engineer_arms.mdl"); player.Weapon_Switch(item); local bg = player.FindBodygroupByName("hat"); local bgint = player.GetBodygroup(bg); scope.wearable.SetBodygroup(bg, bgint); local viewmodel = GetPropEntity(player, "m_hViewModel"); if (!scope.hand_particle || !scope.hand_particle.IsValid()) { scope.hand_particle <- SpawnEntityFromTable("trigger_particle", { particle_name = SpellData[scope.selected_spell].hand_particle, attachment_type = 4, // PATTACH_ABSORIGIN_FOLLOW, attachment_name = "effect_hand_R", spawnflags = 64 // allow everything }); scope.hand_particle.AcceptInput("StartTouch", "!activator", viewmodel, viewmodel); } } EntFireByHandle(player, "RunScriptCode", "Ravenous.SwitchToFirstAvailableWeapon(self);", 0.015, null, null); EntFireByHandle(player, "RunScriptCode", "Ravenous.ApplyAttributes(self)", 0.015, null, null); scope.Think <- function() { if (!("Ravenous" in ROOT)) return; if (!player.IsAlive() || player.GetTeam() == TEAM_SPECTATOR) { if (choosing_spell) Ravenous.ExitSpellMenu(self, null, false); if (self.GetScriptOverlayMaterial() != "") self.SetScriptOverlayMaterial(""); return; } local time = Time(); local tfclass = self.GetPlayerClass(); local wpn = self.GetActiveWeapon(); local current_id = (wpn) ? GetPropInt(wpn, Ravenous.STRING_NETPROP_ITEMDEF) : null; local viewmodel = GetPropEntity(self, "m_hViewModel"); local spelldata = Ravenous.SpellData[selected_spell]; if (wearable && wearable.IsValid() && (self.IsTaunting() || wearable.GetMoveParent() != self)) EntFireByHandle(wearable, "SetParent", "!activator", -1, self, self) if (animseqs && animseqs.len() && viewmodel) { if (currentseq > animseqs.len()) { animseqs = null; currentseq = 0; seqfinishtime = null; SetPropFloat(wpn, "LocalActiveWeaponData.m_flTimeWeaponIdle", time); SetPropFloat(wpn, "LocalActiveWeaponData.m_flNextPrimaryAttack", time+0.05); // A little delay to prevent attacking and no dmg } else { if (time >= seqfinishtime) { if (currentseq < animseqs.len()) { local info = animseqs[currentseq]; if (seqfinishtime == null) { SetPropFloat(wpn, "LocalActiveWeaponData.m_flTimeWeaponIdle", time + 999); SetPropFloat(wpn, "LocalActiveWeaponData.m_flNextPrimaryAttack", time + 999); } seqfinishtime = time + info[1]; local seq = info[0]; if (typeof seq == "string") seq = viewmodel.LookupSequence(seq); EntFireByHandle(viewmodel, "RunScriptCode", format("self.ResetSequence(%d);self.SetPlaybackRate(%d)", seq, info[2]), 0.015, null, null); if (info[0] == "spell_fire") { mana -= spelldata.mana_cost; --spell_charges; if (spell_regen_ticks >= spelldata.regen_duration) spell_regen_ticks = 0; Ravenous.CastSpell(self, self.EyePosition(), self.EyeAngles(), selected_spell); } } ++currentseq; } } } if (tfclass == TF_CLASS_SCOUT) { local origin = self.GetOrigin(); for (local money; money = FindByClassnameWithin(money, "item_currencypack*", origin, 288);) money.SetOrigin(origin); } else if (tfclass == TF_CLASS_ENGINEER) { if (time >= nexthudtime) { if (mana + mana_regen > max_mana) mana = max_mana; else mana += mana_regen; if (reset_spell_regen) { reset_spell_regen = false; spell_regen_ticks = 0; } if (spell_charges < spelldata.max_charges) { if (spell_regen_ticks < spelldata.regen_duration) ++spell_regen_ticks; if (spell_regen_ticks >= spelldata.regen_duration) { ++spell_charges; if (spell_charges < spelldata.max_charges) reset_spell_regen = true; // We use this for visual purposes so the progress bar can fill entirely } } if (!choosing_spell) Ravenous.DisplayWizardHud(self); nexthudtime = time + Ravenous.DISPLAY_TICKRATE; } } // Holiday Punch critboost if (current_id == 656) if (!self.InCond(TF_COND_CRITBOOSTED_CTF_CAPTURE)) self.AddCondEx(TF_COND_CRITBOOSTED_CTF_CAPTURE, -1, null); else if (self.InCond(TF_COND_CRITBOOSTED_CTF_CAPTURE)) self.RemoveCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); // Caber if (current_id == 307) { SetPropBool(wpn, "m_iDetonated", false); SetPropBool(wpn, "m_bBroken", false); } local secondary = Ravenous.GetItemInSlot(self, SLOT_SECONDARY); local secondary_id = (secondary) ? GetPropInt(secondary, Ravenous.STRING_NETPROP_ITEMDEF) : null; // Mantreads wet immunity if (secondary_id == 444) { foreach (cond in [24, 27, 123]) // milk, urine, gas if (self.InCond(cond)) self.RemoveCond(cond); } // Parachute else if (secondary_id == 1101) self.RemoveCond(TF_COND_PARACHUTE_DEPLOYED); // DisallowedUpgrades if (time >= nextloadoutcheck) { foreach (item in loadout) { if (!item || !item.IsValid()) continue; Ravenous.DisallowedUpgradesCheckItem(self, item); } nextloadoutcheck = time + 0.5; } if (sodachecktime && time >= sodachecktime) { sodachecktime = 0; if (GetPropIntArray(self, "m_iAmmo", 5) == 0) EntFireByHandle(self, "RunScriptCode", "Ravenous.PlayerDrinkSoda(self)", 0.4, null, null); } if (rjumping) { if (self.GetFlags() & FL_ONGROUND) { rjumping = false; self.RemoveCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); } else self.AddCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); } local buttons = NetProps.GetPropInt(self, "m_nButtons"); local buttons_changed = buttons_last ^ buttons; local buttons_pressed = buttons_changed & buttons; local buttons_released = buttons_changed & (~buttons); if (buttons & IN_ATTACK && tfclass == TF_CLASS_SCOUT) { if (wpn && wpn.GetClassname() == "tf_weapon_lunchbox_drink") sodachecktime = time + 0.1; } else if (buttons_pressed & IN_ATTACK) { if (tfclass == TF_CLASS_ENGINEER && choosing_spell) Ravenous.ExitSpellMenu(player); } else if (buttons_pressed & IN_ATTACK2) { // todo change this to the superjump spell if (tfclass == TF_CLASS_SOLDIER) { if (!rjumping && time >= rjumpnexttime) { printl("JUMP"); self.SetAbsVelocity(self.GetAbsVelocity() + Vector(0, 0, 1000)); rjumpnexttime = time + 4.0; rjumping = true; } } else if (tfclass == TF_CLASS_ENGINEER) { // todo stopsound if (!animseqs && time >= GetPropFloat(wpn, "m_flNextPrimaryAttack") && !choosing_spell) { if (wizard_type == WIZARD_MANA && mana >= spelldata.mana_cost && spell_charges > 0) { Ravenous.EmitSoundClient(self, spelldata.cast_vo); animseqs = spelldata.animseqs; } } } } else if (buttons_pressed & IN_RELOAD) { if (tfclass == TF_CLASS_ENGINEER) { if (!choosing_spell) { choosing_spell = true; animseqs = null; currentseq = 0; seqfinishtime = null; local ang = self.EyeAngles(); if (ang.x < -85.0) { ang.x = -85.0; self.SnapEyeAngles(ang); } lastangles = ang; cursorx = 0; cursory = 0; viewcontrol = SpawnEntityFromTable("point_viewcontrol", { spawnflags = 8 }); EntFireByHandle(viewcontrol, "Enable", "", -1, self, self) viewcontrol.SetAbsAngles(lastangles); self.SetScriptOverlayMaterial("vgui/spell_menu_overlay.vmt"); self.AddHudHideFlags(HIDEHUD_ALL); SetPropBool(self, "localdata.m_Local.m_bDrawViewmodel", false); SetPropFloat(wpn, "m_flNextPrimaryAttack", Time() + 9999); viewmodel.Destroy(); } else { Ravenous.ExitSpellMenu(self, false); } } } if (viewcontrol) { viewcontrol.SetAbsOrigin(self.EyePosition()); local vecpos = self.EyePosition() + lastangles.Forward() * 32.0; local eyevec = lastangles.Forward(); local right = eyevec.Cross(Vector(0, 0, 1)); right.Norm(); local up = right.Cross(eyevec); up.Norm(); local newangles = self.EyeAngles(); if (!VectorsEqual(newangles, lastangles)) { local diff = newangles - lastangles; local distx = (right * -diff.y).Length(); local disty = (up * -diff.x).Length(); if (diff.y < 0) cursorx += distx / 2; else cursorx -= distx / 2; if (diff.x < 0) cursory -= disty / 2; else cursory += disty / 2; if (Vector(cursorx, cursory, 0).Length() >= 2) { local ang = radtodeg(atan2(cursory, cursorx)); local i = floor((180 - fabs(ang)) / 45.0); chosen_spell = (ang < 0) ? i : i + 4; // Just in case if (chosen_spell > 7) chosen_spell = 7; else if (chosen_spell < 0) chosen_spell = 0; self.SetScriptOverlayMaterial(format("vgui/spell_menu_overlay%d.vmt", chosen_spell)); cursorx = 0; cursory = 0; } } self.SnapEyeAngles(lastangles); } buttons_last = buttons; previous_currency = self.GetCurrency(); return -1; }; AddThinkToEnt(player, "Think"); if ("regenerate" in scope && scope.regenerate) scope.regenerate <- false; else { // Fix certain attributes like move speed not being applied scope.regenerate <- true; EntFireByHandle(player, "RunScriptCode", "self.Regenerate(true)", 0.015, null, null); } } }, function OnScriptHook_OnTakeDamage(params) { local attacker = params.attacker; local victim = params.const_entity; if (!victim) return; if (!attacker) return; // Pumpkin bombs if (attacker.IsPlayer() && !params.weapon) { if ((attacker == victim && !attacker.IsBotOfType(1337)) || victim.GetTeam() == attacker.GetTeam()) params.damage = 0; return; } if (!params.weapon) return; local wpn = params.weapon; local wpn_cls = wpn.GetClassname(); local wpn_id = GetPropInt(wpn, STRING_NETPROP_ITEMDEF); local dmgtype = params.damage_type; local dmgcustom = params.damage_custom; wpn.ValidateScriptScope(); local wpn_scope = wpn.GetScriptScope(); attacker.ValidateScriptScope(); local atk_scope = attacker.GetScriptScope(); local customattributes = null; if ("customattrs" in wpn_scope && "takedamage" in wpn_scope.customattrs) customattributes = wpn_scope.customattrs.takedamage; // Global fixes if (wpn_cls == "tf_weapon_compound_bow") if (wep.GetAttribute("damage bonus", 1.0) != 1.0) params.damage *= 1.263157; else if (wpn_cls == "tf_weapon_cannon") if (dmgcustom == TF_DMG_CUSTOM_CANNONBALL_PUSH) params.damage *= wpn.GetAttribute("damage bonus", 1.0); // Human attacks bot if (victim.IsPlayer() && victim.IsBotOfType(TF_BOT_TYPE) && attacker.IsPlayer() && !attacker.IsBotOfType(TF_BOT_TYPE)) { local spelldata = null; if ("spell_index" in wpn_scope && wpn_scope.spell_index != null) spelldata = SpellData[wpn_scope.spell_index]; else spelldata = SpellData[atk_scope.selected_spell]; // Spells if (wpn_cls == "__spell_weapon") { if ("changes" in wpn_scope && "dmgcustom" in wpn_scope.changes) params.damage_stats <- wpn_scope.changes.dmgcustom; else params.damage_stats <- spelldata.dmgcustom; EntFireByHandle(victim, "RunScriptCode", "self.SetAbsVelocity(Vector(0, 0, 1));", 0, null, null); } else if (wpn_cls == "__wizardglove") { if (atk_scope.wizard_type) { switch (atk_scope.selected_spell) { // Fireball case 0: Ignite(victim, 4); break; // Frostshot case 1: victim.StunPlayer(1.0, 1.0, 1, attacker); victim.EmitSound("weapons/icicle_freeze_victim_01.wav"); // todo might need to put this outside loop victim.TakeDamageCustom(attacker, attacker, null, Vector(), Vector(), 20, DMG_CLUB, TF_DMG_CUSTOM_SPELL_FIREBALL); Ravenous.SetEntityColor(victim, 30, 70, 200, 255); EntFireByHandle(victim, "RunScriptCode", "Ravenous.SetEntityColor(self, 255, 255, 255, 255)", 1.0, null, null); break; // Lightning Surge case 2: victim.StunPlayer(2.0, 1.0 2, attacker); break; // Meteor Shower case 5: Ignite(victim, 10); victim.TakeDamageCustom(attacker, attacker, null, Vector(), Vector(), 50, DMG_CLUB, TF_DMG_CUSTOM_SPELL_FIREBALL); break; // Tornado case 6: break; // Black Hole case 7: break; } } } // Cleaver else if (wpn_cls == "tf_weapon_cleaver") { if (!victim.IsMiniBoss()) { params.damage = victim.GetHealth(); params.damage_type = dmgtype & ~DMG_ACID } } // Boston Basher else if (wpn_id == 325 || wpn_id == 452) { if (attacker.InCond(TF_COND_BLEEDING)) attacker.RemoveCond(TF_COND_BLEEDING); } // Sun on a Stick else if (wpn_id == 349) { if (victim.GetPlayerClass() != TF_CLASS_PYRO) Ignite(victim, 1); } // Disciplinary Action else if (wpn_id == 447) { if (!victim.IsMiniBoss()) victim.Taunt(TAUNT_MISC_ITEM, 92); // MP_CONCEPT_TAUNT_LAUGH } // Holiday Punch else if (wpn_id == 656) { local pos = victim.GetOrigin(); pos.z += 32.0; for ( local player; player = FindByClassnameWithin(player, "player", pos, 72.0); ) { if (!player || player.GetTeam() == attacker.GetTeam() || player.IsMiniBoss()) continue; player.Taunt(TAUNT_MISC_ITEM, 92); // MP_CONCEPT_TAUNT_LAUGH } } // Solemn Vow else if (wpn_id == 413) { local duration = (victim.IsMiniBoss()) ? 1.0 : 3.0; victim.StunPlayer(duration, 1.0, 2, attacker); params.damage = 0; } else if (wpn_cls == "tf_weapon_knife") { if (dmgcustom == TF_DMG_CUSTOM_BACKSTAB) { // Knives may only backstab giants or medics if (!victim.IsMiniBoss() && victim.GetPlayerClass() != TF_CLASS_MEDIC) { params.damage_custom = 0; params.damage_type = DMG_CLUB; params.damage = 40 * wpn.GetAttribute("damage bonus", 1.0); } } // todo give ui for spy knives } } // Bot attacks human else if (victim.IsPlayer() && !victim.IsBotOfType(TF_BOT_TYPE) && attacker.IsPlayer() && attacker.IsBotOfType(TF_BOT_TYPE)) { local melee_wpn = GetItemInSlot(victim, SLOT_MELEE); local melee_id = (melee_wpn) ? GetPropInt(melee_wpn, STRING_NETPROP_ITEMDEF) : null; // Melee resist if (dmgtype & DMG_CLUB) { if (victim.GetPlayerClass() == TF_CLASS_HEAVYWEAPONS) params.damage *= 0.5; if (melee_id) { // Gunboats if (melee_id == 133) params.damage *= 0.75; // YER else if (melee_id == 225 || melee_id == 574) params.damage *= 0.75; // Kunai else if (melee_id == 356) params.damage *= 0.9; } } } // todo account for player custom attributes and other loadout weapons with custom attributes when necessary if (customattributes) { if (victim.IsPlayer()) { if ("mult bleeding dmg" in customattributes) if (dmgcustom == TF_DMG_CUSTOM_BLEEDING) params.damage *= customattributes["mult bleeding dmg"]; if ("stun on hit" in customattributes) if (!victim.IsMiniBoss()) victim.StunPlayer(customattributes["stun on hit"], 1.0, 2, attacker); if ("mult dmg vs giants" in customattributes) if (victim.IsMiniBoss()) params.damage *= customattributes["mult dmg vs giants"]; } else { if ("mult crit dmg" in customattributes) if (dmgtype & DMG_ACID) params.damage *= customattributes["mult crit dmg"]; if ("mult dmg vs tanks" in customattributes) if (victim.GetClassname() == "tank_boss") params.damage *= customattributes["mult dmg vs tanks"]; } } }, }; __CollectGameEventCallbacks(Ravenous); if (!Ravenous.SCRIPT_ENTITY) Ravenous.SCRIPT_ENTITY = SpawnEntityFromTable("info_teleport_destination", { targetname = "__ravenous_entity" }); Ravenous.SCRIPT_ENTITY.ValidateScriptScope(); Ravenous.SCRIPT_ENTITY.GetScriptScope().Think <- function() { // First person to spawn in after map load spawns in before the script if (!Ravenous.FirstSpawn) { for (local i = 1; i <= Ravenous.MAX_CLIENTS; ++i) { local player = PlayerInstanceFromIndex(i); if (!player || player.IsBotOfType(TF_BOT_TYPE)) continue; if (player.GetTeam() != 2 || !player.IsAlive()) continue; EntFireByHandle(player, "RunScriptCode", "self.Regenerate(true)", 0.1, null, null); Ravenous.FirstSpawn = true; } } if (!Ravenous.WaveStarted) { local roundtime = GetPropFloat(Ravenous.GAMERULES, "m_flRestartRoundTime"); if (roundtime > Time() + 0) { local humans = 0; for (local i = 1; i <= Ravenous.MAX_CLIENTS; ++i) { local player = PlayerInstanceFromIndex(i); if (!player || player.IsBotOfType(TF_BOT_TYPE)) continue; ++humans; } local ready = Ravenous.GetPlayerReadyCount(); if (ready && (ready >= humans) || (roundtime <= 12.0)) SetPropFloat(Ravenous.GAMERULES, "m_flRestartRoundTime", Time() + 0); } } for (local ent; ent = Entities.FindByClassname(ent, "tf_ragdoll"); ) { if (GetPropInt(ent, "m_iTeam") == 2) continue; ent.AcceptInput("Kill", "", null, null); } for (local ent; ent = Entities.FindByClassname(ent, "tf_powerup_bottle"); ) ent.AcceptInput("Kill", "", null, null); for (local ent; ent = Entities.FindByClassname(ent, "tf_weapon_spellbook"); ) ent.AcceptInput("Kill", "", null, null); for (local ent; ent = Entities.FindByClassname(ent, "tf_projectile*"); ) { local owner = ent.GetOwner(); local launcher = GetPropEntity(ent, "m_hLauncher"); if (!owner) { if (launcher) { owner = GetPropEntity(launcher, "m_hOwner"); ent.SetOwner(owner); } } if (!owner || !owner.IsPlayer() || owner.IsBotOfType(1337)) continue; if (!launcher || (launcher && launcher.GetClassname() != "__spell_weapon")) continue; launcher.ValidateScriptScope(); local scope = launcher.GetScriptScope(); // todo castspell changes support if (!(ent.GetEFlags() & 1048576) && "spell_index" in scope && scope.spell_index != null) { local spelldata = Ravenous.SpellData[scope.spell_index]; if (spelldata.projectile_particle) { ent.AcceptInput("DispatchEffect", "ParticleEffectStop", null, null); ent.SetModelSimple("models/empty.mdl"); local particle = SpawnEntityFromTable("info_particle_system", { start_active = true, effect_name = spelldata.projectile_particle, }); Ravenous.SetParentLocal(particle, ent); } if (spelldata.onthink) { ent.ValidateScriptScope(); ent.GetScriptScope().Think <- function() { spelldata.onthink(self); return -1; }; AddThinkToEnt(ent, "Think"); } Ravenous.SetDestroyCallback(ent, function() { local origin = self.GetOrigin(); local launcher = GetPropEntity(self, "m_hLauncher"); if (launcher && launcher.IsValid()) EntFireByHandle(launcher, "Kill", "", 0.015, null, null); if (spelldata.explode_particle) DispatchParticleEffect(spelldata.explode_particle, origin, self.GetAbsAngles().Forward()); if (spelldata.explode_sound) Ravenous.PlaySound(spelldata.explode_sound, origin); if (spelldata.onexplode) spelldata.onexplode(self); }); } ent.AddEFlags(1048576); } foreach (npc in Ravenous.NPCS) for ( local n; n = FindByClassname(n, npc); ) n.FlagForUpdate(true); return -1; }; AddThinkToEnt(Ravenous.SCRIPT_ENTITY, "Think"); Ravenous.Cleanup(true); SetPropBool(Ravenous.GAMERULES, "m_bPlayingMedieval", false); foreach (spelldata in Ravenous.SpellData) { if (spelldata.hand_particle) Ravenous.PrecacheParticle(spelldata.hand_particle); if (spelldata.projectile_particle) Ravenous.PrecacheParticle(spelldata.projectile_particle); if (spelldata.explode_particle) Ravenous.PrecacheParticle(spelldata.explode_particle); if (spelldata.cast_vo) PrecacheSound(spelldata.cast_vo); if (spelldata.deploy_sound) PrecacheSound(spelldata.deploy_sound); if (spelldata.cast_sound) PrecacheSound(spelldata.cast_sound); if (spelldata.explode_sound) PrecacheSound(spelldata.explode_sound); } Ravenous.PrecacheParticle("rd_robot_explosion_smoke_linger"); Ravenous.PrecacheParticle("utaunt_tornado_parent_black"); Ravenous.PrecacheParticle("utaunt_lightning_bolt"); PrecacheSound("ambient/energy/zap1.wav"); PrecacheSound("weapons/barret_arm_fizzle.wav");