::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); // 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 ::Ravenous <- { function CleanupWearables() { DoEntFire("__extra_wearable", "Kill", "", -1, null, null); for (local i = 1; i <= MAX_CLIENTS; ++i) { local player = PlayerInstanceFromIndex(i); if (!player) continue; 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 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", SINGLE_TICK = 0.015, CLASSES = ["", "scout", "sniper", "soldier", "demo", "medic", "heavy", "pyro", "spy", "engineer", "civilian"], NPCS = ["headless_hatman", "eyeball_boss", "merasmus", "tf_zombie"], WaveStarted = false, FirstSpawn = false, 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_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(); local p = (target instanceof CEconEntity) ? target.GetOwner() : target; 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", "", SINGLE_TICK, 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 PrecacheParticle(name) { PrecacheEntityFromTable({ classname = "info_particle_system", effect_name = name }); } 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 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); if (!player.IsBotOfType(TF_BOT_TYPE)) return 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; player.SetForcedTauntCam(0); player.RemoveCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); SetEntityColor(player, 255, 255, 255, 255); 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 { 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(); } for (local ent; ent = Entities.FindByClassname(ent, "tf_powerup_bottle"); ) { local owner = ent.GetOwner(); if (owner && owner.GetTeam() == 2) ent.Destroy(); } local tfclass = player.GetPlayerClass(); if (tfclass == TF_CLASS_ENGINEER) { local melee = GetItemInSlot(player, SLOT_MELEE); melee.Destroy(); 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); player.Weapon_Equip(item); local wearable = CreateByClassname("tf_wearable") SetPropString(wearable, "m_iName", "__bot_bonemerge_model") SetPropInt(wearable, "m_nModelIndex", PrecacheModel("models/player/engineer.mdl")) SetPropBool(wearable, "m_bValidatedAttachedEntity", true) SetPropBool(wearable, STRING_NETPROP_ITEMDEF, true) SetPropEntity(wearable, "m_hOwnerEntity", player) wearable.SetTeam(player.GetTeam()) wearable.SetOwner(player) DispatchSpawn(wearable) EntFireByHandle(wearable, "SetParent", "!activator", -1, player, player) SetPropInt(wearable, "m_fEffects", EF_BONEMERGE|EF_BONEMERGE_FASTCULL) scope.wearable <- wearable 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); wearable.SetBodygroup(bg, bgint); local proxy_entity = Entities.CreateByClassname("obj_teleporter") // not using SpawnEntityFromTable as that creates spawning noises proxy_entity.SetAbsOrigin(player.GetOrigin()) proxy_entity.DispatchSpawn() proxy_entity.SetModel("models/weapons/c_models/c_engineer_arms.mdl") proxy_entity.AddEFlags(Constants.FEntityEFlags.EFL_NO_THINK_FUNCTION) // prevents the entity from disappearing proxy_entity.SetSolid(Constants.ESolidType.SOLID_NONE) NetProps.SetPropBool(proxy_entity, "m_bPlacing", true) NetProps.SetPropInt(proxy_entity, "m_fObjectFlags", 2) // sets "attachment" flag, prevents entity being snapped to player feet NetProps.SetPropEntity(proxy_entity, "m_hBuilder", player) local viewmodel = GetPropEntity(player, "m_hViewModel"); EntFireByHandle(proxy_entity, "SetParent", "!activator", -1, viewmodel, viewmodel) SetPropInt(proxy_entity, "m_fEffects", EF_BONEMERGE|EF_BONEMERGE_FASTCULL) SetPropInt(proxy_entity, "m_nRenderMode", kRenderTransColor) SetPropInt(proxy_entity, "m_clrRender", 0) local particle = SpawnEntityFromTable("trigger_particle", { particle_name = "superrare_greenenergy", attachment_type = 4, // PATTACH_ABSORIGIN_FOLLOW, attachment_name = "effect_hand_R", spawnflags = 64 // allow everything }) particle.AcceptInput("StartTouch", "!activator", proxy_entity, proxy_entity) particle.Kill() } EntFireByHandle(player, "RunScriptCode", "Ravenous.SwitchToFirstAvailableWeapon(self);", SINGLE_TICK, null, null); EntFireByHandle(player, "RunScriptCode", "Ravenous.ApplyAttributes(self)", SINGLE_TICK, null, null); if (!("previous_currency" in scope)) scope.previous_currency <- player.GetCurrency(); if (!("sodarevertattrs" in scope)) scope.sodarevertattrs <- {}; scope.loadout <- GetPlayerLoadout(player); scope.nextloadoutcheck <- Time(); scope.buttons_last <- 0; scope.sodachecktime <- 0; scope.rjumpnexttime <- 0; scope.rjumping <- false; scope.animating <- false; scope.animfinishtime <- 0; scope.animseq <- null; if (!("wearable" in scope)) scope.wearable <- null; scope.Think <- function() { if (!player.IsAlive() || !("Ravenous" in ROOT)) return; local tfclass = self.GetPlayerClass(); local wpn = self.GetActiveWeapon(); local current_id = (wpn) ? GetPropInt(wpn, Ravenous.STRING_NETPROP_ITEMDEF) : null; if (wearable && wearable.IsValid() && (self.IsTaunting() || wearable.GetMoveParent() != self)) EntFireByHandle(wearable, "SetParent", "!activator", -1, self, self) if (tfclass == TF_CLASS_SCOUT) { local origin = self.GetOrigin(); for (local money; money = FindByClassnameWithin(money, "item_currencypack*", origin, 288);) money.SetOrigin(origin); } // 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); } 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 (rjumping) { if (self.GetFlags() & FL_ONGROUND) { rjumping = false; self.RemoveCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); } else self.AddCond(TF_COND_CRITBOOSTED_CTF_CAPTURE); } local viewmodel = GetPropEntity(self, "m_hViewModel"); local seq_spell_draw = viewmodel.LookupSequence("spell_draw"); local seq_spell_fire = viewmodel.LookupSequence("spell_fire"); if (animating) { if (Time() >= animfinishtime) { if (animseq == seq_spell_draw) { animfinishtime = Time() + 0.4; animseq = seq_spell_fire; SetPropFloat(wpn, "LocalActiveWeaponData.m_flTimeWeaponIdle", Time() + 0.4); SetPropFloat(wpn, "LocalActiveWeaponData.m_flNextPrimaryAttack", Time() + 0.4); viewmodel.ResetSequence(animseq); } else { animating = false; animfinishtime = 0; animseq = null; } } } 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_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 (buttons_pressed & IN_RELOAD) { DoEntFire("obj_teleporter", "RunScriptCode", "self.SetAbsOrigin(activator.GetOrigin())", -1, self, self); if (tfclass == TF_CLASS_ENGINEER) { if (viewmodel && wpn) { local animtime = Time() + 0.4; SetPropFloat(wpn, "LocalActiveWeaponData.m_flTimeWeaponIdle", Time() + 1); SetPropFloat(wpn, "LocalActiveWeaponData.m_flNextPrimaryAttack", Time() + 1); viewmodel.ResetSequence(seq_spell_draw); viewmodel.SetCycle(0.0); viewmodel.SetPlaybackRate(2.0); animating = true; animfinishtime = animtime; animseq = seq_spell_draw; } } } if (buttons_pressed & IN_ATTACK) { } 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)", SINGLE_TICK, null, null); } } }, function OnScriptHook_OnTakeDamage(params) { if (!params.const_entity || (!params.const_entity.IsPlayer() && params.const_entity.GetClassname() != "tank_boss")) return; if (!params.attacker || !params.attacker.IsPlayer()) return; if (!params.weapon) return; local attacker = params.attacker; local victim = params.const_entity; 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(); 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.IsBotOfType(TF_BOT_TYPE)) { // Cleaver 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.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.Kill(); } 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); Ravenous.PrecacheParticle("utaunt_lightning_bolt"); Ravenous.PrecacheParticle("superrare_greenenergy"); PrecacheSound("ambient/energy/zap1.wav");