diff --git a/.travis.yml b/.travis.yml index 9b43c37..07433dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ before_install: - sudo apt-get install -y g++-multilib install: - - wget http://www.sourcemod.net/smdrop/1.6/sourcemod-1.6.1-git4552-linux.tar.gz -O /tmp/sourcemod.tar.gz + - wget http://www.sourcemod.net/smdrop/1.8/sourcemod-1.8.0-git5657-linux.tar.gz -O /tmp/sourcemod.tar.gz - tar -xzvf /tmp/sourcemod.tar.gz before_script: @@ -16,5 +16,4 @@ before_script: script: - ./spcomp prophunt.sp - - ./spcomp prophunt-stats.sp \ No newline at end of file diff --git a/addons/sourcemod/data/prophunt/maps/ph_abandoned.cfg b/addons/sourcemod/data/prophunt/maps/ph_abandoned_prison.cfg similarity index 100% rename from addons/sourcemod/data/prophunt/maps/ph_abandoned.cfg rename to addons/sourcemod/data/prophunt/maps/ph_abandoned_prison.cfg diff --git a/addons/sourcemod/data/prophunt/maps/ph_nightclub.cfg b/addons/sourcemod/data/prophunt/maps/ph_nightclub.cfg index 6dc344d..7f34ecc 100644 --- a/addons/sourcemod/data/prophunt/maps/ph_nightclub.cfg +++ b/addons/sourcemod/data/prophunt/maps/ph_nightclub.cfg @@ -2,33 +2,25 @@ { "Props" { +"models/props_badlands/barrel01.mdl" {} "models/props_badlands/barrel02.mdl" {} +"models/props_badlands/barrel03.mdl" {} "models/props_farm/wooden_barrel.mdl" {} "models/props_farm/wood_pile.mdl" {} "models/props_farm/metal_pile.mdl" {} -"models/props_farm/pallet001.mdl" {} -"models/props_farm/welding_machine01.mdl" {} "models/props_hydro/water_barrel.mdl" {} "models/props_hydro/water_barrel_cluster.mdl" {} "models/props_hydro/barrel_crate_half.mdl" {} "models/props_spytech/bench001a.mdl" {} "models/props_trainyard/beer_keg001.mdl" {} -"models/props_trainyard/distillery_barrel001.mdl" {} "models/props_well/computer_cart01.mdl" {} "models/props_well/hand_truck01.mdl" {} "models/props_2fort/propane_tank_tall01.mdl" {} "models/props_2fort/miningcrate001.mdl" {} "models/props_2fort/miningcrate002.mdl" {} +"models/props_2fort/oildrum.mdl" {} +"models/props_2fort/tire003.mdl" {} "models/props_2fort/wastebasket01.mdl" {} -"models/props_2fort/handrail001_i1.mdl" {} -"models/props_2fort/locker001.mdl" {} -"models/props_2fort/frog.mdl" {} -"models/props_2fort/mop_and_bucket.mdl" {} -"models/props_manor/chair_01.mdl" {} -"models/props_manor/couch_01.mdl" {} -"models/props_manor/table_01.mdl" {} -"models/props_urban/urban_chair.mdl" {} -"models/props_medical/beer_barrels.mdl" {} } "Settings" { diff --git a/addons/sourcemod/data/prophunt/maps/ph_nightclub_v2.cfg b/addons/sourcemod/data/prophunt/maps/ph_nightclub_v2.cfg new file mode 100644 index 0000000..6dc344d --- /dev/null +++ b/addons/sourcemod/data/prophunt/maps/ph_nightclub_v2.cfg @@ -0,0 +1,41 @@ +"prophuntmapconfig" +{ +"Props" +{ +"models/props_badlands/barrel02.mdl" {} +"models/props_farm/wooden_barrel.mdl" {} +"models/props_farm/wood_pile.mdl" {} +"models/props_farm/metal_pile.mdl" {} +"models/props_farm/pallet001.mdl" {} +"models/props_farm/welding_machine01.mdl" {} +"models/props_hydro/water_barrel.mdl" {} +"models/props_hydro/water_barrel_cluster.mdl" {} +"models/props_hydro/barrel_crate_half.mdl" {} +"models/props_spytech/bench001a.mdl" {} +"models/props_trainyard/beer_keg001.mdl" {} +"models/props_trainyard/distillery_barrel001.mdl" {} +"models/props_well/computer_cart01.mdl" {} +"models/props_well/hand_truck01.mdl" {} +"models/props_2fort/propane_tank_tall01.mdl" {} +"models/props_2fort/miningcrate001.mdl" {} +"models/props_2fort/miningcrate002.mdl" {} +"models/props_2fort/wastebasket01.mdl" {} +"models/props_2fort/handrail001_i1.mdl" {} +"models/props_2fort/locker001.mdl" {} +"models/props_2fort/frog.mdl" {} +"models/props_2fort/mop_and_bucket.mdl" {} +"models/props_manor/chair_01.mdl" {} +"models/props_manor/couch_01.mdl" {} +"models/props_manor/table_01.mdl" {} +"models/props_urban/urban_chair.mdl" {} +"models/props_medical/beer_barrels.mdl" {} +} +"Settings" +{ +"doors" "0" +"relay" "0" +"round" "250" +"freeze" "1" +"alt" "" +} +} \ No newline at end of file diff --git a/addons/sourcemod/data/prophunt/prophunt_config.cfg b/addons/sourcemod/data/prophunt/prophunt_config.cfg index b2d58eb..7d227c0 100644 --- a/addons/sourcemod/data/prophunt/prophunt_config.cfg +++ b/addons/sourcemod/data/prophunt/prophunt_config.cfg @@ -46,7 +46,7 @@ "6" { "name" "Heavy" - "hunter_limit" "2" + "hunter_limit" "-1" "base_speed" "230.0" "max_speed" "400.0" "speed_increment" "50.0" @@ -105,8 +105,32 @@ { "sound" "vo/mvm_bonus01.mp3" // "game" "Announcer.MVM_Bonus" -// "channel" "7" + "channel" "7" + } + "FirstBlood" + { + "sound" "vo/announcer_AM_FirstBlood01.wav" + "game" "Announcer.AM_FirstBloodRandom" + "channel" "7" + "soundlevel" "95" + } + + "FirstBloodFast" + { + "sound" "vo/announcer_AM_FirstBlood06.wav" + "game" "Announcer.AM_FirstBloodFast" + "channel" "7" + "soundlevel" "95" + } + + "FirstBloodFinally" + { + "sound" "vo/announcer_AM_FirstBlood03.wav" + "game" "Announcer.AM_FirstBloodFinally" + "channel" "7" + "soundlevel" "95" } + "Internet" { "sound" "vo/pyro_positivevocalization01.mp3" @@ -222,3207 +246,614 @@ "sound" "buttons/button3.wav" } } + + // Class bits for onlyclass specifiers + // Add these up to apply things to specific classes + // 1 = Scout + // 2 = Sniper + // 4 = Soldier + // 8 = DemoMan + // 16 = Medic + // 32 = Heavy + // 64 = Pyro + // 128 = Spy + // 256 = Engineer + // i.e. to replace a weapon for both DemoMan and Soldier, but no one else: + // "replace_onlyclasses" "12" - "items" +////////////////////////////////////////////// +////////////////////////////////////////////// +//////////////Items by Classname////////////// +////////////////////////////////////////////// +////////////////////////////////////////////// + + "item_classes" { - + ////////////////////////////////////////////// -////////////////Melee Weapons///////////////// +////////////////////Shared//////////////////// ////////////////////////////////////////////// -////////////////Scout Melee/////////////////// - "0" + "saxxy" { - "name" "TF_WEAPON_BAT" - "item_name" "#TF_Weapon_Bat" - } - - "44" - { - "name" "The Sandman" - "item_name" "#TF_Unique_Achievement_Bat" - "propername" "1" + "name" "Generic Attributeless Melee" } - "190" + // Blocked Slot for Demo, replaced by Shotgun for Soldier + "tf_weapon_parachute" { - "name" "Upgradeable TF_WEAPON_BAT" - "item_name" "#TF_Weapon_Bat" + "name" "B.A.S.E. Jumper" + "removed_hunters" "1" + "replace_onlyclasses" "4" + "replace" "tf_weapon_shotgun_soldier : 10 : 0 : 1" } - "221" + "tf_weapon_katana" { - "name" "The Holy Mackerel" - "item_name" "#TF_TheHolyMackerel" - "propername" "1" + "name" "Half-Zatoichi" } - "317" + // All shotguns + // Note the class-specific restriction for Pyro + "tf_weapon_shotgun" { - "name" "The Candy Cane" - "item_name" "#TF_CandyCane" - "propername" "1" + "name" "Generic Shotgun" + "damage_hunters" "0.8" + "self_damage_hunters" "8.0" + "addattribs" "3 ; -4.0 ; 37 ; 6.25" + "attribs_onlyclasses" "64" } - "325" + // Remove all shared pistols from Hunters + "tf_weapon_pistol" { - "name" "The Boston Basher" - "item_name" "#TF_BostonBasher" - "propername" "1" + "name" "Generic Pistol" + "removed_hunters" "1" } - "349" + "tf_weapon_spellbook" { - "name" "Sun-on-a-Stick" - "item_name" "#TF_Unique_RiftFireMace" + "removed_hunters" "1" + "removed_props" "1" } - - "355" + +////////////////////////////////////////////// +///////////////////Scout////////////////////// +////////////////////////////////////////////// + +///////////////Scout Primary/////////////////// + + "tf_weapon_scattergun" { - "name" "The Fan O'War" - "item_name" "#TF_Gunbai" - "propername" "1" + "name" "Generic Scout Primary" } - "450" + "tf_weapon_handgun_scout_primary" { - "name" "The Atomizer" - "item_name" "#TF_Atomizer" - "propername" "1" + "name" "The Shortstop" } - "452" + "tf_weapon_pep_brawler_blaster" { - "name" "Three-Rune Blade" - "item_name" "#TF_ScoutSword" + "name" "Baby Face's Blaster" } - - "572" + +//////////////Scout Secondary////////////////// + + // Replace Bonk and Crit-a-Cola with pistol + "tf_weapon_lunchbox_drink" { - "name" "Unarmed Combat" - "item_name" "#TF_UnarmedCombat" + "name" "Bonk or Crit-a-Cola" + "replace" "tf_weapon_pistol : 23 : 0 : 1" } - - "648" + + "tf_weapon_jar_milk" { - "name" "The Wrap Assassin" - "item_name" "#TF_BallBuster" - "propername" "1" + "name" "Mad or Mutated Milk" } - "660" + "tf_weapon_handgun_scout_secondary" { - "name" "Festive Bat 2011" - "item_name" "#TF_Weapon_Bat_Festive2011" + "name" "Winger or Pocket Pistol" } - "999" + "tf_weapon_cleaver" { - "name" "Festive Holy Mackeral" - "item_name" "#TF_Weapon HolyMackeral_Festive2012" + "name" "Flying Guillotine" } - "30667" +////////////////Scout Melee//////////////////// + + "tf_weapon_bat" { - "name" "Batsaber" - "item_name" "#TF_invasion_bat" + "name" "Generic Scout Melee" } -///////////////Demo Melee///////////////////// - "1" + "tf_weapon_bat_wood" { - "name" "TF_WEAPON_BOTTLE" - "item_name" "#TF_Weapon_Bottle" + "name" "Sandman" } - "132" + "tf_weapon_bat_fish" { - "name" "The Eyelander" - "item_name" "#TF_Unique_Achievement_Sword" - "propername" "1" + "name" "Holy Mackeral or Unarmed Combat" } - "172" + "tf_weapon_bat_giftwrap" { - "name" "The Scotsman's Skullcutter" - "item_name" "#TF_Unique_BattleAxe" - "propername" "1" + "name" "Wrap Assassin" } + +////////////////////////////////////////////// +////////////////////Soldier/////////////////// +////////////////////////////////////////////// + +////////////////Soldier Primary/////////////// - "191" + "tf_weapon_rocketlauncher" { - "name" "Upgradeable TF_WEAPON_BOTTLE" - "item_name" "#TF_Weapon_Bottle" + "name" "Generic Rocket Launcher" + "damage_hunters" "0.75" } - "266" + "tf_weapon_rocketlauncher_directhit" { - "name" "The Horseless Headless Horseman's Headtaker" - "item_name" "#TF_HalloweenBoss_Axe" - "propername" "1" + "name" "Direct Hit" + "damage_hunters" "0.75" } - - "307" + +// Replace with rocket launcher due to charge shot and unlimit ammo being direct upgrade + "tf_weapon_particle_cannon" { - "name" "The Ullapool Caber" - "item_name" "#TF_UllapoolCaber" - "propername" "1" + "name" "Cow Mangler 5000" + "replace" "tf_weapon_rocketlauncher : 18 : 0 : 1" } - "327" + "tf_weapon_rocketlauncher_airstrike" { - "name" "The Claidheamohmor" - "item_name" "#TF_Claidheamohmor" - "propername" "1" + "name" "Air Strike" + "damage_hunters" "0.75" } - "357" +///////////////Soldier Secondary////////////// +// Note: Boots cannot be represented by class + + "tf_weapon_shotgun_soldier" { - "name" "The Half-Zatoichi" - "item_name" "#TF_SoldierKatana" - "propername" "1" + "name" "Soldier's Shotgun" } - "404" + "tf_weapon_buff_item" { - "name" "The Persian Persuader" - "item_name" "#TF_PersianPersuader" - "propername" "1" + "name" "Buff Banner, Battalion's Backup, Concheror" } - "482" + "tf_weapon_raygun" { - "name" "Nessie's Nine Iron" - "item_name" "#TF_NineIron" + "name" "Righteous Bison" } - "609" - { - "name" "The Scottish Handshake" - "item_name" "#TF_ScottishHandshake" - "propername" "1" - } - - "1082" +/////////////////Soldier Melee//////////////// + + "tf_weapon_shovel" { - "name" "Festive Eyelander" - "item_name" "#TF_Weapon_Eyelander_Festive2013" - "propername" "1" + "name" "Generic Soldier Melee" } +////////////////////////////////////////////// +/////////////////////Pyro///////////////////// +////////////////////////////////////////////// -///////////////Pyro Melee///////////////////// +/////////////////Pyro Primary///////////////// - "2" + "tf_weapon_flamethrower" { - "name" "TF_WEAPON_FIREAXE" - "item_name" "#TF_Weapon_FireAxe" - "self_damage_hunters" "5.0" + "name" "Generic Flamethrower" + "self_damage_hunters" "1.0" + // Remove airblast + "addattribs" "354 ; 1.0" } - "38" +///////////////Pyro Secondary///////////////// + + "tf_weapon_shotgun_pyro" { - "name" "The Axtinguisher" - "item_name" "#TF_Unique_Achievement_FireAxe1" - "propername" "1" + "name" "Pyro's Shotgun" + "damage_hunters" "0.8" + "self_damage_hunters" "8.0" + // Clipsize 2, 625% ammo (unlimited in PH 3.x and below) + "addattribs" "3 ; -4.0 ; 37 ; 6.25" } - - "153" + + "tf_weapon_flaregun" { - "name" "The Homewrecker" - "item_name" "#TF_Unique_SledgeHammer" - "propername" "1" + "name" "Generic Flare Gun" + "self_damage_hunters" "5.0" } - "192" + "tf_weapon_flaregun_revenge" { - "name" "Upgradeable TF_WEAPON_FIREAXE" - "item_name" "#TF_Weapon_FireAxe" + "name" "Manmelter" + "self_damage_hunters" "5.0" } - "214" +/////////////////Pyro Melee/////////////////// + + "tf_weapon_fireaxe" { - "name" "The Powerjack" - "item_name" "#TF_ThePowerjack" - "propername" "1" + "name" "Generic Pyro Melee" } - - "326" + +////////////////////////////////////////////// +////////////////////DemoMan/////////////////// +////////////////////////////////////////////// + +////////////////DemoMan Primary/////////////// +// Note: Boots cannot be represented by class +// Blocked slot + + "tf_weapon_grenadelauncher" { - "name" "The Back Scratcher" - "item_name" "#TF_BackScratcher" - "propername" "1" + "name" "Generic Grenade Launcher" + "removed_hunters" "1" } - "348" + "tf_weapon_cannon" { - "name" "Sharpened Volcano Fragment" - "item_name" "#TF_Unique_RiftFireAxe" + "name" "Loose Cannon" + "removed_hunters" "1" } - "457" - { - "name" "The Postal Pummeler" - "item_name" "#TF_Mailbox" - "propername" "1" - } +///////////////DemoMan Secondary////////////// - "466" + "tf_weapon_pipebomblauncher" { - "name" "The Maul" - "item_name" "#TF_RFAHammer" - "propername" "1" + "name" "Generic Stickybomb Launcher" + "preserveattribs" "0" + // Turn into Stickybomb Jumper + "addattribs" "1 ; 0.0 ; 15 ; 1.0 ; 181 ; 1.0 ; 78 ; 3.0 ; 280 ; 14.0 ; 89 ; -6.0 ; 400 ; 1.0" } - "593" + "tf_wearable_demoshield" { - "name" "The Third Degree" - "item_name" "#TF_ThirdDegree" - "propername" "1" + "name" "Chargin' Targe, Splendid Screen, or Tide Turner" } - "739" - { - "name" "The Lollichop" - "item_name" "#TF_Lollichop" - "propername" "1" - "self_damage_hunters" "5.0" - } +/////////////////DemoMan Melee//////////////// - "813" + "tf_weapon_bottle" { - "name" "The Neon Annihilator" - "item_name" "#TF_SD_Sign" - "propername" "1" - "self_damage_hunters" "8.0" + "name" "Generic DemoMan Melee" } - "834" + "tf_weapon_sword" { - "name" "Promo Neon Annihilator" - "item_name" "#TF_SD_Sign" - "propername" "1" - "self_damage_hunters" "8.0" + "name" "Eyelander, HHHH, Calidheamh Mor, Persian Persuader, Nessie's Nine Iron" } - "1000" +////////////////////////////////////////////// +////////////////////Heavy///////////////////// +////////////////////////////////////////////// + +////////////////Heavy Primary///////////////// +// Blocked slot + + "tf_weapon_minigun" { - "name" "Festive Axstinguisher" - "item_name" "#TF_Weapon_Axtinguisher_Festive2012" + "name" "Generic Minigun" + "removed_hunters" "1" } -//////////////Sniper Melee///////////////////// - "3" +///////////////Heavy Secondary//////////////// + + "tf_weapon_shotgun_hwg" { - "name" "TF_WEAPON_CLUB" - "item_name" "#TF_Weapon_Club" + "name" "Heavy's Shotgun" } - - "171" + + "tf_weapon_lunchbox" { - "name" "The Tribalman's Shiv" - "item_name" "#TF_Unique_TribalmanKukri" - "propername" "1" + "name" "Sandvich, Dalokohs Bar, Buffalo Steak" } - "193" +/////////////////Heavy Melee////////////////// + + "tf_weapon_fists" { - "name" "Upgradeable TF_WEAPON_CLUB" - "item_name" "#TF_Weapon_Club" + "name" "Generic Heavy Melee" } - - "232" + +////////////////////////////////////////////// +///////////////////Engineer/////////////////// +////////////////////////////////////////////// + +///////////////Engineer Primary/////////////// + + "tf_weapon_shotgun_primary" { - "name" "The Bushwacka" - "item_name" "#TF_TheBushwacka" - "propername" "1" + "name" "Engineer's Shotgun" } - - "401" + + "tf_weapon_sentry_revenge" { - "name" "The Shahanshah" - "item_name" "#TF_Shahanshah" - "propername" "1" + "name" "Frontier Justice" } -//////////////Spy Melee//////////////////////// - "4" + "tf_weapon_drg_pomson" { - "name" "TF_WEAPON_KNIFE" - "item_name" "#TF_Weapon_Knife" + "name" "Pomson 6000" } - "194" + "tf_weapon_shotgun_building_rescue" { - "name" "Upgradeable TF_WEAPON_KNIFE" - "item_name" "#TF_Weapon_Knife" + "name" "Rescue Ranger" } - "225" - { - "name" "Your Eternal Reward" - "item_name" "#TF_EternalReward" - } +//////////////Engineer Secondary////////////// +// Blocked slot - "356" + "tf_weapon_laser_pointer" { - "name" "Conniver's Kunai" - "item_name" "#TF_Kunai" + "name" "Wrangler, Gigar Counter" + "removed_hunters" "1" } - "461" + "tf_weapon_mechanical_arm" { - "name" "The Big Earner" - "item_name" "#TF_BigEarner" - "propername" "1" + "name" "Short-Circuit" + "removed_hunters" "1" } - "574" - { - "name" "The Wanga Prick" - "item_name" "#TF_VoodooPin" - } +////////////////Engineer Melee//////////////// - "638" + "tf_weapon_wrench" { - "name" "The Sharp Dresser" - "item_name" "#TF_SharpDresser" - "propername" "1" + "name" "Generic Engineer Melee" } - "649" + "tf_weapon_robot_arm" { - "name" "The Spy-cicle" - "item_name" "#TF_SpyCicle" - "propername" "1" + "name" "Gunslinger" } - "665" +////////////////////////////////////////////// +////////////////////Medic///////////////////// +////////////////////////////////////////////// + +////////////////Medic Primary///////////////// + + "tf_weapon_syringegun_medic" { - "name" "Festive Knife 2011" - "item_name" "#TF_Weapon_Knife_Festive2011" + "name" "Generic Syringe Gun" + "self_damage_hunters" "4.0" } - - "727" + +// Replace with syringe gun + "tf_weapon_crossbow" { - "name" "The Black Rose" - "item_name" "#TF_BlackRose" - "propername" "1" + "name" "Crusader's Crossbow" + "replace" "tf_weapon_syringegun_medic : 17 : 0 : 1" } + +///////////////Medic Secondary//////////////// +// Blocked slot - "794" + "tf_weapon_medigun" { - "name" "Silver Botkiller Knife Mk.I" - "item_name" "#TF_Weapon_Knife_MVMLoot" - "propername" "1" + "name" "Generic Medigun" + "removed_hunters" "1" } + +/////////////////Medic Melee////////////////// - "803" + "tf_weapon_bonesaw" { - "name" "Gold Botkiller Knife Mk.I" - "item_name" "#TF_Weapon_Knife_MVMLootGold" - "propername" "1" + "name" "Generic Bonesaw" } + +////////////////////////////////////////////// +///////////////////Sniper///////////////////// +////////////////////////////////////////////// + +///////////////Sniper Primary///////////////// - "883" + //Replace with Huntsman + "tf_weapon_sniperrifle" { - "name" "Rust Botkiller Knife" - "item_name" "#TF_Weapon_Knife_IntermediateMVMLoot" - "propername" "1" + "name" "Generic Sniper Rifle" + "replace" "tf_weapon_compound_bow : 56 : 10 : 6" } - "892" + "tf_weapon_compound_bow" { - "name" "Blood Botkiller Knife" - "item_name" "#TF_Weapon_Knife_IntermediateMVMRareLoot" - "propername" "1" + "name" "Huntsman or Fortified Compound" + "damage_hunters" "0.85" } - "901" + "tf_weapon_sniperrifle_decap" { - "name" "Carbonado Botkiller Knife" - "item_name" "#TF_Weapon_Knife_ExpertMVMLoot" - "propername" "1" + "name" "Bazaar Bargain" + "replace" "tf_weapon_compound_bow : 56 : 10 : 6" } - "910" + "tf_weapon_sniperrifle_classic" { - "name" "Diamond Botkiller Knife" - "item_name" "#TF_Weapon_Knife_ExpertMVMRareLoot" - "propername" "1" + "name" "The Classic" + "replace" "tf_weapon_compound_bow : 56 : 10 : 6" } + +//////////////Sniper Secondary//////////////// +// Note: Shields cannot be represented by class - "959" + "tf_weapon_smg" { - "name" "Silver Botkiller Knife Mk.II" - "item_name" "#TF_Weapon_Knife_AdvancedMVMLoot_Engineer" - "propername" "1" + "name" "Generic SMG" + "replace" "tf_weapon_jar : 58 : 5 : 6" } - "968" - { - "name" "Gold Botkiller Knife Mk.II" - "item_name" "#TF_Weapon_Knife_AdvancedMVMRareLoot_Engineer" - "propername" "1" - } - -//////////////Heavy Melee////////////////////// - "5" + "tf_weapon_jar" { - "name" "TF_WEAPON_FISTS" - "item_name" "#TF_Weapon_Fists" + "name" "Jarate or Self-Aware Beauty Mark" } - "43" - { - "name" "The Killing Gloves of Boxing" - "item_name" "#TF_Unique_Achievement_Fists" - "propername" "1" - } +////////////////Sniper Melee////////////////// - "195" + "tf_weapon_club" { - "name" "Upgradeable TF_WEAPON_FISTS" - "item_name" "#TF_Weapon_Fists" + "name" "Generic Sniper Melee" } + +////////////////////////////////////////////// +//////////////////////Spy///////////////////// +////////////////////////////////////////////// + +// Spy is not allowed in PropHunt, so don't waste time listing his items. - "239" + } + +////////////////////////////////////////////// +////////////////////////////////////////////// +////////////////Items by Index//////////////// +////////////////////////////////////////////// +////////////////////////////////////////////// + +// This will override the classname section above + "items" + { + +////////////Shared Secondary//////////////////// + +// Pyro gets Shotgun instead of Reserve Shooter + "415" { - "name" "Gloves of Running Urgently" - "item_name" "#TF_Unique_Gloves_of_Running_Urgently" + "name" "The Reserve Shooter" + "damage_hunters" "0.8" + "self_damage_hunters" "8.0" + "replace" "tf_weapon_shotgun : 12 : 0 : 1" + "replace_onlyclasses" "64" } - "310" +/////////////Scout Primary////////////////////// + +//Backscatter is cheesy in PropHunt + "1103" { - "name" "Warrior's Spirit" - "item_name" "#TF_WarriorsSpirit" + "name" "The Back Scatter" + "item_name" "#TF_Weapon_BackScatter_Desc" + "propername" "1" + "replace" "tf_weapon_scattergun : 13 : 0 : 1" } - "331" +////////////Soldier Primary////////////////////// + +// Override rocket launcher changes for Rocket Jumper + "237" { - "name" "Fists of Steel" - "item_name" "#TF_FistsOfSteel" + "name" "Rocket Jumper" + "item_name" "#TF_Weapon_RocketLauncher_Jump" } - "426" +////////////Soldier Secondary//////////////////// + + "133" { - "name" "The Eviction Notice" - "item_name" "#TF_EvictionNotice" - "propername" "1" + "name" "The Gunboats" } - "587" + "444" { - "name" "Apoco-Fists" - "item_name" "#TF_Apocofists" - "propername" "1" + "name" "The Mantreads" } - - "656" + +/////////////Demo Primary//////////////////////// +// Boots can't be matched by class +// Blocked Slot + + "405" { - "name" "The Holiday Punch" - "item_name" "#TF_MasculineMittens" - "propername" "1" + "name" "Ali Baba's Wee Booties" + "removed_hunters" "1" } - "1084" + "608" { - "name" "Festive Gloves of Running Urgently" - "item_name" "#TF_Weapon_GRU_Festive2013" - "propername" "1" + "name" "The Bootlegger" + "removed_hunters" "1" } - "1100" +////////////Demo Secondary/////////////////////// + +// Override stickybomb launcher changes for Stickybomb Jumper + "265" { - "name" "The Bread Bite" - "item_name" "#TF_Weapon_BreadBite" - "propername" "1" + "name" "Stickybomb Jumper" + "item_name" "#TF_Weapon_StickyBomb_Jump" } -//////////////Soldier Melee//////////////////// - "6" +////////////Engineer Primary///////////////////// + +// Reduce shots per clip for Panic Attack + "1153" { - "name" "TF_WEAPON_SHOVEL" - "item_name" "#TF_Weapon_Shovel" + "name" "Panic Attack" + "item_name" "#TF_Weapon_PanicAttack" + "damage_hunters" "0.8" + "self_damage_hunters" "8.0" + "addattribs" "424 ; 0.5" } + - "128" +//////////////Engineer Melee//////////////////// + +// Eureka Effect is broken in Arena + "589" { - "name" "The Equalizer" - "item_name" "#TF_Unique_Achievement_Pickaxe" + "name" "The Eureka Effect" + "item_name" "#TF_Wrenchmotron" "propername" "1" + "replace" "tf_weapon_wrench : 7 : 0 : 1" } - - "154" + +//////////////Medic Melee/////////////////////// + +// Amputator heals + "304" { - "name" "The Pain Train" - "item_name" "#TF_Unique_Makeshiftclub" + "name" "The Amputator" + "item_name" "#TF_Amputator" "propername" "1" + "replace" "tf_weapon_bonesaw : 8 : 0 : 1" } - "196" - { - "name" "Upgradeable TF_WEAPON_SHOVEL" - "item_name" "#TF_Weapon_Shovel" - } - - "416" +// Solemn Vow breaks PropHunt + "413" { - "name" "The Market Gardener" - "item_name" "#TF_MarketGardener" + "name" "The Solemn Vow" + "item_name" "#TF_SolemnVow" "propername" "1" + "replace" "tf_weapon_bonesaw : 8 : 0 : 1" } - "447" +////////////Sniper Secondary///////////////////// +// Note: Shields cannot be represented by class + +// Razorback is useless in PropHunt +// Give them the same thing we give SMG users + "57" { - "name" "The Disciplinary Action" - "item_name" "#TF_DisciplinaryAction" - "propername" "1" + "name" "The Razorback" + "replace" "tf_weapon_jar : 58 : 5 : 6" } - "775" + "231" { - "name" "The Escape Plan" - "item_name" "#TF_Unique_Pickaxe_EscapePlan" - "propername" "1" + "name" "Darwin's Danger Shield" } - "880" + "642" { - "name" "The Freedom Staff" - "item_name" "#TF_TW_Staff" - "propername" "1" + "name" "Cozy Camper" } -//////////////Engineer Melee//////////////////// - "7" - { - "name" "TF_WEAPON_WRENCH" - "item_name" "#TF_Weapon_Wrench" - } - - "142" - { - "name" "The Gunslinger" - "item_name" "#TF_Unique_Robot_Arm" - "propername" "1" - } - - "155" - { - "name" "The Southern Hospitality" - "item_name" "#TF_Unique_Combat_Wrench" - "propername" "1" - } - - "169" - { - "name" "Golden Wrench" - "item_name" "#TF_Unique_Golden_Wrench" - } - - "197" - { - "name" "Upgradeable TF_WEAPON_WRENCH" - "item_name" "#TF_Weapon_Wrench" - } - - "329" - { - "name" "The Jag" - "item_name" "#TF_Jag" - "propername" "1" - } - - "589" - { - "name" "The Eureka Effect" - "item_name" "#TF_Wrenchmotron" - "propername" "1" - "replace" "tf_weapon_wrench:7:0:1" - } - - "662" - { - "name" "Festive Wrench 2011" - "item_name" "#TF_Weapon_Wrench_Festive2011" - } - - "795" - { - "name" "Silver Botkiller Wrench Mk.I" - "item_name" "#TF_Weapon_Wrench_MVMLoot" - "propername" "1" - } - - "804" - { - "name" "Gold Botkiller Wrench Mk.I" - "item_name" "#TF_Weapon_Wrench_MVMLootGold" - "propername" "1" - } - - "884" - { - "name" "Rust Botkiller Wrench" - "item_name" "#TF_Weapon_Wrench_IntermediateMVMLoot" - "propername" "1" - } - - "893" - { - "name" "Blood Botkiller Wrench" - "item_name" "#TF_Weapon_Wrench_IntermediateMVMRareLoot" - "propername" "1" - } - - "902" - { - "name" "Carbonado Botkiller Wrench" - "item_name" "#TF_Weapon_Wrench_ExpertMVMLoot" - "propername" "1" - } - - "911" - { - "name" "Diamond Botkiller Wrench" - "item_name" "#TF_Weapon_Wrench_ExpertMVMRareLoot" - "propername" "1" - } - - "960" - { - "name" "Silver Botkiller Wrench Mk.II" - "item_name" "#TF_Weapon_Wrench_AdvancedMVMLoot_Engineer" - "propername" "1" - } - - "969" - { - "name" "Gold Botkiller Wrench Mk.II" - "item_name" "#TF_Weapon_Wrench_AdvancedMVMRareLoot_Engineer" - "propername" "1" - } - -//////////////Medic Melee/////////////////////// - "8" - { - "name" "TF_WEAPON_BONESAW" - "item_name" "#TF_Weapon_Bonesaw" - } - - "37" - { - "name" "The Ubersaw" - "item_name" "#TF_Unique_Achievement_Bonesaw1" - "propername" "1" - } - - "173" - { - "name" "The Vita-Saw" - "item_name" "#TF_Unique_BattleSaw" - "propername" "1" - } - - "198" - { - "name" "Upgradeable TF_WEAPON_BONESAW" - "item_name" "#TF_Weapon_Bonesaw" - } - - "304" - { - "name" "The Amputator" - "item_name" "#TF_Amputator" - "propername" "1" - "replace" "tf_weapon_bonesaw:8:0:1" - } - - "413" - { - "name" "The Solemn Vow" - "item_name" "#TF_SolemnVow" - "propername" "1" - "replace" "tf_weapon_bonesaw:8:0:1" - } - - "1003" - { - "name" "Festive Ubersaw" - "item_name" "#TF_Weapon_Ubersaw_Festive2012" - } - - "1143" - { - "name" "Festive Bonesaw" - "item_name" "#TF_Weapon_FestiveBonesaw" - } - -//////////////Multi-class Melee/////////////////////// - "264" - { - "name" "Frying Pan" - "item_name" "#TF_Weapon_FryingPan" - } - - "423" - { - "name" "Saxxy" - "item_name" "#TF_Saxxy" - } - - "474" - { - "name" "The Conscientious Objector " - "item_name" "#TF_ConscientiousObjector" - } - - "880" - { - "name" "The Freedom Staff" - "item_name" "#TF_TW_Staff" - } - - "939" - { - "name" "The Bat Outta Hell" - "item_name" "#TF_BatOuttaHell" - } - - "954" - { - "name" "The Memory Maker" - "item_name" "#TF_Memory_Maker" - } - - "1013" - { - "name" "The Ham Shank" - "item_name" "#TF_HamShank" - } - - "1071" - { - "name" "Gold Frying Pan" - "item_name" "#TF_Gold_FryingPan" - } - - "1123" - { - "name" "Necro Smasher" - "item_name" "#TF_Weapon_Necro_Smasher" - } - - "1127" - { - "name" "The Crossing Guard" - "item_name" "#TF_CrossingGuard" - } - - - -//////////////////////////////////////////////// -/////////////Primary Weapons//////////////////// -//////////////////////////////////////////////// - -/////////////Scout Primary////////////////////// - - "13" - { - "name" "TF_WEAPON_SCATTERGUN" - "item_name" "#TF_Weapon_Scattergun" - } - - "45" - { - "name" "The Force-a-Nature" - "item_name" "#TF_Unique_Achievement_Scattergun_Double" - "propername" "1" - } - - "200" - { - "name" "Upgradeable TF_WEAPON_SCATTERGUN" - "item_name" "#TF_Weapon_Scattergun" - } - - "220" - { - "name" "The Shortstop" - "item_name" "#TF_TheShortstop" - "propername" "1" - } - - "448" - { - "name" "The Soda Popper" - "item_name" "#TF_SodaPopper" - "propername" "1" - } - - "669" - { - "name" "Festive Scattergun 2011" - "item_name" "#TF_Weapon_Scattergun_Festive2011" - } - - "772" - { - "name" "Baby Face's Blaster" - "item_name" "#TF_Weapon_PEP_Scattergun" - } - - "799" - { - "name" "Silver Botkiller Scattergun Mk.I" - "item_name" "#TF_Weapon_Scattergun_MVMLoot" - "propername" "1" - } - - "808" - { - "name" "Gold Botkiller Scattergun Mk.I" - "item_name" "#TF_Weapon_Scattergun_MVMLootGold" - "propername" "1" - } - - "888" - { - "name" "Rust Botkiller Scattergun" - "item_name" "#TF_Weapon_Scattergun_IntermediateMVMLoot" - "propername" "1" - } - - "897" - { - "name" "Blood Botkiller Scattergun" - "item_name" "#TF_Weapon_Scattergun_IntermediateMVMRareLoot" - "propername" "1" - } - - "906" - { - "name" "Carbonado Botkiller Scattergun" - "item_name" "#TF_Weapon_Scattergun_ExpertMVMLoot" - "propername" "1" - } - - "915" - { - "name" "Diamond Botkiller Scattergun" - "item_name" "#TF_Weapon_Scattergun_ExpertMVMRareLoot" - "propername" "1" - } - - "964" - { - "name" "Silver Botkiller Scattergun Mk.II" - "item_name" "#TF_Weapon_Scattergun_AdvancedMVMLoot_Engineer" - "propername" "1" - } - - "973" - { - "name" "Gold Botkiller Scattergun Mk.II" - "item_name" "#TF_Weapon_Scattergun_ADVANCEDMVMRareLoot_Engineer" - "propername" "1" - } - - "1078" - { - "name" "Festive Force-a-Nature" - "item_name" "#TF_Weapon_ForceANature_Festive2013" - "propername" "1" - } - - "1103" - { - "name" "The Back Scatter" - "item_name" "#TF_Weapon_BackScatter_Desc" - "propername" "1" - "replace" "tf_weapon_scattergun:13:0:1" - } - - "15002" - { - "name" "concealedkiller_scattergun_nightterror" - "item_name" "#TF_Weapon_Scattergun" - } - - "15015" - { - "name" "craftsmann_scattergun_tartantorpedo" - "item_name" "#TF_Weapon_Scattergun" - } - - "15021" - { - "name" "craftsmann_scattergun_countrycrusher" - "item_name" "#TF_Weapon_Scattergun" - } - - "15029" - { - "name" "craftsmann_scattergun_backcountryblaster" - "item_name" "#TF_Weapon_Scattergun" - } - - "15036" - { - "name" "teufort_scattergun_sprucedeuce" - "item_name" "#TF_Weapon_Scattergun" - } - - "15053" - { - "name" "powerhouse_scattergun_currentevent" - "item_name" "#TF_Weapon_Scattergun" - } - - "16006" - { - "name" "template_civilian_scattergun" - "item_name" "#TF_Weapon_Scattergun" - } - - "16018" - { - "name" "template_commando_scattergun" - "item_name" "#TF_Weapon_Scattergun" - } - - "16029" - { - "name" "template_elite_scattergun" - "item_name" "#TF_Weapon_Scattergun" - } - -/////////////Demo Primary//////////////////////// - - "19" - { - "name" "TF_WEAPON_GRENADELAUNCHER" - "item_name" "#TF_Weapon_GrenadeLauncher" - "removed_hunters" "1" - } - - "206" - { - "name" "Upgradeable TF_WEAPON_GRENADELAUNCHER" - "item_name" "#TF_Weapon_GrenadeLauncher" - "removed_hunters" "1" - } - - "308" - { - "name" "The Loch-n-Load" - "item_name" "#TF_LochNLoad" - "propername" "1" - "removed_hunters" "1" - } - - "405" - { - "name" "Ali Baba's Wee Booties" - "item_name" "#TF_Ali_Babas_Wee_Booties" - } - - "608" - { - "name" "The Bootlegger" - "item_name" "#TF_Bootlegger" - "propername" "1" - } - - "996" - { - "name" "Loose Cannon" - "item_name" "TF_Weapon_Cannon" - "removed_hunters" "1" - } - - "1007" - { - "name" "Festive Grenade Launcher" - "item_name" "#TF_Weapon_GrenadeLauncher_Festive2012" - "removed_hunters" "1" - } - - "1151" - { - "name" "The Iron Bomber" - "item_name" "#TF_Weapon_Iron_bomber" - "removed_hunters" "1" - } - -/////////////Pyro Primary//////////////////////// - - "21" - { - "name" "TF_WEAPON_FLAMETHROWER" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "40" - { - "name" "The Backburner" - "item_name" "#TF_Unique_Achievement_Flamethrower" - "propername" "1" - "damage_hunters" "0.8" - "self_damage_hunters" "1.0" - } - - "208" - { - "name" "Upgradeable TF_WEAPON_FLAMETHROWER" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "215" - { - "name" "The Degreaser" - "item_name" "#TF_TheDegreaser" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "594" - { - "name" "The Phlogistinator" - "item_name" "#TF_Phlogistinator" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "659" - { - "name" "Festive Flamethrower 2011" - "item_name" "#TF_Weapon_Flamethrower_Festive2011" - "self_damage_hunters" "1.0" - } - - "741" - { - "name" "The Rainblower" - "item_name" "#TF_Rainblower" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "798" - { - "name" "Silver Botkiller Flame Thrower Mk.I" - "item_name" "#TF_Weapon_Flamethrower_MVMLoot" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "807" - { - "name" "Gold Botkiller Flame Thrower Mk.I" - "item_name" "#TF_Weapon_Flamethrower_MVMLootGold" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "887" - { - "name" "Rust Botkiller Flame Thrower" - "item_name" "#TF_Weapon_Flamethrower_IntermediateMVMLoot" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "896" - { - "name" "Blood Botkiller Flame Thrower" - "item_name" "#TF_Weapon_Flamethrower_IntermediateMVMRareLoot" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "905" - { - "name" "Carbonado Botkiller Flame Thrower" - "item_name" "#TF_Weapon_Flamethrower_ExpertMVMLoot" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "914" - { - "name" "Diamond Botkiller Flame Thrower" - "item_name" "#TF_Weapon_Flamethrower_ExpertMVMRareLoot" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "963" - { - "name" "Silver Botkiller Flame Thrower Mk.II" - "item_name" "#TF_Weapon_Flamethrower_AdvancedMVMLoot_Engineer" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "972" - { - "name" "Gold Botkiller Flame Thrower Mk.II" - "item_name" "#TF_Weapon_Flamethrower_AdvancedMVMRareLoot_Engineer" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "30474" - { - "name" "The Nostromo Napalmer" - "item_name" "#TF_NostromoNapalmer" - "propername" "1" - "self_damage_hunters" "1.0" - } - - "1146" - { - "name" "Festive Backburner" - "item_name" "#TF_Weapon_FestiveBackburner" - "damage_hunters" "0.8" - "self_damage_hunters" "1.0" - } - - "15005" - { - "name" "concealedkiller_flamethrower_forestfire" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "15017" - { - "name" "craftsmann_flamethrower_barnburner" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "15030" - { - "name" "teufort_flamethrower_bovineblazemaker" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "15034" - { - "name" "teufort_flamethrower_earthskyandfire" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "15049" - { - "name" "powerhouse_flamethrower_flashfryer" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "15054" - { - "name" "powerhouse_flamethrower_turbinetorcher" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "16000" - { - "name" "template_civilian_flamethrower" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "16012" - { - "name" "template_commando_flamethrower" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - - "16023" - { - "name" "template_elite_flamethrower" - "item_name" "#TF_Weapon_FlameThrower" - "self_damage_hunters" "1.0" - } - -/////////////Sniper Primary////////////////////// - - "14" - { - "name" "TF_WEAPON_SNIPERRIFLE" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "56" - { - "name" "The Huntsman" - "item_name" "#TF_Unique_Achievement_CompoundBow" - "propername" "1" - "damage_hunters" "0.85" - } - - "201" - { - "name" "Upgradeable TF_WEAPON_SNIPERRIFLE" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "230" - { - "name" "The Sydney Sleeper" - "item_name" "#TF_SydneySleeper" - "propername" "1" - "removed_hunters" "1" - } - - "402" - { - "name" "The Bazaar Bargain" - "item_name" "#TF_BazaarBargain" - "propername" "1" - "removed_hunters" "1" - } - - "526" - { - "name" "The Machina" - "item_name" "#TF_DEX_Rifle" - "propername" "1" - "removed_hunters" "1" - } - - "664" - { - "name" "Festive Sniper Rifle 2011" - "item_name" "#TF_Weapon_SniperRifle_Festive2011" - "removed_hunters" "1" - } - - "752" - { - "name" "The Hitman's Heatmaker" - "item_name" "#TF_Pro_SniperRifle" - "propername" "1" - "removed_hunters" "1" - } - - "792" - { - "name" "Silver Botkiller Sniper Rifle Mk.I" - "item_name" "#TF_Weapon_SniperRifle_MVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "801" - { - "name" "Gold Botkiller Sniper Rifle Mk.I" - "item_name" "#TF_Weapon_SniperRifle_MVMLootGold" - "propername" "1" - "removed_hunters" "1" - } - - "851" - { - "name" "The AWPer Hand" - "item_name" "#TF_CSGO_AWP" - "propername" "1" - "removed_hunters" "1" - } - - "881" - { - "name" "Rust Botkiller Sniper Rifle" - "item_name" "#TF_Weapon_SniperRifle_IntermediateMVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "890" - { - "name" "Blood Botkiller Sniper Rifle" - "item_name" "#TF_Weapon_SniperRifle_IntermediateMVMRareLoot" - "propername" "1" - "removed_hunters" "1" - } - - "899" - { - "name" "Carbonado Botkiller Sniper Rifle" - "item_name" "#TF_Weapon_SniperRifle_ExpertMVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "908" - { - "name" "Diamond Botkiller Sniper Rifle" - "item_name" "#TF_Weapon_SniperRifle_ExpertMVMRareLoot" - "propername" "1" - "removed_hunters" "1" - } - - "957" - { - "name" "Silver Botkiller Sniper Rifle Mk.II" - "item_name" "#TF_Weapon_SniperRifle_AdvancedMVMLoot_Engineer" - "propername" "1" - "removed_hunters" "1" - } - - "966" - { - "name" "Gold Botkiller Sniper Rifle Mk.II" - "item_name" "#TF_Weapon_SniperRifle_AdvancedMVMRareLoot_Engineer" - "propername" "1" - "removed_hunters" "1" - } - - "1005" - { - "name" "Festive Huntsman" - "item_name" "#TF_Weapon_Huntsman_Festive2012" - "propername" "1" - "damage_hunters" "0.85" - } - - "1092" - { - "name" "The Fortified Compound" - "item_name" "#TF_FortifiedCompound" - "propername" "1" - "damage_hunters" "0.85" - } - - "1098" - { - "name" "The Classic" - "item_name" "#TF_ClassicSniperRifle" - "removed_hunters" "1" - } - - "15000" - { - "name" "concealedkiller_sniperrifle_nightowl" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "15007" - { - "name" "concealedkiller_sniperrifle_purplerange" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "15019" - { - "name" "craftsmann_sniperrifle_lumberfromdownunder" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "15023" - { - "name" "craftsmann_sniperrifle_shotinthedark" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "15033" - { - "name" "teufort_sniperrifle_bogtrotter" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "15059" - { - "name" "powerhouse_sniperrifle_thunderbolt" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "16008" - { - "name" "template_civilian_sniperrifle" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "16020" - { - "name" "template_commando_sniperrifle" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "16031" - { - "name" "template_elite_sniperrifle" - "item_name" "#TF_Weapon_SniperRifle" - "removed_hunters" "1" - } - - "30665" - { - "name" "Shooting Star" - "item_name" "#TF_invasion_sniperrifle" - "removed_hunters" "1" - } - -/////////////Heavy Primary/////////////////////// - - "15" - { - "name" "TF_WEAPON_MINIGUN" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "41" - { - "name" "Natascha" - "item_name" "#TF_Unique_Achievement_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "202" - { - "name" "Upgradeable TF_WEAPON_MINIGUN" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "298" - { - "name" "Iron Curtain" - "item_name" "#TF_Iron_Curtain" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "312" - { - "name" "The Brass Beast" - "item_name" "#TF_GatlingGun" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "424" - { - "name" "Tomislav" - "item_name" "#TF_Tomislav" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "654" - { - "name" "Festive Minigun 2011" - "item_name" "#TF_Weapon_Minigun_Festive2011" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "793" - { - "name" "Silver Botkiller Minigun Mk.I" - "item_name" "#TF_Weapon_Minigun_MVMLoot" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "802" - { - "name" "Gold Botkiller Minigun Mk.I" - "item_name" "#TF_Weapon_Minigun_MVMLootGold" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "811" - { - "name" "The Huo Long Heatmaker" - "item_name" "#TF_SD_Minigun" - "propername" "1" - "removed_hunters" "1" - } - - "832" - { - "name" "Promo Huo Long Heatmaker" - "item_name" "#TF_SD_Minigun" - "removed_hunters" "1" - } - - "882" - { - "name" "Rust Botkiller Minigun" - "item_name" "#TF_Weapon_Minigun_IntermediateMVMLoot" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "891" - { - "name" "Blood Botkiller Minigun" - "item_name" "#TF_Weapon_Minigun_IntermediateMVMRareLoot" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "900" - { - "name" "Carbonado Botkiller Minigun" - "item_name" "#TF_Weapon_Minigun_ExpertMVMLoot" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "909" - { - "name" "Diamond Botkiller Minigun" - "item_name" "#TF_Weapon_Minigun_ExpertMVMRareLoot" - "propername" "1" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "958" - { - "name" "Silver Botkiller Minigun Mk.II" - "propername" "1" - "item_name" "#TF_Weapon_Minigun_AdvancedMVMLoot_Engineer" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "967" - { - "name" "Gold Botkiller Minigun Mk.II" - "propername" "1" - "item_name" "#TF_Weapon_Minigun_AdvancedMVMRareLoot_Engineer" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "15004" - { - "name" "concealedkiller_minigun_kingofthejungle" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "15020" - { - "name" "craftsmann_minigun_ironwood" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "15026" - { - "name" "craftsmann_minigun_antiqueannihilator" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "15031" - { - "name" "teufort_minigun_warroom" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "15040" - { - "name" "teufort_minigun_citizenpain" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "15055" - { - "name" "powerhouse_minigun_brickhouse" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "16002" - { - "name" "template_civilian_minigun" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "16014" - { - "name" "template_commando_minigun" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - - "16025" - { - "name" "template_elite_minigun" - "item_name" "#TF_Weapon_Minigun" - "damage_hunters" "0.75" - "self_damage_hunters" "2.0" - "removed_hunters" "1" - } - -////////////Soldier Primary////////////////////// - - "18" - { - "name" "TF_WEAPON_ROCKETLAUNCHER" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "127" - { - "name" "The Direct Hit" - "item_name" "#TF_Unique_Achievement_RocketLauncher" - "propername" "1" - "damage_hunters" "0.75" - } - - "205" - { - "name" "Upgradeable TF_WEAPON_ROCKETLAUNCHER" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "228" - { - "name" "The Black Box" - "item_name" "#TF_TheBlackBox" - "propername" "1" - "damage_hunters" "0.75" - } - - "237" - { - "name" "Rocket Jumper" - "item_name" "#TF_Weapon_RocketLauncher_Jump" - } - - "414" - { - "name" "The Liberty Launcher" - "item_name" "#TF_LibertyLauncher" - "propername" "1" - "damage_hunters" "0.75" - } - - "441" - { - "name" "The Cow Mangler 5000" - "item_name" "#TF_CowMangler" - "propername" "1" - "removed_hunters" "1" - } - - "513" - { - "name" "The Original" - "item_name" "#TF_TheOriginal" - "propername" "1" - "damage_hunters" "0.75" - } - - "658" - { - "name" "Festive Rocket Launcher 2011" - "item_name" "#TF_Weapon_RocketLauncher_Festive2011" - "damage_hunters" "0.75" - } - - "730" - { - "name" "The Beggar's Bazooka" - "item_name" "#TF_DS_DumpsterDevice" - "propername" "1" - "damage_hunters" "0.75" - } - - "800" - { - "name" "Silver Botkiller Rocket Launcher Mk.I" - "item_name" "#TF_Weapon_RocketLauncher_MVMLoot" - "propername" "1" - "damage_hunters" "0.75" - } - - "809" - { - "name" "Gold Botkiller Rocket Launcher Mk.I" - "item_name" "#TF_Weapon_RocketLauncher_MVMLootGold" - "propername" "1" - "damage_hunters" "0.75" - } - - "889" - { - "name" "Rust Botkiller Rocket Launcher" - "item_name" "#TF_Weapon_RocketLauncher_IntermediateMVMLoot" - "propername" "1" - "damage_hunters" "0.75" - } - - "898" - { - "name" "Blood Botkiller Rocket Launcher" - "item_name" "#TF_Weapon_RocketLauncher_IntermediateMVMRareLoot" - "propername" "1" - "damage_hunters" "0.75" - } - - "907" - { - "name" "Carbonado Botkiller Rocket Launcher" - "item_name" "#TF_Weapon_RocketLauncher_ExpertMVMLoot" - "propername" "1" - "damage_hunters" "0.75" - } - - "916" - { - "name" "Diamond Botkiller Rocket Launcher" - "item_name" "#TF_Weapon_RocketLauncher_ExpertMVMRareLoot" - "propername" "1" - "damage_hunters" "0.75" - } - - "965" - { - "name" "Silver Botkiller Rocket Launcher Mk.II" - "item_name" "#TF_Weapon_RocketLauncher_AdvancedMVMLoot_Engineer" - "propername" "1" - "damage_hunters" "0.75" - } - - "974" - { - "name" "Gold Botkiller Rocket Launcher Mk.II" - "item_name" "#TF_Weapon_RocketLauncher_AdvancedMVMRareLoot_Engineer" - "propername" "1" - "damage_hunters" "0.75" - } - - "1085" - { - "name" "Festive Black Box" - "item_name" "#TF_Weapon_Blackbox_Festive2013" - "propername" "1" - "damage_hunters" "0.75" - } - - "1104" - { - "name" "The Air Strike" - "item_name" "#TF_Weapon_AirStrike" - "propername" "1" - "damage_hunters" "0.75" - } - - "15006" - { - "name" "concealedkiller_rocketlauncher_woodlandwarrior" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "15014" - { - "name" "concealedkiller_rocketlauncher_sandcannon" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "15028" - { - "name" "craftsmann_rocketlauncher_americanpastoral" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "15043" - { - "name" "teufort_rocketlauncher_smalltownbringdown" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "15052" - { - "name" "powerhouse_rocketlauncher_shellshocker" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "15057" - { - "name" "powerhouse_rocketlauncher_aquamarine" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "16005" - { - "name" "template_civilian_rocketlauncher" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "16017" - { - "name" "template_commando_rocketlauncher" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - - "16028" - { - "name" "template_elite_rocketlauncher" - "item_name" "#TF_Weapon_RocketLauncher" - "damage_hunters" "0.75" - } - -////////////Engineer Primary///////////////////// - - "9" - { - "name" "TF_WEAPON_SHOTGUN_PRIMARY" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "141" - { - "name" "The Frontier Justice" - "item_name" "#TF_Unique_Sentry_Shotgun" - "propername" "1" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "199" - { - "name" "Upgradeable TF_WEAPON_SHOTGUN_PRIMARY" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "527" - { - "name" "The Widowmaker" - "item_name" "#TF_DEX_Shotgun" - "propername" "1" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "588" - { - "name" "The Pomson 6000" - "item_name" "#TF_Pomson" - "propername" "1" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "997" - { - "name" "Rescue Ranger" - "item_name" "#TF_Weapon_Shotgun_Building_Rescue" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "1004" - { - "name" "Festive Frontier Justice" - "item_name" "#TF_Weapon_FrontierJustice_Festive2012" - "propername" "1" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "1141" - { - "name" "Festive Shotgun" - "item_name" "#TF_Weapon_FestiveShotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "1153" - { - "name" "Panic Attack" - "item_name" "#TF_Weapon_PanicAttack" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - "addattribs" "424 ; 0.5" - } - - "15003" - { - "name" "concealedkiller_shotgun_backwoodsboomstick" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "15016" - { - "name" "craftsmann_shotgun_rusticruiner" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "15044" - { - "name" "teufort_shotgun_civicduty" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "15047" - { - "name" "powerhouse_shotgun_lightningrod" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "16007" - { - "name" "template_civilian_shotgun" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "16019" - { - "name" "template_commando_shotgun" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "16030" - { - "name" "template_elite_shotgun" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - -////////////Medic Primary//////////////////////// - - "17" - { - "name" "TF_WEAPON_SYRINGEGUN_MEDIC" - "item_name" "#TF_Weapon_SyringeGun" - "self_damage_hunters" "4.0" - } - - "36" - { - "name" "The Blutsauger" - "item_name" "#TF_Unique_Achievement_Syringegun1" - "propername" "1" - "removed_hunters" "1" - "self_damage_hunters" "4.0" - } - - "204" - { - "name" "Upgradeable TF_WEAPON_SYRINGEGUN_MEDIC" - "item_name" "#TF_Weapon_SyringeGun" - "self_damage_hunters" "4.0" - } - - "305" - { - "name" "The Crusader's Crossbow" - "item_name" "#TF_CrusadersCrossbow" - "propername" "1" - "replace" "tf_weapon_syringegun_medic:17:0:1" - } - - "412" - { - "name" "The Overdose" - "item_name" "#TF_Overdose" - "self_damage_hunters" "4.0" - "propername" "1" - } - - "1079" - { - "name" "Festive Crusader's Crossbow" - "item_name" "#TF_Weapon_CrusaderCrossbow_Festive2013" - "propername" "1" - "replace" "tf_weapon_syringegun_medic:17:0:1" - } - -///////////////////////////////////////////////// -////////////Secondary Wepons///////////////////// -///////////////////////////////////////////////// - -////////////Scout Secondary////////////////////// - - "23" - { - "name" "TF_WEAPON_PISTOL_SCOUT" - "item_name" "#TF_Weapon_Pistol" - } - - "46" - { - "name" "Bonk! Atomic Punch" - "item_name" "#TF_Unique_Achievement_EnergyDrink" - "removed_hunters" "1" - } - - "160" - { - "name" "Vintage Lugermorph" - "item_name" "#TF_TTG_MaxGun" - "propername" "1" - "removed_hunters" "1" - } - - "163" - { - "name" "Crit-a-Cola" - "item_name" "#TF_Unique_EnergyDrink_CritCola" - "removed_hunters" "1" - } - - "222" - { - "name" "Mad Milk" - "item_name" "#TF_MadMilk" - "propername" "1" - } - - "294" - { - "name" "Lugermorph" - "item_name" "#TF_TTG_MaxGun" - "propername" "1" - "removed_hunters" "1" - } - - "449" - { - "name" "The Winger" - "item_name" "#TF_Winger" - "propername" "1" - } - - "773" - { - "name" "Pretty Boy's Pocket Pistol" - "item_name" "#TF_Weapon_PEP_Pistol" - "removed_hunters" "1" - } - - "812" - { - "name" "The Flying Guillotine" - "item_name" "#TF_SD_Cleaver" - "propername" "1" - } - - "833" - { - "name" "Promo Flying Guillotine" - "item_name" "#TF_SD_Cleaver" - "propername" "1" - } - - "1121" - { - "name" "Mutated Milk" - "item_name" "#TF_MadMilk" - "propername" "1" - } - - "1145" - { - "name" "Festive Bonk" - "item_name" "#TF_Weapon_FestiveBonk" - "removed_hunters" "1" - } - -////////////Demo Secondary/////////////////////// - - "20" - { - "name" "TF_WEAPON_PIPEBOMBLAUNCHER" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "207" - { - "name" "Upgradeable TF_WEAPON_PIPEBOMBLAUNCHER" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "130" - { - "name" "The Scottish Resistance" - "item_name" "#TF_Unique_Achievement_StickyLauncher" - "propername" "1" - "removed_hunters" "1" - } - - "131" - { - "name" "The Chargin' Targe" - "item_name" "#TF_Unique_Achievement_Shield" - "propername" "1" - } - - "265" - { - "name" "Stickybomb Jumper" - "item_name" "#TF_Weapon_StickyBomb_Jump" - } - - "406" - { - "name" "The Splendid Screen" - "item_name" "#TF_SplendidScreen" - "propername" "1" - "damage_hunters" "0.75" - } - - "661" - { - "name" "Festive Stickybomb Launcher 2011" - "item_name" "#TF_Weapon_StickybombLauncher_Festive2011" - "removed_hunters" "1" - } - - "797" - { - "name" "Silver Botkiller Stickybomb Launcher Mk.I" - "item_name" "#TF_Weapon_StickybombLauncher_MVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "806" - { - "name" "Gold Botkiller Stickybomb Launcher Mk.I" - "item_name" "#TF_Weapon_StickybombLauncher_MVMLootGold" - "propername" "1" - "removed_hunters" "1" - } - - "886" - { - "name" "Rust Botkiller Stickybomb Launcher" - "item_name" "#TF_Weapon_StickybombLauncher_IntermediateMVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "895" - { - "name" "Blood Botkiller Stickybomb Launcher" - "item_name" "#TF_Weapon_StickybombLauncher_IntermediateMVMRareLoot" - "propername" "1" - "removed_hunters" "1" - } - - "904" - { - "name" "Carbonado Botkiller Stickybomb Launcher" - "item_name" "#TF_Weapon_StickybombLauncher_ExpertMVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "913" - { - "name" "Diamond Botkiller Stickybomb Launcher" - "item_name" "#TF_Weapon_StickybombLauncher_ExpertMVMRareLoot" - "propername" "1" - "removed_hunters" "1" - } - - "962" - { - "name" "Silver Botkiller Stickybomb Launcher Mk.II" - "item_name" "#TF_Weapon_Stickybomblauncher_AdvancedMVMLoot_Engineer" - "propername" "1" - "removed_hunters" "1" - } - - "971" - { - "name" "Gold Botkiller Stickybomb Launcher Mk.II" - "item_name" "#TF_Weapon_Stickybomblauncher_AdvancedMVMRareLoot_Engineer" - "propername" "1" - "removed_hunters" "1" - } - - "1099" - { - "name" "The Tide Turner" - "item_name" "#TF_Wearable_Shield" - "propername" "1" - } - - "1144" - { - "name" "Festive Targe" - "item_name" "#TF_Weapon_FestiveTarge" - } - - "1150" - { - "name" "The Quickiebomb Launcher" - "item_name" "#TF_Weapon_Sticky_Quickie" - "removed_hunters" "1" - } - - "15009" - { - "name" "concealedkiller_stickybomblauncher_suddenflurry" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "15012" - { - "name" "concealedkiller_stickybomblauncher_carpetbomber" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "15024" - { - "name" "craftsmann_stickybomblauncher_blastedbombardier" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "15038" - { - "name" "teufort_stickybomblauncher_rooftopwrangler" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "15045" - { - "name" "powerhouse_stickybomblauncher_liquidasset" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "15048" - { - "name" "powerhouse_stickybomblauncher_pinkelephant" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "16010" - { - "name" "template_civilian_stickybomb_launcher" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "16022" - { - "name" "template_commando_stickybomb_launcher" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - - "16033" - { - "name" "template_elite_stickybomb_launcher" - "item_name" "#TF_Weapon_PipebombLauncher" - "removed_hunters" "1" - } - -////////////Pyro Secondary/////////////////////// - - "12" - { - "name" "TF_WEAPON_SHOTGUN_PYRO" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "39" - { - "name" "The Flare Gun" - "item_name" "#TF_Unique_Achievement_Flaregun" - "propername" "1" - "self_damage_hunters" "5.0" - } - - "351" - { - "name" "The Detonator" - "item_name" "#TF_Weapon_Flaregun_Detonator" - "propername" "1" - "damage_hunters" "0.95" - "self_damage_hunters" "5.0" - } - - "595" - { - "name" "The Manmelter" - "item_name" "#TF_ManMelter" - "propername" "1" - "self_damage_hunters" "5.0" - } - - "740" - { - "name" "The Scorch Shot" - "item_name" "#TF_ScorchShot" - "propername" "1" - "self_damage_hunters" "5.0" - } - - "1081" - { - "name" "Festive Flare Gun" - "item_name" "#TF_Weapon_Flaregun_Festive2013" - "propername" "1" - "self_damage_hunters" "5.0" - } - -////////////Sniper Secondary///////////////////// - - "16" - { - "name" "TF_WEAPON_SMG" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "57" - { - "name" "The Razorback" - "item_name" "#TF_Unique_Backstab_Shield" - "propername" "1" - } - - "58" - { - "name" "Jarate" - "item_name" "#TF_Unique_Achievement_Jar" - } - - "203" - { - "name" "Upgradeable TF_WEAPON_SMG" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "231" - { - "name" "Darwin's Danger Shield" - "item_name" "#TF_DarwinsDangerShield" - } - - "751" - { - "name" "The Cleaner's Carbine" - "item_name" "#TF_Pro_SMG" - "propername" "1" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "1083" - { - "name" "Festive Jarate" - "item_name" "#TF_Weapon_Jarate_Festive2013" - "propername" "1" - } - - "1105" - { - "name" "The Self-Aware Beauty Mark" - "item_name" "#TF_Weapon_SelfAwareBeautyMark" - "propername" "1" - } - - "1149" - { - "name" "Festive SMG" - "item_name" "#TF_Weapon_FestiveSMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "15001" - { - "name" "concealedkiller_smg_woodsywidowmaker" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "15022" - { - "name" "craftsmann_smg_plaidpotshotter" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "15032" - { - "name" "teufort_smg_treadplatetormenter" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "15037" - { - "name" "teufort_smg_teamsprayer" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "15058" - { - "name" "powerhouse_smg_lowprofile" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "16009" - { - "name" "template_civilian_smg" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "16021" - { - "name" "template_commando_smg" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - - "16032" - { - "name" "template_elite_smg" - "item_name" "#TF_Weapon_SMG" - "removed_hunters" "1" - "self_damage_hunters" "1.0" - } - -/////////////Spy Secondary/////////////////////// - - "24" - { - "name" "TF_WEAPON_REVOLVER" - "item_name" "#TF_Weapon_Revolver" - } - - "61" - { - "name" "The Ambassador" - "item_name" "#TF_Unique_Achievement_Revolver" - "propername" "1" - } - - "161" - { - "name" "TTG Sam Revolver" - "item_name" "#TF_TTG_SamRevolver" - "propername" "1" - } - - "210" - { - "name" "Upgradeable TF_WEAPON_REVOLVER" - "item_name" "#TF_Weapon_Revolver" - } - - "224" - { - "name" "L'Etranger" - "item_name" "#TF_LEtranger" - } - - "460" - { - "name" "The Enforcer" - "item_name" "#TF_Enforcer" - "propername" "1" - } - - "525" - { - "name" "The Diamondback" - "item_name" "#TF_DEX_Revolver" - "propername" "1" - } - - "1006" - { - "name" "Festive Ambassador" - "item_name" "#TF_Weapon_Ambassador_Festive2012" - } - - "1142" - { - "name" "Festive Revolver" - "item_name" "#TF_Weapon_FestiveRevolver" - } - - "15011" - { - "name" "concealedkiller_revolver_psychedelicslugger" - "item_name" "#TF_Weapon_Revolver" - } - - "15027" - { - "name" "craftsmann_revolver_oldcountry" - "item_name" "#TF_Weapon_Revolver" - } - - "15042" - { - "name" "teufort_revolver_mayor" - "item_name" "#TF_Weapon_Revolver" - } - - "15051" - { - "name" "powerhouse_revolver_deadreckoner" - "item_name" "#TF_Weapon_Revolver" - } - - "16004" - { - "name" "template_civilian_revolver" - "item_name" "#TF_Weapon_Revolver" - } - - "16016" - { - "name" "template_commando_revolver" - "item_name" "#TF_Weapon_Revolver" - } - - "16027" - { - "name" "template_elite_revolver" - "item_name" "#TF_Weapon_Revolver" - } - -////////////Heavy Secondary////////////////////// - - "11" - { - "name" "TF_WEAPON_SHOTGUN_HWG" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "42" - { - "name" "The Sandvich" - "item_name" "#TF_Unique_Achievement_LunchBox" - "propername" "1" - } - - "159" - { - "name" "The Dalokohs Bar" - "item_name" "#TF_Unique_Lunchbox_Chocolate" - "propername" "1" - } - - "311" - { - "name" "The Buffalo Steak Sandvich" - "item_name" "#TF_BuffaloSteak" - "propername" "1" - } - - "425" - { - "name" "The Family Business" - "item_name" "#TF_RussianRiot" - "propername" "1" - "self_damage_hunters" "8.0" - "removed_hunters" "1" - } - - "433" - { - "name" "Fishcake" - "item_name" "#TF_SpaceChem_Fishcake" - } - - "863" - { - "name" "The Robo-Sandvich" - "item_name" "#TF_Robot_Sandvich" - "propername" "1" - } - - "1002" - { - "name" "Festive Sandvich" - "item_name" "#TF_Weapon_Sandvich_Festive2012" - } - -////////////Soldier Secondary//////////////////// - - "10" - { - "name" "TF_WEAPON_SHOTGUN_SOLDIER" - "item_name" "#TF_Weapon_Shotgun" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - } - - "129" - { - "name" "The Buff Banner" - "item_name" "#TF_Unique_Achievement_SoldierBuff" - "propername" "1" - } - - "133" - { - "name" "The Gunboats" - "item_name" "#TF_Unique_Blast_Boots" - "propername" "1" - } - - "226" - { - "name" "The Battalion's Backup" - "item_name" "#TF_TheBattalionsBackup" - "propername" "1" - } - - "354" - { - "name" "The Concheror" - "item_name" "#TF_SoldierSashimono" - "propername" "1" - } - - "415" - { - "name" "The Reserve Shooter" - "item_name" "#TF_ReserveShooter" - "propername" "1" - "damage_hunters" "0.8" - "self_damage_hunters" "8.0" - "replace" "tf_weapon_shotgun:12:0:1" - "replace_onlyclasses" "64" - } - - "442" - { - "name" "The Righteous Bison" - "item_name" "#TF_RighteousBison" - "propername" "1" - } - - "444" - { - "name" "The Mantreads" - "item_name" "#TF_Mantreads" - "propername" "1" - } - - "1001" - { - "name" "Festive Buff Banner" - "item_name" "#TF_Weapon_BuffBanner_Festive2012" - "propername" "1" - } - - "1101" - { - "name" "The B.A.S.E. Jumper" - "item_name" "#TF_Weapon_Parachute" - "propername" "1" - } - -////////////Engineer Secondary//////////////////// - - "22" - { - "name" "TF_WEAPON_PISTOL" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "140" - { - "name" "The Wrangler" - "item_name" "#TF_Unique_Achievement_Laser_Pointer" - "propername" "1" - "removed_hunters" "1" - } - - "209" - { - "name" "Upgradeable TF_WEAPON_PISTOL" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "528" - { - "name" "The Short Circuit" - "item_name" "#TF_DEX_Pistol" - "propername" "1" - "removed_hunters" "1" - } - - "1086" - { - "name" "Festive Wrangler" - "item_name" "#TF_Weapon_Wrangler_Festive2013" - "propername" "1" - "removed_hunters" "1" - } - - "15013" - { - "name" "concealedkiller_pistol_redrockroscoe" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "15018" - { - "name" "craftsmann_pistol_homemadeheater" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "15035" - { - "name" "teufort_pistol_hickoryholepuncher" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "15041" - { - "name" "teufort_pistol_localhero" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "15046" - { - "name" "powerhouse_pistol_blackdahlia" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "15056" - { - "name" "powerhouse_pistol_sandstonespecial" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "16003" - { - "name" "template_civilian_pistol" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "16015" - { - "name" "template_commando_pistol" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "16026" - { - "name" "template_elite_pistol" - "item_name" "#TF_Weapon_Pistol" - "removed_hunters" "1" - } - - "30666" - { - "name" "The C.A.P.P.E.R." - "item_name" "#TF_invasion_pistol" - "removed_hunters" "1" - } - - "30668" - { - "name" "The Gigar Counter" - "item_name" "#TF_invasion_wrangler" - "propername" "1" - "removed_hunters" "1" - } - -///////////Medic Secondary//////////////////////// - - "29" - { - "name" "TF_WEAPON_MEDIGUN" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "35" - { - "name" "The Kritzkrieg" - "item_name" "#TF_Unique_Achievement_Medigun1" - "propername" "1" - "removed_hunters" "1" - } - - "211" - { - "name" "Upgradeable TF_WEAPON_MEDIGUN" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "411" - { - "name" "The Quick-Fix" - "item_name" "#TF_Unique_MediGun_QuickFix" - "propername" "1" - "removed_hunters" "1" - } - - "663" - { - "name" "Festive Medigun 2011" - "item_name" "#TF_Weapon_Medigun_Festive2011" - "removed_hunters" "1" - } - - "796" - { - "name" "Silver Botkiller Medi Gun Mk.I" - "item_name" "#TF_Weapon_Medigun_MVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "805" - { - "name" "Gold Botkiller Medi Gun Mk.I" - "item_name" "#TF_Weapon_Medigun_MVMLootGold" - "propername" "1" - "removed_hunters" "1" - } - - "885" - { - "name" "Rust Botkiller Medi Gun" - "item_name" "#TF_Weapon_Medigun_IntermediateMVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "894" - { - "name" "Blood Botkiller Medi Gun" - "item_name" "#TF_Weapon_Medigun_IntermediateMVMRareLoot" - "propername" "1" - "removed_hunters" "1" - } - - "903" - { - "name" "Carbonado Botkiller Medi Gun" - "item_name" "#TF_Weapon_Medigun_ExpertMVMLoot" - "propername" "1" - "removed_hunters" "1" - } - - "912" - { - "name" "Diamond Botkiller Medi Gun" - "item_name" "#TF_Weapon_Medigun_ExpertMVMRareLoot" - "propername" "1" - "removed_hunters" "1" - } - - "961" - { - "name" "Silver Botkiller Medi gun Mk.II" - "item_name" "#TF_Weapon_Medigun_AdvancedMVMLoot_Engineer" - "propername" "1" - "removed_hunters" "1" - } - - "970" - { - "name" "Gold Botkiller Medi Gun Mk.II" - "item_name" "#TF_Weapon_Medigun_AdvancedMVMRareLoot_Engineer" - "propername" "1" - "removed_hunters" "1" - } - - "998" - { - "name" "Vaccinator" - "item_name" "#TF_Unique_Medigun_Resist" - "propername" "1" - "removed_hunters" "1" - } - - "15008" - { - "name" "concealedkiller_medigun_maskedmender" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "15010" - { - "name" "concealedkiller_medigun_wrappedreviver" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "15025" - { - "name" "craftsmann_medigun_reclaimedreanimator" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "15039" - { - "name" "teufort_medigun_civilservant" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "15050" - { - "name" "powerhouse_medigun_sparkoflife" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "16001" - { - "name" "template_civilian_medigun" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "16013" - { - "name" "template_commando_medigun" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - - "16024" - { - "name" "template_elite_medigun" - "item_name" "#TF_Weapon_Medigun" - "removed_hunters" "1" - } - -////////////////////////////////////////////////// -//////////////////PDAs//////////////////////////// -////////////////////////////////////////////////// - -///////////////Engineer PDAs////////////////////// - - "25" - { - "name" "TF_WEAPON_PDA_ENGINEER_BUILD" - "item_name" "#TF_Weapon_PDA_Engineer" - } - - "26" - { - "name" "TF_WEAPON_PDA_ENGINEER_DESTROY" - "item_name" "#TF_Weapon_PDA_Engineer" - } - - "28" - { - "name" "TF_WEAPON_BUILDER" - "item_name" "#TF_Weapon_PDA_Engineer" - } - -//////////////Spy's PDAs////////////////////////// - - "27" - { - "name" "TF_WEAPON_PDA_SPY" - "item_name" "#TF_Weapon_PDA_Engineer" - } - - "30" - { - "name" "TF_WEAPON_INVIS" - "item_name" "#TF_Weapon_Watch" - } - - "59" - { - "name" "The Dead Ringer" - "item_name" "#TF_Unique_Achievement_FeignWatch" - "propername" "1" - } - - "60" - { - "name" "The Cloak and Dagger" - "item_name" "#TF_Unique_Achievement_CloakWatch" - "propername" "1" - } - - "212" - { - "name" "Upgradeable TF_WEAPON_INVIS" - "item_name" "#TF_Weapon_Watch" - } - - "735" - { - "name" "TF_WEAPON_BUILDER_SPY" - "item_name" "#TF_Weapon_Spy_Sapper" - } - - "736" - { - "name" "Upgradeable TF_WEAPON_BUILDER_SPY" - "item_name" "#TF_Weapon_Spy_Sapper" - } - - "810" - { - "name" "The Red-Tape Recorder" - "item_name" "#TF_SD_Sapper" - "propername" "1" - } - - "831" - { - "name" "Promo Red-Tape Recorder" - "item_name" "#TF_SD_Sapper" - } - - "1080" - { - "name" "Festive Sapper" - "item_name" "#TF_Weapon_Sapper_Festive2013" - "propername" "1" - } - - "1102" - { - "name" "The Snack Attack" - "item_name" "#TF_Weapon_SnackAttack" - "propername" "1" - } - -//////////Misc???/////////////////////////////// - } } \ No newline at end of file diff --git a/addons/sourcemod/gamedata/tf2-roundend.games.txt b/addons/sourcemod/gamedata/tf2-roundend.games.txt deleted file mode 100644 index 7fb0007..0000000 --- a/addons/sourcemod/gamedata/tf2-roundend.games.txt +++ /dev/null @@ -1,16 +0,0 @@ -"Games" -{ - - "tf" - { - "Offsets" - - { - "SetWinningTeam" - { - "windows" "160" - "linux" "161" - } - } - } -} diff --git a/addons/sourcemod/gamedata/tf2-switch-teams.txt b/addons/sourcemod/gamedata/tf2-switch-teams.txt new file mode 100644 index 0000000..a00c31d --- /dev/null +++ b/addons/sourcemod/gamedata/tf2-switch-teams.txt @@ -0,0 +1,28 @@ +"Games" +{ + + "tf" + { + "Offsets" + + { + "CTFGameRules::SetSwitchTeams" + { + "windows" "162" + "linux" "163" + } + + "CTFGameRules::ShouldSwitchTeams" + { + "windows" "163" + "linux" "164" + } + + "CTFGameRules::HandleSwitchTeams" + { + "windows" "164" + "linux" "165" + } + } + } +} diff --git a/addons/sourcemod/scripting/include/SteamWorks.inc b/addons/sourcemod/scripting/include/SteamWorks.inc new file mode 100644 index 0000000..02e69af --- /dev/null +++ b/addons/sourcemod/scripting/include/SteamWorks.inc @@ -0,0 +1,311 @@ +#if defined _SteamWorks_Included + #endinput +#endif +#define _SteamWorks_Included + +/* results from UserHasLicenseForApp */ +enum EUserHasLicenseForAppResult +{ + k_EUserHasLicenseResultHasLicense = 0, // User has a license for specified app + k_EUserHasLicenseResultDoesNotHaveLicense = 1, // User does not have a license for the specified app + k_EUserHasLicenseResultNoAuth = 2, // User has not been authenticated +}; + +/* General result codes */ +enum EResult +{ + k_EResultOK = 1, // success + k_EResultFail = 2, // generic failure + k_EResultNoConnection = 3, // no/failed network connection +// k_EResultNoConnectionRetry = 4, // OBSOLETE - removed + k_EResultInvalidPassword = 5, // password/ticket is invalid + k_EResultLoggedInElsewhere = 6, // same user logged in elsewhere + k_EResultInvalidProtocolVer = 7, // protocol version is incorrect + k_EResultInvalidParam = 8, // a parameter is incorrect + k_EResultFileNotFound = 9, // file was not found + k_EResultBusy = 10, // called method busy - action not taken + k_EResultInvalidState = 11, // called object was in an invalid state + k_EResultInvalidName = 12, // name is invalid + k_EResultInvalidEmail = 13, // email is invalid + k_EResultDuplicateName = 14, // name is not unique + k_EResultAccessDenied = 15, // access is denied + k_EResultTimeout = 16, // operation timed out + k_EResultBanned = 17, // VAC2 banned + k_EResultAccountNotFound = 18, // account not found + k_EResultInvalidSteamID = 19, // steamID is invalid + k_EResultServiceUnavailable = 20, // The requested service is currently unavailable + k_EResultNotLoggedOn = 21, // The user is not logged on + k_EResultPending = 22, // Request is pending (may be in process, or waiting on third party) + k_EResultEncryptionFailure = 23, // Encryption or Decryption failed + k_EResultInsufficientPrivilege = 24, // Insufficient privilege + k_EResultLimitExceeded = 25, // Too much of a good thing + k_EResultRevoked = 26, // Access has been revoked (used for revoked guest passes) + k_EResultExpired = 27, // License/Guest pass the user is trying to access is expired + k_EResultAlreadyRedeemed = 28, // Guest pass has already been redeemed by account, cannot be acked again + k_EResultDuplicateRequest = 29, // The request is a duplicate and the action has already occurred in the past, ignored this time + k_EResultAlreadyOwned = 30, // All the games in this guest pass redemption request are already owned by the user + k_EResultIPNotFound = 31, // IP address not found + k_EResultPersistFailed = 32, // failed to write change to the data store + k_EResultLockingFailed = 33, // failed to acquire access lock for this operation + k_EResultLogonSessionReplaced = 34, + k_EResultConnectFailed = 35, + k_EResultHandshakeFailed = 36, + k_EResultIOFailure = 37, + k_EResultRemoteDisconnect = 38, + k_EResultShoppingCartNotFound = 39, // failed to find the shopping cart requested + k_EResultBlocked = 40, // a user didn't allow it + k_EResultIgnored = 41, // target is ignoring sender + k_EResultNoMatch = 42, // nothing matching the request found + k_EResultAccountDisabled = 43, + k_EResultServiceReadOnly = 44, // this service is not accepting content changes right now + k_EResultAccountNotFeatured = 45, // account doesn't have value, so this feature isn't available + k_EResultAdministratorOK = 46, // allowed to take this action, but only because requester is admin + k_EResultContentVersion = 47, // A Version mismatch in content transmitted within the Steam protocol. + k_EResultTryAnotherCM = 48, // The current CM can't service the user making a request, user should try another. + k_EResultPasswordRequiredToKickSession = 49,// You are already logged in elsewhere, this cached credential login has failed. + k_EResultAlreadyLoggedInElsewhere = 50, // You are already logged in elsewhere, you must wait + k_EResultSuspended = 51, // Long running operation (content download) suspended/paused + k_EResultCancelled = 52, // Operation canceled (typically by user: content download) + k_EResultDataCorruption = 53, // Operation canceled because data is ill formed or unrecoverable + k_EResultDiskFull = 54, // Operation canceled - not enough disk space. + k_EResultRemoteCallFailed = 55, // an remote call or IPC call failed + k_EResultPasswordUnset = 56, // Password could not be verified as it's unset server side + k_EResultExternalAccountUnlinked = 57, // External account (PSN, Facebook...) is not linked to a Steam account + k_EResultPSNTicketInvalid = 58, // PSN ticket was invalid + k_EResultExternalAccountAlreadyLinked = 59, // External account (PSN, Facebook...) is already linked to some other account, must explicitly request to replace/delete the link first + k_EResultRemoteFileConflict = 60, // The sync cannot resume due to a conflict between the local and remote files + k_EResultIllegalPassword = 61, // The requested new password is not legal + k_EResultSameAsPreviousValue = 62, // new value is the same as the old one ( secret question and answer ) + k_EResultAccountLogonDenied = 63, // account login denied due to 2nd factor authentication failure + k_EResultCannotUseOldPassword = 64, // The requested new password is not legal + k_EResultInvalidLoginAuthCode = 65, // account login denied due to auth code invalid + k_EResultAccountLogonDeniedNoMail = 66, // account login denied due to 2nd factor auth failure - and no mail has been sent + k_EResultHardwareNotCapableOfIPT = 67, // + k_EResultIPTInitError = 68, // + k_EResultParentalControlRestricted = 69, // operation failed due to parental control restrictions for current user + k_EResultFacebookQueryError = 70, // Facebook query returned an error + k_EResultExpiredLoginAuthCode = 71, // account login denied due to auth code expired + k_EResultIPLoginRestrictionFailed = 72, + k_EResultAccountLockedDown = 73, + k_EResultAccountLogonDeniedVerifiedEmailRequired = 74, + k_EResultNoMatchingURL = 75, + k_EResultBadResponse = 76, // parse failure, missing field, etc. + k_EResultRequirePasswordReEntry = 77, // The user cannot complete the action until they re-enter their password + k_EResultValueOutOfRange = 78 // the value entered is outside the acceptable range +}; + +/* This enum is used in client API methods, do not re-number existing values. */ +enum EHTTPMethod +{ + k_EHTTPMethodInvalid = 0, + k_EHTTPMethodGET, + k_EHTTPMethodHEAD, + k_EHTTPMethodPOST, + + // The remaining HTTP methods are not yet supported, per rfc2616 section 5.1.1 only GET and HEAD are required for + // a compliant general purpose server. We'll likely add more as we find uses for them. + + // k_EHTTPMethodOPTIONS, + k_EHTTPMethodPUT, + k_EHTTPMethodDELETE, + // k_EHTTPMethodTRACE, + // k_EHTTPMethodCONNECT +}; + + +/* HTTP Status codes that the server can send in response to a request, see rfc2616 section 10.3 for descriptions + of each of these. */ +enum EHTTPStatusCode +{ + // Invalid status code (this isn't defined in HTTP, used to indicate unset in our code) + k_EHTTPStatusCodeInvalid = 0, + + // Informational codes + k_EHTTPStatusCode100Continue = 100, + k_EHTTPStatusCode101SwitchingProtocols = 101, + + // Success codes + k_EHTTPStatusCode200OK = 200, + k_EHTTPStatusCode201Created = 201, + k_EHTTPStatusCode202Accepted = 202, + k_EHTTPStatusCode203NonAuthoritative = 203, + k_EHTTPStatusCode204NoContent = 204, + k_EHTTPStatusCode205ResetContent = 205, + k_EHTTPStatusCode206PartialContent = 206, + + // Redirection codes + k_EHTTPStatusCode300MultipleChoices = 300, + k_EHTTPStatusCode301MovedPermanently = 301, + k_EHTTPStatusCode302Found = 302, + k_EHTTPStatusCode303SeeOther = 303, + k_EHTTPStatusCode304NotModified = 304, + k_EHTTPStatusCode305UseProxy = 305, + //k_EHTTPStatusCode306Unused = 306, (used in old HTTP spec, now unused in 1.1) + k_EHTTPStatusCode307TemporaryRedirect = 307, + + // Error codes + k_EHTTPStatusCode400BadRequest = 400, + k_EHTTPStatusCode401Unauthorized = 401, + k_EHTTPStatusCode402PaymentRequired = 402, // This is reserved for future HTTP specs, not really supported by clients + k_EHTTPStatusCode403Forbidden = 403, + k_EHTTPStatusCode404NotFound = 404, + k_EHTTPStatusCode405MethodNotAllowed = 405, + k_EHTTPStatusCode406NotAcceptable = 406, + k_EHTTPStatusCode407ProxyAuthRequired = 407, + k_EHTTPStatusCode408RequestTimeout = 408, + k_EHTTPStatusCode409Conflict = 409, + k_EHTTPStatusCode410Gone = 410, + k_EHTTPStatusCode411LengthRequired = 411, + k_EHTTPStatusCode412PreconditionFailed = 412, + k_EHTTPStatusCode413RequestEntityTooLarge = 413, + k_EHTTPStatusCode414RequestURITooLong = 414, + k_EHTTPStatusCode415UnsupportedMediaType = 415, + k_EHTTPStatusCode416RequestedRangeNotSatisfiable = 416, + k_EHTTPStatusCode417ExpectationFailed = 417, + + // Server error codes + k_EHTTPStatusCode500InternalServerError = 500, + k_EHTTPStatusCode501NotImplemented = 501, + k_EHTTPStatusCode502BadGateway = 502, + k_EHTTPStatusCode503ServiceUnavailable = 503, + k_EHTTPStatusCode504GatewayTimeout = 504, + k_EHTTPStatusCode505HTTPVersionNotSupported = 505, +}; + +native bool:SteamWorks_IsVACEnabled(); +native bool:SteamWorks_GetPublicIP(ipaddr[4]); +native SteamWorks_GetPublicIPCell(); +native bool:SteamWorks_IsLoaded(); +native bool:SteamWorks_SetGameDescription(String:sDesc[]); +native bool:SteamWorks_IsConnected(); +native bool:SteamWorks_SetRule(const String:sKey[], const String:sValue[]); +native bool:SteamWorks_ClearRules(); +native bool:SteamWorks_ForceHeartbeat(); + +native EUserHasLicenseForAppResult:SteamWorks_HasLicenseForApp(client, app); +native SteamWorks_GetClientSteamID(client, String:sSteamID[], length); + +native bool:SteamWorks_RequestStatsAuthID(authid, appid); +native bool:SteamWorks_RequestStats(client, appid); +native bool:SteamWorks_GetStatCell(client, const String:sKey[], &value); +native bool:SteamWorks_GetStatAuthIDCell(authid, const String:sKey[], &value); +native bool:SteamWorks_GetStatFloat(client, const String:sKey[], &Float:value); +native bool:SteamWorks_GetStatAuthIDFloat(authid, const String:sKey[], &Float:value); + +native Handle:SteamWorks_CreateHTTPRequest(EHTTPMethod:method, const String:sURL[]); +native bool:SteamWorks_SetHTTPRequestContextValue(Handle:hHandle, any:data1, any:data2=0); +native bool:SteamWorks_SetHTTPRequestNetworkActivityTimeout(Handle:hHandle, timeout); +native bool:SteamWorks_SetHTTPRequestHeaderValue(Handle:hHandle, const String:sName[], const String:sValue[]); +native bool:SteamWorks_SetHTTPRequestGetOrPostParameter(Handle:hHandle, const String:sName[], const String:sValue[]); + +funcenum SteamWorksHTTPRequestCompleted +{ + public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode), + public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode, any:data1), + public(Handle:hRequest, bool:bFailure, bool:bRequestSuccessful, EHTTPStatusCode:eStatusCode, any:data1, any:data2) +}; + +funcenum SteamWorksHTTPHeadersReceived +{ + public(Handle:hRequest, bool:bFailure), + public(Handle:hRequest, bool:bFailure, any:data1), + public(Handle:hRequest, bool:bFailure, any:data1, any:data2) +}; + +funcenum SteamWorksHTTPDataReceived +{ + public(Handle:hRequest, bool:bFailure, offset, bytesreceived), + public(Handle:hRequest, bool:bFailure, offset, bytesreceived, any:data1), + public(Handle:hRequest, bool:bFailure, offset, bytesreceived, any:data1, any:data2) +}; + +native bool:SteamWorks_SetHTTPCallbacks(Handle:hHandle, SteamWorksHTTPRequestCompleted:fCompleted = INVALID_FUNCTION, SteamWorksHTTPHeadersReceived:fHeaders = INVALID_FUNCTION, SteamWorksHTTPDataReceived:fData = INVALID_FUNCTION, Handle:hCalling = INVALID_HANDLE); +native bool:SteamWorks_SendHTTPRequest(Handle:hRequest); +native bool:SteamWorks_DeferHTTPRequest(Handle:hRequest); +native bool:SteamWorks_PrioritizeHTTPRequest(Handle:hRequest); +native bool:SteamWorks_GetHTTPResponseHeaderSize(Handle:hRequest, const String:sHeader[], &size); +native bool:SteamWorks_GetHTTPResponseHeaderValue(Handle:hRequest, const String:sHeader[], String:sValue[], size); +native bool:SteamWorks_GetHTTPResponseBodySize(Handle:hRequest, &size); +native bool:SteamWorks_GetHTTPResponseBodyData(Handle:hRequest, String:sBody[], length); +native bool:SteamWorks_GetHTTPDownloadProgressPct(Handle:hRequest, &Float:percent); +native bool:SteamWorks_SetHTTPRequestRawPostBody(Handle:hRequest, const String:sContentType[], const String:sBody[], bodylen); + +funcenum SteamWorksHTTPBodyCallback +{ + public(const String:sData[]), + public(const String:sData[], any:value), + public(const data[], any:value, datalen) +}; + +native bool:SteamWorks_GetHTTPResponseBodyCallback(Handle:hRequest, SteamWorksHTTPBodyCallback:fCallback, any:data = 0, Handle:hPlugin = INVALID_HANDLE); +native bool:SteamWorks_WriteHTTPResponseBodyToFile(Handle:hRequest, const String:sFileName[]); + +forward SW_OnValidateClient(ownerauthid, authid); +forward SteamWorks_OnValidateClient(ownerauthid, authid); +forward SteamWorks_SteamServersConnected(); +forward SteamWorks_SteamServersConnectFailure(EResult:result); +forward SteamWorks_SteamServersDisconnected(EResult:result); + +forward Action:SteamWorks_RestartRequested(); +forward SteamWorks_TokenRequested(String:sToken[], maxlen); + +public Extension:__ext_SteamWorks = +{ + name = "SteamWorks", + file = "SteamWorks.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_EXTENSIONS +public __ext_SteamWorks_SetNTVOptional() +{ + MarkNativeAsOptional("SteamWorks_IsVACEnabled"); + MarkNativeAsOptional("SteamWorks_GetPublicIP"); + MarkNativeAsOptional("SteamWorks_GetPublicIPCell"); + MarkNativeAsOptional("SteamWorks_IsLoaded"); + MarkNativeAsOptional("SteamWorks_SetGameDescription"); + MarkNativeAsOptional("SteamWorks_IsConnected"); + MarkNativeAsOptional("SteamWorks_SetRule"); + MarkNativeAsOptional("SteamWorks_ClearRules"); + MarkNativeAsOptional("SteamWorks_ForceHeartbeat"); + + MarkNativeAsOptional("SteamWorks_HasLicenseForApp"); + MarkNativeAsOptional("SteamWorks_GetClientSteamID"); + + MarkNativeAsOptional("SteamWorks_RequestStatsAuthID"); + MarkNativeAsOptional("SteamWorks_RequestStats"); + MarkNativeAsOptional("SteamWorks_GetStatCell"); + MarkNativeAsOptional("SteamWorks_GetStatAuthIDCell"); + MarkNativeAsOptional("SteamWorks_GetStatFloat"); + MarkNativeAsOptional("SteamWorks_GetStatAuthIDFloat"); + + MarkNativeAsOptional("SteamWorks_CreateHTTPRequest"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestContextValue"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestNetworkActivityTimeout"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestHeaderValue"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestGetOrPostParameter"); + + MarkNativeAsOptional("SteamWorks_SetHTTPCallbacks"); + MarkNativeAsOptional("SteamWorks_SendHTTPRequest"); + MarkNativeAsOptional("SteamWorks_DeferHTTPRequest"); + MarkNativeAsOptional("SteamWorks_PrioritizeHTTPRequest"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderSize"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseHeaderValue"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodySize"); + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyData"); + MarkNativeAsOptional("SteamWorks_GetHTTPDownloadProgressPct"); + MarkNativeAsOptional("SteamWorks_SetHTTPRequestRawPostBody"); + + MarkNativeAsOptional("SteamWorks_GetHTTPResponseBodyCallback"); + MarkNativeAsOptional("SteamWorks_WriteHTTPResponseBodyToFile"); +} +#endif diff --git a/addons/sourcemod/scripting/include/dhooks.inc b/addons/sourcemod/scripting/include/dhooks.inc deleted file mode 100644 index 48d3184..0000000 --- a/addons/sourcemod/scripting/include/dhooks.inc +++ /dev/null @@ -1,459 +0,0 @@ -#if defined _dhooks_included -#endinput -#endif -#define _dhooks_included -enum ObjectValueType -{ - ObjectValueType_Int = 0, - ObjectValueType_Bool, - ObjectValueType_Ehandle, - ObjectValueType_Float, - ObjectValueType_CBaseEntityPtr, - ObjectValueType_IntPtr, - ObjectValueType_BoolPtr, - ObjectValueType_EhandlePtr, - ObjectValueType_FloatPtr, - ObjectValueType_Vector, - ObjectValueType_VectorPtr, - ObjectValueType_CharPtr, - ObjectValueType_String -}; -enum ListenType -{ - ListenType_Created, - ListenType_Deleted -}; -enum ReturnType -{ - ReturnType_Unknown, - ReturnType_Void, - ReturnType_Int, - ReturnType_Bool, - ReturnType_Float, - ReturnType_String, //Note this is a string_t - ReturnType_StringPtr, //Note this is a string_t * - ReturnType_CharPtr, - ReturnType_Vector, - ReturnType_VectorPtr, - ReturnType_CBaseEntity, - ReturnType_Edict -}; -enum HookParamType -{ - HookParamType_Unknown, - HookParamType_Int, - HookParamType_Bool, - HookParamType_Float, - HookParamType_String, //Note this is a string_t - HookParamType_StringPtr, //Note this is a string_t * - HookParamType_CharPtr, - HookParamType_VectorPtr, - HookParamType_CBaseEntity, - HookParamType_ObjectPtr, - HookParamType_Edict, - HookParamType_Object -}; -enum ThisPointerType -{ - ThisPointer_Ignore, - ThisPointer_CBaseEntity, - ThisPointer_Address -}; -enum HookType -{ - HookType_Entity, - HookType_GameRules, - HookType_Raw -}; -enum MRESReturn -{ - MRES_ChangedHandled = -2, // Use changed values and return MRES_Handled - MRES_ChangedOverride, // Use changed values and return MRES_Override - MRES_Ignored, // plugin didn't take any action - MRES_Handled, // plugin did something, but real function should still be called - MRES_Override, // call real function, but use my return value - MRES_Supercede // skip real function; use my return value -}; -enum DHookPassFlag -{ - DHookPass_ByVal = (1<<0), - DHookPass_ByRef = (1<<1) -}; -funcenum ListenCB -{ - //Deleted - public (entity), - //Created - public (entity, const String:classname[]) -} -funcenum DHookRemovalCB -{ - public (hookid) -}; -funcenum DHookCallback -{ - //Function Example: void Ham::Test() with this pointer ignore - MRESReturn:public(), - - //Function Example: void Ham::Test() with this pointer passed - MRESReturn:public(this), - - //Function Example: void Ham::Test(int cake) with this pointer ignore - MRESReturn:public(Handle:hParams), - - //Function Example: void Ham::Test(int cake) with this pointer passed - MRESReturn:public(this, Handle:hParams), - - //Function Example: int Ham::Test() with this pointer ignore - MRESReturn:public(Handle:hReturn), - - //Function Example: int Ham::Test() with this pointer passed - MRESReturn:public(this, Handle:hReturn), - - //Function Example: int Ham::Test(int cake) with this pointer ignore - MRESReturn:public(Handle:hReturn, Handle:hParams), - - //Function Example: int Ham::Test(int cake) with this pointer passed - MRESReturn:public(this, Handle:hReturn, Handle:hParams), - - //Address NOW - - //Function Example: void Ham::Test() with this pointer passed - MRESReturn:public(Address:this), - - //Function Example: void Ham::Test(int cake) with this pointer passed - MRESReturn:public(Address:this, Handle:hParams), - - //Function Example: int Ham::Test() with this pointer passed - MRESReturn:public(Address:this, Handle:hReturn), - - //Function Example: int Ham::Test(int cake) with this pointer passed - MRESReturn:public(Address:this, Handle:hReturn, Handle:hParams) - -}; -/* Adds an entity listener hook - * - * @param type Type of listener to add - * @param callback Callback to use - * - * @noreturn -*/ -native DHookAddEntityListener(ListenType:type, ListenCB:callback); - -/* Removes an entity listener hook - * - * @param type Type of listener to remove - * @param callback Callback this listener was using - * - * @return True if one was removed false otherwise. -*/ -native bool:DHookRemoveEntityListener(ListenType:type, ListenCB:callback); - -/* Creates a hook - * - * @param offset vtable offset for function to hook - * @param hooktype Type of hook - * @param returntype Type type of return - * @param thistype Type of this pointer or ignore (ignore can be used if not needed) - * @param callback Callback function - * - * @return Returns setup handle for the hook or INVALID_HANDLE. -*/ -native Handle:DHookCreate(offset, HookType:hooktype, ReturnType:returntype, ThisPointerType:thistype, DHookCallback:callback); - -/* Adds param to a hook setup - * - * @param setup Setup handle to add the param to. - * @param type Param type - * @param size Used for Objects (not Object ptr) to define the size of the object. - * @param flag Used to change the pass type. - * - * @error Invalid setup handle or too many params added (request upping the max in thread) - * @noreturn -*/ -native DHookAddParam(Handle:setup, HookParamType:type, size=-1, DHookPassFlag:flag=DHookPass_ByVal); -//native DHookAddParam(Handle:setup, HookParamType:type); - -/* Hook entity - * - * @param setup Setup handle to use to add the hook. - * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) - * @param entity Entity index to hook on. - * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and will call this callback) - * - * @error Invalid setup handle, invalid entity or invalid hook type. - * @return -1 on fail a hookid on success -*/ -native DHookEntity(Handle:setup, bool:post, entity, DHookRemovalCB:removalcb=DHookRemovalCB:-1); - -/* Hook gamerules - * - * @param setup Setup handle to use to add the hook. - * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) - * @param removalcb Callback for when the hook is removed (Game rules hooks are auto-removed on map end and will call this callback) - * - * @error Invalid setup handle, failing to get gamerules pointer or invalid hook type. - * @return -1 on fail a hookid on success -*/ -native DHookGamerules(Handle:setup, bool:post, DHookRemovalCB:removalcb=DHookRemovalCB:-1); - -/* Hook a raw pointer - * - * @param setup Setup handle to use to add the hook. - * @param post True to make the hook a post hook. (If you need to change the retunr value or need the return value use a post hook! If you need to change params and return use a pre and post hook!) - * @param addr This pointer address. - * @param removalcb Callback for when the hook is removed (Entity hooks are auto-removed on entity destroyed and will call this callback) - * - * @error Invalid setup handle, invalid address or invalid hook type. - * @return -1 on fail a hookid on success -*/ -native DHookRaw(Handle:setup, bool:post, Address:addr, DHookRemovalCB:removalcb=DHookRemovalCB:-1); - -/* Remove hook by hook id - * - * @param hookid Hook id to remove - * - * @return true on success false otherwise - * @note This will not fire the removal callback! -*/ -native bool:DHookRemoveHookID(hookid); - -/* Get param value (Only use for: int, entity, bool or float param types) - * - * @param hParams Handle to params structure - * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) - * - * @error Invalid handle. Invalid param number. Invalid param type. - * @return value if num greater than 0. If 0 returns paramcount. -*/ -native any:DHookGetParam(Handle:hParams, num); - -/* Get vector param value - * - * @param hParams Handle to params structure - * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) - * @param vec Vector buffer to store result. - * - * @error Invalid handle. Invalid param number. Invalid param type. - * @noreturn -*/ -native DHookGetParamVector(Handle:hParams, num, Float:vec[3]); - -/* Get string param value - * - * @param hParams Handle to params structure - * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1.) - * @param buffer String buffer to store result - * @param size Buffer size - * - * @error Invalid handle. Invalid param number. Invalid param type. - * @return value if num greater than 0. -*/ -native DHookGetParamString(Handle:hParams, num, String:buffer[], size); - -/* Set param value (Only use for: int, entity, bool or float param types) - * - * @param hParams Handle to params structure - * @params num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) - * @param value Value to set it as (only pass int, bool, float or entity index) - * - * @error Invalid handle. Invalid param number. Invalid param type. - * @noreturn -*/ -native DHookSetParam(Handle:hParams, num, any:value); - -/* Set vector param value - * - * @param hParams Handle to params structure - * @params num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) - * @param vec Value to set vector as. - * - * @error Invalid handle. Invalid param number. Invalid param type. - * @noreturn -*/ -native DHookSetParamVector(Handle:hParams, num, Float:vec[3]); - -/* Set string param value - * - * @param hParams Handle to params structure - * @params num Param number to set (Example if the function has 2 params and you need to set the value of the first param num would be 1.) - * @param value Value to set string as. - * - * @error Invalid handle. Invalid param number. Invalid param type. - * @noreturn -*/ -native DHookSetParamString(Handle:hParams, num, String:value[]); - -/* Get return value (Only use for: int, entity, bool or float return types) - * - * @param hReturn Handle to return structure - * - * @error Invalid Handle, invalid type. - * @return Returns default value if prehook returns actual value if post hook. -*/ -native any:DHookGetReturn(Handle:hReturn); - -/* Get return vector value - * - * @param hReturn Handle to return structure - * @param vec Vector buffer to store result in. (In pre hooks will be default value (0.0,0.0,0.0)) - * - * @error Invalid Handle, invalid type. - * @noreturn -*/ -native DHookGetReturnVector(Handle:hReturn, Float:vec[3]); - -/* Get return string value - * - * @param hReturn Handle to return structure - * @param buffer String buffer to store result in. (In pre hooks will be default value "") - * @param size String buffer size - * - * @error Invalid Handle, invalid type. - * @noreturn -*/ -native DHookGetReturnString(Handle:hReturn, String:buffer[], size); - -/* Set return value (Only use for: int, entity, bool or float return types) - * - * @param hReturn Handle to return structure - * @param value Value to set return as - * - * @error Invalid Handle, invalid type. - * @noreturn -*/ -native DHookSetReturn(Handle:hReturn, any:value); - -/* Set return vector value - * - * @param hReturn Handle to return structure - * @param vec Value to set return vector as - * - * @error Invalid Handle, invalid type. - * @noreturn -*/ -native DHookSetReturnVector(Handle:hReturn, Float:vec[3]); - -/* Set return string value - * - * @param hReturn Handle to return structure - * @param value Value to set return string as - * - * @error Invalid Handle, invalid type. - * @noreturn -*/ -native DHookSetReturnString(Handle:hReturn, String:value[]); - -/* Gets an objects variable value - * - * @param hParams Handle to params structure - * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) - * @param offset Offset within the object to the var to get. - * @param type Type of var it is - * - * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. - * @return Value of the objects var. If EHANDLE type or entity returns entity index. -*/ -native any:DHookGetParamObjectPtrVar(Handle:hParams, num, offset, ObjectValueType:type); - -/* Sets an objects variable value - * - * @param hParams Handle to params structure - * @param num Param number to set. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) - * @param offset Offset within the object to the var to set. - * @param type Type of var it is - * @param value The value to set the var to. - * - * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. - * @noreturn -*/ -native DHookSetParamObjectPtrVar(Handle:hParams, num, offset, ObjectValueType:type, any:value); - -/* Gets an objects vector variable value - * - * @param hParams Handle to params structure - * @param num Param number to get. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) - * @param offset Offset within the object to the var to get. - * @param type Type of var it is - * @param buffer Buffer to store the result vector - * - * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. - * @return Value of the objects var. -*/ -native DHookGetParamObjectPtrVarVector(Handle:hParams, num, offset, ObjectValueType:type, Float:buffer[3]); - -/* Sets an objects vector variable value - * - * @param hParams Handle to params structure - * @param num Param number to set. (Example if the function has 2 params and you need the value of the first param num would be 1. 0 Will return the number of params stored) - * @param offset Offset within the object to the var to set. - * @param type Type of var it is - * @param value The value to set the vector var to. - * - * @error Invalid handle. Invalid param number. Invalid param type. Invalid Object type. - * @noreturn -*/ -native DHookSetParamObjectPtrVarVector(Handle:hParams, num, offset, ObjectValueType:type, Float:value[3]); - - -//ADD DOCS OR ELSE -//WE SHOULD WRAP THESE AROUND STOCKS FOR NON PTR AS WE SUPPORT BOTH WITH THIS NATIVE -native DHookGetParamObjectPtrString(Handle:hParams, num, offset, ObjectValueType:type, String:buffer[], size); - -/* Checks if a pointer param is null - * - * @param hParams Handle to params structure - * @param num Param number to check. - * - * @error Non pointer param - * @return True if null false otherwise. -*/ -native bool:DHookIsNullParam(Handle:hParams, num); - -public Extension:__ext_dhooks = -{ - name = "dhooks", - file = "dhooks.ext", -#if defined AUTOLOAD_EXTENSIONS - autoload = 1, -#else - autoload = 0, -#endif -#if defined REQUIRE_EXTENSIONS - required = 1, -#else - required = 0, -#endif -}; - -#if !defined REQUIRE_EXTENSIONS -public __ext_dhooks_SetNTVOptional() -{ - MarkNativeAsOptional("DHookAddEntityListener"); - MarkNativeAsOptional("DHookRemoveEntityListener"); - MarkNativeAsOptional("DHookCreate"); - MarkNativeAsOptional("DHookAddParam"); - MarkNativeAsOptional("DHookEntity"); - MarkNativeAsOptional("DHookGamerules"); - MarkNativeAsOptional("DHookRaw"); - MarkNativeAsOptional("DHookRemoveHookID"); - MarkNativeAsOptional("DHookGetParam"); - MarkNativeAsOptional("DHookGetParamVector"); - MarkNativeAsOptional("DHookGetParamString"); - MarkNativeAsOptional("DHookSetParam"); - MarkNativeAsOptional("DHookSetParamVector"); - MarkNativeAsOptional("DHookSetParamString"); - MarkNativeAsOptional("DHookGetReturn"); - MarkNativeAsOptional("DHookGetReturnVector"); - MarkNativeAsOptional("DHookGetReturnString"); - MarkNativeAsOptional("DHookSetReturn"); - MarkNativeAsOptional("DHookSetReturnVector"); - MarkNativeAsOptional("DHookSetReturnString"); - MarkNativeAsOptional("DHookGetParamObjectPtrVar"); - MarkNativeAsOptional("DHookSetParamObjectPtrVar"); - MarkNativeAsOptional("DHookGetParamObjectPtrVarVector"); - MarkNativeAsOptional("DHookSetParamObjectPtrVarVector"); - MarkNativeAsOptional("DHookIsNullParam"); - MarkNativeAsOptional("DHookGetParamObjectPtrString"); -} -#endif \ No newline at end of file diff --git a/addons/sourcemod/scripting/include/prophunt.inc b/addons/sourcemod/scripting/include/prophunt.inc new file mode 100644 index 0000000..577912b --- /dev/null +++ b/addons/sourcemod/scripting/include/prophunt.inc @@ -0,0 +1,121 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * TF2 PropHunt Redux + * Hide as a prop from the evil Pyro menace... or hunt down the hidden prop scum + * + * TF2 PropHunt (C)2007-2015 Darkimmortal. All rights reserved. + * TF2 PropHunt Redux (C)2013-2015 Powerlord (Ross Bemrose). All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: 4.0.0 + */ +// PropHunt Redux by Powerlord +// Based on +// PropHunt by Darkimmortal +// - GamingMasters.org - +#if defined _prophunt_redux_included_ + #endinput +#endif +#define _prophunt_redux_included_ + +// PropHunt Redux 4.0+ series + +#include + +#define TEAM_PROP view_as(TFTeam_Red) +#define TEAM_HUNTER view_as(TFTeam_Blue) +#define TEAM_SPECTATOR view_as(TFTeam_Spectator) + +/** + * Is PropHunt Redux currently running? + * + * This should be valid for teamplay_round_start post-hooks (but possibly not pre-hooks) + * + * @return true if PropHunt Redux is running, false otherwise + */ +native bool PropHuntRedux_IsRunning(); + +/** + * Is the named map a valid PropHunt map? + * + * @return true if yes, false if no + */ +native bool PropHuntRedux_ValidateMap(const char[] map); + +/** + * Get the model of a prop player. + * + * @return true if client is/was a prop and has a model, false otherwise + */ +native bool PropHuntRedux_GetPropModel(int client, char[] model, int maxlen); + +/** + * Get the name of the model of a prop player. + * + * Note: Defaults to the server language (likely English) + * + * @return true if client is/was a prop and has a model, false otherwise + */ +native bool PropHuntRedux_GetPropModelName(int client, char[] name, int maxlen, int targetClient=LANG_SERVER); + +/** + * Retrieve whether we're currently in Last Prop Mode. + * + * @return true if this is last prop mode, false otherwise. + */ +native bool PropHuntRedux_IsLastPropMode(); + +/** + * Callback called when the game description is being updated + * + * Stat plugins can use this to append data to the end of the string. + * + * @param description The current / updated description. Maxes out at 128 characters. + * @return Plugin_Changed if your plugin changed the game description. + * Plugin_Continued otherwise. + */ +forward Action PropHuntRedux_UpdateGameDescription(char[] description, int maxlength); + +public SharedPlugin __pl_prophunt_redux = +{ + name = "Prophunt Redux", + file = "prophunt.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_prophunt_redux_SetNTVOptional() +{ + MarkNativeAsOptional("PropHuntRedux_IsRunning"); + MarkNativeAsOptional("PropHuntRedux_ValidateMap"); + MarkNativeAsOptional("PropHuntRedux_GetPropModel"); + MarkNativeAsOptional("PropHuntRedux_GetPropModelName"); + MarkNativeAsOptional("PropHuntRedux_IsLastPropMode"); +} +#endif diff --git a/addons/sourcemod/scripting/include/updater.inc b/addons/sourcemod/scripting/include/updater.inc new file mode 100644 index 0000000..f37bdf2 --- /dev/null +++ b/addons/sourcemod/scripting/include/updater.inc @@ -0,0 +1,97 @@ +#if defined _updater_included + #endinput +#endif +#define _updater_included + +/** + * Adds your plugin to the updater. The URL will be updated if + * your plugin was previously added. + * + * @param url URL to your plugin's update file. + * @noreturn + */ +native Updater_AddPlugin(const String:url[]); + +/** + * Removes your plugin from the updater. This does not need to + * be called during OnPluginEnd. + * + * @noreturn + */ +native Updater_RemovePlugin(); + +/** + * Forces your plugin to be checked for updates. The behaviour + * of the update is dependant on the server's configuration. + * + * @return True if an update was triggered. False otherwise. + * @error Plugin not found in updater. + */ +native bool:Updater_ForceUpdate(); + +/** + * Called when your plugin is about to be checked for updates. + * + * @return Plugin_Handled to prevent checking, Plugin_Continue to allow it. + */ +forward Action:Updater_OnPluginChecking(); + +/** + * Called when your plugin is about to begin downloading an available update. + * + * @return Plugin_Handled to prevent downloading, Plugin_Continue to allow it. + */ +forward Action:Updater_OnPluginDownloading(); + +/** + * Called when your plugin's update files have been fully downloaded + * and are about to write to their proper location. This should be used + * to free read-only resources that require write access for your update. + * + * @note OnPluginUpdated will be called later during the same frame. + * + * @noreturn + */ +forward Updater_OnPluginUpdating(); + +/** + * Called when your plugin's update has been completed. It is safe + * to reload your plugin at this time. + * + * @noreturn + */ +forward Updater_OnPluginUpdated(); + +/** + * @brief Reloads a plugin. + * + * @param plugin Plugin Handle (INVALID_HANDLE uses the calling plugin). + * @noreturn + */ +stock ReloadPlugin(Handle:plugin=INVALID_HANDLE) +{ + decl String:filename[64]; + GetPluginFilename(plugin, filename, sizeof(filename)); + ServerCommand("sm plugins reload %s", filename); +} + + +public SharedPlugin:__pl_updater = +{ + name = "updater", + file = "updater.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public __pl_updater_SetNTVOptional() +{ + MarkNativeAsOptional("Updater_AddPlugin"); + MarkNativeAsOptional("Updater_RemovePlugin"); + MarkNativeAsOptional("Updater_ForceUpdate"); +} +#endif diff --git a/addons/sourcemod/scripting/prophunt-stats.sp b/addons/sourcemod/scripting/prophunt-stats.sp deleted file mode 100644 index acd81cf..0000000 --- a/addons/sourcemod/scripting/prophunt-stats.sp +++ /dev/null @@ -1,298 +0,0 @@ -// PropHunt Redux Stats by Powerlord -// - reddit.com/r/RUGC_Midwest - - -// This plugin just has the commands to create and upgrade the Local Stats database - -// This is based heavily on the SourceMod SQLAdmins code because I'm lazy. - -#pragma semicolon 1 -#include - -#define CURRENT_SCHEMA_VERSION 1409 -#define SCHEMA_UPGRADE_1 1409 - -new current_version[4] = {3, 1, 0, CURRENT_SCHEMA_VERSION}; - -#define MYSQL "mysql" -#define SQLITE "sqlite" - -#define PL_VERSION "3.1.0 alpha 1" - -public Plugin:myinfo = -{ - name = "PropHunt Redux Local Stats Manager", - author = "Powerlord", - description = "Create/Update PropHunt Redux's local stats DB", - version = PL_VERSION, - url = "https://forums.alliedmods.net/showthread.php?t=228086" -}; - -public OnPluginStart() -{ - LoadTranslations("common.phrases"); - - RegServerCmd("ph_create_stats_tables", Command_CreateTables); - RegServerCmd("ph_update_stats_tables", Command_UpdateTables); - -} - -Handle:Connect() -{ - decl String:error[255]; - new Handle:db; - - if (SQL_CheckConfig("prophunt_local")) - { - db = SQL_Connect("prophunt_local", true, error, sizeof(error)); - } else { - db = SQL_Connect("default", true, error, sizeof(error)); - } - - if (db == INVALID_HANDLE) - { - LogError("Could not connect to database: %s", error); - } - - return db; -} - -CreateMySQL(client, Handle:db) -{ - new String:queries[7][1024]; - - Format(queries[0], sizeof(queries[]), "CREATE TABLE ph_servers (id int(10) unsigned NOT NULL auto_increment, ip varchar(65), points int(10), time int(10), PRIMARY KEY(id) )"); - Format(queries[1], sizeof(queries[]), "CREATE TABLE ph_players (steamid varchar(25), name varchar(%d), points int(10), wins int(10), losses int(10), time int(10), PRIMARY KEY(steamid) )", MAX_NAME_LENGTH); - Format(queries[2], sizeof(queries[]), "CREATE TABLE ph_deaths (id int(10) unsigned NOT NULL auto_increment, victimid varchar(25), killerid varchar(20), killerteam int(1), weapon varchar(64), assisterid varchar(25), ip varchar(65), map varchar(%d), prop varchar(%d), victim_position_x float, victim_position_y float, victim_position_z float, killer_position_x float, killer_position_y float, killer_position_z float, victim_class int(1), killer_class int(1), survival_time int(4), PRIMARY KEY(id) )", PLATFORM_MAX_PATH, PLATFORM_MAX_PATH); - Format(queries[3], sizeof(queries[]), "CREATE TABLE ph_survivals (id int(10) unsigned NOT NULL auto_increment, steamid varchar(25), prop varchar(%d), ip varchar(65), map varchar(%d), position_x float, position_y float, position_z float, class int(1), team int(1), survival_time int(4), PRIMARY KEY(id) )", PLATFORM_MAX_PATH, PLATFORM_MAX_PATH); - Format(queries[4], sizeof(queries[]), "CREATE TABLE ph_rounds (id int(10) unsigned NOT NULL auto_increment, team char(3), map varchar(%d), ip varchar(65), PRIMARY KEY(id) )", PLATFORM_MAX_PATH); - Format(queries[5], sizeof(queries[]), "CREATE TABLE ph_props (id int(10) unsigned NOT NULL auto_increment, name varchar(%d), deaths int(10), survivals int(10))", PLATFORM_MAX_PATH); - Format(queries[6], sizeof(queries[]), "CREATE TABLE IF NOT EXISTS ph_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))"); - - for (new i = 0; i < 7; i++) - { - if (!DoQuery(client, db, queries[i])) - { - return; - } - } - - decl String:query[256]; - Format(query, - sizeof(query), - "INSERT INTO ph_config (cfg_key, cfg_value) VALUES ('schema_version', '3.1.0.%d') ON DUPLICATE KEY UPDATE cfg_value = '3.1.0.%d'", - CURRENT_SCHEMA_VERSION, - CURRENT_SCHEMA_VERSION); - - if (!DoQuery(client, db, query)) - { - return; - } - - ReplyToCommand(client, "[PH] Stats tables have been created."); -} - -public Action:Command_CreateTables(args) -{ - new client = 0; - new Handle:db = Connect(); - if (db == INVALID_HANDLE) - { - ReplyToCommand(client, "[PH] %t", "Could not connect to database"); - return Plugin_Handled; - } - - new String:ident[16]; - SQL_ReadDriver(db, ident, sizeof(ident)); - - if (strcmp(ident, "mysql") == 0) - { - CreateMySQL(client, db); - } else if (strcmp(ident, "sqlite") == 0) { - ReplyToCommand(client, "[PH] SQLite is not supported at this time."); - } else { - ReplyToCommand(client, "[PH] Unknown driver type '%s', cannot create tables.", ident); - } - - CloseHandle(db); - - return Plugin_Handled; -} - -bool:GetUpdateVersion(client, Handle:db, versions[4]) -{ - decl String:query[256]; - new Handle:hQuery; - - Format(query, sizeof(query), "SELECT cfg_value FROM ph_config WHERE cfg_key = 'schema_version'"); - if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) - { - DoError(client, db, query, "Version lookup query failed"); - return false; - } - if (SQL_FetchRow(hQuery)) - { - decl String:version_string[255]; - SQL_FetchString(hQuery, 0, version_string, sizeof(version_string)); - - decl String:version_numbers[4][12]; - if (ExplodeString(version_string, ".", version_numbers, 4, 12) == 4) - { - for (new i = 0; i < 4; i++) - { - versions[i] = StringToInt(version_numbers[i]); - } - } - } - - CloseHandle(hQuery); - - if (current_version[3] < versions[3]) - { - ReplyToCommand(client, "[PH] The database is newer than the expected version."); - return false; - } - - if (current_version[3] == versions[3]) - { - ReplyToCommand(client, "[PH] Your tables are already up to date."); - return false; - } - - - return true; -} - -UpdateMySQL(client, Handle:db) -{ - decl String:query[512]; - new Handle:hQuery; - - Format(query, sizeof(query), "SHOW TABLES"); - if ((hQuery = SQL_Query(db, query)) == INVALID_HANDLE) - { - DoError(client, db, query, "Table lookup query failed"); - return; - } - - decl String:table[64]; - new bool:found = false; - while (SQL_FetchRow(hQuery)) - { - SQL_FetchString(hQuery, 0, table, sizeof(table)); - if (strcmp(table, "ph_config") == 0) - { - found = true; - } - } - CloseHandle(hQuery); - - new versions[4]; - - if (found && !GetUpdateVersion(client, db, versions)) - { - return; - } - - /* - * There are presently no upgrades - */ - if (versions[3] < SCHEMA_UPGRADE_1) - { - /* - new String:queries[6][] = - { - "CREATE TABLE IF NOT EXISTS sm_config (cfg_key varchar(32) NOT NULL, cfg_value varchar(255) NOT NULL, PRIMARY KEY (cfg_key))", - "ALTER TABLE sm_admins ADD immunity INT UNSIGNED NOT NULL", - "ALTER TABLE sm_groups ADD immunity_level INT UNSIGNED NOT NULL", - "UPDATE sm_groups SET immunity_level = 2 WHERE immunity = 'default'", - "UPDATE sm_groups SET immunity_level = 1 WHERE immunity = 'global'", - "ALTER TABLE sm_groups DROP immunity" - }; - - for (new i = 0; i < 6; i++) - { - if (!DoQuery(client, db, queries[i])) - { - return; - } - } - - decl String:upgr[48]; - Format(upgr, sizeof(upgr), "1.0.0.%d", SCHEMA_UPGRADE_1); - - Format(query, sizeof(query), "INSERT INTO ph_config (cfg_key, cfg_value) VALUES ('schema_version', '%s') ON DUPLICATE KEY UPDATE cfg_value = '%s'", upgr, upgr); - if (!DoQuery(client, db, query)) - { - return; - } - - versions[3] = SCHEMA_UPGRADE_1; - */ - } - - //ReplyToCommand(client, "[PH] Your tables are now up to date."); - ReplyToCommand(client, "[PH] There are no upgrades at this time."); -} - -public Action:Command_UpdateTables(args) -{ - new client = 0; - new Handle:db = Connect(); - if (db == INVALID_HANDLE) - { - ReplyToCommand(client, "[PH] %t", "Could not connect to database"); - return Plugin_Handled; - } - - new String:ident[16]; - SQL_ReadDriver(db, ident, sizeof(ident)); - - if (strcmp(ident, "mysql") == 0) - { - UpdateMySQL(client, db); - } else if (strcmp(ident, "sqlite") == 0) { - ReplyToCommand(client, "[PH] SQLite is not supported at this time."); - } else { - ReplyToCommand(client, "[PH] Unknown driver type, cannot upgrade."); - } - - CloseHandle(db); - - return Plugin_Handled; -} - -stock bool:DoQuery(client, Handle:db, const String:query[]) -{ - if (!SQL_FastQuery(db, query)) - { - decl String:error[255]; - SQL_GetError(db, error, sizeof(error)); - LogError("Query failed: %s", error); - LogError("Query dump: %s", query); - ReplyToCommand(client, "[PH] %t", "Failed to query database"); - return false; - } - - return true; -} - -stock Action:DoError(client, Handle:db, const String:query[], const String:msg[]) -{ - decl String:error[255]; - SQL_GetError(db, error, sizeof(error)); - LogError("%s: %s", msg, error); - LogError("Query dump: %s", query); - CloseHandle(db); - ReplyToCommand(client, "[PH] %t", "Failed to query database"); - return Plugin_Handled; -} - -stock Action:DoStmtError(client, Handle:db, const String:query[], const String:error[], const String:msg[]) -{ - LogError("%s: %s", msg, error); - LogError("Query dump: %s", query); - CloseHandle(db); - ReplyToCommand(client, "[PH] %t", "Failed to query database"); - return Plugin_Handled; -} - diff --git a/addons/sourcemod/scripting/prophunt.sp b/addons/sourcemod/scripting/prophunt.sp index 8f872b3..f32e70e 100644 --- a/addons/sourcemod/scripting/prophunt.sp +++ b/addons/sourcemod/scripting/prophunt.sp @@ -29,14 +29,13 @@ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), * or . * - * Version: 3.3.4 + * Version: 4.0.0 */ // PropHunt Redux by Powerlord // Based on // PropHunt by Darkimmortal // - GamingMasters.org - -#pragma semicolon 1 #include #include #include @@ -47,9 +46,15 @@ #undef REQUIRE_EXTENSIONS #include +#include #undef REQUIRE_PLUGIN -#include +#include + +#define REQUIRE_EXTENSIONS +#define REQUIRE_PLUGIN +#pragma semicolon 1 +#pragma newdecls required #if !defined SNDCHAN_VOICE2 #define SNDCHAN_VOICE2 7 @@ -60,19 +65,15 @@ #define MAXLANGUAGECODE 4 -#define PL_VERSION "3.3.4" +#define PL_VERSION "4.0.0.0 alpha 1" + +// sv_tags has a 255 limit +#define SV_TAGS_SIZE 255 + //-------------------------------------------------------------------------------------------------------------------------------- //-------------------------------------------- MAIN PROPHUNT CONFIGURATION ------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------------------------- -// Enable for global stats support (.inc file available on request due to potential for cheating and database abuse) -// Default: OFF -//#define STATS - -// Enable for local stats support -// Default: OFF -//#define LOCALSTATS - // GM only stuff //#define GM @@ -81,15 +82,19 @@ #include #endif +// Include support for Workshop maps +// Requires SourceMod 1.8 or later +// Default: ON +#define WORKSHOP_SUPPORT + // Include support for Opt-In MultiMod // Default: OFF //#define OIMM -// Include support for DHooks to switch teams on round end -// You only really want to disable this if the SetWinningTeam offset breaks -// and Powerlord (or someone else) hasn't fixed it yet +// Include support for switching teams using gamedata +// You only really want to disable this if team switchs aren't working (i.e. gamedata hasn't yet been updated) // Default: ON -#define DHOOKS +#define SWITCH_TEAMS // Give last prop a scattergun and apply jarate to all pyros on last prop alive // Default: ON @@ -135,39 +140,29 @@ #define SETUP_MIN 30 #define SETUP_MAX 120 +// "Production" URL (3.3 branch) +//#define UPDATE_URL "http://tf2.rbemrose.com/sourcemod/prophunt/prophunt.txt" + +// "Dev" URL (3.4 branch) +#define UPDATE_URL "http://tf2.rbemrose.com/sourcemod/prophunt/dev/prophunt.txt" //-------------------------------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------------------------- -#if defined DHOOKS -#include -#endif - #if defined OIMM #include #endif // Needed for stats2.inc compatibility -#define TEAM_BLUE _:TFTeam_Blue -#define TEAM_RED _:TFTeam_Red -#define TEAM_SPEC _:TFTeam_Spectator -#define TEAM_UNASSIGNED _:TFTeam_Unassigned +#define TEAM_BLUE view_as(TFTeam_Blue) +#define TEAM_RED view_as(TFTeam_Red) +#define TEAM_SPEC view_as(TFTeam_Spectator) +#define TEAM_UNASSIGNED view_as(TFTeam_Unassigned) #define TEAM_PROP TEAM_RED #define TEAM_HUNTER TEAM_BLUE -#define FLAMETHROWER "models/weapons/w_models/w_flamethrower.mdl" - -#define STATE_WAT -1 -#define STATE_IDLE 0 -#define STATE_RUNNING 1 -#define STATE_SWING 2 -#define STATE_CROUCH 3 - -// Not sure what this is for, but pretty sure it's wrong.. player conditions are no longer defined like this -#define PLAYER_ONFIRE (1 << 14) - // Weapon Indexes #define WEP_SHOTGUN_UNIQUE 199 @@ -179,10 +174,6 @@ #define LOCKVOL 0.7 #define UNBALANCE_LIMIT 1 #define MAXMODELNAME 96 -// Not sure what this is for, but pretty sure it's wrong.. player conditions are no longer defined like this -#define TF2_PLAYERCOND_ONFIREALERT (1<<20) - -#define IN_MOVEMENT IN_MOVELEFT | IN_MOVERIGHT | IN_FORWARD | IN_BACK | IN_JUMP #define TIMER_NAME "prop_hunt_timer" @@ -195,16 +186,6 @@ enum Item_Attributes, } -enum ScReason -{ - ScReason_TeamWin = 0, - ScReason_TeamLose, - ScReason_Death, - ScReason_Kill, - ScReason_Time, - ScReason_Friendly -}; - enum PropData { String:PropData_Name[MAXMODELNAME], @@ -219,30 +200,6 @@ enum RoundChange RoundChange_Disable, } -enum -{ - ScoreData_Captures, - ScoreData_Defenses, - ScoreData_Kills, - ScoreData_Deaths, - ScoreData_Suicides, - ScoreData_Dominations, - ScoreData_Revenge, - ScoreData_BuildingsBuilt, - ScoreData_BuildingsDestroyed, - ScoreData_Headshots, - ScoreData_Backstabs, - ScoreData_HealPoints, - ScoreData_Invulns, - ScoreData_Teleports, - ScoreData_DamageDone, - ScoreData_Crits, - ScoreData_ResupplyPoints, - ScoreData_KillAssists, - ScoreData_BonusPoints, - ScoreData_Points -} - enum { TFClassBits_None = 0, @@ -257,190 +214,205 @@ enum TFClassBits_Engineer = (1 << 8) // 256 } -new bool:g_RoundOver = true; -new bool:g_inPreRound = true; - -new bool:g_LastProp; -new bool:g_Attacking[MAXPLAYERS+1]; -new bool:g_SetClass[MAXPLAYERS+1]; -new bool:g_Spawned[MAXPLAYERS+1]; -new bool:g_TouchingCP[MAXPLAYERS+1]; -new bool:g_Charge[MAXPLAYERS+1]; -new bool:g_First[MAXPLAYERS+1]; -new bool:g_HoldingLMB[MAXPLAYERS+1]; -new bool:g_HoldingRMB[MAXPLAYERS+1]; -new bool:g_AllowedSpawn[MAXPLAYERS+1]; -new bool:g_RotLocked[MAXPLAYERS+1]; -new bool:g_Hit[MAXPLAYERS+1]; -new bool:g_Spec[MAXPLAYERS+1]; -new String:g_PlayerModel[MAXPLAYERS+1][PLATFORM_MAX_PATH]; - -new String:g_Mapname[PLATFORM_MAX_PATH]; -new String:g_ServerIP[32]; -new String:g_Version[16]; - -new g_Message_red; -new g_Message_blue; -new g_RoundTime = 175; -new g_Message_bit = 0; -new g_Setup = 0; -//new g_iVelocity = -1; - -#if defined STATS || defined LOCALSTATS -new bool:g_MapChanging = false; -new g_StartTime; -#endif +bool g_RoundOver = true; +bool g_inSetup = false; +bool g_inPreRound = true; +bool g_PlayerDied = false; +float g_flRoundStart = 0.0; + +bool g_LastProp; +bool g_Attacking[MAXPLAYERS+1]; +bool g_SetClass[MAXPLAYERS+1]; +bool g_Spawned[MAXPLAYERS+1]; +bool g_TouchingCP[MAXPLAYERS+1]; +bool g_Charge[MAXPLAYERS+1]; +bool g_First[MAXPLAYERS+1]; +bool g_HoldingLMB[MAXPLAYERS+1]; +bool g_HoldingRMB[MAXPLAYERS+1]; +bool g_AllowedSpawn[MAXPLAYERS+1]; +bool g_RotLocked[MAXPLAYERS+1]; +bool g_Hit[MAXPLAYERS+1]; +bool g_Spec[MAXPLAYERS+1]; +char g_PlayerModel[MAXPLAYERS+1][PLATFORM_MAX_PATH]; + +char g_Mapname[PLATFORM_MAX_PATH]; +char g_ServerIP[32]; + +int g_Message_red; +int g_Message_blue; +int g_RoundTime = 175; +int g_Message_bit = 0; +int g_Setup = 0; //new Handle:g_TimerStart = INVALID_HANDLE; -new Handle:g_Sounds = INVALID_HANDLE; -new Handle:g_BroadcastSounds = INVALID_HANDLE; - -new bool:g_Doors = false; -new bool:g_Relay = false; -new bool:g_Freeze = true; - -//new bool:g_weaponRemovals[MAXITEMS]; -//new Float:g_weaponNerfs[MAXITEMS]; -//new Float:g_weaponSelfDamage[MAXITEMS]; - -new Handle:g_hWeaponRemovals; -new Handle:g_hWeaponNerfs; -new Handle:g_hWeaponSelfDamage; -new Handle:g_hWeaponStripAttribs; -new Handle:g_hWeaponAddAttribs; -new Handle:g_hWeaponReplacements; -new Handle:g_hWeaponReplacementPlayerClasses; - -new g_classLimits[2][10]; -new TFClassType:g_defaultClass[2]; -new Float:g_classSpeeds[10][3]; //0 - Base speed, 1 - Max Speed, 2 - Increment Value -new Float:g_currentSpeed[MAXPLAYERS+1]; - -//new g_oFOV; -//new g_oDefFOV; - -new Handle:g_PropData = INVALID_HANDLE; +StringMap g_Sounds; +StringMap g_BroadcastSounds; + +bool g_Doors = false; +bool g_Relay = false; +bool g_Freeze = true; + +ArrayList g_hWeaponRemovals; +ArrayList g_hPropWeaponRemovals; +StringMap g_hWeaponNerfs; +StringMap g_hWeaponSelfDamage; +ArrayList g_hWeaponStripAttribs; +StringMap g_hWeaponAddAttribs; +StringMap g_hWeaponReplacements; +StringMap g_hWeaponReplacementPlayerClasses; + +int g_classLimits[2][10]; +TFClassType g_defaultClass[2]; +float g_classSpeeds[10][3]; //0 - Base speed, 1 - Max Speed, 2 - Increment Value +float g_currentSpeed[MAXPLAYERS+1]; + +StringMap g_PropData; // Multi-language support -new Handle:g_ModelLanguages = INVALID_HANDLE; -new Handle:g_PropNames = INVALID_HANDLE; -new Handle:g_PropNamesIndex = INVALID_HANDLE; - -new Handle:g_ConfigKeyValues = INVALID_HANDLE; -new Handle:g_ModelName = INVALID_HANDLE; -new Handle:g_ModelOffset = INVALID_HANDLE; -new Handle:g_ModelRotation = INVALID_HANDLE; -new Handle:g_ModelSkin = INVALID_HANDLE; -new Handle:g_Text1 = INVALID_HANDLE; -new Handle:g_Text2 = INVALID_HANDLE; -new Handle:g_Text3 = INVALID_HANDLE; -new Handle:g_Text4 = INVALID_HANDLE; - +ArrayList g_ModelLanguages; +StringMap g_PropNames; +ArrayList g_PropNamesIndex; + +KeyValues g_ConfigKeyValues; +ArrayList g_ModelName; +ArrayList g_ModelOffset; +ArrayList g_ModelRotation; +ArrayList g_ModelSkin; +Handle g_Text1; +Handle g_Text2; +Handle g_Text3; +Handle g_Text4; + +// PropHunt Redux Menus //new Handle:g_RoundTimer = INVALID_HANDLE; -new Handle:g_PropMenu = INVALID_HANDLE; -new Handle:g_ConfigMenu = INVALID_HANDLE; - -new Handle:g_PHEnable = INVALID_HANDLE; -new Handle:g_PHPropMenu = INVALID_HANDLE; -new Handle:g_PHPropMenuRestrict = INVALID_HANDLE; -//new Handle:g_PHAdmFlag = INVALID_HANDLE; -new Handle:g_PHAdvertisements = INVALID_HANDLE; -new Handle:g_PHPreventFallDamage = INVALID_HANDLE; -new Handle:g_PHGameDescription = INVALID_HANDLE; -new Handle:g_PHAirblast = INVALID_HANDLE; -new Handle:g_PHAntiHack = INVALID_HANDLE; -new Handle:g_PHReroll = INVALID_HANDLE; -new Handle:g_PHStaticPropInfo = INVALID_HANDLE; -new Handle:g_PHSetupLength = INVALID_HANDLE; -new Handle:g_PHDamageBlocksPropChange = INVALID_HANDLE; -new Handle:g_PHPropMenuNames = INVALID_HANDLE; -new Handle:g_PHMultilingual = INVALID_HANDLE; - -new String:g_AdText[128] = ""; - -new bool:g_MapStarted = false; - -new bool:g_SteamTools = false; -new bool:g_TF2Attribs = false; +Menu g_PropMenu; +Menu g_ConfigMenu; + +// PropHunt Redux CVars +ConVar g_PHEnable; +ConVar g_PHPropMenu; +ConVar g_PHPropMenuRestrict; +ConVar g_PHAdvertisements; +ConVar g_PHPreventFallDamage; +ConVar g_PHGameDescription; +ConVar g_PHAirblast; +ConVar g_PHAntiHack; +ConVar g_PHReroll; +ConVar g_PHStaticPropInfo; +ConVar g_PHSetupLength; +ConVar g_PHDamageBlocksPropChange; +ConVar g_PHPropMenuNames; +ConVar g_PHMultilingual; +ConVar g_PHRespawnDuringSetup; +ConVar g_PHUseUpdater; +ConVar g_PHAllowTaunts; + +char g_AdText[128] = ""; + +bool g_MapStarted = false; + +// Track optional dependencies +bool g_SteamTools = false; +bool g_SteamWorks = false; +bool g_Updater = false; + #if defined OIMM -new bool:g_OptinMultiMod = false; +bool g_OptinMultiMod = false; #endif -new bool:g_Enabled = true; +bool g_Enabled = true; // Timers -new Handle:g_hAntiHack; -new Handle:g_hLocked; -new Handle:g_hScore; +Handle g_hAntiHack; +Handle g_hLocked; +Handle g_hScore; // Valve CVars we're going to save and adjust -new Handle:g_hArenaRoundTime; -new g_ArenaRoundTime; -new Handle:g_hWeaponCriticals; -new g_WeaponCriticals; -new Handle:g_hIdledealmethod; -new g_Idledealmethod; -new Handle:g_hTournamentStopwatch; -new g_TournamentStopwatch; -new Handle:g_hTournamentHideDominationIcons; -new g_TournamentHideDominationIcons; -new Handle:g_hFriendlyfire; -new g_Friendlyfire; -new Handle:g_hGravity; -new g_Gravity; -new Handle:g_hForcecamera; -new g_Forcecamera; -new Handle:g_hArenaCapEnableTime; -new g_ArenaCapEnableTime; -new Handle:g_hTeamsUnbalanceLimit; -new g_TeamsUnbalanceLimit; -new Handle:g_hArenaMaxStreak; -new g_ArenaMaxStreak; -new Handle:g_hEnableRoundWaitTime; -new g_EnableRoundWaitTime; -new Handle:g_hWaitingForPlayerTime; -new g_WaitingForPlayerTime; -new Handle:g_hArenaUseQueue; -new g_ArenaUseQueue; -new Handle:g_hShowVoiceIcons; -new g_ShowVoiceIcons; -new Handle:g_hSolidObjects; -new g_SolidObjects; -new Handle:g_hArenaPreroundTime; -new g_ArenaPreroundTime; - -new Handle:g_hBonusRoundTime; - -new g_Replacements[MAXPLAYERS+1][6]; -new g_ReplacementCount[MAXPLAYERS+1]; -new bool:g_Rerolled[MAXPLAYERS+1] = { false, ... }; - -new bool:g_CvarsSet; - -new RoundChange:g_RoundChange; - -new bool:g_CurrentlyFlaming[MAXPLAYERS+1]; -new g_FlameCount[MAXPLAYERS+1]; +ConVar g_hArenaRoundTime; +int g_ArenaRoundTime; +ConVar g_hWeaponCriticals; +bool g_WeaponCriticals; +ConVar g_hIdledealmethod; +int g_Idledealmethod; +ConVar g_hTournamentStopwatch; +bool g_TournamentStopwatch; +ConVar g_hTournamentHideDominationIcons; +bool g_TournamentHideDominationIcons; +ConVar g_hFriendlyfire; +bool g_Friendlyfire; +ConVar g_hGravity; +int g_Gravity; +ConVar g_hForcecamera; +int g_Forcecamera; +ConVar g_hArenaCapEnableTime; +int g_ArenaCapEnableTime; +ConVar g_hTeamsUnbalanceLimit; +int g_TeamsUnbalanceLimit; +ConVar g_hArenaMaxStreak; +int g_ArenaMaxStreak; +ConVar g_hEnableRoundWaitTime; +bool g_EnableRoundWaitTime; +ConVar g_hWaitingForPlayerTime; +int g_WaitingForPlayerTime; +ConVar g_hArenaUseQueue; +bool g_ArenaUseQueue; +ConVar g_hShowVoiceIcons; +bool g_ShowVoiceIcons; +ConVar g_hSolidObjects; +bool g_SolidObjects; +ConVar g_hArenaPreroundTime; +int g_ArenaPreroundTime; +ConVar g_hArenaFirstBlood; +bool g_ArenaFirstBlood; + +ConVar g_hWeaponDropTime; +int g_WeaponDropTime; + +// Regular convars +#if !defined SWITCH_TEAMS +ConVar g_hBonusRoundTime; +#endif +ConVar g_hTags; + +int g_Replacements[MAXPLAYERS+1][6]; +int g_ReplacementCount[MAXPLAYERS+1]; +bool g_Rerolled[MAXPLAYERS+1] = { false, ... }; + +bool g_CvarsSet; + +RoundChange g_RoundChange; + +bool g_CurrentlyFlaming[MAXPLAYERS+1]; +int g_FlameCount[MAXPLAYERS+1]; #define FLY_COUNT 3 -new g_LastPropDamageTime[MAXPLAYERS+1] = { -1, ... }; -new g_LastPropPlayer = 0; +int g_LastPropDamageTime[MAXPLAYERS+1] = { -1, ... }; +int g_LastPropPlayer = 0; -new bool:g_PHMap; +bool g_PHMap; -new bool:g_RoundStartMessageSent[MAXPLAYERS+1]; +bool g_RoundStartMessageSent[MAXPLAYERS+1]; // Multi-round support -new g_RoundCount = 0; -new g_RoundCurrent = 0; -new g_RoundSwitchAlways = false; +int g_RoundCount = 0; +int g_RoundCurrent = 0; +int g_RoundSwitchAlways = false; // New override support for propmenu and stuff -new g_PropMenuOldFlags = 0; -new bool:g_PropMenuOverrideInstalled = false; -new g_PropRerollOldFlags = 0; -new bool:g_PropRerollOverrideInstalled = false; +int g_PropMenuOldFlags = 0; +bool g_PropMenuOverrideInstalled = false; +int g_PropRerollOldFlags = 0; +bool g_PropRerollOverrideInstalled = false; + +int g_GameRulesProxy = INVALID_ENT_REFERENCE; + +#if defined SHINX +TFClassType g_PreferredHunterClass[MAXPLAYERS+1] = { TFClass_Unknown, ... }; +#endif + +// Forward for subplugins +Handle g_GameDescriptionForward; -public Plugin:myinfo = +public Plugin myinfo = { name = "PropHunt Redux", author = "Darkimmortal, Geit, and Powerlord", @@ -479,96 +451,97 @@ enum LAST_SHARED_COLLISION_GROUP } -#if defined STATS - -#include "prophunt\stats2.inc" -/* -public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) +enum { - decl String:hostname[255], String:ip[32], String:port[8]; //, String:map[92]; - GetConVarString(FindConVar("hostname"), hostname, sizeof(hostname)); - GetConVarString(FindConVar("ip"), ip, sizeof(ip)); - GetConVarString(FindConVar("hostport"), port, sizeof(port)); + SOLID_NONE = 0, // no solid model + SOLID_BSP = 1, // a BSP tree + SOLID_BBOX = 2, // an AABB + SOLID_OBB = 3, // an OBB (not implemented yet) + SOLID_OBB_YAW = 4, // an OBB, constrained so that it can only yaw + SOLID_CUSTOM = 5, // Always call into the entity for tests + SOLID_VPHYSICS = 6, // solid vphysics object, get vcollide from the model and collide with that + SOLID_LAST, +}; - if(StrContains(hostname, "GamingMasters.co.uk", false) != -1) +Handle g_hSwitchTeams; + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + if (GetEngineVersion() != Engine_TF2) { - if(StrContains(hostname, "PropHunt", false) == -1 && StrContains(hostname, "Arena", false) == -1 && StrContains(hostname, "Dark", false) == -1 && - StrContains(ip, "8.9.4.169", false) == -1) - return APLRes_SilentFailure; + strcopy(error, err_max, "PropHunt Redux only works on Team Fortress 2."); + return APLRes_Failure; } - return APLRes_Success; -} -*/ -#endif - -#if defined LOCALSTATS - -#include "prophunt\localstats2.inc" + + // This SHOULD be done in steamtools.inc, but isn't. + MarkNativeAsOptional("Steam_SetGameDescription"); +#if defined WORKSHOP_SUPPORT + // Part of SM 1.8 + MarkNativeAsOptional("GetMapDisplayName"); #endif -// DHooks stuff -#if defined DHOOKS -new Handle:hWinning = INVALID_HANDLE; -new bool:g_DHooks = false; -new g_SetWinningTeamOffset = -1; -new g_SetWinningTeamHook = -1; -#endif + CreateNative("PropHuntRedux_ValidateMap", Native_ValidateMap); + CreateNative("PropHuntRedux_IsRunning", Native_IsRunning); + CreateNative("PropHuntRedux_GetPropModel", Native_GetModel); + CreateNative("PropHuntRedux_GetPropModelName", Native_GetModelName); + CreateNative("PropHuntRedux_IsLastPropMode", Native_LastPropMode); + + RegPluginLibrary("prophuntredux"); -public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max) -{ - MarkNativeAsOptional("Steam_SetGameDescription"); return APLRes_Success; } -public OnPluginStart() +public void OnPluginStart() { -#if defined DHOOKS - new Handle:gc = LoadGameConfigFile("tf2-roundend.games"); - if (gc != INVALID_HANDLE) - { - g_SetWinningTeamOffset = GameConfGetOffset(gc, "SetWinningTeam"); - CloseHandle(gc); - } + Handle gc; + +#if defined SWITCH_TEAMS + gc = LoadGameConfigFile("tf2-switch-teams"); + + StartPrepSDKCall(SDKCall_GameRules); + PrepSDKCall_SetFromConf(gc, SDKConf_Virtual, "CTFGameRules::SetSwitchTeams"); + PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain); + g_hSwitchTeams = EndPrepSDKCall(); + +#if defined LOG + LogMessage("[PH] Created call to SetSwitchTeams at vtable offset %d", GameConfGetOffset(gc, "CTeamplayRules::SetSwitchTeams")); +#endif + + delete gc; #if defined LOG else { - LogMessage("Failed to load gamedata"); + LogMessage("[PH] Failed to load gamedata"); } #endif #endif - decl String:hostname[255], String:ip[32], String:port[8]; //, String:map[92]; + char hostname[255], ip[32], port[8]; //, String:map[92]; GetConVarString(FindConVar("hostname"), hostname, sizeof(hostname)); GetConVarString(FindConVar("ip"), ip, sizeof(ip)); GetConVarString(FindConVar("hostport"), port, sizeof(port)); Format(g_ServerIP, sizeof(g_ServerIP), "%s:%s", ip, port); - new bool:statsbool = false; -#if defined STATS || defined LOCALSTATS - statsbool = true; -#endif - - g_hWeaponRemovals = CreateArray(); - g_hWeaponNerfs = CreateTrie(); - g_hWeaponSelfDamage = CreateTrie(); - g_hWeaponStripAttribs = CreateArray(); - g_hWeaponAddAttribs = CreateTrie(); - g_hWeaponReplacements = CreateTrie(); - g_hWeaponReplacementPlayerClasses = CreateTrie(); + g_hWeaponRemovals = new ArrayList(); + g_hPropWeaponRemovals = new ArrayList(); + g_hWeaponNerfs = new StringMap(); + g_hWeaponSelfDamage = new StringMap(); + g_hWeaponStripAttribs = new ArrayList(); + g_hWeaponAddAttribs = new StringMap(); + g_hWeaponReplacements = new StringMap(); + g_hWeaponReplacementPlayerClasses = new StringMap(); - Format(g_Version, sizeof(g_Version), "%s%s", PL_VERSION, statsbool ? "s":""); // PropHunt Redux now lies and pretends to be PropHunt as well - CreateConVar("sm_prophunt_version", g_Version, "PropHunt Version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD); - CreateConVar("prophunt_redux_version", g_Version, "PropHunt Redux Version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD); + CreateConVar("sm_prophunt_version", PL_VERSION, "PropHunt Version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD); + CreateConVar("prophunt_redux_version", PL_VERSION, "PropHunt Redux Version", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD); -// g_PHAdmFlag = CreateConVar("ph_propmenu_flag", "c", "Flag to use for the PropMenu"); g_PHEnable = CreateConVar("ph_enable", "1", "Enables the plugin", FCVAR_DONTRECORD); g_PHPropMenu = CreateConVar("ph_propmenu", "0", "Control use of the propmenu command: -1 = Disabled, 0 = admins or people with the propmenu override, 1 = all players", _, true, -1.0, true, 1.0); g_PHPropMenuRestrict = CreateConVar("ph_propmenurestrict", "0", "If ph_propmenu is allowed, restrict typed props to the propmenu list? Defaults to 0 (no).", _, true, 0.0, true, 1.0); g_PHAdvertisements = CreateConVar("ph_adtext", g_AdText, "Controls the text used for Advertisements"); g_PHPreventFallDamage = CreateConVar("ph_preventfalldamage", "0", "Set to 1 to prevent fall damage. Will use TF2Attributes if available due to client prediction", _, true, 0.0, true, 1.0); - g_PHGameDescription = CreateConVar("ph_gamedescription", "1", "If SteamTools is loaded, set the Game Description to PropHunt Redux?", _, true, 0.0, true, 1.0); + g_PHGameDescription = CreateConVar("ph_gamedescription", "1", "If SteamTools/SteamWorks is loaded, set the Game Description to PropHunt Redux?", _, true, 0.0, true, 1.0); g_PHAirblast = CreateConVar("ph_airblast", "0", "Allow Pyros to airblast? Takes effect on round change unless TF2Attributes is installed.", _, true, 0.0, true, 1.0); g_PHAntiHack = CreateConVar("ph_antihack", "1", "Make sure props don't have weapons. Leave this on unless you're having issues with other plugins.", _, true, 0.0, true, 1.0); g_PHReroll = CreateConVar("ph_propreroll", "0", "Control use of the propreroll command: -1 = Disabled, 0 = admins or people with the propreroll override, 1 = all players", _, true, -1.0, true, 1.0); @@ -577,6 +550,9 @@ public OnPluginStart() g_PHDamageBlocksPropChange = CreateConVar("ph_burningblockspropchange", "1", "Block Prop Change while players are bleeding, jarated, or on fire? (Fixes bugs)", _, true, 0.0, true, 1.0); g_PHPropMenuNames = CreateConVar("ph_propmenuusenames", "0", "Use names for Prop Menu? This is disabled by default for compatibility reasons.", _, true, 0.0, true, 1.0); g_PHMultilingual = CreateConVar("ph_multilingual", "0", "Use multilingual support? Uses more Handles if enabled. Disabled by default as we have no alternate languages (yet)", _, true, 0.0, true, 1.0); + g_PHRespawnDuringSetup = CreateConVar("ph_respawnduringsetup", "1", "If a player dies during setup, should we respawn them?", _, true, 0.0, true, 1.0); + g_PHUseUpdater = CreateConVar("ph_useupdater", "1", "Use Updater to keep PropHunt Redux up to date? Only applies if the Updater plugin is installed.", _, true, 0.0, true, 1.0); + g_PHAllowTaunts = CreateConVar("ph_allowproptaunts", "0", "Allow props to use taunt items", _, true, 0.0, true, 1.0); // These are expensive and should be done just once at plugin start. g_hArenaRoundTime = FindConVar("tf_arena_round_time"); @@ -596,19 +572,25 @@ public OnPluginStart() g_hShowVoiceIcons = FindConVar("mp_show_voice_icons"); g_hSolidObjects = FindConVar("tf_solidobjects"); g_hArenaPreroundTime = FindConVar("tf_arena_preround_time"); + g_hArenaFirstBlood = FindConVar("tf_arena_first_blood"); + + g_hWeaponDropTime = FindConVar("tf_dropped_weapon_lifetime"); +#if !defined SWITCH_TEAMS g_hBonusRoundTime = FindConVar("mp_bonusroundtime"); +#endif + + g_hTags = FindConVar("sv_tags"); - HookConVarChange(g_PHEnable, OnEnabledChanged); - HookConVarChange(g_PHAdvertisements, OnAdTextChanged); - HookConVarChange(g_PHGameDescription, OnGameDescriptionChanged); - HookConVarChange(g_PHAntiHack, OnAntiHackChanged); - HookConVarChange(g_PHStaticPropInfo, OnAntiHackChanged); - HookConVarChange(g_PHAirblast, OnAirblastChanged); - HookConVarChange(g_PHPreventFallDamage, OnFallDamageChanged); - HookConVarChange(g_PHPropMenu, OnPropMenuChanged); - HookConVarChange(g_PHReroll, OnPropRerollChanged); - HookConVarChange(g_PHMultilingual, OnMultilingualChanged); + g_PHEnable.AddChangeHook(OnEnabledChanged); + g_PHAdvertisements.AddChangeHook(OnAdTextChanged); + g_PHGameDescription.AddChangeHook(OnGameDescriptionChanged); + g_PHAntiHack.AddChangeHook(OnAntiHackChanged); + g_PHStaticPropInfo.AddChangeHook(OnAntiHackChanged); + g_PHPropMenu.AddChangeHook(OnPropMenuChanged); + g_PHReroll.AddChangeHook(OnPropRerollChanged); + g_PHMultilingual.AddChangeHook(OnMultilingualChanged); + g_PHUseUpdater.AddChangeHook(OnUseUpdaterChanged); g_Text1 = CreateHudSynchronizer(); g_Text2 = CreateHudSynchronizer(); @@ -617,6 +599,7 @@ public OnPluginStart() AddServerTag("PropHunt"); + // Events HookEvent("player_spawn", Event_player_spawn); HookEvent("player_team", Event_player_team); HookEvent("player_death", Event_player_death, EventHookMode_Pre); @@ -624,18 +607,11 @@ public OnPluginStart() HookEvent("arena_win_panel", Event_arena_win_panel); HookEvent("post_inventory_application", Event_post_inventory_application); HookEvent("teamplay_broadcast_audio", Event_teamplay_broadcast_audio, EventHookMode_Pre); + HookEvent("teamplay_round_start", Event_teamplay_round_start_pre, EventHookMode_Pre); HookEvent("teamplay_round_start", Event_teamplay_round_start); HookEvent("teamplay_restart_round", Event_teamplay_restart_round); //HookEvent("teamplay_setup_finished", Event_teamplay_setup_finished); // No longer used since 2.0.3 or so because of issues with certain maps -#if defined STATS - Stats_Init(); -#endif - -#if defined LOCALSTATS - LocalStats_Init(); -#endif - RegConsoleCmd("help", Command_motd); RegConsoleCmd("phstats", Command_motd); RegConsoleCmd("ph_stats", Command_motd); @@ -653,9 +629,6 @@ public OnPluginStart() LoadTranslations("prophunt.phrases"); LoadTranslations("common.phrases"); - //g_oFOV = FindSendPropOffs("CBasePlayer", "m_iFOV"); - //g_oDefFOV = FindSendPropOffs("CBasePlayer", "m_iDefaultFOV"); - g_Sounds = CreateTrie(); g_BroadcastSounds = CreateTrie(); @@ -668,22 +641,11 @@ public OnPluginStart() RegAdminCmd("ph_pyro", Command_pyro, ADMFLAG_BAN, "Switches to BLU"); RegAdminCmd("ph_reloadconfig", Command_ReloadConfig, ADMFLAG_BAN, "Reloads the PropHunt configuration"); - //if((g_iVelocity = FindSendPropOffs("CBasePlayer", "m_vecVelocity[0]")) == -1) - //LogError("Could not find offset for CBasePlayer::m_vecVelocity[0]"); - - //CreateTimer(7.0, Timer_AntiHack, 0, TIMER_REPEAT); - //CreateTimer(0.6, Timer_Locked, 0, TIMER_REPEAT); - //CreateTimer(55.0, Timer_Score, 0, TIMER_REPEAT); - - - for(new client=1; client <= MaxClients; client++) + for(int client = 1; client <= MaxClients; client++) { if(IsClientInGame(client)) { ForcePlayerSuicide(client); -#if defined STATS || defined LOCALSTATS - OnClientPostAdminCheck(client); -#endif } } g_PropData = CreateTrie(); @@ -711,33 +673,22 @@ public OnPluginStart() AddMenuItem(g_ConfigMenu, "#propreroll", "PropReroll"); AddMenuItem(g_ConfigMenu, "#preventfalldamage", "Prevent Fall Damage"); AddMenuItem(g_ConfigMenu, "#setuptime", "Setup Time"); -#if defined STATS - AddMenuItem(g_ConfigMenu, "#stats", "Stats"); -#endif -} -// Unfortunately, until we rewrite stats2.inc, this check is going to cause problems. -/* -public OnClientPostAdminCheck(client) -{ -#if defined LOCALSTATS - LocalStats_OnClientPostAdminCheck(client); -#endif + g_GameDescriptionForward = CreateGlobalForward("PropHuntRedux_UpdateGameDescription", ET_Event, Param_String); } -*/ -ReadCommonPropData(bool:onlyLanguageRefresh = false) +void ReadCommonPropData(bool onlyLanguageRefresh = false) { - decl String:Path[PLATFORM_MAX_PATH]; + char Path[PLATFORM_MAX_PATH]; BuildPath(Path_SM, Path, sizeof(Path), "data/prophunt/prop_common.txt"); - new Handle:propCommon = CreateKeyValues("propcommon"); - if (!FileToKeyValues(propCommon, Path)) + KeyValues propCommon = CreateKeyValues("propcommon"); + if (!propCommon.ImportFromFile(Path)) { LogError("Could not load the g_PropData file!"); return; } - if (!KvGotoFirstSubKey(propCommon)) + if (!propCommon.GotoFirstSubKey()) { LogError("Prop Common file is empty!"); return; @@ -747,26 +698,26 @@ ReadCommonPropData(bool:onlyLanguageRefresh = false) if (!onlyLanguageRefresh) { - ClearTrie(g_PropData); + g_PropData.Clear(); } - ClearArray(g_ModelLanguages); + g_ModelLanguages.Clear(); - new counter = 0; + int counter = 0; do { counter++; - decl String:modelPath[PLATFORM_MAX_PATH]; + char modelPath[PLATFORM_MAX_PATH]; - new propData[PropData]; + int propData[PropData]; - KvGetSectionName(propCommon, modelPath, PLATFORM_MAX_PATH); - KvGetString(propCommon, "name", propData[PropData_Name], sizeof(propData[PropData_Name]), ""); // Still around for compat reasons + propCommon.GetSectionName(modelPath, sizeof(modelPath)); + propCommon.GetString("name", propData[PropData_Name], sizeof(propData[PropData_Name]), ""); // Still around for compat reasons if (strlen(propData[PropData_Name]) == 0) { - KvGetString(propCommon, "en", propData[PropData_Name], sizeof(propData[PropData_Name]), ""); // Default this to English otherwise + propCommon.GetString("en", propData[PropData_Name], sizeof(propData[PropData_Name]), ""); // Default this to English otherwise } - KvGetString(propCommon, "offset", propData[PropData_Offset], sizeof(propData[PropData_Offset]), "0 0 0"); - KvGetString(propCommon, "rotation", propData[PropData_Rotation], sizeof(propData[PropData_Rotation]), "0 0 0"); + propCommon.GetString("offset", propData[PropData_Offset], sizeof(propData[PropData_Offset]), "0 0 0"); + propCommon.GetString("rotation", propData[PropData_Rotation], sizeof(propData[PropData_Rotation]), "0 0 0"); if (strlen(propData[PropData_Name]) == 0) { @@ -776,24 +727,24 @@ ReadCommonPropData(bool:onlyLanguageRefresh = false) if (!onlyLanguageRefresh) { - if (!SetTrieArray(g_PropData, modelPath, propData[0], sizeof(propData), false)) + if (!g_PropData.SetArray(modelPath, propData[0], sizeof(propData), false)) { - LogError("Error saving prop data for %s", modelPath); + LogError("Error saving prop data for %s, probably a duplicate prop in data/prophunt/prop_common.txt", modelPath); continue; } } - if (GetConVarBool(g_PHMultilingual)) + if (g_PHMultilingual.BoolValue) { - new Handle:languageTrie = CreateTrie(); + StringMap languageTrie = CreateTrie(); - for (new i=0;i 0) { //language new? - if (FindStringInArray(g_ModelLanguages, lang) == -1) + if (g_ModelLanguages.FindString(lang) == -1) { #if defined LOG LogMessage("[PH] Adding language \"%s\" to languages list", lang); #endif - PushArrayString(g_ModelLanguages, lang); + g_ModelLanguages.PushString(lang); } - SetTrieString(languageTrie, lang, name); + languageTrie.SetString(lang, name); } } - if (!SetTrieValue(g_PropNames, modelPath, languageTrie, false)) + if (!g_PropNames.SetValue(modelPath, languageTrie, false)) { LogError("Error saving prop names for %s", modelPath); } else { - PushArrayString(g_PropNamesIndex, modelPath); + g_PropNamesIndex.PushString(modelPath); } } - } while (KvGotoNextKey(propCommon)); + } while (propCommon.GotoNextKey()); - CloseHandle(propCommon); + delete propCommon; LogMessage("Loaded %d props from props_common.txt", counter); #if defined LOG - LogMessage("[PH] Loaded %d language(s)", GetArraySize(g_ModelLanguages)); + LogMessage("[PH] Loaded %d language(s)", g_ModelLanguages.Length); #endif } -ClearPropNames() +void ClearPropNames() { - new arraySize = GetArraySize(g_PropNamesIndex); - for (new i = 0; i < arraySize; i++) + int arraySize = g_PropNamesIndex.Length; + for (int i = 0; i < arraySize; i++) { - decl String:modelName[PLATFORM_MAX_PATH]; - new Handle:languageTrie = INVALID_HANDLE; - GetArrayString(g_PropNamesIndex, i, modelName, sizeof(modelName)); - if (GetTrieValue(g_PropNames, modelName, languageTrie) && languageTrie != INVALID_HANDLE) + char modelName[PLATFORM_MAX_PATH]; + StringMap languageTrie; + g_PropNamesIndex.GetString(i, modelName, sizeof(modelName)); + if (g_PropNames.GetValue(modelName, languageTrie) && languageTrie != null) { - CloseHandle(languageTrie); + delete languageTrie; } } - ClearTrie(g_PropNames); - ClearArray(g_PropNamesIndex); + g_PropNames.Clear(); + g_PropNamesIndex.Clear(); } -public OnAllPluginsLoaded() +public void OnAllPluginsLoaded() { g_SteamTools = LibraryExists("SteamTools"); - if (g_SteamTools) + g_SteamWorks = LibraryExists("SteamWorks"); + if (g_SteamTools || g_SteamWorks) { #if defined LOG - LogMessage("[PH] Found SteamTools on startup."); + if (g_SteamTools) + LogMessage("[PH] Found SteamTools on startup."); + + if (g_SteamWorks) + LogMessage("[PH] Found SteamWorks on startup."); #endif UpdateGameDescription(); } + #if defined OIMM g_OptinMultiMod = LibraryExists("optin_multimod"); if (g_OptinMultiMod) @@ -875,111 +832,21 @@ public OnAllPluginsLoaded() } #endif - g_TF2Attribs = LibraryExists("tf2attributes"); - -#if defined LOG - if (g_TF2Attribs) - { - LogMessage("[PH] Found TF2Attributes on startup."); - } -#endif - -#if defined DHOOKS - g_DHooks = LibraryExists("dhooks"); - - if (g_DHooks) - { -#if defined LOG - LogMessage("[PH] Found DHooks on startup."); -#endif - InitializeDHooks(); - } - -#endif -} - -#if defined DHOOKS -public Action:LateLoadDHooks(Handle:timer) -{ - if (!g_DHooks) - return Plugin_Stop; - - LogMessage("[PH] DHooks Loaded "); - - g_DHooks = true; - InitializeDHooks(); - - if (g_Enabled && g_MapStarted) - RegisterDHooks(); - - return Plugin_Stop; -} - -InitializeDHooks() -{ - if (!g_DHooks || g_SetWinningTeamOffset == -1) - return; - -#if defined LOG - LogMessage("[PH] Creating SetWinningTeam hook using offset %d", g_SetWinningTeamOffset); -#endif - hWinning = DHookCreate(g_SetWinningTeamOffset, HookType_GameRules, ReturnType_Void, ThisPointer_Ignore, ForceSwitchTeams); - DHookAddParam(hWinning, HookParamType_Int); - DHookAddParam(hWinning, HookParamType_Int); - DHookAddParam(hWinning, HookParamType_Bool); - DHookAddParam(hWinning, HookParamType_Bool); - DHookAddParam(hWinning, HookParamType_Bool); - DHookAddParam(hWinning, HookParamType_Bool); -} - -RegisterDHooks() -{ - if (!g_DHooks || g_SetWinningTeamHook > -1) - return; + g_Updater = LibraryExists("updater"); - g_SetWinningTeamHook = DHookGamerules(hWinning, false, UnloadForceSwitchTeamsHook); - -#if defined LOG - LogMessage("[PH] Hooking Gamerules SetWinningTeam team switch override using hookid %d", g_SetWinningTeamHook); -#endif -} - -UnregisterDHooks() -{ - if (!g_DHooks || g_SetWinningTeamHook == -1) - return; - - DHookRemoveHookID(g_SetWinningTeamHook); #if defined LOG - LogMessage("[PH] Manually unhooking Gamerules SetWinningTeam team switch override from hookid %d", g_SetWinningTeamHook); -#endif - g_SetWinningTeamHook = -1; -} - -// virtual void SetWinningTeam( int team, int iWinReason, bool bForceMapReset = true, bool bSwitchTeams = false, bool bDontAddScore = false ); -public MRESReturn:ForceSwitchTeams(Handle:hParams) -{ - if (ShouldSwitchTeams()) + if (g_Updater) { - // params are 1-based - DHookSetParam(hParams, 4, true); - return MRES_ChangedHandled; + LogMessage("[PH] Found Updater on startup."); } - return MRES_Ignored; -} - -public UnloadForceSwitchTeamsHook(hookid) -{ - g_SetWinningTeamHook = -1; -#if defined LOG - LogMessage("[PH] Automatically unhooking Gamerules SetWinningTeam team switch override from hookid %d", hookid); #endif } -#endif -bool:ShouldSwitchTeams() +// Should we switch teams this round? +// Note: Don't confuse this with the games ShouldSwitchTeams +bool Internal_ShouldSwitchTeams() { - new bool:lastRound = (g_RoundCurrent == g_RoundCount); + bool lastRound = (g_RoundCurrent == g_RoundCount); if (lastRound) { #if defined LOG @@ -1000,16 +867,16 @@ bool:ShouldSwitchTeams() return false; } -loadGlobalConfig() +void loadGlobalConfig() { - decl String:Path[PLATFORM_MAX_PATH]; + char Path[PLATFORM_MAX_PATH]; BuildPath(Path_SM, Path, sizeof(Path), "data/prophunt/prophunt_config.cfg"); - if (g_ConfigKeyValues != INVALID_HANDLE) + if (g_ConfigKeyValues != null) { - CloseHandle(g_ConfigKeyValues); + delete g_ConfigKeyValues; } g_ConfigKeyValues = CreateKeyValues("prophunt_config"); - if (!FileToKeyValues(g_ConfigKeyValues, Path)) + if (!g_ConfigKeyValues.ImportFromFile(Path)) { LogError("Could not load the PropHunt config file!"); } @@ -1021,7 +888,7 @@ loadGlobalConfig() ReadCommonPropData(false); } -public OnLibraryAdded(const String:name[]) +public void OnLibraryAdded(const char[] name) { if (StrEqual(name, "SteamTools", false)) { @@ -1031,6 +898,15 @@ public OnLibraryAdded(const String:name[]) g_SteamTools = true; UpdateGameDescription(); } + else + if (StrEqual(name, "SteamWorks", false)) + { +#if defined LOG + LogMessage("[PH] SteamWorks Loaded "); +#endif + g_SteamWorks = true; + UpdateGameDescription(); + } #if defined OIMM else if (StrEqual(name, "optin_multimod", false)) @@ -1042,23 +918,17 @@ public OnLibraryAdded(const String:name[]) } #endif else - if (StrEqual(name, "tf2attributes", false)) + if (StrEqual(name, "updater", false)) { #if defined LOG - LogMessage("[PH] TF2Attributes Loaded "); + LogMessage("[PH] Updater Loaded."); #endif - g_TF2Attribs = true; - } -#if defined DHOOKS - else - if (StrEqual(name, "dhooks", false)) - { - CreateTimer(0.5, LateLoadDHooks); + g_Updater = true; + Updater_AddPlugin(UPDATE_URL); } -#endif } -public OnLibraryRemoved(const String:name[]) +public void OnLibraryRemoved(const char[] name) { if (StrEqual(name, "SteamTools", false)) { @@ -1067,121 +937,85 @@ public OnLibraryRemoved(const String:name[]) #endif g_SteamTools = false; } -#if defined OIMM else - if (StrEqual(name, "optin_multimod", false)) + if (StrEqual(name, "SteamWorks", false)) { #if defined LOG - LogMessage("[PH] Opt-In Multimod Unloaded "); + LogMessage("[PH] SteamWorks Unloaded "); #endif - g_OptinMultiMod = false; + g_SteamWorks = false; } -#endif +#if defined OIMM else - if (StrEqual(name, "tf2attributes", false)) + if (StrEqual(name, "optin_multimod", false)) { #if defined LOG - LogMessage("[PH] TF2Attributes Unloaded "); + LogMessage("[PH] Opt-In Multimod Unloaded "); #endif - g_TF2Attribs = false; + g_OptinMultiMod = false; } -#if defined DHOOKS +#endif else - if (StrEqual(name, "dhooks", false)) + if (StrEqual(name, "updater", false)) { #if defined LOG - LogMessage("[PH] DHooks Unloaded "); -#endif - - g_DHooks = false; - g_SetWinningTeamHook = -1; - hWinning = INVALID_HANDLE; // Don't attempt to close this Handle as the extension unloader will have already done so - } + LogMessage("[PH] Updater Unloaded."); #endif + g_Updater = false; + } } -public OnGameDescriptionChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnGameDescriptionChanged(ConVar convar, const char[] oldValue, const char[] newValue) { UpdateGameDescription(); } -public OnAntiHackChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnAntiHackChanged(ConVar convar, const char[] oldValue, const char[] newValue) { if (!g_Enabled) return; - if ((GetConVarBool(g_PHAntiHack) || GetConVarBool(g_PHStaticPropInfo)) && g_hAntiHack == INVALID_HANDLE) + if (g_PHAntiHack.BoolValue || g_PHStaticPropInfo.BoolValue && g_hAntiHack == null) { g_hAntiHack = CreateTimer(7.0, Timer_AntiHack, _, TIMER_REPEAT); // Also run said timer 0.1 seconds after round start. CreateTimer(0.1, Timer_AntiHack, _, TIMER_FLAG_NO_MAPCHANGE); } - else if (!GetConVarBool(g_PHAntiHack) && !GetConVarBool(g_PHStaticPropInfo) && g_hAntiHack != INVALID_HANDLE) - { - CloseHandle(g_hAntiHack); - g_hAntiHack = INVALID_HANDLE; - } -} - -public OnAirblastChanged(Handle:convar, const String:oldValue[], const String:newValue[]) -{ - if (!g_Enabled || !g_TF2Attribs) - return; - - new bool:airblast = GetConVarBool(g_PHAirblast); - - new flamethrower = -1; - - while ((flamethrower = FindEntityByClassname(flamethrower, "tf_weapon_flamethrower")) != -1) - { - new iItemDefinitionIndex = GetEntProp(flamethrower, Prop_Send, "m_iItemDefinitionIndex"); - if (iItemDefinitionIndex != WEP_PHLOGISTINATOR || FindValueInArray(g_hWeaponStripAttribs, WEP_PHLOGISTINATOR) >= 0) - { - if (airblast) - { - TF2Attrib_RemoveByName(flamethrower, "airblast disabled"); - } - else - { - TF2Attrib_SetByName(flamethrower, "airblast disabled", 1.0); - } - } - } -} - -public OnFallDamageChanged(Handle:convar, const String:oldValue[], const String:newValue[]) -{ - if (!g_Enabled || !g_TF2Attribs) - return; - - new Float:fall = GetConVarFloat(g_PHPreventFallDamage); - - for (new i = 1; i <= MaxClients; ++i) + else if (!g_PHAntiHack.BoolValue && !g_PHStaticPropInfo.BoolValue && g_hAntiHack != null) { - if (IsClientInGame(i)) - { - TF2Attrib_SetByName(i, "cancel falling damage", fall); - } + delete g_hAntiHack; } } -UpdateGameDescription(bool:bAddOnly=false) +void UpdateGameDescription(bool bAddOnly=false) { - if (!g_SteamTools) + if (!g_SteamTools && !g_SteamWorks) { return; } - decl String:gamemode[128]; - if (g_Enabled && GetConVarBool(g_PHGameDescription)) + char gamemode[128]; + if (g_Enabled && g_PHGameDescription.BoolValue) { if (strlen(g_AdText) > 0) { - Format(gamemode, sizeof(gamemode), "PropHunt Redux %s (%s)", g_Version, g_AdText); + Format(gamemode, sizeof(gamemode), "PropHunt Redux %s (%s)", PL_VERSION, g_AdText); } else { - Format(gamemode, sizeof(gamemode), "PropHunt Redux %s", g_Version); + Format(gamemode, sizeof(gamemode), "PropHunt Redux %s", PL_VERSION); + } + + // Global forward for subplugins to change the game description. + Action result = Plugin_Continue; + char tempGamemode[128]; + strcopy(tempGamemode, sizeof(tempGamemode), gamemode); + Call_StartForward(g_GameDescriptionForward); + Call_PushStringEx(tempGamemode, sizeof(tempGamemode), SM_PARAM_COPYBACK, SM_PARAM_STRING_COPY); + Call_Finish(result); + if (result == Plugin_Changed) + { + strcopy(gamemode, sizeof(gamemode), tempGamemode); } } else if (bAddOnly) @@ -1194,96 +1028,105 @@ UpdateGameDescription(bool:bAddOnly=false) strcopy(gamemode, sizeof(gamemode), "Team Fortress"); } - Steam_SetGameDescription(gamemode); + + + if (g_SteamTools) + { + Steam_SetGameDescription(gamemode); + } + else + if (g_SteamWorks) + { + SteamWorks_SetGameDescription(gamemode); + } } -config_parseWeapons() +void config_parseWeapons() { - /* - for(new i = 0; i < MAXITEMS; i++) - { - g_weaponNerfs[i] = 1.0; - g_weaponSelfDamage[i] = 10.0; - g_weaponRemovals[i] = false; - } - */ - - ClearArray(g_hWeaponRemovals); - ClearTrie(g_hWeaponNerfs); - ClearTrie(g_hWeaponSelfDamage); - ClearArray(g_hWeaponStripAttribs); - ClearTrie(g_hWeaponAddAttribs); - ClearTrie(g_hWeaponReplacements); - ClearTrie(g_hWeaponReplacementPlayerClasses); + g_hWeaponRemovals.Clear(); + g_hPropWeaponRemovals.Clear(); + g_hWeaponNerfs.Clear(); + g_hWeaponSelfDamage.Clear(); + g_hWeaponStripAttribs.Clear(); + g_hWeaponAddAttribs.Clear(); + g_hWeaponReplacements.Clear(); + g_hWeaponReplacementPlayerClasses.Clear(); - if (g_ConfigKeyValues == INVALID_HANDLE) + if (g_ConfigKeyValues == null) { return; } - while(KvGoBack(g_ConfigKeyValues)) + while(g_ConfigKeyValues.GoBack()) { continue; } - if(KvJumpToKey(g_ConfigKeyValues, "items")) + if(g_ConfigKeyValues.JumpToKey("items")) { do { - decl String:SectionName[128]; - KvGotoFirstSubKey(g_ConfigKeyValues); - KvGetSectionName(g_ConfigKeyValues, SectionName, sizeof(SectionName)); - if(KvGetDataType(g_ConfigKeyValues, "damage_hunters") == KvData_Float) + char SectionName[128]; + g_ConfigKeyValues.GotoFirstSubKey(); + g_ConfigKeyValues.GetSectionName(SectionName, sizeof(SectionName)); + if(g_ConfigKeyValues.GetDataType("damage_hunters") == KvData_Float) { - SetTrieValue(g_hWeaponNerfs, SectionName, KvGetFloat(g_ConfigKeyValues, "damage_hunters")); + g_hWeaponNerfs.SetValue(SectionName, g_ConfigKeyValues.GetFloat("damage_hunters")); } - if(KvGetDataType(g_ConfigKeyValues, "removed_hunters") == KvData_Int) + if(g_ConfigKeyValues.GetDataType("removed_hunters") == KvData_Int) { - if (bool:KvGetNum(g_ConfigKeyValues, "removed_hunters")) + if (g_ConfigKeyValues.GetNum("removed_hunters")) { - PushArrayCell(g_hWeaponRemovals, StringToInt(SectionName)); + g_hWeaponRemovals.Push(StringToInt(SectionName)); } } - if(KvGetDataType(g_ConfigKeyValues, "self_damage_hunters") == KvData_Float) + if(g_ConfigKeyValues.GetDataType("removed_props") == KvData_Int) { - SetTrieValue(g_hWeaponSelfDamage, SectionName, KvGetFloat(g_ConfigKeyValues, "self_damage_hunters")); + if (g_ConfigKeyValues.GetNum("removed_props")) + { + g_hPropWeaponRemovals.Push(StringToInt(SectionName)); + } + } + if(g_ConfigKeyValues.GetDataType("self_damage_hunters") == KvData_Float) + { + g_hWeaponSelfDamage.SetValue(SectionName, KvGetFloat(g_ConfigKeyValues, "self_damage_hunters")); } - if(KvGetDataType(g_ConfigKeyValues, "stripattribs") == KvData_Int) + if(g_ConfigKeyValues.GetDataType("stripattribs") == KvData_Int) { - if (bool:KvGetNum(g_ConfigKeyValues, "stripattribs")) + if (g_ConfigKeyValues.GetNum("stripattribs")) { - PushArrayCell(g_hWeaponStripAttribs, StringToInt(SectionName)); + g_hWeaponStripAttribs.Push(StringToInt(SectionName)); } } - if(KvGetDataType(g_ConfigKeyValues, "addattribs") == KvData_String) + if(g_ConfigKeyValues.GetDataType("addattribs") == KvData_String) { - new String:attribs[128]; - KvGetString(g_ConfigKeyValues, "addattribs", attribs, sizeof(attribs)); + char attribs[128]; + g_ConfigKeyValues.GetString("addattribs", attribs, sizeof(attribs)); if (attribs[0] != '\0') { - SetTrieString(g_hWeaponAddAttribs, SectionName, attribs); + g_hWeaponAddAttribs.SetString(SectionName, attribs); } } - if(KvGetDataType(g_ConfigKeyValues, "replace") == KvData_String) + if(g_ConfigKeyValues.GetDataType("replace") == KvData_String) { - new String:attribs[128]; - KvGetString(g_ConfigKeyValues, "replace", attribs, sizeof(attribs)); + char attribs[128]; + g_ConfigKeyValues.GetString("replace", attribs, sizeof(attribs)); - new class = KvGetNum(g_ConfigKeyValues, "replace_onlyclasses", TFClassBits_None); + int class = g_ConfigKeyValues.GetNum("replace_onlyclasses", TFClassBits_None); if (attribs[0] != '\0') { - SetTrieString(g_hWeaponReplacements, SectionName, attribs); + g_hWeaponReplacements.SetString(SectionName, attribs); } if (class != TFClassBits_None) { - SetTrieValue(g_hWeaponReplacementPlayerClasses, SectionName, class); + g_hWeaponReplacementPlayerClasses.SetValue(SectionName, class); } } } - while(KvGotoNextKey(g_ConfigKeyValues)); + while(g_ConfigKeyValues.GotoNextKey()); } else { @@ -1291,14 +1134,14 @@ config_parseWeapons() } } -config_parseClasses() +void config_parseClasses() { - new red = TEAM_PROP-2; - new blue = TEAM_HUNTER-2; + int red = TEAM_PROP-2; + int blue = TEAM_HUNTER-2; g_defaultClass[red] = TFClass_Scout; g_defaultClass[blue] = TFClass_Pyro; - for(new i = 0; i < 10; i++) + for(int i = 0; i < 10; i++) { g_classLimits[blue][i] = -1; g_classLimits[red][i] = -1; @@ -1307,54 +1150,54 @@ config_parseClasses() g_classSpeeds[i][2] = 15.0; } - if (g_ConfigKeyValues == INVALID_HANDLE) + if (g_ConfigKeyValues == null) { return; } - while(KvGoBack(g_ConfigKeyValues)) + while(g_ConfigKeyValues.GoBack()) { continue; } - if(KvJumpToKey(g_ConfigKeyValues, "classes")) + if(g_ConfigKeyValues.JumpToKey("classes")) { do { - decl String:SectionName[128]; - KvGotoFirstSubKey(g_ConfigKeyValues); - KvGetSectionName(g_ConfigKeyValues, SectionName, sizeof(SectionName)); - if(KvGetDataType(g_ConfigKeyValues, "hunter_limit") == KvData_Int) + char SectionName[128]; + g_ConfigKeyValues.GotoFirstSubKey(); + g_ConfigKeyValues.GetSectionName(SectionName, sizeof(SectionName)); + if(g_ConfigKeyValues.GetDataType("hunter_limit") == KvData_Int) { - g_classLimits[blue][StringToInt(SectionName)] = KvGetNum(g_ConfigKeyValues, "hunter_limit"); + g_classLimits[blue][StringToInt(SectionName)] = g_ConfigKeyValues.GetNum("hunter_limit"); } - if(KvGetDataType(g_ConfigKeyValues, "prop_limit") == KvData_Int) + if(g_ConfigKeyValues.GetDataType("prop_limit") == KvData_Int) { - g_classLimits[red][StringToInt(SectionName)] = KvGetNum(g_ConfigKeyValues, "prop_limit"); + g_classLimits[red][StringToInt(SectionName)] = g_ConfigKeyValues.GetNum("prop_limit"); } - if(KvGetDataType(g_ConfigKeyValues, "hunter_default_class") == KvData_Int) + if(g_ConfigKeyValues.GetDataType("hunter_default_class") == KvData_Int) { - g_defaultClass[blue] = TFClassType:StringToInt(SectionName); + g_defaultClass[blue] = view_as(StringToInt(SectionName)); } - if(KvGetDataType(g_ConfigKeyValues, "prop_default_class") == KvData_Int) + if(g_ConfigKeyValues.GetDataType("prop_default_class") == KvData_Int) { - g_defaultClass[red] = TFClassType:StringToInt(SectionName); + g_defaultClass[red] = view_as(StringToInt(SectionName)); } - if(KvGetDataType(g_ConfigKeyValues, "base_speed") == KvData_Float) + if(g_ConfigKeyValues.GetDataType("base_speed") == KvData_Float) { - g_classSpeeds[StringToInt(SectionName)][0] = KvGetFloat(g_ConfigKeyValues, "base_speed"); + g_classSpeeds[StringToInt(SectionName)][0] = g_ConfigKeyValues.GetFloat("base_speed"); } - if(KvGetDataType(g_ConfigKeyValues, "max_speed") == KvData_Float) + if(g_ConfigKeyValues.GetDataType("max_speed") == KvData_Float) { - g_classSpeeds[StringToInt(SectionName)][1] = KvGetFloat(g_ConfigKeyValues, "max_speed"); + g_classSpeeds[StringToInt(SectionName)][1] = g_ConfigKeyValues.GetFloat("max_speed"); } - if(KvGetDataType(g_ConfigKeyValues, "speed_increment") == KvData_Float) + if(g_ConfigKeyValues.GetDataType("speed_increment") == KvData_Float) { - g_classSpeeds[StringToInt(SectionName)][2] = KvGetFloat(g_ConfigKeyValues, "speed_increment"); + g_classSpeeds[StringToInt(SectionName)][2] = g_ConfigKeyValues.GetFloat("speed_increment"); } } - while(KvGotoNextKey(g_ConfigKeyValues)); + while(g_ConfigKeyValues.GotoNextKey()); } else { @@ -1362,60 +1205,62 @@ config_parseClasses() } } -config_parseSounds() +void config_parseSounds() { - ClearTrie(g_Sounds); - ClearTrie(g_BroadcastSounds); + g_Sounds.Clear(); + g_BroadcastSounds.Clear(); - if (g_ConfigKeyValues == INVALID_HANDLE) + if (g_ConfigKeyValues == null) { return; } - while(KvGoBack(g_ConfigKeyValues)) + while(g_ConfigKeyValues.GoBack()) { continue; } - if(KvJumpToKey(g_ConfigKeyValues, "sounds")) + if(g_ConfigKeyValues.JumpToKey("sounds")) { do { - decl String:SectionName[128]; - KvGotoFirstSubKey(g_ConfigKeyValues); - KvGetSectionName(g_ConfigKeyValues, SectionName, sizeof(SectionName)); - if(KvGetDataType(g_ConfigKeyValues, "sound") == KvData_String) + char SectionName[128]; + g_ConfigKeyValues.GotoFirstSubKey(); + g_ConfigKeyValues.GetSectionName(SectionName, sizeof(SectionName)); + if(g_ConfigKeyValues.GetDataType("sound") == KvData_String) { - decl String:soundString[PLATFORM_MAX_PATH]; - KvGetString(g_ConfigKeyValues, "sound", soundString, sizeof(soundString)); + char soundString[PLATFORM_MAX_PATH]; + g_ConfigKeyValues.GetString("sound", soundString, sizeof(soundString)); if(PrecacheSound(soundString)) { - decl String:downloadString[PLATFORM_MAX_PATH]; + char downloadString[PLATFORM_MAX_PATH]; Format(downloadString, sizeof(downloadString), "sound/%s", soundString); AddFileToDownloadsTable(downloadString); - SetTrieString(g_Sounds, SectionName, soundString, true); + g_Sounds.SetString(SectionName, soundString, true); } } - if(KvGetDataType(g_ConfigKeyValues, "broadcast") == KvData_String) + if(g_ConfigKeyValues.GetDataType("broadcast") == KvData_String) { - decl String:soundString[128]; - KvGetString(g_ConfigKeyValues, "broadcast", soundString, sizeof(soundString)); + char soundString[128]; + g_ConfigKeyValues.GetString("broadcast", soundString, sizeof(soundString)); PrecacheScriptSound(soundString); - SetTrieString(g_BroadcastSounds, SectionName, soundString, true); + g_BroadcastSounds.SetString(SectionName, soundString, true); } - if(KvGetDataType(g_ConfigKeyValues, "game") == KvData_String) + if(g_ConfigKeyValues.GetDataType("game") == KvData_String) { - decl String:soundString[128]; - KvGetString(g_ConfigKeyValues, "game", soundString, sizeof(soundString)); + char soundString[128]; + g_ConfigKeyValues.GetString("game", soundString, sizeof(soundString)); - SetTrieString(g_BroadcastSounds, SectionName, soundString, true); + PrecacheScriptSound(soundString); + + g_BroadcastSounds.SetString(SectionName, soundString, true); } } - while(KvGotoNextKey(g_ConfigKeyValues)); + while(g_ConfigKeyValues.GotoNextKey()); } else { @@ -1423,115 +1268,144 @@ config_parseSounds() } } -SetCVars(){ +void SetCVars(){ - SetConVarFlags(g_hArenaRoundTime, GetConVarFlags(g_hArenaRoundTime) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hArenaUseQueue, GetConVarFlags(g_hArenaUseQueue) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hArenaMaxStreak, GetConVarFlags(g_hArenaMaxStreak) & ~(FCVAR_NOTIFY)); - //cvar = FindConVar("mp_tournament"); - //SetConVarFlags(cvar, GetConVarFlags(cvar) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hTournamentStopwatch, GetConVarFlags(g_hTournamentStopwatch) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hTournamentHideDominationIcons, GetConVarFlags(g_hTournamentHideDominationIcons) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hTeamsUnbalanceLimit, GetConVarFlags(g_hTeamsUnbalanceLimit) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hArenaPreroundTime, GetConVarFlags(g_hArenaPreroundTime) & ~(FCVAR_NOTIFY)); + g_hArenaRoundTime.Flags = g_hArenaRoundTime.Flags & ~FCVAR_NOTIFY; + g_hArenaUseQueue.Flags = g_hArenaUseQueue.Flags & ~FCVAR_NOTIFY; + g_hArenaMaxStreak.Flags = g_hArenaMaxStreak.Flags & ~FCVAR_NOTIFY; + g_hTournamentStopwatch.Flags = g_hTournamentStopwatch.Flags & ~FCVAR_NOTIFY; + g_hTournamentHideDominationIcons.Flags = g_hTournamentHideDominationIcons.Flags & ~FCVAR_NOTIFY; + g_hTeamsUnbalanceLimit.Flags = g_hTeamsUnbalanceLimit.Flags & ~FCVAR_NOTIFY; + g_hArenaPreroundTime.Flags = g_hArenaPreroundTime.Flags & ~FCVAR_NOTIFY; - g_ArenaRoundTime = GetConVarInt(g_hArenaRoundTime); - SetConVarInt(g_hArenaRoundTime, 0, true); + g_ArenaRoundTime = g_hArenaRoundTime.IntValue; + g_hArenaRoundTime.IntValue = 0; - g_ArenaUseQueue = GetConVarInt(g_hArenaUseQueue); - SetConVarInt(g_hArenaUseQueue, 0, true); + g_ArenaUseQueue = g_hArenaUseQueue.BoolValue; + g_hArenaUseQueue.BoolValue = false; - g_ArenaMaxStreak = GetConVarInt(g_hArenaMaxStreak); - SetConVarInt(g_hArenaMaxStreak, 4, true); + g_ArenaMaxStreak = g_hArenaMaxStreak.IntValue; + g_hArenaMaxStreak.IntValue = 2; - g_TournamentStopwatch = GetConVarInt(g_hTournamentStopwatch); - SetConVarInt(g_hTournamentStopwatch, 0, true); + g_TournamentStopwatch = g_hTournamentStopwatch.BoolValue; + g_hTournamentStopwatch.BoolValue = false; - g_TournamentHideDominationIcons = GetConVarInt(g_hTournamentHideDominationIcons); - SetConVarInt(g_hTournamentHideDominationIcons, 0, true); + g_TournamentHideDominationIcons = g_hTournamentHideDominationIcons.BoolValue; + g_hTournamentHideDominationIcons.BoolValue = true; - g_TeamsUnbalanceLimit = GetConVarInt(g_hTeamsUnbalanceLimit); - SetConVarInt(g_hTeamsUnbalanceLimit, UNBALANCE_LIMIT, true); - - SetConVarBounds(g_hArenaPreroundTime, ConVarBound_Upper, false); - g_ArenaPreroundTime = GetConVarInt(g_hArenaPreroundTime); - SetConVarInt(g_hArenaPreroundTime, IsDedicatedServer() ? 20:5, true); + g_TeamsUnbalanceLimit = g_hTeamsUnbalanceLimit.IntValue; + g_hTeamsUnbalanceLimit.IntValue = UNBALANCE_LIMIT; + + g_hArenaPreroundTime.SetBounds(ConVarBound_Upper, false); + g_ArenaPreroundTime = g_hArenaPreroundTime.IntValue; + g_hArenaPreroundTime.IntValue = IsDedicatedServer() ? 20 : 5; - g_WeaponCriticals = GetConVarInt(g_hWeaponCriticals); - SetConVarInt(g_hWeaponCriticals, 0, true); + g_WeaponCriticals = g_hWeaponCriticals.BoolValue; + g_hWeaponCriticals.BoolValue = false; - g_Idledealmethod = GetConVarInt(g_hIdledealmethod); - SetConVarInt(g_hIdledealmethod, 0, true); + // Idle Deal Method is buggy on Arena sometimes + g_Idledealmethod = g_hIdledealmethod.IntValue; + g_hIdledealmethod.IntValue = 0; - g_Friendlyfire = GetConVarInt(g_hFriendlyfire); - SetConVarInt(g_hFriendlyfire, 0, true); + g_Friendlyfire = g_hFriendlyfire.BoolValue; + g_hFriendlyfire.BoolValue = false; - g_Gravity = GetConVarInt(g_hGravity); - SetConVarInt(g_hGravity, 500, true); + // Lower gravity to 500 for PropHunt + g_Gravity = g_hGravity.IntValue; + g_hGravity.IntValue = 500; - g_Forcecamera = GetConVarInt(g_hForcecamera); - SetConVarInt(g_hForcecamera, 1, true); + g_Forcecamera = g_hForcecamera.IntValue; + g_hForcecamera.IntValue = 1; - g_ArenaCapEnableTime = GetConVarInt(g_hArenaCapEnableTime); - SetConVarInt(g_hArenaCapEnableTime, 3600, true); // Set really high + g_ArenaCapEnableTime = g_hArenaCapEnableTime.IntValue; + g_hArenaCapEnableTime.IntValue = 3600; // Set really high - g_EnableRoundWaitTime = GetConVarInt(g_hEnableRoundWaitTime); - SetConVarInt(g_hEnableRoundWaitTime, 0, true); + g_EnableRoundWaitTime = g_hEnableRoundWaitTime.BoolValue; + g_hEnableRoundWaitTime.BoolValue = false; - g_WaitingForPlayerTime = GetConVarInt(g_hWaitingForPlayerTime); - SetConVarInt(g_hWaitingForPlayerTime, 40, true); + g_WaitingForPlayerTime = g_hWaitingForPlayerTime.IntValue; + g_hWaitingForPlayerTime.IntValue = 40; - g_ShowVoiceIcons = GetConVarInt(g_hShowVoiceIcons); - SetConVarInt(g_hShowVoiceIcons, 0, true); + g_ShowVoiceIcons = g_hShowVoiceIcons.BoolValue; + g_hShowVoiceIcons.BoolValue = false; - g_SolidObjects = GetConVarInt(g_hSolidObjects); - SetConVarInt(g_hSolidObjects, 0, true); + g_SolidObjects = g_hSolidObjects.BoolValue; + g_hSolidObjects.BoolValue = false; + + g_ArenaFirstBlood = g_hArenaFirstBlood.BoolValue; + g_hArenaFirstBlood.BoolValue = false; + + // Force weapons to immediately vanish when dropped + g_WeaponDropTime = g_hWeaponDropTime.IntValue; + g_hWeaponDropTime.IntValue = 0; + + g_hArenaRoundTime.Flags = g_hArenaRoundTime.Flags |= FCVAR_NOTIFY; + g_hArenaUseQueue.Flags = g_hArenaUseQueue.Flags |= FCVAR_NOTIFY; + g_hArenaMaxStreak.Flags = g_hArenaMaxStreak.Flags |= FCVAR_NOTIFY; + g_hTournamentStopwatch.Flags = g_hTournamentStopwatch.Flags |= FCVAR_NOTIFY; + g_hTournamentHideDominationIcons.Flags = g_hTournamentHideDominationIcons.Flags |= FCVAR_NOTIFY; + g_hTeamsUnbalanceLimit.Flags = g_hTeamsUnbalanceLimit.Flags |= FCVAR_NOTIFY; + g_hArenaPreroundTime.Flags = g_hArenaPreroundTime.Flags |= FCVAR_NOTIFY; g_CvarsSet = true; } -ResetCVars() +void ResetCVars() { if (!g_CvarsSet) return; - SetConVarFlags(g_hArenaRoundTime, GetConVarFlags(g_hArenaRoundTime) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hArenaUseQueue, GetConVarFlags(g_hArenaUseQueue) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hArenaMaxStreak, GetConVarFlags(g_hArenaMaxStreak) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hTeamsUnbalanceLimit, GetConVarFlags(g_hTeamsUnbalanceLimit) & ~(FCVAR_NOTIFY)); - SetConVarFlags(g_hArenaPreroundTime, GetConVarFlags(g_hArenaPreroundTime) & ~(FCVAR_NOTIFY)); - - SetConVarInt(g_hArenaRoundTime, g_ArenaRoundTime, true); - SetConVarInt(g_hArenaUseQueue, g_ArenaUseQueue, true); - SetConVarInt(g_hArenaMaxStreak, g_ArenaMaxStreak, true); - SetConVarInt(g_hTournamentStopwatch, g_TournamentStopwatch, true); - SetConVarInt(g_hTournamentHideDominationIcons, g_TournamentHideDominationIcons, true); - SetConVarInt(g_hTeamsUnbalanceLimit, g_TeamsUnbalanceLimit, true); - SetConVarInt(g_hArenaPreroundTime, g_ArenaPreroundTime, true); - SetConVarInt(g_hWeaponCriticals, g_WeaponCriticals, true); - SetConVarInt(g_hIdledealmethod, g_Idledealmethod, true); - SetConVarInt(g_hFriendlyfire, g_Friendlyfire, true); - SetConVarInt(g_hGravity, g_Gravity, true); - SetConVarInt(g_hForcecamera, g_Forcecamera, true); - SetConVarInt(g_hArenaCapEnableTime, g_ArenaCapEnableTime, true); - SetConVarInt(g_hEnableRoundWaitTime, g_EnableRoundWaitTime, true); - SetConVarInt(g_hWaitingForPlayerTime, g_WaitingForPlayerTime, true); - SetConVarInt(g_hShowVoiceIcons, g_ShowVoiceIcons, true); - SetConVarInt(g_hSolidObjects, g_SolidObjects, true); + g_hArenaRoundTime.Flags = g_hArenaRoundTime.Flags & ~FCVAR_NOTIFY; + g_hArenaUseQueue.Flags = g_hArenaUseQueue.Flags & ~FCVAR_NOTIFY; + g_hArenaMaxStreak.Flags = g_hArenaMaxStreak.Flags & ~FCVAR_NOTIFY; + g_hTournamentStopwatch.Flags = g_hTournamentStopwatch.Flags & ~FCVAR_NOTIFY; + g_hTournamentHideDominationIcons.Flags = g_hTournamentHideDominationIcons.Flags & ~FCVAR_NOTIFY; + g_hTeamsUnbalanceLimit.Flags = g_hTeamsUnbalanceLimit.Flags & ~FCVAR_NOTIFY; + g_hArenaPreroundTime.Flags = g_hArenaPreroundTime.Flags & ~FCVAR_NOTIFY; + + g_hArenaRoundTime.IntValue = g_ArenaRoundTime; + g_hArenaUseQueue.BoolValue = g_ArenaUseQueue; + g_hArenaMaxStreak.IntValue = g_ArenaMaxStreak; + g_hTournamentStopwatch.BoolValue = g_TournamentStopwatch; + g_hTournamentHideDominationIcons.BoolValue = g_TournamentHideDominationIcons; + g_hTeamsUnbalanceLimit.IntValue = g_TeamsUnbalanceLimit; + g_hArenaPreroundTime.IntValue = g_ArenaPreroundTime; + g_hWeaponCriticals.BoolValue = g_WeaponCriticals; + g_hIdledealmethod.IntValue = g_Idledealmethod; + g_hFriendlyfire.BoolValue = g_Friendlyfire; + g_hGravity.IntValue = g_Gravity; + g_hForcecamera.IntValue = g_Forcecamera; + g_hArenaCapEnableTime.IntValue = g_ArenaCapEnableTime; + g_hEnableRoundWaitTime.BoolValue = g_EnableRoundWaitTime; + g_hWaitingForPlayerTime.IntValue = g_WaitingForPlayerTime; + g_hShowVoiceIcons.BoolValue = g_ShowVoiceIcons; + g_hSolidObjects.BoolValue = g_SolidObjects; + g_hArenaFirstBlood.BoolValue = g_ArenaFirstBlood; + g_hWeaponDropTime.IntValue = g_WeaponDropTime; + + g_hArenaRoundTime.Flags = g_hArenaRoundTime.Flags |= FCVAR_NOTIFY; + g_hArenaUseQueue.Flags = g_hArenaUseQueue.Flags |= FCVAR_NOTIFY; + g_hArenaMaxStreak.Flags = g_hArenaMaxStreak.Flags |= FCVAR_NOTIFY; + g_hTournamentStopwatch.Flags = g_hTournamentStopwatch.Flags |= FCVAR_NOTIFY; + g_hTournamentHideDominationIcons.Flags = g_hTournamentHideDominationIcons.Flags |= FCVAR_NOTIFY; + g_hTeamsUnbalanceLimit.Flags = g_hTeamsUnbalanceLimit.Flags |= FCVAR_NOTIFY; + g_hArenaPreroundTime.Flags = g_hArenaPreroundTime.Flags |= FCVAR_NOTIFY; g_CvarsSet = false; } -public OnConfigsExecuted() +public void OnConfigsExecuted() { - g_Enabled = GetConVarBool(g_PHEnable) && g_PHMap; + g_Enabled = g_PHEnable.BoolValue && g_PHMap; + + g_GameRulesProxy = EntIndexToEntRef(FindEntityByClassname(-1, "tf_gamerules")); - if (GetConVarInt(g_PHPropMenu) == 1 && !g_PropMenuOverrideInstalled) + if (g_PHPropMenu.IntValue == 1 && !g_PropMenuOverrideInstalled) { InstallPropMenuOverride(); } - if (GetConVarInt(g_PHReroll) == 1 && !g_PropRerollOverrideInstalled) + if (g_PHReroll.IntValue == 1 && !g_PropRerollOverrideInstalled) { InstallPropRerollOverride(); } @@ -1539,10 +1413,11 @@ public OnConfigsExecuted() if (g_Enabled) { SetCVars(); -// Moved to round start -//#if defined DHOOKS -// RegisterDHooks(); -//#endif + Internal_AddServerTag(); + } + else + { + Internal_RemoveServerTag(); } UpdateGameDescription(true); @@ -1550,9 +1425,9 @@ public OnConfigsExecuted() CountRounds(); } -InstallPropMenuOverride() +void InstallPropMenuOverride() { - new tempFlags; + int tempFlags; if (GetCommandOverride("propmenu", Override_Command, tempFlags)) { g_PropMenuOldFlags = tempFlags; @@ -1562,7 +1437,7 @@ InstallPropMenuOverride() g_PropMenuOverrideInstalled = true; } -RemovePropMenuOverride() +void RemovePropMenuOverride() { if (g_PropMenuOldFlags == ADMFLAG_NONE) { @@ -1577,9 +1452,9 @@ RemovePropMenuOverride() g_PropMenuOverrideInstalled = false; } -InstallPropRerollOverride() +void InstallPropRerollOverride() { - new tempFlags; + int tempFlags; if (GetCommandOverride("propreroll", Override_Command, tempFlags)) { g_PropRerollOldFlags = tempFlags; @@ -1589,7 +1464,7 @@ InstallPropRerollOverride() g_PropRerollOverrideInstalled = true; } -RemovePropRerollOverride() +void RemovePropRerollOverride() { if (g_PropRerollOldFlags == ADMFLAG_NONE) { @@ -1604,9 +1479,9 @@ RemovePropRerollOverride() g_PropRerollOverrideInstalled = false; } -public OnPropMenuChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnPropMenuChanged(ConVar convar, const char[] oldValue, const char[] newValue) { - new newVal = GetConVarInt(g_PHPropMenu); + int newVal = g_PHPropMenu.IntValue; if (g_PropMenuOverrideInstalled && newVal < 1) { RemovePropMenuOverride(); @@ -1617,9 +1492,9 @@ public OnPropMenuChanged(Handle:convar, const String:oldValue[], const String:ne } } -public OnPropRerollChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnPropRerollChanged(ConVar convar, const char[] oldValue, const char[] newValue) { - new newVal = GetConVarInt(g_PHReroll); + int newVal = g_PHReroll.IntValue; if (g_PropRerollOverrideInstalled && newVal < 1) { RemovePropRerollOverride(); @@ -1630,9 +1505,9 @@ public OnPropRerollChanged(Handle:convar, const String:oldValue[], const String: } } -public OnMultilingualChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnMultilingualChanged(ConVar convar, const char[] oldValue, const char[] newValue) { - if (GetConVarBool(convar)) + if (convar.BoolValue) { ReadCommonPropData(true); } @@ -1643,34 +1518,34 @@ public OnMultilingualChanged(Handle:convar, const String:oldValue[], const Strin } -public OnRebuildAdminCache(AdminCachePart:part) +public void OnRebuildAdminCache(AdminCachePart part) { - if (part == AdminCache_Overrides && (GetConVarInt(g_PHPropMenu) == 1 || GetConVarInt(g_PHReroll) == 1)) + if (part == AdminCache_Overrides && (g_PHPropMenu.IntValue == 1 || g_PHReroll.IntValue == 1)) { CreateTimer(0.2, Timer_RestoreOverrides); } } -public Action:Timer_RestoreOverrides(Handle:timer) +public Action Timer_RestoreOverrides(Handle timer) { - if (GetConVarInt(g_PHPropMenu) == 1) + if (g_PHPropMenu.IntValue == 1) { InstallPropMenuOverride(); } - if (GetConVarInt(g_PHReroll) == 1) + if (g_PHReroll.IntValue == 1) { InstallPropRerollOverride(); } } -CountRounds() +void CountRounds() { g_RoundCurrent = 0; g_RoundCount = 0; - new entity = -1; + int entity = -1; // new prevPriority = 0; - new bool:roundSwitchAlways = true; + bool roundSwitchAlways = true; while ((entity = FindEntityByClassname(entity, "team_control_point_round")) != -1) { @@ -1712,19 +1587,19 @@ CountRounds() } -StartTimers(bool:noScoreTimer = false) +void StartTimers(bool noScoreTimer = false) { - if (g_hLocked == INVALID_HANDLE) + if (g_hLocked == null) { g_hLocked = CreateTimer(0.6, Timer_Locked, _, TIMER_REPEAT); } - if (!noScoreTimer && g_hScore == INVALID_HANDLE) + if (!noScoreTimer && g_hScore == null) { g_hScore = CreateTimer(55.0, Timer_Score, _, TIMER_REPEAT); } - if ((GetConVarBool(g_PHAntiHack) || GetConVarBool(g_PHStaticPropInfo)) && g_hAntiHack == INVALID_HANDLE) + if ((g_PHAntiHack.BoolValue || g_PHStaticPropInfo.BoolValue) && g_hAntiHack == null) { g_hAntiHack = CreateTimer(7.0, Timer_AntiHack, _, TIMER_REPEAT); // Also run said timer 0.1 seconds after round start. @@ -1732,35 +1607,32 @@ StartTimers(bool:noScoreTimer = false) } } -StopTimers() +void StopTimers() { - if (g_hAntiHack != INVALID_HANDLE) + if (g_hAntiHack != null) { - CloseHandle(g_hAntiHack); - g_hAntiHack = INVALID_HANDLE; + delete g_hAntiHack; } - if (g_hLocked != INVALID_HANDLE) + if (g_hLocked != null) { - CloseHandle(g_hLocked); - g_hLocked = INVALID_HANDLE; + delete g_hLocked; } - if (g_hScore != INVALID_HANDLE) + if (g_hScore != null) { - CloseHandle(g_hScore); - g_hScore = INVALID_HANDLE; + delete g_hScore; } } -public OnEnabledChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnEnabledChanged(ConVar convar, const char[] oldValue, const char[] newValue) { if (!g_MapStarted) { return; } - if (GetConVarBool(g_PHEnable)) + if (g_PHEnable.BoolValue) { if (g_Enabled) { @@ -1768,7 +1640,7 @@ public OnEnabledChanged(Handle:convar, const String:oldValue[], const String:new } else { - new bool:enabled = IsPropHuntMap(); + bool enabled = IsPropHuntMap(); if (enabled) { g_RoundChange = RoundChange_Enable; @@ -1792,12 +1664,34 @@ public OnEnabledChanged(Handle:convar, const String:oldValue[], const String:new } } -public OnAdTextChanged(Handle:convar, const String:oldValue[], const String:newValue[]) +public void OnAdTextChanged(ConVar convar, const char[] oldValue, const char[] newValue) { strcopy(g_AdText, sizeof(g_AdText), newValue); } -public StartTouchHook(entity, other) +public void OnUseUpdaterChanged(ConVar convar, const char[] oldValue, const char[] newValue) +{ + if (!g_Updater) + return; + + if (convar.BoolValue) + { + Updater_ForceUpdate(); + } +} + +public Action Updater_OnPluginDownloading() +{ + return g_PHUseUpdater.BoolValue ? Plugin_Continue : Plugin_Handled; +} + +public int Updater_OnPluginUpdated() +{ + PrintCenterTextAll("PropHunt Redux Updated, server restart may be required."); + LogMessage("[PH] PropHunt Redux Updated, server restart may be required."); +} + +public void StartTouchHook(int entity, int other) { if(other <= MaxClients && other > 0 && !g_TouchingCP[other] && IsClientInGame(other) && IsPlayerAlive(other)) { @@ -1809,7 +1703,7 @@ public StartTouchHook(entity, other) } } -stock FillHealth (entity) +stock void FillHealth (int entity) { if(IsValidEntity(entity)) { @@ -1817,25 +1711,7 @@ stock FillHealth (entity) } } -/* -stock bool:IsValidAdmin(client) -{ - decl String:flags[26]; - GetConVarString(g_PHAdmFlag, flags, sizeof(flags)); - if (GetUserFlagBits(client) & ADMFLAG_ROOT) - { - return true; - } - new iFlags = ReadFlagString(flags); - if (GetUserFlagBits(client) & iFlags) - { - return true; - } - return false; -} -*/ - -stock ExtinguishPlayer (client){ +stock void ExtinguishPlayer (int client){ if(IsClientInGame(client) && IsPlayerAlive(client) && IsValidEntity(client)) { ExtinguishEntity(client); @@ -1843,7 +1719,7 @@ stock ExtinguishPlayer (client){ } } -public OnEntityCreated(entity, const String:classname[]) +public void OnEntityCreated(int entity, const char[] classname) { if (!g_Enabled) { @@ -1866,22 +1742,16 @@ public OnEntityCreated(entity, const String:classname[]) SDKHook(entity, SDKHook_Spawn, OnBullshitEntitySpawned); } else - if(strcmp(classname, "prop_dynamic") == 0 || strcmp(classname, "prop_static") == 0) + if(strcmp(classname, "prop_dynamic") == 0) { SDKHook(entity, SDKHook_SpawnPost, OnCPEntitySpawned); } else if(strcmp(classname, "team_control_point_master") == 0) { - SDKHook(entity, SDKHook_Spawn, OnCPMasterSpawned); SDKHook(entity, SDKHook_SpawnPost, OnCPMasterSpawnedPost); } else - if(strcmp(classname, "tf_weapon_builder") == 0) - { - SDKHook(entity, SDKHook_SpawnPost, OnBuilderSpawned); - } - else if (strcmp(classname, "team_round_timer") == 0) { SDKHook(entity, SDKHook_SpawnPost, OnTimerSpawned); @@ -1893,7 +1763,7 @@ public OnEntityCreated(entity, const String:classname[]) } } -public Action:OnBullshitEntitySpawned(entity) +public Action OnBullshitEntitySpawned(int entity) { if(IsValidEntity(entity)) AcceptEntityInput(entity, "Kill"); @@ -1901,35 +1771,37 @@ public Action:OnBullshitEntitySpawned(entity) return Plugin_Continue; } -// This doesn't actually work, sadly. -// It SHOULD work, but doesn't -public Action:OnBuilderSpawned(entity) -{ - if(IsValidEntity(entity)) - { - SetEntProp(entity, Prop_Send, "m_aBuildableObjectTypes", 0, _, _:TFObject_Sentry); - } - return Plugin_Continue; -} - -public OnCPEntitySpawned(entity) +public void OnCPEntitySpawned(int entity) { - decl String:propName[500]; + char propName[500]; GetEntPropString(entity, Prop_Data, "m_ModelName", propName, sizeof(propName)); if(StrEqual(propName, "models/props_gameplay/cap_point_base.mdl")) { // Reset the skin to neutral. I'm looking at you, cp_manor_event SetVariantInt(0); AcceptEntityInput(entity, "Skin"); + + // Check if the control point is using VPhysics. + // If not, make it so it does (fixes issues with CP touch) + int curSolidType = GetEntProp(entity, Prop_Data, "m_nSolidType"); + if (curSolidType != SOLID_VPHYSICS) + { + char solidType[2]; + IntToString(SOLID_VPHYSICS, solidType, sizeof(solidType)); + DispatchKeyValue(entity, "solid", solidType); +#if defined LOG + LogMessage("[PH] Converting control point to VPhysics."); +#endif + } // Also, hook it for the heal touch hook SDKHook(entity, SDKHook_StartTouch, StartTouchHook); } } -public Action:OnTimerSpawned(entity) +public Action OnTimerSpawned(int entity) { // Attempt to shut the pre-round timer up at start, unless 5 secs or less are left - decl String:name[64]; + char name[64]; GetEntPropString(entity, Prop_Data, "m_iName", name, sizeof(name)); if (!StrEqual(name, TIMER_NAME)) @@ -1946,50 +1818,104 @@ public Action:OnTimerSpawned(entity) } } -public Action:OnCPMasterSpawned(entity) +public void OnCPMasterSpawnedPost(int entity) { #if defined LOG LogMessage("[PH] cpmaster spawned"); #endif - DispatchKeyValue(entity, "switch_teams", "0"); - //SetEntProp(entity, Prop_Data, "m_bSwitchTeamsOnWin", 0); // Changed in 3.0.0 beta 6, now forced off instead of on. - - return Plugin_Continue; -} + if (!g_MapStarted) + { + return; + } + + char name[64]; + if (GetEntPropString(entity, Prop_Data, "m_iName", name, sizeof(name)) == 0) + { + DispatchKeyValue(entity, "targetname", "master_control_point"); + } + + CreateRoundTimer(entity); +} -public OnCPMasterSpawnedPost(entity) +public Action OnArenaSpawned(int entity) { - if (!g_MapStarted) + char finishedCommand[256]; + + Format(finishedCommand, sizeof(finishedCommand), "OnArenaRoundStart %s:ShowInHUD:1:0:-1", TIMER_NAME); + SetVariantString(finishedCommand); + AcceptEntityInput(entity, "AddOutput"); + + Format(finishedCommand, sizeof(finishedCommand), "OnArenaRoundStart %s:Resume:0:0:-1", TIMER_NAME); + SetVariantString(finishedCommand); + AcceptEntityInput(entity, "AddOutput"); + + Format(finishedCommand, sizeof(finishedCommand), "OnArenaRoundStart %s:Enable:0:0:-1", TIMER_NAME); + SetVariantString(finishedCommand); + AcceptEntityInput(entity, "AddOutput"); +} + +void CheckRoundTimer() +{ + int entity = -1; + + while ((entity = FindEntityByClassname(entity, "team_round_timer")) != -1) { + char name[64]; + + GetEntPropString(entity, Prop_Data, "m_iName", name, sizeof(name)); + + if (StrEqual(name, TIMER_NAME)) + { +#if defined LOG + LogMessage("[PH] Found timer: %d", entity); +#endif return; } + } + + // No timer found, create it + LogMessage("[PH] PropHunt timer is missing. Attempting to recreate it..."); + int cpMaster = FindEntityByClassname(-1, "team_control_point_master"); + + if (cpMaster > -1) + { + CreateRoundTimer(cpMaster); + } + else + { + LogError("[PH] Could not locate team_control_point_master when recreating PropHunt timer"); + } +} +void CreateRoundTimer(int entity) +{ // We need to subtract 30 from the round time for compatibility with older PropHunt Versions - decl String:time[5]; + char time[5]; IntToString(g_RoundTime - 30, time, sizeof(time)); - decl String:name[64]; - if (GetEntPropString(entity, Prop_Data, "m_iName", name, sizeof(name)) == 0) + char name[64]; + GetEntPropString(entity, Prop_Data, "m_iName", name, sizeof(name)); + + if (name[0] == '\0') { - DispatchKeyValue(entity, "targetname", "master_control_point"); - strcopy(name, sizeof(name), "master_control_point"); + LogError("team_control_point_master %d has no name", entity); + return; } // Create our timer. - new timer = CreateEntityByName("team_round_timer"); + int timer = CreateEntityByName("team_round_timer"); DispatchKeyValue(timer, "targetname", TIMER_NAME); - new String:setupLength[5]; + char setupLength[5]; if (g_Setup >= SETUP_MIN && g_Setup <= SETUP_MAX) { IntToString(g_Setup, setupLength, sizeof(setupLength)); } else { - GetConVarString(g_PHSetupLength, setupLength, sizeof(setupLength)); + g_PHSetupLength.GetString(setupLength, sizeof(setupLength)); } DispatchKeyValue(timer, "setup_length", setupLength); - //DispatchKeyValue(timer, "setup_length", "30"); DispatchKeyValue(timer, "reset_time", "1"); DispatchKeyValue(timer, "auto_countdown", "1"); DispatchKeyValue(timer, "timer_length", time); @@ -1999,219 +1925,197 @@ public OnCPMasterSpawnedPost(entity) LogMessage("[PH] setting up cpmaster \"%s\" (%d) with team round timer \"%s\" (%d) ", name, entity, TIMER_NAME, timer); #endif - decl String:finishedCommand[256]; + char finishedCommand[256]; Format(finishedCommand, sizeof(finishedCommand), "OnFinished %s:SetWinnerAndForceCaps:%d:0:-1", name, TEAM_PROP); SetVariantString(finishedCommand); AcceptEntityInput(timer, "AddOutput"); + HookSingleEntityOutput(timer, "OnSetupStart", OnSetupStart); HookSingleEntityOutput(timer, "OnSetupFinished", OnSetupFinished); } -public Action:OnArenaSpawned(entity) +public void OnMapEnd() { - decl String:finishedCommand[256]; - - Format(finishedCommand, sizeof(finishedCommand), "OnArenaRoundStart %s:ShowInHUD:1:0:-1", TIMER_NAME); - SetVariantString(finishedCommand); - AcceptEntityInput(entity, "AddOutput"); - - Format(finishedCommand, sizeof(finishedCommand), "OnArenaRoundStart %s:Resume:0:0:-1", TIMER_NAME); - SetVariantString(finishedCommand); - AcceptEntityInput(entity, "AddOutput"); - - Format(finishedCommand, sizeof(finishedCommand), "OnArenaRoundStart %s:Enable:0:0:-1", TIMER_NAME); - SetVariantString(finishedCommand); - AcceptEntityInput(entity, "AddOutput"); -} - -public OnMapEnd() -{ -#if defined STATS || defined LOCALSTATS - g_MapChanging = true; -#endif - // workaround for CreateEntityByName g_MapStarted = false; ResetCVars(); StopTimers(); - new bool:remove = g_Enabled; // Save the enabled value + bool remove = g_Enabled; // Save the enabled value g_Enabled = false; if (remove) { UpdateGameDescription(); } - for (new client = 1; client<=MaxClients; client++) + for (int client = 1; client<=MaxClients; client++) { g_CurrentlyFlaming[client] = false; g_FlameCount[client] = 0; } - ClearArray(g_ModelName); - ClearArray(g_ModelOffset); - ClearArray(g_ModelRotation); - ClearArray(g_ModelSkin); + g_ModelName.Clear(); + g_ModelOffset.Clear(); + g_ModelRotation.Clear(); + g_ModelSkin.Clear(); - ClearArray(g_hWeaponRemovals); - ClearTrie(g_hWeaponNerfs); - ClearTrie(g_hWeaponSelfDamage); - ClearArray(g_hWeaponStripAttribs); - ClearTrie(g_hWeaponAddAttribs); - ClearTrie(g_hWeaponReplacements); - ClearTrie(g_hWeaponReplacementPlayerClasses); + g_hWeaponRemovals.Clear(); + g_hPropWeaponRemovals.Clear(); + g_hWeaponNerfs.Clear(); + g_hWeaponSelfDamage.Clear(); + g_hWeaponStripAttribs.Clear(); + g_hWeaponAddAttribs.Clear(); + g_hWeaponReplacements.Clear(); + g_hWeaponReplacementPlayerClasses.Clear(); - ClearTrie(g_Sounds); - ClearTrie(g_BroadcastSounds); + g_Sounds.Clear(); + g_BroadcastSounds.Clear(); } -public OnMapStart() +public void OnMapStart() { + g_GameRulesProxy = INVALID_ENT_REFERENCE; GetCurrentMap(g_Mapname, sizeof(g_Mapname)); g_PHMap = IsPropHuntMap(); if (g_PHMap) { - decl String:confil[PLATFORM_MAX_PATH], String:buffer[256], String:offset[32], String:rotation[32]; + char confil[PLATFORM_MAX_PATH], buffer[256], offset[32], rotation[32]; - new Handle:fl; + KeyValues fl; - if (g_PropMenu != INVALID_HANDLE) + if (g_PropMenu != null) { - CloseHandle(g_PropMenu); - g_PropMenu = INVALID_HANDLE; + delete g_PropMenu; } - g_PropMenu = CreateMenu(Handler_PropMenu, MENU_ACTIONS_DEFAULT|MenuAction_DisplayItem); - SetMenuTitle(g_PropMenu, "PropHunt Prop Menu"); - SetMenuExitButton(g_PropMenu, true); + g_PropMenu = new Menu(Handler_PropMenu, MENU_ACTIONS_DEFAULT|MenuAction_DisplayItem); + g_PropMenu.SetTitle("PropHunt Prop Menu"); + g_PropMenu.ExitButton = true; BuildPath(Path_SM, confil, sizeof(confil), "data/prophunt/prop_menu.txt"); fl = CreateKeyValues("propmenu"); - if (FileToKeyValues(fl, confil)) + if (fl.ImportFromFile(confil)) { - new count = 0; + int count = 0; PrintToServer("Successfully loaded %s", confil); - KvGotoFirstSubKey(fl); + fl.GotoFirstSubKey(); do { - KvGetSectionName(fl, buffer, sizeof(buffer)); - AddMenuItem(g_PropMenu, buffer, buffer); + fl.GetSectionName(buffer, sizeof(buffer)); + if (!FileExists(buffer, true)) + { + LogError("prop_menu.txt: Prop does not exist: %s", buffer); + continue; + } + g_PropMenu.AddItem(buffer, buffer); count++; } - while (KvGotoNextKey(fl)); + while (fl.GotoNextKey()); PrintToServer("Successfully parsed %s", confil); PrintToServer("Added %i models to prop menu.", GetMenuItemCount(g_PropMenu)); } - CloseHandle(fl); + delete fl; - new sharedCount = 0; + int sharedCount = 0; BuildPath(Path_SM, confil, sizeof(confil), "data/prophunt/props_allmaps.txt"); fl = CreateKeyValues("sharedprops"); - if (FileToKeyValues(fl, confil)) + if (fl.ImportFromFile(confil)) { PrintToServer("Successfully loaded %s", confil); - KvGotoFirstSubKey(fl); + fl.GotoFirstSubKey(); do { - KvGetSectionName(fl, buffer, sizeof(buffer)); - PushArrayString(g_ModelName, buffer); - AddMenuItem(g_PropMenu, buffer, buffer); - KvGetString(fl, "offset", offset, sizeof(offset), "0 0 0"); - PushArrayString(g_ModelOffset, offset); - KvGetString(fl, "rotation", rotation, sizeof(rotation), "0 0 0"); - PushArrayString(g_ModelRotation, rotation); - PushArrayCell(g_ModelSkin, KvGetNum(fl, "skin", 0)); + fl.GetSectionName(buffer, sizeof(buffer)); + if (!FileExists(buffer, true)) + { + LogError("props_allmaps.txt: Prop does not exist: %s", buffer); + continue; + } + g_ModelName.PushString(buffer); + g_PropMenu.AddItem(buffer, buffer); + fl.GetString("offset", offset, sizeof(offset), "0 0 0"); + g_ModelOffset.PushString(offset); + fl.GetString("rotation", rotation, sizeof(rotation), "0 0 0"); + g_ModelRotation.PushString(rotation); + g_ModelSkin.Push(KvGetNum(fl, "skin", 0)); } - while (KvGotoNextKey(fl)); + while (fl.GotoNextKey()); PrintToServer("Successfully parsed %s", confil); - sharedCount = GetArraySize(g_ModelName); + sharedCount = g_ModelName.Length; PrintToServer("Loaded %i shared models.", sharedCount); } - CloseHandle(fl); + delete fl; - decl String:tidyname[2][PLATFORM_MAX_PATH], String:maptidyname[PLATFORM_MAX_PATH]; - ExplodeString(g_Mapname, "_", tidyname, 2, 32); - if (strncmp("workshop/", tidyname[0], 9) == 0) - { - ReplaceString(tidyname[0], sizeof(tidyname[]), "workshop/", ""); - } - else if (strncmp("workshop\\", tidyname[0], 9) == 0) + if (!FindConfigFileForMap(g_Mapname, confil, sizeof(confil))) { - ReplaceString(tidyname[0], sizeof(tidyname[]), "workshop\\", ""); + LogMessage("[PH] Config file for map %s not found. Disabling plugin.", g_Mapname); + g_Enabled = false; + return; } - // Because we only care about the first two pieces of the map name, we can ignore the ugc stuff - Format(maptidyname, sizeof(maptidyname), "%s_%s", tidyname[0], tidyname[1]); - BuildPath(Path_SM, confil, sizeof(confil), "data/prophunt/maps/%s.cfg", maptidyname); + fl = CreateKeyValues("prophuntmapconfig"); - if(!FileToKeyValues(fl, confil)) + if(!fl.ImportFromFile(confil)) { - LogMessage("[PH] Config file for map %s not found at %s. Disabling plugin.", maptidyname, confil); - CloseHandle(fl); + LogMessage("[PH] Config file for map %s at %s could not be opened. Disabling plugin.", g_Mapname, confil); + delete fl; g_Enabled = false; return; } else { PrintToServer("Successfully loaded %s", confil); - KvGotoFirstSubKey(fl); - KvJumpToKey(fl, "Props", false); - KvGotoFirstSubKey(fl); + fl.GotoFirstSubKey(); + fl.JumpToKey("Props", false); + fl.GotoFirstSubKey(); do { - KvGetSectionName(fl, buffer, sizeof(buffer)); - PushArrayString(g_ModelName, buffer); - AddMenuItem(g_PropMenu, buffer, buffer); - KvGetString(fl, "offset", offset, sizeof(offset), "0 0 0"); - PushArrayString(g_ModelOffset, offset); - KvGetString(fl, "rotation", rotation, sizeof(rotation), "0 0 0"); - PushArrayString(g_ModelRotation, rotation); - PushArrayCell(g_ModelSkin, KvGetNum(fl, "skin", 0)); + fl.GetSectionName(buffer, sizeof(buffer)); + if (!FileExists(buffer, true)) + { + LogError("%s: Prop does not exist: %s", confil, buffer); + continue; + } + g_ModelName.PushString(buffer); + g_PropMenu.AddItem(buffer, buffer); + fl.GetString("offset", offset, sizeof(offset), "0 0 0"); + g_ModelOffset.PushString(offset); + fl.GetString("rotation", rotation, sizeof(rotation), "0 0 0"); + g_ModelRotation.PushString(rotation); + g_ModelSkin.Push(fl.GetNum("skin", 0)); } - while (KvGotoNextKey(fl)); - KvRewind(fl); - KvJumpToKey(fl, "Settings", false); + while (fl.GotoNextKey()); + fl.Rewind(); + fl.JumpToKey("Settings", false); - g_Doors = bool:KvGetNum(fl, "doors", 0); - g_Relay = bool:KvGetNum(fl, "relay", 0); - g_Freeze = bool:KvGetNum(fl, "freeze", 1); - g_RoundTime = KvGetNum(fl, "round", 175); - g_Setup = KvGetNum(fl, "setup", 30); + g_Doors = view_as(fl.GetNum("doors", 0)); + g_Relay = view_as(fl.GetNum("relay", 0)); + g_Freeze = view_as(fl.GetNum("freeze", 1)); + g_RoundTime = fl.GetNum("round", 175); + g_Setup = fl.GetNum("setup", 30); PrintToServer("Successfully parsed %s", confil); - PrintToServer("Loaded %i models, doors: %i, relay: %i, freeze: %i, round time: %i, setup: %i.", GetArraySize(g_ModelName)-sharedCount, g_Doors ? 1:0, g_Relay ? 1:0, g_Freeze ? 1:0, g_RoundTime, g_Setup); + PrintToServer("Loaded %i models, doors: %i, relay: %i, freeze: %i, round time: %i, setup: %i.", g_ModelName.Length-sharedCount, g_Doors ? 1:0, g_Relay ? 1:0, g_Freeze ? 1:0, g_RoundTime, g_Setup); } - CloseHandle(fl); + delete fl; - decl String:model[100]; + char model[100]; - for(new i = 0; i < GetArraySize(g_ModelName); i++) + for(int i = 0; i < g_ModelName.Length; i++) { - GetArrayString(g_ModelName, i, model, sizeof(model)); + g_ModelName.GetString(i, model, sizeof(model)); if(PrecacheModel(model, true) == 0) { - RemoveFromArray(g_ModelName, i); + g_ModelName.Erase(i); } } - PrecacheModel(FLAMETHROWER, true); - - /*new ent = FindEntityByClassname(-1, "team_control_point_master"); - if (ent == 1) - { - AcceptEntityInput(ent, "Kill"); - } - ent = CreateEntityByName("team_control_point_master"); - DispatchKeyValue(ent, "switch_teams", "1"); - DispatchSpawn(ent); - AcceptEntityInput(ent, "Enable");*/ - // workaround for CreateEntityByNsme g_MapStarted = true; @@ -2220,57 +2124,42 @@ public OnMapStart() // workaround no win panel event - admin changes, rtv, etc. g_LastProp = false; - for (new client = 1; client <= MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { g_LastPropDamageTime[client] = -1; } g_LastPropPlayer = 0; g_RoundOver = true; + g_inSetup = false; //g_inPreRound = true; + g_PlayerDied = false; + g_flRoundStart = 0.0; // Clear the replacement weapon list - for (new i = 1; i <= MaxClients; ++i) + for (int i = 1; i <= MaxClients; ++i) { - for (new j = 0; j < sizeof(g_Replacements[]); ++j) + for (int j = 0; j < sizeof(g_Replacements[]); ++j) { g_Replacements[i][j] = -1; } g_ReplacementCount[i] = 0; } - -#if defined STATS || defined LOCALSTATS - g_MapChanging = false; -#endif - -} - -/* -public Action:OnGetGameDescription(String:gameDesc[64]) -{ - if (strlen(g_AdText) > 0) - Format(gameDesc, sizeof(gameDesc), "PropHunt %s (%s)", g_Version, g_AdText); - else - Format(gameDesc, sizeof(gameDesc), "PropHunt %s", g_Version); - - return Plugin_Changed; } -*/ -public OnPluginEnd() +public void OnPluginEnd() { PrintCenterTextAll("%t", "#TF_PH_PluginReload"); -#if defined STATS - Stats_Uninit(); -#endif -#if defined LOCALSTATS - LocalStats_Uninit(); -#endif ResetCVars(); if (g_SteamTools) { Steam_SetGameDescription("Team Fortress"); } + else + if (g_SteamWorks) + { + SteamWorks_SetGameDescription("Team Fortress"); + } #if defined OIMM if (g_OptinMultiMod) { @@ -2279,7 +2168,7 @@ public OnPluginEnd() #endif } -public Action:TakeDamageHook(victim, &attacker, &inflictor, &Float:damage, &damagetype, &weapon, Float:damageForce[3], Float:damagePosition[3], damagecustom) +public Action TakeDamageHook(int victim, int &attacker, int &inflictor, float &damage, int &damagetype, int &weapon, float damageForce[3], float damagePosition[3], int damagecustom) { if (!g_Enabled) { @@ -2290,7 +2179,7 @@ public Action:TakeDamageHook(victim, &attacker, &inflictor, &Float:damage, &dama { if (IsPlayerAlive(victim) && GetClientTeam(victim) == TEAM_PROP && GetClientTeam(attacker) == TEAM_HUNTER && !g_Hit[victim]) { - new Float:pos[3]; + float pos[3]; GetClientAbsOrigin(victim, pos); PH_EmitSoundToClient(victim, "PropFound", _, SNDCHAN_WEAPON, _, _, 0.8, _, victim, pos); PH_EmitSoundToClient(attacker, "PropFound", _, SNDCHAN_WEAPON, _, _, 0.8, _, victim, pos); @@ -2304,11 +2193,11 @@ public Action:TakeDamageHook(victim, &attacker, &inflictor, &Float:damage, &dama if(weapon > MaxClients && IsValidEntity(weapon)) { - new String:weaponIndex[10]; + char weaponIndex[10]; IntToString(GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"), weaponIndex, sizeof(weaponIndex)); - new Float:multiplier; - if (GetTrieValue(g_hWeaponNerfs, weaponIndex, multiplier)) + float multiplier; + if (g_hWeaponNerfs.GetValue(weaponIndex, multiplier)) { damage *= multiplier; return Plugin_Changed; @@ -2322,7 +2211,8 @@ public Action:TakeDamageHook(victim, &attacker, &inflictor, &Float:damage, &dama return Plugin_Changed; } - if(GetConVarBool(g_PHPreventFallDamage) && damagetype & DMG_FALL && victim > 0 && victim <= MaxClients && IsClientInGame(victim) && attacker == 0) + // block fall damage if set + if(g_PHPreventFallDamage.BoolValue && damagetype & DMG_FALL && victim > 0 && victim <= MaxClients && IsClientInGame(victim) && attacker == 0) { damage = 0.0; return Plugin_Changed; @@ -2330,7 +2220,7 @@ public Action:TakeDamageHook(victim, &attacker, &inflictor, &Float:damage, &dama return Plugin_Continue; } -stock RemoveAnimeModel (client){ +stock void RemovePropModel (int client){ if(IsClientInGame(client) && IsPlayerAlive(client) && IsValidEntity(client)) { SetVariantString("0 0 0"); @@ -2340,41 +2230,19 @@ stock RemoveAnimeModel (client){ SetVariantString(""); AcceptEntityInput(client, "SetCustomModel"); - + RemoveValveHat(client, false); SetEntProp(client, Prop_Send, "m_bForcedSkin", false); SetEntProp(client, Prop_Send, "m_nForcedSkin", 0); } } -public OnClientDisconnect(client) -{ -#if defined STATS - OCD(client); -#endif - -#if defined LOCALSTATS - LocalStats_OnClientDisconnect(client); -#endif -} - -public OnClientDisconnect_Post(client) +public void OnClientDisconnect_Post(int client) { ResetPlayer(client); -#if defined STATS - OCD_Post(client); -#endif - -#if defined LOCALSTATS - LocalStats_OnClientDisconnect_Post(client); -#endif } -stock SwitchView (target, bool:observer, bool:viewmodel){ +stock void SwitchView (int target, bool observer, bool viewmodel){ g_First[target] = !observer; - /*SetEntPropEnt(target, Prop_Send, "m_hObserverTarget", observer ? target:-1); - SetEntProp(target, Prop_Send, "m_iObserverMode", observer ? 1:0); - SetEntData(target, g_oFOV, observer ? 100:GetEntData(target, g_oDefFOV, 4), 4, true); - SetEntProp(target, Prop_Send, "m_bDrawViewmodel", viewmodel ? 1:0);*/ SetVariantInt(observer ? 1 : 0); AcceptEntityInput(target, "SetForcedTauntCam"); @@ -2383,25 +2251,9 @@ stock SwitchView (target, bool:observer, bool:viewmodel){ AcceptEntityInput(target, "SetCustomModelVisibletoSelf"); } -/* -stock ForceTeamWin (team){ - new ent = FindEntityByClassname(-1, "team_control_point_master"); - if (ent == -1) - { - ent = CreateEntityByName("team_control_point_master"); - DispatchKeyValue(ent, "targetname", "master_control_point"); - DispatchKeyValue(ent, "StartDisabled", "0"); - DispatchSpawn(ent); - AcceptEntityInput(ent, "Enable"); - } - SetVariantInt(team); - AcceptEntityInput(ent, "SetWinner"); -} -*/ - -public Action:Command_jointeam(client, args) +public Action Command_jointeam(int client, int args) { - decl String:argstr[16]; + char argstr[16]; GetCmdArgString(argstr, sizeof(argstr)); if(StrEqual(argstr, "spectatearena")) { @@ -2414,14 +2266,14 @@ public Action:Command_jointeam(client, args) return Plugin_Continue; } -public Action:Command_ReloadConfig(client, args) +public Action Command_ReloadConfig(int client, int args) { loadGlobalConfig(); CReplyToCommand(client, "PropHunt Config reloaded"); return Plugin_Handled; } -public Action:Command_propmenu(client, args) +public Action Command_propmenu(int client, int args) { if (!g_Enabled) return Plugin_Continue; @@ -2431,7 +2283,7 @@ public Action:Command_propmenu(client, args) CReplyToCommand(client, "%t", "Command is in-game only"); return Plugin_Handled; } - if(GetConVarInt(g_PHPropMenu) >= 0) + if(g_PHPropMenu.IntValue >= 0) { if(GetClientTeam(client) == TEAM_PROP && IsPlayerAlive(client)) { @@ -2439,7 +2291,7 @@ public Action:Command_propmenu(client, args) { if (CanPropChange(client)) { - decl String:model[PLATFORM_MAX_PATH]; + char model[PLATFORM_MAX_PATH]; GetCmdArg(1, model, sizeof(model)); if (!FileExists(model, true)) @@ -2448,18 +2300,18 @@ public Action:Command_propmenu(client, args) return Plugin_Handled; } - new bool:restrict = true; + bool restrict = true; - restrict = GetConVarBool(g_PHPropMenuRestrict); + restrict = g_PHPropMenuRestrict.BoolValue; if (restrict) { - new found = false; + bool found = false; - new count = GetMenuItemCount(g_PropMenu); - for (new i = 0; i < count; i++) + int count = GetMenuItemCount(g_PropMenu); + for (int i = 0; i < count; i++) { - decl String:otherModel[PLATFORM_MAX_PATH]; - GetMenuItem(g_PropMenu, i, otherModel, sizeof(otherModel)); + char otherModel[PLATFORM_MAX_PATH]; + g_PropMenu.GetItem(i, otherModel, sizeof(otherModel)); if (strcmp(model, otherModel, false) == 0) { found = true; @@ -2502,7 +2354,7 @@ public Action:Command_propmenu(client, args) return Plugin_Handled; } -public Action:Command_propreroll(client, args) +public Action Command_propreroll(int client, int args) { if (!g_Enabled) return Plugin_Continue; @@ -2512,7 +2364,7 @@ public Action:Command_propreroll(client, args) CReplyToCommand(client, "%t", "Command is in-game only"); return Plugin_Handled; } - if(GetConVarInt(g_PHReroll) >= 0) + if(g_PHReroll.IntValue >= 0) { if(GetClientTeam(client) == TEAM_PROP && IsPlayerAlive(client)) { @@ -2547,38 +2399,38 @@ public Action:Command_propreroll(client, args) return Plugin_Handled; } -public Handler_PropMenu(Handle:menu, MenuAction:action, param1, param2) +public int Handler_PropMenu(Menu menu, MenuAction action, int param1, int param2) { switch (action) { case MenuAction_Display: { - decl String:buffer[255]; + char buffer[255]; Format(buffer, sizeof(buffer), "%T", "#TF_PH_PropMenuName", param1); - new Handle:panel = Handle:param2; - SetPanelTitle(panel, buffer); + Panel panel = view_as(param2); + panel.SetTitle(buffer); } case MenuAction_Select: { if(IsClientInGame(param1)) { - if(GetConVarInt(g_PHPropMenu) == 1 || CheckCommandAccess(param1, "propmenu", ADMFLAG_KICK)) + if(g_PHPropMenu.IntValue == 1 || CheckCommandAccess(param1, "propmenu", ADMFLAG_KICK)) { if(GetClientTeam(param1) == TEAM_PROP && IsPlayerAlive(param1)) { if (CanPropChange(param1)) { - GetMenuItem(menu, param2, g_PlayerModel[param1], PLATFORM_MAX_PATH); + menu.GetItem(param2, g_PlayerModel[param1], sizeof(g_PlayerModel[])); g_RoundStartMessageSent[param1] = false; RequestFrame(DoEquipProp, GetClientUserId(param1)); } else { CPrintToChat(param1, "%t", "#TF_PH_PropCantChange"); - new pos = GetMenuSelectionPosition(); - DisplayMenuAtItem(menu, param1, pos, MENU_TIME_FOREVER); + int pos = menu.Selection; + menu.DisplayAt(param1, pos, MENU_TIME_FOREVER); } } else @@ -2595,15 +2447,15 @@ public Handler_PropMenu(Handle:menu, MenuAction:action, param1, param2) case MenuAction_DisplayItem: { - if (!GetConVarBool(g_PHPropMenuNames)) + if (!g_PHPropMenuNames.BoolValue) { return 0; } - new String:model[PLATFORM_MAX_PATH]; - GetMenuItem(menu, param2, model, sizeof(model)); + char model[PLATFORM_MAX_PATH]; + menu.GetItem(param2, model, sizeof(model)); - new String:modelName[MAXMODELNAME]; + char modelName[MAXMODELNAME]; if (GetModelNameForClient(param1, model, modelName, sizeof(modelName))) { return RedrawMenuItem(modelName); @@ -2613,9 +2465,9 @@ public Handler_PropMenu(Handle:menu, MenuAction:action, param1, param2) return 0; } -bool:CanPropChange(client) +bool CanPropChange(int client) { - if (!GetConVarBool(g_PHDamageBlocksPropChange)) + if (!g_PHDamageBlocksPropChange.BoolValue) { return true; } @@ -2627,7 +2479,7 @@ bool:CanPropChange(client) return true; } -public OnClientPutInServer(client) +public void OnClientPutInServer(int client) { SDKHook(client, SDKHook_WeaponSwitchPost, WeaponSwitch); @@ -2641,7 +2493,7 @@ public OnClientPutInServer(client) ResetPlayer(client); } -public ResetPlayer(client) +public void ResetPlayer(int client) { g_Spawned[client] = false; g_Charge[client] = false; @@ -2661,7 +2513,7 @@ public ResetPlayer(client) g_RoundStartMessageSent[client] = false; } -public Action: Command_respawn(client, args) +public Action Command_respawn(int client, int args) { if (!g_Enabled) return Plugin_Continue; @@ -2675,66 +2527,143 @@ public Action: Command_respawn(client, args) return Plugin_Handled; } -public Action:Command_internet(client, args) +public Action Command_internet(int client, int args) { if (!g_Enabled) return Plugin_Continue; - decl String:name[255]; - for(new i = 0; i < 3; i++) + //char name[PLATFORM_MAX_PATH]; + for(int i = 0; i < 3; i++) { PH_EmitSoundToAll("Internet", _, _, SNDLEVEL_AIRCRAFT); } - GetClientName(client, name, sizeof(name)); + //GetClientName(client, name, sizeof(name)); return Plugin_Handled; } -PH_EmitSoundToAll(const String:soundid[], entity = SOUND_FROM_PLAYER, channel = SNDCHAN_AUTO, level = SNDLEVEL_NORMAL, flags = SND_NOFLAGS, Float:volume = SNDVOL_NORMAL, pitch = SNDPITCH_NORMAL, speakerentity = -1, const Float:origin[3] = NULL_VECTOR, const Float:dir[3] = NULL_VECTOR, bool:updatePos = true, Float:soundtime = 0.0) +// This is a fallback for if other sound playing methods fail +void BroadcastAudio(const char[] sample, int team = -1) +{ + Event event = CreateEvent("teamplay_broadcast_audio"); + + if (event == null) + return; + + event.SetString("sound", sample); + event.SetInt("team", view_as(team)); + event.SetInt("additional_flags", 0); + event.Fire(); +} + +void PH_EmitSoundToAll(const char[] soundid, int entity = SOUND_FROM_PLAYER, int channel = SNDCHAN_AUTO, int level = SNDLEVEL_NORMAL, int flags = SND_NOFLAGS, float volume = SNDVOL_NORMAL, + int pitch = SNDPITCH_NORMAL, int speakerentity = -1, const float origin[3] = NULL_VECTOR, const float dir[3] = NULL_VECTOR, bool updatePos = true, float soundtime = 0.0) +{ + char sample[128]; + + if(g_BroadcastSounds.GetString(soundid, sample, sizeof(sample))) + { + if (EntRefToEntIndex(g_GameRulesProxy) != INVALID_ENT_REFERENCE) + { + SetVariantString(sample); + AcceptEntityInput(g_GameRulesProxy, "PlayVO"); + } + else if (PrecacheScriptSound(sample)) + { + EmitGameSoundToAll(sample, entity, flags, speakerentity, origin, dir, updatePos, soundtime); + } + else + { + BroadcastAudio(sample); + } + } + else if(g_Sounds.GetString(soundid, sample, sizeof(sample))) + { + EmitSoundToAll(sample, entity, channel, level, flags, volume, pitch, speakerentity, origin, dir, updatePos, soundtime); + } +} + +stock void PH_EmitSoundToTeam(TFTeam team, const char[] soundid, int entity = SOUND_FROM_PLAYER, int channel = SNDCHAN_AUTO, int level = SNDLEVEL_NORMAL, int flags = SND_NOFLAGS, + float volume = SNDVOL_NORMAL, int pitch = SNDPITCH_NORMAL, int speakerentity = -1, const float origin[3] = NULL_VECTOR, const float dir[3] = NULL_VECTOR, bool updatePos = true, + float soundtime = 0.0) { - decl String:sample[128]; + char sample[128]; - if(GetTrieString(g_BroadcastSounds, soundid, sample, sizeof(sample))) + if(g_BroadcastSounds.GetString(soundid, sample, sizeof(sample))) { - if (!EmitGameSoundToAll(sample, entity, flags, speakerentity, origin, dir, updatePos, soundtime)) + if (EntRefToEntIndex(g_GameRulesProxy) != INVALID_ENT_REFERENCE) + { + SetVariantString(sample); + if (team == TFTeam_Red) + { + AcceptEntityInput(g_GameRulesProxy, "PlayVORed"); + } + else if (team == TFTeam_Blue) + { + AcceptEntityInput(g_GameRulesProxy, "PlayVOBlue"); + } + } + else if (PrecacheScriptSound(sample)) + { + int count = 0; + int clients = new int[MaxClients]; + + for (int client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) == view_as(team)) + { + clients[count++] = client; + } + } + + if (count > 0) + { + EmitGameSound(clients, count, sample, entity, flags, speakerentity, origin, dir, updatePos, soundtime); + } + } + else { - new Handle:broadcastEvent = CreateEvent("teamplay_broadcast_audio"); - SetEventInt(broadcastEvent, "team", 0); // despite documentation saying otherwise, it's team -1 for all (docs say team 0). Edit: changed from -1 to 0 in a 2014 update - SetEventString(broadcastEvent, "sound", sample); - FireEvent(broadcastEvent); + BroadcastAudio(sample, view_as(team)); } } - else if(GetTrieString(g_Sounds, soundid, sample, sizeof(sample))) + else if(g_Sounds.GetString(soundid, sample, sizeof(sample))) { - if(!IsSoundPrecached(sample)) + int count = 0; + int clients = new int[MaxClients]; + + for (int client = 1; client <= MaxClients; client++) { - PrecacheSound(sample); + if (IsClientInGame(client) && !IsFakeClient(client) && GetClientTeam(client) == view_as(team)) + { + clients[count++] = client; + } } - EmitSoundToAll(sample, entity, channel, level, flags, volume, pitch, speakerentity, origin, dir, updatePos, soundtime); + + EmitSound(clients, count, sample, entity, channel, level, flags, volume, pitch, speakerentity, origin, dir, updatePos, soundtime); } + } -PH_EmitSoundToClient(client, const String:soundid[], entity = SOUND_FROM_PLAYER, channel = SNDCHAN_AUTO, level = SNDLEVEL_NORMAL, flags = SND_NOFLAGS, Float:volume = SNDVOL_NORMAL, pitch = SNDPITCH_NORMAL, speakerentity = -1, const Float:origin[3] = NULL_VECTOR, const Float:dir[3] = NULL_VECTOR, bool:updatePos = true, Float:soundtime = 0.0) + +void PH_EmitSoundToClient(int client, const char[] soundid, int entity = SOUND_FROM_PLAYER, int channel = SNDCHAN_AUTO, int level = SNDLEVEL_NORMAL, int flags = SND_NOFLAGS, + float volume = SNDVOL_NORMAL, int pitch = SNDPITCH_NORMAL, int speakerentity = -1, const float origin[3] = NULL_VECTOR, const float dir[3] = NULL_VECTOR, bool updatePos = true, + float soundtime = 0.0) { - decl String:sample[128]; + char sample[128]; - new bool:emitted = false; + bool emitted = false; - if(GetTrieString(g_BroadcastSounds, soundid, sample, sizeof(sample))) + if(g_BroadcastSounds.GetString(soundid, sample, sizeof(sample))) { emitted = EmitGameSoundToClient(client, sample, entity, flags, speakerentity, origin, dir, updatePos, soundtime); } - if(!emitted && GetTrieString(g_Sounds, soundid, sample, sizeof(sample))) + if(!emitted && g_Sounds.GetString(soundid, sample, sizeof(sample))) { - if(!IsSoundPrecached(sample)) - { - PrecacheSound(sample); - } EmitSoundToClient(client, sample, entity, channel, level, flags, volume, pitch, speakerentity, origin, dir, updatePos, soundtime); } } -public Action:Command_switch(client, args) +public Action Command_switch(int client, int args) { if (!g_Enabled) return Plugin_Continue; @@ -2751,7 +2680,7 @@ public Action:Command_switch(client, args) return Plugin_Handled; } -public Action:Command_pyro(client, args) +public Action Command_pyro(int client, int args) { if (!g_Enabled) return Plugin_Continue; @@ -2769,9 +2698,9 @@ public Action:Command_pyro(client, args) CreateTimer(0.8, Timer_Unfreeze, GetClientUserId(client)); return Plugin_Handled; } -stock PlayersAlive (){ - new alive = 0; - for(new i=1; i <= MaxClients; i++) +stock int PlayersAlive (){ + int alive = 0; + for(int i=1; i <= MaxClients; i++) { if(IsClientInGame(i) && IsPlayerAlive(i)) alive++; @@ -2779,39 +2708,16 @@ stock PlayersAlive (){ return alive; } -/* -public StopPreroundTimers(bool:instant) -{ - StopTimer(g_TimerStart); - for (new i = 0; i < sizeof(g_CountdownSoundTimers); i++) - { - if(g_CountdownSoundTimers[i] != INVALID_HANDLE) - { - StopTimer(g_CountdownSoundTimers[i]); - } - } - if(instant) - { - StopTimer(g_RoundTimer); - } - else - { - CreateTimer(2.0, Timer_AfterWinPanel); - } -} -*/ - - -stock ChangeClientTeamAlive (client, team) +stock void ChangeClientTeamAlive (int client, int team) { SetEntProp(client, Prop_Send, "m_lifeState", 2); ChangeClientTeam(client, team); SetEntProp(client, Prop_Send, "m_lifeState", 0); } -stock GetRandomPlayer (team) +stock int GetRandomPlayer (int team) { - new client, totalclients; + int client, totalclients; for(client=1; client <= MaxClients; client++) { @@ -2821,7 +2727,7 @@ stock GetRandomPlayer (team) } } - new clientarray[totalclients], i; + int clientarray[totalclients], i; for(client=1; client <= MaxClients; client++) { if(IsClientInGame(client)) @@ -2839,19 +2745,12 @@ stock GetRandomPlayer (team) return client; } -stock StopTimer (Handle:timer) -{ - if(timer != INVALID_HANDLE) KillTimer(timer); - timer = INVALID_HANDLE; -} - -stock bool:IsPropHuntMap () +stock bool IsPropHuntMap() { return ValidateMap(g_Mapname); } - -public Action:TF2_CalcIsAttackCritical(client, weapon, String:weaponname[], &bool:result) +public Action TF2_CalcIsAttackCritical(int client, int weapon, char[] weaponname, bool &result) { if(!g_Enabled || g_RoundOver) return Plugin_Continue; @@ -2867,12 +2766,12 @@ public Action:TF2_CalcIsAttackCritical(client, weapon, String:weaponname[], &boo DoSelfDamage(client, weapon); result = false; - return Plugin_Handled; + return Plugin_Changed; } return Plugin_Continue; } -public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:angles[3], &weapon, &subtype, &cmdnum, &tickcount, &seed, mouse[2]) +public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon, int &subtype, int &cmdnum, int &tickcount, int &seed, int mouse[2]) { if (!g_Enabled || g_RoundOver || !g_CurrentlyFlaming[client]) { @@ -2889,21 +2788,21 @@ public Action:OnPlayerRunCmd(client, &buttons, &impulse, Float:vel[3], Float:ang return Plugin_Continue; } -public OnGameFrame() +public void OnGameFrame() { if (!g_Enabled || g_RoundOver) { return; } - for (new client = 1; client <= MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { if (!IsClientInGame(client) || !g_CurrentlyFlaming[client] || GetClientTeam(client) != TEAM_HUNTER || !IsPlayerAlive(client) || g_FlameCount[client]++ % FLY_COUNT != 0) { continue; } - new weapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); + int weapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); if (IsValidEntity(weapon)) { @@ -2913,14 +2812,14 @@ public OnGameFrame() } } -public WeaponSwitch(client, weapon) +public void WeaponSwitch(int client, int weapon) { if (!g_Enabled || g_RoundOver || !g_CurrentlyFlaming[client]) { return; } - new String:weaponname[64]; + char weaponname[64]; GetEntityClassname(weapon, weaponname, sizeof(weaponname)); if(strcmp(weaponname, "tf_weapon_flamethrower") != 0) @@ -2930,18 +2829,18 @@ public WeaponSwitch(client, weapon) } } -stock DoSelfDamage(client, weapon) +stock void DoSelfDamage(int client, int weapon) { - new Float:damage; - new attacker = client; + float damage; + int attacker = client; - new String:weaponIndex[10]; + char weaponIndex[10]; IntToString(GetEntProp(weapon, Prop_Send, "m_iItemDefinitionIndex"), weaponIndex, sizeof(weaponIndex)); - new String:weaponname[64]; + char weaponname[64]; GetEntityClassname(weapon, weaponname, sizeof(weaponname)); - if (!GetTrieValue(g_hWeaponSelfDamage, weaponIndex, damage)) + if (!g_hWeaponSelfDamage.GetValue(weaponIndex, damage)) { damage = 10.0; } @@ -2958,10 +2857,9 @@ stock DoSelfDamage(client, weapon) SDKHooks_TakeDamage(client, client, attacker, damage, DMG_PREVENT_PHYSICS_FORCE); } -stock AddVelocity (client, Float:speed){ - new Float:velocity[3]; +stock void AddVelocity(int client, float speed){ + float velocity[3]; GetEntPropVector(client, Prop_Data, "m_vecVelocity", velocity); - //GetEntDataVector(client, g_iVelocity, velocity); // fucking win if(velocity[0] < 200 && velocity[0] > -200) @@ -2974,7 +2872,7 @@ stock AddVelocity (client, Float:speed){ TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, velocity); } -public Action:SetTransmitHook(entity, client) +public Action SetTransmitHook(int entity, int client) { if(g_First[client] && client == entity) { @@ -2983,7 +2881,7 @@ public Action:SetTransmitHook(entity, client) return Plugin_Continue; } -public PreThinkHook(client) +public void PreThinkHook(int client) { if (!g_Enabled) return; @@ -3000,7 +2898,7 @@ public PreThinkHook(client) ResetSpeed(client); } - new buttons = GetClientButtons(client); + int buttons = GetClientButtons(client); if((buttons & IN_ATTACK) == IN_ATTACK && GetClientTeam(client) == TEAM_HUNTER) { g_Attacking[client] = true; @@ -3024,7 +2922,7 @@ public PreThinkHook(client) if(!g_RotLocked[client]) { - new Float:velocity[3]; + float velocity[3]; GetEntPropVector(client, Prop_Data, "m_vecVelocity", velocity); //GetEntDataVector(client, g_iVelocity, velocity); // if the client is moving, don't allow them to lock in place @@ -3094,24 +2992,14 @@ public PreThinkHook(client) else if(GetClientTeam(client) == TEAM_HUNTER && TF2_GetPlayerClass(client) == TFClass_Pyro) { - /* - if(IsValidEntity(GetPlayerWeaponSlot(client, 1)) && GetEntProp(GetPlayerWeaponSlot(client, 1), Prop_Send, "m_iItemDefinitionIndex") == WEP_SHOTGUNPYRO || IsValidEntity(GetPlayerWeaponSlot(client, 1)) && GetEntProp(GetPlayerWeaponSlot(client, 1), Prop_Send, "m_iItemDefinitionIndex") == WEP_SHOTGUN_UNIQUE) - { - SetEntData(client, FindDataMapOffs(client, "m_iAmmo") + 8, SHOTGUN_MAX_AMMO-GetEntData(GetPlayerWeaponSlot(client, 1), FindSendPropInfo("CBaseCombatWeapon", "m_iClip1"))); - if(GetEntData(GetPlayerWeaponSlot(client, 1), FindSendPropInfo("CBaseCombatWeapon", "m_iClip1")) > SHOTGUN_MAX_AMMO) - { - SetEntData(GetPlayerWeaponSlot(client, 1), FindSendPropInfo("CBaseCombatWeapon", "m_iClip1"), SHOTGUN_MAX_AMMO); - } - } - */ - new shotgun = GetPlayerWeaponSlot(client, 1); + int shotgun = GetPlayerWeaponSlot(client, 1); if(IsValidEntity(shotgun)) { - new index = GetEntProp(shotgun, Prop_Send, "m_iItemDefinitionIndex"); + int index = GetEntProp(shotgun, Prop_Send, "m_iItemDefinitionIndex"); if (index == WEP_SHOTGUNPYRO || index == WEP_SHOTGUN_UNIQUE) { - new ammoOffset = GetEntProp(shotgun, Prop_Send, "m_iPrimaryAmmoType"); - new clip = GetEntProp(shotgun, Prop_Send, "m_iClip1"); + int ammoOffset = GetEntProp(shotgun, Prop_Send, "m_iPrimaryAmmoType"); + int clip = GetEntProp(shotgun, Prop_Send, "m_iClip1"); SetEntProp(client, Prop_Send, "m_iAmmo", SHOTGUN_MAX_AMMO - clip, _, ammoOffset); if (clip > SHOTGUN_MAX_AMMO) { @@ -3126,18 +3014,10 @@ public PreThinkHook(client) } // in game } -/* -public SetupRoundTime(time) -{ - g_RoundTimer = CreateTimer(float(time-1), Timer_TimeUp, _, TIMER_FLAG_NO_MAPCHANGE); - SetConVarInt(g_hArenaRoundTime, time, true, false); -} -*/ - -public GetClassCount(TFClassType:class, team) +public int GetClassCount(TFClassType class, int team) { - new classCount; - for(new client = 1; client <= MaxClients; client++) + int classCount; + for(int client = 1; client <= MaxClients; client++) { if (IsClientInGame(client) && GetClientTeam(client) == team && TF2_GetPlayerClass(client) == class) { @@ -3147,8 +3027,7 @@ public GetClassCount(TFClassType:class, team) return classCount; } - -public Action:Command_motd(client, args) +public Action Command_motd(int client, int args) { if (client <= 0) { @@ -3162,7 +3041,7 @@ public Action:Command_motd(client, args) return Plugin_Handled; } -public Action:Command_config(client, args) +public Action Command_config(int client, int args) { if (client == 0) { @@ -3179,18 +3058,18 @@ public Action:Command_config(client, args) return Plugin_Handled; } -DisplayConfigToConsole(client) +void DisplayConfigToConsole(int client) { - decl String:propMenuStatus[18]; - decl String:propRerollStatus[18]; - decl String:preventFallDamage[4]; - decl String:propChangeRestrict[4]; - decl String:airblast[4]; - decl String:propMenuRestrict[4]; - new propMenu = GetConVarInt(g_PHPropMenu); - new propReroll = GetConVarInt(g_PHReroll); + char propMenuStatus[18]; + char propRerollStatus[18]; + char preventFallDamage[4]; + char propChangeRestrict[4]; + char airblast[4]; + char propMenuRestrict[4]; + int propMenu = g_PHPropMenu.IntValue; + int propReroll = g_PHReroll.IntValue; - if (GetConVarBool(g_PHAirblast)) + if (g_PHAirblast.BoolValue) { airblast = "On"; } @@ -3217,7 +3096,7 @@ DisplayConfigToConsole(client) } } - if (GetConVarBool(g_PHPropMenuRestrict)) + if (g_PHPropMenuRestrict.BoolValue) { propMenuRestrict = "On"; } @@ -3226,7 +3105,7 @@ DisplayConfigToConsole(client) propMenuRestrict = "Off"; } - if (GetConVarBool(g_PHDamageBlocksPropChange)) + if (g_PHDamageBlocksPropChange.BoolValue) { propChangeRestrict = "On"; } @@ -3253,7 +3132,7 @@ DisplayConfigToConsole(client) } } - if (GetConVarBool(g_PHPreventFallDamage)) + if (g_PHPreventFallDamage.BoolValue) { preventFallDamage = "On"; } @@ -3262,32 +3141,29 @@ DisplayConfigToConsole(client) preventFallDamage = "Off"; } - ReplyToCommand(client, "%t", "#TF_PH_ConfigName"); - ReplyToCommand(client, "---------------------"); - ReplyToCommand(client, "%t", "#TF_PH_ConfigVersion", PL_VERSION); - ReplyToCommand(client, "%t", "#TF_PH_ConfigAirblast", airblast); - ReplyToCommand(client, "%t", "#TF_PH_ConfigPropMenu", propMenuStatus); - ReplyToCommand(client, "%t", "#TF_PH_ConfigPropMenuRestrict", propMenuRestrict); - ReplyToCommand(client, "%t", "#TF_PH_ConfigPropChange", propChangeRestrict); - ReplyToCommand(client, "%t", "#TF_PH_ConfigPropReroll", propRerollStatus); - ReplyToCommand(client, "%t", "#TF_PH_ConfigPreventFallDamage", preventFallDamage); - ReplyToCommand(client, "%t", "#TF_PH_ConfigSetupTime", GetConVarInt(g_PHSetupLength)); -#if defined STATS - ReplyToCommand(client, "%t", "#TF_PH_ConfigStats"); -#endif + CReplyToCommand(client, "%t", "#TF_PH_ConfigName"); + CReplyToCommand(client, "---------------------"); + CReplyToCommand(client, "%t", "#TF_PH_ConfigVersion", PL_VERSION); + CReplyToCommand(client, "%t", "#TF_PH_ConfigAirblast", airblast); + CReplyToCommand(client, "%t", "#TF_PH_ConfigPropMenu", propMenuStatus); + CReplyToCommand(client, "%t", "#TF_PH_ConfigPropMenuRestrict", propMenuRestrict); + CReplyToCommand(client, "%t", "#TF_PH_ConfigPropChange", propChangeRestrict); + CReplyToCommand(client, "%t", "#TF_PH_ConfigPropReroll", propRerollStatus); + CReplyToCommand(client, "%t", "#TF_PH_ConfigPreventFallDamage", preventFallDamage); + CReplyToCommand(client, "%t", "#TF_PH_ConfigSetupTime", g_PHSetupLength.IntValue); } -public Handler_ConfigMenu(Handle:menu, MenuAction:action, param1, param2) +public int Handler_ConfigMenu(Menu menu, MenuAction action, int param1, int param2) { switch (action) { case MenuAction_Display: { - decl String:buffer[255]; + char buffer[255]; Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigName", param1); - new Handle:panel = Handle:param2; - SetPanelTitle(panel, buffer); + Panel panel = view_as(param2); + panel.SetTitle(buffer); } case MenuAction_Select: @@ -3297,9 +3173,9 @@ public Handler_ConfigMenu(Handle:menu, MenuAction:action, param1, param2) case MenuAction_DisplayItem: { - decl String:infoBuf[64]; - GetMenuItem(menu, param2, infoBuf, sizeof(infoBuf)); - new String:buffer[255]; + char infoBuf[64]; + menu.GetItem(param2, infoBuf, sizeof(infoBuf)); + char buffer[255]; if (StrEqual(infoBuf, "#version")) { @@ -3308,24 +3184,14 @@ public Handler_ConfigMenu(Handle:menu, MenuAction:action, param1, param2) else if (StrEqual(infoBuf, "#airblast")) { - decl String:airblast[4]; - if (GetConVarBool(g_PHAirblast)) - { - airblast = "On"; - } - else - { - airblast = "Off"; - } - - Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigAirblast", param1, airblast); + Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigAirblast", param1, g_PHAirblast.BoolValue ? "On" : "Off"); } else if (StrEqual(infoBuf, "#propmenu")) { - new propMenu = GetConVarInt(g_PHPropMenu); + int propMenu = g_PHPropMenu.IntValue; - decl String:propMenuStatus[25]; + char propMenuStatus[25]; switch (propMenu) { @@ -3357,39 +3223,19 @@ public Handler_ConfigMenu(Handle:menu, MenuAction:action, param1, param2) else if (StrEqual(infoBuf, "#proprestrict")) { - decl String:propMenuRestrict[4]; - if (GetConVarBool(g_PHPropMenuRestrict)) - { - propMenuRestrict = "On"; - } - else - { - propMenuRestrict = "Off"; - } - - Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigPropMenuRestrict", param1, propMenuRestrict); + Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigPropMenuRestrict", param1, g_PHPropMenuRestrict.BoolValue ? "On" : "Off"); } else if (StrEqual(infoBuf, "#propdamage")) { - decl String:propChangeRestrict[4]; - if (GetConVarBool(g_PHDamageBlocksPropChange)) - { - propChangeRestrict = "On"; - } - else - { - propChangeRestrict = "Off"; - } - - Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigPropChange", param1, propChangeRestrict); + Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigPropChange", param1, g_PHDamageBlocksPropChange.BoolValue ? "On" : "Off"); } else if (StrEqual(infoBuf, "#propreroll")) { - new propReroll = GetConVarInt(g_PHReroll); + int propReroll = g_PHReroll.IntValue; - decl String:propRerollStatus[25]; + char propRerollStatus[25]; switch (propReroll) { @@ -3421,30 +3267,13 @@ public Handler_ConfigMenu(Handle:menu, MenuAction:action, param1, param2) else if (StrEqual(infoBuf, "#preventfalldamage")) { - decl String:preventFallDamage[4]; - if (GetConVarBool(g_PHPreventFallDamage)) - { - preventFallDamage = "On"; - } - else - { - preventFallDamage = "Off"; - } - - Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigPreventFallDamage", param1, preventFallDamage); + Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigPreventFallDamage", param1, g_PHPreventFallDamage.BoolValue ? "On" : "Off"); } else if (StrEqual(infoBuf, "#setuptime")) { - Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigSetupTime", param1, GetConVarInt(g_PHSetupLength)); + Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigSetupTime", param1, g_PHSetupLength.IntValue); } -#if defined STATS - else - if (StrEqual(infoBuf, "#stats")) - { - Format(buffer, sizeof(buffer), "%T", "#TF_PH_ConfigStats", param1); - } -#endif return RedrawMenuItem(buffer); } @@ -3452,100 +3281,50 @@ public Handler_ConfigMenu(Handle:menu, MenuAction:action, param1, param2) return 0; } -stock SetAlpha (target, alpha){ +stock void SetAlpha(int target, int alpha) { SetWeaponsAlpha(target,alpha); SetEntityRenderMode(target, RENDER_TRANSCOLOR); SetEntityRenderColor(target, 255, 255, 255, alpha); } -stock SetWeaponsAlpha (target, alpha){ +stock void SetWeaponsAlpha(int target, int alpha) { if(IsPlayerAlive(target)) { - //decl String:classname[64]; - - /* - // Old version - new m_hMyWeapons = FindSendPropOffs("CBasePlayer", "m_hMyWeapons"); - for(new i = 0, weapon; i < 47; i += 4) - { - weapon = GetEntDataEnt2(target, m_hMyWeapons + i); - if(weapon > -1 && IsValidEdict(weapon)) - { - GetEdictClassname(weapon, classname, sizeof(classname)); - if(StrContains(classname, "tf_weapon", false) != -1) - { - SetEntityRenderMode(weapon, RENDER_TRANSCOLOR); - SetEntityRenderColor(weapon, 255, 255, 255, alpha); - } - } - } - */ - - /* - // "New" old version - // There are 47 weapon slots of offsets 0 - 188, the previous code was unfortunately wrong. - for(new i = 0, weapon; i <= 47; ++i) - { - weapon = GetEntPropEnt(target, Prop_Send, "m_hMyWeapons", i); - if(weapon > -1 && IsValidEdict(weapon)) - { - GetEdictClassname(weapon, classname, sizeof(classname)); - if(StrContains(classname, "tf_weapon", false) != -1) - { - SetEntityRenderMode(weapon, RENDER_TRANSCOLOR); - SetEntityRenderColor(weapon, 255, 255, 255, alpha); - } - } - } - */ - // TF2 only supports 1 weapon per slot, so save time and just check all 6 slots. // Engy is the only class with 6 items (3 weapons, 2 tools, and an invisible builder) - for(new i = 0; i <= 5; ++i) + for(int i = 0; i <= 5; ++i) { - new weapon = GetPlayerWeaponSlot(target, i); + int weapon = GetPlayerWeaponSlot(target, i); SetItemAlpha(weapon, alpha); } } } -stock SetItemAlpha(item, alpha) +stock void SetItemAlpha(int item, int alpha) { if(item > -1 && IsValidEdict(item)) { - // Don't bother checking the classname, it's always tf_weapon_[something] in TF2 for GetPlayerWeaponSlot SetEntityRenderMode(item, RENDER_TRANSCOLOR); SetEntityRenderColor(item, 255, 255, 255, alpha); - new String:classname[65]; + char classname[65]; GetEntityClassname(item, classname, sizeof(classname)); + // If it's a weapon, lets adjust the alpha on its extra wearable and its viewmodel too if (strncmp(classname, "tf_weapon_", 10) == 0) { - // It's a weapon, lets hide the extra wearables too - new extraWearable = GetEntPropEnt(item, Prop_Send, "m_hExtraWearable"); - if(extraWearable > -1 && IsValidEdict(extraWearable)) - { - SetEntityRenderMode(extraWearable, RENDER_TRANSCOLOR); - SetEntityRenderColor(extraWearable, 255, 255, 255, alpha); - } - - extraWearable = GetEntPropEnt(item, Prop_Send, "m_hExtraWearableViewModel"); - if(extraWearable > -1 && IsValidEdict(extraWearable)) - { - SetEntityRenderMode(extraWearable, RENDER_TRANSCOLOR); - SetEntityRenderColor(extraWearable, 255, 255, 255, alpha); - } + SetItemAlpha(GetEntPropEnt(item, Prop_Send, "m_hExtraWearable"), alpha); + SetItemAlpha(GetEntPropEnt(item, Prop_Send, "m_hExtraWearableViewModel"), alpha); } } } -public Speedup (client) +public void Speedup(int client) { - new TFClassType:clientClass = TF2_GetPlayerClass(client); + TFClassType clientClass = TF2_GetPlayerClass(client); - new Float:clientSpeed = g_currentSpeed[client] + g_classSpeeds[clientClass][2]; + float clientSpeed = g_currentSpeed[client] + g_classSpeeds[clientClass][2]; if(clientSpeed < g_classSpeeds[clientClass][0]) { @@ -3561,23 +3340,23 @@ public Speedup (client) g_currentSpeed[client] = clientSpeed; } -ResetSpeed (client) +void ResetSpeed(int client) { SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", g_currentSpeed[client]); } -public bool:TraceRayDontHitSelf(entity, mask, any:data) +public bool TraceRayDontHitSelf(int entity, int mask, any data) { return entity != data; } -public Action:Event_teamplay_broadcast_audio(Handle:event, const String:name[], bool:dontBroadcast) +public Action Event_teamplay_broadcast_audio(Event event, const char[] name, bool dontBroadcast) { if (!g_Enabled) return Plugin_Continue; - decl String:sound[64]; - GetEventString(event, "sound", sound, sizeof(sound)); + char sound[64]; + event.GetString("sound", sound, sizeof(sound)); if (StrEqual(sound, "Announcer.AM_RoundStartRandom", false)) { @@ -3587,17 +3366,17 @@ public Action:Event_teamplay_broadcast_audio(Handle:event, const String:name[], return Plugin_Continue; } -public Event_player_team(Handle:event, const String:name[], bool:dontBroadcast) +public void Event_player_team(Event event, const char[] name, bool dontBroadcast) { - new client = GetClientOfUserId(GetEventInt(event, "userid")); - if(GetEventInt(event, "team") > 1) + int client = GetClientOfUserId(GetEventInt(event, "userid")); + if(event.GetInt("team") > 1) { g_Spec[client] = false; } g_RoundStartMessageSent[client] = false; } -public Event_arena_win_panel(Handle:event, const String:name[], bool:dontBroadcast) +public void Event_arena_win_panel(Event event, const char[] name, bool dontBroadcast) { StopTimers(); #if defined LOG @@ -3611,111 +3390,30 @@ public Event_arena_win_panel(Handle:event, const String:name[], bool:dontBroadca if (!g_Enabled) return; -#if defined STATS || defined LOCALSTATS - new winner = GetEventInt(event, "winning_team"); -#if defined STATS - DbRound(winner); + if (Internal_ShouldSwitchTeams()) + { +#if defined SWITCH_TEAMS + // This is OK as arena_win_panel is fired *after* SetWinningTeam is called. + SetSwitchTeams(true); +#else + CreateTimer(g_hBonusRoundTime.FloatValue - TEAM_CHANGE_TIME, Timer_ChangeTeam, _, TIMER_FLAG_NO_MAPCHANGE); #endif + } -#if defined LOCALSTATS - LocalStats_DbRound(winner); -#endif + g_hTeamsUnbalanceLimit.IntValue = 0; -#endif + // Players are now reset on round start instead of round end + g_hTeamsUnbalanceLimit.Flags = g_hTeamsUnbalanceLimit.Flags & ~FCVAR_NOTIFY; + g_hTeamsUnbalanceLimit.IntValue = UNBALANCE_LIMIT; +} -#if defined DHOOKS - if (!g_DHooks || g_SetWinningTeamHook == -1) +public Action Timer_ChangeTeam(Handle timer) +{ + for (int client = 1; client <= MaxClients; ++client) { -#endif - if (ShouldSwitchTeams()) + if (!IsClientInGame(client)) { - CreateTimer(GetConVarFloat(g_hBonusRoundTime) - TEAM_CHANGE_TIME, Timer_ChangeTeam, _, TIMER_FLAG_NO_MAPCHANGE); - } -#if defined DHOOKS - } -#endif - - SetConVarInt(g_hTeamsUnbalanceLimit, 0, true); - - for(new client=1; client <= MaxClients; client++) - { - if(IsClientInGame(client)) - { -#if defined STATS || defined LOCALSTATS - if(GetClientTeam(client) == winner) - { -#if defined STATS - AlterScore(client, 3, ScReason_TeamWin, 0); -#endif - -#if defined LOCALSTATS - LocalStats_AlterScore(client, 3, ScReason_TeamWin, 0); -#endif - } - else - if(GetClientTeam(client) != _:TFTeam_Spectator) - { -#if defined STATS - AlterScore(client, -1, ScReason_TeamLose, 0); -#endif - -#if defined LOCALSTATS - LocalStats_AlterScore(client, -1, ScReason_TeamLose, 0); -#endif - } -#endif - // bit annoying when testing the plugin and/or maps on a listen server - /* - if(IsDedicatedServer()) - { - team = GetClientTeam(client); - if(team == TEAM_PROP || team == TEAM_HUNTER) - { - team = team == TEAM_PROP ? TEAM_HUNTER:TEAM_PROP; - ChangeClientTeamAlive(client, team); - } - } - */ - } - //ResetPlayer(client); // Players are now reset on round start instead of round end - } -/* -#if defined LOG - LogMessage("Team balancing..."); -#endif - decl String:cname[64]; - while(GetTeamClientCount(TEAM_PROP) > GetTeamClientCount(TEAM_HUNTER) + 1 ) - { - client = GetRandomPlayer(TEAM_PROP); - GetClientName(client, cname, sizeof(cname)); - CPrintToChatAll("%t", "#TF_PH_BalanceBlu", cname); - ChangeClientTeamAlive(client, TEAM_HUNTER); - } - while(GetTeamClientCount(TEAM_HUNTER) > GetTeamClientCount(TEAM_PROP) +1 ) - { - client = GetRandomPlayer(TEAM_HUNTER); - GetClientName(client, cname, sizeof(cname)); - CPrintToChatAll("%t", "#TF_PH_BalanceRed", cname); - ChangeClientTeamAlive(client, TEAM_PROP); - } -#if defined LOG - LogMessage("Complete"); -#endif -*/ - - SetConVarFlags(g_hTeamsUnbalanceLimit, GetConVarFlags(g_hTeamsUnbalanceLimit) & ~(FCVAR_NOTIFY)); - SetConVarInt(g_hTeamsUnbalanceLimit, UNBALANCE_LIMIT, true); - - //StopPreroundTimers(false); -} - -public Action:Timer_ChangeTeam(Handle:timer) -{ - for (new client = 1; client <= MaxClients; ++client) - { - if (!IsClientInGame(client)) - { - continue; + continue; } if (GetClientTeam(client) == TEAM_HUNTER) @@ -3725,71 +3423,36 @@ public Action:Timer_ChangeTeam(Handle:timer) else if (GetClientTeam(client) == TEAM_PROP) { - RemoveAnimeModel(client); + RemovePropModel(client); ChangeClientTeamAlive(client, TEAM_HUNTER); } } - return Plugin_Continue; } -/* -public Action:Event_teamplay_round_start_pre(Handle:event, const String:name[], bool:dontBroadcast) -{ - if (!g_Enabled) - return; - - new bool:reset = GetEventBool(event, "full_reset"); -#if defined LOG - LogMessage("[PH] teamplay round start: %i, %i", reset, g_RoundOver); -#endif - // checking for the first time this calls (pre-setup), i think - if(reset && g_RoundOver) - { - new team, zteam=TEAM_HUNTER; - for(new client=1; client <= MaxClients; client++) - { - if(IsClientInGame(client)) - { - - team = GetClientTeam(client); - - // prevent sitting out - if(team == TEAM_SPEC && !g_Spec[client]) - { - ChangeClientTeam(client, zteam); - zteam = zteam == TEAM_HUNTER ? TEAM_PROP:TEAM_HUNTER; - } - - } - } - } -} -*/ - -public Event_post_inventory_application(Handle:event, const String:name[], bool:dontBroadcast) +public void Event_post_inventory_application(Event event, const char[] name, bool dontBroadcast) { - new client = GetClientOfUserId(GetEventInt(event, "userid")); + int client = GetClientOfUserId(GetEventInt(event, "userid")); if (g_ReplacementCount[client] > 0) { - for (new i = 0; i < g_ReplacementCount[client]; ++i) + for (int i = 0; i < g_ReplacementCount[client]; ++i) { // DON'T require FORCE_GENERATION here, since they could pass back tf_weapon_shotgun - new Handle:weapon = TF2Items_CreateItem(OVERRIDE_ALL); + Handle weapon = TF2Items_CreateItem(OVERRIDE_ALL|PRESERVE_ATTRIBUTES); - new String:defIndex[7]; + char defIndex[7]; IntToString(g_Replacements[client][i], defIndex, sizeof(defIndex)); - new String:replacement[140]; - if (!GetTrieString(g_hWeaponReplacements, defIndex, replacement, sizeof(replacement))) + char replacement[140]; + if (!g_hWeaponReplacements.GetString(defIndex, replacement, sizeof(replacement))) { continue; } - new String:pieces[5][128]; + char pieces[5][128]; ExplodeString(replacement, ":", pieces, sizeof(pieces), sizeof(pieces[])); @@ -3799,127 +3462,117 @@ public Event_post_inventory_application(Handle:event, const String:name[], bool: TrimString(pieces[Item_Level]); TrimString(pieces[Item_Attributes]); - new index = StringToInt(pieces[Item_Index]); - new quality = StringToInt(pieces[Item_Quality]); - new level = StringToInt(pieces[Item_Level]); + int index = StringToInt(pieces[Item_Index]); + int quality = StringToInt(pieces[Item_Quality]); + int level = StringToInt(pieces[Item_Level]); TF2Items_SetClassname(weapon, pieces[Item_Classname]); TF2Items_SetItemIndex(weapon, index); TF2Items_SetQuality(weapon, quality); TF2Items_SetLevel(weapon, level); - new attribCount = 0; + int attribCount = 0; if (strlen(pieces[Item_Attributes]) > 0) { - new String:newAttribs[32][6]; - new count = ExplodeString(pieces[Item_Attributes], ";", newAttribs, sizeof(newAttribs), sizeof(newAttribs[])); + char newAttribs[32][6]; + int count = ExplodeString(pieces[Item_Attributes], ";", newAttribs, sizeof(newAttribs), sizeof(newAttribs[])); if (count % 2 > 0) { LogError("Error parsing replacement attributes for item definition index %d", g_Replacements[client][i]); return; } - for (new j = 0; j < count && attribCount < 16; j += 2) + for (int j = 0; j < count && attribCount < 16; j += 2) { - new attrib = StringToInt(newAttribs[i]); - new Float:value = StringToFloat(newAttribs[i+1]); + TrimString(newAttribs[i]); + TrimString(newAttribs[i+1]); + int attrib = StringToInt(newAttribs[i]); + float value = StringToFloat(newAttribs[i+1]); TF2Items_SetAttribute(weapon, attribCount++, attrib, value); } } TF2Items_SetNumAttributes(weapon, attribCount); - new item = TF2Items_GiveNamedItem(client, weapon); + int item = TF2Items_GiveNamedItem(client, weapon); EquipPlayerWeapon(client, item); g_Replacements[client][i] = -1; - CloseHandle(weapon); + delete weapon; } g_ReplacementCount[client] = 0; - } - - TF2Attrib_ChangeBoolAttrib(client, "cancel falling damage", GetConVarBool(g_PHPreventFallDamage)); -} - -TF2Attrib_ChangeBoolAttrib(entity, String:attribute[], bool:value) -{ - if (!g_TF2Attribs) - { - return; - } - - if (value) - { - TF2Attrib_SetByName(entity, attribute, 1.0); - } - else if (TF2Attrib_GetByName(entity, attribute) != Address_Null) - { - TF2Attrib_RemoveByName(entity, attribute); + // Now that we're adjusting weapons, this needs to happen to fix max ammo counts + //TF2_RegeneratePlayer(client); } } -public Event_teamplay_round_start(Handle:event, const String:name[], bool:dontBroadcast) +public Action Event_teamplay_round_start_pre(Event event, const char[] name, bool dontBroadcast) { - StopTimers(); - switch (g_RoundChange) { case RoundChange_Enable: { g_Enabled = true; SetCVars(); -// Moved to after g_Enabled check -//#if defined DHOOKS -// RegisterDHooks(); -//#endif UpdateGameDescription(); g_RoundChange = RoundChange_NoChange; g_RoundCurrent = 0; + + Internal_AddServerTag(); } case RoundChange_Disable: { g_Enabled = false; ResetCVars(); -#if defined DHOOKS - UnregisterDHooks(); -#endif UpdateGameDescription(); g_RoundChange = RoundChange_NoChange; + + Internal_RemoveServerTag(); } } + return Plugin_Continue; +} + +public void Event_teamplay_round_start(Event event, const char[] name, bool dontBroadcast) +{ + if (HasSwitchedTeams()) + { + SwitchTeamScoresClassic(); + } + + StopTimers(); + if (!g_Enabled) return; - // This is being called because TF2 sometimes destroys the gamerules object when there are 0-1 players at round end. - // This has the side effect of unsetting our hooks. Luckily, we have checks in place to prevent double hooking. -#if defined DHOOKS - RegisterDHooks(); -#endif + CheckRoundTimer(); g_inPreRound = true; + g_PlayerDied = false; + g_flRoundStart = 0.0; // This is now in round start after an issue was reported with last prop not resetting in 3.0.2 g_LastProp = false; g_LastPropPlayer = 0; g_RoundOver = true; - for (new client = 1; client <= MaxClients; client++) + for (int client = 1; client <= MaxClients; client++) { ResetPlayer(client); if (IsClientInGame(client) && !IsFakeClient(client)) { // For some reason, this has to be set every round or the player GUI messes up - SendConVarValue(client, g_hArenaRoundTime, "600"); + g_hArenaRoundTime.ReplicateToClient(client, "600"); } } // Delay freeze by a frame RequestFrame(Delay_teamplay_round_start); // Arena maps should have a team_control_point_master already, but just in case... - new ent = FindEntityByClassname(-1, "team_control_point_master"); + int ent = FindEntityByClassname(-1, "team_control_point_master"); if (ent == -1) { ent = CreateEntityByName("team_control_point_master"); @@ -3929,27 +3582,28 @@ public Event_teamplay_round_start(Handle:event, const String:name[], bool:dontBr } //GameMode Explanation - decl String:message[256]; - ent = FindEntityByClassname(-1, "tf_gamerules"); // Can't use sdktools_gamerules for this + char message[256]; + // Not sure this has to be done every round, but we'll keep it here just in case. + //BLU Format(message, sizeof(message), "%T", "#TF_PH_BluHelp", LANG_SERVER); SetVariantString(message); - AcceptEntityInput(ent, "SetBlueTeamGoalString"); + AcceptEntityInput(g_GameRulesProxy, "SetBlueTeamGoalString"); SetVariantString("2"); - AcceptEntityInput(ent, "SetBlueTeamRole"); + AcceptEntityInput(g_GameRulesProxy, "SetBlueTeamRole"); //RED Format(message, sizeof(message), "%T", "#TF_PH_RedHelp", LANG_SERVER); SetVariantString(message); - AcceptEntityInput(ent, "SetRedTeamGoalString"); + AcceptEntityInput(g_GameRulesProxy, "SetRedTeamGoalString"); SetVariantString("1"); - AcceptEntityInput(ent, "SetRedTeamRole"); + AcceptEntityInput(g_GameRulesProxy, "SetRedTeamRole"); } -public Delay_teamplay_round_start(any:data) +public void Delay_teamplay_round_start(any data) { - for (new i = 1; i <= MaxClients; i++) + for (int i = 1; i <= MaxClients; i++) { if (IsClientInGame(i) && !IsClientObserver(i)) { @@ -3958,12 +3612,12 @@ public Delay_teamplay_round_start(any:data) } } -public Event_teamplay_restart_round(Handle:event, const String:name[], bool:dontBroadcast) +public void Event_teamplay_restart_round(Event event, const char[] name, bool dontBroadcast) { // I'm not sure what needs to be here... does teamplay_round_start get called on round restart? } -public Event_arena_round_start(Handle:event, const String:name[], bool:dontBroadcast) +public void Event_arena_round_start(Event event, const char[] name, bool dontBroadcast) { #if defined LOG LogMessage("[PH] round start - %i", g_RoundOver ); @@ -3973,6 +3627,8 @@ public Event_arena_round_start(Handle:event, const String:name[], bool:dontBroad if (!g_Enabled) return; + g_inSetup = true; + g_RoundCurrent++; StartTimers(true); @@ -3982,19 +3638,14 @@ public Event_arena_round_start(Handle:event, const String:name[], bool:dontBroad // bl4nk mentions arena_round_start, but I think he meant teamplay_round_start RequestFrame(Delay_arena_round_start); - //SetupRoundTime(g_RoundTime); - CreateTimer(0.1, Timer_Info); -#if defined STATS || defined LOCALSTATS - g_StartTime = GetTime(); -#endif } } -public Delay_arena_round_start(any:data) +public void Delay_arena_round_start(any data) { - for(new client=1; client <= MaxClients; client++) + for(int client=1; client <= MaxClients; client++) { if(IsClientInGame(client) && IsPlayerAlive(client)) { @@ -4016,7 +3667,7 @@ public Delay_arena_round_start(any:data) } } -stock bool:IsValidClient(client, bool:replaycheck = true) +stock bool IsValidClient(int client, bool replaycheck = true) { if (client <= 0 || client > MaxClients) return false; if (!IsClientInGame(client)) return false; @@ -4028,22 +3679,15 @@ stock bool:IsValidClient(client, bool:replaycheck = true) return true; } -public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) +public void Event_player_spawn(Event event, const char[] name, bool dontBroadcast) { if (!g_Enabled) return; - new red = TEAM_PROP - 2; - new blue = TEAM_HUNTER - 2; - new client = GetClientOfUserId(GetEventInt(event, "userid")); + int red = TEAM_PROP - 2; + int blue = TEAM_HUNTER - 2; + int client = GetClientOfUserId(event.GetInt("userid")); - // Lets remove this since we wipe their model later anyway - /* - if (IsValidClient(client)){ - RemoveAnimeModel(client); - } - */ - g_currentSpeed[client] = g_classSpeeds[TF2_GetPlayerClass(client)][0]; // Reset to default speed. if(IsClientInGame(client) && IsPlayerAlive(client)) @@ -4052,12 +3696,13 @@ public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) if(!g_RoundOver && !g_AllowedSpawn[client]) { #if defined LOG - LogMessage("%N spawned outside of a round"); + LogMessage("[PH] %N spawned outside of a round"); #endif ForcePlayerSuicide(client); return; } - RemoveAnimeModel(client); + // Wipe their model in case they were a prop last round and lived + RemovePropModel(client); SDKHook(client, SDKHook_OnTakeDamage, TakeDamageHook); SDKHook(client, SDKHook_PreThink, PreThinkHook); #if defined LOG @@ -4065,7 +3710,7 @@ public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) #endif g_Hit[client] = false; - new team = GetClientTeam(client); + int team = GetClientTeam(client); if(team == TEAM_HUNTER) { if (!g_RoundStartMessageSent[client]) @@ -4075,24 +3720,35 @@ public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) } #if defined SHINX - new TFClassType:clientClass = TF2_GetPlayerClass(client); + TFClassType clientClass = TF2_GetPlayerClass(client); if (g_classLimits[blue][clientClass] != -1 && GetClassCount(clientClass, TEAM_HUNTER) > g_classLimits[blue][clientClass]) { if(g_classLimits[blue][clientClass] == 0) { + // By default, this fires when they're Scout //CPrintToChat(client, "%t", "#TF_PH_ClassBlocked"); } else { CPrintToChat(client, "%t", "#TF_PH_ClassFull"); } + + if (g_PreferredHunterClass[client] != TFClass_Unknown && g_PreferredHunterClass[client] != clientClass) + { + TF2_SetPlayerClass(client, g_PreferredHunterClass[client]); + } + else + { + TF2_SetPlayerClass(client, g_defaultClass[blue]); + } - TF2_SetPlayerClass(client, g_defaultClass[blue]); TF2_RespawnPlayer(client); return; } + // If we didn't just change their class, store it for later + g_PreferredHunterClass[client] = clientClass; #else if(TF2_GetPlayerClass(client) != g_defaultClass[blue]) { @@ -4117,7 +3773,7 @@ public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) if(TF2_GetPlayerClass(client) != g_defaultClass[red]) { - TF2_SetPlayerClass(client, TFClassType:g_defaultClass[red]); + TF2_SetPlayerClass(client, view_as(g_defaultClass[red]), false, false); TF2_RespawnPlayer(client); return; // This was missing prior to PHR 3.3.4 } @@ -4128,7 +3784,7 @@ public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) { // Players are spawning on a non-player team? #if defined LOG - LogMessage("%N spawned on a non-player team: %d, slaying...", client, team); + LogMessage("[PH] %N spawned on a non-player team: %d, slaying...", client, team); #endif ForcePlayerSuicide(client); } @@ -4140,11 +3796,17 @@ public Event_player_spawn(Handle:event, const String:name[], bool:dontBroadcast) } } -public Action:Event_player_death(Handle:event, const String:name[], bool:dontBroadcast) +public Action Event_player_death(Event event, const char[] name, bool dontBroadcast) { if (!g_Enabled || g_inPreRound) return Plugin_Continue; + if (event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) + { + // This shouldn't fire since Spy is usually disabled, but just in case... + return Plugin_Continue; + } + // This should be a separate event now, but we're leaving this in just in case if (GetEventInt(event, "weaponid") == TF_WEAPON_BAT_FISH && GetEventInt(event, "customkill") == TF_CUSTOM_FISH_KILL) { @@ -4152,7 +3814,10 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro } //new bool:changed = false; - new client = GetClientOfUserId(GetEventInt(event, "userid")); + int client = GetClientOfUserId(event.GetInt("userid")); + if (client == 0) + return Plugin_Continue; + g_CurrentlyFlaming[client] = false; g_FlameCount[client] = 0; @@ -4161,7 +3826,7 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro #if defined LOG LogMessage("[PH] Player death %N", client); #endif - //RemoveAnimeModel(client); + //RemovePropModel(client); CreateTimer(0.1, Timer_Ragdoll, GetClientUserId(client)); @@ -4169,26 +3834,23 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro SDKUnhook(client, SDKHook_PreThink, PreThinkHook); } - new attacker = GetClientOfUserId(GetEventInt(event, "attacker")); - new assister = GetClientOfUserId(GetEventInt(event, "assister")); + if(g_inSetup && g_PHRespawnDuringSetup.BoolValue) + { + CreateTimer(0.1, Timer_Respawn, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); + return Plugin_Continue; + } + + int attacker = GetClientOfUserId(event.GetInt("attacker")); + int assister = GetClientOfUserId(event.GetInt("assister")); -#if defined STATS || defined LOCALSTATS - decl String:weapon[64]; - new attackerID = GetEventInt(event, "attacker"); - new assisterID = GetEventInt(event, "assister"); - new clientID = GetEventInt(event, "userid"); - new weaponid = GetEventInt(event, "weaponid"); - GetEventString(event, "weapon", weapon, sizeof(weapon)); -#endif - if(!g_RoundOver) g_Spawned[client] = false; g_Hit[client] = false; // I would move this, but I'm not sure if it's used by STATS - new playas = 0; - for(new i=1; i <= MaxClients; i++) + int playas = 0; + for(int i=1; i <= MaxClients; i++) { if(IsClientInGame(i) /*&& !IsFakeClient(i)*/ && IsPlayerAlive(i) && GetClientTeam(i) == TEAM_PROP) { @@ -4205,6 +3867,7 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro // This is to kill the particle effects from the Harvest Ghost prop and the like // Moved to RED-only section so we don't kill unusual effects. + // Reference: https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/game/shared/particle_parse.cpp#L482 SetVariantString("ParticleEffectStop"); AcceptEntityInput(client, "DispatchEffect"); } @@ -4213,13 +3876,6 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro { if(client > 0 && attacker > 0 && IsClientInGame(client) && IsClientInGame(attacker) && client != attacker) { -#if defined STATS - PlayerKilled(clientID, attackerID, assisterID, weaponid, weapon); -#endif - -#if defined LOCALSTATS - LocalStats_PlayerKilled(clientID, attackerID, assisterID, weaponid, weapon); -#endif if(IsPlayerAlive(attacker)) { Speedup(attacker); @@ -4236,34 +3892,56 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro } } - } + + // We now manually handle First Blood and avoid having the crit effect. + // We also adjust the various sound times to be more in line with PropHunt. + if (!g_PlayerDied) + { + // Fast is 0-30, regular is 31-90, finally is 91+ + int expendedTime = RoundToNearest(GetGameTime() - g_flRoundStart); + if (expendedTime <= 30) + { + PH_EmitSoundToAll("FirstBloodFast"); + } + else if (expendedTime <= 90) + { + PH_EmitSoundToAll("FirstBlood"); + } + else + { + PH_EmitSoundToAll("FirstBloodFinally"); + } + + g_PlayerDied = true; + } - if(!g_LastProp && playas == 2 && !g_RoundOver && GetClientTeam(client) == TEAM_PROP) - { - g_LastProp = true; - PH_EmitSoundToAll("OneAndOnly", _, _, SNDLEVEL_AIRCRAFT); -#if defined SCATTERGUN - for(new client2=1; client2 <= MaxClients; client2++) + if(!g_LastProp && playas == 2 && GetClientTeam(client) == TEAM_PROP) { - if(IsClientInGame(client2) && !IsFakeClient(client2) && IsPlayerAlive(client2)) + g_LastProp = true; + PH_EmitSoundToAll("OneAndOnly", _, _, SNDLEVEL_AIRCRAFT); + #if defined SCATTERGUN + for(int client2=1; client2 <= MaxClients; client2++) { - if(GetClientTeam(client2) == TEAM_PROP) + if(IsClientInGame(client2) && !IsFakeClient(client2) && IsPlayerAlive(client2)) { - g_LastPropPlayer = client2; - TF2_RegeneratePlayer(client2); + if(GetClientTeam(client2) == TEAM_PROP) + { + g_LastPropPlayer = client2; + TF2_RegeneratePlayer(client2); // Replaced by TF2Items_OnGiveNamedItem_Post //CreateTimer(0.1, Timer_WeaponAlpha, GetClientUserId(client2)); - } - else - if(GetClientTeam(client2) == TEAM_HUNTER) - { - TF2_AddCondition(client2, TFCond_Jarated, 15.0); + } + else + if(GetClientTeam(client2) == TEAM_HUNTER) + { + TF2_AddCondition(client2, TFCond_Jarated, 15.0); + } } } + #endif } -#endif } - + return Plugin_Continue; } @@ -4271,34 +3949,43 @@ public Action:Event_player_death(Handle:event, const String:name[], bool:dontBro /////////////////// TIMERS //////////////////////// //////////////////////////////////////////////////// -/* -public Action:Timer_WeaponAlpha(Handle:timer, any:userid) +public Action Timer_Respawn(Handle timer, any userid) { - new client = GetClientOfUserId(userid); + int client = GetClientOfUserId(userid); + if (client > 0) + { + g_AllowedSpawn[client] = true; + TF2_RespawnPlayer(client); + } +} + +public Action Timer_WeaponAlpha(Handle timer, any userid) +{ + int client = GetClientOfUserId(userid); if(client > 0 && IsClientInGame(client) && IsPlayerAlive(client)) SetWeaponsAlpha(client, 0); } -*/ -public Action:Timer_Info(Handle:timer) +// This is used for the fading in of the game mode name and stuff +public Action Timer_Info(Handle timer) { g_Message_bit++; if(g_Message_bit == 2) { SetHudTextParamsEx(-1.0, 0.22, 5.0, {0,204,255,255}, {0,0,0,255}, 2, 1.0, 0.05, 0.5); - for(new i=1; i <= MaxClients; i++) + for(int i=1; i <= MaxClients; i++) { if(IsClientInGame(i) && !IsFakeClient(i)) { - ShowSyncHudText(i, g_Text1, "PropHunt %s", g_Version); + ShowSyncHudText(i, g_Text1, "PropHunt %s", PL_VERSION); } } } else if(g_Message_bit == 3) { SetHudTextParamsEx(-1.0, 0.25, 4.0, {255,128,0,255}, {0,0,0,255}, 2, 1.0, 0.05, 0.5); - for(new i=1; i <= MaxClients; i++) + for(int i=1; i <= MaxClients; i++) { if(IsClientInGame(i) && !IsFakeClient(i)) { @@ -4309,7 +3996,7 @@ public Action:Timer_Info(Handle:timer) else if(g_Message_bit == 4 && strlen(g_AdText) > 0) { SetHudTextParamsEx(-1.0, 0.3, 3.0, {0,220,0,255}, {0,0,0,255}, 2, 1.0, 0.05, 0.5); - for(new i=1; i <= MaxClients; i++) + for(int i=1; i <= MaxClients; i++) { if(IsClientInGame(i) && !IsFakeClient(i)) { @@ -4327,9 +4014,9 @@ public Action:Timer_Info(Handle:timer) } //public Action:Timer_DoEquipBlu(Handle:timer, any:UserId) -public DoEquipHunter(any:UserId) +public void DoEquipHunter(any UserId) { - new client = GetClientOfUserId(UserId); + int client = GetClientOfUserId(UserId); if(client > 0 && IsClientInGame(client) && IsPlayerAlive(client)) { if(g_inPreRound) @@ -4340,11 +4027,11 @@ public DoEquipHunter(any:UserId) SwitchView(client, false, true); SetAlpha(client, 255); - new validWeapons; + int validWeapons; - for (new i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { - new playerItemSlot = GetPlayerWeaponSlot(client, i); + int playerItemSlot = GetPlayerWeaponSlot(client, i); if(playerItemSlot > MaxClients && IsValidEntity(playerItemSlot)) { @@ -4362,9 +4049,9 @@ public DoEquipHunter(any:UserId) } //public Action:Timer_DoEquip(Handle:timer, any:UserId) -public DoEquipProp(any:UserId) +public void DoEquipProp(any UserId) { - new client = GetClientOfUserId(UserId); + int client = GetClientOfUserId(UserId); if(client > 0 && IsClientInGame(client) && IsPlayerAlive(client)) { //TF2_RegeneratePlayer(client); @@ -4372,47 +4059,38 @@ public DoEquipProp(any:UserId) #if defined LOG LogMessage("[PH] do equip %N", client); #endif - // Lets comment this out since we don't block RED weapons with TF2Items - // slot commands fix "remember last weapon" glitch, despite their client console spam - /* - FakeClientCommand(client, "slot0"); - FakeClientCommand(client, "slot3"); - TF2_RemoveAllWeapons(client); - FakeClientCommand(client, "slot3"); - FakeClientCommand(client, "slot0"); - */ - decl String:pname[32]; + char pname[32]; Format(pname, sizeof(pname), "ph_player_%i", client); DispatchKeyValue(client, "targetname", pname); #if defined LOG LogMessage("[PH] do equip_2 %N", client); #endif - new propData[PropData]; + int propData[PropData]; // fire in a nice random model - decl String:model[PLATFORM_MAX_PATH]; - new String:offset[32] = "0 0 0"; - new String:rotation[32] = "0 0 0"; - new skin = 0; - new modelIndex = -1; + char model[PLATFORM_MAX_PATH]; + char offset[32] = "0 0 0"; + char rotation[32] = "0 0 0"; + int skin = 0; + int modelIndex = -1; if(strlen(g_PlayerModel[client]) > 1) { model = g_PlayerModel[client]; - modelIndex = FindStringInArray(g_ModelName, model); + modelIndex = g_ModelName.FindString(model); #if defined LOG - LogMessage("Change user model to %s", model); + LogMessage("[PH] Change \"%N\"'s user model to %s", client, model); #endif } else { - modelIndex = GetRandomInt(0, GetArraySize(g_ModelName)-1); - GetArrayString(g_ModelName, modelIndex, model, sizeof(model)); + modelIndex = GetRandomInt(0, g_ModelName.Length-1); + g_ModelName.GetString(modelIndex, model, sizeof(model)); } // This wackiness with [0] is required when dealing with enums containing strings - if (GetTrieArray(g_PropData, model, propData[0], sizeof(propData))) + if (g_PropData.GetArray(model, propData[0], sizeof(propData))) { strcopy(offset, sizeof(offset), propData[PropData_Offset]); strcopy(rotation, sizeof(rotation), propData[PropData_Rotation]); @@ -4420,7 +4098,7 @@ public DoEquipProp(any:UserId) if (!g_RoundStartMessageSent[client]) { - new String:modelName[MAXMODELNAME]; + char modelName[MAXMODELNAME]; GetModelNameForClient(client, model, modelName, sizeof(modelName)); CPrintToChat(client, "%t", "#TF_PH_NowDisguised", modelName); g_RoundStartMessageSent[client] = true; @@ -4428,10 +4106,10 @@ public DoEquipProp(any:UserId) if (modelIndex > -1) { - new String:tempOffset[32]; - new String:tempRotation[32]; - GetArrayString(g_ModelOffset, modelIndex, tempOffset, sizeof(tempOffset)); - GetArrayString(g_ModelRotation, modelIndex, tempRotation, sizeof(tempRotation)); + char tempOffset[32]; + char tempRotation[32]; + g_ModelOffset.GetString(modelIndex, tempOffset, sizeof(tempOffset)); + g_ModelRotation.GetString(modelIndex, tempRotation, sizeof(tempRotation)); TrimString(tempOffset); TrimString(tempRotation); // We don't want to override the default value unless it's set to something other than "0 0 0" @@ -4443,7 +4121,7 @@ public DoEquipProp(any:UserId) { strcopy(rotation, sizeof(rotation), tempRotation); } - skin = GetArrayCell(g_ModelSkin, modelIndex); + skin = g_ModelSkin.Get(modelIndex); } #if defined LOG @@ -4491,9 +4169,9 @@ public DoEquipProp(any:UserId) } } -public Action:Timer_Locked(Handle:timer, any:entity) +public Action Timer_Locked(Handle timer, any entity) { - for(new client=1; client <= MaxClients; client++) + for(int client=1; client <= MaxClients; client++) { if(g_RotLocked[client] && IsClientInGame(client) && !IsFakeClient(client) && IsPlayerAlive(client) && GetClientTeam(client) == TEAM_PROP) { @@ -4503,22 +4181,22 @@ public Action:Timer_Locked(Handle:timer, any:entity) } } -public Action:Timer_AntiHack(Handle:timer, any:entity) +public Action Timer_AntiHack(Handle timer, any entity) { - new red = TEAM_PROP - 2; + int red = TEAM_PROP - 2; if(!g_RoundOver) { - decl String:name[MAX_NAME_LENGTH]; - for(new client=1; client <= MaxClients; client++) + char name[MAX_NAME_LENGTH]; + for(int client=1; client <= MaxClients; client++) { if(IsClientInGame(client) && IsPlayerAlive(client)) { - if (GetConVarBool(g_PHStaticPropInfo) && !IsFakeClient(client)) + if (g_PHStaticPropInfo.BoolValue && !IsFakeClient(client)) { QueryClientConVar(client, "r_staticpropinfo", QueryStaticProp); } - if(!g_LastProp && GetConVarBool(g_PHAntiHack) && GetClientTeam(client) == TEAM_PROP && TF2_GetPlayerClass(client) == g_defaultClass[red]) + if(!g_LastProp && g_PHAntiHack.BoolValue && GetClientTeam(client) == TEAM_PROP && TF2_GetPlayerClass(client) == g_defaultClass[red]) { if(GetPlayerWeaponSlot(client, 1) != -1 || GetPlayerWeaponSlot(client, 0) != -1 || GetPlayerWeaponSlot(client, 2) != -1) { @@ -4540,22 +4218,25 @@ public Action:Timer_AntiHack(Handle:timer, any:entity) // Fix a prop player who still had weapons // One frame shouldn't be enough to change players, but just in case... -public FixPropPlayer(any:userid) +public void FixPropPlayer(any userid) { - new client = GetClientOfUserId(userid); - if (client < 1 || GetClientTeam(client) != TEAM_PROP) + int client = GetClientOfUserId(userid); + if (client < 1 || GetClientTeam(client) != TEAM_PROP || g_LastProp) return; - - TF2_RegeneratePlayer(client); + + //TF2_RegeneratePlayer(client); + + TF2_RemoveAllWeapons(client); + //Timer_DoEquip(INVALID_HANDLE, userid); RequestFrame(DoEquipProp, userid); } -public QueryStaticProp(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[]) +public void QueryStaticProp(QueryCookie cookie, int client, ConVarQueryResult result, const char[] cvarName, const char[] cvarValue) { if (result == ConVarQuery_Okay) { - new value = StringToInt(cvarValue); + int value = StringToInt(cvarValue); if (value == 0) { return; @@ -4566,56 +4247,44 @@ public QueryStaticProp(QueryCookie:cookie, client, ConVarQueryResult:result, con KickClient(client, "r_staticpropinfo detection was blocked"); } -public Action:Timer_Ragdoll(Handle:timer, any:userid) +public Action Timer_Ragdoll(Handle timer, any userid) { - new client = GetClientOfUserId(userid); + int client = GetClientOfUserId(userid); if (client < 1) return Plugin_Handled; - new rag = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); + int rag = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); if(rag > MaxClients && IsValidEntity(rag)) AcceptEntityInput(rag, "Kill"); - RemoveAnimeModel(client); + RemovePropModel(client); return Plugin_Handled; } -public Action:Timer_Score(Handle:timer) +public Action Timer_Score(Handle timer) { - for(new client=1; client <= MaxClients; client++) + for(int client=1; client <= MaxClients; client++) { -#if defined STATS || defined LOCALSTATS - if(IsClientInGame(client) && IsPlayerAlive(client) && GetClientTeam(client) == TEAM_PROP) - { - #if defined STATS - AlterScore(client, 2, ScReason_Time, 0); - #endif - - #if defined LOCALSTATS - LocalStats_AlterScore(client, 2, ScReason_Time, 0); - #endif - } -#endif g_TouchingCP[client] = false; } CPrintToChatAll("%t", "#TF_PH_CPBonusRefreshed"); } -public OnSetupStart(const String:output[], caller, activator, Float:delay) +public void OnSetupStart(const char[] output, int caller, int activator, float delay) { - new Handle:event = CreateEvent("teamplay_update_timer"); - if (event != INVALID_HANDLE) - FireEvent(event); + g_inSetup = true; + Event event = CreateEvent("teamplay_update_timer"); + if (event != null) + event.Fire(); } // This used to hook the teamplay_setup_finished event, but ph_kakariko messes with that -//public Action:Event_teamplay_setup_finished(Handle:event, const String:name[], bool:dontBroadcast) -public OnSetupFinished(const String:output[], caller, activator, Float:delay) +public void OnSetupFinished(const char[] output, int caller, int activator, float delay) { - if (g_hScore != INVALID_HANDLE) + if (g_hScore != null) { - CloseHandle(g_hScore); + delete g_hScore; } g_hScore = CreateTimer(55.0, Timer_Score, _, TIMER_REPEAT); TriggerTimer(g_hScore); @@ -4624,8 +4293,10 @@ public OnSetupFinished(const String:output[], caller, activator, Float:delay) LogMessage("[PH] Setup_Finish"); #endif g_RoundOver = false; + g_inSetup = false; + g_flRoundStart = GetGameTime(); - for(new client2=1; client2 <= MaxClients; client2++) + for(int client2=1; client2 <= MaxClients; client2++) { if(IsClientInGame(client2) && IsPlayerAlive(client2)) { @@ -4635,7 +4306,7 @@ public OnSetupFinished(const String:output[], caller, activator, Float:delay) CPrintToChatAll("%t", "#TF_PH_PyrosReleased"); PH_EmitSoundToAll("RoundStart", _, _, SNDLEVEL_AIRCRAFT); - new ent; + int ent; if(g_Doors) { while ((ent = FindEntityByClassname(ent, "func_door")) != -1) @@ -4646,7 +4317,7 @@ public OnSetupFinished(const String:output[], caller, activator, Float:delay) if(g_Relay) { - decl String:relayName[128]; + char relayName[128]; while ((ent = FindEntityByClassname(ent, "logic_relay")) != -1) { GetEntPropString(ent, Prop_Data, "m_iName", relayName, sizeof(relayName)); @@ -4660,13 +4331,12 @@ public OnSetupFinished(const String:output[], caller, activator, Float:delay) } #if defined CHARGE -public Action:Timer_Charge(Handle:timer, any:userid) +public Action Timer_Charge(Handle timer, any userid) { - new client = GetClientOfUserId(userid); - new red = TEAM_PROP-2; + int client = GetClientOfUserId(userid); + int red = TEAM_PROP-2; if(client > 0 && IsPlayerAlive(client)) { - //SetEntData(client, g_offsCollisionGroup, COLLISION_GROUP_PLAYER, _, true); SetEntProp(client, Prop_Send, "m_CollisionGroup", COLLISION_GROUP_PLAYER); TF2_SetPlayerClass(client, g_defaultClass[red], false); } @@ -4674,65 +4344,23 @@ public Action:Timer_Charge(Handle:timer, any:userid) } #endif -/* -public TimeEnd(const String:output[], caller, activator, Float:delay) -{ -#if defined LOG - LogMessage("[PH] Time Up"); -#endif - if(!g_RoundOver) - { - ForceTeamWin(TEAM_PROP); - g_RoundOver = true; - g_inPreRound = true; - } -} -*/ - -/* -public Action:Timer_TimeUp(Handle:timer, any:lol) -{ -#if defined LOG - LogMessage("[PH] Time Up"); -#endif - if(!g_RoundOver) - { - ForceTeamWin(TEAM_PROP); - g_RoundOver = true; - g_inPreRound = true; - } - g_RoundTimer = INVALID_HANDLE; - return Plugin_Handled; -} -*/ - -/* -public Action:Timer_AfterWinPanel(Handle:timer, any:lol) -{ -#if defined LOG - LogMessage("[PH] After Win Panel"); -#endif - StopTimer(g_RoundTimer); -} -*/ - -public Action:Timer_Unfreeze(Handle:timer, any:userid) +public Action Timer_Unfreeze(Handle timer, any userid) { - new client = GetClientOfUserId(userid); + int client = GetClientOfUserId(userid); if(client > 0 && IsPlayerAlive(client)) SetEntityMoveType(client, MOVETYPE_WALK); return Plugin_Handled; } -public Action:Timer_Move(Handle:timer, any:userid) +public Action Timer_Move(Handle timer, any userid) { - new client = GetClientOfUserId(userid); + int client = GetClientOfUserId(userid); if (client > 0) { g_AllowedSpawn[client] = false; if(IsPlayerAlive(client)) { - new rag = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); + int rag = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); if(IsValidEntity(rag)) AcceptEntityInput(rag, "Kill"); SetEntityMoveType(client, MOVETYPE_WALK); @@ -4750,14 +4378,13 @@ public Action:Timer_Move(Handle:timer, any:userid) return Plugin_Handled; } -public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefinitionIndex, &Handle:hItem) +public Action TF2Items_OnGiveNamedItem(int client, char[] classname, int iItemDefinitionIndex, Handle &hItem) { // This section is to prevent Handle leaks - static Handle:weapon = INVALID_HANDLE; - if (weapon != INVALID_HANDLE) + static Handle weapon = null; + if (weapon != null) { - CloseHandle(weapon); - weapon = INVALID_HANDLE; + delete weapon; } if (!g_Enabled) @@ -4776,8 +4403,16 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti return Plugin_Stop; } - if (GetClientTeam(client) == TEAM_PROP) + int team = GetClientTeam(client); + + if (team == TEAM_PROP) { + // Taunt items have the classname "no_entity" now + if (g_PHAllowTaunts.BoolValue && StrEqual(classname, "no_entity", false)) + { + return Plugin_Continue; + } + // If they're not the last prop, don't give them anything if (!g_LastProp) { @@ -4792,26 +4427,30 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti { return Plugin_Stop; } + + if (g_hPropWeaponRemovals.FindValue(iItemDefinitionIndex) >= 0) + { + return Plugin_Stop; + } } - new String:defIndex[7]; + char defIndex[7]; IntToString(iItemDefinitionIndex, defIndex, sizeof(defIndex)); - new flags; + int flags; - new String:replacement[140]; - new String:addAttributes[128]; - new bool:replace = GetTrieString(g_hWeaponReplacements, defIndex, replacement, sizeof(replacement)); - new bool:stripattribs = FindValueInArray(g_hWeaponStripAttribs , iItemDefinitionIndex) >= 0; - new bool:addattribs = GetTrieString(g_hWeaponAddAttribs, defIndex, addAttributes, sizeof(addAttributes)); - new bool:removeAirblast = !GetConVarBool(g_PHAirblast) && StrEqual(classname, "tf_weapon_flamethrower"); -// new String:pieces[5][128]; + char replacement[140]; + char addAttributes[128]; + bool replace = g_hWeaponReplacements.GetString(defIndex, replacement, sizeof(replacement)); + bool stripattribs = g_hWeaponStripAttribs.FindValue(iItemDefinitionIndex) >= 0; + bool addattribs = g_hWeaponAddAttribs.GetString(defIndex, addAttributes, sizeof(addAttributes)); + bool removeAirblast = !g_PHAirblast.BoolValue && StrEqual(classname, "tf_weapon_flamethrower"); if (replace) { - new classBits; + int classBits; - if (!GetTrieValue(g_hWeaponReplacementPlayerClasses, defIndex, classBits)) + if (!g_hWeaponReplacementPlayerClasses.GetValue(defIndex, classBits)) { g_Replacements[client][g_ReplacementCount[client]++] = iItemDefinitionIndex; return Plugin_Stop; @@ -4819,7 +4458,7 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti else { // We subtract 1 here because we're left shifting a 1, so 1 is intrinsically added to the class. - new class = _:TF2_GetPlayerClass(client) - 1; + int class = view_as(TF2_GetPlayerClass(client)) - 1; if (classBits & (1 << class)) { g_Replacements[client][g_ReplacementCount[client]++] = iItemDefinitionIndex; @@ -4830,7 +4469,7 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti } // If we're supposed to remove it, just block it here - if (FindValueInArray(g_hWeaponRemovals, iItemDefinitionIndex) >= 0) + if (team == TEAM_HUNTER && g_hWeaponRemovals.FindValue(iItemDefinitionIndex) >= 0) { return Plugin_Stop; } @@ -4840,7 +4479,7 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti return Plugin_Continue; } - new bool:weaponChanged = false; + bool weaponChanged = false; if (stripattribs) { @@ -4855,7 +4494,7 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti weapon = TF2Items_CreateItem(flags); - new attribCount = 0; + int attribCount = 0; // 594 is Phlogistinator and already has airblast disabled if (removeAirblast && (iItemDefinitionIndex != WEP_PHLOGISTINATOR || stripattribs)) { @@ -4863,58 +4502,23 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti weaponChanged = true; } - // This block isn't used as all weapon replacements are now done on spawn. - /* - if (replace) - { - TrimString(pieces[Item_Index]); - TrimString(pieces[Item_Quality]); - TrimString(pieces[Item_Level]); - TrimString(pieces[Item_Attributes]); - - new index = StringToInt(pieces[Item_Index]); - new quality = StringToInt(pieces[Item_Quality]); - new level = StringToInt(pieces[Item_Level]); - TF2Items_SetItemIndex(weapon, index); - TF2Items_SetQuality(weapon, quality); - TF2Items_SetLevel(weapon, level); - - if (strlen(pieces[Item_Attributes]) > 0) - { - new String:newAttribs[32][6]; - new count = ExplodeString(pieces[Item_Attributes], ";", newAttribs, sizeof(newAttribs), sizeof(newAttribs[])); - if (count % 2 > 0) - { - LogError("Error parsing replacement attributes for item definition index %d", iItemDefinitionIndex); - return Plugin_Continue; - } - - for (new i = 0; i < count && attribCount < 16; i += 2) - { - new attrib = StringToInt(newAttribs[i]); - new Float:value = StringToFloat(newAttribs[i+1]); - TF2Items_SetAttribute(weapon, attribCount++, attrib, value); - } - } - weaponChanged = true; - } - */ - if (addattribs) { // Pawn is dumb and this "shadows" a preceding variable despite being at a different block level - new String:newAttribs2[32][6]; - new count = ExplodeString(addAttributes, ";", newAttribs2, sizeof(newAttribs2), sizeof(newAttribs2[])); + char newAttribs2[32][6]; + int count = ExplodeString(addAttributes, ";", newAttribs2, sizeof(newAttribs2), sizeof(newAttribs2[])); if (count % 2 > 0) { LogError("Error parsing additional attributes for item definition index %d", iItemDefinitionIndex); return Plugin_Continue; } - for (new i = 0; i < count && attribCount < 16; i += 2) + for (int i = 0; i < count && attribCount < 16; i += 2) { - new attrib = StringToInt(newAttribs2[i]); - new Float:value = StringToFloat(newAttribs2[i+1]); + TrimString(newAttribs2[i]); + TrimString(newAttribs2[i+1]); + int attrib = StringToInt(newAttribs2[i]); + float value = StringToFloat(newAttribs2[i+1]); TF2Items_SetAttribute(weapon, attribCount++, attrib, value); } } @@ -4935,53 +4539,47 @@ public Action:TF2Items_OnGiveNamedItem(client, String:classname[], iItemDefiniti } #if defined OIMM -public MultiMod_Status(bool:enabled) +public void MultiMod_Status(bool enabled) { - SetConVarBool(g_PHEnable, enabled); + g_PHEnable.BoolValue = enabled } -public MultiMod_TranslateName(client, String:translation[], maxlength) +public void MultiMod_TranslateName(int client, char[] translation, int maxlength) { Format(translation, maxlength, "%T", "#TF_PH_ModeName", client); } #endif -public bool:ValidateMap(const String:map[]) +public bool ValidateMap(const char[] map) { - // As per SourceMod standard, anything dealing with map names should now be PLATFORM_MAX_PATH long - new String:confil[PLATFORM_MAX_PATH], String:tidyname[2][PLATFORM_MAX_PATH], String:maptidyname[PLATFORM_MAX_PATH]; - ExplodeString(map, "_", tidyname, sizeof(tidyname), sizeof(tidyname[])); - if (strncmp("workshop/", tidyname[0], 9)) - { - ReplaceString(tidyname[0], sizeof(tidyname[]), "workshop/", ""); - } - else if (strncmp("workshop\\", tidyname[0], 9)) - { - ReplaceString(tidyname[0], sizeof(tidyname[]), "workshop\\", ""); - } - // Because we only care about the first two pieces of the map name, we can ignore the ugc stuff - - Format(maptidyname, sizeof(maptidyname), "%s_%s", tidyname[0], tidyname[1]); - BuildPath(Path_SM, confil, sizeof(confil), "data/prophunt/maps/%s.cfg", maptidyname); - - return FileExists(confil, true); + return FindConfigFileForMap(map); } // These functions are based on versions taken from CS:S/CS:GO Hide and Seek -GetLanguageID(const String:langCode[]) +int GetLanguageID(const char[] langCode) { - return FindStringInArray(g_ModelLanguages, langCode); + return g_ModelLanguages.FindString(langCode); } -GetClientLanguageID(client, String:languageCode[]="", maxlen=0) +int GetClientLanguageID(int client, char[] languageCode="", int maxlen=0) { - decl String:langCode[MAXLANGUAGECODE]; - GetLanguageInfo(GetClientLanguage(client), langCode, sizeof(langCode)); + char langCode[MAXLANGUAGECODE]; + int languageID; + if (client == LANG_SERVER) + { + languageID = GetServerLanguage(); + } + else + { + languageID = GetClientLanguage(client); + } + + GetLanguageInfo(languageID, langCode, sizeof(langCode)); #if defined LOG - LogMessage("Client is using language code %s", langCode); + LogMessage("[PH] Client is using language code %s", langCode); #endif // is client's prefered language available? - new langID = GetLanguageID(langCode); + int langID = GetLanguageID(langCode); if(langID != -1) { strcopy(languageCode, maxlen, langCode); @@ -4990,11 +4588,11 @@ GetClientLanguageID(client, String:languageCode[]="", maxlen=0) else { #if defined LOG - LogMessage("PH language code \"%s\" not found.", langCode); + LogMessage("[PH] Language code \"%s\" not found.", langCode); #endif GetLanguageInfo(GetServerLanguage(), langCode, sizeof(langCode)); #if defined LOG - LogMessage("Falling back to server language code \"%s\".", langCode); + LogMessage("[PH] Falling back to server language code \"%s\".", langCode); #endif // is default server language available? langID = GetLanguageID(langCode); @@ -5020,12 +4618,12 @@ GetClientLanguageID(client, String:languageCode[]="", maxlen=0) // english not found? happens on custom map configs e.g. // use the first language available // this should always work, since we would have SetFailState() on parse - if(GetArraySize(g_ModelLanguages) > 0) + if(g_ModelLanguages.Length > 0) { #if defined LOG LogMessage("PH language \"en\" not found, Falling back to lang 0."); #endif - GetArrayString(g_ModelLanguages, 0, languageCode, maxlen); + g_ModelLanguages.GetString(0, languageCode, maxlen); return 0; } } @@ -5034,18 +4632,18 @@ GetClientLanguageID(client, String:languageCode[]="", maxlen=0) return -1; } -bool:GetModelNameForClient(client, const String:modelName[], String:name[], maxlen) +bool GetModelNameForClient(int client, const char[] modelName, char[] name, int maxlen) { - if (GetConVarBool(g_PHMultilingual)) + if (g_PHMultilingual.BoolValue) { - decl String:langCode[MAXLANGUAGECODE]; + char langCode[MAXLANGUAGECODE]; GetClientLanguageID(client, langCode, sizeof(langCode)); #if defined LOG LogMessage("[PH] Retrieving %s name for %s", langCode, modelName); #endif - new Handle:languageTrie; - if (strlen(langCode) > 0 && GetTrieValue(g_PropNames, modelName, languageTrie) && languageTrie != INVALID_HANDLE && GetTrieString(languageTrie, langCode, name, maxlen)) + StringMap languageTrie; + if (strlen(langCode) > 0 && g_PropNames.GetValue(modelName, languageTrie) && languageTrie != null && languageTrie.GetString(langCode, name, maxlen)) { return true; } @@ -5057,9 +4655,9 @@ bool:GetModelNameForClient(client, const String:modelName[], String:name[], maxl } else { - new propData[PropData]; + int propData[PropData]; - if (GetTrieArray(g_PropData, modelName, propData[0], sizeof(propData))) + if (g_PropData.GetArray(modelName, propData[0], sizeof(propData))) { strcopy(name, maxlen, propData[PropData_Name]); return true; @@ -5073,10 +4671,243 @@ bool:GetModelNameForClient(client, const String:modelName[], String:name[], maxl } // Fix for weapon alphas for last prop -public TF2Items_OnGiveNamedItem_Post(client, String:classname[], itemDefinitionIndex, itemLevel, itemQuality, entityIndex) +public int TF2Items_OnGiveNamedItem_Post(int client, char[] classname, int itemDefinitionIndex, int itemLevel, int itemQuality, int entityIndex) { if (g_LastProp && g_LastPropPlayer == client && IsValidEntity(entityIndex)) { SetItemAlpha(entityIndex, 0); } } + +bool HasSwitchedTeams() +{ + return view_as(GameRules_GetProp("m_bSwitchedTeamsThisRound")); +} + +void SetSwitchTeams(bool bSwitchTeams) +{ + // Note, this doesn't switch team scores... those are switched when SetWinner is called in the game + // Also, SetWinner will override our selection here, so this must be sent AFTER arena_win_panel fires + SDKCall(g_hSwitchTeams, bSwitchTeams); +} + +// Manually switch the scores. +// The game only does this if SetWinningTeam is called with bSwitchTeams set to true. +// This is what DHooks did, but Arena confused it anyway and it only sometimes worked +void SwitchTeamScoresClassic() +{ + int propScore = GetTeamScore(TEAM_PROP); + int hunterScore = GetTeamScore(TEAM_HUNTER); + + if (propScore == 0 && hunterScore == 0) + { + return; + } + +#if defined LOG + LogMessage("[PH] Swapping team scores: Props: %d, Hunters: %d", propScore, hunterScore); +#endif + + SetTeamScore(TEAM_PROP, hunterScore); + SetTeamScore(TEAM_HUNTER, propScore); +} + +bool FindConfigFileForMap(const char[] map, char[] destination = "", int maxlen = 0) +{ + char mapPiece[PLATFORM_MAX_PATH]; + +#if defined WORKSHOP_SUPPORT + // Handle workshop maps + if (GetFeatureStatus(FeatureType_Native, "GetMapDisplayName") == FeatureStatus_Available) + { + if (!GetMapDisplayName(map, mapPiece, sizeof(mapPiece))) + { + return false; + } + } + else + { +#endif + strcopy(mapPiece, sizeof(mapPiece), map); + + if (!IsMapValid(mapPiece)) + { + return false; + } +#if defined WORKSHOP_SUPPORT + } +#endif + char confil[PLATFORM_MAX_PATH]; + + // Optimization so we don't immediately rebuild the whole string after ExplodeString + BuildPath(Path_SM, confil, sizeof(confil), "data/prophunt/maps/%s.cfg", mapPiece); + if (FileExists(confil, true)) + { + strcopy(destination, maxlen, confil); +#if defined LOG + LogMessage("[PH] Using config file \"%s\"", confil); +#endif + return true; + } + + char fileParts[4][PLATFORM_MAX_PATH]; + int count = ExplodeString(mapPiece, "_", fileParts, sizeof(fileParts), sizeof(fileParts[])) - 1; + + while (count > 1) + { + mapPiece[0] = '\0'; + ImplodeStrings(fileParts, count, "_", mapPiece, sizeof(mapPiece)); + + BuildPath(Path_SM, confil, sizeof(confil), "data/prophunt/maps/%s.cfg", mapPiece); + + if (FileExists(confil, true)) + { + strcopy(destination, maxlen, confil); +#if defined LOG + LogMessage("[PH] Using config file \"%s\"", confil); +#endif + return true; + } + + count--; + } + + destination[0] = '\0'; // In case of decl + return false; +} + +// Natives + +public int Native_ValidateMap(Handle plugin, int numParams) +{ + int mapLength; + GetNativeStringLength(1, mapLength); + + char[] map = new char[mapLength+1]; + GetNativeString(1, map, mapLength+1); + + return ValidateMap(map); +} + +public int Native_IsRunning(Handle plugin, int numParams) +{ + return g_Enabled; +} + +public int Native_GetModel(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + if (!IsClientInGame(client) || GetClientTeam(client) != TEAM_PROP || strlen(g_PlayerModel[client]) == 0) + { + return false; + } + + SetNativeString(2, g_PlayerModel[client], GetNativeCell(3)); + + return true; +} + +public int Native_GetModelName(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + + if (!IsClientInGame(client) || GetClientTeam(client) != TEAM_PROP || strlen(g_PlayerModel[client]) == 0) + { + return false; + } + + int targetClient = GetNativeCell(4); + + int length = GetNativeCell(3); + char[] model = new char[length]; + + GetModelNameForClient(targetClient, g_PlayerModel[client], model, length); + + SetNativeString(2, model, length); + return true; +} + +public int Native_LastPropMode(Handle plugin, int numParams) +{ + return g_LastProp; +} + +void Internal_AddServerTag() +{ + char tags[SV_TAGS_SIZE+1]; + g_hTags.GetString(tags, sizeof(tags)); + + if (StrContains(tags, "PropHunt", false) == -1 && strlen(tags) + 9 <= SV_TAGS_SIZE) + { + char tagArray[20][SV_TAGS_SIZE+1]; + int count = ExplodeString(tags, ",", tagArray, sizeof(tagArray), sizeof(tagArray[])); + + if (count < sizeof(tagArray)) + { + tagArray[count] = "PropHunt"; + + ImplodeStrings(tagArray, count+1, ",", tags, sizeof(tags)); + SetConVarString(g_hTags, tags); + } + } +} + +void Internal_RemoveServerTag() +{ + char tags[SV_TAGS_SIZE+1]; + g_hTags.GetString(tags, sizeof(tags)); + + if (StrContains(tags, "PropHunt", false) > -1) + { + char tagArray[20][SV_TAGS_SIZE+1]; + int count = ExplodeString(tags, ",", tagArray, sizeof(tagArray), sizeof(tagArray[])); + + for (int i = count - 1; i >= 0; i--) + { + TrimString(tagArray[i]); + if (StrEqual(tagArray[i], "PropHunt", false)) + { + count--; + + // Move all elements above this one down by one + for (int j = i; j < count; j++) + { + tagArray[j] = tagArray[j+1]; + } + tagArray[count][0] = '\0'; + } + } + + ImplodeStrings(tagArray, count, ",", tags, sizeof(tags)); + g_hTags.SetString(tags); + } + +} + +stock RemoveValveHat(client, bool:unhide = false) +{ + new edict = MaxClients+1; + while((edict = FindEntityByClassnameSafe(edict, "tf_wearable")) != -1) + { + decl String:netclass[32]; + if (GetEntityNetClass(edict, netclass, sizeof(netclass)) && strcmp(netclass, "CTFWearable") == 0) + { + new idx = GetEntProp(edict, Prop_Send, "m_iItemDefinitionIndex"); + if (idx != 57 && idx != 133 && idx != 231 && idx != 444 && idx != 405 && idx != 608 && GetEntPropEnt(edict, Prop_Send, "m_hOwnerEntity") == client) + { + SetEntityRenderMode(edict, (unhide ? RENDER_NORMAL : RENDER_TRANSCOLOR)); + SetEntityRenderColor(edict, 255, 255, 255, (unhide ? 255 : 0)); + } + } + } +} +int FindEntityByClassnameSafe(int iStart, char[] sClassName) +{ + while (iStart > -1 && !IsValidEntity(iStart)) + { + iStart--; + } + + return FindEntityByClassname(iStart, sClassName); +} diff --git a/addons/sourcemod/scripting/prophunt/localstats2.inc b/addons/sourcemod/scripting/prophunt/localstats2.inc deleted file mode 100644 index e652d18..0000000 --- a/addons/sourcemod/scripting/prophunt/localstats2.inc +++ /dev/null @@ -1,89 +0,0 @@ -// PropHunt Redux Local Stats by Powerlord -// - reddit.com/r/RUGC_Midwest - - -// Don't use this code yet, it's not finished -/* - * NOTE: Queries used when connecting to the DB are non-threaded because we want them to complete before - * any threaded queries would run - */ - -#define CURRENT_SCHEMA_VERSION 1 -#define SCHEMA_UPGRADE_1 1 - -#define DATABASE "prophunt_local" -new Handle:g_LocalStatsDb; - -LocalStats_Init() -{ - RegConsoleCmd("phrank", Command_localstatsme); - RegConsoleCmd("phstatsme", Command_localstats); - RegConsoleCmd("phtop10", Command_localtop10); - - decl String:error[255], String:query[392]; - new Handle:db; - - if (!SQL_CheckConfig(DATABASE)) - { - LogError("[PH] No %s database found in configs/databases.cfg", DATABASE); - return; - } - - PrintToServer("Connecting to Local PropHunt database..."); - - db = LocalStats_Connect(error); - - if (db != INVALID_HANDLE) - { - PrintToServer("Connected successfully."); - } - else - { - PrintToServer("Connection failed: %s", error); - return; - } - - RegConsoleCmd("phstats", Command_motdlocal, "Local Prop Hunt Stats page"); -} - -//SQL_TConnect doesn't support persistent connections, so just use SQL_Connect -static Handle:LocalStats_Connect(String:error[]="") -{ - new Handle:db = INVALID_HANDLE; - decl String:error[255]; - - if (SQL_CheckConfig(DATABASE)) - { - db = SQL_Connect(DATABASE, true, error, sizeof(error)); - if (db != INVALID_HANDLE) - { - SQL_FastQuery("SET NAMES 'utf8'"); - } - } - - return db; -} - -LocalStats_OnClientDisconnect(client) -{ - // TODO: Do something with disconnecting clients -} - -LocalStats_OnClientDisconnect_Post(client) -{ - // TODO: Do something with disconnecting clients -} - -LocalStats_DbRound(winner) -{ - // TODO: Do something with round winners -} - -LocalStats_AlterScore(client, points, ScReason:reason, time) -{ - // TODO: Do something with round winners -} - -LocalStats_OnClientPostAdminCheck(client) -{ - // TODO: Post-logon stuff -} \ No newline at end of file diff --git a/addons/sourcemod/scripting/prophunt_stats_example.sp b/addons/sourcemod/scripting/prophunt_stats_example.sp new file mode 100644 index 0000000..b1251a8 --- /dev/null +++ b/addons/sourcemod/scripting/prophunt_stats_example.sp @@ -0,0 +1,924 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * PropHunt Stats Example + * Example on how to do PropHunt Stats Stuff using a DB connection + * + * PropHunt Stats Example (C)2015 Powerlord (Ross Bemrose). All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#include +#include "include/prophunt" +#include +#include +#include +#include + +#pragma semicolon 1 +#pragma newdecls required + +#define VERSION "4.0.0 alpha 1" + +#define DATABASE "prophuntstats" + +enum ScReason +{ + ScReason_TeamWin = 0, + ScReason_TeamLose, + ScReason_Death, // Not used in default scoring + ScReason_Kill, // Not used in default scoring + ScReason_Time, + ScReason_Friendly // Not used in default scoring, friendly fire has been disabled by default in PH for years. +}; + +//#define LOGSTATS 1 + +// AuthID_Steam2 (STEAM_0:0:123456789) uses 19 characters (may be 20 soon) +// AuthID_Steam3 ([U:1:1234567] uses 13 (may be 14) or more characters. I would recommend setting to at least 18. +// AuthId_SteamID64 (765345678901234567) uses 17 characters +const AuthIdType SteamIdType = AuthId_Steam2; +const int SteamIdLength = 20; // STEAM_0:0:1234567890 <-- this is 1 larger than currently needed, but just to make sure... + +const int Points_Time = 2; +const int Points_TeamWin = 3; +const int Points_TeamLose = -1; + +// How many points are awarded for each action? +// Note that victims lose half the killer's points +// (Killer gains 2, victim loses 1; kiler gains 8, victim loses 4) +const int Points_Killer_Min = 2; +const int Points_Killer_Max = 8; +const int Points_Assister_Min = 1; +const int Points_Assister_Max = 8; + +// What lengths do we use for escaped strings? +const int SQLNameLength = MAX_NAME_LENGTH*2+1; +const int SQLSteamIdLength = SteamIdLength*2+1; +const int SQLPathLength = PLATFORM_MAX_PATH*2+1; + +// Which field in a transaction is the SELECT query? +const int TransactionData = 1; + +// 60.0 * 60.0 +const float SecondsPerHour = 3600.0; + +char ignore[16]; + +ConVar g_Cvar_Enabled; + +Database g_StatsDB; +Handle g_hScoreTimer; + +bool isPropHuntRound = false; + +int g_ServerPointCount; +int g_PointCount[MAXPLAYERS+1]; +int g_ClientTime[MAXPLAYERS+1]; +int g_ServerTime; + +char g_ServerIP[32]; +//char g_ServerHostname[128]; +//int g_ServerPort; + +char g_SteamID[MAXPLAYERS+1][SteamIdLength+1]; + +bool g_inRound = false; + +int g_StartTime; + +/* +enum DBType +{ + DBType_Unknown, + DBType_MySQL, + DBType_SQLite, + DBType_PostgreSQL +} + +DBType g_DBType = DBType_Unknown; +*/ + +public Plugin myinfo = { + name = "PropHunt Redux Stats", + author = "Powerlord", + description = "PropHunt stats using a database", + version = VERSION, + url = "" +}; + +// SOURCEMOD CALLBACKS + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + if (GetEngineVersion() != Engine_TF2) + { + strcopy(error, err_max, "PropHunt Redux Stats only works on Team Fortress 2."); + return APLRes_Failure; + } + + if (late) + { + strcopy(error, err_max, "PropHunt Redux Stats does not support late loading."); + return APLRes_Failure; + } + + return APLRes_Success; +} + +public void OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("prophunt.phrases"); + + CreateConVar("prophunt_stats_example_version", VERSION, "PropHunt Stats version", FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); + g_Cvar_Enabled = CreateConVar("prophunt_stats_example_enable", "1", "Enable PropHunt Stats Example?", FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); + + HookEvent("teamplay_round_start", Event_RoundStart); + HookEvent("arena_round_start", Event_ArenaRoundStart); + HookEvent("teamplay_round_win", Event_RoundWin); + HookEvent("player_death", Event_PlayerDeath); + + RegConsoleCmd("rank", Cmd_Rank); + RegConsoleCmd("statsme", Cmd_StatsMe); + RegConsoleCmd("top10", Cmd_Top10); + + char ip[16]; // 123.567.901.345\0 + int port = FindConVar("hostport").IntValue; + FindConVar("ip").GetString(ip, sizeof(ip)); + Format(g_ServerIP, sizeof(g_ServerIP), "%s:%d", ip, port); + + Stats_Init(); +} + + +public void OnPluginEnd() +{ + delete g_StatsDB; +} + +public void OnMapStart() +{ + // assume false until round start + isPropHuntRound = false; + + g_inRound = false; +} + +public void OnClientConnected(int client) +{ + // This is to deal with their connect time in case this was a map change + g_ClientTime[client] = RoundFloat(GetClientTime(client)); +} + +public void OnClientPostAdminCheck(int client) +{ + // Get PropHunt stats even if this isn't a PH round + if (!IsClientInGame(client) || IsFakeClient(client)) + { + return; + } + + char steamid[SteamIdLength+1]; + char name[MAX_NAME_LENGTH+1]; + + if (!GetClientAuthId(client, SteamIdType, g_SteamID[client], sizeof(g_SteamID[]))) + { + LogMessage("Steam ID lookup failed for clent %d (\"%N\")", client, client); + return; + } + + char escapedSteamid[SQLSteamIdLength]; + g_StatsDB.Escape(steamid, escapedSteamid, sizeof(escapedSteamid)); + + char escapedName[SQLNameLength]; + g_StatsDB.Escape(name, escapedName, sizeof(escapedName)); + + int userId = GetClientUserId(client); + + Transaction tx = new Transaction(); + + char query[384]; + Format(query, sizeof(query), "INSERT %s INTO players (steamid, name) VALUES('%s', '%s')", ignore, escapedSteamid, escapedName); + tx.AddQuery(query); + + Format(query, sizeof(query), "SELECT points FROM players WHERE steamid='%s'", escapedSteamid); + tx.AddQuery(query); + + g_StatsDB.Execute(tx, Tx_PlayerConnect, Tx_Error, userId, DBPrio_High); +} + +public void OnClientDisconnect(int client) +{ + if (IsClientInGame(client) && !IsFakeClient(client) && DatabaseIntact()) + { + char query[392], ip[32], geoip[3], name[MAX_NAME_LENGTH+1]; + + GetClientName(client, name, sizeof(name)); + + char escapedSteamid[SQLSteamIdLength]; + g_StatsDB.Escape(g_SteamID[client], escapedSteamid, sizeof(escapedSteamid)); + + char escapedName[SQLNameLength]; + g_StatsDB.Escape(name, escapedName, sizeof(escapedName)); + + Transaction tx = new Transaction(); + Format(query, sizeof(query), "INSERT %s INTO players (steamid, name) VALUES('%s', '%s')", ignore, escapedSteamid, escapedName); + tx.AddQuery(query); + + int clientTime = RoundFloat(GetClientTime(client)); + Format(query, sizeof(query), "UPDATE players SET lastserver = '%s', time = time + %d, ip = '%s', geoip = '%s', name = '%s' WHERE steamid = '%s'", + g_ServerIP, clientTime - g_ClientTime[client], ip, geoip, escapedName, escapedSteamid); + tx.AddQuery(query); + + g_StatsDB.Execute(tx, .onError=Tx_Error); + } + + g_ClientTime[client] = 0; + g_SteamID[client][0] = '\0'; +} + +// COMMANDS + +public Action Cmd_StatsMe(int client, int args) +{ + if (client == 0) + { + ReplyToCommand(client, "%t", "Command is in-game only"); + return Plugin_Handled; + } + + if (IsClientInGame(client) && DatabaseIntact()) + { + char query[392]; + + char escapedSteamid[SQLSteamIdLength]; + g_StatsDB.Escape(g_SteamID[client], escapedSteamid, sizeof(escapedSteamid)); + + Format(query, sizeof(query), "SELECT points, wins, losses, a.rank, time FROM players, (SELECT count(*)+1 as rank FROM players WHERE points > (SELECT points FROM players WHERE steamid = '%s')) as a WHERE steamid = '%s'", escapedSteamid, escapedSteamid); + g_StatsDB.Query(T_Statsme, query, GetClientUserId(client)); + } + + return Plugin_Handled; +} + +public Action Cmd_Rank(int client, int args) +{ + if (client == 0) + { + ReplyToCommand(client, "%t", "Command is in-game only"); + return Plugin_Handled; + } + + if (IsClientInGame(client) && DatabaseIntact()) + { + char query[392]; + + char escapedSteamid[SQLSteamIdLength]; + g_StatsDB.Escape(g_SteamID[client], escapedSteamid, sizeof(escapedSteamid)); + + Format(query, sizeof(query), "SELECT points, wins, losses, a.rank, time FROM players, (SELECT count(*)+1 as rank FROM players WHERE points > (SELECT points FROM players WHERE steamid = '%s')) as a WHERE steamid = '%s'", escapedSteamid, escapedSteamid); + g_StatsDB.Query(T_Rank, query, GetClientUserId(client)); + } + + return Plugin_Handled; +} + +public Action Cmd_Top10(int client, int args) +{ + if (client == 0) + { + ReplyToCommand(client, "%t", "Command is in-game only"); + return Plugin_Handled; + } + + if (IsClientInGame(client) && DatabaseIntact()) + { + g_StatsDB.Query(T_Top10, "SELECT name, points FROM players ORDER BY points DESC LIMIT 10", GetClientUserId(client)); + } + + return Plugin_Handled; +} + +public void Event_RoundStart(Event event, const char[] name, bool dontBroadcast) +{ + isPropHuntRound = PropHuntRedux_IsRunning(); // This can change every round + + g_inRound = false; + + if (!g_Cvar_Enabled.BoolValue || !isPropHuntRound) + { + return; + } + + g_hScoreTimer = CreateTimer(55.0, Timer_Score, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public void Event_ArenaRoundStart(Event event, const char[] name, bool dontBroadcast) +{ + g_inRound = true; + g_StartTime = GetTime(); +} + +public void Event_RoundWin(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_Cvar_Enabled.BoolValue || !isPropHuntRound) + { + return; + } + + g_inRound = false; + isPropHuntRound = false; + + delete g_hScoreTimer; + + int winner = event.GetInt("team"); + + DbRound(winner); + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client)) + { + continue; + } + + int team = GetClientTeam(client); + + if (team == winner) // either props or hunters + { + AlterScore(client, Points_TeamWin, ScReason_TeamWin); + } + else if (team > TEAM_SPECTATOR) // either props or hunters + { + AlterScore(client, Points_TeamLose, ScReason_TeamLose); + } + } +} + +public void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) +{ + if (!g_Cvar_Enabled.BoolValue || !g_inRound || !isPropHuntRound) + { + return; + } + + // PropHunt usually doesn't allow Spies, but just in case + if (event.GetInt("death_flags") & TF_DEATHFLAG_DEADRINGER) + { + return; + } + + int client = GetClientOfUserId(event.GetInt("userid")); + int attacker = GetClientOfUserId(event.GetInt("attacker")); + int assister = GetClientOfUserId(event.GetInt("assister")); + + char weapon[255]; + event.GetString("weapon_logclassname", weapon, sizeof(weapon)); + + if (client > 0 && attacker > 0 && IsClientInGame(client) && IsClientInGame(attacker) && client != attacker) + { + PlayerKilled(client, attacker, assister, weapon); + } +} + +// FUNCTIONS + +void PlayerKilled(int client, int attacker, int assister, char[] weaponname) +{ + int clientPoints, attackerPoints, assisterPoints; + char attackerName[MAX_NAME_LENGTH+1], clientName[MAX_NAME_LENGTH+1]; + + GetClientName(attacker, attackerName, sizeof(attackerName)); + GetClientName(client, clientName, sizeof(clientName)); + + // This algorithm has corrects a long-standing bug in PropHunt stats where the float conversion was done after + // int math had already taken place + attackerPoints = RoundFloat(float(g_PointCount[client]) / float(g_PointCount[attacker])); + + // Point caps + if (attackerPoints < Points_Killer_Min) + attackerPoints = Points_Killer_Min; + else if (attackerPoints > Points_Killer_Max) + attackerPoints = Points_Killer_Max; + + //Assister point caps + if (assister > 0) + { + assisterPoints = RoundFloat(float(g_PointCount[client]) / float(g_PointCount[assister]) / 2.0); + + if (assisterPoints < Points_Assister_Min) + assisterPoints = Points_Assister_Min; + else if (assisterPoints > Points_Assister_Max) + assisterPoints = Points_Assister_Max; + } + + // victim points are between -1 and -4 + clientPoints = 0 - RoundFloat(attackerPoints * 0.5); + + if (IsClientInGame(attacker)) + { + if (GetClientTeam(client) == TEAM_PROP) + { + char model[PLATFORM_MAX_PATH]; + PropHuntRedux_GetPropModel(client, model, sizeof(model)); + DbProp(model, "death", 1); + } + CPrintToChat(client, "%t", "#TF_PH_AlterScore_Death", attackerName, clientPoints*-1); + DbInt(client, "points", clientPoints); + } + + CPrintToChat(attacker, "%t", "#TF_PH_AlterScore_Kill", clientName, attackerPoints); + if (assister > 0) + CPrintToChat(assister, "%t", "#TF_PH_AlterScore_Assist", clientName, assisterPoints); + + DbInt(attacker, "points", attackerPoints); + if (assister > 0) + DbInt(assister, "points", assisterPoints); + + // This is commented as we already subtract points from clients + // DbInt(client, "points", -2); + DbDeaths(client, attacker, assister, weaponname); +} + +void DbDeaths(int client, int attacker, int assister, char[] weaponname) +{ + if (!DatabaseIntact() || !g_inRound) + { + return; + } + + char query[1024], map[PLATFORM_MAX_PATH], escapedMap[SQLPathLength], model[PLATFORM_MAX_PATH], escapedModel[SQLPathLength]; + int team, time; + float killerPos[3], victimPos[3]; + char escapedVictimId[SQLSteamIdLength], escapedKillerId[SQLSteamIdLength], escapedAssisterId[SQLSteamIdLength]; + + // Ditch the legacy code dealing with weapon names + + if (client > 0 && !IsFakeClient(client) && IsClientInGame(client)) + { + g_StatsDB.Escape(g_SteamID[client], escapedVictimId, sizeof(escapedVictimId)); + + GetClientEyePosition(client, victimPos); + time - GetTime() - g_StartTime; + } + + if (attacker > 0 && !IsFakeClient(attacker) && IsClientInGame(attacker)) + { + g_StatsDB.Escape(g_SteamID[attacker], escapedKillerId, sizeof(escapedKillerId)); + + GetClientEyePosition(attacker, killerPos); + team = GetClientTeam(attacker); + } + + if (assister > 0 && !IsFakeClient(assister) && IsClientInGame(assister)) + { + g_StatsDB.Escape(g_SteamID[assister], escapedAssisterId, sizeof(escapedAssisterId)); + } + + GetCurrentMap(map, sizeof(map)); + GetMapDisplayName(map, map, sizeof(map)); + g_StatsDB.Escape(map, escapedMap, sizeof(escapedMap)); + + PropHuntRedux_GetPropModel(client, model, sizeof(model)); + g_StatsDB.Escape(model, escapedModel, sizeof(escapedModel)); + + Format(query, sizeof(query), "INSERT INTO deaths (victimid, killerid, killerteam, weapon, assisterid, ip, map, prop, victim_position_x, victim_position_y, victim_position_z, killer_position_x, killer_position_y, killer_position_z, victim_class, killer_class, survival_time) VALUES('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %.0f, %.0f, %.0f, %.0f, %.0f, %.0f, %i, %i, %i)", + escapedVictimId, escapedKillerId, (team == TEAM_PROP ? "RED" : "BLU"), weaponname, escapedAssisterId, g_ServerIP, escapedMap, escapedModel, victimPos[0], victimPos[1], victimPos[2], + killerPos[0], killerPos[1], killerPos[2], TF2_GetPlayerClass(client), TF2_GetPlayerClass(attacker), time); + + g_StatsDB.Query(T_ErrorOnly, query); + +#if defined LOGSTATS + LogMessage("[PH] DbDeaths [%s]", query); +#endif +} + +void DbSurvivals(int client) +{ + if (!DatabaseIntact()) + { + return; + } + + char query[1024], map[PLATFORM_MAX_PATH], escapedMap[SQLPathLength], model[PLATFORM_MAX_PATH], escapedModel[SQLPathLength], escapedSteamid[SQLSteamIdLength]; + int time; + float pos[3]; + + if (client > 0 && !IsFakeClient(client) && IsClientInGame(client)) + { + g_StatsDB.Escape(g_SteamID[client], escapedSteamid, sizeof(escapedSteamid)); + GetClientEyePosition(client, pos); + time = GetTime() - g_StartTime; + } + + GetCurrentMap(map, sizeof(map)); + GetMapDisplayName(map, map, sizeof(map)); + g_StatsDB.Escape(map, escapedMap, sizeof(escapedMap)); + + PropHuntRedux_GetPropModel(client, model, sizeof(model)); + g_StatsDB.Escape(model, escapedModel, sizeof(escapedModel)); + + Format(query, sizeof(query), "INSERT INTO survivals (steamid, prop, ip, map, lastprop, position_x, position_y, position_z, class, team, survival_time) VALUES('%s', '%s', '%s', '%s', '%s', %.0f, %.0f, %.0f, %i, '%s', %i)", + escapedSteamid, escapedModel, g_ServerIP, escapedMap, PropHuntRedux_IsLastPropMode() ? "1" : "0" , pos[0], pos[1], pos[2], TF2_GetPlayerClass(client), + GetClientTeam(client) == TEAM_PROP ? "RED" : "BLU", time); + + g_StatsDB.Query(T_ErrorOnly, query); + +#if defined LOGSTATS + LogMessage("[PH] DbSurvivals [%s]", query); +#endif +} + +void AlterScore(int client, int sc, ScReason reason) +{ + switch(reason) + { + case ScReason_TeamWin: + { + DbSurvivals(client); + CPrintToChat(client, "%t", "#TF_PH_AlterScore_TeamWin", sc); + DbInt(client, "wins", 1); + + if (GetClientTeam(client) == TEAM_PROP && IsPlayerAlive(client)) + { + char model[PLATFORM_MAX_PATH]; + PropHuntRedux_GetPropModel(client, model, sizeof(model)); + DbProp(model, "survivals", 1); + } + } + + case ScReason_Time: + { + CPrintToChat(client, "%t", "#TF_PH_AlterScore_TimeAward", sc); + } + + case ScReason_TeamLose: + { + CPrintToChat(client, "%t", "#TF_PH_AlterScore_TeamLose", sc*-1); + DbInt(client, "losses", 1); + } + } + DbInt(client, "points", sc); +} + +// MENU HANDLING + +public int MenuHandler(Menu menu, MenuAction action, int param1, int param2) +{ + switch (action) + { + case MenuAction_End: + { + delete menu; + } + } +} + +// TIMERS + +public Action Timer_UpdateServerScore(Handle timer) +{ + char query[384]; + + Format(query, sizeof(query), "UPDATE servers SET points = %d, time = %d WHERE ip = '%s'", g_ServerPointCount, g_ServerTime, g_ServerIP); + g_StatsDB.Query(T_ErrorOnly, query); +} + +public Action Timer_Score(Handle timer) +{ + for (int client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client) && IsPlayerAlive(client) && GetClientTeam(client) == TEAM_PROP) + { + AlterScore(client, Points_Time, ScReason_Time); + } + } +} + +// SQL + +void DbInt(int client, const char[] what, int points, Transaction tx=null) +{ + if (!IsClientConnected(client) || IsFakeClient(client)) + { + return; + } + + char escapedSteamid[SQLSteamIdLength]; + g_StatsDB.Escape(g_SteamID[client], escapedSteamid, sizeof(escapedSteamid)); + + char name[MAX_NAME_LENGTH+1]; + GetClientName(client, name, sizeof(name)); + + char escapedName[SQLNameLength]; + g_StatsDB.Escape(name, escapedName, sizeof(escapedName)); + + char query[384]; + Format(query, sizeof(query), "UPDATE players SET %s = %s + %d, name = '%s' WHERE steamdi = '%s'", what, what, points, escapedName, escapedSteamid); + if (tx == null) + { + g_StatsDB.Query(T_ErrorOnly, query); + } + else + { + tx.AddQuery(query); + } + g_ServerPointCount += points; + g_PointCount[client] += points; + +#if defined LOGSTATS + LogMessage("[PH] DbInt [%s]", query); +#endif +} + +void DbRound(int team) +{ + if (!DatabaseIntact()) + { + return; + } + + char query[384]; + + char map[PLATFORM_MAX_PATH], escapedMap[SQLPathLength]; + GetCurrentMap(map, sizeof(map)); + GetMapDisplayName(map, map, sizeof(map)); + g_StatsDB.Escape(map, escapedMap, sizeof(escapedMap)); + + Format(query, sizeof(query), "INSERT INTO rounds (team, server, map) VALUES('%s', '%s', '%s')", team == TEAM_PROP ? "RED" : "BLU", g_ServerIP, escapedMap); + g_StatsDB.Query(T_ErrorOnly, query); + +#if defined LOGSTATS + LogMessage("[PH] DbRound [%s]", query); +#endif +} + +void DbProp(const char[] prop, const char[] what, int points) +{ + if (!DatabaseIntact()) + { + return; + } + + char query[384], escapedProp[SQLPathLength]; + + g_StatsDB.Escape(prop, escapedProp, sizeof(escapedProp)); + + Transaction tx = new Transaction(); + + Format(query, sizeof(query), "INSERT %s INTO props (name) VALUES('%s')", ignore, escapedProp); + tx.AddQuery(query); + + Format(query, sizeof(query), "UPDATE props SET %s = %s + %d WHERE name = '%s'", what, what, points, escapedProp); + tx.AddQuery(query); + + g_StatsDB.Execute(tx, .onError=Tx_Error); + +#if defined LOGSTATS + LogMessage("[PH] DbProp [%s]", query); +#endif + +} + +public bool DatabaseIntact() +{ + return (g_StatsDB != null); +} + +//THREADED QUERY CALLBACKS + +public void T_ErrorOnly(Database db, DBResultSet result, const char[] error, any data) +{ + if (result == null) + { + LogError("[PH] DATABASE ERROR (error: %s)", error); + PrintToChatAll("[PH] DATABASE ERROR (error: %s)", error); + } +} + +public void T_DBConnect(Database db, const char[] error, any data) +{ + if (db == null) + { + SetFailState("%T: %s", "Could not connect to database", LANG_SERVER, error); + return; + } + + PrintToServer("Connected successfully."); + + g_StatsDB = db; + + db.SetCharset("utf8"); + + char driver[64]; + db.Driver.GetIdentifier(driver, sizeof(driver)); + + if (StrEqual(driver, "mysql", false)) + { + //g_DBType = DBType_MySQL; + ignore = "IGNORE"; + } + else if (StrEqual(driver, "sqlite", false)) + { + //g_DBType = DBType_SQLite; + ignore = "OR IGNORE"; + } + else if (StrEqual(driver, "pgsql", false)) + { + //g_DBType = DBType_PostgreSQL; + // In Postgres, use a rule to ignore dupes: http://stackoverflow.com/a/6176044/15880 + } + + Transaction tx = new Transaction(); + + char query[384]; + + Format(query, sizeof(query), "INSERT %s INTO servers (ip) VALUES('%s')", ignore, g_ServerIP); + tx.AddQuery(query); + + Format(query, sizeof(query), "SELECT points, time FROM servers WHERE ip='%s'", g_ServerIP); + tx.AddQuery(query); + + db.Execute(tx, Tx_GetServerPoints, Tx_Error, _, DBPrio_High); + + CreateTimer(120.0, Timer_UpdateServerScore, _, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); +} + +public void T_Statsme(Database db, DBResultSet result, const char[] error, any userid) +{ + int client = GetClientOfUserId(userid); + + if (client == 0) + { + return; + } + + if (result == null || !result.HasResults || result.RowCount < 1 || !result.FetchRow()) + { + PrintToChatAll("Failed to query (error: %s)", error); + return; + } + + char name[MAX_NAME_LENGTH+1]; + GetClientName(client, name, sizeof(name)); + + // "\x04%s is on rank %i with %i score (%i wins and %i losses). Time played %i hours." + CPrintToChatAll("\x04%t", "#TF_PH_Stats_StatsMe", name, result.FetchInt(3), result.FetchInt(0), result.FetchInt(1), + result.FetchInt(2), RoundFloat(float(result.FetchInt(4)) / SecondsPerHour)); +} + +public void T_Rank(Database db, DBResultSet result, const char[] error, any userid) +{ + int client = GetClientOfUserId(userid); + + if (client == 0) + { + return; + } + + if (result == null || !result.HasResults || result.RowCount < 1 || !result.FetchRow()) + { + PrintToChatAll("Failed to query (error: %s)", error); + return; + } + + Menu smenu = new Menu(MenuHandler); + + char buffer[255]; + char winloss[10]; + + smenu.Pagination = MENU_NO_PAGINATION; + smenu.SetTitle("%T", "#TF_PH_Stats_PersonalHeader", client); + + Format(buffer, sizeof(buffer), "%T", "#TF_PH_Stats_PersonalRank", client, result.FetchInt(3)); + smenu.AddItem("#TF_PH_Stats_PersonalRank", buffer, ITEMDRAW_DISABLED); + + Format(buffer, sizeof(buffer), "%T", "#TF_PH_Stats_PersonalWins", client, result.FetchInt(1)); + smenu.AddItem("#TF_PH_Stats_PersonalWins", buffer, ITEMDRAW_DISABLED); + + Format(buffer, sizeof(buffer), "%T", "#TF_PH_Stats_PersonalLosses", client, result.FetchInt(2)); + smenu.AddItem("#TF_PH_Stats_PersonalLosses", buffer, ITEMDRAW_DISABLED); + + Format(winloss, sizeof(winloss), "%.2f", float(result.FetchInt(1)) / float(result.FetchInt(2))); + Format(buffer, sizeof(buffer), "%T", "#TF_PH_Stats_PersonalWinLoss", client, winloss); + smenu.AddItem("#TF_PH_Stats_PersonalWinLoss", buffer, ITEMDRAW_DISABLED); + + Format(buffer, sizeof(buffer), "%T", "#TF_PH_Stats_PersonalScore", client, result.FetchInt(0)); + smenu.AddItem("#TF_PH_Stats_PersonalScore", buffer, ITEMDRAW_DISABLED); + + Format(buffer, sizeof(buffer), "%T", "#TF_PH_Stats_PersonalTime", client, RoundFloat(float(result.FetchInt(4)) / SecondsPerHour)); + smenu.AddItem("#TF_PH_Stats_PersonalTime", buffer, ITEMDRAW_DISABLED); + + smenu.Display(client, MENU_TIME_FOREVER); +} + +public void T_Top10(Database db, DBResultSet result, const char[] error, any userid) +{ + int client = GetClientOfUserId(userid); + + if (client == 0) + { + return; + } + + if (result == null || !result.HasResults) + { + PrintToChatAll("Failed to query (error: %s)", error); + return; + } + + Menu smenu = new Menu(MenuHandler); + // Top 10 needs pagination + //smenu.Pagination = MENU_NO_PAGINATION; + smenu.SetTitle("%T", "#TF_PH_Stats_Top10", client); + + char buffer[256]; + while (result.FetchRow()) + { + char name[MAX_NAME_LENGTH+1]; + result.FetchString(0, name, sizeof(name)); + + Format(buffer, sizeof(buffer), "(%i) %s", result.FetchInt(1)); + smenu.AddItem(buffer, buffer, ITEMDRAW_DISABLED); + } + + smenu.Display(client, MENU_TIME_FOREVER); +} + +// THREADED TRANSACTION CALLBACKS + +public void Tx_Error(Database db, any data, int numQueries, const char[] error, int failindex, any[] queryData) +{ + LogError("[PH] DATABASE ERROR (error: %s)", error); + PrintToChatAll("DATABASE ERROR (error: %s)", error); +} + +public void Tx_GetServerPoints(Database db, any data, int numQueriest, DBResultSet[] results, any[] queryData) +{ + if (results[TransactionData] == null || results[TransactionData].RowCount < 1 || !results[TransactionData].FetchRow()) + { + return; + } + + g_ServerPointCount = results[TransactionData].FetchInt(0); + g_ServerTime = results[TransactionData].FetchInt(1); +} + +public void Tx_PlayerConnect(Database db, any userid, int numQueries, DBResultSet[] results, any[] queryData) +{ + int client = GetClientOfUserId(userid); + if (client == 0 || results[TransactionData] == null || results[TransactionData].RowCount < 1 || !results[TransactionData].FetchRow()) + { + return; + } + + g_PointCount[client] = results[TransactionData].FetchInt(0); +} + +//STOCK FUNCTIONS + +void Stats_Init() +{ + // DB Connect logic here + if (!SQL_CheckConfig(DATABASE)) + { + SetFailState("No database configuration for %s", DATABASE); + } + + PrintToServer("Connecting to PropHunt database..."); + + Database.Connect(T_DBConnect, DATABASE); +} + +// EXTERNAL CALLBACKS + +// Add s to the game description +public Action PropHuntRedux_UpdateGameDescription(char[] description, int maxlength) +{ + StrCat(description, maxlength, "s"); + return Plugin_Changed; +} + diff --git a/addons/sourcemod/scripting/prophunt_stats_example_ajax.sp b/addons/sourcemod/scripting/prophunt_stats_example_ajax.sp new file mode 100644 index 0000000..cbd9199 --- /dev/null +++ b/addons/sourcemod/scripting/prophunt_stats_example_ajax.sp @@ -0,0 +1,430 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * PropHunt Stats Ajax Example + * Example on how to do PropHunt Stats Stuff using REST calls + * + * This plugin is used to exhibit more control over the stats system. + * The remote web application can do heuristics to tell if a server is being + * used to cheat at stats. + * + * It also makes it so the central stats system controls the point values for + * actions as they are no longer sent. + * + * It forces Steam 2 IDs for consistency. This way, you don't have to deal + * with Valve suddenly changing the Steam ID types (like they did last year). + * + * It's a good idea if the stats system ignores any kills on STEAM_ID_BOT + * or STEAM_ID_LAN for example. + * + * PropHunt Stats Ajax Example (C)2015 Powerlord (Ross Bemrose). + * All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ +#include +#include "include/prophunt" +#include +#include +#include +#include +#pragma semicolon 1 + +#define VERSION "1.0.0" + +#define DATABASE "prophuntstats" + +new const String:server[] = "https://www.example.com/prophunt/stats"; +const SQLNameLength = MAX_NAME_LENGTH*2+1; +const Steam2IdLength = 20; // STEAM_0:0:1234567890 <-- this is 1 larger than currently needed, but just to make sure... + +new Handle:g_Cvar_Enabled; +new Handle:g_Cvar_ServerID; + +new Handle:g_hScoreTimer; + +new bool:isPropHuntRound = false; + +new String:g_sServerID[256]; + +new String:g_sSteam2IDs[MAXPLAYERS+1][Steam2IdLength+1]; + +public Plugin:myinfo = { + name = "PropHunt Stats REST Example", + author = "Powerlord", + description = "Example on how to do PropHunt Stats Stuff using REST endpoints", + version = VERSION, + url = "" +}; + +public OnPluginStart() +{ + LoadTranslations("common.phrases"); + LoadTranslations("prophunt.phrases"); + + CreateConVar("prophunt_stats_example_ajax_version", VERSION, "PropHunt Stats Example version", FCVAR_PLUGIN|FCVAR_NOTIFY|FCVAR_DONTRECORD|FCVAR_SPONLY); + g_Cvar_Enabled = CreateConVar("prophunt_stats_example_ajax_enable", "1", "Enable PropHunt Stats Example?", FCVAR_PLUGIN|FCVAR_NOTIFY|FCVAR_DONTRECORD, true, 0.0, true, 1.0); + g_Cvar_ServerID = CreateConVar("prophunt_stats_example_ajax_serverid", "", "Server ID for PropHunt Stats", FCVAR_PLUGIN|FCVAR_PROTECTED); + + HookEvent("teamplay_round_start", Event_RoundStart); + HookEvent("teamplay_round_win", Event_RoundWin); + HookEvent("player_death", Event_PlayerDeath); + + RegConsoleCmd("rank", Cmd_Rank); + RegConsoleCmd("statsme", Cmd_StatsMe); + RegConsoleCmd("top10", Cmd_Top10); + RegConsoleCmd("stats", Cmd_Stats); +} + +public OnAllPluginsLoaded() +{ + // We do this to record the server's current IP address + new String:url[255]; + Format(url, sizeof(url), "%s/serverStart", server); + + // PropHunt Stats methods are not idempotent and thus should use POST + new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url); + + // All requests require a server ID + Steam_SetHTTPRequestGetOrPostParameter(request, "serverid", g_sServerID); + Steam_SendHTTPRequest(request, Response_ErrorOnly); + +} + +public OnMapStart() +{ + // assume false until round start + isPropHuntRound = false; +} + +public OnConfigsExecuted() +{ + GetConVarString(g_Cvar_ServerID, g_sServerID, sizeof(g_sServerID)); +} + +public OnClientAuthorized(client, const String:auth[]) +{ + if (!IsClientConnected(client) || IsFakeClient(client)) + { + return; + } + + // Ignore auth and use the Steam2 ID + if (!GetClientAuthId(client, AuthId_Steam2, g_sSteam2IDs[client], sizeof(g_sSteam2IDs[]))) + { + return; + } + + new String:url[255]; + Format(url, sizeof(url), "%s/connected", server); + + // PropHunt Stats methods are not idempotent and thus should use POST + new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url); + Steam_SetHTTPRequestGetOrPostParameter(request, "serverid", g_sServerID); + // Steam_SetHTTPRequestGetOrPostParameter(request, "format", "vdf"); + Steam_SetHTTPRequestGetOrPostParameter(request, "steamid", g_sSteam2IDs[client]); + Steam_PrioritizeHTTPRequest(request); // Connected needs to be high priority because it may need to create the stats user on the DB side + Steam_SendHTTPRequest(request, Response_ErrorOnly); +} + +public OnClientDisconnect(client) +{ + new String:url[255]; + Format(url, sizeof(url), "%s/disconnected", server); + + new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url); + Steam_SetHTTPRequestGetOrPostParameter(request, "serverid", g_sServerID); + // Steam_SetHTTPRequestGetOrPostParameter(request, "format", "vdf"); + Steam_SetHTTPRequestGetOrPostParameter(request, "steamid", g_sSteam2IDs[client]); + Steam_SendHTTPRequest(request, Response_ErrorOnly); + + g_sSteam2IDs[client][0] = '\0'; +} + +public Response_ErrorOnly(HTTPRequestHandle:HTTPRequest, bool:requestSuccessful, HTTPStatusCode:statusCode) +{ + if (!requestSuccessful) + { + LogError("Stats server returned error. Status code: %d", statusCode); + return; + } +} + +public Action:Cmd_Rank(client, args) +{ + if (client == 0) + { + ReplyToCommand(client, "%t", "Command is in-game only"); + return Plugin_Handled; + } + + + if (g_sSteam2IDs[client][0] == '\0') + { + ReplyToCommand(client, "%t", "Target is not in game"); + return Plugin_Handled; + } + + // Do REST call here + + return Plugin_Handled; +} + +public Action:Cmd_StatsMe(client, args) +{ +} + +public Action:Cmd_Top10(client, args) +{ +} + +public Action:Cmd_Stats(client, args) +{ +} + +public Event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast) +{ + isPropHuntRound = PropHuntRedux_IsRunning(); // This can change every round + + if (!GetConVarBool(g_Cvar_Enabled) || !isPropHuntRound) + { + return; + } + + g_hScoreTimer = CreateTimer(55.0, Timer_Score, _, TIMER_FLAG_NO_MAPCHANGE); +} + +public Event_RoundWin(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (!GetConVarBool(g_Cvar_Enabled) || !isPropHuntRound) + { + return; + } + + isPropHuntRound = false; + + CloseHandle(g_hScoreTimer); + + new winner = GetEventInt(event, "team"); + + for (new client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client)) + { + continue; + } + + new team = GetClientTeam(client); + + if (team == winner) // either props or hunters + { + // REST logic to add 3 points + } + else if (team > _:TFTeam_Spectator) // either props or hunters + { + // REST logic to subtract 1 point + } + } + + new String:map[PLATFORM_MAX_PATH]; + GetCurrentMap(map, sizeof(map)); + // REST logic to record map and team win +} + +public Event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast) +{ + if (!GetConVarBool(g_Cvar_Enabled) || !isPropHuntRound) + { + return; + } + + // PropHunt usually doesn't allow Spies, but just in case + if (GetEventInt(event, "death_flags") & TF_DEATHFLAG_DEADRINGER) + { + return; + } + + new clientID = GetEventInt(event, "userid"); + new attackerID = GetEventInt(event, "attacker"); + new assisterID = GetEventInt(event, "assister"); + new weaponID = GetEventInt(event, "weapon"); + new String:weapon[255]; + GetEventString(event, "weapon_logclassname", weapon, sizeof(weapon)); + + new client = GetClientOfUserId(clientID); + new attacker; + if (attackerID > 0) + { + attacker = GetClientOfUserId(attackerID); + } + + new assister; + if (assisterID > 0) + { + assister = GetClientOfUserId(assisterID); + } + + new String:url[255]; + Format(url, sizeof(url), "%s/playerkill", server); + + new String:clientName[MAX_NAME_LENGTH+1]; + new String:attackerName[MAX_NAME_LENGTH+1]; + new String:assisterName[MAX_NAME_LENGTH+1]; + + GetClientName(client, clientName, sizeof(clientName)); + + // Note that in the original, this method had no less than 3 DB calls in it. However, I want to do just one REST call. + + new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url); + Steam_SetHTTPRequestGetOrPostParameter(request, "serverid", g_sServerID); + Steam_SetHTTPRequestGetOrPostParameter(request, "format", "vdf"); + Steam_SetHTTPRequestGetOrPostParameter(request, "clientSteamId", g_sSteam2IDs[client]); + if (attacker > 0) + { + GetClientName(attacker, attackerName, sizeof(attackerName)); + Steam_SetHTTPRequestGetOrPostParameter(request, "attackerSteamId", g_sSteam2IDs[attacker]); + new team = GetClientTeam(attacker); + new String:strTeam[2]; + IntToString(team, strTeam, sizeof(strTeam)); + + Steam_SetHTTPRequestGetOrPostParameter(request, "killerTeam", strTeam); + + } + + if (assister > 0) + { + GetClientName(assister, assisterName, sizeof(assisterName)); + Steam_SetHTTPRequestGetOrPostParameter(request, "assisterSteamId", g_sSteam2IDs[assister]); + } + + new weaponIndex = -1; + + new String:strWeaponIndex[7]; + + if (weaponID > MaxClients) + { + weaponIndex = GetEntProp(weaponID, Prop_Send, "m_iItemDefinitionIndex"); + } + + IntToString(weaponIndex, strWeaponIndex, sizeof(strWeaponIndex)); + + // Definition index will allow lookup of weapons on remote web server. + Steam_SetHTTPRequestGetOrPostParameter(request, "weaponDefinitionIndex", strWeaponIndex); + + new String:propModel[PLATFORM_MAX_PATH]; + PropHuntRedux_GetPropModel(client, propModel, sizeof(propModel)); + + new Handle:data = CreateDataPack(); + + WritePackCell(data, clientID); + WritePackCell(data, attackerID); + WritePackCell(data, assisterID); + WritePackString(data, clientName); + WritePackString(data, attackerName); + WritePackString(data, assisterName); + + // TODO Check to see what other fields we're missing. + Steam_SendHTTPRequest(request, Response_PlayerKilled, data); // different method so we can print score changes +} + +public Response_PlayerKilled(HTTPRequestHandle:response, bool:requestSuccessful, HTTPStatusCode:statusCode, any:data) +{ + if (!requestSuccessful) + { + LogError("Stats server returned error. Status code: %d", statusCode); + return; + } + + ResetPack(data); + + new clientID = ReadPackCell(data); + new attackerID = ReadPackCell(data); + new assisterID = ReadPackCell(data); + + new client = GetClientOfUserId(clientID); + new attacker; + + if (attackerID > 0) + { + attacker = GetClientOfUserId(attackerID); + } + + new assister; + + if (assisterID > 0) + { + assister = GetClientOfUserId(assisterID); + } + + new String:clientName[MAX_NAME_LENGTH+1]; + new String:attackerName[MAX_NAME_LENGTH+1]; + new String:assisterName[MAX_NAME_LENGTH+1]; + + ReadPackString(data, clientName, sizeof(clientName)); + ReadPackString(data, attackerName, sizeof(attackerName)); + ReadPackString(data, assisterName, sizeof(assisterName)); + + new bufferSize = Steam_GetHTTPResponseBodySize(response); + new String:buffer[bufferSize]; + Steam_GetHTTPResponseBodyData(response, buffer, bufferSize); + + new Handle:kvBuffer; + if (StringToKeyValues(kvBuffer, buffer, "PropHuntKillResponse")) + { + new clientPoints = KvGetNum(kvBuffer, "clientPoints", -1); + new killerPoints = KvGetNum(kvBuffer, "killerPoints", 2); + new assisterPoints = KvGetNum(kvBuffer, "assisterPoints", 1); + + if (client > 0) + CPrintToChat(client, "%t", "#TF_PH_AlterScore_Death", attackerName, clientPoints*-1); + + if (attacker > 0) + CPrintToChat(attacker, "%t", "#TF_PH_AlterScore_Kill", clientName, killerPoints); + + if (assister > 0) + CPrintToChat(assister, "%t", "#TF_PH_AlterScore_Assist", clientName, assisterPoints); + + + // Do stuff with the keyvalues + } +} + +public Action:Timer_Score(Handle:Timer) +{ + for (new client = 1; client <= MaxClients; client++) + { + if (IsClientInGame(client) && IsPlayerAlive(client) && GetClientTeam(client) == TEAM_PROP) + { + new String:url[255]; + Format(url, sizeof(url), "%s/proptimescore", server); + + new HTTPRequestHandle:request = Steam_CreateHTTPRequest(HTTPMethod_POST, url); + Steam_SetHTTPRequestGetOrPostParameter(request, "serverid", g_sServerID); + // Steam_SetHTTPRequestGetOrPostParameter(request, "format", "vdf"); + Steam_SetHTTPRequestGetOrPostParameter(request, "steamid", g_sSteam2IDs[client]); + Steam_SendHTTPRequest(request, Response_ErrorOnly); + } + } +} diff --git a/updater/dev/prophunt.txt b/updater/dev/prophunt.txt new file mode 100644 index 0000000..658df0b --- /dev/null +++ b/updater/dev/prophunt.txt @@ -0,0 +1,116 @@ +"Updater" +{ + "Information" + { + "Version" + { + "Latest" "3.4.0.0" + "Previous" "3.3.3" + } + + "Notes" "Added updater support" + "Notes" "Added workshop map support" + "Notes" "Removed DHooks dependency" + "Notes" "Removed TF2Attributes dependency" + "Notes" "Override First Blood with our own system" + "Notes" "Add an option to enable respawning during Setup (on by default)" + + "Notes" "See https://forums.alliedmods.net/showthread.php?t=228086 for full changelog." + } + + "Files" + { + "Patch" + { + "Plugin" "Path_SM/plugins/prophunt.smx" + "Plugin" "Path_SM/data/prophunt_config.cfg" + "Plugin" "Path_SM/gamedata/tf2-switch-teams.txt" + + "Source" "Path_SM/scripting/prophunt.sp" + } + + "Plugin" "Path_SM/plugins/prophunt.smx" + + "Plugin" "Path_SM/translations/prophunt.phrases.txt" + + "Plugin" "Path_SM/gamedata/tf2-switch-teams.txt" + + "Plugin" "Path_SM/data/prophunt_config.cfg" + "Plugin" "Path_SM/data/prop_common.txt" + "Plugin" "Path_SM/data/prop_menu.txt" + "Plugin" "Path_SM/data/props_allmaps.txt" + + "Plugin" "Path_SM/data/maps/arena_brawl.cfg" + "Plugin" "Path_SM/data/maps/arena_concord.cfg" + "Plugin" "Path_SM/data/maps/arena_desolation.cfg" + "Plugin" "Path_SM/data/maps/arena_devils.cfg" + "Plugin" "Path_SM/data/maps/arena_farm.cfg" + "Plugin" "Path_SM/data/maps/arena_goldtooth.cfg" + "Plugin" "Path_SM/data/maps/arena_harvest.cfg" + "Plugin" "Path_SM/data/maps/arena_ravage.cfg" + "Plugin" "Path_SM/data/maps/arena_sawmill.cfg" + "Plugin" "Path_SM/data/maps/arena_storm.cfg" + "Plugin" "Path_SM/data/maps/ph_007facility.cfg" + "Plugin" "Path_SM/data/maps/ph_amazon.cfg" + "Plugin" "Path_SM/data/maps/ph_basalt.cfg" + "Plugin" "Path_SM/data/maps/ph_bigbutt.cfg" + "Plugin" "Path_SM/data/maps/ph_campsite.cfg" + "Plugin" "Path_SM/data/maps/ph_canyon.cfg" + "Plugin" "Path_SM/data/maps/ph_cargo.cfg" + "Plugin" "Path_SM/data/maps/ph_cchotel.cfg" + "Plugin" "Path_SM/data/maps/ph_chapel.cfg" + "Plugin" "Path_SM/data/maps/ph_cliffface.cfg" + "Plugin" "Path_SM/data/maps/ph_courtyard.cfg" + "Plugin" "Path_SM/data/maps/ph_crater.cfg" + "Plugin" "Path_SM/data/maps/ph_cyberpunk.cfg" + "Plugin" "Path_SM/data/maps/ph_devils.cfg" + "Plugin" "Path_SM/data/maps/ph_dockyard.cfg" + "Plugin" "Path_SM/data/maps/ph_farm.cfg" + "Plugin" "Path_SM/data/maps/ph_goldtooth.cfg" + "Plugin" "Path_SM/data/maps/ph_gorge.cfg" + "Plugin" "Path_SM/data/maps/ph_grassland.cfg" + "Plugin" "Path_SM/data/maps/ph_hanger18.cfg" + "Plugin" "Path_SM/data/maps/ph_harvest.cfg" + "Plugin" "Path_SM/data/maps/ph_headquarters.cfg" + "Plugin" "Path_SM/data/maps/ph_hilltop.cfg" + "Plugin" "Path_SM/data/maps/ph_kakariko.cfg" + "Plugin" "Path_SM/data/maps/ph_laboratory.cfg" + "Plugin" "Path_SM/data/maps/ph_lakeside.cfg" + "Plugin" "Path_SM/data/maps/ph_lumberyard.cfg" + "Plugin" "Path_SM/data/maps/ph_manor.cfg" + "Plugin" "Path_SM/data/maps/ph_manorgrounds.cfg" + "Plugin" "Path_SM/data/maps/ph_maze.cfg" + "Plugin" "Path_SM/data/maps/ph_medieval.cfg" + "Plugin" "Path_SM/data/maps/ph_mountain.cfg" + "Plugin" "Path_SM/data/maps/ph_nightclub.cfg" + "Plugin" "Path_SM/data/maps/ph_northural.cfg" + "Plugin" "Path_SM/data/maps/ph_oasis.cfg" + "Plugin" "Path_SM/data/maps/ph_otherside.cfg" + "Plugin" "Path_SM/data/maps/ph_petitcourtyard.cfg" + "Plugin" "Path_SM/data/maps/ph_range.cfg" + "Plugin" "Path_SM/data/maps/ph_redquarters.cfg" + "Plugin" "Path_SM/data/maps/ph_redstone.cfg" + "Plugin" "Path_SM/data/maps/ph_sawmill.cfg" + "Plugin" "Path_SM/data/maps/ph_snowworks.cfg" + "Plugin" "Path_SM/data/maps/ph_spookycanyon.cfg" + "Plugin" "Path_SM/data/maps/ph_spookyharvest.cfg" + "Plugin" "Path_SM/data/maps/ph_switcheroo.cfg" + "Plugin" "Path_SM/data/maps/ph_target.cfg" + "Plugin" "Path_SM/data/maps/ph_timbertown.cfg" + "Plugin" "Path_SM/data/maps/ph_town.cfg" + "Plugin" "Path_SM/data/maps/ph_trainset.cfg" + "Plugin" "Path_SM/data/maps/ph_tundra.cfg" + "Plugin" "Path_SM/data/maps/ph_warehouse.cfg" + "Plugin" "Path_SM/data/maps/ph_watchtower.cfg" + "Plugin" "Path_SM/data/maps/ph_watermill.cfg" + "Plugin" "Path_SM/data/maps/ph_well.cfg" + "Plugin" "Path_SM/data/maps/spooky_ravine.cfg" + +// Sounds never change +// "Plugin" "Path_Mod/sound/prophunt/found.mp3" +// "Plugin" "Path_Mod/sound/prophunt/oneandonly.mp3" +// "Plugin" "Path_Mod/sound/prophunt/snaaake.mp3" + + "Source" "Path_SM/scripting/prophunt.sp" + } +} \ No newline at end of file diff --git a/updater/prophunt.txt b/updater/prophunt.txt new file mode 100644 index 0000000..aee12b7 --- /dev/null +++ b/updater/prophunt.txt @@ -0,0 +1,114 @@ +"Updater" +{ + "Information" + { + "Version" + { + "Latest" "3.3.3" + "Previous" "3.3.2" + } + + "Notes" "Fix issue with DHooks causing crashes / not team switching" + + "Notes" "See https://forums.alliedmods.net/showthread.php?t=228086 for full changelog." + } + + "Files" + { + "Patch" + { + "Plugin" "Path_SM/plugins/prophunt.smx" + + "Plugin" "Path_SM/gamedata/tf2-roundend.games.txt" + + "Plugin" "Path_SM/data/maps/ph_blockfort.cfg" + "Plugin" "Path_SM/data/maps/ph_buckethunt.cfg" + } + + "Plugin" "Path_SM/plugins/prophunt.smx" + + "Plugin" "Path_SM/translations/prophunt.phrases.txt" + + "Plugin" "Path_SM/gamedata/tf2-roundend.games.txt" + + "Plugin" "Path_SM/data/prophunt_config.cfg" + "Plugin" "Path_SM/data/prop_common.txt" + "Plugin" "Path_SM/data/prop_menu.txt" + "Plugin" "Path_SM/data/props_allmaps.txt" + + "Plugin" "Path_SM/data/maps/arena_brawl.cfg" + "Plugin" "Path_SM/data/maps/arena_concord.cfg" + "Plugin" "Path_SM/data/maps/arena_desolation.cfg" + "Plugin" "Path_SM/data/maps/arena_devils.cfg" + "Plugin" "Path_SM/data/maps/arena_farm.cfg" + "Plugin" "Path_SM/data/maps/arena_goldtooth.cfg" + "Plugin" "Path_SM/data/maps/arena_harvest.cfg" + "Plugin" "Path_SM/data/maps/arena_ravage.cfg" + "Plugin" "Path_SM/data/maps/arena_sawmill.cfg" + "Plugin" "Path_SM/data/maps/arena_storm.cfg" + "Plugin" "Path_SM/data/maps/ph_007facility.cfg" + "Plugin" "Path_SM/data/maps/ph_amazon.cfg" + "Plugin" "Path_SM/data/maps/ph_basalt.cfg" + "Plugin" "Path_SM/data/maps/ph_bigbutt.cfg" + "Plugin" "Path_SM/data/maps/ph_blockfort.cfg" + "Plugin" "Path_SM/data/maps/ph_buckethunt.cfg" + "Plugin" "Path_SM/data/maps/ph_campsite.cfg" + "Plugin" "Path_SM/data/maps/ph_canyon.cfg" + "Plugin" "Path_SM/data/maps/ph_cargo.cfg" + "Plugin" "Path_SM/data/maps/ph_cchotel.cfg" + "Plugin" "Path_SM/data/maps/ph_chapel.cfg" + "Plugin" "Path_SM/data/maps/ph_cliffface.cfg" + "Plugin" "Path_SM/data/maps/ph_courtyard.cfg" + "Plugin" "Path_SM/data/maps/ph_crater.cfg" + "Plugin" "Path_SM/data/maps/ph_cyberpunk.cfg" + "Plugin" "Path_SM/data/maps/ph_devils.cfg" + "Plugin" "Path_SM/data/maps/ph_dockyard.cfg" + "Plugin" "Path_SM/data/maps/ph_farm.cfg" + "Plugin" "Path_SM/data/maps/ph_goldtooth.cfg" + "Plugin" "Path_SM/data/maps/ph_gorge.cfg" + "Plugin" "Path_SM/data/maps/ph_grassland.cfg" + "Plugin" "Path_SM/data/maps/ph_hanger18.cfg" + "Plugin" "Path_SM/data/maps/ph_harvest.cfg" + "Plugin" "Path_SM/data/maps/ph_headquarters.cfg" + "Plugin" "Path_SM/data/maps/ph_hilltop.cfg" + "Plugin" "Path_SM/data/maps/ph_kakariko.cfg" + "Plugin" "Path_SM/data/maps/ph_laboratory.cfg" + "Plugin" "Path_SM/data/maps/ph_lakeside.cfg" + "Plugin" "Path_SM/data/maps/ph_lumberyard.cfg" + "Plugin" "Path_SM/data/maps/ph_manor.cfg" + "Plugin" "Path_SM/data/maps/ph_manorgrounds.cfg" + "Plugin" "Path_SM/data/maps/ph_maze.cfg" + "Plugin" "Path_SM/data/maps/ph_medieval.cfg" + "Plugin" "Path_SM/data/maps/ph_mountain.cfg" + "Plugin" "Path_SM/data/maps/ph_nightclub.cfg" + "Plugin" "Path_SM/data/maps/ph_northural.cfg" + "Plugin" "Path_SM/data/maps/ph_oasis.cfg" + "Plugin" "Path_SM/data/maps/ph_otherside.cfg" + "Plugin" "Path_SM/data/maps/ph_petitcourtyard.cfg" + "Plugin" "Path_SM/data/maps/ph_range.cfg" + "Plugin" "Path_SM/data/maps/ph_redquarters.cfg" + "Plugin" "Path_SM/data/maps/ph_redstone.cfg" + "Plugin" "Path_SM/data/maps/ph_sawmill.cfg" + "Plugin" "Path_SM/data/maps/ph_snowworks.cfg" + "Plugin" "Path_SM/data/maps/ph_spookycanyon.cfg" + "Plugin" "Path_SM/data/maps/ph_spookyharvest.cfg" + "Plugin" "Path_SM/data/maps/ph_switcheroo.cfg" + "Plugin" "Path_SM/data/maps/ph_target.cfg" + "Plugin" "Path_SM/data/maps/ph_timbertown.cfg" + "Plugin" "Path_SM/data/maps/ph_town.cfg" + "Plugin" "Path_SM/data/maps/ph_trainset.cfg" + "Plugin" "Path_SM/data/maps/ph_tundra.cfg" + "Plugin" "Path_SM/data/maps/ph_warehouse.cfg" + "Plugin" "Path_SM/data/maps/ph_watchtower.cfg" + "Plugin" "Path_SM/data/maps/ph_watermill.cfg" + "Plugin" "Path_SM/data/maps/ph_well.cfg" + "Plugin" "Path_SM/data/maps/spooky_ravine.cfg" + +// Sounds never change +// "Plugin" "Path_Mod/sound/prophunt/found.mp3" +// "Plugin" "Path_Mod/sound/prophunt/oneandonly.mp3" +// "Plugin" "Path_Mod/sound/prophunt/snaaake.mp3" + + "Source" "Path_SM/scripting/prophunt.sp" + } +} \ No newline at end of file