Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

21410 lines
680 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: The TF Game rules
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================
  7. #include "cbase.h"
  8. #include "tf_gamerules.h"
  9. #include "tf_classdata.h"
  10. #include "ammodef.h"
  11. #include "KeyValues.h"
  12. #include "tf_weaponbase.h"
  13. #include "tf_weaponbase_gun.h"
  14. #include "tier0/icommandline.h"
  15. #include "convar_serverbounded.h"
  16. #include "econ_item_system.h"
  17. #include "tf_weapon_grenadelauncher.h"
  18. #include "tf_logic_robot_destruction.h"
  19. #include "tf_logic_player_destruction.h"
  20. #include "tf_matchmaking_shared.h"
  21. #ifdef CLIENT_DLL
  22. #include <game/client/iviewport.h>
  23. #include "c_tf_player.h"
  24. #include "c_tf_objective_resource.h"
  25. #include <filesystem.h>
  26. #include "c_tf_team.h"
  27. #include "dt_utlvector_recv.h"
  28. #include "tf_autorp.h"
  29. #include "player_vs_environment/c_tf_upgrades.h"
  30. #include "video/ivideoservices.h"
  31. #include "tf_gc_client.h"
  32. #include "c_tf_playerresource.h"
  33. #else
  34. #include "basemultiplayerplayer.h"
  35. #include "voice_gamemgr.h"
  36. #include "items.h"
  37. #include "team.h"
  38. #include "game.h"
  39. #include "tf_bot_temp.h"
  40. #include "tf_player.h"
  41. #include "tf_team.h"
  42. #include "player_resource.h"
  43. #include "entity_tfstart.h"
  44. #include "filesystem.h"
  45. #include "minigames/tf_duel.h"
  46. #include "tf_obj.h"
  47. #include "tf_objective_resource.h"
  48. #include "tf_player_resource.h"
  49. #include "team_control_point_master.h"
  50. #include "team_train_watcher.h"
  51. #include "playerclass_info_parse.h"
  52. #include "team_control_point_master.h"
  53. #include "coordsize.h"
  54. #include "entity_healthkit.h"
  55. #include "tf_gamestats.h"
  56. #include "entity_capture_flag.h"
  57. #include "tf_player_resource.h"
  58. #include "tf_obj_sentrygun.h"
  59. #include "activitylist.h"
  60. #include "AI_ResponseSystem.h"
  61. #include "hl2orange.spa.h"
  62. #include "hltvdirector.h"
  63. #include "tf_projectile_arrow.h"
  64. #include "func_suggested_build.h"
  65. #include "tf_gc_api.h"
  66. #include "tf_weaponbase_grenadeproj.h"
  67. #include "engine/IEngineSound.h"
  68. #include "soundenvelope.h"
  69. #include "dt_utlvector_send.h"
  70. #include "tf_tactical_mission.h"
  71. #include "nav_mesh/tf_nav_area.h"
  72. #include "bot/tf_bot.h"
  73. #include "bot/tf_bot_manager.h"
  74. #include "bot/map_entities/tf_bot_roster.h"
  75. #include "econ_gcmessages.h"
  76. #include "vgui/ILocalize.h"
  77. #include "tier3/tier3.h"
  78. #include "tf_ammo_pack.h"
  79. #include "tf_gcmessages.h"
  80. #include "vote_controller.h"
  81. #include "tf_voteissues.h"
  82. #include "halloween/headless_hatman.h"
  83. #include "halloween/ghost/ghost.h"
  84. #include "halloween/eyeball_boss/eyeball_boss.h"
  85. #include "halloween/merasmus/merasmus.h"
  86. #include "halloween/merasmus/merasmus_dancer.h"
  87. #include "tf_extra_map_entity.h"
  88. #include "tf_weapon_grenade_pipebomb.h"
  89. #include "tf_weapon_flaregun.h"
  90. #include "tf_weapon_sniperrifle.h"
  91. #include "tf_weapon_knife.h"
  92. #include "tf_weapon_jar.h"
  93. #include "halloween/tf_weapon_spellbook.h"
  94. #include "player_vs_environment/tf_population_manager.h"
  95. #include "player_vs_environment/monster_resource.h"
  96. #include "util_shared.h"
  97. #include "gc_clientsystem.h"
  98. #include "raid/tf_raid_logic.h"
  99. #include "player_vs_environment/tf_boss_battle_logic.h"
  100. #include "player_vs_environment/tf_mann_vs_machine_logic.h"
  101. #include "player_vs_environment/tf_upgrades.h"
  102. #include "tf_wheel_of_doom.h"
  103. #include "tf_halloween_souls_pickup.h"
  104. #include "halloween/zombie/zombie.h"
  105. #include "teamplay_round_timer.h"
  106. #include "halloween/spell/tf_spell_pickup.h"
  107. #include "tf_weapon_laser_pointer.h"
  108. #include "effect_dispatch_data.h"
  109. #include "tf_fx.h"
  110. #include "econ_game_account_server.h"
  111. #include "tf_gc_server.h"
  112. #include "tf_logic_halloween_2014.h"
  113. #include "tf_obj_sentrygun.h"
  114. #include "entity_halloween_pickup.h"
  115. #include "entity_rune.h"
  116. #include "func_powerupvolume.h"
  117. #include "workshop/maps_workshop.h"
  118. #include "tf_passtime_logic.h"
  119. #include "cdll_int.h"
  120. #include "halloween/halloween_gift_spawn_locations.h"
  121. #include "tf_weapon_invis.h"
  122. #include "tf_gc_server.h"
  123. #include "gcsdk/msgprotobuf.h"
  124. #include "tf_party.h"
  125. #include "tf_autobalance.h"
  126. #endif
  127. #include "tf_mann_vs_machine_stats.h"
  128. #include "tf_upgrades_shared.h"
  129. #include "tf_item_powerup_bottle.h"
  130. #include "tf_weaponbase_gun.h"
  131. #include "tf_weaponbase_melee.h"
  132. #include "tf_wearable_item_demoshield.h"
  133. #include "tf_weapon_buff_item.h"
  134. #include "tf_weapon_flamethrower.h"
  135. #include "tf_weapon_medigun.h"
  136. #include "econ_holidays.h"
  137. #include "rtime.h"
  138. #include "tf_revive.h"
  139. #include "tf_duckleaderboard.h"
  140. #include "passtime_convars.h"
  141. #include "tier3/tier3.h"
  142. // memdbgon must be the last include file in a .cpp file!!!
  143. #include "tier0/memdbgon.h"
  144. #define ITEM_RESPAWN_TIME 10.0f
  145. #define MASK_RADIUS_DAMAGE ( MASK_SHOT & ~( CONTENTS_HITBOX ) )
  146. // Halloween 2013 VO defines for plr_hightower_event
  147. #define HELLTOWER_TIMER_INTERVAL ( 60 + RandomInt( -30, 30 ) )
  148. #define HELLTOWER_RARE_LINE_CHANCE 0.15 // 15%
  149. #define HELLTOWER_MISC_CHANCE 0.50 // 50%
  150. static int g_TauntCamRagdollAchievements[] =
  151. {
  152. 0, // TF_CLASS_UNDEFINED
  153. 0, // TF_CLASS_SCOUT,
  154. 0, // TF_CLASS_SNIPER,
  155. 0, // TF_CLASS_SOLDIER,
  156. 0, // TF_CLASS_DEMOMAN,
  157. ACHIEVEMENT_TF_MEDIC_FREEZECAM_RAGDOLL, // TF_CLASS_MEDIC,
  158. 0, // TF_CLASS_HEAVYWEAPONS,
  159. 0, // TF_CLASS_PYRO,
  160. ACHIEVEMENT_TF_SPY_FREEZECAM_FLICK, // TF_CLASS_SPY,
  161. 0, // TF_CLASS_ENGINEER,
  162. 0, // TF_CLASS_CIVILIAN,
  163. 0, // TF_CLASS_COUNT_ALL,
  164. };
  165. static int g_TauntCamAchievements[] =
  166. {
  167. 0, // TF_CLASS_UNDEFINED
  168. 0, // TF_CLASS_SCOUT,
  169. ACHIEVEMENT_TF_SNIPER_FREEZECAM_HAT, // TF_CLASS_SNIPER,
  170. ACHIEVEMENT_TF_SOLDIER_FREEZECAM_GIBS, // TF_CLASS_SOLDIER, (extra check to count the number of gibs onscreen)
  171. ACHIEVEMENT_TF_DEMOMAN_FREEZECAM_SMILE, // TF_CLASS_DEMOMAN,
  172. 0, // TF_CLASS_MEDIC,
  173. ACHIEVEMENT_TF_HEAVY_FREEZECAM_TAUNT, // TF_CLASS_HEAVYWEAPONS, (there's an extra check on this one to see if we're also invuln)
  174. ACHIEVEMENT_TF_PYRO_FREEZECAM_TAUNTS, // TF_CLASS_PYRO,
  175. 0, // TF_CLASS_SPY,
  176. ACHIEVEMENT_TF_ENGINEER_FREEZECAM_TAUNT, // TF_CLASS_ENGINEER,
  177. 0, // TF_CLASS_CIVILIAN,
  178. 0, // TF_CLASS_COUNT_ALL,
  179. };
  180. // used for classes that have more than one freeze cam achievement (example: Sniper)
  181. static int g_TauntCamAchievements2[] =
  182. {
  183. 0, // TF_CLASS_UNDEFINED
  184. 0, // TF_CLASS_SCOUT,
  185. ACHIEVEMENT_TF_SNIPER_FREEZECAM_WAVE, // TF_CLASS_SNIPER,
  186. ACHIEVEMENT_TF_SOLDIER_FREEZECAM_TAUNT, // TF_CLASS_SOLDIER,
  187. ACHIEVEMENT_TF_DEMOMAN_FREEZECAM_RUMP, // TF_CLASS_DEMOMAN,
  188. 0, // TF_CLASS_MEDIC,
  189. 0, // TF_CLASS_HEAVYWEAPONS,
  190. 0, // TF_CLASS_PYRO,
  191. 0, // TF_CLASS_SPY,
  192. 0, // TF_CLASS_ENGINEER,
  193. 0, // TF_CLASS_CIVILIAN,
  194. 0, // TF_CLASS_COUNT_ALL,
  195. };
  196. struct MapInfo_t
  197. {
  198. const char *pDiskName;
  199. const char *pDisplayName;
  200. const char *pGameType;
  201. };
  202. static MapInfo_t s_ValveMaps[] = {
  203. { "ctf_2fort", "2Fort", "#Gametype_CTF" },
  204. { "cp_dustbowl", "Dustbowl", "#TF_AttackDefend" },
  205. { "cp_granary", "Granary", "#Gametype_CP" },
  206. { "cp_well", "Well", "#Gametype_CP" },
  207. { "cp_foundry", "Foundry", "#Gametype_CP" },
  208. { "cp_gravelpit", "Gravel Pit", "#TF_AttackDefend" },
  209. { "tc_hydro", "Hydro", "#TF_TerritoryControl" },
  210. { "ctf_well", "Well", "#Gametype_CTF" },
  211. { "cp_badlands", "Badlands", "#Gametype_CP" },
  212. { "pl_goldrush", "Gold Rush", "#Gametype_Escort" },
  213. { "pl_badwater", "Badwater Basin", "#Gametype_Escort" },
  214. { "plr_pipeline", "Pipeline", "#Gametype_EscortRace" },
  215. { "cp_gorge", "Gorge", "#TF_AttackDefend" },
  216. { "ctf_doublecross", "Double Cross", "#Gametype_CTF" },
  217. { "pl_thundermountain", "Thunder Mountain", "#Gametype_Escort" },
  218. { "tr_target", "Target", "#GameType_Training" },
  219. { "tr_dustbowl", "Dustbowl", "#GameType_Training" },
  220. { "cp_manor_event", "Mann Manor", "#TF_AttackDefend" },
  221. { "cp_mountainlab", "Mountain Lab", "#TF_AttackDefend" },
  222. { "cp_degrootkeep", "DeGroot Keep", "#TF_MedievalAttackDefend" },
  223. { "pl_barnblitz", "Barnblitz", "#Gametype_Escort" },
  224. { "pl_upward", "Upward", "#Gametype_Escort" },
  225. { "plr_hightower", "Hightower", "#Gametype_EscortRace" },
  226. { "koth_viaduct", "Viaduct", "#Gametype_Koth" },
  227. { "koth_viaduct_event", "Eyeaduct", "#Gametype_Koth" },
  228. { "koth_king", "Kong King", "#Gametype_Koth" },
  229. { "koth_lakeside_event", "Ghost Fort", "#Gametype_Koth" },
  230. { "plr_hightower_event", "Helltower", "#Gametype_EscortRace" },
  231. { "rd_asteroid", "Asteroid", "#Gametype_RobotDestruction" },
  232. { "pl_cactuscanyon", "Cactus Canyon", "#Gametype_Escort" },
  233. { "sd_doomsday", "Doomsday", "#Gametype_SD" },
  234. { "sd_doomsday_event", "Carnival of Carnage", "#Gametype_SD" },
  235. };
  236. static MapInfo_t s_CommunityMaps[] = {
  237. { "pl_borneo", "Borneo", "#Gametype_Escort" },
  238. { "koth_suijin", "Suijin", "#Gametype_Koth" },
  239. { "cp_snowplow", "Snowplow", "#TF_AttackDefend" },
  240. { "koth_probed", "Probed", "#Gametype_Koth" },
  241. { "pd_watergate", "Watergate", "#Gametype_PlayerDestruction" },
  242. { "arena_byre", "Byre", "#Gametype_Arena" },
  243. { "ctf_2fort_invasion", "2Fort Invasion", "#Gametype_CTF" },
  244. { "cp_sunshine_event", "Sinshine", "#Gametype_CP" },
  245. { "pl_millstone_event", "Hellstone", "#Gametype_Escort" },
  246. { "cp_gorge_event", "Gorge Event", "#TF_AttackDefend" },
  247. { "koth_moonshine_event", "Moonshine Event", "#Gametype_Koth" },
  248. { "pl_snowycoast", "Snowycoast", "#Gametype_Escort" },
  249. { "cp_vanguard", "Vanguard", "#Gametype_CP" },
  250. { "ctf_landfall", "Landfall", "#Gametype_CTF" },
  251. { "koth_highpass", "Highpass", "#Gametype_Koth" },
  252. { "koth_maple_ridge_event", "Maple Ridge Event", "#Gametype_Koth" },
  253. { "pl_fifthcurve_event", "Brimstone", "#Gametype_Escort" },
  254. { "pd_pit_of_death_event", "Pit of Death", "#Gametype_PlayerDestruction" },
  255. };
  256. /*
  257. !! Commented out until we use this data, but we should keep updating it so its current when we need it
  258. struct FeaturedWorkshopMap_t
  259. {
  260. // The name the map was shipped as in our files (e.g. it was available as maps/<this>.bsp)
  261. // NOTE After maps are un-shipped we leave them here and don't change this value, this allows mapcycles that have
  262. // this map to proper redirect to the workshop in the future
  263. const char *pShippedName;
  264. // The workshop ID of the map (The public one, not the the private upload they use to send us the sources)
  265. PublishedFileId_t nWorkshopID;
  266. };
  267. static FeaturedWorkshopMap_t s_FeaturedWorkshopMaps[] = {
  268. // !! DO NOT remove these after we stop shipping the file -- they exist to ensure users can refer to
  269. // !! e.g. "koth_suijin" and get redirected to the workshop map once the file is no longer in our depots.
  270. // Summer 2015 Operation
  271. { "pl_borneo", 454139147 },
  272. { "koth_suijin", 454188876 },
  273. { "cp_snowplow", 454116615 },
  274. // September 2015 Invasion Community Update
  275. { "koth_probed", 454139808 },
  276. { "pd_watergate", 456016898 },
  277. { "arena_byre", 454142123 },
  278. { "ctf_2fort_invasion", FIXME }, // No public workshop entry yet
  279. // Halloween 2015
  280. { "cp_sunshine_event", 532473747 },
  281. { "pl_millstone_event", 531384846 },
  282. { "cp_gorge_event", 527145539 },
  283. { "koth_moonshine_event", 534874830 },
  284. // December 2015 Campaign
  285. { "pl_snowycoast", 469072819 },
  286. { "cp_vanguard", 462908782 },
  287. { "ctf_landfall", 459651881 },
  288. { "koth_highpass", 463803443 },
  289. // Halloween 2016
  290. { "koth_maple_ridge_event", 537540619 },
  291. { "pl_fifthcurve_event", 764966851 },
  292. { "pd_pit_of_death_event", 537319626 },
  293. };
  294. */
  295. bool IsValveMap( const char *pMapName )
  296. {
  297. for ( int i = 0; i < ARRAYSIZE( s_ValveMaps ); ++i )
  298. {
  299. if ( !Q_stricmp( s_ValveMaps[i].pDiskName, pMapName ) )
  300. {
  301. return true;
  302. }
  303. }
  304. return false;
  305. }
  306. extern ConVar mp_capstyle;
  307. extern ConVar sv_turbophysics;
  308. extern ConVar tf_vaccinator_small_resist;
  309. extern ConVar tf_vaccinator_uber_resist;
  310. extern ConVar tf_teleporter_fov_time;
  311. extern ConVar tf_teleporter_fov_start;
  312. #ifdef GAME_DLL
  313. extern ConVar mp_holiday_nogifts;
  314. extern ConVar tf_debug_damage;
  315. extern ConVar tf_damage_range;
  316. extern ConVar tf_damage_disablespread;
  317. extern ConVar tf_populator_damage_multiplier;
  318. extern ConVar tf_mm_trusted;
  319. extern ConVar tf_weapon_criticals;
  320. extern ConVar tf_weapon_criticals_melee;
  321. extern ConVar mp_idledealmethod;
  322. extern ConVar mp_idlemaxtime;
  323. extern ConVar tf_mm_strict;
  324. extern ConVar mp_autoteambalance;
  325. // STAGING_SPY
  326. ConVar tf_feign_death_activate_damage_scale( "tf_feign_death_activate_damage_scale", "0.25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  327. ConVar tf_feign_death_damage_scale( "tf_feign_death_damage_scale", "0.35", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  328. ConVar tf_stealth_damage_reduction( "tf_stealth_damage_reduction", "0.8", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  329. // training
  330. ConVar training_class( "training_class", "3", FCVAR_REPLICATED, "Class to use in training." );
  331. ConVar training_can_build_sentry( "training_can_build_sentry", "1", FCVAR_REPLICATED, "Player can build sentry as engineer." );
  332. ConVar training_can_build_dispenser( "training_can_build_dispenser", "1", FCVAR_REPLICATED, "Player can build dispenser as engineer." );
  333. ConVar training_can_build_tele_entrance( "training_can_build_tele_entrance", "1", FCVAR_REPLICATED, "Player can build teleporter entrance as engineer." );
  334. ConVar training_can_build_tele_exit( "training_can_build_tele_exit", "1", FCVAR_REPLICATED, "Player can build teleporter exit as engineer." );
  335. ConVar training_can_destroy_buildings( "training_can_destroy_buildings", "1", FCVAR_REPLICATED, "Player can destroy buildings as engineer." );
  336. ConVar training_can_pickup_sentry( "training_can_pickup_sentry", "1", FCVAR_REPLICATED, "Player can pickup sentry gun as engineer." );
  337. ConVar training_can_pickup_dispenser( "training_can_pickup_dispenser", "1", FCVAR_REPLICATED, "Player can pickup dispenser as engineer." );
  338. ConVar training_can_pickup_tele_entrance( "training_can_pickup_tele_entrance", "1", FCVAR_REPLICATED, "Player can pickup teleporter entrance as engineer." );
  339. ConVar training_can_pickup_tele_exit( "training_can_pickup_tele_exit", "1", FCVAR_REPLICATED, "Player can pickup teleporter entrance as engineer." );
  340. ConVar training_can_select_weapon_primary ( "training_can_select_weapon_primary", "1", FCVAR_REPLICATED, "In training player select primary weapon." );
  341. ConVar training_can_select_weapon_secondary ( "training_can_select_weapon_secondary", "1", FCVAR_REPLICATED, "In training player select secondary weapon." );
  342. ConVar training_can_select_weapon_melee ( "training_can_select_weapon_melee", "1", FCVAR_REPLICATED, "In training player select melee weapon." );
  343. ConVar training_can_select_weapon_building ( "training_can_select_weapon_building", "1", FCVAR_REPLICATED, "In training player select building tool." );
  344. ConVar training_can_select_weapon_pda ( "training_can_select_weapon_pda", "1", FCVAR_REPLICATED, "In training player select pda." );
  345. ConVar training_can_select_weapon_item1 ( "training_can_select_weapon_item1", "1", FCVAR_REPLICATED, "In training player select item 1." );
  346. ConVar training_can_select_weapon_item2 ( "training_can_select_weapon_item2", "1", FCVAR_REPLICATED, "In training player select item 2." );
  347. ConVar tf_birthday_ball_chance( "tf_birthday_ball_chance", "100", FCVAR_REPLICATED, "Percent chance of a birthday beach ball spawning at each round start" );
  348. ConVar tf_halloween_boss_spawn_interval( "tf_halloween_boss_spawn_interval", "480", FCVAR_CHEAT, "Average interval between boss spawns, in seconds" );
  349. ConVar tf_halloween_boss_spawn_interval_variation( "tf_halloween_boss_spawn_interval_variation", "60", FCVAR_CHEAT, "Variation of spawn interval +/-" );
  350. ConVar tf_halloween_eyeball_boss_spawn_interval( "tf_halloween_eyeball_boss_spawn_interval", "180", FCVAR_CHEAT, "Average interval between boss spawns, in seconds" );
  351. ConVar tf_halloween_eyeball_boss_spawn_interval_variation( "tf_halloween_eyeball_boss_spawn_interval_variation", "30", FCVAR_CHEAT, "Variation of spawn interval +/-" );
  352. ConVar tf_merasmus_spawn_interval( "tf_merasmus_spawn_interval", "180", FCVAR_CHEAT, "Average interval between boss spawns, in seconds" );
  353. ConVar tf_merasmus_spawn_interval_variation( "tf_merasmus_spawn_interval_variation", "30", FCVAR_CHEAT, "Variation of spawn interval +/-" );
  354. ConVar tf_halloween_zombie_mob_enabled( "tf_halloween_zombie_mob_enabled", "0", FCVAR_CHEAT, "If set to 1, spawn zombie mobs on non-Halloween Valve maps" );
  355. ConVar tf_halloween_zombie_mob_spawn_interval( "tf_halloween_zombie_mob_spawn_interval", "180", FCVAR_CHEAT, "Average interval between zombie mob spawns, in seconds" );
  356. ConVar tf_halloween_zombie_mob_spawn_count( "tf_halloween_zombie_mob_spawn_count", "20", FCVAR_CHEAT, "How many zombies to spawn" );
  357. ConVar tf_halloween_allow_truce_during_boss_event( "tf_halloween_allow_truce_during_boss_event", "0", FCVAR_NOTIFY, "Determines if RED and BLU can damage each other while fighting Monoculus or Merasmus on non-Valve maps." );
  358. ConVar tf_player_spell_drop_on_death_rate( "tf_player_spell_drop_on_death_rate", "0", FCVAR_REPLICATED );
  359. ConVar tf_player_drop_bonus_ducks( "tf_player_drop_bonus_ducks", "-1", FCVAR_REPLICATED, "-1 Default (Holiday-based)\n0 - Force off\n1 - Force on" );
  360. ConVar tf_player_name_change_time( "tf_player_name_change_time", "60", FCVAR_NOTIFY, "Seconds between name changes." );
  361. ConVar tf_weapon_criticals_distance_falloff( "tf_weapon_criticals_distance_falloff", "0", FCVAR_CHEAT, "Critical weapon damage will take distance into account." );
  362. ConVar tf_weapon_minicrits_distance_falloff( "tf_weapon_minicrits_distance_falloff", "0", FCVAR_CHEAT, "Mini-crit weapon damage will take distance into account." );
  363. ConVar mp_spectators_restricted( "mp_spectators_restricted", "0", FCVAR_NONE, "Prevent players on game teams from joining team spectator if it would unbalance the teams." );
  364. ConVar tf_test_special_ducks( "tf_test_special_ducks", "1", FCVAR_DEVELOPMENTONLY );
  365. ConVar tf_mm_abandoned_players_per_team_max( "tf_mm_abandoned_players_per_team_max", "1", FCVAR_DEVELOPMENTONLY );
  366. #endif // GAME_DLL
  367. ConVar tf_mm_next_map_vote_time( "tf_mm_next_map_vote_time", "30", FCVAR_REPLICATED );
  368. #ifdef STAGING_ONLY
  369. #ifdef GAME_DLL
  370. void cc_tf_truce_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
  371. {
  372. if ( TFGameRules() )
  373. {
  374. TFGameRules()->RecalculateTruce();
  375. }
  376. }
  377. #endif // GAME_DLL
  378. ConVar tf_truce( "tf_truce", "0", FCVAR_REPLICATED, "Force a team truce on or off.", true, 0, true, 1
  379. #ifdef GAME_DLL
  380. , cc_tf_truce_changed
  381. #endif // GAME_DLL
  382. );
  383. #endif // STAGING_ONLY
  384. static float g_fEternaweenAutodisableTime = 0.0f;
  385. ConVar tf_spec_xray( "tf_spec_xray", "1", FCVAR_NOTIFY | FCVAR_REPLICATED, "Allows spectators to see player glows. 1 = same team, 2 = both teams" );
  386. ConVar tf_spawn_glows_duration( "tf_spawn_glows_duration", "10", FCVAR_NOTIFY | FCVAR_REPLICATED, "How long should teammates glow after respawning\n" );
  387. #ifdef GAME_DLL
  388. void cc_tf_forced_holiday_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
  389. {
  390. // Tell the listeners to recalculate the holiday
  391. IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_holidays" );
  392. if ( event )
  393. {
  394. gameeventmanager->FireEvent( event );
  395. }
  396. }
  397. #endif // GAME_DLL
  398. ConVar tf_forced_holiday( "tf_forced_holiday", "0", FCVAR_REPLICATED, "Forced holiday, \n Birthday = 1\n Halloween = 2\n" // Christmas = 3\n Valentines = 4\n MeetThePyro = 5\n FullMoon=6
  399. #ifdef GAME_DLL
  400. , cc_tf_forced_holiday_changed
  401. #endif // GAME_DLL
  402. );
  403. ConVar tf_item_based_forced_holiday( "tf_item_based_forced_holiday", "0", FCVAR_REPLICATED, "" // like a clone of tf_forced_holiday, but controlled by client consumable item use
  404. #ifdef GAME_DLL
  405. , cc_tf_forced_holiday_changed
  406. #endif // GAME_DLL
  407. );
  408. ConVar tf_force_holidays_off( "tf_force_holidays_off", "0", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, ""
  409. #ifdef GAME_DLL
  410. , cc_tf_forced_holiday_changed
  411. #endif // GAME_DLL
  412. );
  413. ConVar tf_birthday( "tf_birthday", "0", FCVAR_NOTIFY | FCVAR_REPLICATED );
  414. ConVar tf_spells_enabled( "tf_spells_enabled", "0", FCVAR_NOTIFY | FCVAR_REPLICATED, "Enable to Allow Halloween Spells to be dropped and used by players" );
  415. ConVar tf_caplinear( "tf_caplinear", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "If set to 1, teams must capture control points linearly." );
  416. ConVar tf_stalematechangeclasstime( "tf_stalematechangeclasstime", "20", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Amount of time that players are allowed to change class in stalemates." );
  417. ConVar mp_tournament_redteamname( "mp_tournament_redteamname", "RED", FCVAR_REPLICATED | FCVAR_HIDDEN );
  418. ConVar mp_tournament_blueteamname( "mp_tournament_blueteamname", "BLU", FCVAR_REPLICATED | FCVAR_HIDDEN );
  419. //tagES revisit this later
  420. ConVar tf_attack_defend_map( "tf_attack_defend_map", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  421. #ifdef GAME_DLL
  422. void cc_tf_stopwatch_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
  423. {
  424. IGameEvent *event = gameeventmanager->CreateEvent( "stop_watch_changed" );
  425. if ( event )
  426. {
  427. gameeventmanager->FireEvent( event );
  428. }
  429. }
  430. #endif // GAME_DLL
  431. ConVar mp_tournament_stopwatch( "mp_tournament_stopwatch", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Use Stopwatch mode while using Tournament mode (mp_tournament)"
  432. #ifdef GAME_DLL
  433. , cc_tf_stopwatch_changed
  434. #endif
  435. );
  436. ConVar mp_tournament_readymode( "mp_tournament_readymode", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Enable per-player ready status for tournament mode." );
  437. ConVar mp_tournament_readymode_min( "mp_tournament_readymode_min", "2", FCVAR_REPLICATED | FCVAR_NOTIFY, "Minimum number of players required on the server before players can toggle ready status." );
  438. ConVar mp_tournament_readymode_team_size( "mp_tournament_readymode_team_size", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Minimum number of players required to be ready per-team before the game can begin." );
  439. ConVar mp_tournament_readymode_countdown( "mp_tournament_readymode_countdown", "10", FCVAR_REPLICATED | FCVAR_NOTIFY, "The number of seconds before a match begins when both teams are ready." );
  440. ConVar mp_windifference( "mp_windifference", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Score difference between teams before server changes maps", true, 0, false, 0 );
  441. ConVar mp_windifference_min( "mp_windifference_min", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Minimum score needed for mp_windifference to be applied", true, 0, false, 0 );
  442. ConVar tf_tournament_classlimit_scout( "tf_tournament_classlimit_scout", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Scouts.\n" );
  443. ConVar tf_tournament_classlimit_sniper( "tf_tournament_classlimit_sniper", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Snipers.\n" );
  444. ConVar tf_tournament_classlimit_soldier( "tf_tournament_classlimit_soldier", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Soldiers.\n" );
  445. ConVar tf_tournament_classlimit_demoman( "tf_tournament_classlimit_demoman", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Demomenz.\n" );
  446. ConVar tf_tournament_classlimit_medic( "tf_tournament_classlimit_medic", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Medics.\n" );
  447. ConVar tf_tournament_classlimit_heavy( "tf_tournament_classlimit_heavy", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Heavies.\n" );
  448. ConVar tf_tournament_classlimit_pyro( "tf_tournament_classlimit_pyro", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Pyros.\n" );
  449. ConVar tf_tournament_classlimit_spy( "tf_tournament_classlimit_spy", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Spies.\n" );
  450. ConVar tf_tournament_classlimit_engineer( "tf_tournament_classlimit_engineer", "-1", FCVAR_REPLICATED, "Tournament mode per-team class limit for Engineers.\n" );
  451. ConVar tf_tournament_classchange_allowed( "tf_tournament_classchange_allowed", "1", FCVAR_REPLICATED, "Allow players to change class while the game is active?.\n" );
  452. ConVar tf_tournament_classchange_ready_allowed( "tf_tournament_classchange_ready_allowed", "1", FCVAR_REPLICATED, "Allow players to change class after they are READY?.\n" );
  453. ConVar tf_classlimit( "tf_classlimit", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Limit on how many players can be any class (i.e. tf_class_limit 2 would limit 2 players per class).\n", true, 0.f, false, 0.f );
  454. ConVar tf_player_movement_restart_freeze( "tf_player_movement_restart_freeze", "1", FCVAR_REPLICATED, "When set, prevent player movement during round restart" );
  455. ConVar tf_autobalance_query_lifetime( "tf_autobalance_query_lifetime", "30", FCVAR_REPLICATED );
  456. ConVar tf_autobalance_xp_bonus( "tf_autobalance_xp_bonus", "150", FCVAR_REPLICATED );
  457. //tagES
  458. #ifdef STAGING_ONLY
  459. ConVar tf_test_match_summary( "tf_test_match_summary", "0", FCVAR_REPLICATED );
  460. #ifdef GAME_DLL
  461. void cc_tf_fake_mm_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
  462. {
  463. ConVarRef var( pConVar );
  464. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( (EMatchGroup)var.GetInt() );
  465. if ( pMatchDesc )
  466. {
  467. pMatchDesc->InitServerSettingsForMatch( NULL );
  468. }
  469. }
  470. #endif // GAME_DLL
  471. ConVar tf_fake_mm_group( "tf_fake_mm_group", "-1", FCVAR_REPLICATED, "Fake what kind of MM group is being played"
  472. #ifdef GAME_DLL
  473. , cc_tf_fake_mm_changed
  474. #endif // GAME_DLL
  475. );
  476. #endif // STAGING_ONLY
  477. #ifdef GAME_DLL
  478. static const float g_flStrangeEventBatchProcessInterval = 30.0f;
  479. ConVar mp_humans_must_join_team("mp_humans_must_join_team", "any", FCVAR_REPLICATED, "Restricts human players to a single team {any, blue, red, spectator}" );
  480. void cc_tf_medieval_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
  481. {
  482. ConVarRef var( pConVar );
  483. bool bOldValue = flOldValue > 0;
  484. if ( var.IsValid() && ( bOldValue != var.GetBool() ) )
  485. {
  486. Msg( "Medieval mode changes take effect after the next map change.\n" );
  487. }
  488. }
  489. #endif
  490. ConVar tf_medieval( "tf_medieval", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Enable Medieval Mode.\n", true, 0, true, 1
  491. #ifdef GAME_DLL
  492. , cc_tf_medieval_changed
  493. #endif
  494. );
  495. ConVar tf_medieval_autorp( "tf_medieval_autorp", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Enable Medieval Mode auto-roleplaying.\n", true, 0, true, 1 );
  496. ConVar tf_sticky_radius_ramp_time( "tf_sticky_radius_ramp_time", "2.0", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT | FCVAR_REPLICATED, "Amount of time to get full radius after arming" );
  497. ConVar tf_sticky_airdet_radius( "tf_sticky_airdet_radius", "0.85", FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT | FCVAR_REPLICATED, "Radius Scale if detonated in the air" );
  498. #ifdef STAGING_ONLY
  499. ConVar tf_killstreak_alwayson( "tf_killstreak_alwayson", "0", FCVAR_REPLICATED, "enable to have killstreak effects for all players, bots included");
  500. #ifdef GAME_DLL
  501. // Bounty Mode
  502. ConVar tf_bountymode_currency_starting( "tf_bountymode_currency_starting", "1000", FCVAR_ARCHIVE, "How much new players start with when playing Bounty Mode.\n" );
  503. ConVar tf_bountymode_currency_limit( "tf_bountymode_currency_limit", "0", FCVAR_ARCHIVE, "The maximum amount a player can hold in Bounty Mode.\n" );
  504. ConVar tf_bountymode_currency_penalty_ondeath( "tf_bountymode_currency_penalty_ondeath", "0", FCVAR_ARCHIVE, "The percentage of unspent money players lose when they die in Bounty Mode.\n" );
  505. ConVar tf_bountymode_upgrades_wipeondeath( "tf_bountymode_upgrades_wipeondeath", "0", FCVAR_ARCHIVE, "If set to true, wipe player/item upgrades on death.\n" );
  506. void cc_bountymode_changed( IConVar *pConVar, const char *pOldString, float flOldValue )
  507. {
  508. ConVarRef var( pConVar );
  509. if ( var.IsValid() && TFGameRules() )
  510. {
  511. TFGameRules()->SetBountyMode( var.GetBool() );
  512. if ( TFGameRules()->IsMannVsMachineMode() )
  513. return;
  514. mp_restartgame_immediate.SetValue( 1 );
  515. if ( !g_pPopulationManager )
  516. {
  517. CreateEntityByName( "info_populator" );
  518. }
  519. int nCurrency = tf_bountymode_currency_starting.GetInt();
  520. if ( nCurrency > 0 )
  521. {
  522. // Give everyone starting money
  523. for ( int i = 0; i <= MAX_PLAYERS; ++i )
  524. {
  525. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  526. if ( !pPlayer )
  527. continue;
  528. pPlayer->SetCurrency( nCurrency );
  529. }
  530. }
  531. g_MannVsMachineUpgrades.LoadUpgradesFile();
  532. IGameEvent *pEvent = gameeventmanager->CreateEvent( "upgrades_file_changed" );
  533. if ( pEvent )
  534. {
  535. pEvent->SetString( "path", "" ); // Have the client load the default
  536. gameeventmanager->FireEvent( pEvent );
  537. }
  538. }
  539. }
  540. #endif // GAME_DLL
  541. ConVar tf_bountymode( "tf_bountymode", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_ARCHIVE, "Allow upgrades and award currency for mission objectives and killing enemy players.\n", true, 0, true, 1
  542. #ifdef GAME_DLL
  543. , cc_bountymode_changed
  544. #endif // GAME_DLL
  545. );
  546. #endif // STAGING_ONLY
  547. #ifndef GAME_DLL
  548. extern ConVar cl_burninggibs;
  549. extern ConVar english;
  550. ConVar tf_particles_disable_weather( "tf_particles_disable_weather", "0", FCVAR_ARCHIVE, "Disable particles related to weather effects." );
  551. #endif
  552. // arena mode cvars
  553. ConVar tf_arena_force_class( "tf_arena_force_class", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Forces players to play a random class each time they spawn." );
  554. ConVar tf_arena_change_limit( "tf_arena_change_limit", "1", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Number of times players can change their class when mp_force_random_class is being used." );
  555. ConVar tf_arena_override_cap_enable_time( "tf_arena_override_cap_enable_time", "-1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Overrides the time (in seconds) it takes for the capture point to become enable, -1 uses the level designer specified time." );
  556. ConVar tf_arena_override_team_size( "tf_arena_override_team_size", "0", FCVAR_REPLICATED, "Overrides the maximum team size in arena mode. Set to zero to keep the default behavior of 1/3 maxplayers.");
  557. ConVar tf_arena_first_blood( "tf_arena_first_blood", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Rewards the first player to get a kill each round." );
  558. extern ConVar tf_arena_preround_time;
  559. extern ConVar tf_arena_max_streak;
  560. #if defined( _DEBUG ) || defined( STAGING_ONLY )
  561. extern ConVar mp_developer;
  562. #endif // _DEBUG || STAGING_ONLY
  563. //=============================================================================
  564. // HPE_BEGIN
  565. // [msmith] Used for the client to tell the server that we're watching a movie or not.
  566. // Also contains the name of a movie if it's an in game video.
  567. //=============================================================================
  568. // Training mode cvars
  569. ConVar tf_training_client_message( "tf_training_client_message", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "A simple way for the training client to communicate with the server." );
  570. //=============================================================================
  571. // HPE_END
  572. //=============================================================================
  573. #define TF_ARENA_MODE_FIRST_BLOOD_CRIT_TIME 5.0f
  574. #define TF_ARENA_MODE_FAST_FIRST_BLOOD_TIME 20.0f
  575. #define TF_ARENA_MODE_SLOW_FIRST_BLOOD_TIME 50.0f
  576. #ifdef TF_RAID_MODE
  577. // Raid mode
  578. ConVar tf_gamemode_raid( "tf_gamemode_raid", "0", FCVAR_REPLICATED | FCVAR_NOTIFY ); // client needs access to this for IsRaidMode()
  579. ConVar tf_raid_enforce_unique_classes( "tf_raid_enforce_unique_classes", "0", FCVAR_REPLICATED | FCVAR_NOTIFY );
  580. ConVar tf_raid_respawn_time( "tf_raid_respawn_time", "5", FCVAR_REPLICATED | FCVAR_NOTIFY /*| FCVAR_CHEAT*/, "How long it takes for a Raider to respawn with his team after death." );
  581. ConVar tf_raid_allow_all_classes( "tf_raid_allow_all_classes", "1", FCVAR_REPLICATED | FCVAR_NOTIFY );
  582. ConVar tf_gamemode_boss_battle( "tf_gamemode_boss_battle", "0", FCVAR_REPLICATED | FCVAR_NOTIFY );
  583. #ifdef GAME_DLL
  584. ConVar tf_raid_allow_overtime( "tf_raid_allow_overtime", "0"/*, FCVAR_CHEAT*/ );
  585. #endif // GAME_DLL
  586. #endif // TF_RAID_MODE
  587. enum { kMVM_MaxConnectedPlayers = 10, };
  588. ConVar tf_mvm_min_players_to_start( "tf_mvm_min_players_to_start", "3", FCVAR_REPLICATED | FCVAR_NOTIFY, "Minimum number of players connected to start a countdown timer" );
  589. ConVar tf_mvm_respec_enabled( "tf_mvm_respec_enabled", "1", FCVAR_CHEAT | FCVAR_REPLICATED, "Allow players to refund credits spent on player and item upgrades." );
  590. ConVar tf_mvm_respec_limit( "tf_mvm_respec_limit", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "The total number of respecs a player can earn. Default: 0 (no limit).", true, 0.f, true, 100.f );
  591. ConVar tf_mvm_respec_credit_goal( "tf_mvm_respec_credit_goal", "2000", FCVAR_CHEAT | FCVAR_REPLICATED, "When tf_mvm_respec_limit is non-zero, the total amount of money the team must collect to earn a respec credit." );
  592. #ifdef STAGING_ONLY
  593. ConVar tf_mvm_buybacks_method( "tf_mvm_buybacks_method", "1", FCVAR_REPLICATED | FCVAR_HIDDEN, "When set to 0, use the traditional, currency-based system. When set to 1, use finite, charge-based system.", true, 0.0, true, 1.0 );
  594. #else
  595. ConVar tf_mvm_buybacks_method( "tf_mvm_buybacks_method", "0", FCVAR_REPLICATED | FCVAR_HIDDEN, "When set to 0, use the traditional, currency-based system. When set to 1, use finite, charge-based system.", true, 0.0, true, 1.0 );
  596. #endif
  597. ConVar tf_mvm_buybacks_per_wave( "tf_mvm_buybacks_per_wave", "3", FCVAR_REPLICATED | FCVAR_HIDDEN, "The fixed number of buybacks players can use per-wave." );
  598. #ifdef GAME_DLL
  599. enum { kMVM_CurrencyPackMinSize = 1, };
  600. #endif // GAME_DLL
  601. extern ConVar mp_tournament;
  602. extern ConVar mp_tournament_post_match_period;
  603. extern ConVar tf_flag_return_on_touch;
  604. extern ConVar tf_flag_return_time_credit_factor;
  605. ConVar tf_grapplinghook_enable( "tf_grapplinghook_enable", "0", FCVAR_REPLICATED );
  606. #ifdef GAME_DLL
  607. CUtlString s_strNextMvMPopFile;
  608. CON_COMMAND_F( tf_mvm_popfile, "Change to a target popfile for MvM", FCVAR_GAMEDLL )
  609. {
  610. // Listenserver host or rcon access only!
  611. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  612. return;
  613. if ( args.ArgC() <= 1 )
  614. {
  615. if ( TFGameRules() && g_pPopulationManager )
  616. {
  617. const char *pszFile = g_pPopulationManager->GetPopulationFilename();
  618. if ( pszFile && pszFile[0] )
  619. {
  620. Msg( "Current popfile is: %s\n", pszFile );
  621. return;
  622. }
  623. }
  624. Msg( "Missing Popfile name\n" );
  625. return;
  626. }
  627. const char *pszShortName = args.Arg(1);
  628. if ( !TFGameRules() || !g_pPopulationManager )
  629. {
  630. Warning( "Cannot set population file before map load.\n" );
  631. return;
  632. }
  633. // Make sure we have a file system
  634. if ( !g_pFullFileSystem )
  635. {
  636. Msg( "No File System to find Popfile to load\n" );
  637. return;
  638. }
  639. // Form full path
  640. CUtlString fullPath;
  641. if ( g_pPopulationManager->FindPopulationFileByShortName( pszShortName, fullPath ) )
  642. {
  643. g_pPopulationManager->SetPopulationFilename( fullPath );
  644. g_pPopulationManager->ResetMap();
  645. return;
  646. }
  647. // Give them a message to make it clear what file we were looking for
  648. Warning( "Could not find a population file matching: %s.\n", pszShortName );
  649. }
  650. #ifdef STAGING_ONLY
  651. // Never ship this
  652. CON_COMMAND_F( tf_competitive_mode_force_victory, "For testing.", FCVAR_GAMEDLL )
  653. {
  654. // Listenserver host or rcon access only!
  655. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  656. return;
  657. CTFPlayer *pPlayer = ToTFPlayer( UTIL_GetCommandClient() );
  658. if ( !pPlayer )
  659. return;
  660. CSteamID steamIDForPlayer;
  661. if ( !pPlayer->GetSteamID( &steamIDForPlayer ) )
  662. return;
  663. // TODO: Rewrite this based on the shipping version of comp/casual
  664. }
  665. #endif // STAGING_ONLY
  666. #endif
  667. static bool BIsCvarIndicatingHolidayIsActive( int iCvarValue, /*EHoliday*/ int eHoliday )
  668. {
  669. if ( iCvarValue == 0 )
  670. return false;
  671. // Unfortunately Holidays are not a proper bitfield
  672. switch ( eHoliday )
  673. {
  674. case kHoliday_TFBirthday: return iCvarValue == kHoliday_TFBirthday;
  675. case kHoliday_Halloween: return iCvarValue == kHoliday_Halloween || iCvarValue == kHoliday_HalloweenOrFullMoon || iCvarValue == kHoliday_HalloweenOrFullMoonOrValentines;
  676. case kHoliday_Christmas: return iCvarValue == kHoliday_Christmas;
  677. case kHoliday_Valentines: return iCvarValue == kHoliday_Valentines || iCvarValue == kHoliday_HalloweenOrFullMoonOrValentines;
  678. case kHoliday_MeetThePyro: return iCvarValue == kHoliday_MeetThePyro;
  679. case kHoliday_FullMoon: return iCvarValue == kHoliday_FullMoon || iCvarValue == kHoliday_HalloweenOrFullMoon || iCvarValue == kHoliday_HalloweenOrFullMoonOrValentines;
  680. case kHoliday_HalloweenOrFullMoon: return iCvarValue == kHoliday_Halloween || iCvarValue == kHoliday_FullMoon || iCvarValue == kHoliday_HalloweenOrFullMoon || iCvarValue == kHoliday_HalloweenOrFullMoonOrValentines;
  681. case kHoliday_HalloweenOrFullMoonOrValentines: return iCvarValue == kHoliday_Halloween || iCvarValue == kHoliday_FullMoon || iCvarValue == kHoliday_Valentines || iCvarValue == kHoliday_HalloweenOrFullMoon || iCvarValue == kHoliday_HalloweenOrFullMoonOrValentines;
  682. case kHoliday_AprilFools: return iCvarValue == kHoliday_AprilFools;
  683. case kHoliday_EOTL: return iCvarValue == kHoliday_EOTL;
  684. case kHoliday_CommunityUpdate: return iCvarValue == kHoliday_CommunityUpdate;
  685. }
  686. return false;
  687. }
  688. // Fetch holiday setting taking into account convars, etc, but NOT
  689. // taking into consideration the current game rules, map, etc.
  690. //
  691. // This version can be used outside of gameplay, ie., for matchmaking
  692. bool TF_IsHolidayActive( /*EHoliday*/ int eHoliday )
  693. {
  694. if ( IsX360() || tf_force_holidays_off.GetBool() )
  695. return false;
  696. if ( BIsCvarIndicatingHolidayIsActive( tf_forced_holiday.GetInt(), eHoliday ) )
  697. return true;
  698. if ( BIsCvarIndicatingHolidayIsActive( tf_item_based_forced_holiday.GetInt(), eHoliday ) )
  699. return true;
  700. if ( ( eHoliday == kHoliday_TFBirthday ) && tf_birthday.GetBool() )
  701. return true;
  702. if ( TFGameRules() )
  703. {
  704. if ( eHoliday == kHoliday_HalloweenOrFullMoon )
  705. {
  706. if ( TFGameRules()->IsHolidayMap( kHoliday_Halloween ) )
  707. return true;
  708. if ( TFGameRules()->IsHolidayMap( kHoliday_FullMoon ) )
  709. return true;
  710. }
  711. if ( TFGameRules()->IsHolidayMap( eHoliday ) )
  712. {
  713. return true;
  714. }
  715. }
  716. return UTIL_IsHolidayActive( eHoliday );
  717. }
  718. #ifdef TF_CREEP_MODE
  719. ConVar tf_gamemode_creep_wave( "tf_gamemode_creep_wave", "0", FCVAR_REPLICATED | FCVAR_NOTIFY );
  720. ConVar tf_creep_wave_player_respawn_time( "tf_creep_wave_player_respawn_time", "10", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_CHEAT, "How long it takes for a player to respawn with his team after death." );
  721. #endif
  722. #ifdef GAME_DLL
  723. // TF overrides the default value of this convar
  724. #ifdef _DEBUG
  725. #define WAITING_FOR_PLAYERS_FLAGS 0
  726. #else
  727. #define WAITING_FOR_PLAYERS_FLAGS FCVAR_DEVELOPMENTONLY
  728. #endif
  729. ConVar hide_server( "hide_server", "0", FCVAR_GAMEDLL, "Whether the server should be hidden from the master server" );
  730. ConVar mp_waitingforplayers_time( "mp_waitingforplayers_time", (IsX360()?"15":"30"), FCVAR_GAMEDLL | WAITING_FOR_PLAYERS_FLAGS, "WaitingForPlayers time length in seconds" );
  731. ConVar tf_gamemode_arena ( "tf_gamemode_arena", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  732. ConVar tf_gamemode_cp ( "tf_gamemode_cp", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  733. ConVar tf_gamemode_ctf ( "tf_gamemode_ctf", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  734. ConVar tf_gamemode_sd ( "tf_gamemode_sd", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  735. ConVar tf_gamemode_rd ( "tf_gamemode_rd", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  736. ConVar tf_gamemode_pd ( "tf_gamemode_pd", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  737. ConVar tf_gamemode_tc ( "tf_gamemode_tc", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  738. ConVar tf_beta_content ( "tf_beta_content", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  739. ConVar tf_gamemode_payload ( "tf_gamemode_payload", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  740. ConVar tf_gamemode_mvm ( "tf_gamemode_mvm", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  741. ConVar tf_gamemode_passtime ( "tf_gamemode_passtime", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  742. ConVar tf_gamemode_misc ( "tf_gamemode_misc", "0", FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  743. ConVar tf_bot_count( "tf_bot_count", "0", FCVAR_NOTIFY | FCVAR_DEVELOPMENTONLY );
  744. #ifdef _DEBUG
  745. ConVar tf_debug_ammo_and_health( "tf_debug_ammo_and_health", "0", FCVAR_CHEAT );
  746. #endif // _DEBUG
  747. static Vector s_BotSpawnPosition;
  748. ConVar tf_gravetalk( "tf_gravetalk", "1", FCVAR_NOTIFY, "Allows living players to hear dead players using text/voice chat.", true, 0, true, 1 );
  749. ConVar tf_ctf_bonus_time ( "tf_ctf_bonus_time", "10", FCVAR_NOTIFY, "Length of team crit time for CTF capture." );
  750. #ifdef _DEBUG
  751. ConVar mp_scrambleteams_debug( "mp_scrambleteams_debug", "0", FCVAR_NONE, "Debug spew." );
  752. #endif // _DEBUG
  753. #ifdef STAGING_ONLY
  754. ConVar mp_tournament_readymode_bots_allowed( "mp_tournament_readymode_bots_allowed", "0", FCVAR_ARCHIVE, "Allow bot data to go through the system for debugging." );
  755. #endif // STAGING_ONLY
  756. extern ConVar tf_mm_servermode;
  757. extern ConVar tf_flag_caps_per_round;
  758. void cc_competitive_mode( IConVar *pConVar, const char *pOldString, float flOldValue )
  759. {
  760. IGameEvent *event = gameeventmanager->CreateEvent( "competitive_state_changed" );
  761. if ( event )
  762. {
  763. // Server-side here. Client-side down below in the RecvProxy
  764. gameeventmanager->FireEvent( event, true );
  765. }
  766. }
  767. ConVar tf_competitive_preround_duration( "tf_competitive_preround_duration", "3", FCVAR_REPLICATED, "How long we stay in pre-round when in competitive games." );
  768. ConVar tf_competitive_preround_countdown_duration( "tf_competitive_preround_countdown_duration", "10.5", FCVAR_HIDDEN, "How long we stay in countdown when in competitive games." );
  769. ConVar tf_competitive_abandon_method( "tf_competitive_abandon_method", "0", FCVAR_HIDDEN );
  770. ConVar tf_competitive_required_late_join_timeout( "tf_competitive_required_late_join_timeout", "120", FCVAR_DEVELOPMENTONLY,
  771. "How long to wait for late joiners in matches requiring full player counts before canceling the match" );
  772. ConVar tf_competitive_required_late_join_confirm_timeout( "tf_competitive_required_late_join_confirm_timeout", "30", FCVAR_DEVELOPMENTONLY,
  773. "How long to wait for the GC to confirm we're in the late join pool before canceling the match" );
  774. #endif // GAME_DLL
  775. #ifdef GAME_DLL
  776. void cc_powerup_mode( IConVar *pConVar, const char *pOldString, float flOldValue )
  777. {
  778. ConVarRef var( pConVar );
  779. if ( var.IsValid() )
  780. {
  781. if ( !TFGameRules() )
  782. return;
  783. if ( var.GetBool() )
  784. {
  785. if ( TFGameRules()->IsMannVsMachineMode() )
  786. return;
  787. }
  788. TFGameRules()->SetPowerupMode( var.GetBool() );
  789. TFGameRules()->State_Transition( GR_STATE_PREROUND );
  790. tf_flag_caps_per_round.SetValue( var.GetBool() ? 7 : 3 ); // Hack
  791. }
  792. }
  793. ConVar tf_powerup_mode( "tf_powerup_mode", "0", FCVAR_NOTIFY, "Enable/disable powerup mode. Not compatible with Mann Vs Machine mode", cc_powerup_mode );
  794. ConVar tf_powerup_mode_imbalance_delta( "tf_powerup_mode_imbalance_delta", "24", FCVAR_CHEAT, "Powerup kill score lead one team must have before imbalance measures are initiated" );
  795. ConVar tf_skillrating_update_interval( "tf_skillrating_update_interval", "180", FCVAR_ARCHIVE, "How often to update the GC and OGS." );
  796. extern ConVar mp_teams_unbalance_limit;
  797. static bool g_bRandomMap = false;
  798. void cc_RandomMap( const CCommand& args )
  799. {
  800. CTFGameRules *pRules = TFGameRules();
  801. if ( pRules )
  802. {
  803. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  804. return;
  805. g_bRandomMap = true;
  806. }
  807. else
  808. {
  809. // There's no game rules object yet, so let's load the map cycle and pick a map.
  810. char mapcfile[MAX_PATH];
  811. CMultiplayRules::DetermineMapCycleFilename( mapcfile, sizeof(mapcfile), true );
  812. if ( !mapcfile[0] )
  813. {
  814. Msg( "No mapcyclefile specified. Cannot pick a random map.\n" );
  815. return;
  816. }
  817. CUtlVector<char*> mapList;
  818. // No gamerules entity yet, since we don't need the fixups to find a map just use the base version
  819. CMultiplayRules::RawLoadMapCycleFileIntoVector ( mapcfile, mapList );
  820. if ( !mapList.Count() )
  821. {
  822. Msg( "Map cycle file \"%s\" contains no valid maps or cannot be read. Cannot pick a random map.\n", mapcfile );
  823. return;
  824. }
  825. int iMapIndex = RandomInt( 0, mapList.Count() - 1 );
  826. Msg ( "randommap: selecting map %i out of %i\n", iMapIndex + 1, mapList.Count() );
  827. engine->ServerCommand( UTIL_VarArgs( "map %s\n", mapList[iMapIndex] ) );
  828. CMultiplayRules::FreeMapCycleFileVector( mapList );
  829. }
  830. }
  831. // Simple class for tracking previous gamemode across level transitions
  832. // Allows clean-up of UI/state when going between things like MvM and PvP
  833. class CTFGameModeHistory : public CAutoGameSystem
  834. {
  835. public:
  836. virtual bool Init()
  837. {
  838. m_nPrevState = 0;
  839. return true;
  840. }
  841. void SetPrevState( int nState ) { m_nPrevState = nState; }
  842. int GetPrevState( void ) { return m_nPrevState; }
  843. private:
  844. int m_nPrevState;
  845. } g_TFGameModeHistory;
  846. static ConCommand randommap( "randommap", cc_RandomMap, "Changelevel to a random map in the mapcycle file" );
  847. #endif // GAME_DLL
  848. #ifdef GAME_DLL
  849. static bool PlayerHasDuckStreaks( CTFPlayer *pPlayer )
  850. {
  851. CEconItemView *pActionItem = pPlayer->GetEquippedItemForLoadoutSlot( LOADOUT_POSITION_ACTION );
  852. if ( !pActionItem )
  853. return false;
  854. // Duck Badge Cooldown is based on badge level. Noisemaker is more like an easter egg
  855. static CSchemaAttributeDefHandle pAttr_DuckStreaks( "duckstreaks active" );
  856. uint32 iDuckStreaksActive = 0;
  857. // Don't care about the level, just if the attribute is found
  858. return FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pActionItem, pAttr_DuckStreaks, &iDuckStreaksActive ) && iDuckStreaksActive > 0;
  859. }
  860. void ValidateCapturesPerRound( IConVar *pConVar, const char *oldValue, float flOldValue )
  861. {
  862. ConVarRef var( pConVar );
  863. if ( var.GetInt() <= 0 )
  864. {
  865. // reset the flag captures being played in the current round
  866. int nTeamCount = TFTeamMgr()->GetTeamCount();
  867. for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam )
  868. {
  869. CTFTeam *pTeam = GetGlobalTFTeam( iTeam );
  870. if ( !pTeam )
  871. continue;
  872. pTeam->SetFlagCaptures( 0 );
  873. }
  874. }
  875. }
  876. static void Coaching_Start( CTFPlayer *pCoach, CTFPlayer *pStudent )
  877. {
  878. pCoach->SetIsCoaching( true );
  879. pCoach->ForceChangeTeam( TEAM_SPECTATOR );
  880. pCoach->SetObserverTarget( pStudent );
  881. pCoach->StartObserverMode( OBS_MODE_CHASE );
  882. pCoach->SetStudent( pStudent );
  883. pStudent->SetCoach( pCoach );
  884. }
  885. static void Coaching_Stop( CTFPlayer *pCoach )
  886. {
  887. CTFPlayer *pStudent = pCoach->GetStudent();
  888. if ( pStudent )
  889. {
  890. pStudent->SetCoach( NULL );
  891. }
  892. pCoach->SetIsCoaching( false );
  893. pCoach->SetStudent( NULL );
  894. pCoach->ForceChangeTeam( TEAM_SPECTATOR );
  895. }
  896. #endif
  897. ConVar tf_flag_caps_per_round( "tf_flag_caps_per_round", "3", FCVAR_REPLICATED, "Number of captures per round on CTF and PASS Time maps. Set to 0 to disable.", true, 0, false, 0
  898. #ifdef GAME_DLL
  899. , ValidateCapturesPerRound
  900. #endif
  901. );
  902. /**
  903. * Player hull & eye position for standing, ducking, etc. This version has a taller
  904. * player height, but goldsrc-compatible collision bounds.
  905. */
  906. static CViewVectors g_TFViewVectors(
  907. Vector( 0, 0, 72 ), //VEC_VIEW (m_vView) eye position
  908. Vector(-24, -24, 0 ), //VEC_HULL_MIN (m_vHullMin) hull min
  909. Vector( 24, 24, 82 ), //VEC_HULL_MAX (m_vHullMax) hull max
  910. Vector(-24, -24, 0 ), //VEC_DUCK_HULL_MIN (m_vDuckHullMin) duck hull min
  911. Vector( 24, 24, 62 ), //VEC_DUCK_HULL_MAX (m_vDuckHullMax) duck hull max
  912. Vector( 0, 0, 45 ), //VEC_DUCK_VIEW (m_vDuckView) duck view
  913. Vector( -10, -10, -10 ), //VEC_OBS_HULL_MIN (m_vObsHullMin) observer hull min
  914. Vector( 10, 10, 10 ), //VEC_OBS_HULL_MAX (m_vObsHullMax) observer hull max
  915. Vector( 0, 0, 14 ) //VEC_DEAD_VIEWHEIGHT (m_vDeadViewHeight) dead view height
  916. );
  917. Vector g_TFClassViewVectors[11] =
  918. {
  919. Vector( 0, 0, 72 ), // TF_CLASS_UNDEFINED
  920. Vector( 0, 0, 65 ), // TF_CLASS_SCOUT, // TF_FIRST_NORMAL_CLASS
  921. Vector( 0, 0, 75 ), // TF_CLASS_SNIPER,
  922. Vector( 0, 0, 68 ), // TF_CLASS_SOLDIER,
  923. Vector( 0, 0, 68 ), // TF_CLASS_DEMOMAN,
  924. Vector( 0, 0, 75 ), // TF_CLASS_MEDIC,
  925. Vector( 0, 0, 75 ), // TF_CLASS_HEAVYWEAPONS,
  926. Vector( 0, 0, 68 ), // TF_CLASS_PYRO,
  927. Vector( 0, 0, 75 ), // TF_CLASS_SPY,
  928. Vector( 0, 0, 68 ), // TF_CLASS_ENGINEER,
  929. Vector( 0, 0, 65 ), // TF_CLASS_CIVILIAN, // TF_LAST_NORMAL_CLASS
  930. };
  931. const CViewVectors *CTFGameRules::GetViewVectors() const
  932. {
  933. return &g_TFViewVectors;
  934. }
  935. REGISTER_GAMERULES_CLASS( CTFGameRules );
  936. #ifdef CLIENT_DLL
  937. void RecvProxy_MatchSummary( const CRecvProxyData *pData, void *pStruct, void *pOut )
  938. {
  939. bool bMatchSummary = ( pData->m_Value.m_Int > 0 );
  940. if ( bMatchSummary && !(*(bool*)(pOut)))
  941. {
  942. C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer();
  943. if ( pLocalPlayer )
  944. {
  945. pLocalPlayer->TurnOffTauntCam();
  946. pLocalPlayer->TurnOffTauntCam_Finish();;
  947. }
  948. IGameEvent *event = gameeventmanager->CreateEvent( "show_match_summary" );
  949. if ( event )
  950. {
  951. gameeventmanager->FireEventClientSide( event );
  952. }
  953. }
  954. *(bool*)(pOut) = bMatchSummary;
  955. }
  956. void RecvProxy_CompetitiveMode( const CRecvProxyData *pData, void *pStruct, void *pOut )
  957. {
  958. *(bool*)(pOut) = ( pData->m_Value.m_Int > 0 );
  959. IGameEvent *event = gameeventmanager->CreateEvent( "competitive_state_changed" );
  960. if ( event )
  961. {
  962. // Client-side once it's actually happened
  963. gameeventmanager->FireEventClientSide( event );
  964. }
  965. }
  966. void RecvProxy_PlayerVotedForMap( const CRecvProxyData *pData, void *pStruct, void *pOut )
  967. {
  968. if ( *(int*)(pOut) != pData->m_Value.m_Int )
  969. {
  970. *(int*)(pOut) = pData->m_Value.m_Int;
  971. IGameEvent *event = gameeventmanager->CreateEvent( "player_next_map_vote_change" );
  972. if ( event )
  973. {
  974. event->SetInt( "map_index", pData->m_Value.m_Int );
  975. event->SetInt( "vote", pData->m_Value.m_Int );
  976. // Client-side once it's actually happened
  977. gameeventmanager->FireEventClientSide( event );
  978. }
  979. }
  980. }
  981. void RecvProxy_NewMapVoteStateChanged( const CRecvProxyData *pData, void *pStruct, void *pOut )
  982. {
  983. bool bChange = *(int*)(pOut) != pData->m_Value.m_Int;
  984. *(int*)(pOut) = pData->m_Value.m_Int;
  985. if ( bChange )
  986. {
  987. IGameEvent *event = gameeventmanager->CreateEvent( "vote_maps_changed" );
  988. if ( event )
  989. {
  990. // Client-side once it's actually happened
  991. gameeventmanager->FireEventClientSide( event );
  992. }
  993. }
  994. }
  995. #endif
  996. BEGIN_NETWORK_TABLE_NOBASE( CTFGameRules, DT_TFGameRules )
  997. #ifdef CLIENT_DLL
  998. RecvPropInt( RECVINFO( m_nGameType ) ),
  999. RecvPropInt( RECVINFO( m_nStopWatchState ) ),
  1000. RecvPropString( RECVINFO( m_pszTeamGoalStringRed ) ),
  1001. RecvPropString( RECVINFO( m_pszTeamGoalStringBlue ) ),
  1002. RecvPropTime( RECVINFO( m_flCapturePointEnableTime ) ),
  1003. //=============================================================================
  1004. // HPE_BEGIN:
  1005. // [msmith] Training status and HUD Type.
  1006. //=============================================================================
  1007. RecvPropInt( RECVINFO( m_nHudType ) ),
  1008. RecvPropBool( RECVINFO( m_bIsInTraining ) ),
  1009. RecvPropBool( RECVINFO( m_bAllowTrainingAchievements ) ),
  1010. RecvPropBool( RECVINFO( m_bIsWaitingForTrainingContinue ) ),
  1011. //=============================================================================
  1012. // HPE_END
  1013. //=============================================================================
  1014. RecvPropBool( RECVINFO( m_bIsTrainingHUDVisible ) ),
  1015. RecvPropBool( RECVINFO( m_bIsInItemTestingMode ) ),
  1016. RecvPropEHandle( RECVINFO( m_hBonusLogic ) ),
  1017. RecvPropBool( RECVINFO( m_bPlayingKoth ) ),
  1018. RecvPropBool( RECVINFO( m_bPlayingMedieval ) ),
  1019. RecvPropBool( RECVINFO( m_bPlayingHybrid_CTF_CP ) ),
  1020. RecvPropBool( RECVINFO( m_bPlayingSpecialDeliveryMode ) ),
  1021. RecvPropBool( RECVINFO( m_bPlayingRobotDestructionMode ) ),
  1022. RecvPropEHandle( RECVINFO( m_hRedKothTimer ) ),
  1023. RecvPropEHandle( RECVINFO( m_hBlueKothTimer ) ),
  1024. RecvPropInt( RECVINFO( m_nMapHolidayType ) ),
  1025. RecvPropEHandle( RECVINFO( m_itHandle ) ),
  1026. RecvPropBool( RECVINFO( m_bPlayingMannVsMachine ) ),
  1027. RecvPropEHandle( RECVINFO( m_hBirthdayPlayer ) ),
  1028. RecvPropInt( RECVINFO( m_nBossHealth ) ),
  1029. RecvPropInt( RECVINFO( m_nMaxBossHealth ) ),
  1030. RecvPropInt( RECVINFO( m_fBossNormalizedTravelDistance ) ),
  1031. RecvPropBool( RECVINFO( m_bMannVsMachineAlarmStatus ) ),
  1032. RecvPropBool( RECVINFO( m_bHaveMinPlayersToEnableReady ) ),
  1033. RecvPropBool( RECVINFO( m_bBountyModeEnabled ) ),
  1034. RecvPropInt( RECVINFO( m_nHalloweenEffect ) ),
  1035. RecvPropFloat( RECVINFO( m_fHalloweenEffectStartTime ) ),
  1036. RecvPropFloat( RECVINFO( m_fHalloweenEffectDuration ) ),
  1037. RecvPropInt( RECVINFO( m_halloweenScenario ) ),
  1038. RecvPropBool( RECVINFO( m_bHelltowerPlayersInHell ) ),
  1039. RecvPropBool( RECVINFO( m_bIsUsingSpells ) ),
  1040. RecvPropBool( RECVINFO( m_bCompetitiveMode ), 0, RecvProxy_CompetitiveMode ),
  1041. RecvPropInt( RECVINFO( m_nMatchGroupType ) ),
  1042. RecvPropBool( RECVINFO( m_bMatchEnded ) ),
  1043. RecvPropBool( RECVINFO( m_bPowerupMode ) ),
  1044. RecvPropString( RECVINFO( m_pszCustomUpgradesFile ) ),
  1045. RecvPropBool( RECVINFO( m_bTruceActive ) ),
  1046. RecvPropBool( RECVINFO( m_bShowMatchSummary ), 0, RecvProxy_MatchSummary ),
  1047. RecvPropBool( RECVINFO_NAME( m_bShowMatchSummary, "m_bShowCompetitiveMatchSummary" ), 0, RecvProxy_MatchSummary ), // Renamed
  1048. RecvPropBool( RECVINFO( m_bTeamsSwitched ) ),
  1049. RecvPropBool( RECVINFO( m_bMapHasMatchSummaryStage ) ),
  1050. RecvPropBool( RECVINFO( m_bPlayersAreOnMatchSummaryStage ) ),
  1051. RecvPropBool( RECVINFO( m_bStopWatchWinner ) ),
  1052. RecvPropArray3( RECVINFO_ARRAY(m_ePlayerWantsRematch), RecvPropInt( RECVINFO(m_ePlayerWantsRematch[0]), 0, RecvProxy_PlayerVotedForMap ) ),
  1053. RecvPropInt( RECVINFO( m_eRematchState ) ),
  1054. RecvPropArray3( RECVINFO_ARRAY(m_nNextMapVoteOptions), RecvPropInt( RECVINFO(m_nNextMapVoteOptions[0]), 0, RecvProxy_NewMapVoteStateChanged ) ),
  1055. #else
  1056. SendPropInt( SENDINFO( m_nGameType ), 4, SPROP_UNSIGNED ),
  1057. SendPropInt( SENDINFO( m_nStopWatchState ), 3, SPROP_UNSIGNED ),
  1058. SendPropString( SENDINFO( m_pszTeamGoalStringRed ) ),
  1059. SendPropString( SENDINFO( m_pszTeamGoalStringBlue ) ),
  1060. SendPropTime( SENDINFO( m_flCapturePointEnableTime ) ),
  1061. //=============================================================================
  1062. // HPE_BEGIN:
  1063. // [msmith] Training status and hud type.
  1064. //=============================================================================
  1065. SendPropInt( SENDINFO( m_nHudType ), 3, SPROP_UNSIGNED ),
  1066. SendPropBool( SENDINFO( m_bIsInTraining ) ),
  1067. SendPropBool( SENDINFO( m_bAllowTrainingAchievements ) ),
  1068. SendPropBool( SENDINFO( m_bIsWaitingForTrainingContinue ) ),
  1069. //=============================================================================
  1070. // HPE_END
  1071. //=============================================================================
  1072. SendPropBool( SENDINFO( m_bIsTrainingHUDVisible ) ),
  1073. SendPropBool( SENDINFO( m_bIsInItemTestingMode ) ),
  1074. SendPropEHandle( SENDINFO( m_hBonusLogic ) ),
  1075. SendPropBool( SENDINFO( m_bPlayingKoth ) ),
  1076. SendPropBool( SENDINFO( m_bPlayingMedieval ) ),
  1077. SendPropBool( SENDINFO( m_bPlayingHybrid_CTF_CP ) ),
  1078. SendPropBool( SENDINFO( m_bPlayingSpecialDeliveryMode ) ),
  1079. SendPropBool( SENDINFO( m_bPlayingRobotDestructionMode ) ),
  1080. SendPropEHandle( SENDINFO( m_hRedKothTimer ) ),
  1081. SendPropEHandle( SENDINFO( m_hBlueKothTimer ) ),
  1082. SendPropInt( SENDINFO( m_nMapHolidayType ), 3, SPROP_UNSIGNED ),
  1083. SendPropEHandle( SENDINFO( m_itHandle ) ),
  1084. SendPropBool( SENDINFO( m_bPlayingMannVsMachine ) ),
  1085. SendPropEHandle( SENDINFO( m_hBirthdayPlayer ) ),
  1086. SendPropInt( SENDINFO( m_nBossHealth ) ),
  1087. SendPropInt( SENDINFO( m_nMaxBossHealth ) ),
  1088. SendPropInt( SENDINFO( m_fBossNormalizedTravelDistance ) ),
  1089. SendPropBool( SENDINFO( m_bMannVsMachineAlarmStatus ) ),
  1090. SendPropBool( SENDINFO( m_bHaveMinPlayersToEnableReady ) ),
  1091. SendPropBool( SENDINFO( m_bBountyModeEnabled ) ),
  1092. SendPropInt( SENDINFO( m_nHalloweenEffect ) ),
  1093. SendPropFloat( SENDINFO( m_fHalloweenEffectStartTime ) ),
  1094. SendPropFloat( SENDINFO( m_fHalloweenEffectDuration ) ),
  1095. SendPropInt( SENDINFO( m_halloweenScenario ) ),
  1096. SendPropBool( SENDINFO( m_bHelltowerPlayersInHell ) ),
  1097. SendPropBool( SENDINFO( m_bIsUsingSpells ) ),
  1098. SendPropBool( SENDINFO( m_bCompetitiveMode ) ),
  1099. SendPropBool( SENDINFO( m_bPowerupMode ) ),
  1100. SendPropInt( SENDINFO( m_nMatchGroupType ) ),
  1101. SendPropBool( SENDINFO( m_bMatchEnded ) ),
  1102. SendPropString( SENDINFO( m_pszCustomUpgradesFile ) ),
  1103. SendPropBool( SENDINFO( m_bTruceActive ) ),
  1104. SendPropBool( SENDINFO( m_bShowMatchSummary ) ),
  1105. SendPropBool( SENDINFO( m_bTeamsSwitched ) ),
  1106. SendPropBool( SENDINFO( m_bMapHasMatchSummaryStage ) ),
  1107. SendPropBool( SENDINFO( m_bPlayersAreOnMatchSummaryStage ) ),
  1108. SendPropBool( SENDINFO( m_bStopWatchWinner ) ),
  1109. SendPropArray3( SENDINFO_ARRAY3(m_ePlayerWantsRematch), SendPropInt( SENDINFO_ARRAY(m_ePlayerWantsRematch), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  1110. SendPropInt( SENDINFO( m_eRematchState ) ),
  1111. SendPropArray3( SENDINFO_ARRAY3(m_nNextMapVoteOptions), SendPropInt( SENDINFO_ARRAY(m_nNextMapVoteOptions), -1, SPROP_UNSIGNED | SPROP_VARINT ) ),
  1112. #endif
  1113. END_NETWORK_TABLE()
  1114. LINK_ENTITY_TO_CLASS( tf_gamerules, CTFGameRulesProxy );
  1115. IMPLEMENT_NETWORKCLASS_ALIASED( TFGameRulesProxy, DT_TFGameRulesProxy )
  1116. #ifdef CLIENT_DLL
  1117. void RecvProxy_TFGameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID )
  1118. {
  1119. CTFGameRules *pRules = TFGameRules();
  1120. Assert( pRules );
  1121. *pOut = pRules;
  1122. }
  1123. BEGIN_RECV_TABLE( CTFGameRulesProxy, DT_TFGameRulesProxy )
  1124. RecvPropDataTable( "tf_gamerules_data", 0, 0, &REFERENCE_RECV_TABLE( DT_TFGameRules ), RecvProxy_TFGameRules )
  1125. END_RECV_TABLE()
  1126. #else
  1127. void *SendProxy_TFGameRules( const SendProp *pProp, const void *pStructBase, const void *pData, CSendProxyRecipients *pRecipients, int objectID )
  1128. {
  1129. CTFGameRules *pRules = TFGameRules();
  1130. Assert( pRules );
  1131. pRecipients->SetAllRecipients();
  1132. return pRules;
  1133. }
  1134. BEGIN_SEND_TABLE( CTFGameRulesProxy, DT_TFGameRulesProxy )
  1135. SendPropDataTable( "tf_gamerules_data", 0, &REFERENCE_SEND_TABLE( DT_TFGameRules ), SendProxy_TFGameRules )
  1136. END_SEND_TABLE()
  1137. #endif
  1138. #ifdef GAME_DLL
  1139. BEGIN_DATADESC( CTFGameRulesProxy )
  1140. //=============================================================================
  1141. // HPE_BEGIN:
  1142. // [msmith] Training HUD type
  1143. //=============================================================================
  1144. DEFINE_KEYFIELD( m_nHudType, FIELD_INTEGER, "hud_type" ),
  1145. //=============================================================================
  1146. // HPE_END
  1147. //=============================================================================
  1148. DEFINE_KEYFIELD( m_bOvertimeAllowedForCTF, FIELD_BOOLEAN, "ctf_overtime" ),
  1149. // Inputs.
  1150. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetRedTeamRespawnWaveTime", InputSetRedTeamRespawnWaveTime ),
  1151. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBlueTeamRespawnWaveTime", InputSetBlueTeamRespawnWaveTime ),
  1152. DEFINE_INPUTFUNC( FIELD_FLOAT, "AddRedTeamRespawnWaveTime", InputAddRedTeamRespawnWaveTime ),
  1153. DEFINE_INPUTFUNC( FIELD_FLOAT, "AddBlueTeamRespawnWaveTime", InputAddBlueTeamRespawnWaveTime ),
  1154. DEFINE_INPUTFUNC( FIELD_STRING, "SetRedTeamGoalString", InputSetRedTeamGoalString ),
  1155. DEFINE_INPUTFUNC( FIELD_STRING, "SetBlueTeamGoalString", InputSetBlueTeamGoalString ),
  1156. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRedTeamRole", InputSetRedTeamRole ),
  1157. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetBlueTeamRole", InputSetBlueTeamRole ),
  1158. DEFINE_INPUTFUNC( FIELD_STRING, "SetRequiredObserverTarget", InputSetRequiredObserverTarget ),
  1159. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddRedTeamScore", InputAddRedTeamScore ),
  1160. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddBlueTeamScore", InputAddBlueTeamScore ),
  1161. DEFINE_INPUTFUNC( FIELD_VOID, "SetRedKothClockActive", InputSetRedKothClockActive ),
  1162. DEFINE_INPUTFUNC( FIELD_VOID, "SetBlueKothClockActive", InputSetBlueKothClockActive ),
  1163. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetCTFCaptureBonusTime", InputSetCTFCaptureBonusTime ),
  1164. DEFINE_INPUTFUNC( FIELD_STRING, "PlayVORed", InputPlayVORed ),
  1165. DEFINE_INPUTFUNC( FIELD_STRING, "PlayVOBlue", InputPlayVOBlue ),
  1166. DEFINE_INPUTFUNC( FIELD_STRING, "PlayVO", InputPlayVO ),
  1167. DEFINE_INPUTFUNC( FIELD_STRING, "HandleMapEvent", InputHandleMapEvent ),
  1168. DEFINE_INPUTFUNC( FIELD_STRING, "SetCustomUpgradesFile", InputSetCustomUpgradesFile ),
  1169. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRoundRespawnFreezeEnabled", InputSetRoundRespawnFreezeEnabled ),
  1170. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetMapForcedTruceDuringBossFight", InputSetMapForcedTruceDuringBossFight ),
  1171. DEFINE_OUTPUT( m_OnWonByTeam1, "OnWonByTeam1" ),
  1172. DEFINE_OUTPUT( m_OnWonByTeam2, "OnWonByTeam2" ),
  1173. DEFINE_OUTPUT( m_Team1PlayersChanged, "Team1PlayersChanged" ),
  1174. DEFINE_OUTPUT( m_Team2PlayersChanged, "Team2PlayersChanged" ),
  1175. DEFINE_OUTPUT( m_OnPowerupImbalanceTeam1, "OnPowerupImbalanceTeam1" ),
  1176. DEFINE_OUTPUT( m_OnPowerupImbalanceTeam2, "OnPowerupImbalanceTeam2" ),
  1177. DEFINE_OUTPUT( m_OnPowerupImbalanceMeasuresOver, "OnPowerupImbalanceMeasuresOver" ),
  1178. DEFINE_OUTPUT( m_OnStateEnterRoundRunning, "OnStateEnterRoundRunning" ),
  1179. DEFINE_OUTPUT( m_OnStateEnterBetweenRounds, "OnStateEnterBetweenRounds" ),
  1180. DEFINE_OUTPUT( m_OnStateEnterPreRound, "OnStateEnterPreRound" ),
  1181. DEFINE_OUTPUT( m_OnStateExitPreRound, "OnStateExitPreRound" ),
  1182. DEFINE_OUTPUT( m_OnMatchSummaryStart, "OnMatchSummaryStart" ),
  1183. DEFINE_OUTPUT( m_OnTruceStart, "OnTruceStart" ),
  1184. DEFINE_OUTPUT( m_OnTruceEnd, "OnTruceEnd" ),
  1185. END_DATADESC()
  1186. //-----------------------------------------------------------------------------
  1187. // Purpose:
  1188. //-----------------------------------------------------------------------------
  1189. CTFGameRulesProxy::CTFGameRulesProxy()
  1190. {
  1191. m_nHudType = TF_HUDTYPE_UNDEFINED;
  1192. m_bOvertimeAllowedForCTF = true;
  1193. }
  1194. //-----------------------------------------------------------------------------
  1195. // Purpose:
  1196. //-----------------------------------------------------------------------------
  1197. void CTFGameRulesProxy::InputAddRedTeamScore( inputdata_t &inputdata )
  1198. {
  1199. TFTeamMgr()->AddTeamScore( TF_TEAM_RED, inputdata.value.Int() );
  1200. }
  1201. //-----------------------------------------------------------------------------
  1202. // Purpose:
  1203. //-----------------------------------------------------------------------------
  1204. void CTFGameRulesProxy::InputAddBlueTeamScore( inputdata_t &inputdata )
  1205. {
  1206. TFTeamMgr()->AddTeamScore( TF_TEAM_BLUE, inputdata.value.Int() );
  1207. }
  1208. //-----------------------------------------------------------------------------
  1209. // Purpose:
  1210. //-----------------------------------------------------------------------------
  1211. void CTFGameRulesProxy::InputSetRedKothClockActive( inputdata_t &inputdata )
  1212. {
  1213. if ( TFGameRules() )
  1214. {
  1215. variant_t sVariant;
  1216. CTeamRoundTimer *pTimer = TFGameRules()->GetKothTeamTimer( TF_TEAM_BLUE );
  1217. if ( pTimer )
  1218. {
  1219. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  1220. }
  1221. pTimer = TFGameRules()->GetKothTeamTimer( TF_TEAM_RED );
  1222. if ( pTimer )
  1223. {
  1224. pTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
  1225. }
  1226. }
  1227. }
  1228. //-----------------------------------------------------------------------------
  1229. // Purpose:
  1230. //-----------------------------------------------------------------------------
  1231. void CTFGameRulesProxy::InputSetCTFCaptureBonusTime( inputdata_t &inputdata )
  1232. {
  1233. if ( TFGameRules() )
  1234. {
  1235. TFGameRules()->SetCTFCaptureBonusTime( inputdata.value.Float() );
  1236. }
  1237. }
  1238. //-----------------------------------------------------------------------------
  1239. // Purpose:
  1240. //-----------------------------------------------------------------------------
  1241. void CTFGameRulesProxy::InputSetBlueKothClockActive( inputdata_t &inputdata )
  1242. {
  1243. if ( TFGameRules() )
  1244. {
  1245. variant_t sVariant;
  1246. CTeamRoundTimer *pTimer = TFGameRules()->GetKothTeamTimer( TF_TEAM_BLUE );
  1247. if ( pTimer )
  1248. {
  1249. pTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
  1250. }
  1251. pTimer = TFGameRules()->GetKothTeamTimer( TF_TEAM_RED );
  1252. if ( pTimer )
  1253. {
  1254. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  1255. }
  1256. }
  1257. }
  1258. //-----------------------------------------------------------------------------
  1259. // Purpose:
  1260. //-----------------------------------------------------------------------------
  1261. void CTFGameRulesProxy::InputSetRequiredObserverTarget( inputdata_t &inputdata )
  1262. {
  1263. const char *pszEntName = inputdata.value.String();
  1264. CBaseEntity *pEnt = NULL;
  1265. if ( pszEntName && pszEntName[0] )
  1266. {
  1267. pEnt = gEntList.FindEntityByName( NULL, pszEntName );
  1268. }
  1269. TFGameRules()->SetRequiredObserverTarget( pEnt );
  1270. }
  1271. //-----------------------------------------------------------------------------
  1272. // Purpose:
  1273. //-----------------------------------------------------------------------------
  1274. void CTFGameRulesProxy::InputSetRedTeamRespawnWaveTime( inputdata_t &inputdata )
  1275. {
  1276. TFGameRules()->SetTeamRespawnWaveTime( TF_TEAM_RED, inputdata.value.Float() );
  1277. }
  1278. //-----------------------------------------------------------------------------
  1279. // Purpose:
  1280. //-----------------------------------------------------------------------------
  1281. void CTFGameRulesProxy::InputSetBlueTeamRespawnWaveTime( inputdata_t &inputdata )
  1282. {
  1283. TFGameRules()->SetTeamRespawnWaveTime( TF_TEAM_BLUE, inputdata.value.Float() );
  1284. }
  1285. //-----------------------------------------------------------------------------
  1286. // Purpose:
  1287. //-----------------------------------------------------------------------------
  1288. void CTFGameRulesProxy::InputAddRedTeamRespawnWaveTime( inputdata_t &inputdata )
  1289. {
  1290. TFGameRules()->AddTeamRespawnWaveTime( TF_TEAM_RED, inputdata.value.Float() );
  1291. }
  1292. //-----------------------------------------------------------------------------
  1293. // Purpose:
  1294. //-----------------------------------------------------------------------------
  1295. void CTFGameRulesProxy::InputAddBlueTeamRespawnWaveTime( inputdata_t &inputdata )
  1296. {
  1297. TFGameRules()->AddTeamRespawnWaveTime( TF_TEAM_BLUE, inputdata.value.Float() );
  1298. }
  1299. //-----------------------------------------------------------------------------
  1300. // Purpose:
  1301. //-----------------------------------------------------------------------------
  1302. void CTFGameRulesProxy::InputSetRedTeamGoalString( inputdata_t &inputdata )
  1303. {
  1304. TFGameRules()->SetTeamGoalString( TF_TEAM_RED, inputdata.value.String() );
  1305. }
  1306. //-----------------------------------------------------------------------------
  1307. // Purpose:
  1308. //-----------------------------------------------------------------------------
  1309. void CTFGameRulesProxy::InputSetBlueTeamGoalString( inputdata_t &inputdata )
  1310. {
  1311. TFGameRules()->SetTeamGoalString( TF_TEAM_BLUE, inputdata.value.String() );
  1312. }
  1313. //-----------------------------------------------------------------------------
  1314. // Purpose:
  1315. //-----------------------------------------------------------------------------
  1316. void CTFGameRulesProxy::InputSetRedTeamRole( inputdata_t &inputdata )
  1317. {
  1318. CTFTeam *pTeam = TFTeamMgr()->GetTeam( TF_TEAM_RED );
  1319. if ( pTeam )
  1320. {
  1321. pTeam->SetRole( inputdata.value.Int() );
  1322. }
  1323. }
  1324. //-----------------------------------------------------------------------------
  1325. // Purpose:
  1326. //-----------------------------------------------------------------------------
  1327. void CTFGameRulesProxy::InputSetBlueTeamRole( inputdata_t &inputdata )
  1328. {
  1329. CTFTeam *pTeam = TFTeamMgr()->GetTeam( TF_TEAM_BLUE );
  1330. if ( pTeam )
  1331. {
  1332. pTeam->SetRole( inputdata.value.Int() );
  1333. }
  1334. }
  1335. //-----------------------------------------------------------------------------
  1336. // Purpose: Pass in a VO sound entry to play for RED
  1337. //-----------------------------------------------------------------------------
  1338. void CTFGameRulesProxy::InputPlayVORed( inputdata_t &inputdata )
  1339. {
  1340. const char *szSoundName = inputdata.value.String();
  1341. if ( szSoundName )
  1342. {
  1343. TFGameRules()->BroadcastSound( TF_TEAM_RED, szSoundName );
  1344. }
  1345. }
  1346. //-----------------------------------------------------------------------------
  1347. // Purpose: Pass in a VO sound entry to play for BLUE
  1348. //-----------------------------------------------------------------------------
  1349. void CTFGameRulesProxy::InputPlayVOBlue( inputdata_t &inputdata )
  1350. {
  1351. const char *szSoundName = inputdata.value.String();
  1352. if ( szSoundName )
  1353. {
  1354. TFGameRules()->BroadcastSound( TF_TEAM_BLUE, szSoundName );
  1355. }
  1356. }
  1357. //-----------------------------------------------------------------------------
  1358. // Purpose: Pass in a VO sound entry to play
  1359. //-----------------------------------------------------------------------------
  1360. void CTFGameRulesProxy::InputPlayVO( inputdata_t &inputdata )
  1361. {
  1362. const char *szSoundName = inputdata.value.String();
  1363. if ( szSoundName )
  1364. {
  1365. TFGameRules()->BroadcastSound( 255, szSoundName );
  1366. }
  1367. }
  1368. //-----------------------------------------------------------------------------
  1369. // Purpose:
  1370. //-----------------------------------------------------------------------------
  1371. void CTFGameRulesProxy::InputHandleMapEvent( inputdata_t &inputdata )
  1372. {
  1373. if ( TFGameRules() )
  1374. {
  1375. TFGameRules()->HandleMapEvent( inputdata );
  1376. }
  1377. }
  1378. //-----------------------------------------------------------------------------
  1379. // Purpose:
  1380. //-----------------------------------------------------------------------------
  1381. void CTFGameRulesProxy::InputSetCustomUpgradesFile( inputdata_t &inputdata )
  1382. {
  1383. if ( TFGameRules() )
  1384. {
  1385. TFGameRules()->SetCustomUpgradesFile( inputdata );
  1386. }
  1387. }
  1388. //-----------------------------------------------------------------------------
  1389. // Purpose:
  1390. //-----------------------------------------------------------------------------
  1391. void CTFGameRulesProxy::InputSetRoundRespawnFreezeEnabled( inputdata_t &inputdata )
  1392. {
  1393. tf_player_movement_restart_freeze.SetValue( inputdata.value.Bool() );
  1394. }
  1395. //-----------------------------------------------------------------------------
  1396. // Purpose:
  1397. //-----------------------------------------------------------------------------
  1398. void CTFGameRulesProxy::InputSetMapForcedTruceDuringBossFight( inputdata_t &inputdata )
  1399. {
  1400. if ( TFGameRules() )
  1401. {
  1402. TFGameRules()->SetMapForcedTruceDuringBossFight( inputdata.value.Bool() );
  1403. }
  1404. }
  1405. //-----------------------------------------------------------------------------
  1406. // Purpose:
  1407. //-----------------------------------------------------------------------------
  1408. void CTFGameRulesProxy::Activate()
  1409. {
  1410. TFGameRules()->Activate();
  1411. //=============================================================================
  1412. // HPE_BEGIN:
  1413. // [msmith] We added a HUDTYPE so that the objective and the HUD are independent.
  1414. // This lets us have a non training HUD on a training map. And a training
  1415. // HUD on another type of map.
  1416. //=============================================================================
  1417. if ( m_nHudType != TF_HUDTYPE_UNDEFINED )
  1418. {
  1419. TFGameRules()->SetHUDType( m_nHudType );
  1420. }
  1421. //=============================================================================
  1422. // HPE_END
  1423. //=============================================================================
  1424. TFGameRules()->SetOvertimeAllowedForCTF( m_bOvertimeAllowedForCTF );
  1425. ListenForGameEvent( "teamplay_round_win" );
  1426. BaseClass::Activate();
  1427. }
  1428. //-----------------------------------------------------------------------------
  1429. // Purpose:
  1430. //-----------------------------------------------------------------------------
  1431. void CTFGameRulesProxy::TeamPlayerCountChanged( CTFTeam *pTeam )
  1432. {
  1433. if ( pTeam == TFTeamMgr()->GetTeam( TF_TEAM_RED ) )
  1434. {
  1435. m_Team1PlayersChanged.Set( pTeam->GetNumPlayers(), this, this );
  1436. }
  1437. else if ( pTeam == TFTeamMgr()->GetTeam( TF_TEAM_BLUE ) )
  1438. {
  1439. m_Team2PlayersChanged.Set( pTeam->GetNumPlayers(), this, this );
  1440. }
  1441. // Tell the clients
  1442. IGameEvent *event = gameeventmanager->CreateEvent( "teams_changed" );
  1443. if ( event )
  1444. {
  1445. gameeventmanager->FireEvent( event );
  1446. }
  1447. }
  1448. //-----------------------------------------------------------------------------
  1449. // Purpose:
  1450. //-----------------------------------------------------------------------------
  1451. void CTFGameRulesProxy::PowerupTeamImbalance( int nTeam )
  1452. {
  1453. switch ( nTeam )
  1454. {
  1455. case TF_TEAM_RED:
  1456. m_OnPowerupImbalanceTeam1.FireOutput( this, this );
  1457. break;
  1458. case TF_TEAM_BLUE:
  1459. m_OnPowerupImbalanceTeam2.FireOutput( this, this );
  1460. break;
  1461. default:
  1462. m_OnPowerupImbalanceMeasuresOver.FireOutput( this, this );
  1463. break;
  1464. }
  1465. }
  1466. //-----------------------------------------------------------------------------
  1467. // Purpose:
  1468. //-----------------------------------------------------------------------------
  1469. void CTFGameRulesProxy::StateEnterRoundRunning( void )
  1470. {
  1471. m_OnStateEnterRoundRunning.FireOutput( this, this );
  1472. }
  1473. //-----------------------------------------------------------------------------
  1474. // Purpose:
  1475. //-----------------------------------------------------------------------------
  1476. void CTFGameRulesProxy::StateEnterBetweenRounds( void )
  1477. {
  1478. m_OnStateEnterBetweenRounds.FireOutput( this, this );
  1479. }
  1480. //-----------------------------------------------------------------------------
  1481. // Purpose:
  1482. //-----------------------------------------------------------------------------
  1483. void CTFGameRulesProxy::StateEnterPreRound( void )
  1484. {
  1485. m_OnStateEnterPreRound.FireOutput( this, this );
  1486. }
  1487. //-----------------------------------------------------------------------------
  1488. // Purpose:
  1489. //-----------------------------------------------------------------------------
  1490. void CTFGameRulesProxy::StateExitPreRound( void )
  1491. {
  1492. m_OnStateExitPreRound.FireOutput( this, this );
  1493. }
  1494. //-----------------------------------------------------------------------------
  1495. // Purpose:
  1496. //-----------------------------------------------------------------------------
  1497. void CTFGameRulesProxy::MatchSummaryStart( void )
  1498. {
  1499. m_OnMatchSummaryStart.FireOutput( this, this );
  1500. }
  1501. //-----------------------------------------------------------------------------
  1502. // Purpose:
  1503. //-----------------------------------------------------------------------------
  1504. void CTFGameRulesProxy::TruceStart( void )
  1505. {
  1506. m_OnTruceStart.FireOutput( this, this );
  1507. }
  1508. //-----------------------------------------------------------------------------
  1509. // Purpose:
  1510. //-----------------------------------------------------------------------------
  1511. void CTFGameRulesProxy::TruceEnd( void )
  1512. {
  1513. m_OnTruceEnd.FireOutput( this, this );
  1514. }
  1515. #endif
  1516. //-----------------------------------------------------------------------------
  1517. // Purpose:
  1518. //-----------------------------------------------------------------------------
  1519. void CTFGameRulesProxy::FireGameEvent( IGameEvent *event )
  1520. {
  1521. #ifdef GAME_DLL
  1522. const char *pszEventName = event->GetName();
  1523. if ( FStrEq( pszEventName, "teamplay_round_win" ) )
  1524. {
  1525. int iWinningTeam = event->GetInt( "team" );
  1526. switch( iWinningTeam )
  1527. {
  1528. case TF_TEAM_RED:
  1529. m_OnWonByTeam1.FireOutput( this, this );
  1530. break;
  1531. case TF_TEAM_BLUE:
  1532. m_OnWonByTeam2.FireOutput( this, this );
  1533. break;
  1534. default:
  1535. break;
  1536. }
  1537. }
  1538. #endif
  1539. }
  1540. // (We clamp ammo ourselves elsewhere).
  1541. ConVar ammo_max( "ammo_max", "5000", FCVAR_REPLICATED );
  1542. #ifndef CLIENT_DLL
  1543. ConVar sk_plr_dmg_grenade( "sk_plr_dmg_grenade","0"); // Very lame that the base code needs this defined
  1544. #endif
  1545. //-----------------------------------------------------------------------------
  1546. // Purpose:
  1547. // Input : iDmgType -
  1548. // Output : Returns true on success, false on failure.
  1549. //-----------------------------------------------------------------------------
  1550. bool CTFGameRules::Damage_IsTimeBased( int iDmgType )
  1551. {
  1552. // Damage types that are time-based.
  1553. return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_DROWNRECOVER ) ) != 0 );
  1554. }
  1555. //-----------------------------------------------------------------------------
  1556. // Purpose:
  1557. // Input : iDmgType -
  1558. // Output : Returns true on success, false on failure.
  1559. //-----------------------------------------------------------------------------
  1560. bool CTFGameRules::Damage_ShowOnHUD( int iDmgType )
  1561. {
  1562. // Damage types that have client HUD art.
  1563. return ( ( iDmgType & ( DMG_DROWN | DMG_BURN | DMG_NERVEGAS | DMG_SHOCK ) ) != 0 );
  1564. }
  1565. //-----------------------------------------------------------------------------
  1566. // Purpose:
  1567. // Input : iDmgType -
  1568. // Output : Returns true on success, false on failure.
  1569. //-----------------------------------------------------------------------------
  1570. bool CTFGameRules::Damage_ShouldNotBleed( int iDmgType )
  1571. {
  1572. // Should always bleed currently.
  1573. return false;
  1574. }
  1575. //-----------------------------------------------------------------------------
  1576. // Purpose:
  1577. //-----------------------------------------------------------------------------
  1578. int CTFGameRules::Damage_GetTimeBased( void )
  1579. {
  1580. int iDamage = ( DMG_PARALYZE | DMG_NERVEGAS | DMG_DROWNRECOVER );
  1581. return iDamage;
  1582. }
  1583. //-----------------------------------------------------------------------------
  1584. // Purpose:
  1585. //-----------------------------------------------------------------------------
  1586. int CTFGameRules::Damage_GetShowOnHud( void )
  1587. {
  1588. int iDamage = ( DMG_DROWN | DMG_BURN | DMG_NERVEGAS | DMG_SHOCK );
  1589. return iDamage;
  1590. }
  1591. //-----------------------------------------------------------------------------
  1592. // Purpose:
  1593. //-----------------------------------------------------------------------------
  1594. int CTFGameRules::Damage_GetShouldNotBleed( void )
  1595. {
  1596. return 0;
  1597. }
  1598. //-----------------------------------------------------------------------------
  1599. // Return true if we are playing a PvE mode
  1600. //-----------------------------------------------------------------------------
  1601. bool CTFGameRules::IsPVEModeActive( void ) const
  1602. {
  1603. #ifdef TF_RAID_MODE
  1604. if ( IsRaidMode() || IsBossBattleMode() )
  1605. return true;
  1606. #endif
  1607. if ( IsMannVsMachineMode() )
  1608. return true;
  1609. return false;
  1610. }
  1611. //-----------------------------------------------------------------------------
  1612. // Return true for PvE opponents (ie: enemy bot team)
  1613. //-----------------------------------------------------------------------------
  1614. bool CTFGameRules::IsPVEModeControlled( CBaseEntity *who ) const
  1615. {
  1616. if ( !who )
  1617. {
  1618. return false;
  1619. }
  1620. #ifdef GAME_DLL
  1621. if ( IsMannVsMachineMode() )
  1622. {
  1623. return who->GetTeamNumber() == TF_TEAM_PVE_INVADERS ? true : false;
  1624. }
  1625. if ( IsPVEModeActive() )
  1626. {
  1627. return who->GetTeamNumber() == TF_TEAM_RED ? true : false;
  1628. }
  1629. #endif
  1630. return false;
  1631. }
  1632. //-----------------------------------------------------------------------------
  1633. // Purpose: Return true if engineers can build quickly now
  1634. //-----------------------------------------------------------------------------
  1635. bool CTFGameRules::IsQuickBuildTime( void )
  1636. {
  1637. return IsMannVsMachineMode() && ( InSetup() || TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() );
  1638. }
  1639. //-----------------------------------------------------------------------------
  1640. // Purpose:
  1641. //-----------------------------------------------------------------------------
  1642. bool CTFGameRules::GameModeUsesUpgrades( void )
  1643. {
  1644. if ( IsMannVsMachineMode() || IsBountyMode() )
  1645. return true;
  1646. return false;
  1647. }
  1648. //-----------------------------------------------------------------------------
  1649. // Purpose:
  1650. //-----------------------------------------------------------------------------
  1651. bool CTFGameRules::CanPlayerUseRespec( CTFPlayer *pTFPlayer )
  1652. {
  1653. if ( !pTFPlayer )
  1654. return false;
  1655. bool bAllowed = IsMannVsMachineRespecEnabled() && State_Get() == GR_STATE_BETWEEN_RNDS;
  1656. #ifdef GAME_DLL
  1657. if ( !g_pPopulationManager )
  1658. return false;
  1659. bAllowed &= ( g_pPopulationManager->GetNumRespecsAvailableForPlayer( pTFPlayer ) ) ? true : false;
  1660. #else
  1661. if ( !g_TF_PR )
  1662. return false;
  1663. bAllowed &= ( g_TF_PR->GetNumRespecCredits( pTFPlayer->entindex() ) ) ? true : false;
  1664. #endif
  1665. return bAllowed;
  1666. }
  1667. bool CTFGameRules::IsCompetitiveMode( void ) const
  1668. {
  1669. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  1670. if ( pMatchDesc )
  1671. {
  1672. return pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_COMPETITIVE
  1673. || pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_CASUAL;
  1674. }
  1675. return false;
  1676. }
  1677. bool CTFGameRules::IsMatchTypeCasual( void ) const
  1678. {
  1679. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  1680. if ( pMatchDesc )
  1681. {
  1682. return ( pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_CASUAL );
  1683. }
  1684. return false;
  1685. }
  1686. bool CTFGameRules::IsMatchTypeCompetitive( void ) const
  1687. {
  1688. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  1689. if ( pMatchDesc )
  1690. {
  1691. return ( pMatchDesc->m_params.m_eMatchType == MATCH_TYPE_COMPETITIVE );
  1692. }
  1693. return false;
  1694. }
  1695. bool CTFGameRules::BInMatchStartCountdown() const
  1696. {
  1697. if ( IsCompetitiveMode() )
  1698. {
  1699. float flTime = GetRoundRestartTime();
  1700. if ( ( flTime > 0.f ) && ( (int)( flTime - gpGlobals->curtime ) <= mp_tournament_readymode_countdown.GetInt() ) )
  1701. {
  1702. return true;
  1703. }
  1704. }
  1705. return false;
  1706. }
  1707. EMatchGroup CTFGameRules::GetCurrentMatchGroup() const
  1708. {
  1709. #if defined STAGING_ONLY && defined CLIENT_DLL
  1710. if ( tf_fake_mm_group.GetInt() != -1 )
  1711. {
  1712. return (EMatchGroup)tf_fake_mm_group.GetInt();
  1713. }
  1714. #endif
  1715. #ifdef GAME_DLL
  1716. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1717. return pMatch ? pMatch->m_eMatchGroup : k_nMatchGroup_Invalid;
  1718. #else
  1719. // Client
  1720. // We only care about what the server says if we are in an MM match. We pass false
  1721. // into BConnectedToMatch because we want the match group of the server EVEN IF
  1722. // the match is over, but we're still connected.
  1723. return GTFGCClientSystem()->BConnectedToMatchServer( false ) ? (EMatchGroup)m_nMatchGroupType.Get() : k_nMatchGroup_Invalid;
  1724. #endif
  1725. }
  1726. bool CTFGameRules::IsManagedMatchEnded() const
  1727. {
  1728. #ifdef GAME_DLL
  1729. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1730. return !pMatch || pMatch->BMatchTerminated();
  1731. #else
  1732. // Client
  1733. // We only care about what the server says if we are in an MM match. (Note that the GC client system calls this to
  1734. // determine if an MM server considers the match over, so beware circular logic)
  1735. return !GTFGCClientSystem()->BConnectedToMatchServer( true ) || m_bMatchEnded;
  1736. #endif
  1737. }
  1738. #ifdef GAME_DLL
  1739. //-----------------------------------------------------------------------------
  1740. void CTFGameRules::SyncMatchSettings()
  1741. {
  1742. // These mirror the MatchInfo for the client's sake.
  1743. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1744. m_nMatchGroupType.Set( pMatch ? pMatch->m_eMatchGroup : k_nMatchGroup_Invalid );
  1745. m_bMatchEnded.Set( IsManagedMatchEnded() );
  1746. }
  1747. //-----------------------------------------------------------------------------
  1748. bool CTFGameRules::StartManagedMatch()
  1749. {
  1750. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1751. if ( !pMatch )
  1752. {
  1753. Warning( "Starting a managed match with no match info\n" );
  1754. return false;
  1755. }
  1756. // Cleanup
  1757. m_eRematchState = NEXT_MAP_VOTE_STATE_NONE;
  1758. /// Sync these before level change, so there's no race condition where clients may connect during/before the
  1759. /// changelevel and see that the match is ended or wrong.
  1760. SyncMatchSettings();
  1761. // Change the the correct map from the match. If no match specified, perform a fresh load of the current map
  1762. const char *pszMap = pMatch->GetMatchMap();
  1763. if ( !pszMap )
  1764. {
  1765. pszMap = STRING( gpGlobals->mapname );
  1766. Warning( "Managed match did not specify map, using current map (%s)\n", pszMap );
  1767. }
  1768. engine->ServerCommand( CFmtStr( "changelevel %s\n", pszMap ) );
  1769. return true;
  1770. }
  1771. //-----------------------------------------------------------------------------
  1772. // Purpose:
  1773. //-----------------------------------------------------------------------------
  1774. void CTFGameRules::SetCompetitiveMode( bool bValue )
  1775. {
  1776. m_bCompetitiveMode.Set( bValue );
  1777. //// Competitive mode is only supported on official servers.
  1778. //// It requires matchmaking, and doesn't allow ad-hoc connections.
  1779. //// If cheats are ever enabled, we force this mode off.
  1780. //tf_mm_trusted.SetValue( bValue );
  1781. //mp_tournament.SetValue( bValue );
  1782. //mp_tournament_readymode.SetValue( bValue );
  1783. //// No ad-hoc connections
  1784. //tf_mm_strict.SetValue( bValue );
  1785. //if ( bValue )
  1786. //{
  1787. // engine->ServerCommand( "exec server_ladder.cfg\n" );
  1788. // Assert( tf_mm_servermode.GetInt() == 1 );
  1789. //}
  1790. //// Any state toggle is a reset (so don't spam this call)
  1791. //State_Transition( GR_STATE_PREROUND );
  1792. //SetInWaitingForPlayers( bValue );
  1793. }
  1794. //-----------------------------------------------------------------------------
  1795. // Purpose:
  1796. //-----------------------------------------------------------------------------
  1797. void CTFGameRules::StartCompetitiveMatch( void )
  1798. {
  1799. m_flSafeToLeaveTimer = -1.f;
  1800. SetInWaitingForPlayers( false );
  1801. RoundRespawn();
  1802. State_Transition( GR_STATE_RESTART );
  1803. ResetPlayerAndTeamReadyState();
  1804. }
  1805. //-----------------------------------------------------------------------------
  1806. // Purpose: Stops a match for some specified reason
  1807. //-----------------------------------------------------------------------------
  1808. void CTFGameRules::StopCompetitiveMatch( CMsgGC_Match_Result_Status nCode )
  1809. {
  1810. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1811. int nActiveMatchPlayers = pMatch->GetNumActiveMatchPlayers();
  1812. if ( BAttemptMapVoteRollingMatch() &&
  1813. nCode == CMsgGC_Match_Result_Status_MATCH_SUCCEEDED &&
  1814. nActiveMatchPlayers > 0 )
  1815. {
  1816. ChooseNextMapVoteOptions();
  1817. }
  1818. else
  1819. {
  1820. // If we're not attempting a rolling match, end it
  1821. // TODO ROLLING MATCHES: If we bail between now and RequestNewMatchForLobby, we need to call this or we'll get stuck.
  1822. if ( !IsManagedMatchEnded() )
  1823. {
  1824. GTFGCClientSystem()->EndManagedMatch( /* bKickPlayersToParties */ false );
  1825. Assert( IsManagedMatchEnded() );
  1826. m_bMatchEnded.Set( true );
  1827. }
  1828. }
  1829. if ( nCode == CMsgGC_Match_Result_Status_MATCH_SUCCEEDED )
  1830. {
  1831. IGameEvent *winEvent = gameeventmanager->CreateEvent( "competitive_victory" );
  1832. if ( winEvent )
  1833. {
  1834. gameeventmanager->FireEvent( winEvent );
  1835. }
  1836. //
  1837. // This determines new ratings and creates the GC messages
  1838. //
  1839. CMatchInfo *pInfo = GTFGCClientSystem()->GetMatch();
  1840. if ( pInfo )
  1841. {
  1842. pInfo->CalculateMatchSkillRatingAdjustments( m_iWinningTeam );
  1843. // Performance ranking with medals is currently server-side
  1844. if ( pInfo->CalculatePlayerMatchRankData() )
  1845. {
  1846. // Send scoreboard event with final data
  1847. int nPlayers = pInfo->GetNumTotalMatchPlayers();
  1848. for ( int idx = 0; idx < nPlayers; idx++ )
  1849. {
  1850. IGameEvent *pEvent = gameeventmanager->CreateEvent( "competitive_stats_update" );
  1851. if ( pEvent )
  1852. {
  1853. CMatchInfo::PlayerMatchData_t *pMatchRankData = pInfo->GetMatchDataForPlayer( idx );
  1854. CBasePlayer *pPlayer = UTIL_PlayerBySteamID( pMatchRankData->steamID );
  1855. if ( !pPlayer )
  1856. continue;
  1857. pEvent->SetInt( "index", pPlayer->entindex() );
  1858. pEvent->SetInt( "score_rank", pMatchRankData ? pMatchRankData->nScoreMedal : 0 ); // medal won (if any)
  1859. pEvent->SetInt( "kills_rank", pMatchRankData ? pMatchRankData->nKillsMedal : 0 ); //
  1860. pEvent->SetInt( "damage_rank", pMatchRankData ? pMatchRankData->nDamageMedal : 0 ); //
  1861. pEvent->SetInt( "healing_rank", pMatchRankData ? pMatchRankData->nHealingMedal : 0 ); //
  1862. pEvent->SetInt( "support_rank", pMatchRankData ? pMatchRankData->nSupportMedal : 0 ); //
  1863. gameeventmanager->FireEvent( pEvent );
  1864. }
  1865. }
  1866. }
  1867. }
  1868. else
  1869. {
  1870. Warning( "CalculatePlayerMatchRankData(): General failure (investigate).\n" );
  1871. }
  1872. ReportMatchResultsToGC( nCode );
  1873. }
  1874. else if ( nCode == CMsgGC_Match_Result_Status_MATCH_FAILED_ABANDON )
  1875. {
  1876. // This generates a "safe to leave" notification on clients
  1877. IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_abandoned_match" );
  1878. if ( pEvent )
  1879. {
  1880. pEvent->SetBool( "game_over", ( tf_competitive_abandon_method.GetBool() || State_Get() == GR_STATE_BETWEEN_RNDS ) );
  1881. gameeventmanager->FireEvent( pEvent );
  1882. }
  1883. ReportMatchResultsToGC( nCode );
  1884. }
  1885. }
  1886. //-----------------------------------------------------------------------------
  1887. // Purpose: Fully completes the match
  1888. //-----------------------------------------------------------------------------
  1889. void CTFGameRules::EndCompetitiveMatch( void )
  1890. {
  1891. MatchSummaryEnd();
  1892. Log( "Competitive match ended. Kicking all players.\n" );
  1893. engine->ServerCommand( "kickall #TF_Competitive_Disconnect\n" );
  1894. // Prepare for next match
  1895. g_fGameOver = false;
  1896. m_bAllowBetweenRounds = true;
  1897. State_Transition( GR_STATE_RESTART );
  1898. SetInWaitingForPlayers( true );
  1899. }
  1900. //-----------------------------------------------------------------------------
  1901. // Purpose: Called during CTFGameRules::Think()
  1902. //-----------------------------------------------------------------------------
  1903. void CTFGameRules::ManageCompetitiveMode( void )
  1904. {
  1905. if ( !IsCompetitiveMode() )
  1906. return;
  1907. // Bring this back when we ship?
  1908. // // Security check
  1909. // if ( !tf_skillrating_debug.GetBool() )
  1910. // {
  1911. // m_bCompetitiveMode &= tf_mm_trusted.GetBool() &&
  1912. // IsInTournamentMode() &&
  1913. // !HaveCheatsBeenEnabledDuringLevel();
  1914. // }
  1915. // We lost trusted status
  1916. if ( !tf_mm_trusted.GetBool() )
  1917. {
  1918. m_nMatchGroupType.Set( k_nMatchGroup_Invalid );
  1919. StopCompetitiveMatch( CMsgGC_Match_Result_Status_MATCH_FAILED_TRUSTED );
  1920. UTIL_ClientPrintAll( HUD_PRINTCENTER, "Exiting Competitive Mode!" );
  1921. Log( "Server lost trusted status. Exiting Competitive Mode!" );
  1922. }
  1923. }
  1924. //-----------------------------------------------------------------------------
  1925. // Purpose:
  1926. //-----------------------------------------------------------------------------
  1927. bool CTFGameRules::ReportMatchResultsToGC( CMsgGC_Match_Result_Status nCode )
  1928. {
  1929. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  1930. if ( !pMatch )
  1931. return false;
  1932. GCSDK::CProtoBufMsg< CMsgGC_Match_Result > *pMsg = new GCSDK::CProtoBufMsg< CMsgGC_Match_Result >( k_EMsgGC_Match_Result );
  1933. pMsg->Body().set_match_id( pMatch->m_nMatchID );
  1934. pMsg->Body().set_match_group( pMatch->m_eMatchGroup );
  1935. pMsg->Body().set_status( nCode );
  1936. pMsg->Body().set_duration( CTF_GameStats.m_currentMap.m_Header.m_iTotalTime + ( gpGlobals->curtime - m_flRoundStartTime ) );
  1937. CTeam *pTeam = GetGlobalTeam( TF_TEAM_RED );
  1938. pMsg->Body().set_red_score( pTeam ? pTeam->GetScore() : (uint32)-1 );
  1939. pTeam = GetGlobalTeam( TF_TEAM_BLUE );
  1940. pMsg->Body().set_blue_score( pTeam ? pTeam->GetScore() : (uint32)-1 );
  1941. Assert( m_iWinningTeam >= 0 );
  1942. pMsg->Body().set_winning_team( Max( 0, (int)m_iWinningTeam ) );
  1943. const MapDef_t *pMap = GetItemSchema()->GetMasterMapDefByName( STRING( gpGlobals->mapname ) );
  1944. pMsg->Body().set_map_index( ( pMap ) ? pMap->m_nDefIndex : 0 );
  1945. pMsg->Body().set_game_type( 1 ); // TODO: eMapGameType
  1946. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( pMatch->m_eMatchGroup );
  1947. if( !pMatchDesc || !pMatchDesc->m_pProgressionDesc )
  1948. return false;
  1949. int nTotalScore = 0;
  1950. CTFPlayerResource *pTFResource = dynamic_cast< CTFPlayerResource* >( g_pPlayerResource );
  1951. if ( pTFResource )
  1952. {
  1953. pTFResource->UpdatePlayerData();
  1954. for ( int i=0; i < MAX_PLAYERS; ++i )
  1955. {
  1956. nTotalScore += pTFResource->GetTotalScore( i );
  1957. }
  1958. }
  1959. float flBlueScoreRatio = 0.5f;
  1960. const CTFTeam* pTFTeamRed = GetGlobalTFTeam( TF_TEAM_RED );
  1961. const CTFTeam* pTFTeamBlue = GetGlobalTFTeam( TF_TEAM_BLUE );
  1962. // Figure out how much XP to give each team based on the game mode played
  1963. if ( HasMultipleTrains() )
  1964. {
  1965. // In PLR we want to use the distance along the tracks each of the
  1966. // trains were at the end of each round
  1967. flBlueScoreRatio = RemapValClamped( pTFTeamBlue->GetTotalPLRTrackPercentTraveled(), 0.f, pTFTeamBlue->GetTotalPLRTrackPercentTraveled() + pTFTeamRed->GetTotalPLRTrackPercentTraveled(), 0.f, 1.f );
  1968. }
  1969. else if ( !m_bPlayingKoth && !m_bPowerupMode && ( tf_gamemode_cp.GetInt() || tf_gamemode_sd.GetInt() || tf_gamemode_payload.GetInt() ) )
  1970. {
  1971. // Rounds Won
  1972. // CP - Points can flow back and forever, so we can't count total caps. And the
  1973. // state of the game is always the same at match end, so rounds won is our
  1974. // only real metric.
  1975. // SD - A flag cap is a round, so we could count caps or rounds here. Again, the
  1976. // flag can go back and forth forever, but the match end state is always the same.
  1977. // PL - Count the points captured by each team. We do this A/D style where each team has
  1978. // a chance to score points.
  1979. flBlueScoreRatio = RemapValClamped( pTFTeamBlue->GetScore(), 0.f, pTFTeamBlue->GetScore() + pTFTeamRed->GetScore() , 0.f, 1.f );
  1980. }
  1981. else if ( tf_gamemode_ctf.GetInt() || m_bPowerupMode || tf_gamemode_passtime.GetInt() )
  1982. {
  1983. // Flag captures
  1984. // Mannpower is a variant of CTF and Passtime effectively is CTF. In all of these modes
  1985. // we don't use rounds so our best metric is individual flag captures.
  1986. flBlueScoreRatio = RemapValClamped( pTFTeamBlue->GetTotalFlagCaptures(), 0.f, pTFTeamBlue->GetTotalFlagCaptures() + pTFTeamRed->GetTotalFlagCaptures(), 0.f, 1.f );
  1987. }
  1988. else if ( m_bPlayingKoth )
  1989. {
  1990. // Time capped
  1991. // Looking at the capped time for each team will let us give xp in a fair way. We can
  1992. // actually get as close as 50/50
  1993. float flBlueTimeCapped = pTFTeamBlue->GetKOTHTime();
  1994. float flRedTiemCapped = pTFTeamRed->GetKOTHTime();
  1995. flBlueScoreRatio = RemapValClamped( flBlueTimeCapped, 0.f, flBlueTimeCapped + flRedTiemCapped, 0.f, 1.f );
  1996. }
  1997. else if ( tf_gamemode_rd.GetInt() || tf_gamemode_pd.GetInt() )
  1998. {
  1999. CTFRobotDestructionLogic* pRDLogic = CTFRobotDestructionLogic::GetRobotDestructionLogic();
  2000. // Count bottles/cores scored by each team
  2001. flBlueScoreRatio = RemapValClamped( pRDLogic->GetScore( TF_TEAM_BLUE ), 0.f, pRDLogic->GetScore( TF_TEAM_BLUE ) + pRDLogic->GetScore( TF_TEAM_RED ), 0.f, 1.f );
  2002. }
  2003. else if ( tf_gamemode_tc.GetInt() )
  2004. {
  2005. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  2006. int nBlueMiniRounds = 0;
  2007. int nRedMiniRounds = 0;
  2008. // Use the number of mini-rounds won by each team
  2009. for( int i=0; i < pMaster->GetNumRounds(); ++ i )
  2010. {
  2011. const CTeamControlPointRound* pRound = pMaster->GetRoundByIndex( i );
  2012. if ( pRound->RoundOwnedByTeam( TF_TEAM_RED ) )
  2013. {
  2014. ++nRedMiniRounds;
  2015. }
  2016. else if ( pRound->RoundOwnedByTeam( TF_TEAM_BLUE ) )
  2017. {
  2018. ++nBlueMiniRounds;
  2019. }
  2020. else
  2021. {
  2022. Assert( false );
  2023. }
  2024. }
  2025. flBlueScoreRatio = RemapValClamped( nBlueMiniRounds, 0.f, nBlueMiniRounds + nRedMiniRounds, 0.f, 1.f );
  2026. }
  2027. else
  2028. {
  2029. Assert( !"Game mode not handled for team XP bonus!" );
  2030. }
  2031. const float flRedScoreRatio = 1.f - flBlueScoreRatio;
  2032. const int nBlueTeamObjectiveBonus = flBlueScoreRatio * nTotalScore;
  2033. const int nRedTeamObjectiveBonus = flRedScoreRatio * nTotalScore;
  2034. // Player info
  2035. for ( int idxPlayer = 0; idxPlayer < pMatch->GetNumTotalMatchPlayers(); idxPlayer++ )
  2036. {
  2037. CMatchInfo::PlayerMatchData_t *pMatchPlayer = pMatch->GetMatchDataForPlayer( idxPlayer );
  2038. if ( !pMatchPlayer->steamID.BIndividualAccount() )
  2039. {
  2040. Assert( false );
  2041. continue;
  2042. }
  2043. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerBySteamID( pMatchPlayer->steamID ) );
  2044. PlayerStats_t *pStats = CTF_GameStats.FindPlayerStats( pTFPlayer );
  2045. CMsgGC_Match_Result_Player *pMsgPlayer = pMsg->Body().add_players();
  2046. int nTeam = GetGameTeamForGCTeam( pMatchPlayer->eGCTeam );
  2047. pMsgPlayer->set_steam_id( pMatchPlayer->steamID.ConvertToUint64() );
  2048. pMsgPlayer->set_team( nTeam );
  2049. if ( pTFResource && pTFPlayer )
  2050. {
  2051. pMsgPlayer->set_score( pTFResource->GetTotalScore( pTFPlayer->entindex() ) );
  2052. }
  2053. else
  2054. {
  2055. // They left
  2056. }
  2057. int nPing = 0;
  2058. int nPacketLoss = 0;
  2059. if ( pTFPlayer )
  2060. {
  2061. UTIL_GetPlayerConnectionInfo( pTFPlayer->entindex(), nPing, nPacketLoss );
  2062. }
  2063. pMsgPlayer->set_ping( nPing );
  2064. uint32 unPlayerFlags = 0U;
  2065. if ( pMatchPlayer->bDropped )
  2066. {
  2067. unPlayerFlags |= MATCH_FLAG_PLAYER_LEAVER;
  2068. }
  2069. if ( pMatchPlayer->bLateJoin )
  2070. {
  2071. unPlayerFlags |= MATCH_FLAG_PLAYER_LATEJOIN;
  2072. }
  2073. if ( pMatchPlayer->BDropWasAbandon() )
  2074. {
  2075. unPlayerFlags |= MATCH_FLAG_PLAYER_ABANDONER;
  2076. }
  2077. if ( pMatchPlayer->bPlayed )
  2078. {
  2079. unPlayerFlags |= MATCH_FLAG_PLAYER_PLAYED;
  2080. }
  2081. pMsgPlayer->set_flags( unPlayerFlags );
  2082. // server-side skill system
  2083. FixmeMMRatingBackendSwapping(); // Assuming skill rating is drillo
  2084. pMsgPlayer->set_classes_played( pMatchPlayer->unClassesPlayed );
  2085. pMsgPlayer->set_kills( pStats ? pStats->statsAccumulated.m_iStat[TFSTAT_KILLS] : 0 );
  2086. pMsgPlayer->set_damage( pStats ? pStats->statsAccumulated.m_iStat[TFSTAT_DAMAGE] : 0 );
  2087. pMsgPlayer->set_healing( pStats ? pStats->statsAccumulated.m_iStat[TFSTAT_HEALING] : 0 );
  2088. pMsgPlayer->set_support( pStats ? CalcPlayerSupportScore( &pStats->statsAccumulated, pTFPlayer->entindex() ) : 0 );
  2089. pMsgPlayer->set_score_medal( pMatchPlayer->nScoreMedal );
  2090. pMsgPlayer->set_kills_medal( pMatchPlayer->nKillsMedal );
  2091. pMsgPlayer->set_damage_medal( pMatchPlayer->nDamageMedal );
  2092. pMsgPlayer->set_healing_medal( pMatchPlayer->nHealingMedal );
  2093. pMsgPlayer->set_support_medal( pMatchPlayer->nSupportMedal );
  2094. FixmeMMRatingBackendSwapping(); // Assuming we're using skill rating for rank? Why even include this?
  2095. pMsgPlayer->set_rank( pMatchDesc->m_pProgressionDesc->GetLevelForExperience( pMatchPlayer->unMMSkillRating ).m_nLevelNum );
  2096. pMsgPlayer->set_deaths( pStats ? pStats->statsAccumulated.m_iStat[TFSTAT_DEATHS] : 0 );
  2097. pMsgPlayer->set_party_id( pMatchPlayer->uPartyID );
  2098. uint32 unLeaveTime = ( pMatchPlayer && ( pMatchPlayer->bDropped || pMatchPlayer->BDropWasAbandon() ) ) ?
  2099. pMatchPlayer->GetLastActiveEventTime() : 0u;
  2100. pMsgPlayer->set_leave_time( unLeaveTime );
  2101. pMsgPlayer->set_leave_reason( pMatchPlayer->GetDropReason() );
  2102. pMsgPlayer->set_connect_time( pMatchPlayer->rtJoinedMatch );
  2103. // Somebody won! Match finish bonus
  2104. if ( nCode == CMsgGC_Match_Result_Status_MATCH_SUCCEEDED )
  2105. {
  2106. // Give points based on team performance
  2107. int nPerformanceScore = RemapValClamped( pMsgPlayer->score(), 0, nTotalScore / 24, 0, nTeam == TF_TEAM_RED ? nRedTeamObjectiveBonus : nBlueTeamObjectiveBonus );
  2108. pMatch->GiveXPRewardToPlayerForAction( pMatchPlayer->steamID, CMsgTFXPSource::SOURCE_OBJECTIVE_BONUS, nPerformanceScore );
  2109. // Everyone gets base completion points
  2110. int nCompletionScore = RemapValClamped( pMsgPlayer->score(), 0, nTotalScore / 24, 0, nTotalScore );
  2111. pMatch->GiveXPRewardToPlayerForAction( pMatchPlayer->steamID, CMsgTFXPSource::SOURCE_COMPLETED_MATCH, nCompletionScore );
  2112. }
  2113. // Copy any pending XP sources they had ready to send up
  2114. for( int i=0; i < pMatchPlayer->GetXPSources().sources_size(); ++i )
  2115. {
  2116. CMsgTFXPSource* pXPSource = pMsgPlayer->add_xp_breakdown();
  2117. pXPSource->CopyFrom( pMatchPlayer->GetXPSources().sources( i ) );
  2118. }
  2119. }
  2120. pMsg->Body().set_win_reason( GetWinReason() );
  2121. uint32 unMatchFlags = 0u;
  2122. if ( pMatch->m_uLobbyFlags & LOBBY_FLAG_LOWPRIORITY )
  2123. {
  2124. unMatchFlags |= MATCH_FLAG_LOWPRIORITY;
  2125. }
  2126. if ( pMatch->m_uLobbyFlags & LOBBY_FLAG_REMATCH )
  2127. {
  2128. unMatchFlags |= MATCH_FLAG_REMATCH;
  2129. }
  2130. pMsg->Body().set_flags( unMatchFlags );
  2131. pMsg->Body().set_bots( pMatch->m_nBotsAdded );
  2132. GTFGCClientSystem()->SendCompetitiveMatchResult( pMsg );
  2133. return true;
  2134. }
  2135. //-----------------------------------------------------------------------------
  2136. // Purpose:
  2137. //-----------------------------------------------------------------------------
  2138. bool CTFGameRules::MatchmakingShouldUseStopwatchMode()
  2139. {
  2140. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  2141. bool bRetVal = !HasMultipleTrains() && ( tf_gamemode_payload.GetBool() || ( pMaster && ( pMaster->PlayingMiniRounds() || pMaster->ShouldSwitchTeamsOnRoundWin() ) ) );
  2142. tf_attack_defend_map.SetValue( bRetVal );
  2143. return bRetVal;
  2144. }
  2145. //-----------------------------------------------------------------------------
  2146. // Purpose:
  2147. //-----------------------------------------------------------------------------
  2148. void CTFGameRules::SetPowerupMode( bool bValue )
  2149. {
  2150. // Powerup mode uses grapple and changes some gamerule variables.
  2151. if ( bValue )
  2152. {
  2153. tf_grapplinghook_enable.SetValue( 1 );
  2154. tf_flag_return_time_credit_factor.SetValue( 0 );
  2155. }
  2156. else
  2157. {
  2158. tf_grapplinghook_enable.SetValue( 0 );
  2159. tf_flag_return_time_credit_factor.SetValue( 1 );
  2160. }
  2161. m_bPowerupMode = bValue;
  2162. }
  2163. #ifdef GAME_DLL
  2164. //-----------------------------------------------------------------------------
  2165. void CTFGameRules::EndManagedMvMMatch( bool bKickPlayersToParties )
  2166. {
  2167. // Primarily a pass through so we can ensure our match end state is sync'd -- CPopulationManager manages most of the
  2168. // MvM meta round state
  2169. if ( !IsManagedMatchEnded() )
  2170. {
  2171. GTFGCClientSystem()->EndManagedMatch( bKickPlayersToParties );
  2172. Assert( IsManagedMatchEnded() );
  2173. m_bMatchEnded.Set( true );
  2174. }
  2175. }
  2176. #endif // GAME_DLL
  2177. #ifdef STAGING_ONLY
  2178. //-----------------------------------------------------------------------------
  2179. // Purpose: Enable/Disable Bounty Mode
  2180. //-----------------------------------------------------------------------------
  2181. void CTFGameRules::SetBountyMode( bool bValue )
  2182. {
  2183. if ( m_bBountyModeEnabled.Get() != bValue )
  2184. {
  2185. m_bBountyModeEnabled.Set( bValue );
  2186. }
  2187. // If enabling, dynamically create an upgrade entity
  2188. if ( bValue )
  2189. {
  2190. if ( !g_hUpgradeEntity && !m_pUpgrades )
  2191. {
  2192. m_pUpgrades = CBaseEntity::Create( "func_upgradestation", vec3_origin, vec3_angle );
  2193. }
  2194. }
  2195. // If disabling, remove upgrade entity
  2196. if ( !bValue && m_pUpgrades )
  2197. {
  2198. UTIL_Remove( m_pUpgrades );
  2199. m_pUpgrades = NULL;
  2200. }
  2201. IGameEvent *event = gameeventmanager->CreateEvent( "bountymode_toggled" );
  2202. if ( event )
  2203. {
  2204. event->SetBool( "active", bValue );
  2205. gameeventmanager->FireEvent( event );
  2206. }
  2207. }
  2208. #endif // GAME_DLL
  2209. #endif // STAGING_ONLY
  2210. //-----------------------------------------------------------------------------
  2211. // Purpose:
  2212. //-----------------------------------------------------------------------------
  2213. bool CTFGameRules::UsePlayerReadyStatusMode( void )
  2214. {
  2215. if ( IsMannVsMachineMode() )
  2216. return true;
  2217. if ( IsCompetitiveMode() )
  2218. return true;
  2219. if ( mp_tournament.GetBool() && mp_tournament_readymode.GetBool() )
  2220. return true;
  2221. return false;
  2222. }
  2223. //-----------------------------------------------------------------------------
  2224. // Purpose:
  2225. //-----------------------------------------------------------------------------
  2226. bool CTFGameRules::PlayerReadyStatus_HaveMinPlayersToEnable( void )
  2227. {
  2228. // we always have enough players if the match wants players to autoready
  2229. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  2230. if ( pMatchDesc && pMatchDesc->m_params.m_bAutoReady )
  2231. return true;
  2232. #ifdef GAME_DLL
  2233. // Count connected players
  2234. int nNumPlayers = 0;
  2235. CUtlVector< CTFPlayer* > playerVector;
  2236. CollectPlayers( &playerVector );
  2237. FOR_EACH_VEC( playerVector, i )
  2238. {
  2239. if ( !playerVector[i] )
  2240. continue;
  2241. if ( playerVector[i]->IsFakeClient() )
  2242. continue;
  2243. #ifdef STAGING_ONLY
  2244. if ( !mp_tournament_readymode_bots_allowed.GetBool() && playerVector[i]->IsBot() )
  2245. continue;
  2246. #else
  2247. if ( playerVector[i]->IsBot() )
  2248. continue;
  2249. #endif
  2250. if ( playerVector[i]->IsHLTV() )
  2251. continue;
  2252. if ( playerVector[i]->IsReplay() )
  2253. continue;
  2254. nNumPlayers++;
  2255. }
  2256. // Default
  2257. int nMinPlayers = 1;
  2258. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  2259. if ( pMatch && !pMatch->BMatchTerminated() && pMatchDesc->m_params.m_bRequireCompleteMatch )
  2260. {
  2261. nMinPlayers = pMatch->GetCanonicalMatchSize();
  2262. }
  2263. else if ( IsMannVsMachineMode() &&
  2264. ( engine->IsDedicatedServer() || ( !engine->IsDedicatedServer() && nNumPlayers > 1 ) ) )
  2265. {
  2266. nMinPlayers = tf_mvm_min_players_to_start.GetInt();
  2267. }
  2268. else if ( UsePlayerReadyStatusMode() && engine->IsDedicatedServer() )
  2269. {
  2270. nMinPlayers = mp_tournament_readymode_min.GetInt();
  2271. }
  2272. // Should be renamed to m_bEnableReady, not sure why we encoded our criteria in the names of all associated functions and variables...
  2273. m_bHaveMinPlayersToEnableReady.Set( nNumPlayers >= nMinPlayers );
  2274. #endif
  2275. return m_bHaveMinPlayersToEnableReady;
  2276. }
  2277. #ifdef GAME_DLL
  2278. //-----------------------------------------------------------------------------
  2279. bool CTFGameRules::PlayerReadyStatus_ArePlayersOnTeamReady( int iTeam )
  2280. {
  2281. if ( IsMannVsMachineMode() && iTeam == TF_TEAM_PVE_INVADERS )
  2282. return true;
  2283. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  2284. if ( pMatch )
  2285. {
  2286. int nMatchPlayers = pMatch->GetNumTotalMatchPlayers();
  2287. if ( nMatchPlayers <= 0 )
  2288. return false;
  2289. int iPlayerReadyCount = 0;
  2290. for ( int i = 0; i < nMatchPlayers; i++ )
  2291. {
  2292. CMatchInfo::PlayerMatchData_t *pPlayerData = pMatch->GetMatchDataForPlayer( i );
  2293. if ( !pPlayerData->bDropped && GetGameTeamForGCTeam( pPlayerData->eGCTeam ) == iTeam )
  2294. {
  2295. CBasePlayer *pPlayer = UTIL_PlayerBySteamID( pPlayerData->steamID );
  2296. // XXX(JohnS): Not quite valid yet, We let them join first onto spectate, which is probably a bit
  2297. // confusing
  2298. //
  2299. // AssertMsg( !pPlayer || ToTFPlayer( pPlayer )->GetTeamNumber() == GetGameTeamForGCTeam( pPlayerData->eGCTeam ),
  2300. // "Player's GC assigned team does not match their current team" );
  2301. if ( !pPlayer || !m_bPlayerReady[ pPlayer->entindex() ] )
  2302. return false;
  2303. iPlayerReadyCount++;
  2304. }
  2305. }
  2306. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  2307. if ( pMatchDesc && pMatchDesc->m_params.m_bAutoReady )
  2308. {
  2309. return iPlayerReadyCount > 0 || pMatch->GetNumTotalMatchPlayers() == 1 ;
  2310. }
  2311. else
  2312. {
  2313. int iTeamSize = IsMannVsMachineMode() ? pMatch->GetCanonicalMatchSize() : pMatch->GetCanonicalMatchSize() / 2;
  2314. return iPlayerReadyCount >= iTeamSize;
  2315. }
  2316. }
  2317. // Non-match
  2318. bool bAtLeastOneReady = false;
  2319. for ( int i = 1; i <= MAX_PLAYERS; ++i )
  2320. {
  2321. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  2322. if ( !pPlayer || ToTFPlayer( pPlayer )->GetTeamNumber() != iTeam )
  2323. continue;
  2324. if ( !m_bPlayerReady[i] )
  2325. {
  2326. return false;
  2327. }
  2328. else
  2329. {
  2330. bAtLeastOneReady = true;
  2331. }
  2332. }
  2333. // Team isn't ready if there was nobody on it.
  2334. return bAtLeastOneReady;
  2335. }
  2336. //-----------------------------------------------------------------------------
  2337. // Purpose:
  2338. //-----------------------------------------------------------------------------
  2339. bool CTFGameRules::PlayerReadyStatus_ShouldStartCountdown( void )
  2340. {
  2341. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  2342. #if defined( STAGING_ONLY )
  2343. // Local testing hack - allow match size of one where just that player is ready
  2344. if ( !IsMannVsMachineMode() && !pMatch && BHavePlayers() && ( IsTeamReady( TF_TEAM_RED ) || IsTeamReady( TF_TEAM_BLUE ) ) )
  2345. return true;
  2346. #endif // STAGING_ONLY
  2347. if ( IsMannVsMachineMode() )
  2348. {
  2349. if ( !IsTeamReady( TF_TEAM_PVE_DEFENDERS ) && m_flRestartRoundTime >= gpGlobals->curtime + mp_tournament_readymode_countdown.GetInt() )
  2350. {
  2351. bool bIsTeamReady = PlayerReadyStatus_ArePlayersOnTeamReady( TF_TEAM_PVE_DEFENDERS );
  2352. if ( bIsTeamReady )
  2353. {
  2354. SetTeamReadyState( true, TF_TEAM_PVE_DEFENDERS );
  2355. return true;
  2356. }
  2357. }
  2358. }
  2359. else if ( pMatch &&
  2360. PlayerReadyStatus_ArePlayersOnTeamReady( TF_TEAM_RED ) &&
  2361. PlayerReadyStatus_ArePlayersOnTeamReady( TF_TEAM_BLUE ) )
  2362. {
  2363. return true;
  2364. }
  2365. else if ( IsTeamReady( TF_TEAM_RED ) && IsTeamReady( TF_TEAM_BLUE ) )
  2366. {
  2367. return true;
  2368. }
  2369. return false;
  2370. }
  2371. //-----------------------------------------------------------------------------
  2372. // Purpose:
  2373. //-----------------------------------------------------------------------------
  2374. void CTFGameRules::PlayerReadyStatus_ResetState( void )
  2375. {
  2376. // Reset the players
  2377. ResetPlayerAndTeamReadyState();
  2378. // Reset the team
  2379. SetTeamReadyState( false, TF_TEAM_RED );
  2380. SetTeamReadyState( false, TF_TEAM_BLUE );
  2381. m_flRestartRoundTime.Set( -1.f );
  2382. mp_restartgame.SetValue( 0 );
  2383. m_bAwaitingReadyRestart = true;
  2384. }
  2385. //-----------------------------------------------------------------------------
  2386. // Purpose:
  2387. //-----------------------------------------------------------------------------
  2388. void CTFGameRules::PlayerReadyStatus_UpdatePlayerState( CTFPlayer *pTFPlayer, bool bState )
  2389. {
  2390. if ( !UsePlayerReadyStatusMode() )
  2391. return;
  2392. if ( !pTFPlayer )
  2393. return;
  2394. if ( pTFPlayer->GetTeamNumber() < FIRST_GAME_TEAM )
  2395. return;
  2396. if ( State_Get() != GR_STATE_BETWEEN_RNDS )
  2397. return;
  2398. // Don't allow toggling state in the final countdown
  2399. if ( GetRoundRestartTime() > 0.f && GetRoundRestartTime() <= gpGlobals->curtime + TOURNAMENT_NOCANCEL_TIME )
  2400. return;
  2401. // Make sure we have enough to allow ready mode commands
  2402. if ( !PlayerReadyStatus_HaveMinPlayersToEnable() )
  2403. return;
  2404. int nEntIndex = pTFPlayer->entindex();
  2405. // Already this state
  2406. if ( bState == IsPlayerReady( nEntIndex ) )
  2407. return;
  2408. SetPlayerReadyState( nEntIndex, bState );
  2409. if ( !bState )
  2410. {
  2411. // Slam team status to Not Ready for any player that sets Not Ready
  2412. m_bTeamReady.Set( pTFPlayer->GetTeamNumber(), false );
  2413. // If everyone cancels ready state, stop the clock
  2414. bool bAnyoneReady = false;
  2415. for ( int i = 1; i <= MAX_PLAYERS; ++i )
  2416. {
  2417. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  2418. if ( !pPlayer )
  2419. continue;
  2420. if ( m_bPlayerReady[i] )
  2421. {
  2422. bAnyoneReady = true;
  2423. break;
  2424. }
  2425. }
  2426. if ( !bAnyoneReady )
  2427. {
  2428. m_flRestartRoundTime.Set( -1.f );
  2429. mp_restartgame.SetValue( 0 );
  2430. ResetPlayerAndTeamReadyState();
  2431. }
  2432. }
  2433. else
  2434. {
  2435. if ( IsMannVsMachineMode() || IsCompetitiveMode() )
  2436. {
  2437. // Reduce timer as each player hits Ready, but only once per-player
  2438. if ( !m_bPlayerReadyBefore[nEntIndex] && m_flRestartRoundTime > gpGlobals->curtime + 60.f )
  2439. {
  2440. float flReduceBy = 30.f;
  2441. if ( m_flRestartRoundTime < gpGlobals->curtime + 90.f )
  2442. {
  2443. // Never reduce below 60 seconds remaining
  2444. flReduceBy = m_flRestartRoundTime - gpGlobals->curtime - 60.f;
  2445. }
  2446. m_flRestartRoundTime -= flReduceBy;
  2447. }
  2448. else if ( m_flRestartRoundTime < 0 && !PlayerReadyStatus_ShouldStartCountdown() )
  2449. {
  2450. m_flRestartRoundTime.Set( gpGlobals->curtime + 150.f );
  2451. m_bAwaitingReadyRestart = false;
  2452. IGameEvent* pEvent = gameeventmanager->CreateEvent( "teamplay_round_restart_seconds" );
  2453. if ( pEvent )
  2454. {
  2455. pEvent->SetInt( "seconds", 150 );
  2456. gameeventmanager->FireEvent( pEvent );
  2457. }
  2458. }
  2459. }
  2460. // Unofficial modes set team ready state here
  2461. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  2462. if ( !pMatch && !IsMannVsMachineMode() )
  2463. {
  2464. int nRed = 0;
  2465. int nRedCount = 0;
  2466. int nBlue = 0;
  2467. int nBlueCount = 0;
  2468. for ( int iTeam = FIRST_GAME_TEAM; iTeam < TFTeamMgr()->GetTeamCount(); iTeam++ )
  2469. {
  2470. CTFTeam *pTeam = GetGlobalTFTeam( iTeam );
  2471. if ( pTeam )
  2472. {
  2473. Assert( pTeam->GetTeamNumber() == TF_TEAM_RED || pTeam->GetTeamNumber() == TF_TEAM_BLUE );
  2474. for ( int i = 0; i < pTeam->GetNumPlayers(); ++i )
  2475. {
  2476. if ( !pTeam->GetPlayer(i) )
  2477. continue;
  2478. if ( pTeam->GetTeamNumber() == TF_TEAM_RED && IsPlayerReady( pTeam->GetPlayer(i)->entindex() ) )
  2479. {
  2480. if ( !nRedCount )
  2481. {
  2482. nRedCount = pTeam->GetNumPlayers();
  2483. }
  2484. nRed++;
  2485. }
  2486. else if ( pTeam->GetTeamNumber() == TF_TEAM_BLUE && IsPlayerReady( pTeam->GetPlayer(i)->entindex() ) )
  2487. {
  2488. if ( !nBlueCount )
  2489. {
  2490. nBlueCount = pTeam->GetNumPlayers();
  2491. }
  2492. nBlue++;
  2493. }
  2494. }
  2495. }
  2496. }
  2497. // Check for the convar that requires min team size, or just go with whatever each team has
  2498. int nRedMin = ( mp_tournament_readymode_team_size.GetInt() > 0 ) ? mp_tournament_readymode_team_size.GetInt() : Max( nRedCount, 1 );
  2499. int nBlueMin = ( mp_tournament_readymode_team_size.GetInt() > 0 ) ? mp_tournament_readymode_team_size.GetInt() : Max( nBlueCount, 1 );
  2500. SetTeamReadyState( ( nRed == nRedMin ), TF_TEAM_RED );
  2501. SetTeamReadyState( ( nBlue == nBlueMin ), TF_TEAM_BLUE );
  2502. }
  2503. m_bPlayerReadyBefore[nEntIndex] = true;
  2504. }
  2505. }
  2506. #endif // GAME_DLL
  2507. //-----------------------------------------------------------------------------
  2508. // Purpose:
  2509. //-----------------------------------------------------------------------------
  2510. bool CTFGameRules::IsDefaultGameMode( void )
  2511. {
  2512. if ( IsMannVsMachineMode() )
  2513. return false;
  2514. if ( IsInArenaMode() )
  2515. return false;
  2516. if ( IsInMedievalMode() )
  2517. return false;
  2518. if ( IsCompetitiveMode() )
  2519. return false;
  2520. if ( IsInTournamentMode() )
  2521. return false;
  2522. if ( IsInTraining() )
  2523. return false;
  2524. if ( IsInItemTestingMode() )
  2525. return false;
  2526. #ifdef STAGING_ONLY
  2527. if ( IsPVEModeActive() )
  2528. return false;
  2529. #endif // STAGING_ONLY
  2530. #ifdef TF_RAID_MODE
  2531. if ( IsRaidMode() )
  2532. return false;
  2533. if ( IsBossBattleMode() )
  2534. return false;
  2535. #endif // TF_RAID_MODE
  2536. #ifdef TF_CREEP_MODE
  2537. if ( IsCreepWaveMode() )
  2538. return false;
  2539. #endif // TF_CREEP_MODE
  2540. return true;
  2541. }
  2542. //-----------------------------------------------------------------------------
  2543. // Purpose: Allows us to give situational discounts, such as secondary weapons
  2544. //-----------------------------------------------------------------------------
  2545. int CTFGameRules::GetCostForUpgrade( CMannVsMachineUpgrades *pUpgrade, int iItemSlot, int nClass, CTFPlayer *pPurchaser /*= NULL*/ )
  2546. {
  2547. Assert( pUpgrade );
  2548. if ( !pUpgrade )
  2549. return 0;
  2550. int iCost = pUpgrade->nCost;
  2551. CEconItemSchema *pSchema = ItemSystem()->GetItemSchema();
  2552. if ( pSchema )
  2553. {
  2554. const CEconItemAttributeDefinition *pAttr = pSchema->GetAttributeDefinitionByName( pUpgrade->szAttrib );
  2555. if ( pAttr )
  2556. {
  2557. if ( ( iItemSlot == LOADOUT_POSITION_PRIMARY && nClass == TF_CLASS_ENGINEER ) ||
  2558. ( iItemSlot == LOADOUT_POSITION_SECONDARY && nClass != TF_CLASS_DEMOMAN ) ||
  2559. ( iItemSlot == LOADOUT_POSITION_MELEE && nClass != TF_CLASS_SPY && nClass != TF_CLASS_ENGINEER ) )
  2560. {
  2561. switch ( pAttr->GetDefinitionIndex() )
  2562. {
  2563. case 4: // clip size bonus
  2564. case 6: // fire rate bonus
  2565. case 78: // maxammo secondary increased
  2566. case 180: // heal on kill
  2567. case 266: // projectile penetration
  2568. case 335: // clip size bonus upgrade
  2569. case 397: // projectile penetration heavy
  2570. iCost *= 0.5f;
  2571. break;
  2572. default:
  2573. break;
  2574. }
  2575. }
  2576. else if ( iItemSlot == LOADOUT_POSITION_ACTION )
  2577. {
  2578. if ( pPurchaser )
  2579. {
  2580. int iCanteenSpec = 0;
  2581. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPurchaser, iCanteenSpec, canteen_specialist );
  2582. if ( iCanteenSpec )
  2583. {
  2584. // iCost *= ( 1.f - ( iCanteenSpec * 0.1f ) );
  2585. // iCost = Max( 1, ( iCost - ( iCost % 5 ) ) );
  2586. iCost -= ( 10 * iCanteenSpec );
  2587. iCost = Max( 5, iCost );
  2588. }
  2589. }
  2590. }
  2591. }
  2592. }
  2593. return iCost;
  2594. }
  2595. //-----------------------------------------------------------------------------
  2596. // Purpose:
  2597. //-----------------------------------------------------------------------------
  2598. CTFGameRules::CTFGameRules()
  2599. #ifdef GAME_DLL
  2600. : m_mapCoachToStudentMap( DefLessFunc(uint32) )
  2601. , m_flNextStrangeEventProcessTime( g_flStrangeEventBatchProcessInterval )
  2602. , m_mapTeleportLocations( DefLessFunc(string_t) )
  2603. , m_bMapCycleNeedsUpdate( false )
  2604. , m_flSafeToLeaveTimer( -1.f )
  2605. , m_flCompModeRespawnPlayersAtMatchStart( -1.f )
  2606. , m_bMapForcedTruceDuringBossFight( false )
  2607. , m_flNextHalloweenGiftUpdateTime( -1 )
  2608. #else
  2609. : m_bRecievedBaseline( false )
  2610. #endif
  2611. {
  2612. #ifdef GAME_DLL
  2613. // Create teams.
  2614. TFTeamMgr()->Init();
  2615. ResetMapTime();
  2616. // Create the team managers
  2617. // for ( int i = 0; i < ARRAYSIZE( teamnames ); i++ )
  2618. // {
  2619. // CTeam *pTeam = static_cast<CTeam*>(CreateEntityByName( "tf_team" ));
  2620. // pTeam->Init( sTeamNames[i], i );
  2621. //
  2622. // g_Teams.AddToTail( pTeam );
  2623. // }
  2624. m_flIntermissionEndTime = 0.0f;
  2625. m_flNextPeriodicThink = 0.0f;
  2626. ListenForGameEvent( "teamplay_point_captured" );
  2627. ListenForGameEvent( "teamplay_capture_blocked" );
  2628. ListenForGameEvent( "teamplay_round_win" );
  2629. ListenForGameEvent( "teamplay_flag_event" );
  2630. ListenForGameEvent( "teamplay_round_start" );
  2631. ListenForGameEvent( "player_escort_score" );
  2632. ListenForGameEvent( "player_disconnect" );
  2633. ListenForGameEvent( "teamplay_setup_finished" );
  2634. ListenForGameEvent( "recalculate_truce" );
  2635. Q_memset( m_vecPlayerPositions,0, sizeof(m_vecPlayerPositions) );
  2636. m_iPrevRoundState = -1;
  2637. m_iCurrentRoundState = -1;
  2638. m_iCurrentMiniRoundMask = 0;
  2639. m_iPreviousTeamSize = 0;
  2640. // Lets execute a map specific cfg file
  2641. // ** execute this after server.cfg!
  2642. char szCommand[MAX_PATH] = { 0 };
  2643. // Map names cannot contain quotes or control characters so this is safe but silly that we have to do it.
  2644. Q_snprintf( szCommand, sizeof( szCommand ), "exec \"%s.cfg\"\n", STRING( gpGlobals->mapname ) );
  2645. engine->ServerCommand( szCommand );
  2646. m_flSendNotificationTime = 0.0f;
  2647. m_bOvertimeAllowedForCTF = true;
  2648. m_redPayloadToPush = NULL;
  2649. m_bluePayloadToPush = NULL;
  2650. m_redPayloadToBlock = NULL;
  2651. m_bluePayloadToBlock = NULL;
  2652. SetCTFCaptureBonusTime( -1 );
  2653. m_hasSpawnedToy = false;
  2654. m_flCapInProgressBuffer = 0.f;
  2655. m_bMannVsMachineAlarmStatus = false;
  2656. m_flNextFlagAlarm = 0.0f;
  2657. m_flNextFlagAlert = 0.0f;
  2658. m_doomsdaySetupTimer.Invalidate();
  2659. StopDoomsdayTicketsTimer();
  2660. m_nPowerupKillsRedTeam = 0;
  2661. m_nPowerupKillsBlueTeam = 0;
  2662. m_flTimeToRunImbalanceMeasures = 120.f;
  2663. m_flTimeToStopImbalanceMeasures = 0.f;
  2664. m_bPowerupImbalanceMeasuresRunning = false;
  2665. m_hRequiredObserverTarget = NULL;
  2666. m_bStopWatchWinner.Set( false );
  2667. #else // GAME_DLL
  2668. // Vision Filter Translations for swapping out particle effects and models
  2669. SetUpVisionFilterKeyValues();
  2670. m_bSillyGibs = CommandLine()->FindParm( "-sillygibs" ) ? true : false;
  2671. if ( m_bSillyGibs )
  2672. {
  2673. cl_burninggibs.SetValue( 0 );
  2674. }
  2675. // @todo Tom Bui: game_newmap doesn't seem to be used...
  2676. ListenForGameEvent( "game_newmap" );
  2677. ListenForGameEvent( "overtime_nag" );
  2678. ListenForGameEvent( "recalculate_holidays" );
  2679. #endif
  2680. ClearHalloweenEffectStatus();
  2681. // Initialize the game type
  2682. m_nGameType.Set( TF_GAMETYPE_UNDEFINED );
  2683. m_bPlayingMannVsMachine.Set( false );
  2684. m_bMannVsMachineAlarmStatus.Set( false );
  2685. m_bBountyModeEnabled.Set( false );
  2686. m_bPlayingKoth.Set( false );
  2687. m_bPlayingMedieval.Set( false );
  2688. m_bPlayingHybrid_CTF_CP.Set( false );
  2689. m_bPlayingSpecialDeliveryMode.Set( false );
  2690. m_bPlayingRobotDestructionMode.Set( false );
  2691. m_bPowerupMode.Set( false );
  2692. m_bHelltowerPlayersInHell.Set( false );
  2693. m_bIsUsingSpells.Set( false );
  2694. m_bTruceActive.Set( false );
  2695. m_bTeamsSwitched.Set( false );
  2696. m_halloweenScenario.Set( HALLOWEEN_SCENARIO_NONE );
  2697. m_iGlobalAttributeCacheVersion = 0;
  2698. //=============================================================================
  2699. // HPE_BEGIN
  2700. // [msmith] HUD type
  2701. //=============================================================================
  2702. m_nHudType.Set( TF_HUDTYPE_UNDEFINED );
  2703. m_bIsInTraining.Set( false );
  2704. m_bAllowTrainingAchievements.Set( false );
  2705. m_bIsWaitingForTrainingContinue.Set( false );
  2706. //=============================================================================
  2707. // HPE_END
  2708. //=============================================================================
  2709. m_bIsTrainingHUDVisible.Set( false );
  2710. m_bIsInItemTestingMode.Set( false );
  2711. // Set turbo physics on. Do it here for now.
  2712. sv_turbophysics.SetValue( 1 );
  2713. // Initialize the team manager here, etc...
  2714. // If you hit these asserts its because you added or removed a weapon type
  2715. // and didn't also add or remove the weapon name or damage type from the
  2716. // arrays defined in tf_shareddefs.cpp
  2717. COMPILE_TIME_ASSERT( TF_WEAPON_COUNT == ARRAYSIZE( g_aWeaponDamageTypes ) );
  2718. COMPILE_TIME_ASSERT( TF_WEAPON_COUNT == ARRAYSIZE( g_aWeaponNames ) );
  2719. m_iPreviousRoundWinners = TEAM_UNASSIGNED;
  2720. m_pszTeamGoalStringRed.GetForModify()[0] = '\0';
  2721. m_pszTeamGoalStringBlue.GetForModify()[0] = '\0';
  2722. m_nStopWatchState.Set( STOPWATCH_CAPTURE_TIME_NOT_SET );
  2723. mp_tournament_redteamname.Revert();
  2724. mp_tournament_blueteamname.Revert();
  2725. m_flCapturePointEnableTime = 0.0f;
  2726. m_itHandle = 0;
  2727. m_hBirthdayPlayer = NULL;
  2728. m_nBossHealth = 0;
  2729. m_nMaxBossHealth = 0;
  2730. m_fBossNormalizedTravelDistance = 0.0f;
  2731. m_areHealthAndAmmoVectorsReady = false;
  2732. m_flGravityMultiplier.Set( 1.0 );
  2733. m_pszCustomUpgradesFile.GetForModify()[0] = '\0';
  2734. m_bShowMatchSummary.Set( false );
  2735. m_bMapHasMatchSummaryStage.Set( false );
  2736. m_bPlayersAreOnMatchSummaryStage.Set( false );
  2737. m_bUseMatchHUD = false;
  2738. m_bUsePreRoundDoors = false;
  2739. m_nMatchGroupType.Set( k_nMatchGroup_Invalid );
  2740. m_bMatchEnded.Set( true );
  2741. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  2742. {
  2743. m_ePlayerWantsRematch.Set( i, USER_NEXT_MAP_VOTE_UNDECIDED );
  2744. }
  2745. m_eRematchState = NEXT_MAP_VOTE_STATE_NONE;
  2746. #ifdef GAME_DLL
  2747. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  2748. if ( pMatch )
  2749. {
  2750. SyncMatchSettings();
  2751. }
  2752. for ( int i = 0; i < TF_TEAM_COUNT; i++ )
  2753. {
  2754. m_bHasSpawnedSoccerBall[i] = false;
  2755. }
  2756. m_flCheckPlayersConnectingTime = 0;
  2757. m_pUpgrades = NULL;
  2758. // Determine if a halloween event map is active
  2759. // Map active always turns on Halloween
  2760. {
  2761. char szCurrentMap[MAX_MAP_NAME];
  2762. Q_strncpy( szCurrentMap, STRING( gpGlobals->mapname ), sizeof( szCurrentMap ) );
  2763. if ( !Q_stricmp( szCurrentMap, "cp_manor_event" ) )
  2764. {
  2765. m_halloweenScenario.Set( HALLOWEEN_SCENARIO_MANN_MANOR );
  2766. }
  2767. else if ( !Q_stricmp( szCurrentMap, "koth_viaduct_event" ) )
  2768. {
  2769. m_halloweenScenario.Set( HALLOWEEN_SCENARIO_VIADUCT );
  2770. }
  2771. else if ( !Q_stricmp( szCurrentMap, "koth_lakeside_event" ) )
  2772. {
  2773. m_halloweenScenario.Set( HALLOWEEN_SCENARIO_LAKESIDE );
  2774. }
  2775. else if( !Q_stricmp( szCurrentMap, "plr_hightower_event" ) )
  2776. {
  2777. m_halloweenScenario.Set( HALLOWEEN_SCENARIO_HIGHTOWER );
  2778. }
  2779. else if ( !Q_stricmp( szCurrentMap, "sd_doomsday_event" ) )
  2780. {
  2781. m_halloweenScenario.Set( HALLOWEEN_SCENARIO_DOOMSDAY );
  2782. }
  2783. }
  2784. // Our hook for LoadMapCycleFile wont run during the base class constructor that does this initially
  2785. TrackWorkshopMapsInMapCycle();
  2786. #endif
  2787. }
  2788. #ifdef GAME_DLL
  2789. void CTFGameRules::Precache( void )
  2790. {
  2791. BaseClass::Precache();
  2792. // The Halloween bosses get spawned in code, so they don't get a chance to precache
  2793. // when the map loads. We'll do the precaching for them here.
  2794. if( IsHalloweenScenario( HALLOWEEN_SCENARIO_LAKESIDE ) )
  2795. {
  2796. CMerasmus::PrecacheMerasmus();
  2797. }
  2798. else if( IsHalloweenScenario( HALLOWEEN_SCENARIO_VIADUCT ) )
  2799. {
  2800. CEyeballBoss::PrecacheEyeballBoss();
  2801. }
  2802. else if( IsHalloweenScenario( HALLOWEEN_SCENARIO_MANN_MANOR ) )
  2803. {
  2804. CHeadlessHatman::PrecacheHeadlessHatman();
  2805. }
  2806. else if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  2807. {
  2808. CEyeballBoss::PrecacheEyeballBoss();
  2809. CGhost::PrecacheGhost();
  2810. }
  2811. else if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) )
  2812. {
  2813. CTFPlayer::PrecacheKart();
  2814. CGhost::PrecacheGhost();
  2815. CHeadlessHatman::PrecacheHeadlessHatman();
  2816. CMerasmus::PrecacheMerasmus();
  2817. }
  2818. if ( StringHasPrefix( STRING( gpGlobals->mapname ), "mvm_" ) )
  2819. {
  2820. CTFPlayer::PrecacheMvM();
  2821. }
  2822. CTFPlayer::m_bTFPlayerNeedsPrecache = true;
  2823. }
  2824. #endif
  2825. //-----------------------------------------------------------------------------
  2826. // Purpose:
  2827. //-----------------------------------------------------------------------------
  2828. //#ifdef GAME_DLL
  2829. //extern void AddHalloweenGiftPositionsForMap( const char *pszMapName, CUtlVector<Vector> &vLocations );
  2830. //#endif
  2831. void CTFGameRules::LevelInitPostEntity( void )
  2832. {
  2833. BaseClass::LevelInitPostEntity();
  2834. #ifdef GAME_DLL
  2835. // Refind our proxy, because we might have had it deleted due to a mapmaker placed one
  2836. m_hGamerulesProxy = dynamic_cast<CTFGameRulesProxy*>( gEntList.FindEntityByClassname( NULL, "tf_gamerules" ) );
  2837. // Halloween
  2838. // Flush Halloween Gift Location and grab locations if applicable. NonHalloween maps will have these as zero
  2839. m_halloweenGiftSpawnLocations.Purge();
  2840. if ( IsHolidayActive( kHoliday_Halloween ) )
  2841. {
  2842. for ( int i=0; i<IHalloweenGiftSpawnAutoList::AutoList().Count(); ++i )
  2843. {
  2844. CHalloweenGiftSpawnLocation* pGift = static_cast< CHalloweenGiftSpawnLocation* >( IHalloweenGiftSpawnAutoList::AutoList()[i] );
  2845. m_halloweenGiftSpawnLocations.AddToTail( pGift->GetAbsOrigin() );
  2846. UTIL_Remove( pGift );
  2847. }
  2848. // Ask Halloween System if there are any locations
  2849. AddHalloweenGiftPositionsForMap( STRING(gpGlobals->mapname), m_halloweenGiftSpawnLocations );
  2850. }
  2851. m_flMatchSummaryTeleportTime = -1.f;
  2852. //tagES
  2853. #ifdef STAGING_ONLY
  2854. tf_test_match_summary.SetValue( 0 );
  2855. #endif
  2856. const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  2857. if ( pMatchDesc )
  2858. {
  2859. pMatchDesc->InitGameRulesSettingsPostEntity();
  2860. }
  2861. #endif // GAME_DLL
  2862. }
  2863. //-----------------------------------------------------------------------------
  2864. // Purpose:
  2865. //-----------------------------------------------------------------------------
  2866. float CTFGameRules::GetRespawnTimeScalar( int iTeam )
  2867. {
  2868. // In PvE mode, we don't modify respawn times
  2869. if ( IsPVEModeActive() )
  2870. return 1.0;
  2871. return BaseClass::GetRespawnTimeScalar( iTeam );
  2872. }
  2873. //-----------------------------------------------------------------------------
  2874. // Purpose:
  2875. //-----------------------------------------------------------------------------
  2876. float CTFGameRules::GetRespawnWaveMaxLength( int iTeam, bool bScaleWithNumPlayers )
  2877. {
  2878. bool bScale = bScaleWithNumPlayers;
  2879. #ifdef TF_RAID_MODE
  2880. if ( IsRaidMode() )
  2881. {
  2882. return tf_raid_respawn_time.GetFloat();
  2883. }
  2884. #endif // TF_RAID_MODE
  2885. #ifdef TF_CREEP_MODE
  2886. if ( IsCreepWaveMode() )
  2887. {
  2888. return tf_creep_wave_player_respawn_time.GetFloat();
  2889. }
  2890. #endif
  2891. if ( IsMannVsMachineMode() )
  2892. {
  2893. bScale = false;
  2894. }
  2895. float flTime = BaseClass::GetRespawnWaveMaxLength( iTeam, bScale );
  2896. CTFRobotDestructionLogic* pRoboLogic = CTFRobotDestructionLogic::GetRobotDestructionLogic();
  2897. if ( pRoboLogic )
  2898. {
  2899. flTime *= ( 1.f - pRoboLogic->GetRespawnScaleForTeam( iTeam ) );
  2900. }
  2901. return flTime;
  2902. }
  2903. //-----------------------------------------------------------------------------
  2904. // Purpose:
  2905. //-----------------------------------------------------------------------------
  2906. bool CTFGameRules::FlagsMayBeCapped( void )
  2907. {
  2908. if ( State_Get() != GR_STATE_TEAM_WIN && State_Get() != GR_STATE_PREROUND )
  2909. return true;
  2910. return false;
  2911. }
  2912. //-----------------------------------------------------------------------------
  2913. // Purpose: Return which Halloween scenario is currently running
  2914. //-----------------------------------------------------------------------------
  2915. CTFGameRules::HalloweenScenarioType CTFGameRules::GetHalloweenScenario( void ) const
  2916. {
  2917. if ( !const_cast< CTFGameRules * >( this )->IsHolidayActive( kHoliday_Halloween ) )
  2918. return HALLOWEEN_SCENARIO_NONE;
  2919. return m_halloweenScenario;
  2920. }
  2921. //-----------------------------------------------------------------------------
  2922. bool CTFGameRules::IsUsingSpells( void ) const
  2923. {
  2924. if ( tf_spells_enabled.GetBool() )
  2925. return true;
  2926. if ( IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
  2927. return true;
  2928. return m_bIsUsingSpells;
  2929. }
  2930. //-----------------------------------------------------------------------------
  2931. bool CTFGameRules::IsUsingGrapplingHook( void ) const
  2932. {
  2933. return tf_grapplinghook_enable.GetBool();
  2934. }
  2935. //-----------------------------------------------------------------------------
  2936. bool CTFGameRules::IsTruceActive( void ) const
  2937. {
  2938. return m_bTruceActive;
  2939. }
  2940. //-----------------------------------------------------------------------------
  2941. bool CTFGameRules::CanInitiateDuels( void )
  2942. {
  2943. if ( IsInWaitingForPlayers() )
  2944. return false;
  2945. gamerules_roundstate_t roundState = State_Get();
  2946. if ( ( roundState != GR_STATE_RND_RUNNING ) && ( roundState != GR_STATE_PREROUND ) )
  2947. return false;
  2948. return true;
  2949. }
  2950. //-----------------------------------------------------------------------------
  2951. // Purpose:
  2952. //-----------------------------------------------------------------------------
  2953. int CTFGameRules::GetGameTeamForGCTeam( TF_GC_TEAM nGCTeam )
  2954. {
  2955. if ( nGCTeam == TF_GC_TEAM_INVADERS )
  2956. {
  2957. if ( IsCompetitiveMode() )
  2958. {
  2959. return ( m_bTeamsSwitched ) ? TF_TEAM_RED : TF_TEAM_BLUE;
  2960. }
  2961. return TF_TEAM_BLUE;
  2962. }
  2963. else if ( nGCTeam == TF_GC_TEAM_DEFENDERS )
  2964. {
  2965. if ( IsCompetitiveMode() )
  2966. {
  2967. return ( m_bTeamsSwitched ) ? TF_TEAM_BLUE : TF_TEAM_RED;
  2968. }
  2969. return TF_TEAM_RED;
  2970. }
  2971. return TEAM_UNASSIGNED;
  2972. }
  2973. //-----------------------------------------------------------------------------
  2974. // Purpose:
  2975. //-----------------------------------------------------------------------------
  2976. TF_GC_TEAM CTFGameRules::GetGCTeamForGameTeam( int nGameTeam )
  2977. {
  2978. if ( nGameTeam == TF_TEAM_BLUE )
  2979. {
  2980. if ( IsCompetitiveMode() )
  2981. {
  2982. return ( m_bTeamsSwitched ) ? TF_GC_TEAM_DEFENDERS : TF_GC_TEAM_INVADERS;
  2983. }
  2984. return TF_GC_TEAM_INVADERS;
  2985. }
  2986. else if ( nGameTeam == TF_TEAM_RED )
  2987. {
  2988. if ( IsCompetitiveMode() )
  2989. {
  2990. return ( m_bTeamsSwitched ) ? TF_GC_TEAM_INVADERS : TF_GC_TEAM_DEFENDERS;
  2991. }
  2992. return TF_GC_TEAM_DEFENDERS;
  2993. }
  2994. return TF_GC_TEAM_NOTEAM;
  2995. }
  2996. CTFGameRules::EUserNextMapVote CTFGameRules::GetWinningVote( int (&nVotes)[ EUserNextMapVote::NUM_VOTE_STATES ] ) const
  2997. {
  2998. // We assume "undecided" is the index just after the last vote option
  2999. COMPILE_TIME_ASSERT( USER_NEXT_MAP_VOTE_UNDECIDED == NEXT_MAP_VOTE_OPTIONS );
  3000. memset( nVotes, 0, sizeof( nVotes ) );
  3001. int nTotalPlayers = 0;
  3002. // Tally up votes.
  3003. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  3004. {
  3005. #ifdef CLIENT_DLL
  3006. if ( !g_PR || !g_PR->IsConnected( iPlayerIndex ) )
  3007. continue;
  3008. #else // GAME_DLL
  3009. // We care about those that are still here. If you leave, you don't count towards the vote total
  3010. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  3011. if ( !pTFPlayer || pTFPlayer->IsBot() )
  3012. continue;
  3013. if ( !pTFPlayer->IsConnected() )
  3014. continue;
  3015. CSteamID steamID;
  3016. pTFPlayer->GetSteamID( &steamID );
  3017. // People without parties *should* be getting a new one soon. Count them as undecided
  3018. // until their party shows up and they're allowed to make a real vote.
  3019. CTFParty* pParty = GTFGCClientSystem()->GetPartyForPlayer( steamID );
  3020. if ( !pParty )
  3021. {
  3022. ++nVotes[ EUserNextMapVote::USER_NEXT_MAP_VOTE_UNDECIDED ];
  3023. ++nTotalPlayers;
  3024. continue;
  3025. }
  3026. const CMatchInfo* pMatch = GTFGCClientSystem()->GetMatch();
  3027. Assert( pMatch );
  3028. if ( !pMatch )
  3029. {
  3030. continue;
  3031. }
  3032. // Need to be a match players
  3033. const CMatchInfo::PlayerMatchData_t* pPlayerMatchData = pMatch->GetMatchDataForPlayer( steamID );
  3034. Assert( pPlayerMatchData );
  3035. if ( !pPlayerMatchData )
  3036. {
  3037. // How'd you get here?
  3038. continue;
  3039. }
  3040. #endif
  3041. nTotalPlayers++;
  3042. nVotes[ TFGameRules()->PlayerNextMapVoteState( iPlayerIndex ) ]++;
  3043. }
  3044. if ( nVotes[ USER_NEXT_MAP_VOTE_UNDECIDED ] == nTotalPlayers )
  3045. {
  3046. return USER_NEXT_MAP_VOTE_UNDECIDED;
  3047. }
  3048. else
  3049. {
  3050. EUserNextMapVote eWinningVote = USER_NEXT_MAP_VOTE_MAP_0;
  3051. for( int i = 0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
  3052. {
  3053. // The current map is in slot 0. >= so we favor change.
  3054. eWinningVote = nVotes[ i ] >= nVotes[ eWinningVote ]
  3055. ? (EUserNextMapVote)i
  3056. : eWinningVote;
  3057. }
  3058. return eWinningVote;
  3059. }
  3060. }
  3061. #ifdef GAME_DLL
  3062. void CTFGameRules::UpdateNextMapVoteOptionsFromLobby()
  3063. {
  3064. for( int i = 0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
  3065. {
  3066. m_nNextMapVoteOptions.Set( i, GTFGCClientSystem()->GetNextMapVoteByIndex( i )->m_nDefIndex );
  3067. }
  3068. }
  3069. void CTFGameRules::KickPlayersNewMatchIDRequestFailed()
  3070. {
  3071. Assert( m_eRematchState == NEXT_MAP_VOTE_STATE_MAP_CHOSEN_PAUSE );
  3072. // Let everyone know the rematch failed.
  3073. if ( m_eRematchState == NEXT_MAP_VOTE_STATE_MAP_CHOSEN_PAUSE )
  3074. {
  3075. CBroadcastRecipientFilter filter;
  3076. UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, "#TF_Matchmaking_RollingQueue_NewRematch_GCFail" );
  3077. IGameEvent *pEvent = gameeventmanager->CreateEvent( "rematch_failed_to_create" );
  3078. if ( pEvent )
  3079. {
  3080. gameeventmanager->FireEvent( pEvent );
  3081. }
  3082. }
  3083. // The GC failed to get a new MatchID for us. Let's clear out and reset.
  3084. engine->ServerCommand( "kickall #TF_Competitive_Disconnect\n" );
  3085. // Tell the GC System to end the managed match mode -- we skipped this in StopCompetitiveMatch so we could roll the
  3086. // managed match into a new one.
  3087. Assert( !IsManagedMatchEnded() );
  3088. if ( !IsManagedMatchEnded() )
  3089. {
  3090. GTFGCClientSystem()->EndManagedMatch( /* bKickPlayersToParties */ false );
  3091. Assert( IsManagedMatchEnded() );
  3092. m_bMatchEnded.Set( true );
  3093. }
  3094. // Prepare for next match
  3095. g_fGameOver = false;
  3096. m_bAllowBetweenRounds = true;
  3097. State_Transition( GR_STATE_RESTART );
  3098. SetInWaitingForPlayers( true );
  3099. }
  3100. //-----------------------------------------------------------------------------
  3101. void CTFGameRules::CheckAndSetPartyLeader( CTFPlayer *pTFPlayer, int iTeam )
  3102. {
  3103. if ( !pTFPlayer )
  3104. return;
  3105. Assert( iTeam >= FIRST_GAME_TEAM );
  3106. if ( iTeam < FIRST_GAME_TEAM )
  3107. return;
  3108. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  3109. if ( !pMatch )
  3110. return;
  3111. CSteamID steamID;
  3112. if ( !pTFPlayer->GetSteamID( &steamID ) )
  3113. return;
  3114. // TODO: Whenever a lobby is updated, look at the CTFLobbyMembers and see if
  3115. // everyone has the same partyID and then set whoever is the leader to
  3116. // have their name be the team name
  3117. //CMatchInfo::PlayerMatchData_t *pMatchPlayer = pMatch->GetMatchDataForPlayer( steamID );
  3118. //if ( pMatchPlayer && pMatchPlayer->bPremadeLeader )
  3119. //{
  3120. // CTFPlayerResource *pResource = dynamic_cast< CTFPlayerResource* >( g_pPlayerResource );
  3121. // if ( pResource )
  3122. // {
  3123. // pResource->SetPartyLeaderIndex( iTeam, pTFPlayer->entindex() );
  3124. // }
  3125. //}
  3126. }
  3127. //-----------------------------------------------------------------------------
  3128. // Purpose: Sets current boss victim
  3129. //-----------------------------------------------------------------------------
  3130. void CTFGameRules::SetIT( CBaseEntity *who )
  3131. {
  3132. if ( IsHolidayActive( kHoliday_Halloween ) && !IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) )
  3133. {
  3134. CTFPlayer* newIT = ToTFPlayer( who );
  3135. if ( newIT && newIT != m_itHandle.Get() )
  3136. {
  3137. // new IT victim - warn them
  3138. ClientPrint( newIT, HUD_PRINTTALK, "#TF_HALLOWEEN_BOSS_WARN_VICTIM", newIT->GetPlayerName() );
  3139. ClientPrint( newIT, HUD_PRINTCENTER, "#TF_HALLOWEEN_BOSS_WARN_VICTIM", newIT->GetPlayerName() );
  3140. CSingleUserReliableRecipientFilter filter( newIT );
  3141. newIT->EmitSound( filter, newIT->entindex(), "Player.YouAreIT" );
  3142. // force them to scream when they become it
  3143. newIT->EmitSound( "Halloween.PlayerScream" );
  3144. }
  3145. CTFPlayer *oldIT = ToTFPlayer( m_itHandle );
  3146. if ( oldIT && oldIT != who && oldIT->IsAlive() )
  3147. {
  3148. // tell old IT player they are safe
  3149. CSingleUserReliableRecipientFilter filter( oldIT );
  3150. oldIT->EmitSound( filter, oldIT->entindex(), "Player.TaggedOtherIT" );
  3151. ClientPrint( oldIT, HUD_PRINTTALK, "#TF_HALLOWEEN_BOSS_LOST_AGGRO" );
  3152. ClientPrint( oldIT, HUD_PRINTCENTER, "#TF_HALLOWEEN_BOSS_LOST_AGGRO" );
  3153. }
  3154. }
  3155. m_itHandle = who;
  3156. }
  3157. //-----------------------------------------------------------------------------
  3158. // Purpose: Sets current birthday player
  3159. //-----------------------------------------------------------------------------
  3160. void CTFGameRules::SetBirthdayPlayer( CBaseEntity *pEntity )
  3161. {
  3162. /*
  3163. if ( IsBirthday() )
  3164. {
  3165. if ( pEntity && pEntity->IsPlayer() && pEntity != m_hBirthdayPlayer.Get() )
  3166. {
  3167. CTFPlayer *pTFPlayer = ToTFPlayer( pEntity );
  3168. if ( pTFPlayer )
  3169. {
  3170. // new IT victim - warn them
  3171. // ClientPrint( pTFPlayer, HUD_PRINTTALK, "#TF_HALLOWEEN_BOSS_WARN_VICTIM", player->GetPlayerName() );
  3172. // ClientPrint( pTFPlayer, HUD_PRINTCENTER, "#TF_HALLOWEEN_BOSS_WARN_VICTIM", player->GetPlayerName() );
  3173. CSingleUserReliableRecipientFilter filter( pTFPlayer );
  3174. pTFPlayer->EmitSound( filter, pTFPlayer->entindex(), "Game.HappyBirthday" );
  3175. // force them to scream when they become it
  3176. // pTFPlayer->EmitSound( "Halloween.PlayerScream" );
  3177. }
  3178. }
  3179. // CTFPlayer *oldIT = ToTFPlayer( m_itHandle );
  3180. //
  3181. // if ( oldIT && oldIT != who && oldIT->IsAlive() )
  3182. // {
  3183. // // tell old IT player they are safe
  3184. // CSingleUserReliableRecipientFilter filter( oldIT );
  3185. // oldIT->EmitSound( filter, oldIT->entindex(), "Player.TaggedOtherIT" );
  3186. //
  3187. // ClientPrint( oldIT, HUD_PRINTTALK, "#TF_HALLOWEEN_BOSS_LOST_AGGRO" );
  3188. // ClientPrint( oldIT, HUD_PRINTCENTER, "#TF_HALLOWEEN_BOSS_LOST_AGGRO" );
  3189. // }
  3190. m_hBirthdayPlayer = pEntity;
  3191. }
  3192. else
  3193. {
  3194. m_hBirthdayPlayer = NULL;
  3195. }
  3196. */
  3197. }
  3198. #ifdef GAME_DLL
  3199. //-----------------------------------------------------------------------------
  3200. // Purpose: remove all projectiles in the world
  3201. //-----------------------------------------------------------------------------
  3202. void CTFGameRules::RemoveAllProjectiles()
  3203. {
  3204. for ( int i=0; i<IBaseProjectileAutoList::AutoList().Count(); ++i )
  3205. {
  3206. UTIL_Remove( static_cast< CBaseProjectile* >( IBaseProjectileAutoList::AutoList()[i] ) );
  3207. }
  3208. }
  3209. //-----------------------------------------------------------------------------
  3210. // Purpose: remove all buildings in the world
  3211. //-----------------------------------------------------------------------------
  3212. void CTFGameRules::RemoveAllBuildings( bool bExplodeBuildings /*= false*/ )
  3213. {
  3214. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  3215. {
  3216. CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  3217. if ( !pObj->IsMapPlaced() )
  3218. {
  3219. // this is separate from the object_destroyed event, which does
  3220. // not get sent when we remove the objects from the world
  3221. IGameEvent *event = gameeventmanager->CreateEvent( "object_removed" );
  3222. if ( event )
  3223. {
  3224. CTFPlayer *pOwner = pObj->GetOwner();
  3225. event->SetInt( "userid", pOwner ? pOwner->GetUserID() : -1 ); // user ID of the object owner
  3226. event->SetInt( "objecttype", pObj->GetType() ); // type of object removed
  3227. event->SetInt( "index", pObj->entindex() ); // index of the object removed
  3228. gameeventmanager->FireEvent( event );
  3229. }
  3230. if ( bExplodeBuildings )
  3231. {
  3232. pObj->DetonateObject();
  3233. }
  3234. else
  3235. {
  3236. // This fixes a bug in Raid mode where we could spawn where our sentry was but
  3237. // we didn't get the weapons because they couldn't trace to us in FVisible
  3238. pObj->SetSolid( SOLID_NONE );
  3239. UTIL_Remove( pObj );
  3240. }
  3241. }
  3242. }
  3243. }
  3244. //-----------------------------------------------------------------------------
  3245. // Purpose: remove all sentries ammo
  3246. //-----------------------------------------------------------------------------
  3247. void CTFGameRules::RemoveAllSentriesAmmo()
  3248. {
  3249. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  3250. {
  3251. CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  3252. if ( pObj->GetType() == OBJ_SENTRYGUN )
  3253. {
  3254. CObjectSentrygun *pSentry = assert_cast< CObjectSentrygun* >( pObj );
  3255. pSentry->RemoveAllAmmo();
  3256. }
  3257. }
  3258. }
  3259. //-----------------------------------------------------------------------------
  3260. // Purpose: Removes all projectiles and buildings from world
  3261. //-----------------------------------------------------------------------------
  3262. void CTFGameRules::RemoveAllProjectilesAndBuildings( bool bExplodeBuildings /*= false*/ )
  3263. {
  3264. RemoveAllProjectiles();
  3265. RemoveAllBuildings( bExplodeBuildings );
  3266. }
  3267. #endif // GAME_DLL
  3268. //-----------------------------------------------------------------------------
  3269. // Purpose: Determines whether we should allow mp_timelimit to trigger a map change
  3270. //-----------------------------------------------------------------------------
  3271. bool CTFGameRules::CanChangelevelBecauseOfTimeLimit( void )
  3272. {
  3273. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  3274. // we only want to deny a map change triggered by mp_timelimit if we're not forcing a map reset,
  3275. // we're playing mini-rounds, and the master says we need to play all of them before changing (for maps like Dustbowl)
  3276. if ( !m_bForceMapReset && pMaster && pMaster->PlayingMiniRounds() && pMaster->ShouldPlayAllControlPointRounds() )
  3277. {
  3278. if ( pMaster->NumPlayableControlPointRounds() > 0 )
  3279. {
  3280. return false;
  3281. }
  3282. }
  3283. return true;
  3284. }
  3285. //-----------------------------------------------------------------------------
  3286. // Purpose:
  3287. //-----------------------------------------------------------------------------
  3288. bool CTFGameRules::CanGoToStalemate( void )
  3289. {
  3290. // In CTF, don't go to stalemate if one of the flags isn't at home
  3291. if ( m_nGameType == TF_GAMETYPE_CTF )
  3292. {
  3293. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  3294. {
  3295. CCaptureFlag *pFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
  3296. if ( pFlag->IsDropped() || pFlag->IsStolen() )
  3297. return false;
  3298. }
  3299. // check that one team hasn't won by capping
  3300. if ( CheckCapsPerRound() )
  3301. return false;
  3302. }
  3303. return BaseClass::CanGoToStalemate();
  3304. }
  3305. //-----------------------------------------------------------------------------
  3306. // Purpose:
  3307. //-----------------------------------------------------------------------------
  3308. void CTFGameRules::RestoreActiveTimer( void )
  3309. {
  3310. BaseClass::RestoreActiveTimer();
  3311. if ( IsInKothMode() )
  3312. {
  3313. CTeamRoundTimer *pTimer = GetBlueKothRoundTimer();
  3314. if ( pTimer )
  3315. {
  3316. pTimer->SetShowInHud( true );
  3317. }
  3318. pTimer = GetRedKothRoundTimer();
  3319. if ( pTimer )
  3320. {
  3321. pTimer->SetShowInHud( true );
  3322. }
  3323. }
  3324. }
  3325. // Classnames of entities that are preserved across round restarts
  3326. static const char *s_PreserveEnts[] =
  3327. {
  3328. "tf_gamerules",
  3329. "tf_team_manager",
  3330. "tf_player_manager",
  3331. "tf_team",
  3332. "tf_objective_resource",
  3333. "keyframe_rope",
  3334. "move_rope",
  3335. "tf_viewmodel",
  3336. "tf_logic_training",
  3337. "tf_logic_training_mode",
  3338. #ifdef TF_RAID_MODE
  3339. "tf_logic_raid",
  3340. #endif // TF_RAID_MODE
  3341. "tf_powerup_bottle",
  3342. "tf_mann_vs_machine_stats",
  3343. "tf_wearable",
  3344. "tf_wearable_demoshield",
  3345. "tf_wearable_robot_arm",
  3346. "tf_wearable_vm",
  3347. "tf_logic_bonusround",
  3348. "vote_controller",
  3349. "monster_resource",
  3350. "tf_logic_medieval",
  3351. "tf_logic_cp_timer",
  3352. "tf_logic_tower_defense", // legacy
  3353. "tf_logic_mann_vs_machine",
  3354. "func_upgradestation",
  3355. "entity_rocket",
  3356. "entity_carrier",
  3357. "entity_sign",
  3358. "entity_saucer",
  3359. "tf_halloween_gift_pickup",
  3360. "tf_logic_competitive",
  3361. "", // END Marker
  3362. };
  3363. //-----------------------------------------------------------------------------
  3364. // Purpose:
  3365. //-----------------------------------------------------------------------------
  3366. void CTFGameRules::Activate()
  3367. {
  3368. m_nGameType.Set( TF_GAMETYPE_UNDEFINED );
  3369. tf_gamemode_arena.SetValue( 0 );
  3370. tf_gamemode_cp.SetValue( 0 );
  3371. tf_gamemode_ctf.SetValue( 0 );
  3372. tf_gamemode_sd.SetValue( 0 );
  3373. tf_gamemode_payload.SetValue( 0 );
  3374. tf_gamemode_mvm.SetValue( 0 );
  3375. tf_gamemode_rd.SetValue( 0 );
  3376. tf_gamemode_pd.SetValue( 0 );
  3377. tf_gamemode_tc.SetValue( 0 );
  3378. tf_beta_content.SetValue( 0 );
  3379. tf_gamemode_passtime.SetValue( 0 );
  3380. tf_gamemode_misc.SetValue( 0 );
  3381. tf_bot_count.SetValue( 0 );
  3382. #ifdef TF_RAID_MODE
  3383. tf_gamemode_raid.SetValue( 0 );
  3384. tf_gamemode_boss_battle.SetValue( 0 );
  3385. #endif
  3386. m_bPlayingMannVsMachine.Set( false );
  3387. m_bBountyModeEnabled.Set( false );
  3388. m_nCurrencyAccumulator = 0;
  3389. m_iCurrencyPool = 0;
  3390. m_bMannVsMachineAlarmStatus.Set( false );
  3391. m_bPlayingKoth.Set( false );
  3392. m_bPlayingMedieval.Set( false );
  3393. m_bPlayingHybrid_CTF_CP.Set( false );
  3394. m_bPlayingSpecialDeliveryMode.Set( false );
  3395. m_bPlayingRobotDestructionMode.Set( false );
  3396. m_bPowerupMode.Set( false );
  3397. m_redPayloadToPush = NULL;
  3398. m_bluePayloadToPush = NULL;
  3399. m_redPayloadToBlock = NULL;
  3400. m_bluePayloadToBlock = NULL;
  3401. m_zombieMobTimer.Invalidate();
  3402. m_zombiesLeftToSpawn = 0;
  3403. m_CPTimerEnts.RemoveAll();
  3404. m_nMapHolidayType.Set( kHoliday_None );
  3405. CArenaLogic *pArenaLogic = dynamic_cast< CArenaLogic * > (gEntList.FindEntityByClassname( NULL, "tf_logic_arena" ) );
  3406. if ( pArenaLogic != NULL )
  3407. {
  3408. m_hArenaEntity = pArenaLogic;
  3409. m_nGameType.Set( TF_GAMETYPE_ARENA );
  3410. tf_gamemode_arena.SetValue( 1 );
  3411. Msg( "Executing server arena config file\n" );
  3412. engine->ServerCommand( "exec config_arena.cfg\n" );
  3413. }
  3414. #ifdef TF_RAID_MODE
  3415. CRaidLogic *pRaidLogic = dynamic_cast< CRaidLogic * >( gEntList.FindEntityByClassname( NULL, "tf_logic_raid" ) );
  3416. if ( pRaidLogic )
  3417. {
  3418. m_hRaidLogic = pRaidLogic;
  3419. tf_gamemode_raid.SetValue( 1 );
  3420. Msg( "Executing server raid game mode config file\n" );
  3421. engine->ServerCommand( "exec config_raid.cfg\n" );
  3422. }
  3423. CBossBattleLogic *pBossBattleLogic = dynamic_cast< CBossBattleLogic * >( gEntList.FindEntityByClassname( NULL, "tf_logic_boss_battle" ) );
  3424. if ( pBossBattleLogic )
  3425. {
  3426. m_hBossBattleLogic = pBossBattleLogic;
  3427. tf_gamemode_boss_battle.SetValue( 1 );
  3428. }
  3429. #endif // TF_RAID_MODE
  3430. // This is beta content if this map has "beta" as a tag in the schema
  3431. {
  3432. const MapDef_t* pMap = GetItemSchema()->GetMasterMapDefByName( STRING( gpGlobals->mapname ) );
  3433. if ( pMap && pMap->vecTags.HasElement( GetItemSchema()->GetHandleForTag( "beta" ) ) )
  3434. {
  3435. tf_beta_content.SetValue( 1 );
  3436. }
  3437. }
  3438. if ( !Q_strncmp( STRING( gpGlobals->mapname ), "tc_", 3 ) )
  3439. {
  3440. tf_gamemode_tc.SetValue( 1 );
  3441. }
  3442. CMannVsMachineLogic *pMannVsMachineLogic = dynamic_cast< CMannVsMachineLogic * >( gEntList.FindEntityByClassname( NULL, "tf_logic_mann_vs_machine" ) );
  3443. CTeamTrainWatcher *pTrainWatch = dynamic_cast<CTeamTrainWatcher*> ( gEntList.FindEntityByClassname( NULL, "team_train_watcher" ) );
  3444. bool bFlag = ICaptureFlagAutoList::AutoList().Count() > 0;
  3445. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  3446. {
  3447. m_bPlayingRobotDestructionMode.Set( true );
  3448. if ( CTFRobotDestructionLogic::GetRobotDestructionLogic()->GetType() == CTFRobotDestructionLogic::TYPE_ROBOT_DESTRUCTION )
  3449. {
  3450. tf_gamemode_rd.SetValue( 1 );
  3451. m_nGameType.Set( TF_GAMETYPE_RD );
  3452. tf_beta_content.SetValue( 1 );
  3453. }
  3454. else
  3455. {
  3456. tf_gamemode_pd.SetValue( 1 );
  3457. m_nGameType.Set( TF_GAMETYPE_PD );
  3458. }
  3459. }
  3460. else if ( pMannVsMachineLogic )
  3461. {
  3462. m_bPlayingMannVsMachine.Set( true );
  3463. tf_gamemode_mvm.SetValue( 1 );
  3464. m_nGameType.Set( TF_GAMETYPE_MVM );
  3465. }
  3466. else if ( StringHasPrefix( STRING( gpGlobals->mapname ), "sd_" ) )
  3467. {
  3468. m_bPlayingSpecialDeliveryMode.Set( true );
  3469. tf_gamemode_sd.SetValue( 1 );
  3470. }
  3471. else if ( bFlag && !CTFRobotDestructionLogic::GetRobotDestructionLogic() )
  3472. {
  3473. m_nGameType.Set( TF_GAMETYPE_CTF );
  3474. tf_gamemode_ctf.SetValue( 1 );
  3475. }
  3476. else if ( pTrainWatch )
  3477. {
  3478. m_nGameType.Set( TF_GAMETYPE_ESCORT );
  3479. tf_gamemode_payload.SetValue( 1 );
  3480. CMultipleEscort *pMultipleEscort = dynamic_cast<CMultipleEscort*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_multiple_escort" ) );
  3481. SetMultipleTrains( pMultipleEscort != NULL );
  3482. }
  3483. else if ( g_hControlPointMasters.Count() && m_nGameType != TF_GAMETYPE_ARENA ) // We have cap points in arena but we're not CP
  3484. {
  3485. m_nGameType.Set( TF_GAMETYPE_CP );
  3486. tf_gamemode_cp.SetValue( 1 );
  3487. }
  3488. auto *pPasstime = dynamic_cast<CTFPasstimeLogic*> ( gEntList.FindEntityByClassname( NULL, "passtime_logic" ) );
  3489. if ( pPasstime )
  3490. {
  3491. m_nGameType.Set( TF_GAMETYPE_PASSTIME );
  3492. tf_gamemode_passtime.SetValue( 1 );
  3493. }
  3494. // the game is in training mode if this entity is found
  3495. m_hTrainingModeLogic = dynamic_cast< CTrainingModeLogic * > ( gEntList.FindEntityByClassname( NULL, "tf_logic_training_mode" ) );
  3496. if ( NULL != m_hTrainingModeLogic )
  3497. {
  3498. m_bIsInTraining.Set( true );
  3499. m_bAllowTrainingAchievements.Set( false );
  3500. mp_humans_must_join_team.SetValue( "blue" );
  3501. m_bIsTrainingHUDVisible.Set( true );
  3502. tf_training_client_message.SetValue( (int)TRAINING_CLIENT_MESSAGE_NONE );
  3503. }
  3504. m_bIsInItemTestingMode.Set( false );
  3505. CKothLogic *pKoth = dynamic_cast<CKothLogic*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_koth" ) );
  3506. if ( pKoth )
  3507. {
  3508. m_bPlayingKoth.Set( true );
  3509. }
  3510. CMedievalLogic *pMedieval = dynamic_cast<CMedievalLogic*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_medieval" ) );
  3511. if ( pMedieval || tf_medieval.GetBool() )
  3512. {
  3513. m_bPlayingMedieval.Set( true );
  3514. }
  3515. CCompetitiveLogic *pCompLogic = dynamic_cast< CCompetitiveLogic* > ( gEntList.FindEntityByClassname( NULL, "tf_logic_competitive" ) );
  3516. if ( pCompLogic )
  3517. {
  3518. m_hCompetitiveLogicEntity = pCompLogic;
  3519. }
  3520. CHybridMap_CTF_CP *pHybridMap_CTF_CP = dynamic_cast<CHybridMap_CTF_CP*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_hybrid_ctf_cp" ) );
  3521. if ( pHybridMap_CTF_CP )
  3522. {
  3523. m_bPlayingHybrid_CTF_CP.Set( true );
  3524. }
  3525. CTFHolidayEntity *pHolidayEntity = dynamic_cast<CTFHolidayEntity*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_holiday" ) );
  3526. if ( pHolidayEntity )
  3527. {
  3528. m_nMapHolidayType.Set( pHolidayEntity->GetHolidayType() );
  3529. }
  3530. // bot roster
  3531. m_hBlueBotRoster = NULL;
  3532. m_hRedBotRoster = NULL;
  3533. CHandle<CTFBotRoster> hBotRoster = dynamic_cast< CTFBotRoster* >( gEntList.FindEntityByClassname( NULL, "bot_roster" ) );
  3534. while ( hBotRoster != NULL )
  3535. {
  3536. if ( FStrEq( hBotRoster->m_teamName.ToCStr(), "blue" ) )
  3537. {
  3538. m_hBlueBotRoster = hBotRoster;
  3539. }
  3540. else if ( FStrEq( hBotRoster->m_teamName.ToCStr(), "red" ) )
  3541. {
  3542. m_hRedBotRoster = hBotRoster;
  3543. }
  3544. else
  3545. {
  3546. if ( m_hBlueBotRoster == NULL )
  3547. {
  3548. m_hBlueBotRoster = hBotRoster;
  3549. }
  3550. if ( m_hRedBotRoster == NULL )
  3551. {
  3552. m_hRedBotRoster = hBotRoster;
  3553. }
  3554. }
  3555. hBotRoster = dynamic_cast< CTFBotRoster* >( gEntList.FindEntityByClassname( hBotRoster, "bot_roster" ) );
  3556. }
  3557. CHandle<CCPTimerLogic> hCPTimer = dynamic_cast< CCPTimerLogic* >( gEntList.FindEntityByClassname( NULL, "tf_logic_cp_timer" ) );
  3558. while ( hCPTimer != NULL )
  3559. {
  3560. m_CPTimerEnts.AddToTail( hCPTimer );
  3561. hCPTimer = dynamic_cast< CCPTimerLogic* >( gEntList.FindEntityByClassname( hCPTimer, "tf_logic_cp_timer" ) );
  3562. }
  3563. // hide from the master server if this game is a training game
  3564. // or offline practice
  3565. if ( IsInTraining() || TheTFBots().IsInOfflinePractice() || IsInItemTestingMode() )
  3566. {
  3567. hide_server.SetValue( true );
  3568. }
  3569. m_bVoteCalled = false;
  3570. m_bServerVoteOnReset = false;
  3571. m_flVoteCheckThrottle = 0;
  3572. #ifdef STAGING_ONLY
  3573. // Dynamically create an upgrade entity outside MvM
  3574. if ( tf_bountymode.GetBool() && !IsBountyMode() )
  3575. {
  3576. SetBountyMode( true );
  3577. }
  3578. #endif // STAGING_ONLY
  3579. if ( tf_powerup_mode.GetBool() )
  3580. {
  3581. if ( !IsPowerupMode() )
  3582. {
  3583. SetPowerupMode( true );
  3584. }
  3585. }
  3586. // if ( !IsInTournamentMode() )
  3587. // {
  3588. // CExtraMapEntity::SpawnExtraModel();
  3589. // }
  3590. // If leaving MvM for any other game mode, clean up any sticky UI/state
  3591. if ( IsInTournamentMode() && m_nGameType != TF_GAMETYPE_MVM && g_TFGameModeHistory.GetPrevState() == TF_GAMETYPE_MVM )
  3592. {
  3593. mp_tournament.SetValue( false );
  3594. }
  3595. if ( GameModeUsesUpgrades() && g_pPopulationManager == NULL )
  3596. {
  3597. (CPopulationManager *)CreateEntityByName( "info_populator" );
  3598. }
  3599. if ( tf_gamemode_tc.GetBool() || tf_gamemode_sd.GetBool() || tf_gamemode_pd.GetBool() || m_bPlayingMedieval )
  3600. {
  3601. tf_gamemode_misc.SetValue( 1 );
  3602. }
  3603. CBaseEntity *pStageLogic = gEntList.FindEntityByName( NULL, "competitive_stage_logic_case" );
  3604. if ( pStageLogic )
  3605. {
  3606. m_bMapHasMatchSummaryStage.Set( true );
  3607. }
  3608. m_bCompetitiveMode.Set( false );
  3609. const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  3610. if ( pMatchDesc )
  3611. {
  3612. pMatchDesc->InitGameRulesSettings();
  3613. }
  3614. CLogicMannPower *pLogicMannPower = dynamic_cast< CLogicMannPower* > ( gEntList.FindEntityByClassname( NULL, "tf_logic_mannpower" ) );
  3615. tf_powerup_mode.SetValue( pLogicMannPower ? 1 : 0 );
  3616. }
  3617. //-----------------------------------------------------------------------------
  3618. // Purpose:
  3619. //-----------------------------------------------------------------------------
  3620. bool CTFGameRules::AllowDamage( CBaseEntity *pVictim, const CTakeDamageInfo &info )
  3621. {
  3622. bool bRetVal = true;
  3623. if ( ( State_Get() == GR_STATE_TEAM_WIN ) && pVictim )
  3624. {
  3625. if ( pVictim->GetTeamNumber() == GetWinningTeam() )
  3626. {
  3627. CBaseTrigger *pTrigger = dynamic_cast< CBaseTrigger *>( info.GetInflictor() );
  3628. // we don't want players on the winning team to be
  3629. // hurt by team-specific trigger_hurt entities during the bonus time
  3630. if ( pTrigger && pTrigger->UsesFilter() )
  3631. {
  3632. bRetVal = false;
  3633. }
  3634. }
  3635. }
  3636. return bRetVal;
  3637. }
  3638. //-----------------------------------------------------------------------------
  3639. // Purpose:
  3640. //-----------------------------------------------------------------------------
  3641. void CTFGameRules::SetTeamGoalString( int iTeam, const char *pszGoal )
  3642. {
  3643. if ( iTeam == TF_TEAM_RED )
  3644. {
  3645. if ( !pszGoal || !pszGoal[0] )
  3646. {
  3647. m_pszTeamGoalStringRed.GetForModify()[0] = '\0';
  3648. }
  3649. else
  3650. {
  3651. if ( Q_stricmp( m_pszTeamGoalStringRed.Get(), pszGoal ) )
  3652. {
  3653. Q_strncpy( m_pszTeamGoalStringRed.GetForModify(), pszGoal, MAX_TEAMGOAL_STRING );
  3654. }
  3655. }
  3656. }
  3657. else if ( iTeam == TF_TEAM_BLUE )
  3658. {
  3659. if ( !pszGoal || !pszGoal[0] )
  3660. {
  3661. m_pszTeamGoalStringBlue.GetForModify()[0] = '\0';
  3662. }
  3663. else
  3664. {
  3665. if ( Q_stricmp( m_pszTeamGoalStringBlue.Get(), pszGoal ) )
  3666. {
  3667. Q_strncpy( m_pszTeamGoalStringBlue.GetForModify(), pszGoal, MAX_TEAMGOAL_STRING );
  3668. }
  3669. }
  3670. }
  3671. }
  3672. //=============================================================================
  3673. // HPE_BEGIN:
  3674. // [msmith] Added a HUD type so that we can have the hud independent from the
  3675. // game type. This is useful in training where we want a training hud
  3676. // Instead of the other types of HUD.
  3677. //=============================================================================
  3678. //-----------------------------------------------------------------------------
  3679. // Purpose: Set a HUD type.
  3680. //-----------------------------------------------------------------------------
  3681. void CTFGameRules::SetHUDType( int nHudType )
  3682. {
  3683. if ( nHudType != TF_HUDTYPE_ARENA )
  3684. {
  3685. if ( nHudType >= TF_HUDTYPE_UNDEFINED && nHudType <= TF_HUDTYPE_TRAINING )
  3686. {
  3687. m_nHudType.Set( nHudType );
  3688. }
  3689. }
  3690. }
  3691. //=============================================================================
  3692. // HPE_END
  3693. //=============================================================================
  3694. //-----------------------------------------------------------------------------
  3695. // Purpose:
  3696. //-----------------------------------------------------------------------------
  3697. bool CTFGameRules::RoundCleanupShouldIgnore( CBaseEntity *pEnt )
  3698. {
  3699. if ( FindInList( s_PreserveEnts, pEnt->GetClassname() ) )
  3700. return true;
  3701. //There has got to be a better way of doing this.
  3702. if ( Q_strstr( pEnt->GetClassname(), "tf_weapon_" ) )
  3703. return true;
  3704. return BaseClass::RoundCleanupShouldIgnore( pEnt );
  3705. }
  3706. //-----------------------------------------------------------------------------
  3707. // Purpose:
  3708. //-----------------------------------------------------------------------------
  3709. bool CTFGameRules::ShouldCreateEntity( const char *pszClassName )
  3710. {
  3711. if ( FindInList( s_PreserveEnts, pszClassName ) )
  3712. return false;
  3713. return BaseClass::ShouldCreateEntity( pszClassName );
  3714. }
  3715. const char* CTFGameRules::GetStalemateSong( int nTeam )
  3716. {
  3717. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  3718. {
  3719. return (nTeam == TF_TEAM_RED)
  3720. ? "Announcer.Helltower_Hell_Red_Stalemate"
  3721. : "Announcer.Helltower_Hell_Blue_Stalemate";
  3722. }
  3723. else if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) )
  3724. {
  3725. return "Announcer.SD_Event_MurderedToStalemate";
  3726. }
  3727. return "Game.Stalemate";
  3728. }
  3729. const char* CTFGameRules::WinSongName( int nTeam )
  3730. {
  3731. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  3732. {
  3733. return (nTeam == TF_TEAM_RED)
  3734. ? "Announcer.Helltower_Hell_Red_Win"
  3735. : "Announcer.Helltower_Hell_Blue_Win";
  3736. }
  3737. return "Game.YourTeamWon";
  3738. }
  3739. const char* CTFGameRules::LoseSongName( int nTeam )
  3740. {
  3741. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  3742. {
  3743. return (nTeam == TF_TEAM_RED)
  3744. ? "Announcer.Helltower_Hell_Red_Lose"
  3745. : "Announcer.Helltower_Hell_Blue_Lose";
  3746. }
  3747. else if ( IsMannVsMachineMode() )
  3748. {
  3749. return "music.mvm_lost_wave";
  3750. }
  3751. else
  3752. {
  3753. return BaseClass::LoseSongName( nTeam );
  3754. }
  3755. }
  3756. //-----------------------------------------------------------------------------
  3757. // Purpose:
  3758. //-----------------------------------------------------------------------------
  3759. void CTFGameRules::CleanUpMap( void )
  3760. {
  3761. #ifdef GAME_DLL
  3762. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  3763. {
  3764. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  3765. if ( !pTFPlayer )
  3766. continue;
  3767. // Remove all player conditions to prevent some dependency bugs
  3768. pTFPlayer->m_Shared.RemoveAllCond();
  3769. }
  3770. #endif // GAME_DLL
  3771. BaseClass::CleanUpMap();
  3772. if ( HLTVDirector() )
  3773. {
  3774. HLTVDirector()->BuildCameraList();
  3775. }
  3776. #ifdef GAME_DLL
  3777. m_hasSpawnedToy = false;
  3778. for ( int i = 0; i < TF_TEAM_COUNT; i++ )
  3779. {
  3780. m_bHasSpawnedSoccerBall[i] = false;
  3781. }
  3782. // If we're in a mode with upgrades, we force the players to recreate their weapons on next spawn.
  3783. // This clears out any weapon upgrades they had in the previous round.
  3784. if ( g_hUpgradeEntity )
  3785. {
  3786. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  3787. {
  3788. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  3789. if ( !pTFPlayer )
  3790. continue;
  3791. pTFPlayer->ForceItemRemovalOnRespawn();
  3792. // Remove all player upgrades as well
  3793. pTFPlayer->RemovePlayerAttributes( false );
  3794. }
  3795. }
  3796. #endif
  3797. }
  3798. //-----------------------------------------------------------------------------
  3799. // Purpose:
  3800. //-----------------------------------------------------------------------------
  3801. void CTFGameRules::RecalculateControlPointState( void )
  3802. {
  3803. Assert( ObjectiveResource() );
  3804. if ( !g_hControlPointMasters.Count() )
  3805. return;
  3806. if ( g_pObjectiveResource && g_pObjectiveResource->PlayingMiniRounds() )
  3807. return;
  3808. for ( int iTeam = LAST_SHARED_TEAM+1; iTeam < GetNumberOfTeams(); iTeam++ )
  3809. {
  3810. int iFarthestPoint = GetFarthestOwnedControlPoint( iTeam, true );
  3811. if ( iFarthestPoint == -1 )
  3812. continue;
  3813. // Now enable all spawn points for that spawn point
  3814. for ( int i=0; i<ITFTeamSpawnAutoList::AutoList().Count(); ++i )
  3815. {
  3816. CTFTeamSpawn *pTFSpawn = static_cast< CTFTeamSpawn* >( ITFTeamSpawnAutoList::AutoList()[i] );
  3817. if ( pTFSpawn->GetControlPoint() )
  3818. {
  3819. if ( pTFSpawn->GetTeamNumber() == iTeam )
  3820. {
  3821. if ( pTFSpawn->GetControlPoint()->GetPointIndex() == iFarthestPoint )
  3822. {
  3823. pTFSpawn->SetDisabled( false );
  3824. }
  3825. else
  3826. {
  3827. pTFSpawn->SetDisabled( true );
  3828. }
  3829. }
  3830. }
  3831. }
  3832. }
  3833. }
  3834. DECLARE_AUTO_LIST( ITFTeleportLocationAutoList )
  3835. class CTFTeleportLocation : public CPointEntity, public ITFTeleportLocationAutoList
  3836. {
  3837. public:
  3838. DECLARE_CLASS( CTFTeleportLocation, CPointEntity );
  3839. };
  3840. IMPLEMENT_AUTO_LIST( ITFTeleportLocationAutoList );
  3841. LINK_ENTITY_TO_CLASS( tf_teleport_location, CTFTeleportLocation );
  3842. void SpawnRunes( void )
  3843. {
  3844. // Spawn power-up runes when the round starts. Choice of spawn location and Powerup Type is random
  3845. CUtlVector< CTFInfoPowerupSpawn* > vecSpawnPoints;
  3846. for ( int i = 0; i < IInfoPowerupSpawnAutoList::AutoList().Count(); i++ )
  3847. {
  3848. CTFInfoPowerupSpawn *pSpawnPoint = static_cast< CTFInfoPowerupSpawn* >( IInfoPowerupSpawnAutoList::AutoList()[i] );
  3849. if ( !pSpawnPoint->IsDisabled() && !pSpawnPoint->HasRune() )
  3850. {
  3851. vecSpawnPoints.AddToTail( pSpawnPoint );
  3852. }
  3853. }
  3854. Assert( vecSpawnPoints.Count() > 0 ); // We need at least one valid info_powerup_spawn
  3855. // Warn if there aren't enough valid info_powerup_spawns to spawn all powerups
  3856. if ( vecSpawnPoints.Count() < RUNE_TYPES_MAX )
  3857. {
  3858. Warning( "Warning: Not enough valid info_powerup_spawn locations found. You need a minimum of %i valid locations to spawn all Powerups.\n", RUNE_TYPES_MAX );
  3859. }
  3860. // try to spawn each rune type
  3861. for ( int nRuneTypes = 0; nRuneTypes < RUNE_TYPES_MAX && vecSpawnPoints.Count() > 0; nRuneTypes++ )
  3862. {
  3863. int index = RandomInt( 0, vecSpawnPoints.Count() - 1 );
  3864. CTFInfoPowerupSpawn *pSpawnPoint = vecSpawnPoints[index];
  3865. CTFRune *pNewRune = CTFRune::CreateRune( pSpawnPoint->GetAbsOrigin(), (RuneTypes_t) nRuneTypes, TEAM_ANY, false, false );
  3866. pSpawnPoint->SetRune( pNewRune );
  3867. vecSpawnPoints.Remove( index );
  3868. }
  3869. }
  3870. #ifdef STAGING_ONLY
  3871. // Force spawn runes for testing
  3872. CON_COMMAND_F( tf_force_spawn_runes, "For testing.", FCVAR_CHEAT )
  3873. {
  3874. SpawnRunes();
  3875. }
  3876. #endif
  3877. void CTFGameRules::RespawnPlayers( bool bForceRespawn, bool bTeam, int iTeam )
  3878. {
  3879. // Skip the respawn at the beginning of a round in casual/comp mode since we already
  3880. // handled it when the pre-round doors closed over the players' views
  3881. if ( IsCompetitiveMode() && ( GetRoundsPlayed() == 0 ) && bForceRespawn && ( State_Get() == GR_STATE_BETWEEN_RNDS || State_Get() == GR_STATE_PREROUND ) )
  3882. {
  3883. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  3884. if ( !pMaster || !pMaster->PlayingMiniRounds() || ( pMaster->GetCurrentRoundIndex() == 0 ) )
  3885. return;
  3886. }
  3887. BaseClass::RespawnPlayers( bForceRespawn, bTeam, iTeam );
  3888. }
  3889. //-----------------------------------------------------------------------------
  3890. // Purpose: Called when a new round is being initialized
  3891. //-----------------------------------------------------------------------------
  3892. void CTFGameRules::SetupOnRoundStart( void )
  3893. {
  3894. for ( int i = 0; i < MAX_TEAMS; i++ )
  3895. {
  3896. ObjectiveResource()->SetBaseCP( -1, i );
  3897. }
  3898. for ( int i = 0; i < TF_TEAM_COUNT; i++ )
  3899. {
  3900. m_iNumCaps[i] = 0;
  3901. }
  3902. SetOvertime( false );
  3903. m_hRedKothTimer.Set( NULL );
  3904. m_hBlueKothTimer.Set( NULL );
  3905. // Let all entities know that a new round is starting
  3906. CBaseEntity *pEnt = gEntList.FirstEnt();
  3907. while( pEnt )
  3908. {
  3909. variant_t emptyVariant;
  3910. pEnt->AcceptInput( "RoundSpawn", NULL, NULL, emptyVariant, 0 );
  3911. pEnt = gEntList.NextEnt( pEnt );
  3912. }
  3913. // All entities have been spawned, now activate them
  3914. m_areHealthAndAmmoVectorsReady = false;
  3915. m_ammoVector.RemoveAll();
  3916. m_healthVector.RemoveAll();
  3917. pEnt = gEntList.FirstEnt();
  3918. while( pEnt )
  3919. {
  3920. variant_t emptyVariant;
  3921. pEnt->AcceptInput( "RoundActivate", NULL, NULL, emptyVariant, 0 );
  3922. pEnt = gEntList.NextEnt( pEnt );
  3923. }
  3924. if ( g_pObjectiveResource && !g_pObjectiveResource->PlayingMiniRounds() )
  3925. {
  3926. // Find all the control points with associated spawnpoints
  3927. memset( m_bControlSpawnsPerTeam, 0, sizeof(bool) * MAX_TEAMS * MAX_CONTROL_POINTS );
  3928. for ( int i=0; i<ITFTeamSpawnAutoList::AutoList().Count(); ++i )
  3929. {
  3930. CTFTeamSpawn *pTFSpawn = static_cast< CTFTeamSpawn* >( ITFTeamSpawnAutoList::AutoList()[i] );
  3931. if ( pTFSpawn->GetControlPoint() )
  3932. {
  3933. m_bControlSpawnsPerTeam[ pTFSpawn->GetTeamNumber() ][ pTFSpawn->GetControlPoint()->GetPointIndex() ] = true;
  3934. pTFSpawn->SetDisabled( true );
  3935. }
  3936. }
  3937. RecalculateControlPointState();
  3938. SetRoundOverlayDetails();
  3939. }
  3940. //Do any round specific setup for training logic (resetting the score, messages, etc).
  3941. if ( IsInTraining() )
  3942. {
  3943. if ( m_hTrainingModeLogic )
  3944. {
  3945. m_hTrainingModeLogic->SetupOnRoundStart();
  3946. }
  3947. }
  3948. m_szMostRecentCappers[0] = 0;
  3949. m_halloweenBossTimer.Invalidate();
  3950. m_ghostVector.RemoveAll();
  3951. m_zombieMobTimer.Invalidate();
  3952. m_zombiesLeftToSpawn = 0;
  3953. SetIT( NULL );
  3954. SetBirthdayPlayer( NULL );
  3955. if ( g_pMonsterResource )
  3956. {
  3957. g_pMonsterResource->HideBossHealthMeter();
  3958. }
  3959. #ifdef TF_RAID_MODE
  3960. if ( IsBossBattleMode() )
  3961. {
  3962. CTFTeam *enemyTeam = GetGlobalTFTeam( TF_TEAM_RED );
  3963. for( int i=0; i<enemyTeam->GetNumPlayers(); ++i )
  3964. {
  3965. CTFPlayer *who = ToTFPlayer( enemyTeam->GetPlayer( i ) );
  3966. who->ChangeTeam( TEAM_SPECTATOR, false, true );
  3967. who->RemoveAllObjects();
  3968. }
  3969. }
  3970. #endif // TF_RAID_MODE
  3971. if ( IsMannVsMachineMode() )
  3972. {
  3973. if ( g_hMannVsMachineLogic )
  3974. {
  3975. g_hMannVsMachineLogic->SetupOnRoundStart();
  3976. }
  3977. }
  3978. m_redPayloadToPush = NULL;
  3979. m_bluePayloadToPush = NULL;
  3980. m_redPayloadToBlock = NULL;
  3981. m_bluePayloadToBlock = NULL;
  3982. // Tell the clients to recalculate the holiday
  3983. IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_holidays" );
  3984. if ( event )
  3985. {
  3986. gameeventmanager->FireEvent( event );
  3987. }
  3988. UTIL_CalculateHolidays();
  3989. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  3990. {
  3991. m_helltowerTimer.Start( HELLTOWER_TIMER_INTERVAL );
  3992. }
  3993. // reset hell state
  3994. SetPlayersInHell( false );
  3995. if ( IsPowerupMode() )
  3996. {
  3997. // Reset imbalance detection scores
  3998. m_nPowerupKillsBlueTeam = 0;
  3999. m_nPowerupKillsRedTeam = 0;
  4000. SpawnRunes();
  4001. }
  4002. m_hHolidayLogic = dynamic_cast<CTFHolidayEntity*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_holiday" ) );
  4003. if ( m_hHolidayLogic.IsValid() )
  4004. {
  4005. m_hHolidayLogic->ResetWinner();
  4006. }
  4007. // cache off teleport locations and remove entities to save edicts
  4008. m_mapTeleportLocations.PurgeAndDeleteElements();
  4009. for ( int i=0; i<ITFTeleportLocationAutoList::AutoList().Count(); ++i )
  4010. {
  4011. CTFTeleportLocation *pLocation = static_cast< CTFTeleportLocation* >( ITFTeleportLocationAutoList::AutoList()[i] );
  4012. int iMap = m_mapTeleportLocations.Find( pLocation->GetEntityName() );
  4013. if ( !m_mapTeleportLocations.IsValidIndex( iMap ) )
  4014. {
  4015. CUtlVector< TeleportLocation_t > *pNew = new CUtlVector< TeleportLocation_t >;
  4016. iMap = m_mapTeleportLocations.Insert( pLocation->GetEntityName(), pNew );
  4017. }
  4018. CUtlVector< TeleportLocation_t > *pLocations = m_mapTeleportLocations[iMap];
  4019. int iLocation = pLocations->AddToTail();
  4020. pLocations->Element( iLocation ).m_vecPosition = pLocation->GetAbsOrigin();
  4021. pLocations->Element( iLocation ).m_qAngles = pLocation->GetAbsAngles();
  4022. UTIL_Remove( pLocation );
  4023. }
  4024. // swap our train model for the EOTL holiday
  4025. if ( IsHolidayActive( kHoliday_EOTL ) )
  4026. {
  4027. for ( int i = 0; i < IPhysicsPropAutoList::AutoList().Count(); i++ )
  4028. {
  4029. CPhysicsProp *pPhysicsProp = static_cast<CPhysicsProp*>( IPhysicsPropAutoList::AutoList()[i] );
  4030. const char *pszTemp = pPhysicsProp->GetModelName().ToCStr();
  4031. if ( FStrEq( pszTemp, "models/props_trainyard/bomb_cart.mdl" ) )
  4032. {
  4033. pPhysicsProp->SetModel( "models/props_trainyard/bomb_eotl_blue.mdl" );
  4034. }
  4035. else if ( FStrEq( pszTemp, "models/props_trainyard/bomb_cart_red.mdl" ) )
  4036. {
  4037. pPhysicsProp->SetModel( "models/props_trainyard/bomb_eotl_red.mdl" );
  4038. }
  4039. }
  4040. }
  4041. m_flMatchSummaryTeleportTime = -1.f;
  4042. }
  4043. //-----------------------------------------------------------------------------
  4044. // Purpose:
  4045. //-----------------------------------------------------------------------------
  4046. void CTFGameRules::RestartTournament( void )
  4047. {
  4048. BaseClass::RestartTournament();
  4049. if ( GetStopWatchTimer() )
  4050. {
  4051. UTIL_Remove( GetStopWatchTimer() );
  4052. }
  4053. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  4054. {
  4055. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  4056. if ( !pTFPlayer || !pTFPlayer->IsAlive() )
  4057. continue;
  4058. pTFPlayer->m_Shared.RemoveCond( TF_COND_CRITBOOSTED, true );
  4059. pTFPlayer->m_Shared.RemoveCond( TF_COND_CRITBOOSTED_BONUS_TIME, true );
  4060. pTFPlayer->m_Shared.RemoveCond( TF_COND_CRITBOOSTED_CTF_CAPTURE, true );
  4061. }
  4062. ItemSystem()->ReloadWhitelist();
  4063. ResetPlayerAndTeamReadyState();
  4064. }
  4065. //-----------------------------------------------------------------------------
  4066. // Purpose:
  4067. //-----------------------------------------------------------------------------
  4068. void CTFGameRules::HandleTeamScoreModify( int iTeam, int iScore )
  4069. {
  4070. BaseClass::HandleTeamScoreModify( iTeam, iScore );
  4071. if ( IsInStopWatch() == true )
  4072. {
  4073. if ( GetStopWatchTimer() )
  4074. {
  4075. if ( GetStopWatchTimer()->IsWatchingTimeStamps() == true )
  4076. {
  4077. GetStopWatchTimer()->SetStopWatchTimeStamp();
  4078. }
  4079. StopWatchModeThink();
  4080. }
  4081. }
  4082. }
  4083. //-----------------------------------------------------------------------------
  4084. // Purpose:
  4085. //-----------------------------------------------------------------------------
  4086. void CTFGameRules::StopWatchShouldBeTimedWin_Calculate( void )
  4087. {
  4088. m_bStopWatchShouldBeTimedWin = false;
  4089. if ( IsInTournamentMode() && IsInStopWatch() && ObjectiveResource() )
  4090. {
  4091. int iStopWatchTimer = ObjectiveResource()->GetStopWatchTimer();
  4092. CTeamRoundTimer *pStopWatch = dynamic_cast< CTeamRoundTimer* >( UTIL_EntityByIndex( iStopWatchTimer ) );
  4093. if ( pStopWatch && !pStopWatch->IsWatchingTimeStamps() )
  4094. {
  4095. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  4096. if ( pMaster == NULL )
  4097. return;
  4098. int iNumPoints = pMaster->GetNumPoints();
  4099. CTFTeam *pAttacker = NULL;
  4100. CTFTeam *pDefender = NULL;
  4101. for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
  4102. {
  4103. CTFTeam *pTeam = GetGlobalTFTeam( i );
  4104. if ( pTeam )
  4105. {
  4106. if ( pTeam->GetRole() == TEAM_ROLE_DEFENDERS )
  4107. {
  4108. pDefender = pTeam;
  4109. }
  4110. if ( pTeam->GetRole() == TEAM_ROLE_ATTACKERS )
  4111. {
  4112. pAttacker = pTeam;
  4113. }
  4114. }
  4115. }
  4116. if ( pAttacker == NULL || pDefender == NULL )
  4117. return;
  4118. m_bStopWatchShouldBeTimedWin = ( pDefender->GetScore() == iNumPoints );
  4119. }
  4120. }
  4121. }
  4122. //-----------------------------------------------------------------------------
  4123. // Purpose:
  4124. //-----------------------------------------------------------------------------
  4125. bool CTFGameRules::StopWatchShouldBeTimedWin( void )
  4126. {
  4127. StopWatchShouldBeTimedWin_Calculate();
  4128. return m_bStopWatchShouldBeTimedWin;
  4129. }
  4130. //-----------------------------------------------------------------------------
  4131. // Purpose:
  4132. //-----------------------------------------------------------------------------
  4133. void CTFGameRules::StopWatchModeThink( void )
  4134. {
  4135. if ( IsInTournamentMode() == false || IsInStopWatch() == false )
  4136. return;
  4137. if ( GetStopWatchTimer() == NULL )
  4138. return;
  4139. CTeamRoundTimer *pTimer = GetStopWatchTimer();
  4140. bool bWatchingCaps = pTimer->IsWatchingTimeStamps();
  4141. CTFTeam *pAttacker = NULL;
  4142. CTFTeam *pDefender = NULL;
  4143. for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
  4144. {
  4145. CTFTeam *pTeam = GetGlobalTFTeam( i );
  4146. if ( pTeam )
  4147. {
  4148. if ( pTeam->GetRole() == TEAM_ROLE_DEFENDERS )
  4149. {
  4150. pDefender = pTeam;
  4151. }
  4152. if ( pTeam->GetRole() == TEAM_ROLE_ATTACKERS )
  4153. {
  4154. pAttacker = pTeam;
  4155. }
  4156. }
  4157. }
  4158. if ( pAttacker == NULL || pDefender == NULL )
  4159. return;
  4160. m_bStopWatchWinner.Set( false );
  4161. if ( bWatchingCaps == false )
  4162. {
  4163. if ( pTimer->GetTimeRemaining() <= 0.0f )
  4164. {
  4165. if ( StopWatchShouldBeTimedWin() )
  4166. {
  4167. if ( pAttacker->GetScore() < pDefender->GetScore() )
  4168. {
  4169. m_bStopWatchWinner.Set( true );
  4170. SetWinningTeam( pDefender->GetTeamNumber(), WINREASON_DEFEND_UNTIL_TIME_LIMIT, true, true );
  4171. }
  4172. }
  4173. else
  4174. {
  4175. if ( pAttacker->GetScore() > pDefender->GetScore() )
  4176. {
  4177. m_bStopWatchWinner.Set( true );
  4178. SetWinningTeam( pAttacker->GetTeamNumber(), WINREASON_ALL_POINTS_CAPTURED, true, true );
  4179. }
  4180. }
  4181. if ( pTimer->IsTimerPaused() == false )
  4182. {
  4183. variant_t sVariant;
  4184. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  4185. }
  4186. m_nStopWatchState.Set( STOPWATCH_OVERTIME );
  4187. }
  4188. else
  4189. {
  4190. if ( pAttacker->GetScore() >= pDefender->GetScore() )
  4191. {
  4192. m_bStopWatchWinner.Set( true );
  4193. SetWinningTeam( pAttacker->GetTeamNumber(), WINREASON_ALL_POINTS_CAPTURED, true, true );
  4194. }
  4195. }
  4196. }
  4197. else
  4198. {
  4199. if ( pTimer->GetTimeRemaining() <= 0.0f )
  4200. {
  4201. m_nStopWatchState.Set( STOPWATCH_CAPTURE_TIME_NOT_SET );
  4202. }
  4203. else
  4204. {
  4205. m_nStopWatchState.Set( STOPWATCH_RUNNING );
  4206. }
  4207. }
  4208. if ( m_bStopWatchWinner == true )
  4209. {
  4210. UTIL_Remove( pTimer );
  4211. m_hStopWatchTimer = NULL;
  4212. m_flStopWatchTotalTime = -1.0f;
  4213. m_bStopWatch = false;
  4214. m_nStopWatchState.Set( STOPWATCH_CAPTURE_TIME_NOT_SET );
  4215. ShouldResetRoundsPlayed( false );
  4216. ShouldResetScores( true, false );
  4217. ResetScores();
  4218. }
  4219. }
  4220. //-----------------------------------------------------------------------------
  4221. // Purpose:
  4222. //-----------------------------------------------------------------------------
  4223. void CTFGameRules::ManageStopwatchTimer( bool bInSetup )
  4224. {
  4225. if ( IsInTournamentMode() == false )
  4226. return;
  4227. if ( mp_tournament_stopwatch.GetBool() == false )
  4228. return;
  4229. bool bAttacking = false;
  4230. bool bDefending = false;
  4231. for ( int i = LAST_SHARED_TEAM+1; i < GetNumberOfTeams(); i++ )
  4232. {
  4233. CTFTeam *pTeam = GetGlobalTFTeam( i );
  4234. if ( pTeam )
  4235. {
  4236. if ( pTeam->GetRole() == TEAM_ROLE_DEFENDERS )
  4237. {
  4238. bDefending = true;
  4239. }
  4240. if ( pTeam->GetRole() == TEAM_ROLE_ATTACKERS )
  4241. {
  4242. bAttacking = true;
  4243. }
  4244. }
  4245. }
  4246. if ( bDefending == true && bAttacking == true )
  4247. {
  4248. SetInStopWatch( true );
  4249. }
  4250. else
  4251. {
  4252. SetInStopWatch( false );
  4253. }
  4254. if ( IsInStopWatch() == true )
  4255. {
  4256. if ( m_hStopWatchTimer == NULL )
  4257. {
  4258. variant_t sVariant;
  4259. CTeamRoundTimer *pStopWatch = (CTeamRoundTimer*)CBaseEntity::CreateNoSpawn( "team_round_timer", vec3_origin, vec3_angle );
  4260. m_hStopWatchTimer = pStopWatch;
  4261. pStopWatch->SetName( MAKE_STRING("zz_stopwatch_timer") );
  4262. pStopWatch->SetShowInHud( false );
  4263. pStopWatch->SetStopWatch( true );
  4264. if ( m_flStopWatchTotalTime < 0.0f )
  4265. {
  4266. pStopWatch->SetCaptureWatchState( true );
  4267. DispatchSpawn( pStopWatch );
  4268. pStopWatch->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
  4269. }
  4270. else
  4271. {
  4272. DispatchSpawn( pStopWatch );
  4273. pStopWatch->SetCaptureWatchState( false );
  4274. sVariant.SetInt( m_flStopWatchTotalTime );
  4275. pStopWatch->AcceptInput( "Enable", NULL, NULL, sVariant, 0 );
  4276. pStopWatch->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
  4277. pStopWatch->SetAutoCountdown( true );
  4278. }
  4279. if ( bInSetup == true )
  4280. {
  4281. pStopWatch->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  4282. }
  4283. ObjectiveResource()->SetStopWatchTimer( pStopWatch );
  4284. }
  4285. else
  4286. {
  4287. if ( bInSetup == false )
  4288. {
  4289. variant_t sVariant;
  4290. m_hStopWatchTimer->AcceptInput( "Resume", NULL, NULL, sVariant, 0 );
  4291. }
  4292. else
  4293. {
  4294. variant_t sVariant;
  4295. m_hStopWatchTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  4296. }
  4297. }
  4298. }
  4299. }
  4300. //-----------------------------------------------------------------------------
  4301. // Purpose:
  4302. //-----------------------------------------------------------------------------
  4303. void CTFGameRules::SetSetup( bool bSetup )
  4304. {
  4305. if ( m_bInSetup == bSetup )
  4306. return;
  4307. BaseClass::SetSetup( bSetup );
  4308. ManageStopwatchTimer( bSetup );
  4309. }
  4310. //-----------------------------------------------------------------------------
  4311. // Purpose: Called when a new round is off and running
  4312. //-----------------------------------------------------------------------------
  4313. void CTFGameRules::SetupOnRoundRunning( void )
  4314. {
  4315. // Let out control point masters know that the round has started
  4316. for ( int i = 0; i < g_hControlPointMasters.Count(); i++ )
  4317. {
  4318. variant_t emptyVariant;
  4319. if ( g_hControlPointMasters[i] )
  4320. {
  4321. g_hControlPointMasters[i]->AcceptInput( "RoundStart", NULL, NULL, emptyVariant, 0 );
  4322. }
  4323. }
  4324. // Reset player speeds after preround lock
  4325. CTFPlayer *pPlayer;
  4326. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  4327. {
  4328. pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  4329. if ( !pPlayer )
  4330. continue;
  4331. pPlayer->TeamFortress_SetSpeed();
  4332. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  4333. {
  4334. if ( !IsInWaitingForPlayers() )
  4335. {
  4336. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_ROUND_START );
  4337. }
  4338. }
  4339. else if ( !IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) )
  4340. {
  4341. if ( IsCompetitiveMode() )
  4342. {
  4343. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_ROUND_START_COMP );
  4344. }
  4345. else
  4346. {
  4347. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_ROUND_START );
  4348. }
  4349. }
  4350. // warp the coach to student
  4351. if ( pPlayer->GetStudent() )
  4352. {
  4353. pPlayer->SetObserverTarget( pPlayer->GetStudent() );
  4354. pPlayer->StartObserverMode( OBS_MODE_CHASE );
  4355. }
  4356. if ( FNullEnt( pPlayer->edict() ) )
  4357. continue;
  4358. pPlayer->m_Shared.ResetRoundScores();
  4359. // Store the class they started the round as (tf_player captures everything after this)
  4360. if ( pPlayer->GetTeamNumber() >= FIRST_GAME_TEAM )
  4361. {
  4362. CSteamID steamID;
  4363. pPlayer->GetSteamID( &steamID );
  4364. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  4365. if ( pMatch )
  4366. {
  4367. CMatchInfo::PlayerMatchData_t *pMatchPlayer = pMatch->GetMatchDataForPlayer( steamID );
  4368. if ( pMatchPlayer )
  4369. {
  4370. pMatchPlayer->UpdateClassesPlayed( pPlayer->GetPlayerClass()->GetClassIndex() );
  4371. pMatchPlayer->bPlayed = true;
  4372. }
  4373. }
  4374. }
  4375. }
  4376. if ( IsPlayingSpecialDeliveryMode() && !IsInWaitingForPlayers() )
  4377. {
  4378. if ( !IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) )
  4379. {
  4380. BroadcastSound( 255, "Announcer.SD_RoundStart" );
  4381. }
  4382. }
  4383. if ( IsMannVsMachineMode() && g_pPopulationManager )
  4384. {
  4385. IGameEvent *event = gameeventmanager->CreateEvent( "mvm_begin_wave" );
  4386. if ( event )
  4387. {
  4388. event->SetInt( "wave_index", g_pPopulationManager->GetWaveNumber() );
  4389. event->SetInt( "max_waves", g_pPopulationManager->GetTotalWaveCount() );
  4390. event->SetInt( "advanced", g_pPopulationManager->IsAdvancedPopFile() ? 1 : 0 );
  4391. gameeventmanager->FireEvent( event );
  4392. }
  4393. }
  4394. if ( IsCompetitiveMode() && !( GetActiveRoundTimer() && ( GetActiveRoundTimer()->GetSetupTimeLength() > 0 ) ) )
  4395. {
  4396. // Announcer VO
  4397. if ( ( TFTeamMgr()->GetTeam( TF_TEAM_BLUE )->GetScore() == ( mp_winlimit.GetInt() - 1 ) ) &&
  4398. ( TFTeamMgr()->GetTeam( TF_TEAM_RED )->GetScore() == ( mp_winlimit.GetInt() - 1 ) ) )
  4399. {
  4400. BroadcastSound( 255, "Announcer.CompFinalGameBeginsFight" );
  4401. }
  4402. else
  4403. {
  4404. BroadcastSound( 255, "Announcer.CompGameBeginsFight" );
  4405. }
  4406. }
  4407. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  4408. if ( pMatch && IsMatchTypeCompetitive() )
  4409. {
  4410. static ConVarRef tf_bot_quota( "tf_bot_quota" );
  4411. tf_bot_quota.SetValue( (int)pMatch->GetCanonicalMatchSize() );
  4412. static ConVarRef tf_bot_quota_mode( "tf_bot_quota_mode" );
  4413. tf_bot_quota_mode.SetValue( "fill" );
  4414. }
  4415. if ( m_hGamerulesProxy )
  4416. {
  4417. m_hGamerulesProxy->StateEnterRoundRunning();
  4418. }
  4419. }
  4420. //-----------------------------------------------------------------------------
  4421. // Purpose: Called before a new round is started (so the previous round can end)
  4422. //-----------------------------------------------------------------------------
  4423. void CTFGameRules::PreviousRoundEnd( void )
  4424. {
  4425. // before we enter a new round, fire the "end output" for the previous round
  4426. if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] )
  4427. {
  4428. g_hControlPointMasters[0]->FireRoundEndOutput();
  4429. }
  4430. m_iPreviousRoundWinners = GetWinningTeam();
  4431. }
  4432. //-----------------------------------------------------------------------------
  4433. // Purpose: Called when a round has entered stalemate mode (timer has run out)
  4434. //-----------------------------------------------------------------------------
  4435. void CTFGameRules::SetupOnStalemateStart( void )
  4436. {
  4437. // Remove everyone's objects
  4438. RemoveAllProjectilesAndBuildings();
  4439. if ( IsInArenaMode() == false )
  4440. {
  4441. // Respawn all the players
  4442. RespawnPlayers( true );
  4443. // Disable all the active health packs in the world
  4444. m_hDisabledHealthKits.Purge();
  4445. CHealthKit *pHealthPack = gEntList.NextEntByClass( (CHealthKit *)NULL );
  4446. while ( pHealthPack )
  4447. {
  4448. if ( !pHealthPack->IsDisabled() )
  4449. {
  4450. pHealthPack->SetDisabled( true );
  4451. m_hDisabledHealthKits.AddToTail( pHealthPack );
  4452. }
  4453. pHealthPack = gEntList.NextEntByClass( pHealthPack );
  4454. }
  4455. }
  4456. else
  4457. {
  4458. CArenaLogic *pArenaLogic = dynamic_cast< CArenaLogic * > (gEntList.FindEntityByClassname( NULL, "tf_logic_arena" ) );
  4459. if ( pArenaLogic )
  4460. {
  4461. pArenaLogic->m_OnArenaRoundStart.FireOutput( pArenaLogic, pArenaLogic );
  4462. if ( tf_arena_override_cap_enable_time.GetFloat() > 0 )
  4463. {
  4464. m_flCapturePointEnableTime = gpGlobals->curtime + tf_arena_override_cap_enable_time.GetFloat();
  4465. }
  4466. else
  4467. {
  4468. m_flCapturePointEnableTime = gpGlobals->curtime + pArenaLogic->m_flTimeToEnableCapPoint;
  4469. }
  4470. IGameEvent *event = gameeventmanager->CreateEvent( "arena_round_start" );
  4471. if ( event )
  4472. {
  4473. gameeventmanager->FireEvent( event );
  4474. }
  4475. BroadcastSound( 255, "Announcer.AM_RoundStartRandom" );
  4476. }
  4477. }
  4478. CTFPlayer *pPlayer;
  4479. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  4480. {
  4481. pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  4482. if ( !pPlayer )
  4483. continue;
  4484. if ( IsInArenaMode() == true )
  4485. {
  4486. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_ROUND_START );
  4487. pPlayer->TeamFortress_SetSpeed();
  4488. }
  4489. else
  4490. {
  4491. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_SUDDENDEATH_START );
  4492. }
  4493. }
  4494. m_flStalemateStartTime = gpGlobals->curtime;
  4495. }
  4496. //-----------------------------------------------------------------------------
  4497. // Purpose:
  4498. //-----------------------------------------------------------------------------
  4499. void CTFGameRules::SetupOnStalemateEnd( void )
  4500. {
  4501. // Reenable all the health packs we disabled
  4502. for ( int i = 0; i < m_hDisabledHealthKits.Count(); i++ )
  4503. {
  4504. if ( m_hDisabledHealthKits[i] )
  4505. {
  4506. m_hDisabledHealthKits[i]->SetDisabled( false );
  4507. }
  4508. }
  4509. m_hDisabledHealthKits.Purge();
  4510. }
  4511. //-----------------------------------------------------------------------------
  4512. // Purpose:
  4513. //-----------------------------------------------------------------------------
  4514. void CTFGameRules::InitTeams( void )
  4515. {
  4516. BaseClass::InitTeams();
  4517. // clear the player class data
  4518. ResetFilePlayerClassInfoDatabase();
  4519. }
  4520. class CTraceFilterIgnoreTeammatesWithException : public CTraceFilterSimple
  4521. {
  4522. DECLARE_CLASS( CTraceFilterIgnoreTeammatesWithException, CTraceFilterSimple );
  4523. public:
  4524. CTraceFilterIgnoreTeammatesWithException( const IHandleEntity *passentity, int collisionGroup, int iIgnoreTeam, const IHandleEntity *pExceptionEntity )
  4525. : CTraceFilterSimple( passentity, collisionGroup ), m_iIgnoreTeam( iIgnoreTeam )
  4526. {
  4527. m_pExceptionEntity = pExceptionEntity;
  4528. }
  4529. virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
  4530. {
  4531. CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
  4532. if ( pServerEntity == m_pExceptionEntity )
  4533. return true;
  4534. if ( pEntity->IsPlayer() && pEntity->GetTeamNumber() == m_iIgnoreTeam )
  4535. {
  4536. return false;
  4537. }
  4538. return true;
  4539. }
  4540. int m_iIgnoreTeam;
  4541. const IHandleEntity *m_pExceptionEntity;
  4542. };
  4543. //-----------------------------------------------------------------------------
  4544. // Purpose:
  4545. //-----------------------------------------------------------------------------
  4546. void CTFGameRules::RadiusDamage( CTFRadiusDamageInfo &info )
  4547. {
  4548. float flRadSqr = (info.flRadius * info.flRadius);
  4549. int iDamageEnemies = 0;
  4550. int nDamageDealt = 0;
  4551. // Some weapons pass a radius of 0, since their only goal is to give blast jumping ability
  4552. if ( info.flRadius > 0 )
  4553. {
  4554. // Find all the entities in the radius, and attempt to damage them.
  4555. CBaseEntity *pEntity = NULL;
  4556. for ( CEntitySphereQuery sphere( info.vecSrc, info.flRadius ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
  4557. {
  4558. // Skip the attacker, if we have a RJ radius set. We'll do it post.
  4559. if ( info.flRJRadius && pEntity == info.dmgInfo->GetAttacker() )
  4560. continue;
  4561. // CEntitySphereQuery actually does a box test. So we need to make sure the distance is less than the radius first.
  4562. Vector vecPos;
  4563. pEntity->CollisionProp()->CalcNearestPoint( info.vecSrc, &vecPos );
  4564. if ( (info.vecSrc - vecPos).LengthSqr() > flRadSqr )
  4565. continue;
  4566. int iDamageToEntity = info.ApplyToEntity( pEntity );
  4567. if ( iDamageToEntity )
  4568. {
  4569. // Keep track of any enemies we damaged
  4570. if ( pEntity->IsPlayer() && !pEntity->InSameTeam( info.dmgInfo->GetAttacker() ) )
  4571. {
  4572. nDamageDealt+= iDamageToEntity;
  4573. iDamageEnemies++;
  4574. }
  4575. }
  4576. }
  4577. }
  4578. // If we have a set RJ radius, use it to affect the inflictor. This way Rocket Launchers
  4579. // with modified damage/radius perform like the base rocket launcher when it comes to RJing.
  4580. if ( info.flRJRadius && info.dmgInfo->GetAttacker() )
  4581. {
  4582. // Set our radius & damage to the base RL
  4583. info.flRadius = info.flRJRadius;
  4584. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.dmgInfo->GetWeapon());
  4585. if ( pWeapon )
  4586. {
  4587. float flBaseDamage = pWeapon->GetTFWpnData().GetWeaponData( TF_WEAPON_PRIMARY_MODE ).m_nDamage;
  4588. info.dmgInfo->SetDamage( flBaseDamage );
  4589. info.dmgInfo->CopyDamageToBaseDamage();
  4590. info.dmgInfo->SetDamagedOtherPlayers( iDamageEnemies );
  4591. // If we dealt damage, check radius life leech
  4592. if ( nDamageDealt > 0 )
  4593. {
  4594. // Heal on hits
  4595. int iModHealthOnHit = 0;
  4596. CALL_ATTRIB_HOOK_INT_ON_OTHER( info.dmgInfo->GetWeapon(), iModHealthOnHit, add_health_on_radius_damage );
  4597. if ( iModHealthOnHit )
  4598. {
  4599. // Scale Health mod with damage dealt, input being the maximum amount of health possible
  4600. float flScale = Clamp( nDamageDealt / flBaseDamage, 0.f, 1.0f );
  4601. iModHealthOnHit = (int)( (float)iModHealthOnHit * flScale );
  4602. int iHealed = info.dmgInfo->GetAttacker()->TakeHealth( iModHealthOnHit, DMG_GENERIC );
  4603. if ( iHealed )
  4604. {
  4605. IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" );
  4606. if ( event )
  4607. {
  4608. event->SetInt( "amount", iHealed );
  4609. event->SetInt( "entindex", info.dmgInfo->GetAttacker()->entindex() );
  4610. item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX;
  4611. if ( pWeapon->GetAttributeContainer() && pWeapon->GetAttributeContainer()->GetItem() )
  4612. {
  4613. healingItemDef = pWeapon->GetAttributeContainer()->GetItem()->GetItemDefIndex();
  4614. }
  4615. event->SetInt( "weapon_def_index", healingItemDef );
  4616. gameeventmanager->FireEvent( event );
  4617. }
  4618. }
  4619. }
  4620. }
  4621. }
  4622. // Apply ourselves to our attacker, if we're within range
  4623. Vector vecPos;
  4624. info.dmgInfo->GetAttacker()->CollisionProp()->CalcNearestPoint( info.vecSrc, &vecPos );
  4625. if ( (info.vecSrc - vecPos).LengthSqr() <= (info.flRadius * info.flRadius) )
  4626. {
  4627. info.ApplyToEntity( info.dmgInfo->GetAttacker() );
  4628. }
  4629. }
  4630. }
  4631. //-----------------------------------------------------------------------------
  4632. // Purpose: Calculate the damage falloff over distance
  4633. //-----------------------------------------------------------------------------
  4634. void CTFRadiusDamageInfo::CalculateFalloff( void )
  4635. {
  4636. if ( dmgInfo->GetDamageType() & DMG_RADIUS_MAX )
  4637. flFalloff = 0.0;
  4638. else if ( dmgInfo->GetDamageType() & DMG_HALF_FALLOFF )
  4639. flFalloff = 0.5;
  4640. else if ( flRadius )
  4641. flFalloff = dmgInfo->GetDamage() / flRadius;
  4642. else
  4643. flFalloff = 1.0;
  4644. CBaseEntity *pWeapon = dmgInfo->GetWeapon();
  4645. if ( pWeapon != NULL )
  4646. {
  4647. float flFalloffMod = 1.f;
  4648. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flFalloffMod, mult_dmg_falloff );
  4649. if ( flFalloffMod != 1.f )
  4650. {
  4651. flFalloff += flFalloffMod;
  4652. }
  4653. }
  4654. if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
  4655. {
  4656. CTFPlayer *pOwner = ToTFPlayer( dmgInfo->GetAttacker() );
  4657. if ( pOwner && pOwner->m_Shared.GetCarryingRuneType() == RUNE_PRECISION )
  4658. {
  4659. flFalloff = 1.0;
  4660. }
  4661. }
  4662. }
  4663. //-----------------------------------------------------------------------------
  4664. // Purpose: Attempt to apply the radius damage to the specified entity
  4665. //-----------------------------------------------------------------------------
  4666. int CTFRadiusDamageInfo::ApplyToEntity( CBaseEntity *pEntity )
  4667. {
  4668. if ( pEntity == pEntityIgnore || pEntity->m_takedamage == DAMAGE_NO )
  4669. return 0;
  4670. trace_t tr;
  4671. CBaseEntity *pInflictor = dmgInfo->GetInflictor();
  4672. // Check that the explosion can 'see' this entity.
  4673. Vector vecSpot = pEntity->BodyTarget( vecSrc, false );
  4674. CTraceFilterIgnorePlayers filterPlayers( pInflictor, COLLISION_GROUP_PROJECTILE );
  4675. CTraceFilterIgnoreFriendlyCombatItems filterCombatItems( pInflictor, COLLISION_GROUP_PROJECTILE, pInflictor->GetTeamNumber() );
  4676. CTraceFilterChain filter( &filterPlayers, &filterCombatItems );
  4677. UTIL_TraceLine( vecSrc, vecSpot, MASK_RADIUS_DAMAGE, &filter, &tr );
  4678. if ( tr.startsolid && tr.m_pEnt )
  4679. {
  4680. // Return when inside an enemy combat shield and tracing against a player of that team ("absorbed")
  4681. if ( tr.m_pEnt->IsCombatItem() && pEntity->InSameTeam( tr.m_pEnt ) && ( pEntity != tr.m_pEnt ) )
  4682. return 0;
  4683. filterPlayers.SetPassEntity( tr.m_pEnt );
  4684. CTraceFilterChain filterSelf( &filterPlayers, &filterCombatItems );
  4685. UTIL_TraceLine( vecSrc, vecSpot, MASK_RADIUS_DAMAGE, &filterSelf, &tr );
  4686. }
  4687. // If we don't trace the whole way to the target, and we didn't hit the target entity, we're blocked
  4688. if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
  4689. return 0;
  4690. // Adjust the damage - apply falloff.
  4691. float flAdjustedDamage = 0.0f;
  4692. float flDistanceToEntity;
  4693. // Rockets store the ent they hit as the enemy and have already dealt full damage to them by this time
  4694. if ( pInflictor && ( pEntity == pInflictor->GetEnemy() ) )
  4695. {
  4696. // Full damage, we hit this entity directly
  4697. flDistanceToEntity = 0;
  4698. }
  4699. else if ( pEntity->IsPlayer() )
  4700. {
  4701. // Use whichever is closer, absorigin or worldspacecenter
  4702. float flToWorldSpaceCenter = ( vecSrc - pEntity->WorldSpaceCenter() ).Length();
  4703. float flToOrigin = ( vecSrc - pEntity->GetAbsOrigin() ).Length();
  4704. flDistanceToEntity = MIN( flToWorldSpaceCenter, flToOrigin );
  4705. }
  4706. else
  4707. {
  4708. flDistanceToEntity = ( vecSrc - tr.endpos ).Length();
  4709. }
  4710. flAdjustedDamage = RemapValClamped( flDistanceToEntity, 0, flRadius, dmgInfo->GetDamage(), dmgInfo->GetDamage() * flFalloff );
  4711. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(dmgInfo->GetWeapon());
  4712. // Grenades & Pipebombs do less damage to ourselves.
  4713. if ( pEntity == dmgInfo->GetAttacker() && pWeapon )
  4714. {
  4715. switch( pWeapon->GetWeaponID() )
  4716. {
  4717. case TF_WEAPON_PIPEBOMBLAUNCHER :
  4718. case TF_WEAPON_GRENADELAUNCHER :
  4719. case TF_WEAPON_CANNON :
  4720. case TF_WEAPON_STICKBOMB :
  4721. flAdjustedDamage *= 0.75f;
  4722. break;
  4723. }
  4724. }
  4725. // If we end up doing 0 damage, exit now.
  4726. if ( flAdjustedDamage <= 0 )
  4727. return 0;
  4728. // the explosion can 'see' this entity, so hurt them!
  4729. if (tr.startsolid)
  4730. {
  4731. // if we're stuck inside them, fixup the position and distance
  4732. tr.endpos = vecSrc;
  4733. tr.fraction = 0.0;
  4734. }
  4735. CTakeDamageInfo adjustedInfo = *dmgInfo;
  4736. adjustedInfo.SetDamage( flAdjustedDamage );
  4737. Vector dir = vecSpot - vecSrc;
  4738. VectorNormalize( dir );
  4739. // If we don't have a damage force, manufacture one
  4740. if ( adjustedInfo.GetDamagePosition() == vec3_origin || adjustedInfo.GetDamageForce() == vec3_origin )
  4741. {
  4742. CalculateExplosiveDamageForce( &adjustedInfo, dir, vecSrc );
  4743. }
  4744. else
  4745. {
  4746. // Assume the force passed in is the maximum force. Decay it based on falloff.
  4747. float flForce = adjustedInfo.GetDamageForce().Length() * flFalloff;
  4748. adjustedInfo.SetDamageForce( dir * flForce );
  4749. adjustedInfo.SetDamagePosition( vecSrc );
  4750. }
  4751. adjustedInfo.ScaleDamageForce( m_flForceScale );
  4752. int nDamageTaken = 0;
  4753. if ( tr.fraction != 1.0 && pEntity == tr.m_pEnt )
  4754. {
  4755. ClearMultiDamage( );
  4756. pEntity->DispatchTraceAttack( adjustedInfo, dir, &tr );
  4757. ApplyMultiDamage();
  4758. }
  4759. else
  4760. {
  4761. nDamageTaken = pEntity->TakeDamage( adjustedInfo );
  4762. }
  4763. // Now hit all triggers along the way that respond to damage.
  4764. pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, tr.endpos, dir );
  4765. return nDamageTaken;
  4766. }
  4767. //-----------------------------------------------------------------------------
  4768. // Purpose:
  4769. // Input : &info -
  4770. // &vecSrcIn -
  4771. // flRadius -
  4772. // iClassIgnore -
  4773. // *pEntityIgnore -
  4774. //-----------------------------------------------------------------------------
  4775. void CTFGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore )
  4776. {
  4777. // This shouldn't be used. Call the version above that takes a CTFRadiusDamageInfo pointer.
  4778. Assert(0);
  4779. CTakeDamageInfo dmgInfo = info;
  4780. Vector vecSrc = vecSrcIn;
  4781. CTFRadiusDamageInfo radiusinfo( &dmgInfo, vecSrc, flRadius, pEntityIgnore );
  4782. RadiusDamage(radiusinfo);
  4783. }
  4784. //-----------------------------------------------------------------------------
  4785. // Purpose:
  4786. //-----------------------------------------------------------------------------
  4787. bool CTFGameRules::ApplyOnDamageModifyRules( CTakeDamageInfo &info, CBaseEntity *pVictimBaseEntity, bool bAllowDamage )
  4788. {
  4789. info.SetDamageForForceCalc( info.GetDamage() );
  4790. bool bDebug = tf_debug_damage.GetBool();
  4791. CTFPlayer *pVictim = ToTFPlayer( pVictimBaseEntity );
  4792. CBaseEntity *pAttacker = info.GetAttacker();
  4793. CTFPlayer *pTFAttacker = ToTFPlayer( pAttacker );
  4794. // damage may not come from a weapon (ie: Bosses, etc)
  4795. // The existing code below already checked for NULL pWeapon, anyways
  4796. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
  4797. bool bShowDisguisedCrit = false;
  4798. bool bAllSeeCrit = false;
  4799. EAttackBonusEffects_t eBonusEffect = kBonusEffect_None;
  4800. if ( pVictim )
  4801. {
  4802. pVictim->SetSeeCrit( false, false, false );
  4803. pVictim->SetAttackBonusEffect( kBonusEffect_None );
  4804. }
  4805. int bitsDamage = info.GetDamageType();
  4806. // Damage type was already crit (Flares / headshot)
  4807. if ( bitsDamage & DMG_CRITICAL )
  4808. {
  4809. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  4810. }
  4811. // First figure out whether this is going to be a full forced crit for some specific reason. It's
  4812. // important that we do this before figuring out whether we're going to be a minicrit or not.
  4813. // Allow attributes to force critical hits on players with specific conditions
  4814. if ( pVictim )
  4815. {
  4816. // Crit against players that have these conditions
  4817. int iCritDamageTypes = 0;
  4818. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iCritDamageTypes, or_crit_vs_playercond );
  4819. if ( iCritDamageTypes )
  4820. {
  4821. // iCritDamageTypes is an or'd list of types. We need to pull each bit out and
  4822. // then test against what that bit in the items_master file maps to.
  4823. for ( int i = 0; condition_to_attribute_translation[i] != TF_COND_LAST; i++ )
  4824. {
  4825. if ( iCritDamageTypes & ( 1 << i ) )
  4826. {
  4827. if ( pVictim->m_Shared.InCond( condition_to_attribute_translation[ i ] ) )
  4828. {
  4829. bitsDamage |= DMG_CRITICAL;
  4830. info.AddDamageType( DMG_CRITICAL );
  4831. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  4832. if ( condition_to_attribute_translation[i] == TF_COND_DISGUISED ||
  4833. condition_to_attribute_translation[i] == TF_COND_DISGUISING )
  4834. {
  4835. // if our attribute specifically crits disguised enemies we need to show it on the client
  4836. bShowDisguisedCrit = true;
  4837. }
  4838. break;
  4839. }
  4840. }
  4841. }
  4842. }
  4843. int iCritVsWet = 0;
  4844. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iCritVsWet, crit_vs_wet_players );
  4845. if ( iCritVsWet )
  4846. {
  4847. float flWaterExitTime = pVictim->GetWaterExitTime();
  4848. if ( pVictim->m_Shared.InCond( TF_COND_URINE ) ||
  4849. pVictim->m_Shared.InCond( TF_COND_MAD_MILK ) ||
  4850. ( pVictim->GetWaterLevel() > WL_NotInWater ) ||
  4851. ( ( flWaterExitTime > 0 ) && ( gpGlobals->curtime - flWaterExitTime < 5.0f ) ) ) // or they exited the water in the last few seconds
  4852. {
  4853. bitsDamage |= DMG_CRITICAL;
  4854. info.AddDamageType( DMG_CRITICAL );
  4855. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  4856. }
  4857. }
  4858. // Crit against players that don't have these conditions
  4859. int iCritDamageNotTypes = 0;
  4860. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iCritDamageNotTypes, or_crit_vs_not_playercond );
  4861. if ( iCritDamageNotTypes )
  4862. {
  4863. // iCritDamageTypes is an or'd list of types. We need to pull each bit out and
  4864. // then test against what that bit in the items_master file maps to.
  4865. for ( int i = 0; condition_to_attribute_translation[i] != TF_COND_LAST; i++ )
  4866. {
  4867. if ( iCritDamageNotTypes & ( 1 << i ) )
  4868. {
  4869. if ( !pVictim->m_Shared.InCond( condition_to_attribute_translation[ i ] ) )
  4870. {
  4871. bitsDamage |= DMG_CRITICAL;
  4872. info.AddDamageType( DMG_CRITICAL );
  4873. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  4874. if ( condition_to_attribute_translation[ i ] == TF_COND_DISGUISED ||
  4875. condition_to_attribute_translation[ i ] == TF_COND_DISGUISING )
  4876. {
  4877. // if our attribute specifically crits disguised enemies we need to show it on the client
  4878. bShowDisguisedCrit = true;
  4879. }
  4880. break;
  4881. }
  4882. }
  4883. }
  4884. }
  4885. // Crit burning behind
  4886. int iCritBurning = 0;
  4887. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iCritBurning, axtinguisher_properties );
  4888. if ( iCritBurning && pVictim->m_Shared.InCond( TF_COND_BURNING ) )
  4889. {
  4890. // Full crit in back, mini in front
  4891. Vector toEnt = pVictim->GetAbsOrigin() - pTFAttacker->GetAbsOrigin();
  4892. {
  4893. Vector entForward;
  4894. AngleVectors( pVictim->EyeAngles(), &entForward );
  4895. toEnt.z = 0;
  4896. toEnt.NormalizeInPlace();
  4897. if ( DotProduct( toEnt, entForward ) > 0.0f ) // 90 degrees from center (total of 180)
  4898. {
  4899. bitsDamage |= DMG_CRITICAL;
  4900. info.AddDamageType( DMG_CRITICAL );
  4901. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  4902. }
  4903. else
  4904. {
  4905. bAllSeeCrit = true;
  4906. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4907. eBonusEffect = kBonusEffect_MiniCrit;
  4908. }
  4909. }
  4910. }
  4911. }
  4912. int iCritWhileAirborne = 0;
  4913. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iCritWhileAirborne, crit_while_airborne );
  4914. if ( iCritWhileAirborne && pTFAttacker )
  4915. {
  4916. if ( pTFAttacker->InAirDueToExplosion() )
  4917. {
  4918. bitsDamage |= DMG_CRITICAL;
  4919. info.AddDamageType( DMG_CRITICAL );
  4920. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  4921. }
  4922. }
  4923. // For awarding assist damage stat later
  4924. ETFCond eDamageBonusCond = TF_COND_LAST;
  4925. // Some forms of damage override long range damage falloff
  4926. bool bIgnoreLongRangeDmgEffects = false;
  4927. // Figure out if it's a minicrit or not
  4928. // But we never minicrit ourselves.
  4929. if ( pAttacker != pVictimBaseEntity )
  4930. {
  4931. if ( info.GetCritType() == CTakeDamageInfo::CRIT_NONE )
  4932. {
  4933. CBaseEntity *pInflictor = info.GetInflictor();
  4934. CTFGrenadePipebombProjectile *pBaseGrenade = dynamic_cast< CTFGrenadePipebombProjectile* >( pInflictor );
  4935. CTFBaseRocket *pBaseRocket = dynamic_cast< CTFBaseRocket* >( pInflictor );
  4936. if ( pVictim && ( pVictim->m_Shared.InCond( TF_COND_URINE ) ||
  4937. pVictim->m_Shared.InCond( TF_COND_MARKEDFORDEATH ) ||
  4938. pVictim->m_Shared.InCond( TF_COND_MARKEDFORDEATH_SILENT ) ||
  4939. pVictim->m_Shared.InCond( TF_COND_PASSTIME_PENALTY_DEBUFF ) ) )
  4940. {
  4941. bAllSeeCrit = true;
  4942. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4943. eBonusEffect = kBonusEffect_MiniCrit;
  4944. if ( !pVictim->m_Shared.InCond( TF_COND_MARKEDFORDEATH_SILENT ) )
  4945. {
  4946. eDamageBonusCond = pVictim->m_Shared.InCond( TF_COND_URINE ) ? TF_COND_URINE : TF_COND_MARKEDFORDEATH;
  4947. }
  4948. }
  4949. else if ( pTFAttacker && ( pTFAttacker->m_Shared.InCond( TF_COND_OFFENSEBUFF ) || pTFAttacker->m_Shared.InCond( TF_COND_NOHEALINGDAMAGEBUFF ) ) )
  4950. {
  4951. // Attackers buffed by the soldier do mini-crits.
  4952. bAllSeeCrit = true;
  4953. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4954. eBonusEffect = kBonusEffect_MiniCrit;
  4955. if ( pTFAttacker->m_Shared.InCond( TF_COND_OFFENSEBUFF ) )
  4956. {
  4957. eDamageBonusCond = TF_COND_OFFENSEBUFF;
  4958. }
  4959. }
  4960. else if ( pTFAttacker && (bitsDamage & DMG_RADIUS_MAX) && pWeapon && ( (pWeapon->GetWeaponID() == TF_WEAPON_SWORD) || (pWeapon->GetWeaponID() == TF_WEAPON_BOTTLE)|| (pWeapon->GetWeaponID() == TF_WEAPON_WRENCH) ) )
  4961. {
  4962. // First sword or bottle attack after a charge is a mini-crit.
  4963. bAllSeeCrit = true;
  4964. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4965. eBonusEffect = kBonusEffect_MiniCrit;
  4966. }
  4967. else if ( ( pInflictor && pInflictor->IsPlayer() == false ) && ( ( pBaseRocket && pBaseRocket->GetDeflected() ) || ( pBaseGrenade && pBaseGrenade->GetDeflected() && ( pBaseGrenade->ShouldMiniCritOnReflect() ) ) ) )
  4968. {
  4969. // Reflected rockets, grenades (non-remote detonate), arrows always mini-crit
  4970. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4971. eBonusEffect = kBonusEffect_MiniCrit;
  4972. }
  4973. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED )
  4974. {
  4975. // Charged plasma shots do minicrits.
  4976. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4977. eBonusEffect = kBonusEffect_MiniCrit;
  4978. }
  4979. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_CLEAVER_CRIT )
  4980. {
  4981. // Long range cleaver hit
  4982. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4983. eBonusEffect = kBonusEffect_MiniCrit;
  4984. }
  4985. else if ( pTFAttacker && ( pTFAttacker->m_Shared.InCond( TF_COND_ENERGY_BUFF ) ) )
  4986. {
  4987. // Scouts using crit drink do mini-crits, as well as receive them
  4988. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4989. eBonusEffect = kBonusEffect_MiniCrit;
  4990. }
  4991. else if ( ( info.GetDamageType() & DMG_IGNITE ) && pVictim && pVictim->m_Shared.InCond( TF_COND_BURNING ) && info.GetDamageCustom() == TF_DMG_CUSTOM_BURNING_FLARE )
  4992. {
  4993. CTFFlareGun *pFlareGun = dynamic_cast< CTFFlareGun* >( pWeapon );
  4994. if ( pFlareGun && pFlareGun->GetFlareGunType() != FLAREGUN_GRORDBORT )
  4995. {
  4996. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  4997. eBonusEffect = kBonusEffect_MiniCrit;
  4998. }
  4999. }
  5000. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_FLARE_PELLET )
  5001. {
  5002. CBaseEntity *pInflictor = info.GetInflictor();
  5003. CTFProjectile_Flare *pFlare = dynamic_cast< CTFProjectile_Flare* >( pInflictor );
  5004. if ( pFlare && pFlare->IsFromTaunt() && pFlare->GetTimeAlive() < 0.05f )
  5005. {
  5006. // Taunt crits fired from the scorch shot at short range are super powerful!
  5007. info.SetDamage( 400.0f );
  5008. }
  5009. }
  5010. else if( pTFAttacker && pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_CANNON && ( info.GetDamageType() & DMG_BLAST ) )
  5011. {
  5012. CTFGrenadeLauncher* pGrenadeLauncher = static_cast<CTFGrenadeLauncher*>( pWeapon );
  5013. if( pGrenadeLauncher->IsDoubleDonk( pVictim ) )
  5014. {
  5015. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  5016. eBonusEffect = kBonusEffect_DoubleDonk;
  5017. info.SetDamage( info.GetMaxDamage() ); // Double donk victims score max damage
  5018. EconEntity_OnOwnerKillEaterEvent( pGrenadeLauncher, pTFAttacker, pVictim, kKillEaterEvent_DoubleDonks );
  5019. }
  5020. }
  5021. else
  5022. {
  5023. // Allow Attributes to shortcut out if found, no need to check all of them
  5024. for ( int i = 0; i < 1; ++i )
  5025. {
  5026. // Some weapons force minicrits on burning targets.
  5027. // Does not work for burn but works for ignite
  5028. int iForceMiniCritOnBurning = 0;
  5029. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iForceMiniCritOnBurning, or_minicrit_vs_playercond_burning );
  5030. if ( iForceMiniCritOnBurning == 1 && pVictim && pVictim->m_Shared.InCond( TF_COND_BURNING ) && !( info.GetDamageType() & DMG_BURN ) )
  5031. {
  5032. bAllSeeCrit = true;
  5033. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  5034. eBonusEffect = kBonusEffect_MiniCrit;
  5035. break;
  5036. }
  5037. // Some weapons mini-crit airborne targets. Airborne targets are any target that has been knocked
  5038. // into the air by an explosive force from an enemy.
  5039. int iMiniCritAirborne = 0;
  5040. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iMiniCritAirborne, mini_crit_airborne );
  5041. if ( iMiniCritAirborne == 1 && pVictim && pVictim->InAirDueToKnockback() )
  5042. {
  5043. bAllSeeCrit = true;
  5044. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  5045. eBonusEffect = kBonusEffect_MiniCrit;
  5046. break;
  5047. }
  5048. //// Some weapons minicrit *any* target in the air, regardless of how they got there.
  5049. //int iMiniCritAirborneDeploy = 0;
  5050. //CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iMiniCritAirborneDeploy, mini_crit_airborne_deploy );
  5051. //if ( iMiniCritAirborneDeploy > 0 &&
  5052. // pWeapon &&
  5053. // ( gpGlobals->curtime - pWeapon->GetLastDeployTime() ) < iMiniCritAirborneDeploy &&
  5054. // //
  5055. // pVictim && !( pVictim->GetFlags() & FL_ONGROUND ) &&
  5056. // ( pVictim->GetWaterLevel() == WL_NotInWater ) )
  5057. //{
  5058. // bAllSeeCrit = true;
  5059. // info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  5060. // eBonusEffect = kBonusEffect_MiniCrit;
  5061. // break;
  5062. //}
  5063. if ( pTFAttacker && pVictim )
  5064. {
  5065. // MiniCrit a victims back at close range
  5066. int iMiniCritBackAttack = 0;
  5067. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iMiniCritBackAttack, closerange_backattack_minicrits );
  5068. Vector toEnt = pVictim->GetAbsOrigin() - pTFAttacker->GetAbsOrigin();
  5069. if ( iMiniCritBackAttack == 1 && toEnt.LengthSqr() < Square( 512.0f ) )
  5070. {
  5071. Vector entForward;
  5072. AngleVectors( pVictim->EyeAngles(), &entForward );
  5073. toEnt.z = 0;
  5074. toEnt.NormalizeInPlace();
  5075. if ( DotProduct( toEnt, entForward ) > 0.259f ) // 75 degrees from center (total of 150)
  5076. {
  5077. bAllSeeCrit = true;
  5078. info.SetCritType( CTakeDamageInfo::CRIT_MINI );
  5079. eBonusEffect = kBonusEffect_MiniCrit;
  5080. break;
  5081. }
  5082. }
  5083. }
  5084. }
  5085. }
  5086. // Don't do long range distance falloff when pAttacker has Rocket Specialist attrib and directly hits an enemy
  5087. if ( pBaseRocket && pBaseRocket->GetEnemy() && pBaseRocket->GetEnemy() == pVictimBaseEntity )
  5088. {
  5089. int iRocketSpecialist = 0;
  5090. CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iRocketSpecialist, rocket_specialist );
  5091. if ( iRocketSpecialist )
  5092. bIgnoreLongRangeDmgEffects = true;
  5093. }
  5094. // Some Powerups remove distance damage falloff
  5095. if ( pTFAttacker && ( pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_STRENGTH || pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) )
  5096. {
  5097. bIgnoreLongRangeDmgEffects = true;
  5098. }
  5099. }
  5100. }
  5101. if ( info.GetCritType() == CTakeDamageInfo::CRIT_MINI )
  5102. {
  5103. int iPromoteMiniCritToCrit = 0;
  5104. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iPromoteMiniCritToCrit, minicrits_become_crits );
  5105. if ( iPromoteMiniCritToCrit == 1 )
  5106. {
  5107. info.SetCritType( CTakeDamageInfo::CRIT_FULL );
  5108. eBonusEffect = kBonusEffect_Crit;
  5109. bitsDamage |= DMG_CRITICAL;
  5110. info.AddDamageType( DMG_CRITICAL );
  5111. }
  5112. }
  5113. if ( pVictim )
  5114. {
  5115. pVictim->SetSeeCrit( bAllSeeCrit, info.GetCritType() == CTakeDamageInfo::CRIT_MINI, bShowDisguisedCrit );
  5116. pVictim->SetAttackBonusEffect( eBonusEffect );
  5117. }
  5118. // If we're invulnerable, force ourselves to only take damage events only, so we still get pushed
  5119. if ( pVictim && pVictim->m_Shared.IsInvulnerable() )
  5120. {
  5121. if ( !bAllowDamage )
  5122. {
  5123. int iOldTakeDamage = pVictim->m_takedamage;
  5124. pVictim->m_takedamage = DAMAGE_EVENTS_ONLY;
  5125. // NOTE: Deliberately skip base player OnTakeDamage, because we don't want all the stuff it does re: suit voice
  5126. pVictim->CBaseCombatCharacter::OnTakeDamage( info );
  5127. pVictim->m_takedamage = iOldTakeDamage;
  5128. // Burn sounds are handled in ConditionThink()
  5129. if ( !(bitsDamage & DMG_BURN ) )
  5130. {
  5131. pVictim->SpeakConceptIfAllowed( MP_CONCEPT_HURT );
  5132. }
  5133. // If this is critical explosive damage, and the Medic giving us invuln triggered
  5134. // it in the last second, he's earned himself an achievement.
  5135. if ( (bitsDamage & DMG_CRITICAL) && (bitsDamage & DMG_BLAST) )
  5136. {
  5137. pVictim->m_Shared.CheckForAchievement( ACHIEVEMENT_TF_MEDIC_SAVE_TEAMMATE );
  5138. }
  5139. return false;
  5140. }
  5141. }
  5142. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB )
  5143. {
  5144. // Jarate backstabber
  5145. int iJarateBackstabber = 0;
  5146. CALL_ATTRIB_HOOK_INT_ON_OTHER( pVictim, iJarateBackstabber, jarate_backstabber );
  5147. if ( iJarateBackstabber > 0 && pTFAttacker )
  5148. {
  5149. pTFAttacker->m_Shared.AddCond( TF_COND_URINE, 10.0f, pVictim );
  5150. pTFAttacker->m_Shared.SetPeeAttacker( pVictim );
  5151. pTFAttacker->SpeakConceptIfAllowed( MP_CONCEPT_JARATE_HIT );
  5152. }
  5153. if ( pVictim && pVictim->CheckBlockBackstab( pTFAttacker ) )
  5154. {
  5155. // The backstab was absorbed by a shield.
  5156. info.SetDamage( 0 );
  5157. // Shake nearby players' screens.
  5158. UTIL_ScreenShake( pVictim->GetAbsOrigin(), 25.f, 150.0, 1.0, 50.f, SHAKE_START );
  5159. // Play the notification sound.
  5160. pVictim->EmitSound( "Player.Spy_Shield_Break" );
  5161. // Unzoom the sniper.
  5162. CTFWeaponBase *pWeapon = pVictim->GetActiveTFWeapon();
  5163. if ( pWeapon && WeaponID_IsSniperRifle( pWeapon->GetWeaponID() ) )
  5164. {
  5165. CTFSniperRifle *pSniperRifle = static_cast< CTFSniperRifle* >( pWeapon );
  5166. if ( pSniperRifle->IsZoomed() )
  5167. {
  5168. pSniperRifle->ZoomOut();
  5169. }
  5170. }
  5171. // Vibrate the spy's knife.
  5172. if ( pTFAttacker && pTFAttacker->GetActiveWeapon() )
  5173. {
  5174. CTFKnife *pKnife = (CTFKnife *) pTFAttacker->GetActiveWeapon();
  5175. if ( pKnife )
  5176. {
  5177. pKnife->BackstabBlocked();
  5178. }
  5179. }
  5180. // Tell the clients involved in the jarate
  5181. CRecipientFilter involved_filter;
  5182. involved_filter.AddRecipient( pVictim );
  5183. involved_filter.AddRecipient( pTFAttacker );
  5184. UserMessageBegin( involved_filter, "PlayerShieldBlocked" );
  5185. WRITE_BYTE( pTFAttacker->entindex() );
  5186. WRITE_BYTE( pVictim->entindex() );
  5187. MessageEnd();
  5188. }
  5189. }
  5190. // Apply attributes that increase damage vs players
  5191. if ( pWeapon )
  5192. {
  5193. float flDamage = info.GetDamage();
  5194. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_players );
  5195. // Check if we're to boost damage against the same class
  5196. if( pVictim && pTFAttacker )
  5197. {
  5198. int nVictimClass = pVictim->GetPlayerClass()->GetClassIndex();
  5199. int nAttackerClass = pTFAttacker->GetPlayerClass()->GetClassIndex();
  5200. // Same class?
  5201. if( nVictimClass == nAttackerClass )
  5202. {
  5203. // Potentially boost damage
  5204. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_same_class );
  5205. }
  5206. }
  5207. info.SetDamage( flDamage );
  5208. }
  5209. if ( pVictim && !pVictim->m_Shared.InCond( TF_COND_BURNING ) )
  5210. {
  5211. if ( bitsDamage & DMG_CRITICAL )
  5212. {
  5213. if ( pTFAttacker && !pTFAttacker->m_Shared.IsCritBoosted() )
  5214. {
  5215. int iNonBurningCritsDisabled = 0;
  5216. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iNonBurningCritsDisabled, set_nocrit_vs_nonburning );
  5217. if ( iNonBurningCritsDisabled )
  5218. {
  5219. bitsDamage &= ~DMG_CRITICAL;
  5220. info.SetDamageType( info.GetDamageType() & (~DMG_CRITICAL) );
  5221. info.SetCritType( CTakeDamageInfo::CRIT_NONE );
  5222. }
  5223. }
  5224. }
  5225. float flDamage = info.GetDamage();
  5226. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_nonburning );
  5227. info.SetDamage( flDamage );
  5228. }
  5229. // Alien Isolation SetBonus Checking
  5230. if ( pVictim && pTFAttacker && pWeapon )
  5231. {
  5232. // Alien->Merc melee bonus
  5233. if ( ( info.GetDamageType() & (DMG_CLUB|DMG_SLASH) ) && info.GetDamageCustom() != TF_DMG_CUSTOM_BASEBALL )
  5234. {
  5235. CTFWeaponBaseMelee *pMelee = dynamic_cast<CTFWeaponBaseMelee*>( pWeapon );
  5236. if ( pMelee )
  5237. {
  5238. int iAttackerAlien = 0;
  5239. int iVictimMerc = 0;
  5240. CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFAttacker, iAttackerAlien, alien_isolation_xeno_bonus_pos );
  5241. CALL_ATTRIB_HOOK_INT_ON_OTHER( pVictim, iVictimMerc, alien_isolation_merc_bonus_pos );
  5242. if ( iAttackerAlien && iVictimMerc )
  5243. {
  5244. info.SetDamage( info.GetDamage() * 5.0f );
  5245. }
  5246. }
  5247. }
  5248. // Merc->Alien MK50 damage, aka flamethrower
  5249. if ( ( info.GetDamageType() & DMG_IGNITE ) && pWeapon->GetWeaponID() == TF_WEAPON_FLAMETHROWER )
  5250. {
  5251. int iAttackerMerc = 0;
  5252. int iVictimAlien = 0;
  5253. CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFAttacker, iAttackerMerc, alien_isolation_merc_bonus_pos );
  5254. CALL_ATTRIB_HOOK_INT_ON_OTHER( pVictim, iVictimAlien, alien_isolation_xeno_bonus_pos );
  5255. if ( iAttackerMerc && iVictimAlien )
  5256. {
  5257. info.SetDamage( info.GetDamage() * 3.0f );
  5258. }
  5259. }
  5260. }
  5261. int iPierceResists = 0;
  5262. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWeapon, iPierceResists, mod_ignore_resists_absorbs );
  5263. // Use defense buffs if it's not a backstab or direct crush damage (telefrage, etc.)
  5264. if ( pVictim && info.GetDamageCustom() != TF_DMG_CUSTOM_BACKSTAB && ( info.GetDamageType() & DMG_CRUSH ) == 0 )
  5265. {
  5266. if ( pVictim->m_Shared.InCond( TF_COND_DEFENSEBUFF ) )
  5267. {
  5268. // We take no crits of any kind...
  5269. if( eBonusEffect == kBonusEffect_MiniCrit || eBonusEffect == kBonusEffect_Crit )
  5270. eBonusEffect = kBonusEffect_None;
  5271. info.SetCritType( CTakeDamageInfo::CRIT_NONE );
  5272. bAllSeeCrit = false;
  5273. bShowDisguisedCrit = false;
  5274. pVictim->SetSeeCrit( bAllSeeCrit, false, bShowDisguisedCrit );
  5275. pVictim->SetAttackBonusEffect( eBonusEffect );
  5276. bitsDamage &= ~DMG_CRITICAL;
  5277. info.SetDamageType( bitsDamage );
  5278. info.SetCritType( CTakeDamageInfo::CRIT_NONE );
  5279. }
  5280. if ( !iPierceResists )
  5281. {
  5282. // If we are defense buffed...
  5283. if ( pVictim->m_Shared.InCond( TF_COND_DEFENSEBUFF_HIGH ) )
  5284. {
  5285. // We take 75% less damage... still take crits
  5286. info.SetDamage( info.GetDamage() * 0.25f );
  5287. }
  5288. else if ( pVictim->m_Shared.InCond( TF_COND_DEFENSEBUFF ) || pVictim->m_Shared.InCond( TF_COND_DEFENSEBUFF_NO_CRIT_BLOCK ) )
  5289. {
  5290. // defense buffs gives 50% to sentry dmg and 35% from all other sources
  5291. CObjectSentrygun *pSentry = ( info.GetInflictor() && info.GetInflictor()->IsBaseObject() ) ? dynamic_cast< CObjectSentrygun* >( info.GetInflictor() ) : NULL;
  5292. if ( pSentry )
  5293. {
  5294. info.SetDamage( info.GetDamage() * 0.50f );
  5295. }
  5296. else
  5297. {
  5298. // And we take 35% less damage...
  5299. info.SetDamage( info.GetDamage() * 0.65f );
  5300. }
  5301. }
  5302. }
  5303. }
  5304. // A note about why crits now go through the randomness/variance code:
  5305. // Normally critical damage is not affected by variance. However, we always want to measure what that variance
  5306. // would have been so that we can lump it into the DamageBonus value inside the info. This means crits actually
  5307. // boost more than 3X when you factor the reduction we avoided. Example: a rocket that normally would do 50
  5308. // damage due to range now does the original 100, which is then multiplied by 3, resulting in a 6x increase.
  5309. bool bCrit = ( bitsDamage & DMG_CRITICAL ) ? true : false;
  5310. // If we're not damaging ourselves, apply randomness
  5311. if ( pAttacker != pVictimBaseEntity && !(bitsDamage & (DMG_DROWN | DMG_FALL)) )
  5312. {
  5313. float flDamage = info.GetDamage();
  5314. float flDmgVariance = 0.f;
  5315. // Minicrits still get short range damage bonus
  5316. bool bForceCritFalloff = ( bitsDamage & DMG_USEDISTANCEMOD ) &&
  5317. ( ( bCrit && tf_weapon_criticals_distance_falloff.GetBool() ) ||
  5318. ( info.GetCritType() == CTakeDamageInfo::CRIT_MINI && tf_weapon_minicrits_distance_falloff.GetBool() ) );
  5319. bool bDoShortRangeDistanceIncrease = !bCrit || info.GetCritType() == CTakeDamageInfo::CRIT_MINI ;
  5320. bool bDoLongRangeDistanceDecrease = !bIgnoreLongRangeDmgEffects && ( bForceCritFalloff || ( !bCrit && info.GetCritType() != CTakeDamageInfo::CRIT_MINI ) );
  5321. // If we're doing any distance modification, we need to do that first
  5322. float flRandomDamage = info.GetDamage() * tf_damage_range.GetFloat();
  5323. float flRandomDamageSpread = 0.10f;
  5324. float flMin = 0.5 - flRandomDamageSpread;
  5325. float flMax = 0.5 + flRandomDamageSpread;
  5326. if ( bitsDamage & DMG_USEDISTANCEMOD )
  5327. {
  5328. Vector vAttackerPos = pAttacker->WorldSpaceCenter();
  5329. float flOptimalDistance = 512.0;
  5330. // Use Sentry position for distance mod
  5331. CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>( info.GetInflictor() );
  5332. if ( pSentry )
  5333. {
  5334. vAttackerPos = pSentry->WorldSpaceCenter();
  5335. // Sentries have a much further optimal distance
  5336. flOptimalDistance = SENTRY_MAX_RANGE;
  5337. }
  5338. // The base sniper rifle doesn't have DMG_USEDISTANCEMOD, so this isn't used. Unlockable rifle had it for a bit.
  5339. else if ( pWeapon && WeaponID_IsSniperRifle( pWeapon->GetWeaponID() ) )
  5340. {
  5341. flOptimalDistance *= 2.5f;
  5342. }
  5343. float flDistance = MAX( 1.0, ( pVictimBaseEntity->WorldSpaceCenter() - vAttackerPos).Length() );
  5344. float flCenter = RemapValClamped( flDistance / flOptimalDistance, 0.0, 2.0, 1.0, 0.0 );
  5345. if ( ( flCenter > 0.5 && bDoShortRangeDistanceIncrease ) || flCenter <= 0.5 )
  5346. {
  5347. if ( bitsDamage & DMG_NOCLOSEDISTANCEMOD )
  5348. {
  5349. if ( flCenter > 0.5 )
  5350. {
  5351. // Reduce the damage bonus at close range
  5352. flCenter = RemapVal( flCenter, 0.5, 1.0, 0.5, 0.65 );
  5353. }
  5354. }
  5355. flMin = MAX( 0.0, flCenter - flRandomDamageSpread );
  5356. flMax = MIN( 1.0, flCenter + flRandomDamageSpread );
  5357. if ( bDebug )
  5358. {
  5359. Warning(" RANDOM: Dist %.2f, Ctr: %.2f, Min: %.2f, Max: %.2f\n", flDistance, flCenter, flMin, flMax );
  5360. }
  5361. }
  5362. else
  5363. {
  5364. if ( bDebug )
  5365. {
  5366. Warning(" NO DISTANCE MOD: Dist %.2f, Ctr: %.2f, Min: %.2f, Max: %.2f\n", flDistance, flCenter, flMin, flMax );
  5367. }
  5368. }
  5369. }
  5370. //Msg("Range: %.2f - %.2f\n", flMin, flMax );
  5371. float flRandomRangeVal;
  5372. if ( tf_damage_disablespread.GetBool() || ( pTFAttacker && pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) )
  5373. {
  5374. flRandomRangeVal = flMin + flRandomDamageSpread;
  5375. }
  5376. else
  5377. {
  5378. flRandomRangeVal = RandomFloat( flMin, flMax );
  5379. }
  5380. //if ( bDebug )
  5381. //{
  5382. // Warning( " Val: %.2f\n", flRandomRangeVal );
  5383. //}
  5384. // Weapon Based Damage Mod
  5385. if ( pWeapon && pAttacker && pAttacker->IsPlayer() )
  5386. {
  5387. switch ( pWeapon->GetWeaponID() )
  5388. {
  5389. // Rocket launcher only has half the bonus of the other weapons at short range
  5390. // Rocket Launchers
  5391. case TF_WEAPON_ROCKETLAUNCHER :
  5392. case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT :
  5393. case TF_WEAPON_PARTICLE_CANNON :
  5394. if ( flRandomRangeVal > 0.5 )
  5395. {
  5396. flRandomDamage *= 0.5f;
  5397. }
  5398. break;
  5399. case TF_WEAPON_PIPEBOMBLAUNCHER : // Stickies
  5400. case TF_WEAPON_GRENADELAUNCHER :
  5401. case TF_WEAPON_CANNON :
  5402. case TF_WEAPON_STICKBOMB:
  5403. if ( !( bitsDamage & DMG_NOCLOSEDISTANCEMOD ) )
  5404. {
  5405. flRandomDamage *= 0.2f;
  5406. }
  5407. break;
  5408. case TF_WEAPON_SCATTERGUN :
  5409. case TF_WEAPON_SODA_POPPER :
  5410. case TF_WEAPON_PEP_BRAWLER_BLASTER :
  5411. //case TF_WEAPON_HANDGUN_SCOUT_PRIMARY : // Shortstop
  5412. // Scattergun gets 50% bonus at short range
  5413. if ( flRandomRangeVal > 0.5 )
  5414. {
  5415. flRandomDamage *= 1.5f;
  5416. }
  5417. break;
  5418. }
  5419. }
  5420. // Random damage variance.
  5421. flDmgVariance = SimpleSplineRemapValClamped( flRandomRangeVal, 0, 1, -flRandomDamage, flRandomDamage );
  5422. if ( ( bDoShortRangeDistanceIncrease && flDmgVariance > 0.f ) || bDoLongRangeDistanceDecrease )
  5423. {
  5424. flDamage = info.GetDamage() + flDmgVariance;
  5425. }
  5426. if ( bDebug )
  5427. {
  5428. Warning(" Out: %.2f -> Final %.2f\n", flDmgVariance, flDamage );
  5429. }
  5430. /*
  5431. for ( float flVal = flMin; flVal <= flMax; flVal += 0.05 )
  5432. {
  5433. float flOut = SimpleSplineRemapValClamped( flVal, 0, 1, -flRandomDamage, flRandomDamage );
  5434. Msg("Val: %.2f, Out: %.2f, Dmg: %.2f\n", flVal, flOut, info.GetDamage() + flOut );
  5435. }
  5436. */
  5437. // Burn sounds are handled in ConditionThink()
  5438. if ( !(bitsDamage & DMG_BURN ) && pVictim )
  5439. {
  5440. pVictim->SpeakConceptIfAllowed( MP_CONCEPT_HURT );
  5441. }
  5442. // Save any bonus damage as a separate value
  5443. float flCritDamage = 0.f;
  5444. // Yes, it's weird that we sometimes fabs flDmgVariance. Here's why: In the case of a crit rocket, we
  5445. // know that number will generally be negative due to dist or randomness. In this case, we want to track
  5446. // that effect - even if we don't apply it. In the case of our crit rocket that normally would lose 50
  5447. // damage, we fabs'd so that we can account for it as a bonus - since it's present in a crit.
  5448. float flBonusDamage = bForceCritFalloff ? 0.f : fabs( flDmgVariance );
  5449. CTFPlayer *pProvider = NULL;
  5450. if ( info.GetCritType() == CTakeDamageInfo::CRIT_MINI )
  5451. {
  5452. // We should never have both of these flags set or Weird Things will happen with the damage numbers
  5453. // that aren't clear to the players. Or us, really.
  5454. Assert( !(bitsDamage & DMG_CRITICAL) );
  5455. if ( bDebug )
  5456. {
  5457. Warning( " MINICRIT: Dmg %.2f -> ", flDamage );
  5458. }
  5459. COMPILE_TIME_ASSERT( TF_DAMAGE_MINICRIT_MULTIPLIER > 1.f );
  5460. flCritDamage = ( TF_DAMAGE_MINICRIT_MULTIPLIER - 1.f ) * flDamage;
  5461. bitsDamage |= DMG_CRITICAL;
  5462. info.AddDamageType( DMG_CRITICAL );
  5463. // Any condition assist stats to send out?
  5464. if ( eDamageBonusCond < TF_COND_LAST )
  5465. {
  5466. if ( pVictim )
  5467. {
  5468. pProvider = ToTFPlayer( pVictim->m_Shared.GetConditionProvider( eDamageBonusCond ) );
  5469. if ( pProvider )
  5470. {
  5471. CTF_GameStats.Event_PlayerDamageAssist( pProvider, flCritDamage + flBonusDamage );
  5472. }
  5473. }
  5474. if ( pTFAttacker )
  5475. {
  5476. pProvider = ToTFPlayer( pTFAttacker->m_Shared.GetConditionProvider( eDamageBonusCond ) );
  5477. if ( pProvider && pProvider != pTFAttacker )
  5478. {
  5479. CTF_GameStats.Event_PlayerDamageAssist( pProvider, flCritDamage + flBonusDamage );
  5480. }
  5481. }
  5482. }
  5483. if ( bDebug )
  5484. {
  5485. Warning( "reduced to %.2f before crit mult\n", flDamage );
  5486. }
  5487. }
  5488. if ( bCrit )
  5489. {
  5490. if ( info.GetCritType() != CTakeDamageInfo::CRIT_MINI )
  5491. {
  5492. COMPILE_TIME_ASSERT( TF_DAMAGE_CRIT_MULTIPLIER > 1.f );
  5493. flCritDamage = ( TF_DAMAGE_CRIT_MULTIPLIER - 1.f ) * flDamage;
  5494. }
  5495. if ( bDebug )
  5496. {
  5497. Warning( " CRITICAL! Damage: %.2f\n", flDamage );
  5498. }
  5499. // Burn sounds are handled in ConditionThink()
  5500. if ( !(bitsDamage & DMG_BURN ) && pVictim )
  5501. {
  5502. pVictim->SpeakConceptIfAllowed( MP_CONCEPT_HURT, "damagecritical:1" );
  5503. }
  5504. if ( pTFAttacker && pTFAttacker->m_Shared.IsCritBoosted() )
  5505. {
  5506. pProvider = ToTFPlayer( pTFAttacker->m_Shared.GetConditionProvider( TF_COND_CRITBOOSTED ) );
  5507. if ( pProvider && pTFAttacker && pProvider != pTFAttacker )
  5508. {
  5509. CTF_GameStats.Event_PlayerDamageAssist( pProvider, flCritDamage + flBonusDamage );
  5510. }
  5511. }
  5512. }
  5513. if ( pAttacker && pAttacker->IsPlayer() )
  5514. {
  5515. // Modify damage based on bonuses
  5516. flDamage *= pTFAttacker->m_Shared.GetTmpDamageBonus();
  5517. }
  5518. // Store the extra damage and update actual damage
  5519. if ( bCrit || info.GetCritType() == CTakeDamageInfo::CRIT_MINI )
  5520. {
  5521. info.SetDamageBonus( flCritDamage + flBonusDamage, pProvider ); // Order-of-operations sensitive, but fine as long as TF_COND_CRITBOOSTED is last
  5522. }
  5523. // Crit-A-Cola and Steak Sandwich - only increase normal damage
  5524. if ( pVictim && pVictim->m_Shared.InCond( TF_COND_ENERGY_BUFF ) && !bCrit && info.GetCritType() != CTakeDamageInfo::CRIT_MINI )
  5525. {
  5526. float flDmgMult = 1.f;
  5527. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDmgMult, energy_buff_dmg_taken_multiplier );
  5528. flDamage *= flDmgMult;
  5529. }
  5530. info.SetDamage( flDamage + flCritDamage );
  5531. }
  5532. if ( pTFAttacker && pTFAttacker->IsPlayerClass( TF_CLASS_SPY ) )
  5533. {
  5534. if ( pTFAttacker->GetActiveWeapon() )
  5535. {
  5536. int iAddCloakOnHit = 0;
  5537. CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFAttacker->GetActiveWeapon(), iAddCloakOnHit, add_cloak_on_hit );
  5538. if ( iAddCloakOnHit > 0 )
  5539. {
  5540. pTFAttacker->m_Shared.AddToSpyCloakMeter( iAddCloakOnHit, true );
  5541. }
  5542. }
  5543. }
  5544. // Apply on-hit attributes
  5545. if ( pVictim && pAttacker && pAttacker->GetTeam() != pVictim->GetTeam() && pAttacker->IsPlayer() && pWeapon )
  5546. {
  5547. pWeapon->ApplyOnHitAttributes( pVictimBaseEntity, pTFAttacker, info );
  5548. }
  5549. // Give assist points to the provider of any stun on the victim - up to half the damage, based on the amount of stun
  5550. if ( pVictim && pVictim->m_Shared.InCond( TF_COND_STUNNED ) )
  5551. {
  5552. CTFPlayer *pProvider = ToTFPlayer( pVictim->m_Shared.GetConditionProvider( TF_COND_STUNNED ) );
  5553. if ( pProvider && pTFAttacker && pProvider != pTFAttacker )
  5554. {
  5555. float flStunAmount = pVictim->m_Shared.GetAmountStunned( TF_STUN_MOVEMENT );
  5556. if ( flStunAmount < 1.f && pVictim->m_Shared.IsControlStunned() )
  5557. flStunAmount = 1.f;
  5558. int nAssistPoints = RemapValClamped( flStunAmount, 0.1f, 1.f, 1, ( info.GetDamage() / 2 ) );
  5559. if ( nAssistPoints )
  5560. {
  5561. CTF_GameStats.Event_PlayerDamageAssist( pProvider, nAssistPoints );
  5562. }
  5563. }
  5564. }
  5565. return true;
  5566. }
  5567. //-----------------------------------------------------------------------------
  5568. // Purpose:
  5569. //-----------------------------------------------------------------------------
  5570. static bool CheckForDamageTypeImmunity( int nDamageType, CTFPlayer* pVictim, float &flDamageBase, float &flCritBonusDamage )
  5571. {
  5572. bool bImmune = false;
  5573. if( nDamageType & (DMG_BURN|DMG_IGNITE) )
  5574. {
  5575. bImmune = pVictim->m_Shared.InCond( TF_COND_FIRE_IMMUNE );
  5576. }
  5577. else if( nDamageType & (DMG_BULLET|DMG_BUCKSHOT) )
  5578. {
  5579. bImmune = pVictim->m_Shared.InCond( TF_COND_BULLET_IMMUNE );
  5580. }
  5581. else if( nDamageType & DMG_BLAST )
  5582. {
  5583. bImmune = pVictim->m_Shared.InCond( TF_COND_BLAST_IMMUNE );
  5584. }
  5585. if( bImmune )
  5586. {
  5587. flDamageBase = flCritBonusDamage = 0.f;
  5588. IGameEvent* event = gameeventmanager->CreateEvent( "damage_resisted" );
  5589. if ( event )
  5590. {
  5591. event->SetInt( "entindex", pVictim->entindex() );
  5592. gameeventmanager->FireEvent( event );
  5593. }
  5594. return true;
  5595. }
  5596. return false;
  5597. }
  5598. //-----------------------------------------------------------------------------
  5599. // Purpose:
  5600. //-----------------------------------------------------------------------------
  5601. static bool CheckMedicResist( ETFCond ePassiveCond, ETFCond eDeployedCond, CTFPlayer* pVictim, float flRawDamage, float& flDamageBase, bool bCrit, float& flCritBonusDamage )
  5602. {
  5603. // Might be a tank or some other object that's getting shot
  5604. if( !pVictim || !pVictim->IsPlayer() )
  5605. return false;
  5606. ETFCond eActiveCond;
  5607. // Be in the condition!
  5608. if( pVictim->m_Shared.InCond( eDeployedCond ) )
  5609. {
  5610. eActiveCond = eDeployedCond;
  5611. }
  5612. else if( pVictim->m_Shared.InCond( ePassiveCond ) )
  5613. {
  5614. eActiveCond = ePassiveCond;
  5615. }
  5616. else
  5617. {
  5618. return false;
  5619. }
  5620. // Get our condition provider
  5621. CBaseEntity* pProvider = pVictim->m_Shared.GetConditionProvider( eActiveCond );
  5622. CTFPlayer* pTFProvider = ToTFPlayer( pProvider );
  5623. Assert( pTFProvider );
  5624. float flDamageScale = 0.f;
  5625. //float flCritBarDeplete = 0.f;
  5626. bool bUberResist = false;
  5627. if( pTFProvider )
  5628. {
  5629. switch( eActiveCond )
  5630. {
  5631. case TF_COND_MEDIGUN_UBER_BULLET_RESIST:
  5632. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flDamageScale, medigun_bullet_resist_deployed );
  5633. bUberResist = true;
  5634. // if ( bCrit )
  5635. // {
  5636. // CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flCritBarDeplete, medigun_crit_bullet_percent_bar_deplete );
  5637. // }
  5638. break;
  5639. case TF_COND_MEDIGUN_SMALL_BULLET_RESIST:
  5640. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flDamageScale, medigun_bullet_resist_passive );
  5641. break;
  5642. case TF_COND_MEDIGUN_UBER_BLAST_RESIST:
  5643. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flDamageScale, medigun_blast_resist_deployed );
  5644. bUberResist = true;
  5645. //if( bCrit )
  5646. //{
  5647. // CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flCritBarDeplete, medigun_crit_blast_percent_bar_deplete );
  5648. //}
  5649. break;
  5650. case TF_COND_MEDIGUN_SMALL_BLAST_RESIST:
  5651. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flDamageScale, medigun_blast_resist_passive );
  5652. break;
  5653. case TF_COND_MEDIGUN_UBER_FIRE_RESIST:
  5654. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flDamageScale, medigun_fire_resist_deployed );
  5655. bUberResist = true;
  5656. // if( bCrit )
  5657. // {
  5658. // CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flCritBarDeplete, medigun_crit_fire_percent_bar_deplete );
  5659. // }
  5660. break;
  5661. case TF_COND_MEDIGUN_SMALL_FIRE_RESIST:
  5662. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFProvider, flDamageScale, medigun_fire_resist_passive );
  5663. break;
  5664. }
  5665. }
  5666. if ( bUberResist && pVictim->m_Shared.InCond( TF_COND_HEALING_DEBUFF ) )
  5667. {
  5668. flDamageScale *= 0.75f;
  5669. }
  5670. flDamageScale = 1.f - flDamageScale;
  5671. if( flDamageScale < 0.f )
  5672. flDamageScale = 0.f;
  5673. //// Dont let the medic heal themselves when they take damage
  5674. //if( pTFProvider && pTFProvider != pVictim )
  5675. //{
  5676. // // Heal the medic for 10% of the incoming damage
  5677. // int nHeal = flRawDamage * 0.10f;
  5678. // // Heal!
  5679. // int iHealed = pTFProvider->TakeHealth( nHeal, DMG_GENERIC );
  5680. // // Tell them about it!
  5681. // if ( iHealed )
  5682. // {
  5683. // CTF_GameStats.Event_PlayerHealedOther( pTFProvider, iHealed );
  5684. // }
  5685. // IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" );
  5686. // if ( event )
  5687. // {
  5688. // event->SetInt( "amount", nHeal );
  5689. // event->SetInt( "entindex", pTFProvider->entindex() );
  5690. // gameeventmanager->FireEvent( event );
  5691. // }
  5692. //}
  5693. IGameEvent* event = gameeventmanager->CreateEvent( "damage_resisted" );
  5694. if ( event )
  5695. {
  5696. event->SetInt( "entindex", pVictim->entindex() );
  5697. gameeventmanager->FireEvent( event );
  5698. }
  5699. if ( bCrit && pTFProvider && bUberResist )
  5700. {
  5701. flCritBonusDamage = ( pVictim->m_Shared.InCond( TF_COND_HEALING_DEBUFF ) ) ? flCritBonusDamage *= 0.25f : 0.f;
  5702. //CWeaponMedigun* pMedigun = dynamic_cast<CWeaponMedigun*>( pTFProvider->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) );
  5703. //if( pMedigun )
  5704. //{
  5705. // if( pMedigun->GetChargeLevel() > 0.f && pMedigun->IsReleasingCharge() )
  5706. // {
  5707. // flCritBonusDamage = 0;
  5708. // }
  5709. // pMedigun->SubtractCharge( flCritBarDeplete );
  5710. //}
  5711. }
  5712. // Scale the damage!
  5713. flDamageBase = flDamageBase * flDamageScale;
  5714. return true;
  5715. }
  5716. //-----------------------------------------------------------------------------
  5717. // Purpose:
  5718. //-----------------------------------------------------------------------------
  5719. static void PotentiallyFireDamageMitigatedEvent( const CTFPlayer* pMitigator, const CTFPlayer* pDamaged, const CEconEntity* pMitigationProvidingEconItem, float flBeforeDamage, float flAfterDamage )
  5720. {
  5721. int nAmount = flBeforeDamage - flAfterDamage;
  5722. // Nothing mitigated!
  5723. if ( nAmount == 0 )
  5724. return;
  5725. IGameEvent *pEvent = gameeventmanager->CreateEvent( "damage_mitigated" );
  5726. if ( pEvent )
  5727. {
  5728. item_definition_index_t nDefIndex = INVALID_ITEM_DEF_INDEX;
  5729. if ( pMitigationProvidingEconItem && pMitigationProvidingEconItem->GetAttributeContainer() && pMitigationProvidingEconItem->GetAttributeContainer()->GetItem() )
  5730. {
  5731. nDefIndex = pMitigationProvidingEconItem->GetAttributeContainer()->GetItem()->GetItemDefinition()->GetDefinitionIndex();
  5732. }
  5733. pEvent->SetInt( "mitigator", pMitigator ? pMitigator->GetUserID() : -1 );
  5734. pEvent->SetInt( "damaged", pDamaged ? pDamaged->GetUserID() : -1 );
  5735. pEvent->SetInt( "amount", nAmount );
  5736. pEvent->SetInt( "itemdefindex", nDefIndex );
  5737. gameeventmanager->FireEvent( pEvent, true );
  5738. }
  5739. }
  5740. //-----------------------------------------------------------------------------
  5741. // Purpose:
  5742. //-----------------------------------------------------------------------------
  5743. float CTFGameRules::ApplyOnDamageAliveModifyRules( const CTakeDamageInfo &info, CBaseEntity *pVictimBaseEntity, DamageModifyExtras_t& outParams )
  5744. {
  5745. CTFPlayer *pVictim = ToTFPlayer( pVictimBaseEntity );
  5746. CBaseEntity *pAttacker = info.GetAttacker();
  5747. CTFPlayer *pTFAttacker = ToTFPlayer( pAttacker );
  5748. float flRealDamage = info.GetDamage();
  5749. if ( pVictimBaseEntity && pVictimBaseEntity->m_takedamage != DAMAGE_EVENTS_ONLY && pVictim )
  5750. {
  5751. int iDamageTypeBits = info.GetDamageType() & DMG_IGNITE;
  5752. // Handle attributes that want to change our damage type, but only if we're taking damage from a non-DOT. This
  5753. // stops fire DOT damage from constantly reigniting us. This will also prevent ignites from happening on the
  5754. // damage *from-a-bleed-DOT*, but not from the bleed application attack.
  5755. if ( !IsDOTDmg( info.GetDamageCustom() ) )
  5756. {
  5757. int iAddBurningDamageType = 0;
  5758. CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iAddBurningDamageType, set_dmgtype_ignite );
  5759. if ( iAddBurningDamageType )
  5760. {
  5761. iDamageTypeBits |= DMG_IGNITE;
  5762. }
  5763. }
  5764. // Start burning if we took ignition damage
  5765. outParams.bIgniting = ( ( iDamageTypeBits & DMG_IGNITE ) && ( !pVictim || pVictim->GetWaterLevel() < WL_Waist ) );
  5766. if ( outParams.bIgniting && pVictim )
  5767. {
  5768. if ( pVictim->m_Shared.InCond( TF_COND_DISGUISED ) )
  5769. {
  5770. int iDisguiseNoBurn = 0;
  5771. CALL_ATTRIB_HOOK_INT_ON_OTHER( pVictim, iDisguiseNoBurn, disguise_no_burn );
  5772. if ( iDisguiseNoBurn == 1 )
  5773. {
  5774. // Do a hard out in the caller
  5775. return -1;
  5776. }
  5777. }
  5778. if ( pVictim->m_Shared.InCond( TF_COND_FIRE_IMMUNE ) )
  5779. {
  5780. // Do a hard out in the caller
  5781. return -1;
  5782. }
  5783. }
  5784. // When obscured by smoke, attacks have a chance to miss
  5785. if ( pVictim && pVictim->m_Shared.InCond( TF_COND_OBSCURED_SMOKE ) )
  5786. {
  5787. if ( RandomInt( 1, 4 ) >= 2 )
  5788. {
  5789. flRealDamage = 0.f;
  5790. pVictim->SpeakConceptIfAllowed( MP_CONCEPT_DODGE_SHOT );
  5791. if ( pTFAttacker )
  5792. {
  5793. CEffectData data;
  5794. data.m_nHitBox = GetParticleSystemIndex( "miss_text" );
  5795. data.m_vOrigin = pVictim->WorldSpaceCenter() + Vector( 0.f , 0.f, 32.f );
  5796. data.m_vAngles = vec3_angle;
  5797. data.m_nEntIndex = 0;
  5798. CSingleUserRecipientFilter filter( pTFAttacker );
  5799. te->DispatchEffect( filter, 0.f, data.m_vOrigin, "ParticleEffect", data );
  5800. }
  5801. // No damage
  5802. return -1.f;
  5803. }
  5804. }
  5805. // Proc invicibility upon being hit
  5806. float flUberChance = 0.f;
  5807. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flUberChance, uber_on_damage_taken );
  5808. if( RandomFloat() < flUberChance )
  5809. {
  5810. pVictim->m_Shared.AddCond( TF_COND_INVULNERABLE_CARD_EFFECT, 3.f );
  5811. // Make sure we don't take any damage
  5812. flRealDamage = 0.f;
  5813. }
  5814. // Resists and Boosts
  5815. float flDamageBonus = info.GetDamageBonus();
  5816. float flDamageBase = flRealDamage - flDamageBonus;
  5817. Assert( flDamageBase >= 0.f );
  5818. int iPierceResists = 0;
  5819. CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iPierceResists, mod_pierce_resists_absorbs );
  5820. // This raw damage wont get scaled. Used for determining how much health to give resist medics.
  5821. float flRawDamage = flDamageBase;
  5822. // Check if we're immune
  5823. outParams.bPlayDamageReductionSound = CheckForDamageTypeImmunity( info.GetDamageType(), pVictim, flDamageBase, flDamageBonus );
  5824. if ( !iPierceResists )
  5825. {
  5826. // Reduce only the crit portion of the damage with crit resist
  5827. bool bCrit = ( info.GetDamageType() & DMG_CRITICAL ) > 0;
  5828. if ( bCrit )
  5829. {
  5830. // Break the damage down and reassemble
  5831. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDamageBonus, mult_dmgtaken_from_crit );
  5832. }
  5833. // Apply general dmg type reductions. Should we only ever apply one of these? (Flaregun is DMG_BULLET|DMG_IGNITE, for instance)
  5834. if ( info.GetDamageType() & (DMG_BURN|DMG_IGNITE) )
  5835. {
  5836. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDamageBase, mult_dmgtaken_from_fire );
  5837. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim->GetActiveWeapon(), flDamageBase, mult_dmgtaken_from_fire_active );
  5838. // Check for medic resist
  5839. outParams.bPlayDamageReductionSound = CheckMedicResist( TF_COND_MEDIGUN_SMALL_FIRE_RESIST, TF_COND_MEDIGUN_UBER_FIRE_RESIST, pVictim, flRawDamage, flDamageBase, bCrit, flDamageBonus );
  5840. }
  5841. if ( pTFAttacker && pVictim && pVictim->m_Shared.InCond( TF_COND_BURNING ) )
  5842. {
  5843. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFAttacker->GetActiveWeapon(), flDamageBase, mult_dmg_vs_burning );
  5844. }
  5845. if ( (info.GetDamageType() & (DMG_BLAST) ) )
  5846. {
  5847. bool bReduceBlast = false;
  5848. // If someone else shot us or we're in MvM
  5849. if( pAttacker != pVictimBaseEntity || IsMannVsMachineMode() )
  5850. {
  5851. bReduceBlast = true;
  5852. }
  5853. if ( bReduceBlast )
  5854. {
  5855. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDamageBase, mult_dmgtaken_from_explosions );
  5856. // Check for medic resist
  5857. outParams.bPlayDamageReductionSound = CheckMedicResist( TF_COND_MEDIGUN_SMALL_BLAST_RESIST, TF_COND_MEDIGUN_UBER_BLAST_RESIST, pVictim, flRawDamage, flDamageBase, bCrit, flDamageBonus );
  5858. }
  5859. }
  5860. if ( info.GetDamageType() & (DMG_BULLET|DMG_BUCKSHOT) )
  5861. {
  5862. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDamageBase, mult_dmgtaken_from_bullets );
  5863. // Check for medic resist
  5864. outParams.bPlayDamageReductionSound = CheckMedicResist( TF_COND_MEDIGUN_SMALL_BULLET_RESIST, TF_COND_MEDIGUN_UBER_BULLET_RESIST, pVictim, flRawDamage, flDamageBase, bCrit, flDamageBonus );
  5865. }
  5866. if ( info.GetDamageType() & DMG_MELEE )
  5867. {
  5868. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDamageBase, mult_dmgtaken_from_melee );
  5869. }
  5870. if ( pVictim->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && pVictim->m_Shared.InCond( TF_COND_AIMING ) && ( ( pVictim->GetHealth() - flRealDamage ) / pVictim->GetMaxHealth() ) <= 0.5f )
  5871. {
  5872. float flOriginalDamage = flDamageBase;
  5873. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flDamageBase, spunup_damage_resistance );
  5874. if ( flOriginalDamage != flDamageBase )
  5875. {
  5876. pVictim->PlayDamageResistSound( flOriginalDamage, flDamageBase );
  5877. }
  5878. }
  5879. }
  5880. // If the damage changed at all play the resist sound
  5881. if ( flDamageBase != flRawDamage )
  5882. {
  5883. outParams.bPlayDamageReductionSound = true;
  5884. }
  5885. // Stomp flRealDamage with resist adjusted values
  5886. flRealDamage = flDamageBase + flDamageBonus;
  5887. // Some Powerups apply a damage multiplier. Backstabs are immune to resist protection
  5888. if ( ( pVictim && info.GetDamageCustom() != TF_DMG_CUSTOM_BACKSTAB ) )
  5889. {
  5890. // Plague bleed damage is immune from resist calculation
  5891. if ( ( !pVictim->m_Shared.InCond( TF_COND_PLAGUE ) && info.GetDamageCustom() != TF_DMG_CUSTOM_BLEEDING ) )
  5892. {
  5893. if ( pVictim->m_Shared.GetCarryingRuneType() == RUNE_RESIST )
  5894. {
  5895. flRealDamage *= 0.5f;
  5896. outParams.bPlayDamageReductionSound = true;
  5897. IGameEvent* event = gameeventmanager->CreateEvent( "damage_resisted" );
  5898. if ( event )
  5899. {
  5900. event->SetInt( "entindex", pVictim->entindex() );
  5901. gameeventmanager->FireEvent( event );
  5902. }
  5903. }
  5904. else if ( ( pVictim->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE ) )
  5905. {
  5906. flRealDamage *= 0.75f;
  5907. outParams.bPlayDamageReductionSound = true;
  5908. }
  5909. //Plague powerup carrier is resistant to infected enemies
  5910. else if ( pTFAttacker && ( pVictim->m_Shared.GetCarryingRuneType() == RUNE_PLAGUE ) && pTFAttacker->m_Shared.InCond( TF_COND_PLAGUE ) )
  5911. {
  5912. flRealDamage *= 0.5f;
  5913. outParams.bPlayDamageReductionSound = true;
  5914. }
  5915. }
  5916. }
  5917. // End Resists
  5918. // Increased damage taken from all sources
  5919. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flRealDamage, mult_dmgtaken );
  5920. if ( info.GetInflictor() && info.GetInflictor()->IsBaseObject() )
  5921. {
  5922. CObjectSentrygun* pSentry = dynamic_cast<CObjectSentrygun*>( info.GetInflictor() );
  5923. if ( pSentry )
  5924. {
  5925. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim, flRealDamage, dmg_from_sentry_reduced );
  5926. }
  5927. }
  5928. if ( IsMannVsMachineMode() )
  5929. {
  5930. if ( pTFAttacker && pTFAttacker->IsBot() && pAttacker != pVictimBaseEntity && pVictim && !pVictim->IsBot() )
  5931. {
  5932. flRealDamage *= g_pPopulationManager ? g_pPopulationManager->GetDamageMultiplier() : tf_populator_damage_multiplier.GetFloat();
  5933. }
  5934. }
  5935. // Heavy rage-based knockback+stun effect that also reduces their damage output
  5936. if ( pTFAttacker && pTFAttacker->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
  5937. {
  5938. int iRage = 0;
  5939. CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFAttacker, iRage, generate_rage_on_dmg );
  5940. if ( iRage && pTFAttacker->m_Shared.IsRageDraining() )
  5941. {
  5942. flRealDamage *= 0.5f;
  5943. }
  5944. }
  5945. if ( pVictim && pTFAttacker && info.GetWeapon() )
  5946. {
  5947. CTFWeaponBase *pWeapon = pTFAttacker->GetActiveTFWeapon();
  5948. if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SNIPERRIFLE && info.GetWeapon() == pWeapon )
  5949. {
  5950. CTFSniperRifle *pRifle = static_cast< CTFSniperRifle* >( info.GetWeapon() );
  5951. float flStun = 1.0f;
  5952. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pRifle, flStun, applies_snare_effect );
  5953. if ( flStun != 1.0f )
  5954. {
  5955. float flDuration = pRifle->GetJarateTime();
  5956. pVictim->m_Shared.StunPlayer( flDuration, flStun, TF_STUN_MOVEMENT, pTFAttacker );
  5957. }
  5958. }
  5959. }
  5960. if ( pVictim && pVictim->GetActiveTFWeapon() )
  5961. {
  5962. if ( info.GetDamageType() & (DMG_CLUB) )
  5963. {
  5964. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim->GetActiveTFWeapon(), flRealDamage, dmg_from_melee );
  5965. }
  5966. else if ( info.GetDamageType() & (DMG_BLAST|DMG_BULLET|DMG_BUCKSHOT|DMG_IGNITE|DMG_SONIC) )
  5967. {
  5968. float flBeforeDamage = flRealDamage;
  5969. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pVictim->GetActiveTFWeapon(), flRealDamage, dmg_from_ranged );
  5970. PotentiallyFireDamageMitigatedEvent( pVictim, pVictim, pVictim->GetActiveTFWeapon(), flBeforeDamage, flRealDamage );
  5971. }
  5972. }
  5973. outParams.bSendPreFeignDamage = false;
  5974. if ( pVictim && pVictim->IsPlayerClass( TF_CLASS_SPY ) && ( info.GetDamageCustom() != TF_DMG_CUSTOM_TELEFRAG ) && !pVictim->IsTaunting() )
  5975. {
  5976. // STAGING_SPY
  5977. // Reduce damage taken if we have recently feigned death.
  5978. if ( pVictim->m_Shared.InCond( TF_COND_FEIGN_DEATH ) || pVictim->m_Shared.IsFeignDeathReady() )
  5979. {
  5980. // Damage reduction is proportional to cloak remaining (60%->20%)
  5981. float flDamageReduction = RemapValClamped( pVictim->m_Shared.GetSpyCloakMeter(), 50.0f, 0.0f, tf_feign_death_damage_scale.GetFloat(), tf_stealth_damage_reduction.GetFloat() );
  5982. // On Activate Reduce Remaining Cloak by 50%
  5983. if ( pVictim->m_Shared.IsFeignDeathReady() )
  5984. {
  5985. flDamageReduction = tf_feign_death_activate_damage_scale.GetFloat();
  5986. }
  5987. outParams.bSendPreFeignDamage = true;
  5988. float flBeforeflRealDamage = flRealDamage;
  5989. flRealDamage *= flDamageReduction;
  5990. CTFWeaponInvis *pWatch = (CTFWeaponInvis *) pVictim->Weapon_OwnsThisID( TF_WEAPON_INVIS );
  5991. PotentiallyFireDamageMitigatedEvent( pVictim, pVictim, pWatch, flBeforeflRealDamage, flRealDamage );
  5992. // Original damage would've killed the player, but the reduced damage wont
  5993. if ( flBeforeflRealDamage >= pVictim->GetHealth() && flRealDamage < pVictim->GetHealth() )
  5994. {
  5995. IGameEvent *pEvent = gameeventmanager->CreateEvent( "deadringer_cheat_death" );
  5996. if ( pEvent )
  5997. {
  5998. pEvent->SetInt( "spy", pVictim->GetUserID() );
  5999. pEvent->SetInt( "attacker", pTFAttacker ? pTFAttacker->GetUserID() : -1 );
  6000. gameeventmanager->FireEvent( pEvent, true );
  6001. }
  6002. }
  6003. }
  6004. // Standard Stealth gives small damage reduction
  6005. else if ( pVictim->m_Shared.InCond( TF_COND_STEALTHED ) )
  6006. {
  6007. flRealDamage *= tf_stealth_damage_reduction.GetFloat();
  6008. }
  6009. }
  6010. if ( flRealDamage == 0.0f )
  6011. {
  6012. // Do a hard out in the caller
  6013. return -1;
  6014. }
  6015. if ( pAttacker == pVictimBaseEntity && (info.GetDamageType() & DMG_BLAST) &&
  6016. info.GetDamagedOtherPlayers() == 0 && (info.GetDamageCustom() != TF_DMG_CUSTOM_TAUNTATK_GRENADE) )
  6017. {
  6018. // If we attacked ourselves, hurt no other players, and it is a blast,
  6019. // check the attribute that reduces rocket jump damage.
  6020. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( info.GetAttacker(), flRealDamage, rocket_jump_dmg_reduction );
  6021. outParams.bSelfBlastDmg = true;
  6022. }
  6023. if ( pAttacker == pVictimBaseEntity )
  6024. {
  6025. enum
  6026. {
  6027. kSelfBlastResponse_IgnoreProjectilesFromThisWeapon = 1, // the sticky jumper doesn't disable damage from other explosive weapons
  6028. kSelfBlastResponse_IgnoreProjectilesFromAllWeapons = 2, // the rocket jumper doesn't have a special projectile type and so ignores all self-inflicted damage from explosive sources
  6029. };
  6030. if ( info.GetWeapon() )
  6031. {
  6032. int iNoSelfBlastDamage = 0;
  6033. CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iNoSelfBlastDamage, no_self_blast_dmg );
  6034. const bool bIgnoreThisSelfDamage = ( iNoSelfBlastDamage == kSelfBlastResponse_IgnoreProjectilesFromAllWeapons )
  6035. || ( (iNoSelfBlastDamage == kSelfBlastResponse_IgnoreProjectilesFromThisWeapon) && (info.GetDamageCustom() == TF_DMG_CUSTOM_PRACTICE_STICKY) );
  6036. if ( bIgnoreThisSelfDamage )
  6037. {
  6038. flRealDamage = 0;
  6039. }
  6040. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( info.GetWeapon(), flRealDamage, blast_dmg_to_self );
  6041. }
  6042. }
  6043. // Precision Powerup removes self damage
  6044. if ( pTFAttacker == pVictim && pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_PRECISION )
  6045. {
  6046. flRealDamage = 0.f;
  6047. }
  6048. if ( pTFAttacker && ( pTFAttacker != pVictim ) )
  6049. {
  6050. // Vampire Powerup collects health based on damage received on victim. Does not apply to self damage. Do it here to factor in victim resistance calculations
  6051. if ( pTFAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE )
  6052. {
  6053. if ( flRealDamage > 0 )
  6054. {
  6055. if ( pTFAttacker->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_MINIGUN || pTFAttacker->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_FLAMETHROWER )
  6056. {
  6057. pTFAttacker->TakeHealth( ( flRealDamage * 0.6f ), DMG_GENERIC );
  6058. }
  6059. else if ( info.GetDamageType() & DMG_MELEE )
  6060. {
  6061. pTFAttacker->TakeHealth( ( flRealDamage * 1.25f ), DMG_GENERIC );
  6062. }
  6063. else
  6064. {
  6065. pTFAttacker->TakeHealth( flRealDamage, DMG_GENERIC );
  6066. }
  6067. }
  6068. }
  6069. int iHypeOnDamage = 0;
  6070. CALL_ATTRIB_HOOK_INT_ON_OTHER( pTFAttacker, iHypeOnDamage, hype_on_damage );
  6071. if ( iHypeOnDamage )
  6072. {
  6073. float flHype = RemapValClamped( flRealDamage, 1.f, 200.f, 1.f, 50.f );
  6074. pTFAttacker->m_Shared.SetScoutHypeMeter( Min( 100.f, flHype + pTFAttacker->m_Shared.GetScoutHypeMeter() ) );
  6075. }
  6076. }
  6077. }
  6078. return flRealDamage;
  6079. }
  6080. // --------------------------------------------------------------------------------------------------- //
  6081. // Voice helper
  6082. // --------------------------------------------------------------------------------------------------- //
  6083. class CVoiceGameMgrHelper : public IVoiceGameMgrHelper
  6084. {
  6085. public:
  6086. virtual bool CanPlayerHearPlayer( CBasePlayer *pListener, CBasePlayer *pTalker, bool &bProximity )
  6087. {
  6088. return TFGameRules()->TFVoiceManager( pListener, pTalker );
  6089. }
  6090. };
  6091. CVoiceGameMgrHelper g_VoiceGameMgrHelper;
  6092. IVoiceGameMgrHelper *g_pVoiceGameMgrHelper = &g_VoiceGameMgrHelper;
  6093. // Load the objects.txt file.
  6094. class CObjectsFileLoad : public CAutoGameSystem
  6095. {
  6096. public:
  6097. virtual bool Init()
  6098. {
  6099. LoadObjectInfos( filesystem );
  6100. return true;
  6101. }
  6102. } g_ObjectsFileLoad;
  6103. // --------------------------------------------------------------------------------------------------- //
  6104. // Globals.
  6105. // --------------------------------------------------------------------------------------------------- //
  6106. /*
  6107. // NOTE: the indices here must match TEAM_UNASSIGNED, TEAM_SPECTATOR, TF_TEAM_RED, TF_TEAM_BLUE, etc.
  6108. char *sTeamNames[] =
  6109. {
  6110. "Unassigned",
  6111. "Spectator",
  6112. "Red",
  6113. "Blue"
  6114. };
  6115. */
  6116. // --------------------------------------------------------------------------------------------------- //
  6117. // Global helper functions.
  6118. // --------------------------------------------------------------------------------------------------- //
  6119. // World.cpp calls this but we don't use it in TF.
  6120. void InitBodyQue()
  6121. {
  6122. }
  6123. //-----------------------------------------------------------------------------
  6124. // Purpose:
  6125. //-----------------------------------------------------------------------------
  6126. CTFGameRules::~CTFGameRules()
  6127. {
  6128. // Note, don't delete each team since they are in the gEntList and will
  6129. // automatically be deleted from there, instead.
  6130. TFTeamMgr()->Shutdown();
  6131. ShutdownCustomResponseRulesDicts();
  6132. // clean up cached teleport locations
  6133. m_mapTeleportLocations.PurgeAndDeleteElements();
  6134. // reset this only if we quit MvM to minimize the risk of breaking pub tournament
  6135. if ( IsMannVsMachineMode() )
  6136. {
  6137. mp_tournament.SetValue( 0 );
  6138. }
  6139. }
  6140. //-----------------------------------------------------------------------------
  6141. // Purpose:
  6142. //-----------------------------------------------------------------------------
  6143. void CTFGameRules::CheckTauntAchievement( CTFPlayer *pAchiever, int nGibs, int *pTauntCamAchievements )
  6144. {
  6145. if ( !pAchiever || !pAchiever->GetPlayerClass() )
  6146. return;
  6147. int iClass = pAchiever->GetPlayerClass()->GetClassIndex();
  6148. if ( pTauntCamAchievements[ iClass ] )
  6149. {
  6150. bool bAwardAchievement = true;
  6151. // for the Heavy achievement, the player needs to also be invuln
  6152. if ( iClass == TF_CLASS_HEAVYWEAPONS && pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_HEAVY_FREEZECAM_TAUNT )
  6153. {
  6154. if ( !pAchiever->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) && !pAchiever->m_Shared.InCond( TF_COND_INVULNERABLE ) )
  6155. {
  6156. bAwardAchievement = false;
  6157. }
  6158. }
  6159. // for the Spy achievement, we must be in the cig lighter taunt
  6160. if ( iClass == TF_CLASS_SPY && pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_SPY_FREEZECAM_FLICK )
  6161. {
  6162. if ( pAchiever->GetActiveTFWeapon() && pAchiever->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_PDA_SPY )
  6163. {
  6164. bAwardAchievement = false;
  6165. }
  6166. }
  6167. // for the two Sniper achievements, we need to check for specific taunts
  6168. if ( iClass == TF_CLASS_SNIPER )
  6169. {
  6170. if ( pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_SNIPER_FREEZECAM_HAT )
  6171. {
  6172. if ( pAchiever->GetActiveTFWeapon() && pAchiever->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_CLUB )
  6173. {
  6174. bAwardAchievement = false;
  6175. }
  6176. }
  6177. else if ( pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_SNIPER_FREEZECAM_WAVE )
  6178. {
  6179. if ( pAchiever->GetActiveTFWeapon() && WeaponID_IsSniperRifle( pAchiever->GetActiveTFWeapon()->GetWeaponID() ) )
  6180. {
  6181. bAwardAchievement = false;
  6182. }
  6183. }
  6184. }
  6185. // For the Soldier achievements, we need to be doing a specific taunt, or have enough gibs onscreen
  6186. if ( iClass == TF_CLASS_SOLDIER )
  6187. {
  6188. if ( pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_SOLDIER_FREEZECAM_TAUNT )
  6189. {
  6190. if ( pAchiever->GetActiveTFWeapon() && pAchiever->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_SHOTGUN_SOLDIER )
  6191. {
  6192. bAwardAchievement = false;
  6193. }
  6194. }
  6195. else if ( pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_SOLDIER_FREEZECAM_GIBS )
  6196. {
  6197. // Need at least 3 gibs on-screen
  6198. if ( nGibs < 3 )
  6199. {
  6200. bAwardAchievement = false;
  6201. }
  6202. }
  6203. }
  6204. // for the two Demoman achievements, we need to check for specific taunts
  6205. if ( iClass == TF_CLASS_DEMOMAN )
  6206. {
  6207. if ( pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_DEMOMAN_FREEZECAM_SMILE )
  6208. {
  6209. if ( pAchiever->GetActiveTFWeapon() && pAchiever->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_GRENADELAUNCHER )
  6210. {
  6211. bAwardAchievement = false;
  6212. }
  6213. }
  6214. else if ( pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_DEMOMAN_FREEZECAM_RUMP )
  6215. {
  6216. if ( pAchiever->GetActiveTFWeapon() && pAchiever->GetActiveTFWeapon()->GetAttributeContainer() )
  6217. {
  6218. // Needs to be the Scottish Defender
  6219. CEconItemView *pItem = pAchiever->GetActiveTFWeapon()->GetAttributeContainer()->GetItem();
  6220. if ( pItem && pItem->IsValid() && pItem->GetItemDefIndex() != 130 ) // Scottish Defender is item index 130
  6221. {
  6222. bAwardAchievement = false;
  6223. }
  6224. }
  6225. }
  6226. }
  6227. // for the Engineer achievement, we must be in the guitar taunt
  6228. if ( iClass == TF_CLASS_ENGINEER && pTauntCamAchievements[ iClass ] == ACHIEVEMENT_TF_ENGINEER_FREEZECAM_TAUNT )
  6229. {
  6230. if ( pAchiever->GetActiveTFWeapon() && pAchiever->GetActiveTFWeapon()->GetWeaponID() != TF_WEAPON_SENTRY_REVENGE )
  6231. {
  6232. bAwardAchievement = false;
  6233. }
  6234. }
  6235. if ( bAwardAchievement )
  6236. {
  6237. pAchiever->AwardAchievement( pTauntCamAchievements[ iClass ] );
  6238. }
  6239. }
  6240. }
  6241. //-----------------------------------------------------------------------------
  6242. // Purpose:
  6243. //-----------------------------------------------------------------------------
  6244. bool CTFGameRules::TFVoiceManager( CBasePlayer *pListener, CBasePlayer *pTalker )
  6245. {
  6246. // check coaching--we only want coaches and students to talk and listen to each other!
  6247. CTFPlayer* pTFListener = (CTFPlayer*)pListener;
  6248. CTFPlayer* pTFTalker = (CTFPlayer*)pTalker;
  6249. if ( pTFListener->GetStudent() || pTFListener->GetCoach() ||
  6250. pTFTalker->GetStudent() || pTFTalker->GetCoach() )
  6251. {
  6252. if ( pTFListener->GetStudent() == pTFTalker || pTFTalker->GetStudent() == pTFListener ||
  6253. pTFListener->GetCoach() == pTalker || pTFTalker->GetCoach() == pTFListener )
  6254. {
  6255. return true;
  6256. }
  6257. return false;
  6258. }
  6259. // Always allow teams to hear each other in TD mode
  6260. if ( IsMannVsMachineMode() )
  6261. return true;
  6262. if ( !tf_gravetalk.GetBool() )
  6263. {
  6264. // Dead players can only be heard by other dead team mates but only if a match is in progress
  6265. if ( State_Get() != GR_STATE_TEAM_WIN && State_Get() != GR_STATE_GAME_OVER )
  6266. {
  6267. if ( pTalker->IsAlive() == false )
  6268. {
  6269. if ( pListener->IsAlive() == false )
  6270. return ( pListener->InSameTeam( pTalker ) );
  6271. return false;
  6272. }
  6273. }
  6274. }
  6275. return ( pListener->InSameTeam( pTalker ) );
  6276. }
  6277. //-----------------------------------------------------------------------------
  6278. // Purpose: TF2 Specific Client Commands
  6279. // Input :
  6280. // Output :
  6281. //-----------------------------------------------------------------------------
  6282. bool CTFGameRules::ClientCommand( CBaseEntity *pEdict, const CCommand &args )
  6283. {
  6284. CTFPlayer *pPlayer = ToTFPlayer( pEdict );
  6285. const char *pcmd = args[0];
  6286. if ( IsInTournamentMode() == true && IsInPreMatch() == true )
  6287. {
  6288. if ( FStrEq( pcmd, "tournament_readystate" ) )
  6289. {
  6290. if ( IsMannVsMachineMode() )
  6291. return true;
  6292. if ( UsePlayerReadyStatusMode() )
  6293. return true;
  6294. if ( args.ArgC() < 2 )
  6295. return true;
  6296. if ( pPlayer->GetTeamNumber() <= LAST_SHARED_TEAM )
  6297. return true;
  6298. int iReadyState = atoi( args[1] );
  6299. //Already this state
  6300. if ( iReadyState == (int)IsTeamReady( pPlayer->GetTeamNumber() ) )
  6301. return true;
  6302. SetTeamReadyState( iReadyState == 1, pPlayer->GetTeamNumber() );
  6303. IGameEvent * event = gameeventmanager->CreateEvent( "tournament_stateupdate" );
  6304. if ( event )
  6305. {
  6306. event->SetInt( "userid", pPlayer->entindex() );
  6307. event->SetInt( "readystate", iReadyState );
  6308. event->SetBool( "namechange", 0 );
  6309. event->SetString( "oldname", " " );
  6310. event->SetString( "newname", " " );
  6311. gameeventmanager->FireEvent( event );
  6312. }
  6313. if ( iReadyState == 0 )
  6314. {
  6315. m_flRestartRoundTime.Set( -1.f );
  6316. m_bAwaitingReadyRestart = true;
  6317. }
  6318. return true;
  6319. }
  6320. if ( FStrEq( pcmd, "tournament_teamname" ) )
  6321. {
  6322. if ( IsMannVsMachineMode() )
  6323. return true;
  6324. if ( args.ArgC() < 2 )
  6325. return true;
  6326. if ( pPlayer->GetTeamNumber() <= LAST_SHARED_TEAM )
  6327. return true;
  6328. const char *commandline = args.GetCommandString();
  6329. // find the rest of the command line past the bot index
  6330. commandline = strstr( commandline, args[1] );
  6331. Assert( commandline );
  6332. char szName[MAX_TEAMNAME_STRING + 1] = { 0 };
  6333. Q_strncpy( szName, commandline, sizeof( szName ));
  6334. if ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
  6335. {
  6336. if ( FStrEq( szName, mp_tournament_blueteamname.GetString() ) == true )
  6337. return true;
  6338. mp_tournament_blueteamname.SetValue( szName );
  6339. }
  6340. else if ( pPlayer->GetTeamNumber() == TF_TEAM_RED )
  6341. {
  6342. if ( FStrEq( szName, mp_tournament_redteamname.GetString() ) == true )
  6343. return true;
  6344. mp_tournament_redteamname.SetValue( szName );
  6345. }
  6346. IGameEvent * event = gameeventmanager->CreateEvent( "tournament_stateupdate" );
  6347. if ( event )
  6348. {
  6349. event->SetInt( "userid", pPlayer->entindex() );
  6350. event->SetBool( "readystate", 0 );
  6351. event->SetBool( "namechange", 1 );
  6352. event->SetString( "newname", szName );
  6353. gameeventmanager->FireEvent( event );
  6354. }
  6355. return true;
  6356. }
  6357. if ( FStrEq( pcmd, "tournament_player_readystate" ) )
  6358. {
  6359. if ( State_Get() != GR_STATE_BETWEEN_RNDS )
  6360. return true;
  6361. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  6362. if ( pMatchDesc && pMatchDesc->m_params.m_bAutoReady )
  6363. return true;
  6364. // Make sure we have enough to allow ready mode commands
  6365. if ( !PlayerReadyStatus_HaveMinPlayersToEnable() )
  6366. return true;
  6367. if ( args.ArgC() < 2 )
  6368. return true;
  6369. bool bReady = ( atoi( args[1] ) == 1 );
  6370. PlayerReadyStatus_UpdatePlayerState( pPlayer, bReady );
  6371. if ( bReady )
  6372. {
  6373. pPlayer->PlayReadySound();
  6374. }
  6375. return true;
  6376. }
  6377. }
  6378. if ( FStrEq( pcmd, "objcmd" ) )
  6379. {
  6380. if ( args.ArgC() < 3 )
  6381. return true;
  6382. int entindex = atoi( args[1] );
  6383. edict_t* pEdict = INDEXENT(entindex);
  6384. if ( pEdict )
  6385. {
  6386. CBaseEntity* pBaseEntity = GetContainingEntity(pEdict);
  6387. CBaseObject* pObject = dynamic_cast<CBaseObject*>(pBaseEntity);
  6388. if ( pObject )
  6389. {
  6390. // We have to be relatively close to the object too...
  6391. // BUG! Some commands need to get sent without the player being near the object,
  6392. // eg delayed dismantle commands. Come up with a better way to ensure players aren't
  6393. // entering these commands in the console.
  6394. //float flDistSq = pObject->GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
  6395. //if (flDistSq <= (MAX_OBJECT_SCREEN_INPUT_DISTANCE * MAX_OBJECT_SCREEN_INPUT_DISTANCE))
  6396. {
  6397. // Strip off the 1st two arguments and make a new argument string
  6398. CCommand objectArgs( args.ArgC() - 2, &args.ArgV()[2]);
  6399. pObject->ClientCommand( pPlayer, objectArgs );
  6400. }
  6401. }
  6402. }
  6403. return true;
  6404. }
  6405. // Handle some player commands here as they relate more directly to gamerules state
  6406. if ( FStrEq( pcmd, "nextmap" ) )
  6407. {
  6408. if ( pPlayer->m_flNextTimeCheck < gpGlobals->curtime )
  6409. {
  6410. char szNextMap[MAX_MAP_NAME];
  6411. if ( nextlevel.GetString() && *nextlevel.GetString() )
  6412. {
  6413. Q_strncpy( szNextMap, nextlevel.GetString(), sizeof( szNextMap ) );
  6414. }
  6415. else
  6416. {
  6417. GetNextLevelName( szNextMap, sizeof( szNextMap ) );
  6418. }
  6419. ClientPrint( pPlayer, HUD_PRINTTALK, "#TF_nextmap", szNextMap);
  6420. pPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1;
  6421. }
  6422. return true;
  6423. }
  6424. else if ( FStrEq( pcmd, "timeleft" ) )
  6425. {
  6426. if ( pPlayer->m_flNextTimeCheck < gpGlobals->curtime )
  6427. {
  6428. if ( mp_timelimit.GetInt() > 0 )
  6429. {
  6430. int iTimeLeft = GetTimeLeft();
  6431. char szMinutes[5];
  6432. char szSeconds[3];
  6433. if ( iTimeLeft <= 0 )
  6434. {
  6435. Q_snprintf( szMinutes, sizeof(szMinutes), "0" );
  6436. Q_snprintf( szSeconds, sizeof(szSeconds), "00" );
  6437. }
  6438. else
  6439. {
  6440. Q_snprintf( szMinutes, sizeof(szMinutes), "%d", iTimeLeft / 60 );
  6441. Q_snprintf( szSeconds, sizeof(szSeconds), "%02d", iTimeLeft % 60 );
  6442. }
  6443. ClientPrint( pPlayer, HUD_PRINTTALK, "#TF_timeleft", szMinutes, szSeconds );
  6444. }
  6445. else
  6446. {
  6447. ClientPrint( pPlayer, HUD_PRINTTALK, "#TF_timeleft_nolimit" );
  6448. }
  6449. pPlayer->m_flNextTimeCheck = gpGlobals->curtime + 1;
  6450. }
  6451. return true;
  6452. }
  6453. #ifdef STAGING_ONLY
  6454. else if ( FStrEq( pcmd, "mvm_allupgrades" ) )
  6455. {
  6456. if ( GameModeUsesUpgrades() && g_hUpgradeEntity )
  6457. {
  6458. g_hUpgradeEntity->GrantOrRemoveAllUpgrades( pPlayer );
  6459. }
  6460. return true;
  6461. }
  6462. #endif
  6463. else if( pPlayer->ClientCommand( args ) )
  6464. {
  6465. return true;
  6466. }
  6467. return BaseClass::ClientCommand( pEdict, args );
  6468. }
  6469. //-----------------------------------------------------------------------------
  6470. // Purpose:
  6471. //-----------------------------------------------------------------------------
  6472. void CTFGameRules::LevelShutdown()
  6473. {
  6474. if ( IsInTraining() )
  6475. {
  6476. mp_humans_must_join_team.SetValue( "any" );
  6477. training_can_build_sentry.Revert();
  6478. training_can_build_dispenser.Revert();
  6479. training_can_build_tele_entrance.Revert();
  6480. training_can_build_tele_exit.Revert();
  6481. training_can_destroy_buildings.Revert();
  6482. training_can_pickup_sentry.Revert();
  6483. training_can_pickup_dispenser.Revert();
  6484. training_can_pickup_tele_entrance.Revert();
  6485. training_can_pickup_tele_exit.Revert();
  6486. training_can_select_weapon_primary.Revert();
  6487. training_can_select_weapon_secondary.Revert();
  6488. training_can_select_weapon_melee.Revert();
  6489. training_can_select_weapon_building.Revert();
  6490. training_can_select_weapon_pda.Revert();
  6491. training_can_select_weapon_item1.Revert();
  6492. training_can_select_weapon_item2.Revert();
  6493. tf_training_client_message.Revert();
  6494. }
  6495. TheTFBots().LevelShutdown();
  6496. hide_server.Revert();
  6497. DuelMiniGame_LevelShutdown();
  6498. GameCoordinator_NotifyLevelShutdown();
  6499. g_TFGameModeHistory.SetPrevState( m_nGameType );
  6500. if ( m_pUpgrades )
  6501. {
  6502. UTIL_Remove( m_pUpgrades );
  6503. }
  6504. }
  6505. //-----------------------------------------------------------------------------
  6506. // Purpose:
  6507. //-----------------------------------------------------------------------------
  6508. void CTFGameRules::Think()
  6509. {
  6510. if ( m_bMapCycleNeedsUpdate )
  6511. {
  6512. m_bMapCycleNeedsUpdate = false;
  6513. LoadMapCycleFile();
  6514. }
  6515. if ( g_fGameOver )
  6516. {
  6517. if ( UsePlayerReadyStatusMode() && !IsMannVsMachineMode() )
  6518. {
  6519. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  6520. static int nLastTimeSent = -1;
  6521. int nTimeLeft = ( m_flStateTransitionTime - gpGlobals->curtime );
  6522. int nTimePassed = gpGlobals->curtime - m_flLastRoundStateChangeTime;
  6523. if ( pMatchDesc && pMatchDesc->m_params.m_pszMatchEndKickWarning && nTimeLeft <= 50 && nTimeLeft % 10 == 0 && nTimeLeft != nLastTimeSent )
  6524. {
  6525. nLastTimeSent = nTimeLeft;
  6526. CBroadcastRecipientFilter filter;
  6527. UTIL_ClientPrintFilter( filter, HUD_PRINTTALK, pMatchDesc->m_params.m_pszMatchEndKickWarning, CFmtStr( "%d", nTimeLeft ) );
  6528. }
  6529. if ( BAttemptMapVoteRollingMatch() )
  6530. {
  6531. const CMatchInfo* pMatch = GTFGCClientSystem()->GetMatch();
  6532. if ( pMatch && pMatch->GetNumActiveMatchPlayers() == 0 )
  6533. {
  6534. Msg( "All players left during next map voting period. Ending match.\n" );
  6535. GTFGCClientSystem()->EndManagedMatch( /* bKickPlayersToParties */ false );
  6536. Assert( IsManagedMatchEnded() );
  6537. m_bMatchEnded.Set( true );
  6538. return;
  6539. }
  6540. if ( m_eRematchState == NEXT_MAP_VOTE_STATE_WAITING_FOR_USERS_TO_VOTE )
  6541. {
  6542. bool bVotePeriodExpired = false;
  6543. // Judgment time has arrived. Force a result below
  6544. if ( nTimePassed == tf_mm_next_map_vote_time.GetInt() )
  6545. {
  6546. bVotePeriodExpired = true;
  6547. }
  6548. int nVotes[ EUserNextMapVote::NUM_VOTE_STATES ];
  6549. EUserNextMapVote eWinningVote = GetWinningVote( nVotes );
  6550. if ( bVotePeriodExpired ||
  6551. ( nVotes[ USER_NEXT_MAP_VOTE_UNDECIDED ] == 0 && eWinningVote != USER_NEXT_MAP_VOTE_UNDECIDED ) )
  6552. {
  6553. CBroadcastRecipientFilter filter;
  6554. const MapDef_t *pMap = NULL;
  6555. if ( eWinningVote == USER_NEXT_MAP_VOTE_UNDECIDED )
  6556. {
  6557. // Nobody voted! We're playing on the same map again by default
  6558. pMap = GetItemSchema()->GetMasterMapDefByName( STRING( gpGlobals->mapname ) );
  6559. }
  6560. else
  6561. {
  6562. pMap = GetItemSchema()->GetMasterMapDefByIndex( GetNextMapVoteOption( eWinningVote ) );
  6563. }
  6564. if ( pMap == NULL )
  6565. {
  6566. Assert( !"We somehow didn't pick a new map to rotate to! Default to the current one" );
  6567. pMap = GetItemSchema()->GetMasterMapDefByName( STRING( gpGlobals->mapname ) );
  6568. }
  6569. if ( pMap )
  6570. {
  6571. m_eRematchState = NEXT_MAP_VOTE_STATE_MAP_CHOSEN_PAUSE;
  6572. GTFGCClientSystem()->RequestNewMatchForLobby( pMap );
  6573. }
  6574. }
  6575. }
  6576. else if ( m_eRematchState == NEXT_MAP_VOTE_STATE_MAP_CHOSEN_PAUSE )
  6577. {
  6578. // CTFGCServerSystem is in control at this point
  6579. }
  6580. }
  6581. if ( gpGlobals->curtime > m_flStateTransitionTime || !BHavePlayers() )
  6582. {
  6583. nLastTimeSent = -1;
  6584. if ( pMatchDesc )
  6585. {
  6586. // Matchmaking path
  6587. pMatchDesc->PostMatchClearServerSettings();
  6588. }
  6589. else
  6590. {
  6591. // Readymode (Tournament) path
  6592. g_fGameOver = false;
  6593. m_bAllowBetweenRounds = true;
  6594. State_Transition( GR_STATE_RESTART );
  6595. SetInWaitingForPlayers( true );
  6596. }
  6597. return;
  6598. }
  6599. }
  6600. if ( ( m_flMatchSummaryTeleportTime > 0 ) && ( gpGlobals->curtime > m_flMatchSummaryTeleportTime ) )
  6601. {
  6602. m_flMatchSummaryTeleportTime = -1.f;
  6603. MatchSummaryTeleport();
  6604. }
  6605. }
  6606. else
  6607. {
  6608. if ( gpGlobals->curtime > m_flNextPeriodicThink )
  6609. {
  6610. if ( State_Get() != GR_STATE_BONUS && State_Get() != GR_STATE_TEAM_WIN && State_Get() != GR_STATE_GAME_OVER && IsInWaitingForPlayers() == false )
  6611. {
  6612. if ( CheckCapsPerRound() )
  6613. return;
  6614. }
  6615. }
  6616. // These network variables mirror the MM system's match state for client's sake. Gamerules should still
  6617. // be aware of when these change, either because we caused it or via a callback. This warning will
  6618. // detect desync. (Ideally we'd have the ability to just share between the client GC system and server
  6619. // GC system directly without passing things through gamerules)
  6620. if ( m_bMatchEnded != IsManagedMatchEnded() )
  6621. {
  6622. Assert( false );
  6623. Warning( "Mirrored Match parameters on gamerules don't match MatchInfo\n" );
  6624. m_bMatchEnded.Set( IsManagedMatchEnded() );
  6625. }
  6626. if ( GTFGCClientSystem()->GetMatch() && GetCurrentMatchGroup() != (EMatchGroup)m_nMatchGroupType.Get() )
  6627. {
  6628. Assert( false );
  6629. Warning( "Mirrored Match parameters on gamerules don't match MatchInfo\n" );
  6630. m_nMatchGroupType.Set( GetCurrentMatchGroup() );
  6631. }
  6632. // Managed matches (MvM and competitive) abandon thing
  6633. CMatchInfo *pMatch = GTFGCClientSystem()->GetMatch();
  6634. bool bEveryoneSafeToLeave = true;
  6635. if ( pMatch )
  6636. {
  6637. // Send current safe-to-leave flags down from the GCServerSystem
  6638. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  6639. {
  6640. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  6641. if ( !pPlayer )
  6642. { continue; }
  6643. CSteamID steamID;
  6644. bool bSafe = !pMatch || !pPlayer->GetSteamID( &steamID ) || pMatch->BPlayerSafeToLeaveMatch( steamID );
  6645. pPlayer->SetMatchSafeToLeave( bSafe );
  6646. bEveryoneSafeToLeave = bEveryoneSafeToLeave && bSafe;
  6647. }
  6648. }
  6649. if ( IsCompetitiveMode() )
  6650. {
  6651. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  6652. Assert( pMatch ); // Should not be in competitive mode without a match
  6653. //
  6654. // Check if this is mode requires a complete match, but doesn't have one
  6655. //
  6656. bool bEndMatch = false;
  6657. int nActiveMatchPlayers = pMatch->GetNumActiveMatchPlayers();
  6658. int nMissingPlayers = pMatch->GetCanonicalMatchSize() - nActiveMatchPlayers;
  6659. if ( pMatchDesc->m_params.m_bRequireCompleteMatch &&
  6660. !IsManagedMatchEnded() &&
  6661. nMissingPlayers )
  6662. {
  6663. // See if we are requesting late join right now, and give that time to work
  6664. if ( pMatchDesc->ShouldRequestLateJoin() )
  6665. {
  6666. // End match if GC system didn't request late join in response to players leaving
  6667. auto *pGCSys = GTFGCClientSystem();
  6668. double flRequestedLateJoin = pGCSys->GetTimeRequestedLateJoin();
  6669. if ( flRequestedLateJoin == -1.f )
  6670. {
  6671. bEndMatch = true;
  6672. Msg( "Failed to request late join, ending competitive match\n" );
  6673. }
  6674. else
  6675. {
  6676. // Otherwise, since we can't proceed without players, apply a timeout after which we'll
  6677. // cancel the match and release these players. The time to wait is shorter if the GC
  6678. // hasn't confirmed our late join request, so we're not spending the full time waiting
  6679. // when the GC is just non-responsive.
  6680. double flTimeWaitingForLateJoin = CRTime::RTime32TimeCur() - flRequestedLateJoin;
  6681. bool bGotLateJoin = pGCSys->BLateJoinEligible();
  6682. double flWaitLimit = bGotLateJoin ? tf_competitive_required_late_join_timeout.GetFloat()
  6683. : tf_competitive_required_late_join_confirm_timeout.GetFloat();
  6684. if ( flTimeWaitingForLateJoin > flWaitLimit )
  6685. {
  6686. Msg( "Exceeded wait time limit for late joiners, canceling match\n" );
  6687. bEndMatch = true;
  6688. }
  6689. }
  6690. }
  6691. else
  6692. {
  6693. // Can't request late joiners, tank match if number of active players get below some threshold
  6694. int iRedActive = 0;
  6695. int iBlueActive = 0;
  6696. for ( int idxPlayer = 0; idxPlayer < pMatch->GetNumTotalMatchPlayers(); idxPlayer++ )
  6697. {
  6698. CMatchInfo::PlayerMatchData_t *pMatchPlayer = pMatch->GetMatchDataForPlayer( idxPlayer );
  6699. if ( !pMatchPlayer->bDropped )
  6700. {
  6701. int iTeam = GetGameTeamForGCTeam( pMatchPlayer->eGCTeam );
  6702. if ( iTeam == TF_TEAM_RED )
  6703. iRedActive++;
  6704. else
  6705. iBlueActive++;
  6706. }
  6707. }
  6708. int iTeamSize = pMatch->GetCanonicalMatchSize() / 2;
  6709. if ( iRedActive == 0 || iBlueActive == 0 || ( iTeamSize - iRedActive ) > tf_mm_abandoned_players_per_team_max.GetInt() || ( iTeamSize - iBlueActive ) > tf_mm_abandoned_players_per_team_max.GetInt() )
  6710. {
  6711. Msg( "Match type requires a complete match, but there are not enough active players left and we are not requesting late join. Stopping match.\n" );
  6712. bEndMatch = true;
  6713. }
  6714. }
  6715. }
  6716. else if ( !IsManagedMatchEnded() && nActiveMatchPlayers < 1 )
  6717. {
  6718. // For non-complete mode, just stop the match if we lose all players
  6719. Msg( "Competitive managed match in progress, but no remaining match players. Stopping match.\n" );
  6720. bEndMatch = true;
  6721. }
  6722. if ( bEndMatch )
  6723. {
  6724. StopCompetitiveMatch( CMsgGC_Match_Result_Status_MATCH_FAILED_ABANDON );
  6725. }
  6726. else
  6727. {
  6728. // If the match was ended but we're still playing, kick off a timer to remind people that
  6729. // they're in a dead match.
  6730. AssertMsg( !IsManagedMatchEnded() || ( pMatch->BMatchTerminated() && bEveryoneSafeToLeave ),
  6731. "Expect everyone to be safe to leave and the match info to reflect that after the match is over" );
  6732. bool bGameRunning = ( State_Get() == GR_STATE_BETWEEN_RNDS || State_Get() == GR_STATE_RND_RUNNING );
  6733. bool bDeadMatch = bGameRunning && IsManagedMatchEnded() && pMatch->BMatchTerminated() && bEveryoneSafeToLeave;
  6734. if ( bDeadMatch && ( m_flSafeToLeaveTimer == -.1f ||
  6735. m_flSafeToLeaveTimer - gpGlobals->curtime <= 0.f ) )
  6736. {
  6737. // Periodic nag event
  6738. m_flSafeToLeaveTimer = gpGlobals->curtime + 30.f;
  6739. IGameEvent *pEvent = gameeventmanager->CreateEvent( "player_abandoned_match" );
  6740. if ( pEvent )
  6741. {
  6742. pEvent->SetBool( "game_over", false );
  6743. gameeventmanager->FireEvent( pEvent );
  6744. }
  6745. }
  6746. }
  6747. // Handle re-spawning the players after the doors have shut at the beginning of a match
  6748. if ( ( m_flCompModeRespawnPlayersAtMatchStart > 0 ) && ( m_flCompModeRespawnPlayersAtMatchStart < gpGlobals->curtime ) )
  6749. {
  6750. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  6751. {
  6752. CTFPlayer *pPlayer = static_cast<CTFPlayer*>( UTIL_PlayerByIndex( i ) );
  6753. if ( !pPlayer )
  6754. continue;
  6755. pPlayer->RemoveAllOwnedEntitiesFromWorld();
  6756. pPlayer->ForceRespawn();
  6757. }
  6758. m_flCompModeRespawnPlayersAtMatchStart = -1.f;
  6759. }
  6760. }
  6761. }
  6762. if ( g_bRandomMap == true )
  6763. {
  6764. g_bRandomMap = false;
  6765. char szNextMap[MAX_MAP_NAME];
  6766. GetNextLevelName( szNextMap, sizeof(szNextMap), true );
  6767. IncrementMapCycleIndex();
  6768. ChangeLevelToMap( szNextMap );
  6769. return;
  6770. }
  6771. if ( IsInArenaMode() == true )
  6772. {
  6773. if ( m_flSendNotificationTime > 0.0f && m_flSendNotificationTime <= gpGlobals->curtime )
  6774. {
  6775. Arena_SendPlayerNotifications();
  6776. }
  6777. }
  6778. // periodically count up the fake clients and set the bot_count cvar to update server tags
  6779. if ( m_botCountTimer.IsElapsed() )
  6780. {
  6781. m_botCountTimer.Start( 5.0f );
  6782. int botCount = 0;
  6783. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  6784. {
  6785. CTFPlayer *player = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  6786. if ( player && player->IsFakeClient() )
  6787. {
  6788. ++botCount;
  6789. }
  6790. }
  6791. tf_bot_count.SetValue( botCount );
  6792. }
  6793. #ifdef GAME_DLL
  6794. #ifdef _DEBUG
  6795. if ( tf_debug_ammo_and_health.GetBool() )
  6796. {
  6797. CBaseEntity *ent;
  6798. for( int i=0; i<m_healthVector.Count(); ++i )
  6799. {
  6800. ent = m_healthVector[i];
  6801. if ( ent )
  6802. {
  6803. NDebugOverlay::Cross3D( ent->WorldSpaceCenter(), 10.0f, 0, 255, 0, true, 0.1f );
  6804. }
  6805. }
  6806. for( int i=0; i<m_ammoVector.Count(); ++i )
  6807. {
  6808. ent = m_ammoVector[i];
  6809. if ( ent )
  6810. {
  6811. NDebugOverlay::Cross3D( ent->WorldSpaceCenter(), 10.0f, 0, 0, 255, true, 0.1f );
  6812. }
  6813. }
  6814. }
  6815. #endif // _DEBUG
  6816. if ( g_voteController )
  6817. {
  6818. ManageServerSideVoteCreation();
  6819. }
  6820. // ...
  6821. if ( tf_item_based_forced_holiday.GetInt() == kHoliday_Halloween && engine->Time() >= g_fEternaweenAutodisableTime )
  6822. {
  6823. if ( GCClientSystem() )
  6824. {
  6825. GCSDK::CProtoBufMsg<CMsgGC_GameServer_ServerModificationItemExpired> msg( k_EMsgGC_GameServer_ServerModificationItemExpired );
  6826. msg.Body().set_modification_type( kGameServerModificationItem_Halloween );
  6827. GCClientSystem()->BSendMessage( msg );
  6828. }
  6829. tf_item_based_forced_holiday.SetValue( kHoliday_None );
  6830. FlushAllAttributeCaches();
  6831. }
  6832. // play the bomb alarm if we need to
  6833. if ( m_bMannVsMachineAlarmStatus )
  6834. {
  6835. if ( m_flNextFlagAlert < gpGlobals->curtime )
  6836. {
  6837. if ( PlayThrottledAlert( 255, "Announcer.MVM_Bomb_Alert_Near_Hatch", 5.0f ) )
  6838. {
  6839. m_flNextFlagAlarm = gpGlobals->curtime + 3.0;
  6840. m_flNextFlagAlert = gpGlobals->curtime + 20.0f;
  6841. }
  6842. }
  6843. if ( m_flNextFlagAlarm < gpGlobals->curtime )
  6844. {
  6845. m_flNextFlagAlarm = gpGlobals->curtime + 3.0;
  6846. BroadcastSound( 255, "MVM.BombWarning" );
  6847. }
  6848. }
  6849. else if ( m_flNextFlagAlarm > 0.0f )
  6850. {
  6851. m_flNextFlagAlarm = 0.0f;
  6852. m_flNextFlagAlert = gpGlobals->curtime + 5.0f;
  6853. }
  6854. if ( m_bPowerupImbalanceMeasuresRunning )
  6855. {
  6856. if ( m_flTimeToStopImbalanceMeasures < gpGlobals->curtime )
  6857. {
  6858. PowerupTeamImbalance( TEAM_UNASSIGNED ); // passing TEAM_UNASSIGNED will fire the ImbalanceMeasuresOver output
  6859. m_bPowerupImbalanceMeasuresRunning = false;
  6860. }
  6861. }
  6862. PeriodicHalloweenUpdate();
  6863. SpawnHalloweenBoss();
  6864. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  6865. {
  6866. if ( ( State_Get() == GR_STATE_RND_RUNNING ) && ( !m_bHelltowerPlayersInHell ) && ( m_helltowerTimer.IsElapsed() ) )
  6867. {
  6868. // Play our Halloween winning/losing lines for the teams
  6869. int iWinningTeam = TEAM_UNASSIGNED;
  6870. bool bRareLine = ( RandomFloat( 0, 1 ) < HELLTOWER_RARE_LINE_CHANCE );
  6871. float flRedProgress = 0.0f, flBlueProgress = 0.0f;
  6872. for ( int i = 0 ; i < ITFTeamTrainWatcher::AutoList().Count() ; ++i )
  6873. {
  6874. CTeamTrainWatcher *pTrainWatcher = static_cast< CTeamTrainWatcher* >( ITFTeamTrainWatcher::AutoList()[i] );
  6875. if ( !pTrainWatcher->IsDisabled() )
  6876. {
  6877. if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_RED )
  6878. {
  6879. flRedProgress = pTrainWatcher->GetTrainDistanceAlongTrack();
  6880. }
  6881. else
  6882. {
  6883. flBlueProgress = pTrainWatcher->GetTrainDistanceAlongTrack();
  6884. }
  6885. }
  6886. }
  6887. if ( flRedProgress > flBlueProgress )
  6888. {
  6889. iWinningTeam = TF_TEAM_RED;
  6890. }
  6891. else if ( flBlueProgress > flRedProgress )
  6892. {
  6893. iWinningTeam = TF_TEAM_BLUE;
  6894. }
  6895. int iRedLine = HELLTOWER_VO_RED_MISC;
  6896. int iBlueLine = HELLTOWER_VO_BLUE_MISC;
  6897. // should we play the misc lines or the winning/losing lines?
  6898. if ( ( iWinningTeam == TEAM_UNASSIGNED ) || ( RandomFloat( 0, 1 ) < HELLTOWER_MISC_CHANCE ) )
  6899. {
  6900. if ( bRareLine )
  6901. {
  6902. iRedLine = HELLTOWER_VO_RED_MISC_RARE;
  6903. iBlueLine = HELLTOWER_VO_BLUE_MISC_RARE;
  6904. }
  6905. }
  6906. else
  6907. {
  6908. // play a winning/losing line
  6909. iRedLine = ( iWinningTeam == TF_TEAM_RED ) ? HELLTOWER_VO_RED_WINNING : HELLTOWER_VO_RED_LOSING;
  6910. iBlueLine = ( iWinningTeam == TF_TEAM_BLUE ) ? HELLTOWER_VO_BLUE_WINNING : HELLTOWER_VO_BLUE_LOSING;
  6911. if ( bRareLine )
  6912. {
  6913. iRedLine = ( iWinningTeam == TF_TEAM_RED ) ? HELLTOWER_VO_RED_WINNING_RARE : HELLTOWER_VO_RED_LOSING_RARE;
  6914. iBlueLine = ( iWinningTeam == TF_TEAM_BLUE ) ? HELLTOWER_VO_BLUE_WINNING_RARE : HELLTOWER_VO_BLUE_LOSING_RARE;
  6915. }
  6916. }
  6917. PlayHelltowerAnnouncerVO( iRedLine, iBlueLine );
  6918. m_helltowerTimer.Start( HELLTOWER_TIMER_INTERVAL );
  6919. }
  6920. }
  6921. else if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) )
  6922. {
  6923. if ( ( State_Get() == GR_STATE_RND_RUNNING ) && m_doomsdaySetupTimer.HasStarted() && m_doomsdaySetupTimer.IsElapsed() )
  6924. {
  6925. m_doomsdaySetupTimer.Invalidate();
  6926. const char *pszSound = NULL;
  6927. switch( GetRoundsPlayed() )
  6928. {
  6929. case 0:
  6930. pszSound = "sf14.Merasmus.Start.FirstRound";
  6931. if ( RandomInt( 1, 10 ) == 1 )
  6932. {
  6933. pszSound = "sf14.Merasmus.Start.FirstRoundRare";
  6934. }
  6935. break;
  6936. case 1:
  6937. pszSound = "sf14.Merasmus.Start.SecondRound";
  6938. break;
  6939. case 2:
  6940. default:
  6941. pszSound = "sf14.Merasmus.Start.ThirdRoundAndBeyond";
  6942. break;
  6943. }
  6944. if ( pszSound && pszSound[0] )
  6945. {
  6946. BroadcastSound( 255, pszSound );
  6947. }
  6948. }
  6949. }
  6950. #ifdef TF_RAID_MODE
  6951. // This check is here for Boss battles that don't have a tf_raid_logic entity
  6952. if ( IsBossBattleMode() && !IsInWaitingForPlayers() && State_Get() == GR_STATE_RND_RUNNING )
  6953. {
  6954. CUtlVector< CTFPlayer * > alivePlayerVector;
  6955. CollectPlayers( &alivePlayerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS );
  6956. // if everyone is dead at the same time, they lose
  6957. if ( alivePlayerVector.Count() == 0 )
  6958. {
  6959. SetWinningTeam( TF_TEAM_RED, WINREASON_OPPONENTS_DEAD );
  6960. }
  6961. }
  6962. #endif // TF_RAID_MODE
  6963. // Batched strange event message processing?
  6964. if ( engine->Time() > m_flNextStrangeEventProcessTime )
  6965. {
  6966. KillEaterEvents_FlushBatches();
  6967. m_flNextStrangeEventProcessTime = engine->Time() + g_flStrangeEventBatchProcessInterval;
  6968. }
  6969. ManageCompetitiveMode();
  6970. #endif // GAME_DLL
  6971. //=============================================================================
  6972. // HPE_BEGIN:
  6973. // [msmith] Do training summary screen logic.
  6974. //=============================================================================
  6975. if ( IsInTraining() == true )
  6976. {
  6977. if ( m_flStateTransitionTime > gpGlobals->curtime )
  6978. {
  6979. int client_message = tf_training_client_message.GetInt();
  6980. switch ( client_message )
  6981. {
  6982. case TRAINING_CLIENT_MESSAGE_IN_SUMMARY_SCREEN:
  6983. {
  6984. //Keep adding time to the restart while we are in the end screen menu so that we never restart the round until
  6985. //the player presses the replay button (or next button to go to the next map).
  6986. m_flStateTransitionTime = gpGlobals->curtime + 5.0f;
  6987. }
  6988. break;
  6989. case TRAINING_CLIENT_MESSAGE_NEXT_MAP:
  6990. {
  6991. LoadNextTrainingMap();
  6992. tf_training_client_message.SetValue( (int)TRAINING_CLIENT_MESSAGE_NONE );
  6993. }
  6994. break;
  6995. case TRAINING_CLIENT_MESSAGE_REPLAY:
  6996. {
  6997. // Reload the map
  6998. engine->ChangeLevel( STRING( gpGlobals->mapname ), NULL );
  6999. tf_training_client_message.SetValue( (int)TRAINING_CLIENT_MESSAGE_NONE );
  7000. }
  7001. break;
  7002. } // switch
  7003. }
  7004. }
  7005. //=============================================================================
  7006. // HPE_END
  7007. //=============================================================================
  7008. // This is ugly, but, population manager needs to sometimes think when we're not simulating, but it is an entity.
  7009. // Really, we need to better split out some kind of "sub-gamerules" class for modes like this.
  7010. if ( IsMannVsMachineMode() && g_pPopulationManager )
  7011. {
  7012. g_pPopulationManager->GameRulesThink();
  7013. }
  7014. BaseClass::Think();
  7015. }
  7016. #ifdef GAME_DLL
  7017. #ifdef STAGING_ONLY
  7018. ConVar tf_spawn_halloween_gift_test_enabled( "tf_spawn_halloween_gift_test_enabled", "0", 0, "enable to spawn a gift at the world origin. You probably want to use ConCommand 'SpawnHalloweenGiftTest'" );
  7019. CON_COMMAND_F( tf_spawn_halloween_gift_test, "Test Halloween Gifts", FCVAR_NONE )
  7020. {
  7021. ConVarRef gift_test( "tf_spawn_halloween_gift_test_enabled" );
  7022. gift_test.SetValue( 1 );
  7023. }
  7024. #endif // STAGING_ONLY
  7025. void CTFGameRules::PeriodicHalloweenUpdate()
  7026. {
  7027. // DEBUG
  7028. #ifdef STAGING_ONLY
  7029. if ( tf_spawn_halloween_gift_test_enabled.GetBool() )
  7030. {
  7031. m_flNextHalloweenGiftUpdateTime = gpGlobals->curtime;
  7032. tf_spawn_halloween_gift_test_enabled.SetValue( 0 );
  7033. return;
  7034. }
  7035. #endif //staging_only
  7036. // Are we on a Halloween Map?
  7037. // Do we have Halloween Contracts?
  7038. if ( !IsHolidayActive( kHoliday_Halloween ) )
  7039. return;
  7040. // Loop through each player that has a quest and spawn them a gift
  7041. if ( m_halloweenGiftSpawnLocations.Count() == 0 )
  7042. return;
  7043. // If we've never given out gifts before, set the time
  7044. if ( m_flNextHalloweenGiftUpdateTime < 0 )
  7045. {
  7046. m_flNextHalloweenGiftUpdateTime = gpGlobals->curtime + RandomInt( 7, 12 ) * 60;
  7047. return;
  7048. }
  7049. if ( m_flNextHalloweenGiftUpdateTime > gpGlobals->curtime )
  7050. return;
  7051. m_flNextHalloweenGiftUpdateTime = gpGlobals->curtime + RandomInt( 7, 12 ) * 60;
  7052. CUtlVector< CTFPlayer* > playerVector;
  7053. CollectPlayers( &playerVector );
  7054. FOR_EACH_VEC( playerVector, i )
  7055. {
  7056. Vector vLocation = m_halloweenGiftSpawnLocations.Element( RandomInt( 0, m_halloweenGiftSpawnLocations.Count() - 1 ) );
  7057. CHalloweenGiftPickup *pGift = assert_cast<CHalloweenGiftPickup*>( CBaseEntity::CreateNoSpawn( "tf_halloween_gift_pickup", vLocation, vec3_angle, NULL ) );
  7058. if ( pGift )
  7059. {
  7060. pGift->SetTargetPlayer( playerVector[i] );
  7061. DispatchSpawn( pGift );
  7062. }
  7063. }
  7064. }
  7065. #endif
  7066. //-----------------------------------------------------------------------------
  7067. // Purpose:
  7068. //-----------------------------------------------------------------------------
  7069. bool CTFGameRules::SwitchToNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon )
  7070. {
  7071. if ( pPlayer )
  7072. {
  7073. CBaseCombatWeapon *lastWeapon = ToTFPlayer( pPlayer )->GetLastWeapon();
  7074. if ( lastWeapon != NULL && lastWeapon->HasAnyAmmo() )
  7075. {
  7076. return pPlayer->Weapon_Switch( lastWeapon );
  7077. }
  7078. }
  7079. return BaseClass::SwitchToNextBestWeapon( pPlayer, pCurrentWeapon );
  7080. }
  7081. #ifdef GAME_DLL
  7082. //-----------------------------------------------------------------------------
  7083. // Purpose:
  7084. //-----------------------------------------------------------------------------
  7085. bool CTFGameRules::PointsMayBeCaptured( void )
  7086. {
  7087. if ( IsHolidayActive( kHoliday_Halloween ) && GetActiveBoss() )
  7088. {
  7089. switch ( GetHalloweenScenario() )
  7090. {
  7091. case HALLOWEEN_SCENARIO_VIADUCT:
  7092. {
  7093. // the eyeball prevents point capturing while he's in play
  7094. if ( assert_cast< CEyeballBoss * >( GetActiveBoss() ) )
  7095. {
  7096. return false;
  7097. }
  7098. }
  7099. break;
  7100. case HALLOWEEN_SCENARIO_LAKESIDE:
  7101. {
  7102. // merasmus prevents point capturing while he's in play
  7103. if ( assert_cast< CMerasmus * >( GetActiveBoss() ) )
  7104. {
  7105. return false;
  7106. }
  7107. }
  7108. break;
  7109. }
  7110. }
  7111. if ( IsMannVsMachineMode() )
  7112. {
  7113. return true;
  7114. }
  7115. return BaseClass::PointsMayBeCaptured();
  7116. }
  7117. extern bool IsSpaceToSpawnHere( const Vector &where );
  7118. static bool isZombieMobForceSpawning = false;
  7119. #ifdef STAGING_ONLY
  7120. // force the boss to spawn where our cursor is pointing
  7121. CON_COMMAND_F( tf_halloween_force_zombie_mob, "For testing.", FCVAR_CHEAT )
  7122. {
  7123. isZombieMobForceSpawning = true;
  7124. }
  7125. #endif
  7126. //-----------------------------------------------------------------------------
  7127. // Purpose:
  7128. //-----------------------------------------------------------------------------
  7129. void CTFGameRules::SpawnZombieMob( void )
  7130. {
  7131. if ( !tf_halloween_zombie_mob_enabled.GetBool() )
  7132. {
  7133. return;
  7134. }
  7135. // timer was started and has elapsed - time to spawn the boss
  7136. if ( InSetup() || IsInWaitingForPlayers() )
  7137. {
  7138. m_zombieMobTimer.Start( tf_halloween_zombie_mob_spawn_interval.GetFloat() );
  7139. return;
  7140. }
  7141. if ( isZombieMobForceSpawning )
  7142. {
  7143. isZombieMobForceSpawning = false;
  7144. m_zombieMobTimer.Invalidate();
  7145. }
  7146. // spawn pending mob members
  7147. if ( m_zombiesLeftToSpawn > 0 )
  7148. {
  7149. if ( IsSpaceToSpawnHere( m_zombieSpawnSpot ) )
  7150. {
  7151. if ( CZombie::SpawnAtPos( m_zombieSpawnSpot ) )
  7152. {
  7153. --m_zombiesLeftToSpawn;
  7154. }
  7155. }
  7156. }
  7157. // require a minimum number of human players in the game before the boss appears
  7158. CUtlVector< CTFPlayer * > playerVector;
  7159. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  7160. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  7161. // only count humans
  7162. int totalPlayers = 0;
  7163. for( int i=0; i<playerVector.Count(); ++i )
  7164. {
  7165. if ( !playerVector[i]->IsBot() )
  7166. {
  7167. ++totalPlayers;
  7168. }
  7169. }
  7170. if ( totalPlayers == 0 )
  7171. {
  7172. return;
  7173. }
  7174. // spawn a mob
  7175. if ( m_zombieMobTimer.IsElapsed() )
  7176. {
  7177. m_zombieMobTimer.Start( tf_halloween_zombie_mob_spawn_interval.GetFloat() );
  7178. CUtlVector< CTFNavArea * > ambushVector; // vector of hidden but near-to-victim areas
  7179. for( int i=0; i<playerVector.Count(); ++i )
  7180. {
  7181. CTFPlayer *player = playerVector[i];
  7182. if ( player->IsBot() )
  7183. {
  7184. continue;
  7185. }
  7186. if ( !player->GetLastKnownArea() )
  7187. {
  7188. continue;
  7189. }
  7190. const float maxSurroundTravelRange = 2000.0f;
  7191. CUtlVector< CNavArea * > areaVector;
  7192. // collect walkable areas surrounding this player
  7193. CollectSurroundingAreas( &areaVector, player->GetLastKnownArea(), maxSurroundTravelRange, StepHeight, StepHeight );
  7194. // keep subset that isn't visible to any player
  7195. for( int j=0; j<areaVector.Count(); ++j )
  7196. {
  7197. CTFNavArea *area = (CTFNavArea *)areaVector[j];
  7198. if ( !area->IsValidForWanderingPopulation() )
  7199. {
  7200. continue;
  7201. }
  7202. if ( area->IsPotentiallyVisibleToTeam( TF_TEAM_BLUE ) || area->IsPotentiallyVisibleToTeam( TF_TEAM_RED ) )
  7203. {
  7204. continue;
  7205. }
  7206. ambushVector.AddToTail( area );
  7207. }
  7208. }
  7209. if ( ambushVector.Count() == 0 )
  7210. {
  7211. // no place to spawn the mob this time
  7212. return;
  7213. }
  7214. for( int retry=0; retry<10; ++retry )
  7215. {
  7216. int which = RandomInt( 0, ambushVector.Count()-1 );
  7217. m_zombieSpawnSpot = ambushVector[ which ]->GetCenter() + Vector( 0, 0, StepHeight );
  7218. if ( !IsSpaceToSpawnHere( m_zombieSpawnSpot ) )
  7219. {
  7220. continue;
  7221. }
  7222. // spawn a mob here
  7223. m_zombiesLeftToSpawn = tf_halloween_zombie_mob_spawn_count.GetInt();
  7224. break;
  7225. }
  7226. }
  7227. }
  7228. //---------------------------------------------------------------------------------------------------------
  7229. static bool isBossForceSpawning = false;
  7230. // force the boss to spawn where our cursor is pointing
  7231. CON_COMMAND_F( tf_halloween_force_boss_spawn, "For testing.", FCVAR_CHEAT )
  7232. {
  7233. isBossForceSpawning = true;
  7234. }
  7235. CON_COMMAND_F( cc_spawn_merasmus_at_level, "Force Merasmus to spawn at a specific difficulty level", FCVAR_CHEAT )
  7236. {
  7237. if( args.ArgC() != 2 )
  7238. {
  7239. DevMsg( "Must specify a level\n" );
  7240. return;
  7241. }
  7242. CMerasmus::DBG_SetLevel( atoi(args[1]) );
  7243. tf_halloween_force_boss_spawn( args );
  7244. }
  7245. extern ConVar tf_halloween_bot_min_player_count;
  7246. //-----------------------------------------------------------------------------
  7247. // Purpose:
  7248. //-----------------------------------------------------------------------------
  7249. void CTFGameRules::SpawnHalloweenBoss( void )
  7250. {
  7251. if ( !IsHolidayActive( kHoliday_Halloween ) )
  7252. return;
  7253. // only spawn the Halloween Boss on our Halloween maps
  7254. HalloweenBossType bossType = HALLOWEEN_BOSS_INVALID;
  7255. float bossInterval = 0.0f;
  7256. float bossIntervalVariation = 0.0f;
  7257. HalloweenScenarioType scenario = GetHalloweenScenario();
  7258. if ( scenario == HALLOWEEN_SCENARIO_MANN_MANOR )
  7259. {
  7260. bossType = HALLOWEEN_BOSS_HHH;
  7261. bossInterval = tf_halloween_boss_spawn_interval.GetFloat();
  7262. bossIntervalVariation = tf_halloween_boss_spawn_interval_variation.GetFloat();
  7263. }
  7264. else if ( scenario == HALLOWEEN_SCENARIO_VIADUCT )
  7265. {
  7266. bossType = HALLOWEEN_BOSS_MONOCULUS;
  7267. bossInterval = tf_halloween_eyeball_boss_spawn_interval.GetFloat();
  7268. bossIntervalVariation = tf_halloween_eyeball_boss_spawn_interval_variation.GetFloat();
  7269. }
  7270. else if ( scenario == HALLOWEEN_SCENARIO_LAKESIDE )
  7271. {
  7272. bossType = HALLOWEEN_BOSS_MERASMUS;
  7273. if ( CMerasmus::GetMerasmusLevel() <= 3 )
  7274. {
  7275. bossInterval = tf_merasmus_spawn_interval.GetFloat();
  7276. bossIntervalVariation = tf_merasmus_spawn_interval_variation.GetFloat();
  7277. }
  7278. else
  7279. {
  7280. // after level 3, spawn Merasmus every 60 secs
  7281. bossInterval = 60;
  7282. bossIntervalVariation = 0;
  7283. }
  7284. // check if the wheel is still spinning
  7285. CWheelOfDoom* pWheel = assert_cast< CWheelOfDoom* >( gEntList.FindEntityByClassname( NULL, "wheel_of_doom" ) );
  7286. if ( pWheel && !pWheel->IsDoneBoardcastingEffectSound() )
  7287. {
  7288. return;
  7289. }
  7290. }
  7291. //else if ( scenario == HALLOWEEN_SCENARIO_HIGHTOWER )
  7292. //{
  7293. // bool bWasEnabled = tf_halloween_zombie_mob_enabled.GetBool();
  7294. // tf_halloween_zombie_mob_enabled.SetValue( true );
  7295. // // not a boss battle map
  7296. // SpawnZombieMob();
  7297. // tf_halloween_zombie_mob_enabled.SetValue( bWasEnabled );
  7298. // bossType = "eyeball_boss";
  7299. // bossInterval = tf_halloween_eyeball_boss_spawn_interval.GetFloat();
  7300. // bossIntervalVariation = tf_halloween_eyeball_boss_spawn_interval_variation.GetFloat();
  7301. //}
  7302. else
  7303. {
  7304. // not a boss battle map
  7305. SpawnZombieMob();
  7306. return;
  7307. }
  7308. // only one boss at a time
  7309. if ( GetActiveBoss() )
  7310. {
  7311. // boss is still out there - restart the timer
  7312. StartHalloweenBossTimer( bossInterval, bossIntervalVariation );
  7313. isBossForceSpawning = false;
  7314. return;
  7315. }
  7316. if ( !m_halloweenBossTimer.IsElapsed() && !isBossForceSpawning )
  7317. return;
  7318. // boss timer has elapsed
  7319. if ( m_halloweenBossTimer.HasStarted() || isBossForceSpawning )
  7320. {
  7321. if ( !isBossForceSpawning )
  7322. {
  7323. // timer was started and has elapsed - time to spawn the boss
  7324. if ( InSetup() || IsInWaitingForPlayers() )
  7325. return;
  7326. // require a minimum number of human players in the game before the boss appears
  7327. CUtlVector< CTFPlayer * > playerVector;
  7328. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  7329. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  7330. // only count humans
  7331. int totalPlayers = 0;
  7332. for( int i=0; i<playerVector.Count(); ++i )
  7333. {
  7334. if ( !playerVector[i]->IsBot() )
  7335. {
  7336. ++totalPlayers;
  7337. }
  7338. }
  7339. if ( totalPlayers < tf_halloween_bot_min_player_count.GetInt() )
  7340. return;
  7341. }
  7342. Vector bossSpawnPos = vec3_origin;
  7343. // spawn on the currently contested point
  7344. CTeamControlPoint *contestedPoint = NULL;
  7345. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  7346. if ( pMaster )
  7347. {
  7348. for( int i=0; i<pMaster->GetNumPoints(); ++i )
  7349. {
  7350. contestedPoint = pMaster->GetControlPoint( i );
  7351. if ( contestedPoint && pMaster->IsInRound( contestedPoint ) )
  7352. {
  7353. if ( ObjectiveResource()->GetOwningTeam( contestedPoint->GetPointIndex() ) == TF_TEAM_BLUE )
  7354. continue;
  7355. // blue are the invaders
  7356. if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, contestedPoint->GetPointIndex() ) )
  7357. continue;
  7358. break;
  7359. }
  7360. }
  7361. }
  7362. CBaseEntity *pCustomSpawnBossPos = gEntList.FindEntityByClassname( NULL, "spawn_boss" );
  7363. if ( pCustomSpawnBossPos )
  7364. {
  7365. bossSpawnPos = pCustomSpawnBossPos->GetAbsOrigin();
  7366. }
  7367. else if ( contestedPoint )
  7368. {
  7369. bossSpawnPos = contestedPoint->GetAbsOrigin();
  7370. if ( scenario == HALLOWEEN_SCENARIO_VIADUCT || scenario == HALLOWEEN_SCENARIO_LAKESIDE )
  7371. {
  7372. // revert ownership of point to neutral
  7373. contestedPoint->ForceOwner( 0 );
  7374. // pause the timers
  7375. if ( IsInKothMode() )
  7376. {
  7377. variant_t sVariant;
  7378. CTeamRoundTimer *pTimer = GetKothTeamTimer( TF_TEAM_BLUE );
  7379. if ( pTimer )
  7380. {
  7381. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  7382. }
  7383. pTimer = GetKothTeamTimer( TF_TEAM_RED );
  7384. if ( pTimer )
  7385. {
  7386. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  7387. }
  7388. }
  7389. }
  7390. }
  7391. else
  7392. {
  7393. // pick a random spot
  7394. CUtlVector< CTFNavArea * > spawnAreaVector;
  7395. for( int i=0; i<TheNavAreas.Count(); ++i )
  7396. {
  7397. CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
  7398. if ( area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED | TF_NAV_SPAWN_ROOM_EXIT ) )
  7399. {
  7400. // don't spawn in team spawn rooms
  7401. continue;
  7402. }
  7403. // don't use small nav areas
  7404. const float goodSize = 100.0f;
  7405. if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize )
  7406. {
  7407. continue;
  7408. }
  7409. spawnAreaVector.AddToTail( area );
  7410. }
  7411. if ( spawnAreaVector.Count() == 0 )
  7412. {
  7413. // no place to spawn (!)
  7414. return;
  7415. }
  7416. int which = RandomInt( 0, spawnAreaVector.Count()-1 );
  7417. bossSpawnPos = spawnAreaVector[ which ]->GetCenter();
  7418. }
  7419. CHalloweenBaseBoss::SpawnBossAtPos( bossType, bossSpawnPos );
  7420. // pick next spawn time
  7421. StartHalloweenBossTimer( bossInterval, bossIntervalVariation );
  7422. isBossForceSpawning = false;
  7423. }
  7424. else
  7425. {
  7426. // Merasmus has a more reliable initial spawn time
  7427. if( IsHalloweenScenario( HALLOWEEN_SCENARIO_LAKESIDE ) )
  7428. {
  7429. StartHalloweenBossTimer( bossInterval, bossIntervalVariation );
  7430. }
  7431. else
  7432. {
  7433. // initial spawn time
  7434. m_halloweenBossTimer.Start( 0.5f * RandomFloat( 0.0f, bossInterval + bossIntervalVariation ) );
  7435. }
  7436. }
  7437. }
  7438. //-----------------------------------------------------------------------------
  7439. // Purpose:
  7440. //-----------------------------------------------------------------------------
  7441. void CTFGameRules::BeginHaunting( int nDesiredCount, float flMinDuration, float flMaxDuration )
  7442. {
  7443. if ( !IsHolidayActive( kHoliday_Halloween ) )
  7444. return;
  7445. if ( !IsHalloweenScenario( HALLOWEEN_SCENARIO_VIADUCT ) && !IsHalloweenScenario( HALLOWEEN_SCENARIO_LAKESIDE ) )
  7446. {
  7447. CTFHolidayEntity *pHolidayEntity = dynamic_cast<CTFHolidayEntity*> ( gEntList.FindEntityByClassname( NULL, "tf_logic_holiday" ) );
  7448. if ( !pHolidayEntity || !pHolidayEntity->ShouldAllowHaunting() )
  7449. return;
  7450. }
  7451. const int desiredGhostCount = nDesiredCount;
  7452. // if there are existing ghosts, extend their time
  7453. CUtlVector< CGhost * > priorGhostVector;
  7454. for( int g=0; g<m_ghostVector.Count(); ++g )
  7455. {
  7456. if ( m_ghostVector[g] != NULL )
  7457. {
  7458. priorGhostVector.AddToTail( m_ghostVector[g] );
  7459. }
  7460. }
  7461. m_ghostVector.RemoveAll();
  7462. for( int g=0; g<priorGhostVector.Count(); ++g )
  7463. {
  7464. priorGhostVector[g]->SetLifetime( RandomFloat( flMinDuration, flMaxDuration ) );
  7465. m_ghostVector.AddToTail( priorGhostVector[g] );
  7466. }
  7467. if ( m_ghostVector.Count() >= desiredGhostCount )
  7468. return;
  7469. // spawn ghosts away from players
  7470. CUtlVector< CTFPlayer * > playerVector;
  7471. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS );
  7472. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  7473. const int ghostCount = desiredGhostCount - m_ghostVector.Count();
  7474. CUtlVector< Vector > spawnVector;
  7475. for( int i=0; i<TheNavAreas.Count(); ++i )
  7476. {
  7477. CTFNavArea *area = (CTFNavArea *)TheNavAreas.Element(i);
  7478. if ( area->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
  7479. {
  7480. // keep out of spawn rooms
  7481. continue;
  7482. }
  7483. Vector spot = area->GetCenter();
  7484. // don't spawn near players (so they aren't instantly scared)
  7485. int p;
  7486. for( p=0; p<playerVector.Count(); ++p )
  7487. {
  7488. if ( ( playerVector[p]->GetAbsOrigin() - spot ).IsLengthLessThan( 1.25f * GHOST_SCARE_RADIUS ) )
  7489. {
  7490. break;
  7491. }
  7492. }
  7493. if ( p == playerVector.Count() )
  7494. {
  7495. spawnVector.AddToTail( spot );
  7496. }
  7497. }
  7498. if ( spawnVector.Count() == 0 )
  7499. {
  7500. return;
  7501. }
  7502. for( int g=0; g<ghostCount; ++g )
  7503. {
  7504. int which = RandomInt( 0, spawnVector.Count()-1 );
  7505. CGhost *ghost = SpawnGhost( spawnVector[ which ], vec3_angle, RandomFloat( flMinDuration, flMaxDuration ) );
  7506. m_ghostVector.AddToTail( ghost );
  7507. }
  7508. }
  7509. static const int k_RecentPlayerInfoMaxTime = 7200; // 2 hours
  7510. //-----------------------------------------------------------------------------
  7511. // Purpose:
  7512. //-----------------------------------------------------------------------------
  7513. void CTFGameRules::PlayerHistory_AddPlayer( CTFPlayer *pTFPlayer )
  7514. {
  7515. if ( !pTFPlayer )
  7516. return;
  7517. CSteamID steamID;
  7518. pTFPlayer->GetSteamID( &steamID );
  7519. if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
  7520. return;
  7521. // Exists?
  7522. FOR_EACH_VEC_BACK( m_vecPlayerHistory, i )
  7523. {
  7524. if ( m_vecPlayerHistory[i].steamID == steamID )
  7525. {
  7526. m_vecPlayerHistory[i].flTime = Plat_FloatTime();
  7527. return;
  7528. }
  7529. // Do maintenance here.
  7530. if ( Plat_FloatTime() - m_vecPlayerHistory[i].flTime >= (float)k_RecentPlayerInfoMaxTime )
  7531. {
  7532. m_vecPlayerHistory.Remove( i );
  7533. }
  7534. }
  7535. PlayerHistoryInfo_t info =
  7536. {
  7537. steamID,
  7538. (float) Plat_FloatTime(),
  7539. pTFPlayer->GetTeamNumber()
  7540. };
  7541. m_vecPlayerHistory.AddToTail( info );
  7542. }
  7543. //-----------------------------------------------------------------------------
  7544. // Purpose:
  7545. //-----------------------------------------------------------------------------
  7546. PlayerHistoryInfo_t *CTFGameRules::PlayerHistory_GetPlayerInfo( CTFPlayer *pTFPlayer )
  7547. {
  7548. if ( !pTFPlayer )
  7549. return NULL;
  7550. CSteamID steamID;
  7551. pTFPlayer->GetSteamID( &steamID );
  7552. if ( !steamID.IsValid() || !steamID.BIndividualAccount() )
  7553. return NULL;
  7554. FOR_EACH_VEC_BACK( m_vecPlayerHistory, i )
  7555. {
  7556. if ( m_vecPlayerHistory[i].steamID == steamID )
  7557. return &m_vecPlayerHistory[i];
  7558. }
  7559. return NULL;
  7560. }
  7561. //-----------------------------------------------------------------------------
  7562. // Purpose: Returns -1 if we don't have the data
  7563. //-----------------------------------------------------------------------------
  7564. int CTFGameRules::PlayerHistory_GetTimeSinceLastSeen( CTFPlayer *pTFPlayer )
  7565. {
  7566. PlayerHistoryInfo_t *pInfo = PlayerHistory_GetPlayerInfo( pTFPlayer );
  7567. if ( !pInfo )
  7568. return -1;
  7569. // We only care about whole seconds
  7570. return (int)( Plat_FloatTime() - pInfo->flTime );
  7571. }
  7572. #endif
  7573. //=============================================================================
  7574. // HPE_BEGIN:
  7575. // [msmith] TEMP CODE: needs to be replaced with the final solution for our training mission navigation.
  7576. //=============================================================================
  7577. void CTFGameRules::LoadNextTrainingMap()
  7578. {
  7579. if ( m_hTrainingModeLogic )
  7580. {
  7581. g_fGameOver = true;
  7582. const char* pNextMap = m_hTrainingModeLogic->GetNextMap();
  7583. if ( pNextMap && FStrEq( pNextMap, "" ) == false )
  7584. {
  7585. Msg( "CHANGE LEVEL: %s\n", pNextMap );
  7586. engine->ChangeLevel( pNextMap, NULL );
  7587. }
  7588. else
  7589. {
  7590. Msg( "CHANGE LEVEL: %s\n", STRING( gpGlobals->mapname ) );
  7591. engine->ChangeLevel( STRING( gpGlobals->mapname ), NULL );
  7592. }
  7593. return;
  7594. }
  7595. Msg( "CHANGE LEVEL: %s\n", STRING( gpGlobals->mapname ) );
  7596. engine->ChangeLevel( STRING( gpGlobals->mapname ), NULL );
  7597. }
  7598. //=============================================================================
  7599. // HPE_END
  7600. //=============================================================================
  7601. //Runs think for all player's conditions
  7602. //Need to do this here instead of the player so players that crash still run their important thinks
  7603. void CTFGameRules::RunPlayerConditionThink ( void )
  7604. {
  7605. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  7606. {
  7607. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  7608. if ( pPlayer )
  7609. {
  7610. pPlayer->m_Shared.ConditionGameRulesThink();
  7611. }
  7612. }
  7613. }
  7614. //-----------------------------------------------------------------------------
  7615. // Purpose:
  7616. //-----------------------------------------------------------------------------
  7617. void CTFGameRules::FrameUpdatePostEntityThink()
  7618. {
  7619. BaseClass::FrameUpdatePostEntityThink();
  7620. RunPlayerConditionThink();
  7621. }
  7622. //-----------------------------------------------------------------------------
  7623. // Purpose:
  7624. //-----------------------------------------------------------------------------
  7625. bool CTFGameRules::CheckCapsPerRound()
  7626. {
  7627. return IsPasstimeMode()
  7628. ? SetPasstimeWinningTeam()
  7629. : SetCtfWinningTeam();
  7630. }
  7631. //-----------------------------------------------------------------------------
  7632. // Purpose:
  7633. //-----------------------------------------------------------------------------
  7634. bool CTFGameRules::SetPasstimeWinningTeam()
  7635. {
  7636. Assert( IsPasstimeMode() );
  7637. int iScoreLimit = tf_passtime_scores_per_round.GetInt();
  7638. if ( iScoreLimit <= 0 )
  7639. {
  7640. // no score limit set, play forever
  7641. return false;
  7642. }
  7643. // TODO need to generalize the "flag captures" parts of CTFTeam to avoid
  7644. // this confusing overload of the flag captures concept, or maybe defer
  7645. // this logic to the specific object that manages the mode.
  7646. CTFTeamManager *pTeamMgr = TFTeamMgr();
  7647. int iBlueScore = pTeamMgr->GetFlagCaptures( TF_TEAM_BLUE );
  7648. int iRedScore = pTeamMgr->GetFlagCaptures( TF_TEAM_RED );
  7649. if ( ( iBlueScore < iScoreLimit ) && ( iRedScore < iScoreLimit ) )
  7650. {
  7651. // no team has exceeded the score limit
  7652. return false;
  7653. }
  7654. int iWinnerTeam = ( iBlueScore > iRedScore )
  7655. ? TF_TEAM_BLUE
  7656. : TF_TEAM_RED;
  7657. SetWinningTeam( iWinnerTeam, WINREASON_SCORED );
  7658. return true;
  7659. }
  7660. //-----------------------------------------------------------------------------
  7661. // Purpose:
  7662. //-----------------------------------------------------------------------------
  7663. bool CTFGameRules::SetCtfWinningTeam()
  7664. {
  7665. Assert( !IsPasstimeMode() );
  7666. if ( tf_flag_caps_per_round.GetInt() > 0 )
  7667. {
  7668. int iMaxCaps = -1;
  7669. CTFTeam *pMaxTeam = NULL;
  7670. // check to see if any team has won a "round"
  7671. int nTeamCount = TFTeamMgr()->GetTeamCount();
  7672. for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam )
  7673. {
  7674. CTFTeam *pTeam = GetGlobalTFTeam( iTeam );
  7675. if ( !pTeam )
  7676. continue;
  7677. // we might have more than one team over the caps limit (if the server op lowered the limit)
  7678. // so loop through to see who has the most among teams over the limit
  7679. if ( pTeam->GetFlagCaptures() >= tf_flag_caps_per_round.GetInt() )
  7680. {
  7681. if ( pTeam->GetFlagCaptures() > iMaxCaps )
  7682. {
  7683. iMaxCaps = pTeam->GetFlagCaptures();
  7684. pMaxTeam = pTeam;
  7685. }
  7686. }
  7687. }
  7688. if ( iMaxCaps != -1 && pMaxTeam != NULL )
  7689. {
  7690. SetWinningTeam( pMaxTeam->GetTeamNumber(), WINREASON_FLAG_CAPTURE_LIMIT );
  7691. return true;
  7692. }
  7693. }
  7694. return false;
  7695. }
  7696. //-----------------------------------------------------------------------------
  7697. // Purpose:
  7698. //-----------------------------------------------------------------------------
  7699. bool CTFGameRules::CheckWinLimit( bool bAllowEnd /*= true*/, int nAddValueWhenChecking /*= 0*/ )
  7700. {
  7701. if ( IsInPreMatch() )
  7702. return false;
  7703. bool bWinner = false;
  7704. int iTeam = TEAM_UNASSIGNED;
  7705. int iReason = WINREASON_NONE;
  7706. const char *pszReason = "";
  7707. if ( mp_winlimit.GetInt() != 0 )
  7708. {
  7709. if ( ( TFTeamMgr()->GetTeam( TF_TEAM_BLUE )->GetScore() + nAddValueWhenChecking ) >= mp_winlimit.GetInt() )
  7710. {
  7711. pszReason = "Team \"BLUE\" triggered \"Intermission_Win_Limit\"\n";
  7712. bWinner = true;
  7713. iTeam = TF_TEAM_BLUE;
  7714. iReason = WINREASON_WINLIMIT;
  7715. }
  7716. else if ( ( TFTeamMgr()->GetTeam( TF_TEAM_RED )->GetScore() + nAddValueWhenChecking ) >= mp_winlimit.GetInt() )
  7717. {
  7718. pszReason = "Team \"RED\" triggered \"Intermission_Win_Limit\"\n";
  7719. bWinner = true;
  7720. iTeam = TF_TEAM_RED;
  7721. iReason = WINREASON_WINLIMIT;
  7722. }
  7723. }
  7724. // has one team go far enough ahead of the other team to trigger the win difference?
  7725. if ( !bWinner )
  7726. {
  7727. int iWinLimit = mp_windifference.GetInt();
  7728. if ( iWinLimit > 0 )
  7729. {
  7730. int iBlueScore = TFTeamMgr()->GetTeam( TF_TEAM_BLUE )->GetScore();
  7731. int iRedScore = TFTeamMgr()->GetTeam( TF_TEAM_RED )->GetScore();
  7732. if ( (iBlueScore - iRedScore) >= iWinLimit )
  7733. {
  7734. if ( (mp_windifference_min.GetInt() == 0) || (iBlueScore >= mp_windifference_min.GetInt()) )
  7735. {
  7736. pszReason = "Team \"BLUE\" triggered \"Intermission_Win_Limit\" due to mp_windifference\n";
  7737. bWinner = true;
  7738. iTeam = TF_TEAM_BLUE;
  7739. iReason = WINREASON_WINDIFFLIMIT;
  7740. }
  7741. }
  7742. else if ( (iRedScore - iBlueScore) >= iWinLimit )
  7743. {
  7744. if ( (mp_windifference_min.GetInt() == 0) || (iRedScore >= mp_windifference_min.GetInt()) )
  7745. {
  7746. pszReason = "Team \"RED\" triggered \"Intermission_Win_Limit\" due to mp_windifference\n";
  7747. bWinner = true;
  7748. iTeam = TF_TEAM_RED;
  7749. iReason = WINREASON_WINDIFFLIMIT;
  7750. }
  7751. }
  7752. }
  7753. }
  7754. if ( bWinner )
  7755. {
  7756. if ( bAllowEnd )
  7757. {
  7758. IGameEvent *event = gameeventmanager->CreateEvent( "tf_game_over" );
  7759. if ( event )
  7760. {
  7761. if ( iReason == WINREASON_WINDIFFLIMIT )
  7762. {
  7763. event->SetString( "reason", "Reached Win Difference Limit" );
  7764. }
  7765. else
  7766. {
  7767. event->SetString( "reason", "Reached Win Limit" );
  7768. }
  7769. gameeventmanager->FireEvent( event );
  7770. }
  7771. if ( IsInTournamentMode() == true )
  7772. {
  7773. SetWinningTeam( iTeam, iReason, true, false, true );
  7774. }
  7775. else
  7776. {
  7777. GoToIntermission();
  7778. }
  7779. Assert( V_strlen( pszReason ) );
  7780. UTIL_LogPrintf( "%s", pszReason );
  7781. }
  7782. return true;
  7783. }
  7784. return false;
  7785. }
  7786. //-----------------------------------------------------------------------------
  7787. // Purpose:
  7788. //-----------------------------------------------------------------------------
  7789. void CTFGameRules::CheckRespawnWaves()
  7790. {
  7791. BaseClass::CheckRespawnWaves();
  7792. // Look for overrides
  7793. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  7794. {
  7795. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  7796. if ( !pTFPlayer )
  7797. continue;
  7798. if ( pTFPlayer->IsAlive() )
  7799. continue;
  7800. if ( m_iRoundState == GR_STATE_PREROUND )
  7801. continue;
  7802. // Triggers can force a player to spawn at a specific time
  7803. if ( pTFPlayer->GetRespawnTimeOverride() != -1.f &&
  7804. gpGlobals->curtime > pTFPlayer->GetDeathTime() + pTFPlayer->GetRespawnTimeOverride() )
  7805. {
  7806. pTFPlayer->ForceRespawn();
  7807. }
  7808. else if ( IsPVEModeActive() )
  7809. {
  7810. // special stuff for PVE mode
  7811. if ( !ShouldRespawnQuickly( pTFPlayer ) )
  7812. continue;
  7813. // If the player hasn't been dead the minimum respawn time, he
  7814. // waits until the next wave.
  7815. if ( !HasPassedMinRespawnTime( pTFPlayer ) )
  7816. continue;
  7817. if ( !pTFPlayer->IsReadyToSpawn() )
  7818. {
  7819. // Let the player spawn immediately when they do pick a class
  7820. if ( pTFPlayer->ShouldGainInstantSpawn() )
  7821. {
  7822. pTFPlayer->AllowInstantSpawn();
  7823. }
  7824. continue;
  7825. }
  7826. // Respawn this player
  7827. pTFPlayer->ForceRespawn();
  7828. }
  7829. }
  7830. }
  7831. //-----------------------------------------------------------------------------
  7832. // Purpose:
  7833. //-----------------------------------------------------------------------------
  7834. void CTFGameRules::PlayWinSong( int team )
  7835. {
  7836. if ( IsPlayingSpecialDeliveryMode() )
  7837. return;
  7838. bool bGameOver = IsGameOver();
  7839. if ( !IsInStopWatch() || bGameOver )
  7840. {
  7841. // Give the match a chance to play something custom. It returns true if it handled everything
  7842. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  7843. if ( pMatchDesc && pMatchDesc->BPlayWinMusic( team, bGameOver ) )
  7844. {
  7845. return;
  7846. }
  7847. }
  7848. if ( IsInTournamentMode() && IsInStopWatch() && ObjectiveResource() )
  7849. {
  7850. int iStopWatchTimer = ObjectiveResource()->GetStopWatchTimer();
  7851. CTeamRoundTimer *pStopWatch = dynamic_cast< CTeamRoundTimer* >( UTIL_EntityByIndex( iStopWatchTimer ) );
  7852. if ( ( pStopWatch && pStopWatch->IsWatchingTimeStamps() ) || ( !m_bForceMapReset ) )
  7853. {
  7854. BroadcastSound( 255, "MatchMaking.RoundEndStalemateMusic" );
  7855. return;
  7856. }
  7857. }
  7858. CTeamplayRoundBasedRules::PlayWinSong( team );
  7859. }
  7860. //-----------------------------------------------------------------------------
  7861. // Purpose:
  7862. //-----------------------------------------------------------------------------
  7863. void CTFGameRules::SetWinningTeam( int team, int iWinReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false*/, bool bDontAddScore /* = false*/, bool bFinal /*= false*/ )
  7864. {
  7865. // matching the value calculated in CTeamplayRoundBasedRules::State_Enter_TEAM_WIN()
  7866. // for m_flStateTransitionTime and adding 1 second to make sure we're covered
  7867. int nTime = GetBonusRoundTime( bFinal ) + 1;
  7868. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  7869. {
  7870. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  7871. if ( !pTFPlayer )
  7872. continue;
  7873. // store our team for the response rules at the next round start
  7874. // (teams might be switched for attack/defend maps)
  7875. pTFPlayer->SetPrevRoundTeamNum( pTFPlayer->GetTeamNumber() );
  7876. if ( team != TEAM_UNASSIGNED )
  7877. {
  7878. if ( pTFPlayer->GetTeamNumber() == team )
  7879. {
  7880. if ( pTFPlayer->IsAlive() )
  7881. {
  7882. pTFPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_BONUS_TIME, nTime );
  7883. }
  7884. }
  7885. else
  7886. {
  7887. pTFPlayer->ClearExpression();
  7888. #ifdef GAME_DLL
  7889. // Loser karts get max Damage and stun
  7890. if ( pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
  7891. {
  7892. pTFPlayer->AddKartDamage( 666 );
  7893. pTFPlayer->m_Shared.StunPlayer( 1.5f, 1.0f, TF_STUN_BOTH ); // Short full stun then slow
  7894. pTFPlayer->m_Shared.StunPlayer( 10.0f, 0.25f, TF_STUN_MOVEMENT );
  7895. }
  7896. #endif //GAME_DLL
  7897. }
  7898. }
  7899. }
  7900. DuelMiniGame_AssignWinners();
  7901. #ifdef TF_RAID_MODE
  7902. if ( !IsBossBattleMode() )
  7903. {
  7904. // Don't do a full reset in Raid mode if the defending team didn't win
  7905. if ( IsRaidMode() && team != TF_TEAM_PVE_DEFENDERS )
  7906. {
  7907. bForceMapReset = false;
  7908. }
  7909. }
  7910. #endif // TF_RAID_MODE
  7911. SetBirthdayPlayer( NULL );
  7912. #ifdef GAME_DLL
  7913. if ( m_bPlayingKoth )
  7914. {
  7915. // Increment BLUE KOTH cap time
  7916. CTeamRoundTimer *pKOTHTimer = TFGameRules()->GetBlueKothRoundTimer();
  7917. GetGlobalTFTeam( TF_TEAM_BLUE )->AddKOTHTime( pKOTHTimer->GetTimerMaxLength() - pKOTHTimer->GetTimeRemaining() );
  7918. // Increment RED KOTH cap time
  7919. pKOTHTimer = TFGameRules()->GetRedKothRoundTimer();
  7920. GetGlobalTFTeam( TF_TEAM_RED )->AddKOTHTime( pKOTHTimer->GetTimerMaxLength() - pKOTHTimer->GetTimeRemaining() );
  7921. }
  7922. else if ( HasMultipleTrains() )
  7923. {
  7924. for ( int i = 0 ; i < ITFTeamTrainWatcher::AutoList().Count() ; ++i )
  7925. {
  7926. CTeamTrainWatcher *pTrainWatcher = static_cast< CTeamTrainWatcher* >( ITFTeamTrainWatcher::AutoList()[i] );
  7927. if ( !pTrainWatcher->IsDisabled() )
  7928. {
  7929. if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_RED )
  7930. {
  7931. GetGlobalTFTeam( TF_TEAM_RED )->AddPLRTrack( pTrainWatcher->GetTrainProgress() );
  7932. }
  7933. else
  7934. {
  7935. GetGlobalTFTeam( TF_TEAM_BLUE )->AddPLRTrack( pTrainWatcher->GetTrainProgress() );
  7936. }
  7937. }
  7938. }
  7939. }
  7940. #endif
  7941. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) && CTFMinigameLogic::GetMinigameLogic() )
  7942. {
  7943. CTFMiniGame *pMiniGame = CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame();
  7944. if ( pMiniGame )
  7945. {
  7946. IGameEvent *event = gameeventmanager->CreateEvent( "minigame_win" );
  7947. if ( event )
  7948. {
  7949. event->SetInt( "team", team );
  7950. event->SetInt( "type", (int)( pMiniGame->GetMinigameType() ) );
  7951. gameeventmanager->FireEvent( event );
  7952. }
  7953. }
  7954. }
  7955. if ( IsPasstimeMode() )
  7956. {
  7957. CTF_GameStats.m_passtimeStats.summary.nRoundEndReason = iWinReason;
  7958. CTF_GameStats.m_passtimeStats.summary.nRoundRemainingSec = (int) GetActiveRoundTimer()->GetTimeRemaining();
  7959. CTF_GameStats.m_passtimeStats.summary.nScoreBlue = GetGlobalTFTeam( TF_TEAM_BLUE )->GetFlagCaptures();
  7960. CTF_GameStats.m_passtimeStats.summary.nScoreRed = GetGlobalTFTeam( TF_TEAM_RED )->GetFlagCaptures();
  7961. // stats reporting happens as a result of BaseClass::SetWinningTeam, but we need to make sure to
  7962. // update ball carry data before stats are reported.
  7963. // FIXME: refactor this so we're not calling it just for its side effects :/
  7964. CPasstimeBall *pBall = g_pPasstimeLogic->GetBall();
  7965. if ( pBall )
  7966. {
  7967. pBall->SetStateOutOfPlay();
  7968. }
  7969. }
  7970. CTeamplayRoundBasedRules::SetWinningTeam( team, iWinReason, bForceMapReset, bSwitchTeams, bDontAddScore, bFinal );
  7971. if ( IsCompetitiveMode() )
  7972. {
  7973. HaveAllPlayersSpeakConceptIfAllowed( IsGameOver() ? MP_CONCEPT_MATCH_OVER_COMP : MP_CONCEPT_GAME_OVER_COMP );
  7974. }
  7975. }
  7976. //-----------------------------------------------------------------------------
  7977. // Purpose:
  7978. //-----------------------------------------------------------------------------
  7979. void CTFGameRules::SetStalemate( int iReason, bool bForceMapReset /* = true */, bool bSwitchTeams /* = false */ )
  7980. {
  7981. DuelMiniGame_AssignWinners();
  7982. if ( IsPasstimeMode() )
  7983. {
  7984. CTF_GameStats.m_passtimeStats.summary.bStalemate = true;
  7985. CTF_GameStats.m_passtimeStats.summary.bSuddenDeath = mp_stalemate_enable.GetBool();
  7986. CTF_GameStats.m_passtimeStats.summary.bMeleeOnlySuddenDeath = mp_stalemate_meleeonly.GetBool();
  7987. }
  7988. BaseClass::SetStalemate( iReason, bForceMapReset, bSwitchTeams );
  7989. }
  7990. //-----------------------------------------------------------------------------
  7991. // Purpose:
  7992. //-----------------------------------------------------------------------------
  7993. float CTFGameRules::GetPreMatchEndTime() const
  7994. {
  7995. //TFTODO: implement this.
  7996. return gpGlobals->curtime;
  7997. }
  7998. //-----------------------------------------------------------------------------
  7999. // Purpose:
  8000. //-----------------------------------------------------------------------------
  8001. void CTFGameRules::GoToIntermission( void )
  8002. {
  8003. // Tell the clients to recalculate the holiday
  8004. IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_holidays" );
  8005. if ( event )
  8006. {
  8007. gameeventmanager->FireEvent( event );
  8008. }
  8009. UTIL_CalculateHolidays();
  8010. BaseClass::GoToIntermission();
  8011. }
  8012. //-----------------------------------------------------------------------------
  8013. // Purpose:
  8014. //-----------------------------------------------------------------------------
  8015. void CTFGameRules::RecalculateTruce( void )
  8016. {
  8017. bool bTruceActive = false;
  8018. // Call a truce if the teams are fighting a Halloween boss
  8019. if ( IsHolidayActive( kHoliday_Halloween ) )
  8020. {
  8021. if ( ( IMerasmusAutoList::AutoList().Count() > 0 ) || ( IEyeballBossAutoList::AutoList().Count() > 0 ) )
  8022. {
  8023. bool bHaveActiveBoss = false;
  8024. for ( int i = 0; i < IMerasmusAutoList::AutoList().Count(); ++i )
  8025. {
  8026. CMerasmus *pBoss = static_cast< CMerasmus* >( IMerasmusAutoList::AutoList()[i] );
  8027. if ( !pBoss->IsMarkedForDeletion() )
  8028. {
  8029. bHaveActiveBoss = true;
  8030. }
  8031. }
  8032. for ( int i = 0; i < IEyeballBossAutoList::AutoList().Count(); ++i )
  8033. {
  8034. CEyeballBoss *pBoss = static_cast< CEyeballBoss* >( IEyeballBossAutoList::AutoList()[i] );
  8035. if ( !pBoss->IsMarkedForDeletion() )
  8036. {
  8037. if ( ( pBoss->GetTeamNumber() != TF_TEAM_RED ) && ( pBoss->GetTeamNumber() != TF_TEAM_BLUE ) )
  8038. {
  8039. bHaveActiveBoss = true;
  8040. }
  8041. }
  8042. }
  8043. if ( bHaveActiveBoss && ( IsValveMap() || tf_halloween_allow_truce_during_boss_event.GetBool() || IsMapForcedTruceDuringBossFight() ) )
  8044. {
  8045. bTruceActive = true;
  8046. }
  8047. }
  8048. }
  8049. #ifdef STAGING_ONLY
  8050. if ( tf_truce.GetBool() )
  8051. {
  8052. bTruceActive = true;
  8053. }
  8054. #endif
  8055. if ( m_bTruceActive != bTruceActive )
  8056. {
  8057. m_bTruceActive.Set( bTruceActive );
  8058. CReliableBroadcastRecipientFilter filter;
  8059. if ( m_bTruceActive )
  8060. {
  8061. SendHudNotification( filter, HUD_NOTIFY_TRUCE_START, true );
  8062. if ( m_hGamerulesProxy )
  8063. {
  8064. m_hGamerulesProxy->TruceStart();
  8065. }
  8066. }
  8067. else
  8068. {
  8069. SendHudNotification( filter, HUD_NOTIFY_TRUCE_END, true );
  8070. if ( m_hGamerulesProxy )
  8071. {
  8072. m_hGamerulesProxy->TruceEnd();
  8073. }
  8074. }
  8075. }
  8076. }
  8077. //-----------------------------------------------------------------------------
  8078. // Purpose:
  8079. //-----------------------------------------------------------------------------
  8080. bool CTFGameRules::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker, const CTakeDamageInfo &info )
  8081. {
  8082. // guard against NULL pointers if players disconnect
  8083. if ( !pPlayer || !pAttacker )
  8084. return false;
  8085. if ( IsTruceActive() && ( pPlayer != pAttacker ) && ( pPlayer->GetTeamNumber() != pAttacker->GetTeamNumber() ) )
  8086. {
  8087. if ( ( ( pAttacker->GetTeamNumber() == TF_TEAM_RED ) && ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE ) ) || ( ( pAttacker->GetTeamNumber() == TF_TEAM_BLUE ) && ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) ) )
  8088. {
  8089. CBaseEntity *pInflictor = info.GetInflictor();
  8090. if ( pInflictor )
  8091. {
  8092. return !( pInflictor->IsTruceValidForEnt() || pAttacker->IsTruceValidForEnt() );
  8093. }
  8094. else
  8095. {
  8096. return !pAttacker->IsTruceValidForEnt();
  8097. }
  8098. }
  8099. }
  8100. // if pAttacker is an object, we can only do damage if pPlayer is our builder
  8101. if ( pAttacker->IsBaseObject() )
  8102. {
  8103. CBaseObject *pObj = ( CBaseObject *)pAttacker;
  8104. if ( pObj->GetBuilder() == pPlayer || pPlayer->GetTeamNumber() != pObj->GetTeamNumber() )
  8105. {
  8106. // Builder and enemies
  8107. return true;
  8108. }
  8109. else
  8110. {
  8111. // Teammates of the builder
  8112. return false;
  8113. }
  8114. }
  8115. // prevent eyeball rockets from hurting teammates if it's a spell
  8116. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_MONOCULUS && pAttacker->GetTeamNumber() == pPlayer->GetTeamNumber() )
  8117. {
  8118. return false;
  8119. }
  8120. // in PvE modes, if entities are on the same team, they can't hurt each other
  8121. // this is needed since not all entities will be players
  8122. if ( IsPVEModeActive() &&
  8123. pPlayer->GetTeamNumber() == pAttacker->GetTeamNumber() &&
  8124. pPlayer != pAttacker &&
  8125. !info.IsForceFriendlyFire() )
  8126. {
  8127. return false;
  8128. }
  8129. return BaseClass::FPlayerCanTakeDamage( pPlayer, pAttacker, info );
  8130. }
  8131. Vector DropToGround(
  8132. CBaseEntity *pMainEnt,
  8133. const Vector &vPos,
  8134. const Vector &vMins,
  8135. const Vector &vMaxs )
  8136. {
  8137. trace_t trace;
  8138. UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace );
  8139. return trace.endpos;
  8140. }
  8141. void TestSpawnPointType( const char *pEntClassName )
  8142. {
  8143. // Find the next spawn spot.
  8144. CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, pEntClassName );
  8145. while( pSpot )
  8146. {
  8147. // trace a box here
  8148. Vector vTestMins = pSpot->GetAbsOrigin() + VEC_HULL_MIN;
  8149. Vector vTestMaxs = pSpot->GetAbsOrigin() + VEC_HULL_MAX;
  8150. if ( UTIL_IsSpaceEmpty( pSpot, vTestMins, vTestMaxs ) )
  8151. {
  8152. // the successful spawn point's location
  8153. NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 0, 255, 0, 100, 60 );
  8154. // drop down to ground
  8155. Vector GroundPos = DropToGround( NULL, pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX );
  8156. // the location the player will spawn at
  8157. NDebugOverlay::Box( GroundPos, VEC_HULL_MIN, VEC_HULL_MAX, 0, 0, 255, 100, 60 );
  8158. // draw the spawn angles
  8159. QAngle spotAngles = pSpot->GetLocalAngles();
  8160. Vector vecForward;
  8161. AngleVectors( spotAngles, &vecForward );
  8162. NDebugOverlay::HorzArrow( pSpot->GetAbsOrigin(), pSpot->GetAbsOrigin() + vecForward * 32, 10, 255, 0, 0, 255, true, 60 );
  8163. }
  8164. else
  8165. {
  8166. // failed spawn point location
  8167. NDebugOverlay::Box( pSpot->GetAbsOrigin(), VEC_HULL_MIN, VEC_HULL_MAX, 255, 0, 0, 100, 60 );
  8168. }
  8169. // increment pSpot
  8170. pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName );
  8171. }
  8172. }
  8173. // -------------------------------------------------------------------------------- //
  8174. void TestSpawns()
  8175. {
  8176. TestSpawnPointType( "info_player_teamspawn" );
  8177. }
  8178. ConCommand cc_TestSpawns( "map_showspawnpoints", TestSpawns, "Dev - test the spawn points, draws for 60 seconds", FCVAR_CHEAT );
  8179. // -------------------------------------------------------------------------------- //
  8180. void cc_ShowRespawnTimes()
  8181. {
  8182. CTFGameRules *pRules = TFGameRules();
  8183. CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
  8184. if ( pRules && pPlayer )
  8185. {
  8186. float flRedMin = ( pRules->m_TeamRespawnWaveTimes[TF_TEAM_RED] >= 0 ? pRules->m_TeamRespawnWaveTimes[TF_TEAM_RED] : mp_respawnwavetime.GetFloat() );
  8187. float flRedScalar = pRules->GetRespawnTimeScalar( TF_TEAM_RED );
  8188. float flNextRedRespawn = pRules->GetNextRespawnWave( TF_TEAM_RED, NULL ) - gpGlobals->curtime;
  8189. float flBlueMin = ( pRules->m_TeamRespawnWaveTimes[TF_TEAM_BLUE] >= 0 ? pRules->m_TeamRespawnWaveTimes[TF_TEAM_BLUE] : mp_respawnwavetime.GetFloat() );
  8190. float flBlueScalar = pRules->GetRespawnTimeScalar( TF_TEAM_BLUE );
  8191. float flNextBlueRespawn = pRules->GetNextRespawnWave( TF_TEAM_BLUE, NULL ) - gpGlobals->curtime;
  8192. char tempRed[128];
  8193. Q_snprintf( tempRed, sizeof( tempRed ), "Red: Min Spawn %2.2f, Scalar %2.2f, Next Spawn In: %.2f\n", flRedMin, flRedScalar, flNextRedRespawn );
  8194. char tempBlue[128];
  8195. Q_snprintf( tempBlue, sizeof( tempBlue ), "Blue: Min Spawn %2.2f, Scalar %2.2f, Next Spawn In: %.2f\n", flBlueMin, flBlueScalar, flNextBlueRespawn );
  8196. ClientPrint( pPlayer, HUD_PRINTTALK, tempRed );
  8197. ClientPrint( pPlayer, HUD_PRINTTALK, tempBlue );
  8198. }
  8199. }
  8200. ConCommand mp_showrespawntimes( "mp_showrespawntimes", cc_ShowRespawnTimes, "Show the min respawn times for the teams", FCVAR_CHEAT );
  8201. //-----------------------------------------------------------------------------
  8202. // Purpose:
  8203. //-----------------------------------------------------------------------------
  8204. CBaseEntity *CTFGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer )
  8205. {
  8206. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  8207. if ( IsInItemTestingMode() && pTFPlayer->m_bItemTestingRespawn )
  8208. {
  8209. pTFPlayer->m_bItemTestingRespawn = false;
  8210. return NULL;
  8211. }
  8212. // get valid spawn point
  8213. CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint();
  8214. // drop down to ground
  8215. Vector GroundPos = DropToGround( pPlayer, pSpawnSpot->GetAbsOrigin(), VEC_HULL_MIN_SCALED( pTFPlayer ), VEC_HULL_MAX_SCALED( pTFPlayer ) );
  8216. // Move the player to the place it said.
  8217. pPlayer->SetLocalOrigin( GroundPos + Vector(0,0,1) );
  8218. pPlayer->SetAbsVelocity( vec3_origin );
  8219. pPlayer->SetLocalAngles( pSpawnSpot->GetLocalAngles() );
  8220. pPlayer->m_Local.m_vecPunchAngle = vec3_angle;
  8221. pPlayer->m_Local.m_vecPunchAngleVel = vec3_angle;
  8222. pPlayer->SnapEyeAngles( pSpawnSpot->GetLocalAngles() );
  8223. return pSpawnSpot;
  8224. }
  8225. //-----------------------------------------------------------------------------
  8226. // Purpose: Checks to see if the player is on the correct team and whether or
  8227. // not the spawn point is available.
  8228. //-----------------------------------------------------------------------------
  8229. bool CTFGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer, bool bIgnorePlayers, PlayerTeamSpawnMode_t nSpawnMode /* = 0*/ )
  8230. {
  8231. bool bMatchSummary = ShowMatchSummary();
  8232. // Check the team.
  8233. // In Item Testing mode, bots all use the Red team spawns, and the player uses Blue
  8234. if ( IsInItemTestingMode() )
  8235. {
  8236. if ( pSpot->GetTeamNumber() != (pPlayer->IsFakeClient() ? TF_TEAM_RED : TF_TEAM_BLUE) )
  8237. return false;
  8238. }
  8239. else
  8240. {
  8241. if ( !bMatchSummary )
  8242. {
  8243. if ( pSpot->GetTeamNumber() != pPlayer->GetTeamNumber() )
  8244. return false;
  8245. }
  8246. }
  8247. if ( !pSpot->IsTriggered( pPlayer ) )
  8248. return false;
  8249. CTFTeamSpawn *pCTFSpawn = dynamic_cast<CTFTeamSpawn*>( pSpot );
  8250. if ( pCTFSpawn )
  8251. {
  8252. if ( pCTFSpawn->IsDisabled() )
  8253. return false;
  8254. if ( pCTFSpawn->GetTeamSpawnMode() && pCTFSpawn->GetTeamSpawnMode() != nSpawnMode )
  8255. return false;
  8256. if ( bMatchSummary )
  8257. {
  8258. if ( pCTFSpawn->AlreadyUsedForMatchSummary() )
  8259. return false;
  8260. if ( pCTFSpawn->GetMatchSummaryType() == PlayerTeamSpawn_MatchSummary_Winner )
  8261. {
  8262. if ( pPlayer->GetTeamNumber() != GetWinningTeam() )
  8263. return false;
  8264. }
  8265. else if ( pCTFSpawn->GetMatchSummaryType() == PlayerTeamSpawn_MatchSummary_Loser )
  8266. {
  8267. if ( pPlayer->GetTeamNumber() == GetWinningTeam() )
  8268. return false;
  8269. }
  8270. else
  8271. {
  8272. return false;
  8273. }
  8274. }
  8275. else
  8276. {
  8277. if ( pCTFSpawn->GetMatchSummaryType() != PlayerTeamSpawn_MatchSummary_None )
  8278. return false;
  8279. }
  8280. }
  8281. Vector mins = VEC_HULL_MIN_SCALED( pPlayer );
  8282. Vector maxs = VEC_HULL_MAX_SCALED( pPlayer );
  8283. if ( !bIgnorePlayers && !bMatchSummary )
  8284. {
  8285. Vector vTestMins = pSpot->GetAbsOrigin() + mins;
  8286. Vector vTestMaxs = pSpot->GetAbsOrigin() + maxs;
  8287. return UTIL_IsSpaceEmpty( pPlayer, vTestMins, vTestMaxs );
  8288. }
  8289. trace_t trace;
  8290. UTIL_TraceHull( pSpot->GetAbsOrigin(), pSpot->GetAbsOrigin(), mins, maxs, MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
  8291. if ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) )
  8292. {
  8293. if ( bMatchSummary )
  8294. {
  8295. if ( pCTFSpawn )
  8296. {
  8297. pCTFSpawn->SetAlreadyUsedForMatchSummary();
  8298. }
  8299. }
  8300. return true;
  8301. }
  8302. return false;
  8303. }
  8304. Vector CTFGameRules::VecItemRespawnSpot( CItem *pItem )
  8305. {
  8306. return pItem->GetOriginalSpawnOrigin();
  8307. }
  8308. QAngle CTFGameRules::VecItemRespawnAngles( CItem *pItem )
  8309. {
  8310. return pItem->GetOriginalSpawnAngles();
  8311. }
  8312. int CTFGameRules::ItemShouldRespawn( CItem *pItem )
  8313. {
  8314. return BaseClass::ItemShouldRespawn( pItem );
  8315. }
  8316. float CTFGameRules::FlItemRespawnTime( CItem *pItem )
  8317. {
  8318. return ITEM_RESPAWN_TIME;
  8319. }
  8320. //-----------------------------------------------------------------------------
  8321. // Purpose:
  8322. //-----------------------------------------------------------------------------
  8323. const char *CTFGameRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer )
  8324. {
  8325. if ( !pPlayer ) // dedicated server output
  8326. {
  8327. return NULL;
  8328. }
  8329. const char *pszFormat = NULL;
  8330. // coach?
  8331. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  8332. if ( pTFPlayer && pTFPlayer->IsCoaching() )
  8333. {
  8334. pszFormat = "TF_Chat_Coach";
  8335. }
  8336. // team only
  8337. else if ( bTeamOnly == true )
  8338. {
  8339. if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
  8340. {
  8341. pszFormat = "TF_Chat_Spec";
  8342. }
  8343. else
  8344. {
  8345. if ( pPlayer->IsAlive() == false && State_Get() != GR_STATE_TEAM_WIN )
  8346. {
  8347. pszFormat = "TF_Chat_Team_Dead";
  8348. }
  8349. else
  8350. {
  8351. const char *chatLocation = GetChatLocation( bTeamOnly, pPlayer );
  8352. if ( chatLocation && *chatLocation )
  8353. {
  8354. pszFormat = "TF_Chat_Team_Loc";
  8355. }
  8356. else
  8357. {
  8358. pszFormat = "TF_Chat_Team";
  8359. }
  8360. }
  8361. }
  8362. }
  8363. // everyone
  8364. else
  8365. {
  8366. if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
  8367. {
  8368. pszFormat = "TF_Chat_AllSpec";
  8369. }
  8370. else
  8371. {
  8372. if ( pPlayer->IsAlive() == false && State_Get() != GR_STATE_TEAM_WIN )
  8373. {
  8374. pszFormat = "TF_Chat_AllDead";
  8375. }
  8376. else
  8377. {
  8378. pszFormat = "TF_Chat_All";
  8379. }
  8380. }
  8381. }
  8382. return pszFormat;
  8383. }
  8384. VoiceCommandMenuItem_t *CTFGameRules::VoiceCommand( CBaseMultiplayerPlayer *pPlayer, int iMenu, int iItem )
  8385. {
  8386. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  8387. if ( pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
  8388. {
  8389. engine->ClientCommand( pTFPlayer->edict(), "boo" );
  8390. return NULL;
  8391. }
  8392. VoiceCommandMenuItem_t *pItem = BaseClass::VoiceCommand( pPlayer, iMenu, iItem );
  8393. if ( pItem )
  8394. {
  8395. int iActivity = ActivityList_IndexForName( pItem->m_szGestureActivity );
  8396. if ( iActivity != ACT_INVALID )
  8397. {
  8398. if ( pTFPlayer )
  8399. {
  8400. pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_VOICE_COMMAND_GESTURE, iActivity );
  8401. // Note when we call for a Medic.
  8402. // Hardcoding this for menu 0, item 0 is an ugly hack, but we don't have a good way to
  8403. // translate this at this level without plumbing a through bunch of stuff (MSB)
  8404. if ( iMenu == 0 && iItem == 0 )
  8405. {
  8406. pTFPlayer->NoteMedicCall();
  8407. }
  8408. }
  8409. }
  8410. }
  8411. return pItem;
  8412. }
  8413. //-----------------------------------------------------------------------------
  8414. // Purpose: Actually change a player's name.
  8415. //-----------------------------------------------------------------------------
  8416. void CTFGameRules::ChangePlayerName( CTFPlayer *pPlayer, const char *pszNewName )
  8417. {
  8418. const char *pszOldName = pPlayer->GetPlayerName();
  8419. // Check if they can change their name
  8420. // don't show when bots change their names in PvE mode
  8421. if ( State_Get() != GR_STATE_STALEMATE && !( IsPVEModeActive() && pPlayer->IsBot() ) )
  8422. {
  8423. CReliableBroadcastRecipientFilter filter;
  8424. UTIL_SayText2Filter( filter, pPlayer, false, "#TF_Name_Change", pszOldName, pszNewName );
  8425. IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" );
  8426. if ( event )
  8427. {
  8428. event->SetInt( "userid", pPlayer->GetUserID() );
  8429. event->SetString( "oldname", pszOldName );
  8430. event->SetString( "newname", pszNewName );
  8431. gameeventmanager->FireEvent( event );
  8432. }
  8433. }
  8434. pPlayer->SetPlayerName( pszNewName );
  8435. }
  8436. //-----------------------------------------------------------------------------
  8437. // Purpose:
  8438. //-----------------------------------------------------------------------------
  8439. void CTFGameRules::ClientSettingsChanged( CBasePlayer *pPlayer )
  8440. {
  8441. const char *pszName = engine->GetClientConVarValue( pPlayer->entindex(), "name" );
  8442. const char *pszOldName = pPlayer->GetPlayerName();
  8443. CTFPlayer *pTFPlayer = (CTFPlayer*)pPlayer;
  8444. // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name)
  8445. // Note, not using FStrEq so that this is case sensitive
  8446. if ( pszOldName[0] != 0 && Q_strncmp( pszOldName, pszName, MAX_PLAYER_NAME_LENGTH-1 ) )
  8447. {
  8448. ChangePlayerName( pTFPlayer, pszName );
  8449. }
  8450. // keep track of their hud_classautokill value
  8451. int nClassAutoKill = Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "hud_classautokill" ) );
  8452. pTFPlayer->SetHudClassAutoKill( nClassAutoKill > 0 ? true : false );
  8453. // keep track of their tf_medigun_autoheal value
  8454. pTFPlayer->SetMedigunAutoHeal( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "tf_medigun_autoheal" ) ) > 0 );
  8455. // keep track of their cl_autorezoom value
  8456. pTFPlayer->SetAutoRezoom( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autorezoom" ) ) > 0 );
  8457. pTFPlayer->SetAutoReload( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "cl_autoreload" ) ) > 0 );
  8458. // keep track of their tf_remember_lastswitched value
  8459. pTFPlayer->SetRememberActiveWeapon( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "tf_remember_activeweapon" ) ) > 0 );
  8460. pTFPlayer->SetRememberLastWeapon( Q_atoi( engine->GetClientConVarValue( pPlayer->entindex(), "tf_remember_lastswitched" ) ) > 0 );
  8461. const char *pszFov = engine->GetClientConVarValue( pPlayer->entindex(), "fov_desired" );
  8462. int iFov = atoi(pszFov);
  8463. iFov = clamp( iFov, 75, MAX_FOV );
  8464. pTFPlayer->SetDefaultFOV( iFov );
  8465. pTFPlayer->m_bFlipViewModels = Q_strcmp( engine->GetClientConVarValue( pPlayer->entindex(), "cl_flipviewmodels" ), "1" ) == 0;
  8466. }
  8467. //-----------------------------------------------------------------------------
  8468. // Purpose: Return true if the specified player can carry any more of the ammo type
  8469. //-----------------------------------------------------------------------------
  8470. bool CTFGameRules::CanHaveAmmo( CBaseCombatCharacter *pPlayer, int iAmmoIndex )
  8471. {
  8472. if ( iAmmoIndex > -1 )
  8473. {
  8474. CTFPlayer *pTFPlayer = (CTFPlayer*)pPlayer;
  8475. if ( pTFPlayer )
  8476. {
  8477. // Get the max carrying capacity for this ammo
  8478. int iMaxCarry = pTFPlayer->GetMaxAmmo(iAmmoIndex);
  8479. // Does the player have room for more of this type of ammo?
  8480. if ( pTFPlayer->GetAmmoCount( iAmmoIndex ) < iMaxCarry )
  8481. return true;
  8482. }
  8483. }
  8484. return false;
  8485. }
  8486. //-----------------------------------------------------------------------------
  8487. // Purpose:
  8488. //-----------------------------------------------------------------------------
  8489. bool EconEntity_KillEaterEventPassesRestrictionCheck( const IEconItemInterface *pEconInterface, const CEconItemAttributeDefinition *pRestrictionAttribDef, const CEconItemAttributeDefinition *pRestrictionValueAttribDef, const CSteamID VictimSteamID )
  8490. {
  8491. uint32 unRestrictionType = kStrangeEventRestriction_None;
  8492. attrib_value_t unRestrictionValue;
  8493. DbgVerify( pEconInterface->FindAttribute( pRestrictionAttribDef, &unRestrictionType ) ==
  8494. pEconInterface->FindAttribute( pRestrictionValueAttribDef, &unRestrictionValue ) );
  8495. switch (unRestrictionType)
  8496. {
  8497. case kStrangeEventRestriction_None:
  8498. return true;
  8499. case kStrangeEventRestriction_Map:
  8500. {
  8501. const MapDef_t *pMap = GetItemSchema()->GetMasterMapDefByName(STRING(gpGlobals->mapname));
  8502. return pMap && pMap->m_nDefIndex == unRestrictionValue;
  8503. }
  8504. case kStrangeEventRestriction_VictimSteamAccount:
  8505. return VictimSteamID.GetAccountID() == unRestrictionValue;
  8506. case kStrangeEventRestriction_Competitive:
  8507. {
  8508. if ( TFGameRules() && TFGameRules()->IsMatchTypeCompetitive() )
  8509. {
  8510. // Match Season unRestrictionValue == Season Number
  8511. return true;
  8512. }
  8513. return false;
  8514. }
  8515. } // end Switch
  8516. AssertMsg1( false, "Unhandled strange event restriction %u!", unRestrictionType );
  8517. return false;
  8518. }
  8519. //-----------------------------------------------------------------------------
  8520. // Purpose:
  8521. //-----------------------------------------------------------------------------
  8522. static CSteamID GetSteamIDForKillEaterScoring( CTFPlayer *pPlayer )
  8523. {
  8524. if ( pPlayer->IsBot() )
  8525. return k_steamIDNil;
  8526. CSteamID ret;
  8527. if ( !pPlayer->GetSteamID( &ret ) )
  8528. return k_steamIDNil;
  8529. return ret;
  8530. }
  8531. //-----------------------------------------------------------------------------
  8532. // Purpose:
  8533. //-----------------------------------------------------------------------------
  8534. class CStrangeEventValidator
  8535. {
  8536. public:
  8537. CStrangeEventValidator( IEconItemInterface *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim, kill_eater_event_t eEventType )
  8538. : m_unItemID( INVALID_ITEM_ID )
  8539. , m_eEventType( eEventType )
  8540. {
  8541. m_bIsValid = BInitEventParams( pEconEntity, pOwner, pVictim );
  8542. }
  8543. bool BIsValidEvent() const { return m_bIsValid; }
  8544. CSteamID GetKillerSteamID() const { Assert( m_bIsValid ); return m_KillerSteamID; }
  8545. CSteamID GetVictimSteamID() const { Assert( m_bIsValid ); return m_VictimSteamID; }
  8546. itemid_t GetItemID() const { Assert( m_bIsValid ); return m_unItemID; }
  8547. kill_eater_event_t GetEventType() const { Assert( m_bIsValid ); return m_eEventType; }
  8548. private:
  8549. bool BInitEventParams( IEconItemInterface *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim )
  8550. {
  8551. static CSchemaAttributeDefHandle pAttrDef_KillEater( "kill eater" );
  8552. // Kill-eater weapons.
  8553. if ( !pEconEntity )
  8554. return false;
  8555. if ( !pOwner )
  8556. return false;
  8557. if ( !pVictim )
  8558. return false;
  8559. // Ignore events where we're affecting ourself.
  8560. if ( pOwner == pVictim )
  8561. return false;
  8562. // Store off our item ID for reference post-init.
  8563. m_unItemID = pEconEntity->GetID();
  8564. // Always require that we have at least the base kill eater attribute before sending any messages
  8565. // to the GC.
  8566. if ( !pEconEntity->FindAttribute( pAttrDef_KillEater ) )
  8567. return false;
  8568. // Don't bother sending a message to the GC if either party is a bot, unless we're tracking events against
  8569. // bots specifically.
  8570. if ( !pOwner->GetSteamID( &m_KillerSteamID ) )
  8571. return false;
  8572. m_VictimSteamID = GetSteamIDForKillEaterScoring( pVictim );
  8573. if ( !m_VictimSteamID.IsValid() )
  8574. {
  8575. if ( !GetItemSchema()->GetKillEaterScoreTypeAllowsBotVictims( m_eEventType ) )
  8576. return false;
  8577. }
  8578. // Also require that we have whatever event type we're looking for, unless we're looking for regular
  8579. // player kills in which case we may or may not have a field to describe that.
  8580. for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
  8581. {
  8582. const CEconItemAttributeDefinition *pScoreAttribDef = GetKillEaterAttr_Score(i);
  8583. if ( !pScoreAttribDef )
  8584. continue;
  8585. // If we don't have this attribute, move on. It's possible to be missing this attribute but still
  8586. // have the next one in the list if we have user-customized tracking types.
  8587. if ( !pEconEntity->FindAttribute( pScoreAttribDef ) )
  8588. continue;
  8589. const CEconItemAttributeDefinition *pScoreTypeAttribDef = GetKillEaterAttr_Type(i);
  8590. if ( !pScoreTypeAttribDef )
  8591. continue;
  8592. // If we're missing our type attribute, we interpret that as "track kills" -- none of the original
  8593. // kill eaters have score type attributes.
  8594. float fKillEaterEventType;
  8595. kill_eater_event_t eMatchEvent = FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pEconEntity, pScoreTypeAttribDef, &fKillEaterEventType )
  8596. ? (kill_eater_event_t)(int)fKillEaterEventType
  8597. : kKillEaterEvent_PlayerKill;
  8598. if ( m_eEventType == eMatchEvent )
  8599. {
  8600. // Does this attribute also specify a restriction (ie., "has to be on this specific map" or "has to be
  8601. // against this specific account"? If so, only count this as a match if the restriction check passes.
  8602. if ( EconEntity_KillEaterEventPassesRestrictionCheck( pEconEntity, GetKillEaterAttr_Restriction(i), GetKillEaterAttr_RestrictionValue(i), m_VictimSteamID ) )
  8603. return true;
  8604. }
  8605. }
  8606. return false;
  8607. }
  8608. private:
  8609. bool m_bIsValid;
  8610. CSteamID m_KillerSteamID;
  8611. CSteamID m_VictimSteamID;
  8612. itemid_t m_unItemID;
  8613. kill_eater_event_t m_eEventType;
  8614. };
  8615. //-----------------------------------------------------------------------------
  8616. // Purpose:
  8617. //-----------------------------------------------------------------------------
  8618. template < typename tMsgType >
  8619. static void FillOutBaseKillEaterMessage( tMsgType *out_pMsg, const CStrangeEventValidator& Validator )
  8620. {
  8621. Assert( out_pMsg );
  8622. Assert( Validator.BIsValidEvent() );
  8623. out_pMsg->set_killer_steam_id( Validator.GetKillerSteamID().ConvertToUint64() );
  8624. out_pMsg->set_victim_steam_id( Validator.GetVictimSteamID().ConvertToUint64() );
  8625. out_pMsg->set_item_id( Validator.GetItemID() );
  8626. out_pMsg->set_event_type( Validator.GetEventType() );
  8627. }
  8628. //-----------------------------------------------------------------------------
  8629. // Purpose: Get the actual killeater weapon from a CTakeDamageInfo
  8630. //-----------------------------------------------------------------------------
  8631. CTFWeaponBase *GetKilleaterWeaponFromDamageInfo( const CTakeDamageInfo *pInfo )
  8632. {
  8633. CTFWeaponBase *pTFWeapon = dynamic_cast<CTFWeaponBase *>( pInfo->GetWeapon() );
  8634. CBaseEntity* pInflictor = pInfo->GetInflictor();
  8635. // If there's no weapon specified, it might have been from a sentry
  8636. if ( !pTFWeapon )
  8637. {
  8638. // If a sentry did the damage with bullets, it's the inflictor
  8639. CObjectSentrygun *pSentrygun = dynamic_cast<CObjectSentrygun *>( pInflictor );
  8640. if ( !pSentrygun )
  8641. {
  8642. // If a sentry's rocket did the damage, then the rocket is the inflictor, and the sentry is the rocket's owner
  8643. pSentrygun = pInflictor ? dynamic_cast<CObjectSentrygun *>( pInflictor->GetOwnerEntity() ) : NULL;
  8644. }
  8645. if ( pSentrygun )
  8646. {
  8647. // Maybe they were using a Wrangler? The builder is the player who owns the sentry
  8648. CTFPlayer *pBuilder = pSentrygun->GetBuilder();
  8649. if ( pBuilder )
  8650. {
  8651. // Check if they have a Wrangler and were aiming with it
  8652. CTFLaserPointer* pLaserPointer = dynamic_cast< CTFLaserPointer * >( pBuilder->GetEntityForLoadoutSlot( LOADOUT_POSITION_SECONDARY ) );
  8653. if ( pLaserPointer && pLaserPointer->HasLaserDot() )
  8654. {
  8655. pTFWeapon = pLaserPointer;
  8656. }
  8657. }
  8658. }
  8659. }
  8660. // need to check for deflected projectiles so the weapon that did the most recent
  8661. // deflection will get the killeater credit instead of the original launcher
  8662. if ( pInflictor )
  8663. {
  8664. CTFWeaponBaseGrenadeProj *pBaseGrenade = dynamic_cast< CTFWeaponBaseGrenadeProj* >( pInflictor );
  8665. if ( pBaseGrenade )
  8666. {
  8667. if ( pBaseGrenade->GetDeflected() && pBaseGrenade->GetLauncher() )
  8668. {
  8669. pTFWeapon = dynamic_cast< CTFWeaponBase* >( pBaseGrenade->GetLauncher() );
  8670. }
  8671. }
  8672. else
  8673. {
  8674. CTFBaseRocket *pRocket = dynamic_cast< CTFBaseRocket* >( pInflictor );
  8675. if ( pRocket )
  8676. {
  8677. if ( pRocket->GetDeflected() && pRocket->GetLauncher() )
  8678. {
  8679. pTFWeapon = dynamic_cast< CTFWeaponBase* >( pRocket->GetLauncher() );
  8680. }
  8681. }
  8682. }
  8683. }
  8684. return pTFWeapon;
  8685. }
  8686. //-----------------------------------------------------------------------------
  8687. void EconEntity_ValidateAndSendStrangeMessageToGC( IEconItemInterface *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8688. {
  8689. CStrangeEventValidator Validator( pEconEntity, pOwner, pVictim, eEventType );
  8690. if ( !Validator.BIsValidEvent() )
  8691. return;
  8692. GCSDK::CProtoBufMsg<CMsgIncrementKillCountAttribute> msg( k_EMsgGC_IncrementKillCountAttribute );
  8693. FillOutBaseKillEaterMessage( &msg.Body(), Validator );
  8694. if ( nIncrementValue != 1 )
  8695. {
  8696. msg.Body().set_increment_value( nIncrementValue );
  8697. }
  8698. GCClientSystem()->BSendMessage( msg );
  8699. }
  8700. //-----------------------------------------------------------------------------
  8701. // Purpose:
  8702. //-----------------------------------------------------------------------------
  8703. void EconEntity_OnOwnerKillEaterEvent( CEconEntity *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8704. {
  8705. if ( !pEconEntity )
  8706. return;
  8707. EconItemInterface_OnOwnerKillEaterEvent( pEconEntity->GetAttributeContainer()->GetItem(), pOwner, pVictim, eEventType, nIncrementValue );
  8708. }
  8709. //-----------------------------------------------------------------------------
  8710. void EconItemInterface_OnOwnerKillEaterEvent( IEconItemInterface *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8711. {
  8712. bool bEconEntityHandled = false;
  8713. // Fire the kill eater event on all wearables
  8714. // Wearables can have all Strange parts
  8715. for ( int i = 0; i < pOwner->GetNumWearables(); ++i )
  8716. {
  8717. CTFWearable *pWearableItem = dynamic_cast<CTFWearable *>( pOwner->GetWearable( i ) );
  8718. if ( !pWearableItem )
  8719. continue;
  8720. if ( !pWearableItem->GetAttributeContainer() )
  8721. continue;
  8722. EconEntity_ValidateAndSendStrangeMessageToGC( pWearableItem->GetAttributeContainer()->GetItem(), pOwner, pVictim, eEventType, nIncrementValue );
  8723. if ( pWearableItem->GetAttributeContainer()->GetItem() == pEconEntity )
  8724. {
  8725. bEconEntityHandled = true;
  8726. }
  8727. }
  8728. if ( !bEconEntityHandled )
  8729. {
  8730. EconEntity_ValidateAndSendStrangeMessageToGC( pEconEntity, pOwner, pVictim, eEventType, nIncrementValue );
  8731. }
  8732. }
  8733. //-----------------------------------------------------------------------------
  8734. // Purpose:
  8735. //-----------------------------------------------------------------------------
  8736. void EconEntity_OnOwnerKillEaterEventNoPartner( CEconEntity *pEconEntity, CTFPlayer *pOwner, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8737. {
  8738. if ( !pEconEntity )
  8739. return;
  8740. EconItemInterface_OnOwnerKillEaterEventNoPartner( pEconEntity->GetAttributeContainer()->GetItem(), pOwner, eEventType, nIncrementValue );
  8741. }
  8742. //-----------------------------------------------------------------------------
  8743. void EconItemInterface_OnOwnerKillEaterEventNoPartner( IEconItemInterface *pEconEntity, CTFPlayer *pOwner, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8744. {
  8745. // Look for another player to pass in. We do two passes over the list of candidates.
  8746. // First we look for anyone that we know is a real player. If we don't find any of
  8747. // those then we pass in a known bot. This means that things that allow bots will
  8748. // still find one and work, but event types that don't allow bots won't be dependent
  8749. // on the order of players in the list.
  8750. for( int iPass = 1; iPass <= 2; iPass++ )
  8751. {
  8752. for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
  8753. {
  8754. CTFPlayer *pOtherPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
  8755. if ( !pOtherPlayer )
  8756. continue;
  8757. // Ignore ourself.
  8758. if ( pOtherPlayer == pOwner )
  8759. continue;
  8760. const bool bCandidatePasses = (iPass == 1 && GetSteamIDForKillEaterScoring( pOtherPlayer ).IsValid()) // if we have a real Steam ID we're golden
  8761. || (iPass == 2); // or if we made it to the second pass, any valid player pointer is as good as any other
  8762. if ( bCandidatePasses )
  8763. {
  8764. EconItemInterface_OnOwnerKillEaterEvent( pEconEntity, pOwner, pOtherPlayer, eEventType, nIncrementValue );
  8765. return;
  8766. }
  8767. }
  8768. }
  8769. // We couldn't find anyone else at all. This is hard. Let's give up.
  8770. }
  8771. //-----------------------------------------------------------------------------
  8772. // Purpose:
  8773. //-----------------------------------------------------------------------------
  8774. static GCSDK::CProtoBufMsg<CMsgIncrementKillCountAttribute_Multiple> *s_pmsgIncrementKillCountMessageBatch = NULL;
  8775. //-----------------------------------------------------------------------------
  8776. void EconEntity_ValidateAndSendStrangeMessageToGC_Batched( IEconItemInterface *pEconInterface, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8777. {
  8778. CStrangeEventValidator Validator( pEconInterface, pOwner, pVictim, eEventType );
  8779. if ( !Validator.BIsValidEvent() )
  8780. return;
  8781. // This has to be dynamically allocated because we can't static-init a protobuf message at startup type.
  8782. if ( !s_pmsgIncrementKillCountMessageBatch )
  8783. {
  8784. s_pmsgIncrementKillCountMessageBatch = new GCSDK::CProtoBufMsg<CMsgIncrementKillCountAttribute_Multiple>( k_EMsgGC_IncrementKillCountAttribute_Multiple );
  8785. }
  8786. // Look for an existing message that matches this user and this event.
  8787. for ( int i = 0; i < s_pmsgIncrementKillCountMessageBatch->Body().msgs_size(); i++ )
  8788. {
  8789. CMsgIncrementKillCountAttribute *pMsg = s_pmsgIncrementKillCountMessageBatch->Body().mutable_msgs( i );
  8790. Assert( pMsg );
  8791. if ( pMsg->killer_steam_id() == Validator.GetKillerSteamID().ConvertToUint64() &&
  8792. pMsg->victim_steam_id() == Validator.GetVictimSteamID().ConvertToUint64() &&
  8793. pMsg->item_id() == Validator.GetItemID() &&
  8794. (kill_eater_event_t)pMsg->event_type() == Validator.GetEventType() )
  8795. {
  8796. // We found an existing entry for this message. All we want to do is add to the stat we're tracking
  8797. // but still send up this single message.
  8798. Assert( pMsg->has_increment_value() );
  8799. pMsg->set_increment_value( pMsg->increment_value() + nIncrementValue );
  8800. return;
  8801. }
  8802. }
  8803. // We don't have an existing entry, so make a new one.
  8804. CMsgIncrementKillCountAttribute *pNewMsg = s_pmsgIncrementKillCountMessageBatch->Body().add_msgs();
  8805. FillOutBaseKillEaterMessage( pNewMsg, Validator );
  8806. pNewMsg->set_increment_value( nIncrementValue );
  8807. }
  8808. //-----------------------------------------------------------------------------
  8809. void EconEntity_OnOwnerKillEaterEvent_Batched( CEconEntity *pEconEntity, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8810. {
  8811. if ( !pEconEntity )
  8812. return;
  8813. EconItemInterface_OnOwnerKillEaterEvent_Batched( pEconEntity->GetAttributeContainer()->GetItem(), pOwner, pVictim, eEventType, nIncrementValue );
  8814. }
  8815. //-----------------------------------------------------------------------------
  8816. void EconItemInterface_OnOwnerKillEaterEvent_Batched( IEconItemInterface *pEconInterface, class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue /*= 1*/ )
  8817. {
  8818. for ( int i = 0; i < pOwner->GetNumWearables(); ++i )
  8819. {
  8820. CTFWearable *pWearableItem = dynamic_cast<CTFWearable *>( pOwner->GetWearable( i ) );
  8821. if ( !pWearableItem )
  8822. continue;
  8823. if ( !pWearableItem->GetAttributeContainer() )
  8824. continue;
  8825. CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem();
  8826. if ( !pEconItemView )
  8827. continue;
  8828. EconEntity_ValidateAndSendStrangeMessageToGC_Batched( pWearableItem->GetAttributeContainer()->GetItem(), pOwner, pVictim, eEventType, nIncrementValue );
  8829. }
  8830. EconEntity_ValidateAndSendStrangeMessageToGC_Batched( pEconInterface, pOwner, pVictim, eEventType, nIncrementValue );
  8831. }
  8832. //-----------------------------------------------------------------------------
  8833. // Purpose:
  8834. //-----------------------------------------------------------------------------
  8835. void KillEaterEvents_FlushBatches()
  8836. {
  8837. if ( s_pmsgIncrementKillCountMessageBatch )
  8838. {
  8839. Assert( s_pmsgIncrementKillCountMessageBatch->Body().msgs_size() > 0 );
  8840. GCClientSystem()->BSendMessage( *s_pmsgIncrementKillCountMessageBatch );
  8841. delete s_pmsgIncrementKillCountMessageBatch;
  8842. s_pmsgIncrementKillCountMessageBatch = NULL;
  8843. }
  8844. }
  8845. //-----------------------------------------------------------------------------
  8846. // Purpose:
  8847. //-----------------------------------------------------------------------------
  8848. void HatAndMiscEconEntities_OnOwnerKillEaterEventNoParter( class CTFPlayer *pOwner, kill_eater_event_t eEventType, int nIncrementValue )
  8849. {
  8850. EconItemInterface_OnOwnerKillEaterEventNoPartner( NULL, pOwner, eEventType, nIncrementValue );
  8851. }
  8852. //-----------------------------------------------------------------------------
  8853. void HatAndMiscEconEntities_OnOwnerKillEaterEvent( class CTFPlayer *pOwner, class CTFPlayer *pVictim, kill_eater_event_t eEventType, int nIncrementValue )
  8854. {
  8855. EconItemInterface_OnOwnerKillEaterEvent( NULL, pOwner, pVictim, eEventType, nIncrementValue );
  8856. }
  8857. //-----------------------------------------------------------------------------
  8858. void EconEntity_OnOwnerUniqueEconEvent( IEconItemInterface *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim, kill_eater_event_t eEventType )
  8859. {
  8860. CStrangeEventValidator Validator( pEconEntity, pOwner, pVictim, eEventType );
  8861. if ( !Validator.BIsValidEvent() )
  8862. return;
  8863. GCSDK::CProtoBufMsg<CMsgTrackUniquePlayerPairEvent> msg( k_EMsgGC_TrackUniquePlayerPairEvent );
  8864. FillOutBaseKillEaterMessage( &msg.Body(), Validator );
  8865. GCClientSystem()->BSendMessage( msg );
  8866. }
  8867. //-----------------------------------------------------------------------------
  8868. void EconEntity_OnOwnerUniqueEconEvent( CEconEntity *pEconEntity, CTFPlayer *pOwner, CTFPlayer *pVictim, kill_eater_event_t eEventType )
  8869. {
  8870. if ( !pEconEntity )
  8871. return;
  8872. EconEntity_OnOwnerUniqueEconEvent( pEconEntity->GetAttributeContainer()->GetItem(), pOwner, pVictim, eEventType );
  8873. }
  8874. //-----------------------------------------------------------------------------
  8875. void EconEntity_NonEquippedItemKillTracking( CTFPlayer *pOwner, CTFPlayer *pVictim, item_definition_index_t iDefIndex, kill_eater_event_t eEventType, int nIncrementValue = 1 )
  8876. {
  8877. // Validate
  8878. if ( !pOwner || !pVictim || pOwner == pVictim )
  8879. return;
  8880. #ifndef STAGING_ONLY
  8881. if ( pOwner->IsBot() || pVictim->IsBot() )
  8882. return;
  8883. #endif
  8884. CSteamID pOwnerSteamID;
  8885. CSteamID pVictimSteamID;
  8886. if ( !pOwner->GetSteamID( &pOwnerSteamID ) || !pVictim->GetSteamID( &pVictimSteamID ) )
  8887. return;
  8888. // Current date
  8889. static CSchemaAttributeDefHandle pAttrDef_DeactiveDate( "deactive date" );
  8890. uint32 unCurrentDate = CRTime::RTime32TimeCur();
  8891. CEconItemView *pNonEquippedItem = pOwner->Inventory()->GetFirstItemOfItemDef( iDefIndex, pOwner->Inventory() );
  8892. if ( pNonEquippedItem )
  8893. {
  8894. // Check the date
  8895. uint32 unDeactiveDate = 0;
  8896. if ( !pNonEquippedItem->FindAttribute( pAttrDef_DeactiveDate, &unDeactiveDate ) || unDeactiveDate > unCurrentDate )
  8897. {
  8898. GCSDK::CProtoBufMsg<CMsgIncrementKillCountAttribute> msg( k_EMsgGC_IncrementKillCountAttribute );
  8899. msg.Body().set_killer_steam_id( pOwnerSteamID.ConvertToUint64() );
  8900. msg.Body().set_victim_steam_id( pVictimSteamID.ConvertToUint64() );
  8901. if ( nIncrementValue > 1 )
  8902. {
  8903. msg.Body().set_increment_value( nIncrementValue );
  8904. }
  8905. msg.Body().set_item_id( pNonEquippedItem->GetID() );
  8906. msg.Body().set_event_type( eEventType );
  8907. GCClientSystem()->BSendMessage( msg );
  8908. }
  8909. }
  8910. }
  8911. //-----------------------------------------------------------------------------
  8912. void EconEntity_NonEquippedItemKillTracking_NoPartner( CTFPlayer *pOwner, item_definition_index_t iDefIndex, kill_eater_event_t eEventType, int nIncrementValue )
  8913. {
  8914. // Find a partner or give up
  8915. for( int iPass = 1; iPass <= 2; iPass++ )
  8916. {
  8917. for( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
  8918. {
  8919. CTFPlayer *pOtherPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
  8920. if ( !pOtherPlayer )
  8921. continue;
  8922. // Ignore ourself.
  8923. if ( pOtherPlayer == pOwner )
  8924. continue;
  8925. const bool bCandidatePasses = (iPass == 1 && GetSteamIDForKillEaterScoring( pOtherPlayer ).IsValid()) // if we have a real Steam ID we're golden
  8926. || (iPass == 2); // or if we made it to the second pass, any valid player pointer is as good as any other
  8927. if ( bCandidatePasses )
  8928. {
  8929. EconEntity_NonEquippedItemKillTracking( pOwner, pOtherPlayer, iDefIndex, eEventType, nIncrementValue );
  8930. return;
  8931. }
  8932. }
  8933. }
  8934. }
  8935. void EconEntity_NonEquippedItemKillTracking_NoPartnerBatched( class CTFPlayer *pOwner, item_definition_index_t iDefIndex, kill_eater_event_t eEventType, int nIncrementValue )
  8936. {
  8937. // Find a partner or give up
  8938. for ( int iPass = 1; iPass <= 2; iPass++ )
  8939. {
  8940. for ( int playerIndex = 1; playerIndex <= MAX_PLAYERS; playerIndex++ )
  8941. {
  8942. CTFPlayer *pOtherPlayer = ToTFPlayer( UTIL_PlayerByIndex( playerIndex ) );
  8943. if ( !pOtherPlayer )
  8944. continue;
  8945. // Ignore ourself.
  8946. if ( pOtherPlayer == pOwner )
  8947. continue;
  8948. const bool bCandidatePasses = ( iPass == 1 && GetSteamIDForKillEaterScoring( pOtherPlayer ).IsValid() ) // if we have a real Steam ID we're golden
  8949. || ( iPass == 2 ); // or if we made it to the second pass, any valid player pointer is as good as any other
  8950. if ( bCandidatePasses )
  8951. {
  8952. // find the item
  8953. // Current date
  8954. static CSchemaAttributeDefHandle pAttrDef_DeactiveDate( "deactive date" );
  8955. uint32 unCurrentDate = CRTime::RTime32TimeCur();
  8956. CEconItemView *pNonEquippedItem = pOwner->Inventory()->GetFirstItemOfItemDef( iDefIndex, pOwner->Inventory() );
  8957. if ( pNonEquippedItem )
  8958. {
  8959. uint32 unDeactiveDate = 0;
  8960. if ( !pNonEquippedItem->FindAttribute( pAttrDef_DeactiveDate, &unDeactiveDate ) || unDeactiveDate > unCurrentDate )
  8961. {
  8962. EconEntity_ValidateAndSendStrangeMessageToGC_Batched( pNonEquippedItem, pOwner, pOtherPlayer, eEventType, nIncrementValue );
  8963. }
  8964. }
  8965. return;
  8966. }
  8967. }
  8968. }
  8969. }
  8970. //-----------------------------------------------------------------------------
  8971. // TODO: Remove this call and only use the one above
  8972. //-----------------------------------------------------------------------------
  8973. void EconEntity_NonEquippedItemKillTracking( CTFPlayer *pOwner, CTFPlayer *pVictim, int nIncrementValue )
  8974. {
  8975. // Validate
  8976. if ( !pOwner || !pVictim || pOwner == pVictim )
  8977. return;
  8978. if ( pOwner->IsBot() || pVictim->IsBot() )
  8979. return;
  8980. CSteamID pOwnerSteamID;
  8981. CSteamID pVictimSteamID;
  8982. if ( ! pOwner->GetSteamID( &pOwnerSteamID ) || !pVictim->GetSteamID( &pVictimSteamID ) )
  8983. return;
  8984. // Find any active operation
  8985. const auto& mapOperations = GetItemSchema()->GetOperationDefinitions();
  8986. FOR_EACH_MAP_FAST( mapOperations, i )
  8987. {
  8988. CEconOperationDefinition* pOperation = mapOperations[i];
  8989. if ( pOperation->IsActive() && pOperation->IsCampaign() )
  8990. {
  8991. EconEntity_NonEquippedItemKillTracking( pOwner, pVictim, pOperation->GetRequiredItemDefIndex(), kKillEaterEvent_CosmeticOperationKills, nIncrementValue );
  8992. }
  8993. }
  8994. }
  8995. //-----------------------------------------------------------------------------
  8996. // Purpose: Sends a soul with the specified value to every living member of the
  8997. // specified team, originating from vecPosition.
  8998. //-----------------------------------------------------------------------------
  8999. void CTFGameRules::DropHalloweenSoulPackToTeam( int nAmount, const Vector& vecPosition, int nTeamNumber, int nSourceTeam )
  9000. {
  9001. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  9002. {
  9003. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  9004. if ( !pTFPlayer || !pTFPlayer->IsConnected() )
  9005. continue;
  9006. if ( pTFPlayer->GetTeamNumber() != nTeamNumber || !pTFPlayer->IsAlive() || pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
  9007. continue;
  9008. DropHalloweenSoulPack( nAmount, vecPosition, pTFPlayer, nSourceTeam );
  9009. }
  9010. }
  9011. //-----------------------------------------------------------------------------
  9012. // Purpose:
  9013. //-----------------------------------------------------------------------------
  9014. void CTFGameRules::DropHalloweenSoulPack( int nAmount, const Vector& vecSource, CBaseEntity *pTarget, int nSourceTeam )
  9015. {
  9016. QAngle angles(0,0,0);
  9017. CHalloweenSoulPack *pSoulsPack = assert_cast<CHalloweenSoulPack*>( CBaseEntity::CreateNoSpawn( "halloween_souls_pack", vecSource, angles, NULL ) );
  9018. if ( pSoulsPack )
  9019. {
  9020. pSoulsPack->SetTarget( pTarget );
  9021. pSoulsPack->SetAmount( nAmount );
  9022. pSoulsPack->ChangeTeam( nSourceTeam );
  9023. DispatchSpawn( pSoulsPack );
  9024. }
  9025. }
  9026. //-----------------------------------------------------------------------------
  9027. // Purpose:
  9028. //-----------------------------------------------------------------------------
  9029. bool CTFGameRules::ShouldDropSpellPickup()
  9030. {
  9031. if ( IsUsingSpells() )
  9032. {
  9033. return RandomFloat() <= tf_player_spell_drop_on_death_rate.GetFloat();
  9034. }
  9035. return false;
  9036. }
  9037. //-----------------------------------------------------------------------------
  9038. // Purpose:
  9039. //-----------------------------------------------------------------------------
  9040. void CTFGameRules::DropSpellPickup( const Vector& vPosition, int nTier /*= 0*/ ) const
  9041. {
  9042. if ( !IsUsingSpells() )
  9043. return;
  9044. CSpellPickup *pSpellPickup = assert_cast<CSpellPickup*>( CBaseEntity::CreateNoSpawn( "tf_spell_pickup", vPosition + Vector( 0, 0, 50 ), vec3_angle, NULL ) );
  9045. if ( pSpellPickup )
  9046. {
  9047. pSpellPickup->SetTier( nTier );
  9048. DispatchSpawn( pSpellPickup );
  9049. Vector vecImpulse = RandomVector( -0.5f, 0.5f );
  9050. vecImpulse.z = 1.f;
  9051. VectorNormalize( vecImpulse );
  9052. Vector vecVelocity = vecImpulse * 500.f;
  9053. pSpellPickup->DropSingleInstance( vecVelocity, NULL, 0 );
  9054. }
  9055. }
  9056. //-----------------------------------------------------------------------------
  9057. bool CTFGameRules::ShouldDropBonusDuck( void )
  9058. {
  9059. if ( tf_player_drop_bonus_ducks.GetInt() < 0 )
  9060. {
  9061. return IsHolidayActive( kHoliday_EOTL );
  9062. }
  9063. else if ( tf_player_drop_bonus_ducks.GetInt() > 0 )
  9064. {
  9065. return true;
  9066. }
  9067. return false;
  9068. }
  9069. //-----------------------------------------------------------------------------
  9070. bool CTFGameRules::ShouldDropBonusDuckFromPlayer( CTFPlayer *pTFScorer, CTFPlayer *pTFVictim )
  9071. {
  9072. if ( !pTFScorer || !pTFVictim )
  9073. return false;
  9074. #ifndef STAGING_ONLY
  9075. // Only drop if bot is not involved
  9076. if ( pTFScorer->IsBot() || pTFVictim->IsBot() || pTFScorer == pTFVictim )
  9077. return false;
  9078. #endif
  9079. return ShouldDropBonusDuck();
  9080. }
  9081. //-----------------------------------------------------------------------------
  9082. int CTFGameRules::GetDuckSkinForClass( int nTeam, int nClass ) const
  9083. {
  9084. int nSkin = 0;
  9085. if ( nTeam >= FIRST_GAME_TEAM )
  9086. {
  9087. bool bRed = ( nTeam == TF_TEAM_RED );
  9088. switch ( nClass )
  9089. {
  9090. case TF_CLASS_SCOUT:
  9091. nSkin = bRed ? 3 : 12;
  9092. break;
  9093. case TF_CLASS_SNIPER:
  9094. nSkin = bRed ? 4 : 13;
  9095. break;
  9096. case TF_CLASS_SOLDIER:
  9097. nSkin = bRed ? 5 : 14;
  9098. break;
  9099. case TF_CLASS_DEMOMAN:
  9100. nSkin = bRed ? 6 : 15;
  9101. break;
  9102. case TF_CLASS_MEDIC:
  9103. nSkin = bRed ? 7 : 16;
  9104. break;
  9105. case TF_CLASS_HEAVYWEAPONS:
  9106. nSkin = bRed ? 8 : 17;
  9107. break;
  9108. case TF_CLASS_PYRO:
  9109. nSkin = bRed ? 9 : 18;
  9110. break;
  9111. case TF_CLASS_SPY:
  9112. nSkin = bRed ? 10 : 19;
  9113. break;
  9114. case TF_CLASS_ENGINEER:
  9115. nSkin = bRed ? 11 : 20;
  9116. break;
  9117. default:
  9118. nSkin = bRed ? 1 : 2;
  9119. break;
  9120. }
  9121. }
  9122. return nSkin;
  9123. }
  9124. //-----------------------------------------------------------------------------
  9125. //
  9126. #ifdef STAGING_ONLY
  9127. ConVar tf_duck_droprate_min( "tf_duck_droprate_min", "-1", FCVAR_REPLICATED, "Set Minimum Number of Ducks to Spawn. No upgrade" );
  9128. ConVar tf_duck_droprate_bias( "tf_duck_droprate_bias", "0.2", FCVAR_REPLICATED, "Set Bias on Duck Spawns. No upgrade" );
  9129. ConVar tf_duck_power_override( "tf_duck_power_override", "0", FCVAR_REPLICATED, "Override everyone's duck power and use this value when > 0" );
  9130. ConVar tf_duck_random_extra( "tf_duck_random_extra", "3.0f", FCVAR_REPLICATED, "Random Count of Ducks to be added used by bias function" );
  9131. #endif
  9132. ConVar tf_duck_edict_limit( "tf_duck_edict_limit", "1900", FCVAR_REPLICATED, "Maximum number of edicts allowed before spawning a duck" );
  9133. ConVar tf_duck_edict_warning( "tf_duck_edict_warning", "1800", FCVAR_REPLICATED, "Maximum number of edicts allowed before slowing duck spawn rate" );
  9134. void CTFGameRules::DropBonusDuck( const Vector& vPosition, CTFPlayer *pTFCreator /*=NULL*/, CTFPlayer *pAssister /*=NULL*/, CTFPlayer *pTFVictim /*=NULL*/, bool bCrit /*=false*/, bool bObjective /*=false*/) const
  9135. {
  9136. if ( gEntList.NumberOfEdicts() > tf_duck_edict_limit.GetInt() )
  9137. {
  9138. Warning( "Warning: High level of Edicts, Not spawning Ducks \n" );
  9139. return;
  9140. }
  9141. static CSchemaAttributeDefHandle pAttr_DuckLevelBadge( "duck badge level" );
  9142. // Find Badge and increase number of ducks based on badge level (1 duck per 5 levels)
  9143. // Look through equipped items, if one is a duck badge use its level
  9144. int iDuckFlags = 0;
  9145. if ( pTFCreator == NULL || bObjective )
  9146. {
  9147. iDuckFlags |= EDuckFlags::DUCK_FLAG_OBJECTIVE;
  9148. }
  9149. uint32 iDuckBadgeLevel = 0;
  9150. if ( pTFCreator )
  9151. {
  9152. for ( int i = 0; i < pTFCreator->GetNumWearables(); ++i )
  9153. {
  9154. CTFWearable* pWearable = dynamic_cast<CTFWearable*>( pTFCreator->GetWearable( i ) );
  9155. if ( !pWearable )
  9156. continue;
  9157. //if ( pWearable->GetAttributeContainer() && pWearable->GetAttributeContainer()->GetItem() )
  9158. {
  9159. //CEconItemView *pItem = pWearable->GetAttributeContainer()->GetItem();
  9160. CALL_ATTRIB_HOOK_INT_ON_OTHER( pWearable, iDuckBadgeLevel, duck_badge_level );
  9161. //if ( pItem && FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttr_DuckLevelBadge, &iDuckBadgeLevel ) )
  9162. {
  9163. iDuckBadgeLevel++;
  9164. }
  9165. }
  9166. }
  9167. }
  9168. // Badges only go to max of 5 now instead of 10 so just doubling the output value
  9169. int iDuckPower = Min( (int)iDuckBadgeLevel * 2, 11 );
  9170. float flBias = RemapValClamped( (float)iDuckPower, 0.0f, 10.0f, 0.2f, 0.5f);
  9171. float flBiasScale = 3.0f;
  9172. int iMinimum = 0;
  9173. bool bSpecial = false;
  9174. // Drop a few bonus ducks, extra ducks for Crits!
  9175. #ifdef STAGING_ONLY
  9176. flBias = RemapValClamped( (float)iDuckPower, 0.0f, 10.0f, tf_duck_droprate_bias.GetFloat(), 0.5f);
  9177. iMinimum = tf_duck_droprate_min.GetInt();
  9178. if ( tf_duck_power_override.GetInt() > 0 )
  9179. {
  9180. iDuckPower = tf_duck_power_override.GetInt();
  9181. }
  9182. flBiasScale = tf_duck_random_extra.GetFloat();
  9183. #endif
  9184. //tf_duck_droprate_bias
  9185. int iDuckCount = (int)( Bias( RandomFloat( 0, 1 ), flBias ) * ( flBiasScale + iDuckPower ) ) + iMinimum;
  9186. iDuckCount = Max( iDuckCount, (int)iDuckBadgeLevel ); // min ducks for a badge
  9187. if ( bCrit )
  9188. {
  9189. iDuckCount += RandomInt( 1, 2 );
  9190. }
  9191. if ( pTFCreator && ( iDuckBadgeLevel > 0 ) )
  9192. {
  9193. if ( RandomInt( 0, 600 ) <= iDuckPower )
  9194. {
  9195. // MEGA BONUS DUCKS
  9196. iDuckCount += iDuckPower;
  9197. bSpecial = true;
  9198. }
  9199. }
  9200. if ( iDuckCount > 0 )
  9201. {
  9202. // Max of 50 ducks, which is a lot of ducks
  9203. iDuckCount = Min( 50, iDuckCount );
  9204. // High edict count, slow generation
  9205. if ( gEntList.NumberOfEdicts() > tf_duck_edict_warning.GetInt() )
  9206. {
  9207. Warning( "Warning: High level of Edicts, Not spawning as many ducks\n" );
  9208. iDuckCount /= 2;
  9209. }
  9210. int iCreatorId = pTFCreator ? pTFCreator->entindex() : -1;
  9211. int iAssisterId = pAssister ? pAssister->entindex() : -1;
  9212. int iVictimId = -1;
  9213. int iDuckTeam = TEAM_UNASSIGNED;
  9214. if ( pTFVictim )
  9215. {
  9216. iVictimId = pTFVictim->entindex();
  9217. iDuckTeam = pTFVictim->GetTeamNumber();
  9218. }
  9219. for ( int i = 0; i < iDuckCount; ++i )
  9220. {
  9221. Vector vecOrigin = vPosition + Vector( 0, 0, 50 );
  9222. CBonusDuckPickup *pDuckPickup = assert_cast<CBonusDuckPickup*>( CBaseEntity::CreateNoSpawn( "tf_bonus_duck_pickup", vecOrigin, vec3_angle, NULL ) );
  9223. if ( pDuckPickup )
  9224. {
  9225. DispatchSpawn( pDuckPickup );
  9226. Vector vecImpulse = RandomVector( -0.5f, 0.5f );
  9227. vecImpulse.z = 1.f;
  9228. VectorNormalize( vecImpulse );
  9229. Vector vecVelocity = vecImpulse * RandomFloat( 350.0f, 450.0f );
  9230. pDuckPickup->DropSingleInstance( vecVelocity, NULL, 1.0f );
  9231. pDuckPickup->SetCreatorId( iCreatorId );
  9232. pDuckPickup->SetVictimId( iVictimId );
  9233. pDuckPickup->SetAssisterId( iAssisterId );
  9234. pDuckPickup->ChangeTeam( iDuckTeam );
  9235. pDuckPickup->SetDuckFlag( iDuckFlags );
  9236. // random chance to have a special duck appear
  9237. // Bonus duck implies atleast 1 Saxton
  9238. if ( bSpecial || ( RandomInt( 1, 100 ) <= tf_test_special_ducks.GetInt() ) )
  9239. {
  9240. bSpecial = false;
  9241. pDuckPickup->m_nSkin = 21; // Quackston Hale
  9242. pDuckPickup->SetSpecial();
  9243. CSingleUserRecipientFilter filter( pTFCreator );
  9244. UserMessageBegin( filter, "BonusDucks" );
  9245. WRITE_BYTE( iCreatorId );
  9246. WRITE_BYTE( true );
  9247. MessageEnd();
  9248. }
  9249. else
  9250. {
  9251. if ( iDuckPower > 0 )
  9252. {
  9253. pDuckPickup->m_nSkin = GetDuckSkinForClass( iDuckTeam, ( pTFVictim && pTFVictim->GetPlayerClass() ) ? pTFVictim->GetPlayerClass()->GetClassIndex() : -1 );
  9254. }
  9255. else
  9256. {
  9257. // Scorer without a badge only make normal ducks
  9258. pDuckPickup->m_nSkin = iDuckTeam == TF_TEAM_RED ? 1 : 2;
  9259. }
  9260. pDuckPickup->SetModelScale( 0.7f );
  9261. }
  9262. }
  9263. }
  9264. // Update duckstreak count on player and assister if they have badges
  9265. if ( pTFCreator && PlayerHasDuckStreaks( pTFCreator ) )
  9266. {
  9267. pTFCreator->m_Shared.IncrementStreak( CTFPlayerShared::kTFStreak_Ducks, iDuckCount );
  9268. }
  9269. if ( pAssister && PlayerHasDuckStreaks( pAssister ) )
  9270. {
  9271. pAssister->m_Shared.IncrementStreak( CTFPlayerShared::kTFStreak_Ducks, iDuckCount );
  9272. }
  9273. // Duck UserMessage
  9274. if ( pTFCreator && pTFVictim && !TFGameRules()->HaveCheatsBeenEnabledDuringLevel() )
  9275. {
  9276. if ( IsHolidayActive( kHoliday_EOTL ) )
  9277. {
  9278. // Send a Message to Creator
  9279. {
  9280. // IsCreated, ID of Creator, ID of Victim, Count, IsGolden
  9281. CSingleUserRecipientFilter userfilter( pTFCreator );
  9282. UserMessageBegin( userfilter, "EOTLDuckEvent" );
  9283. WRITE_BYTE( true );
  9284. WRITE_BYTE( iCreatorId );
  9285. WRITE_BYTE( iVictimId );
  9286. WRITE_BYTE( 0 );
  9287. WRITE_BYTE( iDuckTeam );
  9288. WRITE_BYTE( iDuckCount );
  9289. WRITE_BYTE( iDuckFlags );
  9290. MessageEnd();
  9291. }
  9292. // To assister
  9293. if ( pAssister )
  9294. {
  9295. // IsCreated, ID of Creator, ID of Victim, Count, IsGolden
  9296. CSingleUserRecipientFilter userfilter( pAssister );
  9297. UserMessageBegin( userfilter, "EOTLDuckEvent" );
  9298. WRITE_BYTE( true );
  9299. WRITE_BYTE( pAssister->entindex() );
  9300. WRITE_BYTE( iVictimId );
  9301. WRITE_BYTE( 0 );
  9302. WRITE_BYTE( iDuckTeam );
  9303. WRITE_BYTE( iDuckCount );
  9304. WRITE_BYTE( iDuckFlags );
  9305. MessageEnd();
  9306. }
  9307. }
  9308. }
  9309. }
  9310. }
  9311. //-----------------------------------------------------------------------------
  9312. // Purpose:
  9313. //-----------------------------------------------------------------------------
  9314. static kill_eater_event_t g_eClassKillEvents[] =
  9315. {
  9316. kKillEaterEvent_ScoutKill, // TF_CLASS_SCOUT
  9317. kKillEaterEvent_SniperKill, // TF_CLASS_SNIPER
  9318. kKillEaterEvent_SoldierKill, // TF_CLASS_SOLDIER
  9319. kKillEaterEvent_DemomanKill, // TF_CLASS_DEMOMAN
  9320. kKillEaterEvent_MedicKill, // TF_CLASS_MEDIC
  9321. kKillEaterEvent_HeavyKill, // TF_CLASS_HEAVYWEAPONS
  9322. kKillEaterEvent_PyroKill, // TF_CLASS_PYRO
  9323. kKillEaterEvent_SpyKill, // TF_CLASS_SPY
  9324. kKillEaterEvent_EngineerKill, // TF_CLASS_ENGINEER
  9325. };
  9326. COMPILE_TIME_ASSERT( ARRAYSIZE( g_eClassKillEvents ) == (TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS) );
  9327. //-----------------------------------------------------------------------------
  9328. static kill_eater_event_t g_eRobotClassKillEvents[] =
  9329. {
  9330. kKillEaterEvent_RobotScoutKill, // TF_CLASS_SCOUT
  9331. kKillEaterEvent_RobotSniperKill, // TF_CLASS_SNIPER
  9332. kKillEaterEvent_RobotSoldierKill, // TF_CLASS_SOLDIER
  9333. kKillEaterEvent_RobotDemomanKill, // TF_CLASS_DEMOMAN
  9334. kKillEaterEvent_RobotMedicKill, // TF_CLASS_MEDIC
  9335. kKillEaterEvent_RobotHeavyKill, // TF_CLASS_HEAVYWEAPONS
  9336. kKillEaterEvent_RobotPyroKill, // TF_CLASS_PYRO
  9337. kKillEaterEvent_RobotSpyKill, // TF_CLASS_SPY
  9338. kKillEaterEvent_RobotEngineerKill, // TF_CLASS_ENGINEER
  9339. };
  9340. COMPILE_TIME_ASSERT( ARRAYSIZE( g_eRobotClassKillEvents ) == (TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS) );
  9341. static bool BHasWearableOfSpecificQualityEquipped( /*const*/ CTFPlayer *pTFPlayer, EEconItemQuality eQuality )
  9342. {
  9343. Assert( pTFPlayer );
  9344. // Fire the kill eater event on all wearables
  9345. for ( int i = 0; i < pTFPlayer->GetNumWearables(); ++i )
  9346. {
  9347. CTFWearable *pWearableItem = dynamic_cast<CTFWearable *>( pTFPlayer->GetWearable( i ) );
  9348. if ( !pWearableItem )
  9349. continue;
  9350. if ( !pWearableItem->GetAttributeContainer() )
  9351. continue;
  9352. CEconItemView *pEconItemView = pWearableItem->GetAttributeContainer()->GetItem();
  9353. if ( !pEconItemView )
  9354. continue;
  9355. if ( pEconItemView->GetQuality() == eQuality )
  9356. return true;
  9357. }
  9358. return false;
  9359. }
  9360. void CTFGameRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info )
  9361. {
  9362. // Find the killer & the scorer
  9363. CBaseEntity *pInflictor = info.GetInflictor();
  9364. CBaseEntity *pKiller = info.GetAttacker();
  9365. CBaseMultiplayerPlayer *pScorer = ToBaseMultiplayerPlayer( GetDeathScorer( pKiller, pInflictor, pVictim ) );
  9366. CTFPlayer *pAssister = NULL;
  9367. CBaseObject *pObject = NULL;
  9368. // if inflictor or killer is a base object, tell them that they got a kill
  9369. // ( depends if a sentry rocket got the kill, sentry may be inflictor or killer )
  9370. if ( pInflictor )
  9371. {
  9372. if ( pInflictor->IsBaseObject() )
  9373. {
  9374. pObject = dynamic_cast<CBaseObject *>( pInflictor );
  9375. }
  9376. else
  9377. {
  9378. CBaseEntity *pInflictorOwner = pInflictor->GetOwnerEntity();
  9379. if ( pInflictorOwner && pInflictorOwner->IsBaseObject() )
  9380. {
  9381. pObject = dynamic_cast<CBaseObject *>( pInflictorOwner );
  9382. }
  9383. }
  9384. }
  9385. else if( pKiller && pKiller->IsBaseObject() )
  9386. {
  9387. pObject = dynamic_cast<CBaseObject *>( pKiller );
  9388. }
  9389. if ( pObject )
  9390. {
  9391. if ( pObject->GetBuilder() != pVictim )
  9392. {
  9393. pObject->IncrementKills();
  9394. // minibosses count for 5 kills
  9395. if ( IsMannVsMachineMode() && pVictim && pVictim->IsPlayer() )
  9396. {
  9397. CTFPlayer *playerVictim = ToTFPlayer( pVictim );
  9398. if ( playerVictim->IsMiniBoss() )
  9399. {
  9400. pObject->IncrementKills();
  9401. pObject->IncrementKills();
  9402. pObject->IncrementKills();
  9403. pObject->IncrementKills();
  9404. }
  9405. }
  9406. }
  9407. pInflictor = pObject;
  9408. if ( pObject->ObjectType() == OBJ_SENTRYGUN )
  9409. {
  9410. // notify the sentry
  9411. CObjectSentrygun *pSentrygun = dynamic_cast<CObjectSentrygun *>( pObject );
  9412. if ( pSentrygun )
  9413. {
  9414. pSentrygun->OnKilledEnemy( pVictim );
  9415. }
  9416. CTFPlayer *pOwner = pObject->GetOwner();
  9417. if ( pOwner )
  9418. {
  9419. int iKills = pObject->GetKills();
  9420. // keep track of max kills per a single sentry gun in the player object
  9421. if ( pOwner->GetMaxSentryKills() < iKills )
  9422. {
  9423. pOwner->SetMaxSentryKills( iKills );
  9424. }
  9425. // if we just got 10 kills with one sentry, tell the owner's client, which will award achievement if it doesn't have it already
  9426. if ( iKills == 10 )
  9427. {
  9428. pOwner->AwardAchievement( ACHIEVEMENT_TF_GET_TURRETKILLS );
  9429. }
  9430. }
  9431. }
  9432. }
  9433. // if not killed by suicide or killed by world, see if the scorer had an assister, and if so give the assister credit
  9434. if ( ( pVictim != pScorer ) && pKiller )
  9435. {
  9436. pAssister = ToTFPlayer( GetAssister( pVictim, pScorer, pInflictor ) );
  9437. CTFPlayer *pTFVictim = ToTFPlayer( pVictim );
  9438. if ( pAssister && pTFVictim )
  9439. {
  9440. EntityHistory_t* entHist = pTFVictim->m_AchievementData.IsSentryDamagerInHistory( pAssister, 5.0 );
  9441. if ( entHist )
  9442. {
  9443. CBaseObject *pObj = dynamic_cast<CBaseObject*>( entHist->hObject.Get() );
  9444. if ( pObj )
  9445. {
  9446. pObj->IncrementAssists();
  9447. }
  9448. }
  9449. // Rage On Assists
  9450. // Sniper Kill Rage
  9451. if ( pAssister->IsPlayerClass( TF_CLASS_SNIPER ) )
  9452. {
  9453. // Item attribute
  9454. // Add Sniper Rage On Assists
  9455. float flRageGain = 0;
  9456. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pAssister, flRageGain, rage_on_assists );
  9457. if (flRageGain != 0)
  9458. {
  9459. pAssister->m_Shared.ModifyRage(flRageGain);
  9460. }
  9461. }
  9462. }
  9463. }
  9464. if ( pVictim )
  9465. {
  9466. if ( ShouldDropSpellPickup() )
  9467. {
  9468. DropSpellPickup( pVictim->GetAbsOrigin() );
  9469. }
  9470. CTFPlayer *pTFScorer = ToTFPlayer( pScorer );
  9471. CTFPlayer *pTFVictim = ToTFPlayer( pVictim );
  9472. if ( pTFScorer && pTFVictim )
  9473. {
  9474. if ( ShouldDropBonusDuckFromPlayer( pTFScorer, pTFVictim ) )
  9475. {
  9476. DropBonusDuck( pTFVictim->GetAbsOrigin(), pTFScorer, pAssister, pTFVictim, ( info.GetDamageType() & DMG_CRITICAL ) != 0 );
  9477. }
  9478. }
  9479. // Drop a halloween soul!
  9480. if ( IsHolidayActive( kHoliday_Halloween ) )
  9481. {
  9482. CBaseCombatCharacter* pBaseCombatScorer = dynamic_cast< CBaseCombatCharacter*>( pScorer ? pScorer : pKiller );
  9483. // No souls for a pure suicide
  9484. if ( pTFVictim != pBaseCombatScorer )
  9485. {
  9486. // Only spawn a soul if the target is a base combat character.
  9487. if ( pTFVictim && pBaseCombatScorer )
  9488. {
  9489. DropHalloweenSoulPack( 1, pVictim->EyePosition(), pBaseCombatScorer, pTFVictim->GetTeamNumber() );
  9490. }
  9491. // Also spawn one for the assister
  9492. if ( pAssister )
  9493. {
  9494. DropHalloweenSoulPack( 1, pVictim->EyePosition(), pAssister, pTFVictim->GetTeamNumber() );
  9495. }
  9496. }
  9497. }
  9498. }
  9499. //find the area the player is in and see if his death causes a block
  9500. CBaseMultiplayerPlayer *pMultiplayerPlayer = ToBaseMultiplayerPlayer(pVictim);
  9501. for ( int i=0; i<ITriggerAreaCaptureAutoList::AutoList().Count(); ++i )
  9502. {
  9503. CTriggerAreaCapture *pArea = static_cast< CTriggerAreaCapture * >( ITriggerAreaCaptureAutoList::AutoList()[i] );
  9504. if ( pArea->IsTouching( pMultiplayerPlayer ) )
  9505. {
  9506. // ACHIEVEMENT_TF_MEDIC_ASSIST_CAPTURER
  9507. // kill 3 players
  9508. if ( pAssister && pAssister->IsPlayerClass( TF_CLASS_MEDIC ) )
  9509. {
  9510. // the victim is touching a cap point, see if they own it
  9511. if ( pMultiplayerPlayer->GetTeamNumber() == pArea->GetOwningTeam() )
  9512. {
  9513. int iDefenderKills = pAssister->GetPerLifeCounterKV( "medic_defender_kills" );
  9514. if ( ++iDefenderKills == 3 )
  9515. {
  9516. pAssister->AwardAchievement( ACHIEVEMENT_TF_MEDIC_ASSIST_CAPTURER );
  9517. }
  9518. pAssister->SetPerLifeCounterKV( "medic_defender_kills", iDefenderKills );
  9519. }
  9520. }
  9521. if ( pArea->CheckIfDeathCausesBlock( pMultiplayerPlayer, pScorer ) )
  9522. break;
  9523. }
  9524. }
  9525. // determine if this kill affected a nemesis relationship
  9526. int iDeathFlags = 0;
  9527. CTFPlayer *pTFPlayerVictim = ToTFPlayer( pVictim );
  9528. CTFPlayer *pTFPlayerScorer = ToTFPlayer( pScorer );
  9529. if ( pScorer )
  9530. {
  9531. CalcDominationAndRevenge( pTFPlayerScorer, info.GetWeapon(), pTFPlayerVictim, false, &iDeathFlags );
  9532. if ( pAssister )
  9533. {
  9534. CalcDominationAndRevenge( pAssister, info.GetWeapon(), pTFPlayerVictim, true, &iDeathFlags );
  9535. }
  9536. }
  9537. pTFPlayerVictim->SetDeathFlags( iDeathFlags );
  9538. CTFPlayer *pTFPlayerAssister = ToTFPlayer( pAssister );
  9539. if ( pTFPlayerAssister )
  9540. {
  9541. CTF_GameStats.Event_AssistKill( pTFPlayerAssister, pVictim );
  9542. }
  9543. // check for achievements
  9544. PlayerKilledCheckAchievements( pTFPlayerScorer, pTFPlayerVictim );
  9545. if ( IsInTraining() && GetTrainingModeLogic() )
  9546. {
  9547. if ( pVictim->IsFakeClient() == false )
  9548. {
  9549. GetTrainingModeLogic()->OnPlayerDied( ToTFPlayer( pVictim ), pKiller );
  9550. }
  9551. else
  9552. {
  9553. GetTrainingModeLogic()->OnBotDied( ToTFPlayer( pVictim ), pKiller );
  9554. }
  9555. }
  9556. // credit for dueling
  9557. if ( pTFPlayerScorer != NULL && pTFPlayerScorer != pTFPlayerVictim )
  9558. {
  9559. DuelMiniGame_NotifyKill( pTFPlayerScorer, pTFPlayerVictim );
  9560. }
  9561. if ( pTFPlayerAssister && pTFPlayerAssister != pTFPlayerVictim )
  9562. {
  9563. DuelMiniGame_NotifyAssist( pTFPlayerAssister, pTFPlayerVictim );
  9564. }
  9565. // Count kills from powerup carriers to detect imbalances
  9566. if ( IFuncPowerupVolumeAutoList::AutoList().Count() != 0 ) // If there are no func_powerupvolumes in the map, there's no point in checking for imbalances
  9567. {
  9568. if ( pTFPlayerScorer && pTFPlayerScorer != pTFPlayerVictim && pTFPlayerScorer->m_Shared.IsCarryingRune() && !pTFPlayerScorer->m_Shared.InCond( TF_COND_RUNE_IMBALANCE ) )
  9569. {
  9570. if ( !m_bPowerupImbalanceMeasuresRunning && !PowerupModeFlagStandoffActive() ) // Only count if imbalance measures aren't running and there is no flag standoff
  9571. {
  9572. if ( pTFPlayerScorer->GetTeamNumber() == TF_TEAM_BLUE )
  9573. {
  9574. m_nPowerupKillsBlueTeam++; // Blue team score increases if a powered up blue player makes a kill
  9575. }
  9576. else if ( pTFPlayerScorer->GetTeamNumber() == TF_TEAM_RED )
  9577. {
  9578. m_nPowerupKillsRedTeam++;
  9579. }
  9580. int nBlueAdvantage = m_nPowerupKillsBlueTeam - m_nPowerupKillsRedTeam; // How far ahead is this team?
  9581. // Msg( "\nnBlueAdvantage = %d\n", nBlueAdvantage );
  9582. int nRedAdvantage = m_nPowerupKillsRedTeam - m_nPowerupKillsBlueTeam;
  9583. // Msg( "nRedAdvantage = %d\n\n", nRedAdvantage );
  9584. if ( nRedAdvantage >= tf_powerup_mode_imbalance_delta.GetInt() )
  9585. {
  9586. PowerupTeamImbalance( TF_TEAM_BLUE ); // Fire the output to map logic
  9587. }
  9588. else if ( nBlueAdvantage >= tf_powerup_mode_imbalance_delta.GetInt() )
  9589. {
  9590. PowerupTeamImbalance( TF_TEAM_RED );
  9591. }
  9592. }
  9593. }
  9594. }
  9595. // credit for kill-eating weapons and anything else that might care
  9596. if ( pTFPlayerScorer && pTFPlayerVictim && pTFPlayerScorer != pTFPlayerVictim )
  9597. {
  9598. // Increment the server-side kill count for this weapon -- this is used for honorbound
  9599. // weapons and has nothing to do with strange weapons/stats.
  9600. pTFPlayerScorer->IncrementKillCountSinceLastDeploy( info );
  9601. CTFWeaponBase *pTFWeapon = GetKilleaterWeaponFromDamageInfo( &info );
  9602. kill_eater_event_t eKillEaterEvent = pTFWeapon // if we have a weapon...
  9603. ? pTFWeapon->GetKillEaterKillEventType() // ...then ask it what type of event to report
  9604. : info.GetDamageCustom() == TF_DMG_CUSTOM_BOOTS_STOMP // if we don't have a weapon, and we're hacking for kill types from wearables (!)...
  9605. ? kKillEaterEvent_PlayerKillByBootStomp // ...then use our hard-coded event
  9606. : kKillEaterEvent_PlayerKill; // otherwise default to a normal player kill
  9607. CEconEntity *pAttackerEconWeapon = NULL;
  9608. // Cosmetic Kill like manntreads or demo shield
  9609. if ( !pTFWeapon )
  9610. {
  9611. pAttackerEconWeapon = dynamic_cast< CEconEntity * >( info.GetWeapon() );
  9612. }
  9613. else
  9614. {
  9615. pAttackerEconWeapon = dynamic_cast< CEconEntity * >( pTFWeapon );
  9616. }
  9617. if ( pAttackerEconWeapon )
  9618. {
  9619. if ( !( IsPVEModeActive() && pTFPlayerVictim->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) )
  9620. {
  9621. // Any type of non-robot kill!
  9622. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, eKillEaterEvent );
  9623. // Cosmetic any kill type tracking
  9624. {
  9625. HatAndMiscEconEntities_OnOwnerKillEaterEvent( pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_CosmeticKills );
  9626. // Halloween silliness: overworld kills for Merasmus carnival.
  9627. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_DOOMSDAY ) && !pTFPlayerScorer->m_Shared.InCond( TF_COND_HALLOWEEN_IN_HELL ) )
  9628. {
  9629. HatAndMiscEconEntities_OnOwnerKillEaterEvent( pTFPlayerScorer,
  9630. pTFPlayerVictim,
  9631. kKillEaterEvent_Halloween_OverworldKills );
  9632. }
  9633. }
  9634. // Operation Kills
  9635. EconEntity_NonEquippedItemKillTracking( pTFPlayerScorer, pTFPlayerVictim, 1 );
  9636. // Unique kill tracking?
  9637. EconEntity_OnOwnerUniqueEconEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_UniqueEvent__KilledAccountWithItem );
  9638. // Optional Taunt Kill tracking
  9639. if ( IsTauntDmg( info.GetDamageCustom() ) )
  9640. {
  9641. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_TauntKill );
  9642. }
  9643. // Scorch Shot Taunt is Different
  9644. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_FLARE_PELLET )
  9645. {
  9646. CBaseEntity *pInflictor = info.GetInflictor();
  9647. CTFProjectile_Flare *pFlare = dynamic_cast<CTFProjectile_Flare*>(pInflictor);
  9648. if ( pFlare && pFlare->IsFromTaunt() )
  9649. {
  9650. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_TauntKill );
  9651. }
  9652. }
  9653. // Optional: also track "killed X players of this specific class".
  9654. int iVictimClassIndex = pTFPlayerVictim->GetPlayerClass()->GetClassIndex();
  9655. if ( iVictimClassIndex >= TF_FIRST_NORMAL_CLASS && iVictimClassIndex <= TF_LAST_NORMAL_CLASS )
  9656. {
  9657. const kill_eater_event_t eClassKillType = g_eClassKillEvents[ iVictimClassIndex - TF_FIRST_NORMAL_CLASS ];
  9658. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, eClassKillType );
  9659. }
  9660. // Optional : Kills while in Victory / Bonus time
  9661. if ( State_Get() == GR_STATE_TEAM_WIN && GetWinningTeam() != pTFPlayerVictim->GetTeamNumber() )
  9662. {
  9663. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_VictoryTimeKill );
  9664. }
  9665. // Optional: also track "killed X players while they were in the air".
  9666. if ( !(pTFPlayerVictim->GetFlags() & FL_ONGROUND) && (pTFPlayerVictim->GetWaterLevel() == WL_NotInWater) )
  9667. {
  9668. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_AirborneEnemyKill );
  9669. }
  9670. // Optional: also track "killed X players with headshots".
  9671. if ( IsHeadshot( info.GetDamageCustom() ) )
  9672. {
  9673. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_HeadshotKill );
  9674. }
  9675. // Optional: also track "gibbed X players".
  9676. if ( pTFPlayerVictim->ShouldGib( info ) )
  9677. {
  9678. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_GibKill );
  9679. }
  9680. // Optional: also track "killed X players during full moons". We intentionally don't call into the
  9681. // game rules here because we don't want server variables to override this. Setting local time on the
  9682. // server will still allow it to happen but it at least takes a little effort.
  9683. if ( UTIL_IsHolidayActive( kHoliday_FullMoon ) )
  9684. {
  9685. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillDuringFullMoon );
  9686. }
  9687. // Optional: also track kills we make while we're dead (afterburn, pre-death-fired rocket, etc.)
  9688. if ( !pTFPlayerScorer->IsAlive() )
  9689. {
  9690. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillPosthumous );
  9691. }
  9692. // Optional: also track kills that were specifically criticals.
  9693. if ( (info.GetDamageType() & DMG_CRITICAL) != 0 )
  9694. {
  9695. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillCritical );
  9696. }
  9697. else
  9698. {
  9699. // Not special at all kill
  9700. if ( pTFPlayerVictim->GetAttackBonusEffect() == kBonusEffect_None )
  9701. {
  9702. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_NonCritKills );
  9703. }
  9704. }
  9705. // Optional: also track kills that were made while we were launched into the air from an explosion (ie., rocket-jumping).
  9706. if ( pTFPlayerScorer->InAirDueToExplosion() )
  9707. {
  9708. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillWhileExplosiveJumping );
  9709. }
  9710. // Optional: also track kills where the victim was a spy who was invisible at the time of death.
  9711. if ( iVictimClassIndex == TF_CLASS_SPY && pTFPlayerVictim->m_Shared.GetPercentInvisible() > 0 )
  9712. {
  9713. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_InvisibleSpiesKilled );
  9714. }
  9715. // Optional: also track kills where the victim was a medic with 100% uber.
  9716. if ( pTFPlayerVictim->MedicGetChargeLevel() >= 1.0f )
  9717. {
  9718. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_MedicsWithFullUberKilled );
  9719. }
  9720. // Optional: also track kills where the killer was at low health when they dealt the final damage. Don't count
  9721. // kills with 0 or fewer health -- those would be post-mortem kills instead.
  9722. if ( ((float)pTFPlayerScorer->GetHealth() / (float)pTFPlayerScorer->GetMaxHealth()) <= 0.1f && pTFPlayerScorer->GetHealth() > 0 )
  9723. {
  9724. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_KillWhileLowHealth );
  9725. }
  9726. // Optional: also track kills that take place during the Halloween holiday. We intentionally *do* check against
  9727. // the game rules logic here -- if folks want to enable Halloween mode on a server and play around, I don't see any
  9728. // reason why we benefit from stopping their fun.
  9729. if ( TF_IsHolidayActive( kHoliday_Halloween ) )
  9730. {
  9731. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_HalloweenKill );
  9732. }
  9733. // Optional: also track kills where the victim was completely underwater.
  9734. if ( pTFPlayerVictim->GetWaterLevel() >= WL_Eyes )
  9735. {
  9736. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_UnderwaterKill );
  9737. }
  9738. // Optional: also track kills where we're under the effects of a medic's ubercharge.
  9739. if ( pTFPlayerScorer->m_Shared.InCond( TF_COND_INVULNERABLE ) || pTFPlayerScorer->m_Shared.InCond( TF_COND_INVULNERABLE_WEARINGOFF ) )
  9740. {
  9741. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_KillWhileUbercharged );
  9742. }
  9743. // Optional: also track kills where the victim was in the process of carrying the intel, capturing a point, or pushing
  9744. // the cart. The actual logic for "killed a flag carrier" is handled elsewhere because by this point we've forgotten
  9745. // if we had a flag before we died.
  9746. if ( pTFPlayerVictim->IsCapturingPoint() )
  9747. {
  9748. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_DefenderKill );
  9749. }
  9750. // Optional: also track kills where the victim was at least N units from the person dealing the damage, where N is "however far
  9751. // you have to be to get the crowd chear noise". This also specifically checks to make sure the damage dealer is alive to avoid
  9752. // casing where you spectate far away, etc.
  9753. if ( pTFPlayerScorer->IsAlive() && (pTFPlayerScorer->GetAbsOrigin() - pTFPlayerVictim->GetAbsOrigin()).Length() >= 2000.0f )
  9754. {
  9755. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_LongDistanceKill );
  9756. }
  9757. // Optional: also track kills where the victim was wearing at least one unusual-quality item.
  9758. if ( BHasWearableOfSpecificQualityEquipped( pTFPlayerVictim, AE_UNUSUAL ) )
  9759. {
  9760. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_PlayersWearingUnusualKill );
  9761. }
  9762. // Optional: also track kills where the victim was on fire at the time that they died. We can't check the condition flag
  9763. // here because that may or may not be active after they die, but instead we test to see whether they still had a burn queued
  9764. // up for the future as a proxy for "was on fire at this point".
  9765. if ( pTFPlayerVictim->m_Shared.GetFlameBurnTime() >= gpGlobals->curtime )
  9766. {
  9767. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_BurningEnemyKill );
  9768. }
  9769. // Optional: also track kills where this kill ended someone else's killstreak, where "killstreak" starts when the first
  9770. // announcement happens. If Mike gets to hard-code constants, I do, too.
  9771. enum { kFirstKillStreakAnnouncement = 5 };
  9772. if ( pTFPlayerVictim->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) >= kFirstKillStreakAnnouncement )
  9773. {
  9774. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_KillstreaksEnded );
  9775. }
  9776. // Optional: also track kills where the killer and the victim were basically right next to each other. This is hard-coded to
  9777. // 1.5x default melee swing range.
  9778. if ( pTFPlayerScorer->IsAlive() && (pTFPlayerScorer->GetAbsOrigin() - pTFPlayerVictim->GetAbsOrigin()).Length() <= 72.0f )
  9779. {
  9780. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_PointBlankKills );
  9781. }
  9782. // Optional : Fullhealth kills
  9783. if ( pTFPlayerScorer->GetHealth() >= pTFPlayerScorer->GetMaxHealth() )
  9784. {
  9785. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_FullHealthKills );
  9786. }
  9787. // Optional : Taunting Player kills
  9788. if ( pTFPlayerVictim->IsTaunting() )
  9789. {
  9790. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_TauntingPlayerKills );
  9791. }
  9792. }
  9793. else
  9794. {
  9795. Assert( pTFPlayerVictim->GetTeamNumber() == TF_TEAM_PVE_INVADERS );
  9796. // Optional: also track kills where the victim was a robot in MvM
  9797. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_RobotsDestroyed );
  9798. // Optional: also track "killed X Robots of this specific class".
  9799. int iVictimClassIndex = pTFPlayerVictim->GetPlayerClass()->GetClassIndex();
  9800. if ( iVictimClassIndex >= TF_FIRST_NORMAL_CLASS && iVictimClassIndex <= TF_LAST_NORMAL_CLASS )
  9801. {
  9802. const kill_eater_event_t eClassKillType = g_eRobotClassKillEvents[ iVictimClassIndex - TF_FIRST_NORMAL_CLASS ];
  9803. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, eClassKillType );
  9804. }
  9805. // Optional: specifically track miniboss kills separately from base robot kills.
  9806. if ( pTFPlayerVictim->IsMiniBoss() )
  9807. {
  9808. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_MinibossRobotsDestroyed );
  9809. }
  9810. // Optional: track whether this shot killed a robot *after* penetrating something else.
  9811. if ( info.GetPlayerPenetrationCount() > 0 )
  9812. {
  9813. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_RobotsDestroyedAfterPenetration );
  9814. }
  9815. // Optional: also track "killed X players with headshots", but for robots specifically.
  9816. if ( IsHeadshot( info.GetDamageCustom() ) )
  9817. {
  9818. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_RobotHeadshotKills );
  9819. }
  9820. // Optional: also track kills that take place during the Halloween holiday. See note for kKillEaterEvent_HalloweenKill
  9821. // regarding calling into game rules here.
  9822. if ( TF_IsHolidayActive( kHoliday_Halloween ) )
  9823. {
  9824. EconEntity_OnOwnerKillEaterEvent( pAttackerEconWeapon, pTFPlayerScorer, pTFPlayerVictim, kKillEaterEvent_HalloweenKillRobot );
  9825. }
  9826. }
  9827. }
  9828. }
  9829. BaseClass::PlayerKilled( pVictim, info );
  9830. }
  9831. //-----------------------------------------------------------------------------
  9832. // Purpose:
  9833. //-----------------------------------------------------------------------------
  9834. void CTFGameRules::PlayerKilledCheckAchievements( CTFPlayer *pAttacker, CTFPlayer *pVictim )
  9835. {
  9836. if ( !pAttacker || !pVictim )
  9837. return;
  9838. // HEAVY WEAPONS GUY
  9839. if ( pAttacker->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
  9840. {
  9841. if ( GetGameType() == TF_GAMETYPE_CP )
  9842. {
  9843. // ACHIEVEMENT_TF_HEAVY_DEFEND_CONTROL_POINT
  9844. CTriggerAreaCapture *pAreaTrigger = pAttacker->GetControlPointStandingOn();
  9845. if ( pAreaTrigger )
  9846. {
  9847. CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint();
  9848. if ( pCP )
  9849. {
  9850. if ( pCP->GetOwner() == pAttacker->GetTeamNumber() )
  9851. {
  9852. // no suicides!
  9853. if ( pAttacker != pVictim )
  9854. {
  9855. pAttacker->AwardAchievement( ACHIEVEMENT_TF_HEAVY_DEFEND_CONTROL_POINT );
  9856. }
  9857. }
  9858. }
  9859. }
  9860. // ACHIEVEMENT_TF_HEAVY_KILL_CAPPING_ENEMIES
  9861. pAreaTrigger = pVictim->GetControlPointStandingOn();
  9862. if ( pAreaTrigger )
  9863. {
  9864. CTeamControlPoint *pCP = pAreaTrigger->GetControlPoint();
  9865. if ( pCP )
  9866. {
  9867. if ( pCP->GetOwner() == pAttacker->GetTeamNumber() &&
  9868. TeamMayCapturePoint( pVictim->GetTeamNumber(), pCP->GetPointIndex() ) &&
  9869. PlayerMayCapturePoint( pVictim, pCP->GetPointIndex() ) )
  9870. {
  9871. pAttacker->AwardAchievement( ACHIEVEMENT_TF_HEAVY_KILL_CAPPING_ENEMIES );
  9872. }
  9873. }
  9874. }
  9875. }
  9876. // ACHIEVEMENT_TF_HEAVY_CLEAR_STICKYBOMBS
  9877. if ( pVictim->IsPlayerClass( TF_CLASS_DEMOMAN ) )
  9878. {
  9879. int iPipes = pVictim->GetNumActivePipebombs();
  9880. for (int i = 0; i < iPipes; i++)
  9881. {
  9882. pAttacker->AwardAchievement( ACHIEVEMENT_TF_HEAVY_CLEAR_STICKYBOMBS );
  9883. }
  9884. }
  9885. // ACHIEVEMENT_TF_HEAVY_DEFEND_MEDIC
  9886. int i;
  9887. int iNumHealers = pAttacker->m_Shared.GetNumHealers();
  9888. for ( i = 0 ; i < iNumHealers ; i++ )
  9889. {
  9890. CTFPlayer *pMedic = ToTFPlayer( pAttacker->m_Shared.GetHealerByIndex( i ) );
  9891. if ( pMedic && pMedic->m_AchievementData.IsDamagerInHistory( pVictim, 3.0 ) )
  9892. {
  9893. pAttacker->AwardAchievement( ACHIEVEMENT_TF_HEAVY_DEFEND_MEDIC );
  9894. break; // just award it once for each kill...even if the victim attacked more than one medic attached to the killer
  9895. }
  9896. }
  9897. // ACHIEVEMENT_TF_HEAVY_STAND_NEAR_DISPENSER
  9898. for ( i = 0 ; i < iNumHealers ; i++ )
  9899. {
  9900. if ( pAttacker->m_Shared.HealerIsDispenser( i ) )
  9901. {
  9902. pAttacker->AwardAchievement( ACHIEVEMENT_TF_HEAVY_STAND_NEAR_DISPENSER );
  9903. break; // just award it once for each kill...even if the attacker is being healed by more than one dispenser
  9904. }
  9905. }
  9906. }
  9907. int i;
  9908. int iNumHealers = pAttacker->m_Shared.GetNumHealers();
  9909. for ( i = 0 ; i < iNumHealers ; i++ )
  9910. {
  9911. CTFPlayer *pMedic = ToTFPlayer( pAttacker->m_Shared.GetHealerByIndex( i ) );
  9912. if ( pMedic && pMedic->m_AchievementData.IsDamagerInHistory( pVictim, 10.0 ) )
  9913. {
  9914. IGameEvent * event = gameeventmanager->CreateEvent( "medic_defended" );
  9915. if ( event )
  9916. {
  9917. event->SetInt( "userid", pAttacker->GetUserID() );
  9918. event->SetInt( "medic", pMedic->GetUserID() );
  9919. gameeventmanager->FireEvent( event );
  9920. }
  9921. }
  9922. }
  9923. }
  9924. //-----------------------------------------------------------------------------
  9925. // Purpose: Determines if attacker and victim have gotten domination or revenge
  9926. //-----------------------------------------------------------------------------
  9927. void CTFGameRules::CalcDominationAndRevenge( CTFPlayer *pAttacker, CBaseEntity *pWeapon, CTFPlayer *pVictim, bool bIsAssist, int *piDeathFlags )
  9928. {
  9929. //=============================================================================
  9930. // HPE_BEGIN:
  9931. // [msmith] If we're in training, we want to disable this domination stuff.
  9932. //=============================================================================
  9933. if ( IsInTraining() )
  9934. return;
  9935. //=============================================================================
  9936. // HPE_END
  9937. //=============================================================================
  9938. // don't do domination stuff in powerup mode
  9939. if ( IsPowerupMode() )
  9940. return;
  9941. // no dominations/revenge in competitive mode
  9942. if ( IsMatchTypeCompetitive() )
  9943. return;
  9944. PlayerStats_t *pStatsVictim = CTF_GameStats.FindPlayerStats( pVictim );
  9945. // no dominations/revenge in PvE mode
  9946. if ( IsPVEModeActive() )
  9947. return;
  9948. CEconEntity *pEconWeapon = dynamic_cast<CEconEntity *>( pWeapon );
  9949. // calculate # of unanswered kills between killer & victim - add 1 to include current kill
  9950. int iKillsUnanswered = pStatsVictim->statsKills.iNumKilledByUnanswered[pAttacker->entindex()] + 1;
  9951. if ( TF_KILLS_DOMINATION == iKillsUnanswered )
  9952. {
  9953. // this is the Nth unanswered kill between killer and victim, killer is now dominating victim
  9954. *piDeathFlags |= ( bIsAssist ? TF_DEATH_ASSISTER_DOMINATION : TF_DEATH_DOMINATION );
  9955. // set victim to be dominated by killer
  9956. pAttacker->m_Shared.SetPlayerDominated( pVictim, true );
  9957. int iCurrentlyDominated = pAttacker->GetNumberofDominations() + 1;
  9958. pAttacker->SetNumberofDominations( iCurrentlyDominated );
  9959. // record stats
  9960. CTF_GameStats.Event_PlayerDominatedOther( pAttacker );
  9961. // strange weapon stat tracking?
  9962. EconEntity_OnOwnerKillEaterEvent( pEconWeapon, pAttacker, pVictim, kKillEaterEvent_PlayerKillStartDomination );
  9963. }
  9964. else if ( pVictim->m_Shared.IsPlayerDominated( pAttacker->entindex() ) )
  9965. {
  9966. // the killer killed someone who was dominating him, gains revenge
  9967. *piDeathFlags |= ( bIsAssist ? TF_DEATH_ASSISTER_REVENGE : TF_DEATH_REVENGE );
  9968. // set victim to no longer be dominating the killer
  9969. pVictim->m_Shared.SetPlayerDominated( pAttacker, false );
  9970. int iCurrentlyDominated = pVictim->GetNumberofDominations() - 1;
  9971. if (iCurrentlyDominated < 0)
  9972. {
  9973. iCurrentlyDominated = 0;
  9974. }
  9975. pVictim->SetNumberofDominations( iCurrentlyDominated );
  9976. // record stats
  9977. CTF_GameStats.Event_PlayerRevenge( pAttacker );
  9978. // strange weapon stat tracking?
  9979. EconEntity_OnOwnerKillEaterEvent( pEconWeapon, pAttacker, pVictim, kKillEaterEvent_PlayerKillRevenge );
  9980. }
  9981. else if ( pAttacker->m_Shared.IsPlayerDominated( pVictim->entindex() ) )
  9982. {
  9983. // the killer killed someone who they were already dominating
  9984. // strange weapon stat tracking?
  9985. EconEntity_OnOwnerKillEaterEvent( pEconWeapon, pAttacker, pVictim, kKillEaterEvent_PlayerKillAlreadyDominated );
  9986. }
  9987. }
  9988. //-----------------------------------------------------------------------------
  9989. // Purpose: create some proxy entities that we use for transmitting data */
  9990. //-----------------------------------------------------------------------------
  9991. void CTFGameRules::CreateStandardEntities()
  9992. {
  9993. // Create the player resource
  9994. g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "tf_player_manager", vec3_origin, vec3_angle );
  9995. // Create the objective resource
  9996. g_pObjectiveResource = (CTFObjectiveResource *)CBaseEntity::Create( "tf_objective_resource", vec3_origin, vec3_angle );
  9997. Assert( g_pObjectiveResource );
  9998. // Create the monster resource for PvE battles
  9999. g_pMonsterResource = (CMonsterResource *)CBaseEntity::Create( "monster_resource", vec3_origin, vec3_angle );
  10000. MannVsMachineStats_Init();
  10001. // Create the entity that will send our data to the client.
  10002. m_hGamerulesProxy = assert_cast<CTFGameRulesProxy*>(CBaseEntity::Create( "tf_gamerules", vec3_origin, vec3_angle ));
  10003. Assert( m_hGamerulesProxy.Get() );
  10004. m_hGamerulesProxy->SetName( AllocPooledString("tf_gamerules" ) );
  10005. CBaseEntity::Create("vote_controller", vec3_origin, vec3_angle);
  10006. // Vote Issue classes are handled/cleaned-up by g_voteController
  10007. new CKickIssue;
  10008. new CRestartGameIssue;
  10009. new CChangeLevelIssue;
  10010. new CNextLevelIssue;
  10011. new CExtendLevelIssue;
  10012. new CScrambleTeams;
  10013. new CMannVsMachineChangeChallengeIssue;
  10014. new CEnableTemporaryHalloweenIssue;
  10015. new CTeamAutoBalanceIssue;
  10016. new CClassLimitsIssue;
  10017. new CPauseGameIssue;
  10018. }
  10019. //-----------------------------------------------------------------------------
  10020. // Purpose: determine the class name of the weapon that got a kill
  10021. //-----------------------------------------------------------------------------
  10022. const char *CTFGameRules::GetKillingWeaponName( const CTakeDamageInfo &info, CTFPlayer *pVictim, int *iWeaponID )
  10023. {
  10024. CBaseEntity *pInflictor = info.GetInflictor();
  10025. CBaseEntity *pKiller = info.GetAttacker();
  10026. CBasePlayer *pScorer = GetDeathScorer( pKiller, pInflictor, pVictim );
  10027. const char *killer_weapon_name = "world";
  10028. *iWeaponID = TF_WEAPON_NONE;
  10029. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BURNING )
  10030. {
  10031. // special-case burning damage, since persistent burning damage may happen after attacker has switched weapons
  10032. killer_weapon_name = "tf_weapon_flamethrower";
  10033. *iWeaponID = TF_WEAPON_FLAMETHROWER;
  10034. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
  10035. if ( pWeapon )
  10036. {
  10037. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  10038. if ( pItem && pItem->GetStaticData() && pItem->GetStaticData()->GetIconClassname() )
  10039. {
  10040. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10041. *iWeaponID = TF_WEAPON_NONE;
  10042. }
  10043. }
  10044. }
  10045. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BURNING_FLARE )
  10046. {
  10047. // special-case burning damage, since persistent burning damage may happen after attacker has switched weapons
  10048. killer_weapon_name = "tf_weapon_flaregun";
  10049. *iWeaponID = TF_WEAPON_FLAREGUN;
  10050. if ( pInflictor && pInflictor->IsPlayer() == false )
  10051. {
  10052. CTFBaseRocket *pBaseRocket = dynamic_cast<CTFBaseRocket*>( pInflictor );
  10053. if ( pBaseRocket )
  10054. {
  10055. if ( pBaseRocket->GetDeflected() )
  10056. {
  10057. killer_weapon_name = "deflect_flare";
  10058. }
  10059. }
  10060. }
  10061. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
  10062. if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_FLAREGUN_REVENGE )
  10063. {
  10064. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  10065. if ( pItem && pItem->GetStaticData() && pItem->GetStaticData()->GetIconClassname() )
  10066. {
  10067. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10068. *iWeaponID = pWeapon->GetWeaponID();
  10069. }
  10070. }
  10071. }
  10072. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_FLARE_EXPLOSION )
  10073. {
  10074. killer_weapon_name = "tf_weapon_detonator";
  10075. *iWeaponID = TF_WEAPON_FLAREGUN;
  10076. if ( pInflictor && pInflictor->IsPlayer() == false )
  10077. {
  10078. CTFBaseRocket *pBaseRocket = dynamic_cast<CTFBaseRocket*>( pInflictor );
  10079. if ( pBaseRocket )
  10080. {
  10081. if ( pBaseRocket->GetDeflected() )
  10082. {
  10083. killer_weapon_name = "deflect_flare_detonator";
  10084. }
  10085. }
  10086. }
  10087. }
  10088. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_CHARGE_IMPACT )
  10089. {
  10090. CTFWearable *pWearable = dynamic_cast< CTFWearable * >( info.GetWeapon() );
  10091. if ( pWearable )
  10092. {
  10093. CEconItemView *pItem = pWearable->GetAttributeContainer()->GetItem();
  10094. if ( pItem && pItem->GetStaticData() && pItem->GetStaticData()->GetIconClassname() )
  10095. {
  10096. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10097. *iWeaponID = TF_WEAPON_NONE;
  10098. }
  10099. }
  10100. }
  10101. else if ( info.GetPlayerPenetrationCount() > 0 )
  10102. {
  10103. // This may be stomped later if the kill was a headshot.
  10104. killer_weapon_name = "player_penetration";
  10105. }
  10106. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PICKAXE )
  10107. {
  10108. killer_weapon_name = "pickaxe";
  10109. *iWeaponID = TF_WEAPON_SHOVEL;
  10110. }
  10111. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_CARRIED_BUILDING )
  10112. {
  10113. killer_weapon_name = "tf_weapon_building_carried_destroyed";
  10114. }
  10115. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_HADOUKEN )
  10116. {
  10117. killer_weapon_name = "tf_weapon_taunt_pyro";
  10118. }
  10119. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_HIGH_NOON )
  10120. {
  10121. killer_weapon_name = "tf_weapon_taunt_heavy";
  10122. }
  10123. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_GRAND_SLAM )
  10124. {
  10125. killer_weapon_name = "tf_weapon_taunt_scout";
  10126. }
  10127. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_BARBARIAN_SWING )
  10128. {
  10129. killer_weapon_name = "tf_weapon_taunt_demoman";
  10130. }
  10131. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_UBERSLICE )
  10132. {
  10133. killer_weapon_name = "tf_weapon_taunt_medic";
  10134. }
  10135. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_FENCING )
  10136. {
  10137. killer_weapon_name = "tf_weapon_taunt_spy";
  10138. }
  10139. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_ARROW_STAB )
  10140. {
  10141. killer_weapon_name = "tf_weapon_taunt_sniper";
  10142. }
  10143. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_GRENADE )
  10144. {
  10145. CTFPlayer *pTFKiller = ToTFPlayer( pKiller );
  10146. if ( pTFKiller && pTFKiller->IsWormsGearEquipped() )
  10147. {
  10148. killer_weapon_name = "tf_weapon_taunt_soldier_lumbricus";
  10149. }
  10150. else
  10151. {
  10152. killer_weapon_name = "tf_weapon_taunt_soldier";
  10153. }
  10154. }
  10155. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_ENGINEER_GUITAR_SMASH )
  10156. {
  10157. killer_weapon_name = "tf_weapon_taunt_guitar_kill";
  10158. }
  10159. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_ALLCLASS_GUITAR_RIFF )
  10160. {
  10161. killer_weapon_name = "tf_weapon_taunt_guitar_riff_kill";
  10162. }
  10163. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_ENGINEER_ARM_KILL )
  10164. {
  10165. killer_weapon_name = "robot_arm_blender_kill";
  10166. }
  10167. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TELEFRAG )
  10168. {
  10169. killer_weapon_name = "telefrag";
  10170. }
  10171. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BOOTS_STOMP )
  10172. {
  10173. killer_weapon_name = "mantreads";
  10174. }
  10175. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BASEBALL )
  10176. {
  10177. killer_weapon_name = "ball";
  10178. }
  10179. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_COMBO_PUNCH )
  10180. {
  10181. killer_weapon_name = "robot_arm_combo_kill";
  10182. }
  10183. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_BLEEDING )
  10184. {
  10185. killer_weapon_name = "tf_weapon_bleed_kill";
  10186. }
  10187. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLAYER_SENTRY )
  10188. {
  10189. int nGigerCounter = 0;
  10190. if ( pScorer )
  10191. {
  10192. CALL_ATTRIB_HOOK_INT_ON_OTHER( pScorer, nGigerCounter, is_giger_counter );
  10193. }
  10194. if ( nGigerCounter > 0 )
  10195. {
  10196. killer_weapon_name = "giger_counter";
  10197. }
  10198. else
  10199. {
  10200. killer_weapon_name = "wrangler_kill";
  10201. }
  10202. }
  10203. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_DECAPITATION_BOSS )
  10204. {
  10205. killer_weapon_name = "headtaker";
  10206. }
  10207. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_EYEBALL_ROCKET )
  10208. {
  10209. killer_weapon_name = "eyeball_rocket";
  10210. }
  10211. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_STICKBOMB_EXPLOSION )
  10212. {
  10213. killer_weapon_name = "ullapool_caber_explosion";
  10214. }
  10215. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_TAUNTATK_ARMAGEDDON )
  10216. {
  10217. killer_weapon_name = "armageddon";
  10218. }
  10219. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH )
  10220. {
  10221. killer_weapon_name = "recorder";
  10222. }
  10223. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_MERASMUS_DECAPITATION )
  10224. {
  10225. killer_weapon_name = "merasmus_decap";
  10226. }
  10227. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_MERASMUS_ZAP )
  10228. {
  10229. killer_weapon_name = "merasmus_zap";
  10230. }
  10231. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_MERASMUS_GRENADE )
  10232. {
  10233. killer_weapon_name = "merasmus_grenade";
  10234. }
  10235. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_MERASMUS_PLAYER_BOMB )
  10236. {
  10237. killer_weapon_name = "merasmus_player_bomb";
  10238. }
  10239. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_CANNONBALL_PUSH )
  10240. {
  10241. killer_weapon_name = "loose_cannon_impact";
  10242. }
  10243. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_TELEPORT )
  10244. {
  10245. killer_weapon_name = "spellbook_teleport";
  10246. }
  10247. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_SKELETON )
  10248. {
  10249. killer_weapon_name = "spellbook_skeleton";
  10250. }
  10251. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_MIRV )
  10252. {
  10253. killer_weapon_name = "spellbook_mirv";
  10254. }
  10255. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_METEOR )
  10256. {
  10257. killer_weapon_name = "spellbook_meteor";
  10258. }
  10259. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_LIGHTNING )
  10260. {
  10261. killer_weapon_name = "spellbook_lightning";
  10262. }
  10263. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_FIREBALL )
  10264. {
  10265. killer_weapon_name = "spellbook_fireball";
  10266. }
  10267. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_MONOCULUS )
  10268. {
  10269. killer_weapon_name = "spellbook_boss";
  10270. }
  10271. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_BLASTJUMP )
  10272. {
  10273. killer_weapon_name = "spellbook_blastjump";
  10274. }
  10275. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_BATS )
  10276. {
  10277. killer_weapon_name = "spellbook_bats";
  10278. }
  10279. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_SPELL_TINY )
  10280. {
  10281. killer_weapon_name = "spellbook_athletic";
  10282. }
  10283. else if ( info.GetDamageCustom() == TF_DMG_CUSTOM_THROWABLE ||
  10284. info.GetDamageCustom() == TF_DMG_CUSTOM_THROWABLE_KILL ) // Throwables
  10285. {
  10286. if ( pVictim && pVictim->GetHealth() <= 0 )
  10287. {
  10288. killer_weapon_name = "water_balloon_kill";
  10289. }
  10290. else
  10291. {
  10292. killer_weapon_name = "water_balloon_hit";
  10293. }
  10294. *iWeaponID = TF_WEAPON_THROWABLE;
  10295. }
  10296. else if ( pScorer && pInflictor && ( pInflictor == pScorer ) )
  10297. {
  10298. // If this is not a suicide
  10299. if ( pVictim != pScorer )
  10300. {
  10301. // If the inflictor is the killer, then it must be their current weapon doing the damage
  10302. if ( pScorer->GetActiveWeapon() )
  10303. {
  10304. killer_weapon_name = pScorer->GetActiveWeapon()->GetClassname();
  10305. if ( pScorer->IsPlayer() )
  10306. {
  10307. *iWeaponID = ToTFPlayer(pScorer)->GetActiveTFWeapon()->GetWeaponID();
  10308. }
  10309. }
  10310. }
  10311. }
  10312. else if ( pInflictor )
  10313. {
  10314. killer_weapon_name = STRING( pInflictor->m_iClassname );
  10315. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pInflictor );
  10316. if ( pWeapon )
  10317. {
  10318. *iWeaponID = pWeapon->GetWeaponID();
  10319. }
  10320. else
  10321. {
  10322. CTFBaseRocket *pBaseRocket = dynamic_cast<CTFBaseRocket*>( pInflictor );
  10323. if ( pBaseRocket )
  10324. {
  10325. *iWeaponID = pBaseRocket->GetWeaponID();
  10326. if ( pBaseRocket->GetDeflected() )
  10327. {
  10328. if ( *iWeaponID == TF_WEAPON_ROCKETLAUNCHER || *iWeaponID == TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT )
  10329. {
  10330. killer_weapon_name = "deflect_rocket";
  10331. }
  10332. else if ( *iWeaponID == TF_WEAPON_COMPOUND_BOW )
  10333. {
  10334. CTFProjectile_Arrow* pArrow = dynamic_cast<CTFProjectile_Arrow*>( pBaseRocket );
  10335. if ( pArrow && pArrow->IsAlight() )
  10336. {
  10337. killer_weapon_name = "deflect_huntsman_flyingburn";
  10338. }
  10339. else
  10340. {
  10341. killer_weapon_name = "deflect_arrow";
  10342. }
  10343. }
  10344. else if ( *iWeaponID == TF_WEAPON_SHOTGUN_BUILDING_RESCUE )
  10345. {
  10346. killer_weapon_name = "rescue_ranger_reflect";
  10347. }
  10348. }
  10349. else if ( *iWeaponID == TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT )
  10350. {
  10351. killer_weapon_name = "rocketlauncher_directhit";
  10352. }
  10353. }
  10354. else
  10355. {
  10356. CTFWeaponBaseGrenadeProj *pBaseGrenade = dynamic_cast<CTFWeaponBaseGrenadeProj*>( pInflictor );
  10357. if ( pBaseGrenade )
  10358. {
  10359. *iWeaponID = pBaseGrenade->GetWeaponID();
  10360. if ( pBaseGrenade->GetDeflected() )
  10361. {
  10362. if ( *iWeaponID == TF_WEAPON_GRENADE_PIPEBOMB )
  10363. {
  10364. killer_weapon_name = "deflect_sticky";
  10365. }
  10366. else if ( *iWeaponID == TF_WEAPON_GRENADE_DEMOMAN )
  10367. {
  10368. killer_weapon_name = "deflect_promode";
  10369. }
  10370. else if ( *iWeaponID == TF_WEAPON_BAT_WOOD )
  10371. {
  10372. killer_weapon_name = "deflect_ball";
  10373. }
  10374. else if ( *iWeaponID == TF_WEAPON_CANNON )
  10375. {
  10376. killer_weapon_name = "loose_cannon_reflect";
  10377. }
  10378. }
  10379. }
  10380. }
  10381. }
  10382. }
  10383. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_DEFENSIVE_STICKY )
  10384. {
  10385. killer_weapon_name = "sticky_resistance";
  10386. }
  10387. // strip certain prefixes from inflictor's classname
  10388. const char *prefix[] = { "tf_weapon_grenade_", "tf_weapon_", "NPC_", "func_" };
  10389. for ( int i = 0; i< ARRAYSIZE( prefix ); i++ )
  10390. {
  10391. // if prefix matches, advance the string pointer past the prefix
  10392. int len = Q_strlen( prefix[i] );
  10393. if ( strncmp( killer_weapon_name, prefix[i], len ) == 0 )
  10394. {
  10395. killer_weapon_name += len;
  10396. break;
  10397. }
  10398. }
  10399. // look out for sentry rocket as weapon and map it to sentry gun, so we get the sentry death icon based off level
  10400. if ( 0 == Q_strcmp( killer_weapon_name, "tf_projectile_sentryrocket" ) )
  10401. {
  10402. killer_weapon_name = "obj_sentrygun3";
  10403. }
  10404. else if ( 0 == Q_strcmp( killer_weapon_name, "obj_sentrygun" ) )
  10405. {
  10406. CObjectSentrygun *pSentrygun = assert_cast<CObjectSentrygun*>( pInflictor );
  10407. if ( pSentrygun )
  10408. {
  10409. if ( pSentrygun->IsMiniBuilding() )
  10410. {
  10411. killer_weapon_name = "obj_minisentry";
  10412. }
  10413. else
  10414. {
  10415. int iSentryLevel = pSentrygun->GetUpgradeLevel();
  10416. switch( iSentryLevel)
  10417. {
  10418. case 1:
  10419. killer_weapon_name = "obj_sentrygun";
  10420. break;
  10421. case 2:
  10422. killer_weapon_name = "obj_sentrygun2";
  10423. break;
  10424. case 3:
  10425. killer_weapon_name = "obj_sentrygun3";
  10426. break;
  10427. default:
  10428. killer_weapon_name = "obj_sentrygun";
  10429. break;
  10430. }
  10431. }
  10432. }
  10433. }
  10434. else if ( 0 == Q_strcmp( killer_weapon_name, "tf_projectile_healing_bolt" ) )
  10435. {
  10436. killer_weapon_name = "crusaders_crossbow";
  10437. }
  10438. else if ( 0 == Q_strcmp( killer_weapon_name, "tf_projectile_pipe" ) )
  10439. {
  10440. // let's look-up the primary weapon to see what type of grenade launcher it is
  10441. if ( pScorer )
  10442. {
  10443. CTFPlayer *pTFScorer = ToTFPlayer( pScorer );
  10444. if ( pTFScorer )
  10445. {
  10446. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pTFScorer->Weapon_GetWeaponByType( TF_WPN_TYPE_PRIMARY ) );
  10447. if ( pWeapon && ( pWeapon->GetWeaponID() == TF_WEAPON_GRENADELAUNCHER ) && pWeapon->GetAttributeContainer() )
  10448. {
  10449. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  10450. if ( pItem && pItem->GetStaticData() )
  10451. {
  10452. if ( pItem->GetStaticData()->GetIconClassname() )
  10453. {
  10454. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10455. *iWeaponID = TF_WEAPON_NONE;
  10456. }
  10457. }
  10458. }
  10459. }
  10460. }
  10461. }
  10462. else if ( 0 == Q_strcmp( killer_weapon_name, "tf_projectile_energy_ring" ) )
  10463. {
  10464. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( info.GetWeapon() );
  10465. if ( pWeapon )
  10466. {
  10467. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  10468. if ( pItem && pItem->GetStaticData() && pItem->GetStaticData()->GetIconClassname() )
  10469. {
  10470. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10471. *iWeaponID = TF_WEAPON_NONE;
  10472. }
  10473. }
  10474. }
  10475. else if ( 0 == Q_strcmp( killer_weapon_name, "obj_attachment_sapper" ) )
  10476. {
  10477. // let's look-up the sapper weapon to see what type it is
  10478. if ( pScorer )
  10479. {
  10480. CTFPlayer *pTFScorer = ToTFPlayer( pScorer );
  10481. if ( pTFScorer )
  10482. {
  10483. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( pTFScorer->GetEntityForLoadoutSlot( LOADOUT_POSITION_BUILDING ) );
  10484. if ( pWeapon && ( pWeapon->GetWeaponID() == TF_WEAPON_BUILDER ) && pWeapon->GetAttributeContainer() )
  10485. {
  10486. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  10487. if ( pItem && pItem->GetStaticData() )
  10488. {
  10489. if ( pItem->GetStaticData()->GetIconClassname() )
  10490. {
  10491. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10492. *iWeaponID = TF_WEAPON_NONE;
  10493. }
  10494. }
  10495. }
  10496. }
  10497. }
  10498. }
  10499. else if ( ( info.GetDamageCustom() == TF_DMG_CUSTOM_STANDARD_STICKY ) || ( info.GetDamageCustom() == TF_DMG_CUSTOM_AIR_STICKY_BURST ) )
  10500. {
  10501. // let's look-up the secondary weapon to see what type of sticky launcher it is
  10502. if ( pScorer )
  10503. {
  10504. CTFPlayer *pTFScorer = ToTFPlayer( pScorer );
  10505. if ( pTFScorer )
  10506. {
  10507. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pTFScorer->Weapon_GetWeaponByType( TF_WPN_TYPE_SECONDARY ) );
  10508. if ( pWeapon && ( pWeapon->GetWeaponID() == TF_WEAPON_PIPEBOMBLAUNCHER ) && pWeapon->GetAttributeContainer() )
  10509. {
  10510. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  10511. if ( pItem && pItem->GetStaticData() )
  10512. {
  10513. if ( pItem->GetStaticData()->GetIconClassname() )
  10514. {
  10515. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10516. *iWeaponID = TF_WEAPON_NONE;
  10517. }
  10518. }
  10519. }
  10520. }
  10521. }
  10522. }
  10523. return killer_weapon_name;
  10524. }
  10525. //-----------------------------------------------------------------------------
  10526. // Purpose: returns the player who assisted in the kill, or NULL if no assister
  10527. //-----------------------------------------------------------------------------
  10528. CBasePlayer *CTFGameRules::GetAssister( CBasePlayer *pVictim, CBasePlayer *pScorer, CBaseEntity *pInflictor )
  10529. {
  10530. CTFPlayer *pTFScorer = ToTFPlayer( pScorer );
  10531. CTFPlayer *pTFVictim = ToTFPlayer( pVictim );
  10532. if ( pTFScorer && pTFVictim )
  10533. {
  10534. // if victim killed himself, don't award an assist to anyone else, even if there was a recent damager
  10535. if ( pTFScorer == pTFVictim )
  10536. return NULL;
  10537. // If an assist has been specified already, use it.
  10538. if ( pTFVictim->m_Shared.GetAssist() )
  10539. {
  10540. return pTFVictim->m_Shared.GetAssist();
  10541. }
  10542. // If a player is healing the scorer, give that player credit for the assist
  10543. CTFPlayer *pHealer = ToTFPlayer( pTFScorer->m_Shared.GetFirstHealer() );
  10544. // Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing.
  10545. // Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills.
  10546. if ( pHealer && ( TF_CLASS_MEDIC == pHealer->GetPlayerClass()->GetClassIndex() ) && ( NULL == dynamic_cast<CObjectSentrygun *>( pInflictor ) ) )
  10547. {
  10548. return pHealer;
  10549. }
  10550. // If we're under the effect of a condition that grants assists, give one to the player that buffed us
  10551. CTFPlayer *pCondAssister = ToTFPlayer( pTFScorer->m_Shared.GetConditionAssistFromAttacker() );
  10552. if ( pCondAssister )
  10553. return pCondAssister;
  10554. // See who has damaged the victim 2nd most recently (most recent is the killer), and if that is within a certain time window.
  10555. // If so, give that player an assist. (Only 1 assist granted, to single other most recent damager.)
  10556. CTFPlayer *pRecentDamager = GetRecentDamager( pTFVictim, 1, TF_TIME_ASSIST_KILL );
  10557. if ( pRecentDamager && ( pRecentDamager != pScorer ) )
  10558. return pRecentDamager;
  10559. // if a teammate has recently helped this sentry (ie: wrench hit), they assisted in the kill
  10560. CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( pInflictor );
  10561. if ( sentry )
  10562. {
  10563. CTFPlayer *pAssister = sentry->GetAssistingTeammate( TF_TIME_ASSIST_KILL );
  10564. if ( pAssister )
  10565. return pAssister;
  10566. }
  10567. }
  10568. return NULL;
  10569. }
  10570. //-----------------------------------------------------------------------------
  10571. // Purpose: Returns specified recent damager, if there is one who has done damage
  10572. // within the specified time period. iDamager=0 returns the most recent
  10573. // damager, iDamager=1 returns the next most recent damager.
  10574. //-----------------------------------------------------------------------------
  10575. CTFPlayer *CTFGameRules::GetRecentDamager( CTFPlayer *pVictim, int iDamager, float flMaxElapsed )
  10576. {
  10577. EntityHistory_t *damagerHistory = pVictim->m_AchievementData.GetDamagerHistory( iDamager );
  10578. if ( !damagerHistory )
  10579. return NULL;
  10580. if ( damagerHistory->hEntity && ( gpGlobals->curtime - damagerHistory->flTimeDamage <= flMaxElapsed ) )
  10581. {
  10582. CTFPlayer *pRecentDamager = ToTFPlayer( damagerHistory->hEntity );
  10583. if ( pRecentDamager )
  10584. return pRecentDamager;
  10585. }
  10586. return NULL;
  10587. }
  10588. //-----------------------------------------------------------------------------
  10589. // Purpose: Returns who should be awarded the kill
  10590. //-----------------------------------------------------------------------------
  10591. CBasePlayer *CTFGameRules::GetDeathScorer( CBaseEntity *pKiller, CBaseEntity *pInflictor, CBaseEntity *pVictim )
  10592. {
  10593. if ( ( pKiller == pVictim ) && ( pKiller == pInflictor ) )
  10594. {
  10595. // If this was an explicit suicide, see if there was a damager within a certain time window. If so, award this as a kill to the damager.
  10596. CTFPlayer *pTFVictim = ToTFPlayer( pVictim );
  10597. if ( pTFVictim )
  10598. {
  10599. CTFPlayer *pRecentDamager = GetRecentDamager( pTFVictim, 0, TF_TIME_SUICIDE_KILL_CREDIT );
  10600. if ( pRecentDamager )
  10601. return pRecentDamager;
  10602. }
  10603. }
  10604. //Handle Pyro's Deflection credit.
  10605. CTFWeaponBaseGrenadeProj *pBaseGrenade = dynamic_cast<CTFWeaponBaseGrenadeProj*>( pInflictor );
  10606. if ( pBaseGrenade )
  10607. {
  10608. if ( pBaseGrenade->GetDeflected() )
  10609. {
  10610. if ( pBaseGrenade->GetWeaponID() == TF_WEAPON_GRENADE_PIPEBOMB )
  10611. {
  10612. CTFPlayer *pDeflectOwner = ToTFPlayer( pBaseGrenade->GetDeflectOwner() );
  10613. if ( pDeflectOwner )
  10614. {
  10615. if ( pDeflectOwner->InSameTeam( pVictim ) == false )
  10616. return pDeflectOwner;
  10617. else
  10618. {
  10619. pBaseGrenade->ResetDeflected();
  10620. pBaseGrenade->SetDeflectOwner( NULL );
  10621. }
  10622. }
  10623. }
  10624. }
  10625. }
  10626. return BaseClass::GetDeathScorer( pKiller, pInflictor, pVictim );
  10627. }
  10628. //-----------------------------------------------------------------------------
  10629. // Purpose:
  10630. // Input : *pVictim -
  10631. // *pKiller -
  10632. // *pInflictor -
  10633. //-----------------------------------------------------------------------------
  10634. void CTFGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info )
  10635. {
  10636. DeathNotice( pVictim, info, "player_death" );
  10637. }
  10638. void CTFGameRules::DeathNotice( CBasePlayer *pVictim, const CTakeDamageInfo &info, const char* eventName )
  10639. {
  10640. int killer_ID = 0;
  10641. // Find the killer & the scorer
  10642. CTFPlayer *pTFPlayerVictim = ToTFPlayer( pVictim );
  10643. CBaseEntity *pInflictor = info.GetInflictor();
  10644. CBaseEntity *pKiller = info.GetAttacker();
  10645. CTFPlayer *pScorer = ToTFPlayer( GetDeathScorer( pKiller, pInflictor, pVictim ) );
  10646. CTFPlayer *pAssister = ToTFPlayer( GetAssister( pVictim, pScorer, pInflictor ) );
  10647. // You can't assist yourself!
  10648. Assert( pScorer != pAssister || !pScorer );
  10649. if ( pScorer == pAssister && pScorer )
  10650. {
  10651. pAssister = NULL;
  10652. }
  10653. // Silent killers cause no death notices.
  10654. bool bSilentKill = false;
  10655. CTFPlayer *pAttacker = (CTFPlayer*)ToTFPlayer( info.GetAttacker() );
  10656. if ( pAttacker )
  10657. {
  10658. CTFWeaponBase *pWpn = pAttacker->GetActiveTFWeapon();
  10659. if ( pWpn && pWpn->IsSilentKiller() && ( info.GetDamageCustom() == TF_DMG_CUSTOM_BACKSTAB ) )
  10660. bSilentKill = true;
  10661. }
  10662. // Determine whether it's a feign death fake death notice
  10663. bool bFeignDeath = pTFPlayerVictim->IsGoingFeignDeath();
  10664. if ( bFeignDeath )
  10665. {
  10666. CTFPlayer *pDisguiseTarget = ToTFPlayer( pTFPlayerVictim->m_Shared.GetDisguiseTarget() );
  10667. if ( pDisguiseTarget && (pTFPlayerVictim->GetTeamNumber() == pDisguiseTarget->GetTeamNumber()) )
  10668. {
  10669. // We're disguised as a team mate. Pretend to die as that player instead of us.
  10670. pVictim = pTFPlayerVictim = pDisguiseTarget;
  10671. }
  10672. }
  10673. // Work out what killed the player, and send a message to all clients about it
  10674. int iWeaponID;
  10675. const char *killer_weapon_name = GetKillingWeaponName( info, pTFPlayerVictim, &iWeaponID );
  10676. const char *killer_weapon_log_name = killer_weapon_name;
  10677. // Kill eater events.
  10678. {
  10679. // Was this a sentry kill? If the sentry did the kill itself with bullets then it'll be the inflictor.
  10680. // If it got the kill by firing a rocket, the rocket will be the inflictor and the sentry will be the
  10681. // owner of the rocket.
  10682. //
  10683. // Holy crap dynamic_cast quagmire of sadness below.
  10684. CObjectSentrygun *pSentrygun = dynamic_cast<CObjectSentrygun *>( pInflictor );
  10685. if ( !pSentrygun )
  10686. {
  10687. pSentrygun = pInflictor ? dynamic_cast<CObjectSentrygun *>( pInflictor->GetOwnerEntity() ) : NULL;
  10688. }
  10689. if ( pSentrygun )
  10690. {
  10691. // Try to grab the wrench that the engineer has equipped right now. We destroy sentries when wrenches
  10692. // get changed so whatever they have equipped right now counts for the wrench that built this sentry.
  10693. CTFPlayer *pBuilder = pSentrygun->GetBuilder();
  10694. if ( pBuilder )
  10695. {
  10696. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pBuilder->GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) ), pScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillsBySentry );
  10697. // PDA's Also count Sentry kills
  10698. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pBuilder->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA ) ), pScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillsBySentry );
  10699. // Check if the engineer is using a Wrangler on this sentry
  10700. CTFLaserPointer* pLaserPointer = dynamic_cast< CTFLaserPointer * >( pBuilder->GetEntityForLoadoutSlot( LOADOUT_POSITION_SECONDARY ) );
  10701. if ( pLaserPointer && pLaserPointer->HasLaserDot() )
  10702. {
  10703. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pLaserPointer ), pScorer, pTFPlayerVictim, kKillEaterEvent_PlayerKillsByManualControlOfSentry );
  10704. }
  10705. }
  10706. }
  10707. // Should we award an assist kill to someone?
  10708. if ( pAssister )
  10709. {
  10710. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pAssister->GetActiveWeapon() ), pAssister, pTFPlayerVictim, kKillEaterEvent_PlayerKillAssist );
  10711. HatAndMiscEconEntities_OnOwnerKillEaterEvent( pAssister, pTFPlayerVictim, kKillEaterEvent_CosmeticAssists );
  10712. }
  10713. }
  10714. if ( pScorer ) // Is the killer a client?
  10715. {
  10716. killer_ID = pScorer->GetUserID();
  10717. }
  10718. CTFWeaponBase *pScorerWeapon = NULL;
  10719. if ( pScorer )
  10720. {
  10721. pScorerWeapon = dynamic_cast< CTFWeaponBase * >( pScorer->Weapon_OwnsThisID( iWeaponID ) );
  10722. if ( pScorerWeapon )
  10723. {
  10724. CEconItemView *pItem = pScorerWeapon->GetAttributeContainer()->GetItem();
  10725. if ( pItem )
  10726. {
  10727. if ( pItem->GetStaticData()->GetIconClassname() )
  10728. {
  10729. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  10730. }
  10731. if ( pItem->GetStaticData()->GetLogClassname() )
  10732. {
  10733. killer_weapon_log_name = pItem->GetStaticData()->GetLogClassname();
  10734. }
  10735. }
  10736. }
  10737. }
  10738. // In Arena mode award first blood to the first player that kills an enemy.
  10739. bool bKillWasFirstBlood = false;
  10740. if ( IsFirstBloodAllowed() )
  10741. {
  10742. if ( pScorer && pVictim && pScorer != pVictim )
  10743. {
  10744. if ( !FStrEq( eventName, "fish_notice" ) && !FStrEq( eventName, "fish_notice__arm" ) && !FStrEq( eventName, "throwable_hit" ) )
  10745. {
  10746. #ifndef _DEBUG
  10747. if ( GetGlobalTeam( pVictim->GetTeamNumber() ) && GetGlobalTeam( pVictim->GetTeamNumber() )->GetNumPlayers() > 1 )
  10748. #endif // !DEBUG
  10749. {
  10750. float flFastTime = IsCompetitiveMode() ? 120.f : TF_ARENA_MODE_FAST_FIRST_BLOOD_TIME;
  10751. float flSlowTime = IsCompetitiveMode() ? 300.f : TF_ARENA_MODE_SLOW_FIRST_BLOOD_TIME;
  10752. if ( ( gpGlobals->curtime - m_flRoundStartTime ) <= flFastTime )
  10753. {
  10754. BroadcastSound( 255, "Announcer.AM_FirstBloodFast" );
  10755. }
  10756. else if ( ( gpGlobals->curtime - m_flRoundStartTime ) >= flSlowTime )
  10757. {
  10758. BroadcastSound( 255, "Announcer.AM_FirstBloodFinally" );
  10759. }
  10760. else
  10761. {
  10762. BroadcastSound( 255, "Announcer.AM_FirstBloodRandom" );
  10763. }
  10764. m_bArenaFirstBlood = true;
  10765. bKillWasFirstBlood = true;
  10766. if ( IsCompetitiveMode() )
  10767. {
  10768. // CTF_GameStats.Event_PlayerAwardBonusPoints( pScorer, pVictim, 10 );
  10769. //
  10770. // CUtlVector< CTFPlayer* > vecPlayers;
  10771. // CollectPlayers( &vecPlayers, pScorer->GetTeamNumber(), false );
  10772. // FOR_EACH_VEC( vecPlayers, i )
  10773. // {
  10774. // if ( !vecPlayers[i] )
  10775. // continue;
  10776. //
  10777. // if ( vecPlayers[i] == pScorer )
  10778. // continue;
  10779. //
  10780. // CTF_GameStats.Event_PlayerAwardBonusPoints( vecPlayers[i], pVictim, 5 );
  10781. // }
  10782. }
  10783. else
  10784. {
  10785. pScorer->m_Shared.AddCond( TF_COND_CRITBOOSTED_FIRST_BLOOD, TF_ARENA_MODE_FIRST_BLOOD_CRIT_TIME );
  10786. }
  10787. }
  10788. }
  10789. }
  10790. }
  10791. else
  10792. {
  10793. // so you can't turn on the ConVar in the middle of a round and get the first blood reward
  10794. m_bArenaFirstBlood = true;
  10795. }
  10796. // Awesome hack for pyroland silliness: if there was no other assister, and the person that got
  10797. // the kill has some sort of "pet" item (balloonicorn, brainslug, etc.), we send the name of
  10798. // that item down as the assister. We'll use a custom name if available and fall back to the
  10799. // localization token (localized on the client) if not.
  10800. CUtlConstString sAssisterOverrideDesc;
  10801. if ( pScorer && !pAssister )
  10802. {
  10803. // Find out whether the killer has at least one item that will ask for kill assist credit.
  10804. int iKillerHasPetItem = 0;
  10805. CUtlVector<CBaseEntity *> vecItems;
  10806. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER_WITH_ITEMS( pScorer, iKillerHasPetItem, &vecItems, counts_as_assister );
  10807. FOR_EACH_VEC( vecItems, i )
  10808. {
  10809. CEconEntity *pEconEntity = dynamic_cast<CEconEntity *>( vecItems[i] );
  10810. if ( !pEconEntity )
  10811. continue;
  10812. CEconItemView *pEconItemView = pEconEntity->GetAttributeContainer()->GetItem();
  10813. if ( !pEconItemView )
  10814. continue;
  10815. if ( pEconItemView->GetCustomName() )
  10816. {
  10817. sAssisterOverrideDesc = CFmtStr( "%c%s", iKillerHasPetItem == 2 ? kHorriblePyroVisionHack_KillAssisterType_CustomName_First : kHorriblePyroVisionHack_KillAssisterType_CustomName, pEconItemView->GetCustomName() );
  10818. }
  10819. else
  10820. {
  10821. sAssisterOverrideDesc = CFmtStr( "%c%s", iKillerHasPetItem == 2 ? kHorriblePyroVisionHack_KillAssisterType_LocalizationString_First : kHorriblePyroVisionHack_KillAssisterType_LocalizationString, pEconItemView->GetItemDefinition()->GetItemBaseName() ).Get();
  10822. }
  10823. break;
  10824. }
  10825. }
  10826. IGameEvent * event = gameeventmanager->CreateEvent( eventName /* "player_death" */ );
  10827. //if ( event && FStrEq( eventName, "throwable_hit" ) )
  10828. //{
  10829. // int iHitCount = 1;
  10830. // PlayerStats_t *pStats = CTF_GameStats.FindPlayerStats( pScorer );
  10831. // if ( pStats )
  10832. // {
  10833. // iHitCount = pStats->statsAccumulated.Get( TFSTAT_THROWABLEHIT );
  10834. // }
  10835. // event->SetInt( "totalhits", iHitCount );
  10836. //}
  10837. if ( event )
  10838. {
  10839. event->SetInt( "userid", pVictim->GetUserID() );
  10840. event->SetInt( "victim_entindex", pVictim->entindex() );
  10841. event->SetInt( "attacker", killer_ID );
  10842. event->SetInt( "assister", pAssister ? pAssister->GetUserID() : -1 );
  10843. event->SetString( "weapon", killer_weapon_name );
  10844. event->SetString( "weapon_logclassname", killer_weapon_log_name );
  10845. event->SetInt( "weaponid", iWeaponID );
  10846. event->SetInt( "damagebits", info.GetDamageType() );
  10847. event->SetInt( "customkill", info.GetDamageCustom() );
  10848. event->SetInt( "inflictor_entindex", pInflictor ? pInflictor->entindex() : -1 );
  10849. event->SetInt( "priority", 7 ); // HLTV event priority, not transmitted
  10850. if ( info.GetPlayerPenetrationCount() > 0 )
  10851. {
  10852. event->SetInt( "playerpenetratecount", info.GetPlayerPenetrationCount() );
  10853. }
  10854. if ( !sAssisterOverrideDesc.IsEmpty() )
  10855. {
  10856. event->SetString( "assister_fallback", sAssisterOverrideDesc.Get() );
  10857. }
  10858. event->SetBool( "silent_kill", bSilentKill );
  10859. int iDeathFlags = pTFPlayerVictim->GetDeathFlags();
  10860. if ( bKillWasFirstBlood )
  10861. {
  10862. iDeathFlags |= TF_DEATH_FIRST_BLOOD;
  10863. }
  10864. if ( bFeignDeath )
  10865. {
  10866. iDeathFlags |= TF_DEATH_FEIGN_DEATH;
  10867. }
  10868. if ( pTFPlayerVictim->WasGibbedOnLastDeath() )
  10869. {
  10870. iDeathFlags |= TF_DEATH_GIBBED;
  10871. }
  10872. if ( pTFPlayerVictim->IsInPurgatory() )
  10873. {
  10874. iDeathFlags |= TF_DEATH_PURGATORY;
  10875. }
  10876. if ( pTFPlayerVictim->IsMiniBoss() )
  10877. {
  10878. iDeathFlags |= TF_DEATH_MINIBOSS;
  10879. }
  10880. // Australium Guns get a Gold Background
  10881. IHasAttributes *pAttribInterface = GetAttribInterface( info.GetWeapon() );
  10882. if ( pAttribInterface )
  10883. {
  10884. int iIsAustralium = 0;
  10885. CALL_ATTRIB_HOOK_INT_ON_OTHER( info.GetWeapon(), iIsAustralium, is_australium_item );
  10886. if ( iIsAustralium )
  10887. {
  10888. iDeathFlags |= TF_DEATH_AUSTRALIUM;
  10889. }
  10890. }
  10891. // We call this directly since we need more information than provided in the event alone.
  10892. if ( FStrEq( eventName, "player_death" ) )
  10893. {
  10894. CTF_GameStats.Event_KillDetail( pScorer, pTFPlayerVictim, pAssister, event, info );
  10895. event->SetInt( "kill_streak_victim", pTFPlayerVictim->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Kills ) );
  10896. event->SetBool( "rocket_jump", ( pTFPlayerVictim->RocketJumped() == 1 ) );
  10897. event->SetInt( "crit_type", info.GetCritType() );
  10898. // Kill streak updating
  10899. if ( pTFPlayerVictim && pScorer && pTFPlayerVictim != pScorer )
  10900. {
  10901. // Propagate duckstreaks
  10902. event->SetInt( "duck_streak_victim", pTFPlayerVictim->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Ducks ) );
  10903. event->SetInt( "duck_streak_total", pScorer->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Ducks ) );
  10904. event->SetInt( "ducks_streaked", pScorer->m_Shared.GetLastDuckStreakIncrement() );
  10905. // Check if they have the appropriate attribute.
  10906. int iKillStreak = 0;
  10907. int iKills = 0;
  10908. CBaseEntity *pKillStreakTarget = NULL;
  10909. if ( !pAttribInterface )
  10910. {
  10911. // Check if you are a sentry and if so, use the wrench
  10912. // For Sentries Inflictor can be the sentry (bullets) or the Sentry Rocket
  10913. CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>( pInflictor );
  10914. if ( !pSentry && pInflictor )
  10915. {
  10916. pSentry = dynamic_cast<CObjectSentrygun*>( pInflictor->GetOwnerEntity() );
  10917. }
  10918. if ( pSentry )
  10919. {
  10920. pKillStreakTarget = dynamic_cast<CTFWeaponBase*>( pScorer->GetEntityForLoadoutSlot( LOADOUT_POSITION_MELEE ) );
  10921. }
  10922. }
  10923. else
  10924. {
  10925. pKillStreakTarget = info.GetWeapon();
  10926. }
  10927. if ( pKillStreakTarget )
  10928. {
  10929. CALL_ATTRIB_HOOK_INT_ON_OTHER( pKillStreakTarget, iKillStreak, killstreak_tier );
  10930. #ifdef STAGING_ONLY
  10931. if ( tf_killstreak_alwayson.GetBool() )
  10932. {
  10933. iKillStreak = 1;
  10934. }
  10935. #endif
  10936. // Always track killstreak regardless of the attribute for data collection purposes
  10937. pScorer->m_Shared.IncrementStreak( CTFPlayerShared::kTFStreak_KillsAll, 1 );
  10938. if ( iKillStreak )
  10939. {
  10940. iKills = pScorer->m_Shared.IncrementStreak( CTFPlayerShared::kTFStreak_Kills, 1 );
  10941. event->SetInt( "kill_streak_total", iKills );
  10942. int iWepKills = 0;
  10943. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase*>( pKillStreakTarget );
  10944. if ( pWeapon )
  10945. {
  10946. iWepKills = pWeapon->GetKillStreak() + 1;
  10947. pWeapon->SetKillStreak( iWepKills );
  10948. }
  10949. else
  10950. {
  10951. CTFWearable *pWearable = dynamic_cast<CTFWearable*>( pKillStreakTarget );
  10952. if ( pWearable )
  10953. {
  10954. iWepKills = pWearable->GetKillStreak() + 1;
  10955. pWearable->SetKillStreak( iWepKills );
  10956. }
  10957. }
  10958. event->SetInt( "kill_streak_wep", iWepKills );
  10959. // Track each player's max streak per-round
  10960. CTF_GameStats.Event_PlayerEarnedKillStreak( pScorer );
  10961. }
  10962. }
  10963. if ( pAssister )
  10964. {
  10965. event->SetInt( "duck_streak_assist", pAssister->m_Shared.GetStreak( CTFPlayerShared::kTFStreak_Ducks ) );
  10966. // Only allow assists for Mediguns
  10967. CTFWeaponBase *pAssisterWpn = pAssister->GetActiveTFWeapon();
  10968. if ( pAssisterWpn && pAssister->IsPlayerClass( TF_CLASS_MEDIC ) )
  10969. {
  10970. CWeaponMedigun *pMedigun = dynamic_cast<CWeaponMedigun*>( pAssisterWpn );
  10971. if ( pMedigun )
  10972. {
  10973. iKillStreak = 0;
  10974. CALL_ATTRIB_HOOK_INT_ON_OTHER( pAssisterWpn, iKillStreak, killstreak_tier );
  10975. #ifdef STAGING_ONLY
  10976. if ( tf_killstreak_alwayson.GetBool() )
  10977. {
  10978. iKillStreak = 1;
  10979. }
  10980. #endif
  10981. if ( iKillStreak )
  10982. {
  10983. iKills = pAssister->m_Shared.IncrementStreak( CTFPlayerShared::kTFStreak_Kills, 1 );
  10984. event->SetInt( "kill_streak_assist", iKills );
  10985. int iWepKills = pAssisterWpn->GetKillStreak() + 1;
  10986. pAssisterWpn->SetKillStreak( iWepKills );
  10987. // Track each player's max streak per-round
  10988. CTF_GameStats.Event_PlayerEarnedKillStreak( pAssister );
  10989. }
  10990. }
  10991. }
  10992. }
  10993. }
  10994. }
  10995. event->SetInt( "death_flags", iDeathFlags );
  10996. event->SetInt( "stun_flags", pTFPlayerVictim->m_iOldStunFlags );
  10997. item_definition_index_t weaponDefIndex = INVALID_ITEM_DEF_INDEX;
  10998. if ( pScorerWeapon )
  10999. {
  11000. CEconItemView *pItem = pScorerWeapon->GetAttributeContainer()->GetItem();
  11001. if ( pItem )
  11002. {
  11003. weaponDefIndex = pItem->GetItemDefIndex();
  11004. }
  11005. }
  11006. else if ( pScorer && pScorer->GetActiveTFWeapon() )
  11007. {
  11008. // get from active weapon instead
  11009. CEconItemView *pItem = pScorer->GetActiveTFWeapon()->GetAttributeContainer()->GetItem();
  11010. if ( pItem )
  11011. {
  11012. weaponDefIndex = pItem->GetItemDefIndex();
  11013. }
  11014. }
  11015. event->SetInt( "weapon_def_index", weaponDefIndex );
  11016. pTFPlayerVictim->m_iOldStunFlags = 0;
  11017. gameeventmanager->FireEvent( event );
  11018. }
  11019. }
  11020. void CTFGameRules::ClientDisconnected( edict_t *pClient )
  11021. {
  11022. CTFPlayer *pPlayer = ToTFPlayer( GetContainingEntity( pClient ) );
  11023. if ( pPlayer )
  11024. {
  11025. // ACHIEVEMENT_TF_PYRO_DOMINATE_LEAVESVR - Pyro causes a dominated player to leave the server
  11026. for ( int i = 1; i <= gpGlobals->maxClients ; i++ )
  11027. {
  11028. if ( pPlayer->m_Shared.IsPlayerDominatingMe(i) )
  11029. {
  11030. CTFPlayer *pDominatingPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11031. if ( pDominatingPlayer && pDominatingPlayer != pPlayer )
  11032. {
  11033. if ( pDominatingPlayer->IsPlayerClass(TF_CLASS_PYRO) )
  11034. {
  11035. pDominatingPlayer->AwardAchievement( ACHIEVEMENT_TF_PYRO_DOMINATE_LEAVESVR );
  11036. }
  11037. }
  11038. }
  11039. }
  11040. CTFPlayerResource *pTFResource = dynamic_cast< CTFPlayerResource* >( g_pPlayerResource );
  11041. if ( pTFResource )
  11042. {
  11043. if ( pPlayer->entindex() == pTFResource->GetPartyLeaderIndex( pPlayer->GetTeamNumber() ) )
  11044. {
  11045. // the leader is leaving so reset the player resource index
  11046. pTFResource->SetPartyLeaderIndex( pPlayer->GetTeamNumber(), 0 );
  11047. }
  11048. }
  11049. // Notify gamestats that the player left.
  11050. CTF_GameStats.Event_PlayerDisconnectedTF( pPlayer );
  11051. // Check Ready status for the player
  11052. if ( UsePlayerReadyStatusMode() )
  11053. {
  11054. if ( !pPlayer->IsBot() && State_Get() != GR_STATE_RND_RUNNING )
  11055. {
  11056. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  11057. if ( !pMatchDesc || !pMatchDesc->m_params.m_bAutoReady )
  11058. {
  11059. // Always reset when a player leaves this type of match
  11060. PlayerReadyStatus_ResetState();
  11061. }
  11062. else if ( !IsTeamReady( pPlayer->GetTeamNumber() ) )
  11063. {
  11064. if ( IsPlayerReady( pPlayer->entindex() ) )
  11065. {
  11066. // Clear the ready status so it doesn't block the rest of the team
  11067. PlayerReadyStatus_UpdatePlayerState( pPlayer, false );
  11068. }
  11069. else
  11070. {
  11071. // Disconnecting player wasn't ready, but is the rest of the team?
  11072. // If so, we want to cancel the ready_state so it doesn't start right away when this player disconnects
  11073. bool bEveryoneReady = true;
  11074. CUtlVector<CTFPlayer *> playerVector;
  11075. CollectPlayers( &playerVector, pPlayer->GetTeamNumber() );
  11076. FOR_EACH_VEC( playerVector, i )
  11077. {
  11078. if ( !playerVector[i] )
  11079. continue;
  11080. if ( playerVector[i]->IsBot() )
  11081. continue;
  11082. if ( playerVector[i] == pPlayer )
  11083. continue;
  11084. if ( !IsPlayerReady( playerVector[i]->entindex() ) )
  11085. {
  11086. bEveryoneReady = false;
  11087. }
  11088. }
  11089. if ( bEveryoneReady )
  11090. {
  11091. PlayerReadyStatus_ResetState();
  11092. }
  11093. }
  11094. }
  11095. else
  11096. {
  11097. // If we're currently in a countdown we should cancel it
  11098. if ( ( m_flRestartRoundTime > 0 ) || ( mp_restartgame.GetInt() > 0 ) )
  11099. {
  11100. PlayerReadyStatus_ResetState();
  11101. }
  11102. }
  11103. }
  11104. }
  11105. // clean up anything they left behind
  11106. pPlayer->TeamFortress_ClientDisconnected();
  11107. Arena_ClientDisconnect( pPlayer->GetPlayerName() );
  11108. }
  11109. BaseClass::ClientDisconnected( pClient );
  11110. // are any of the spies disguising as this player?
  11111. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  11112. {
  11113. CTFPlayer *pTemp = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11114. if ( pTemp && pTemp != pPlayer )
  11115. {
  11116. if ( pTemp->m_Shared.GetDisguiseTarget() == pPlayer )
  11117. {
  11118. // choose someone else...
  11119. pTemp->m_Shared.FindDisguiseTarget();
  11120. }
  11121. }
  11122. }
  11123. }
  11124. // Falling damage stuff.
  11125. #define TF_PLAYER_MAX_SAFE_FALL_SPEED 650
  11126. float CTFGameRules::FlPlayerFallDamage( CBasePlayer *pPlayer )
  11127. {
  11128. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  11129. if ( !pTFPlayer )
  11130. return 0;
  11131. // Karts don't take fall damage
  11132. if ( pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
  11133. {
  11134. return 0;
  11135. }
  11136. // grappling hook don't take fall damage
  11137. if ( pTFPlayer->GetGrapplingHookTarget() )
  11138. {
  11139. return 0;
  11140. }
  11141. if ( pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY )
  11142. {
  11143. return 0;
  11144. }
  11145. if ( pPlayer->m_Local.m_flFallVelocity > TF_PLAYER_MAX_SAFE_FALL_SPEED )
  11146. {
  11147. // Old TFC damage formula
  11148. float flFallDamage = 5 * (pPlayer->m_Local.m_flFallVelocity / 300);
  11149. // Fall damage needs to scale according to the player's max health, or
  11150. // it's always going to be much more dangerous to weaker classes than larger.
  11151. float flRatio = (float)pPlayer->GetMaxHealth() / 100.0;
  11152. flFallDamage *= flRatio;
  11153. flFallDamage *= random->RandomFloat( 0.8, 1.2 );
  11154. int iCancelFallingDamage = 0;
  11155. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iCancelFallingDamage, cancel_falling_damage );
  11156. if ( iCancelFallingDamage > 0 )
  11157. flFallDamage = 0;
  11158. return flFallDamage;
  11159. }
  11160. // Fall caused no damage
  11161. return 0;
  11162. }
  11163. void CTFGameRules::SendArenaWinPanelInfo( void )
  11164. {
  11165. IGameEvent *winEvent = gameeventmanager->CreateEvent( "arena_win_panel" );
  11166. if ( winEvent )
  11167. {
  11168. int iBlueScore = GetGlobalTeam( TF_TEAM_BLUE )->GetScore();
  11169. int iRedScore = GetGlobalTeam( TF_TEAM_RED )->GetScore();
  11170. int iBlueScorePrev = iBlueScore;
  11171. int iRedScorePrev = iRedScore;
  11172. // if this is a complete round, calc team scores prior to this win
  11173. switch ( m_iWinningTeam )
  11174. {
  11175. case TF_TEAM_BLUE:
  11176. {
  11177. iBlueScorePrev = ( iBlueScore - TEAMPLAY_ROUND_WIN_SCORE >= 0 ) ? ( iBlueScore - TEAMPLAY_ROUND_WIN_SCORE ) : 0;
  11178. if ( IsInTournamentMode() == false )
  11179. {
  11180. iRedScore = 0;
  11181. }
  11182. }
  11183. break;
  11184. case TF_TEAM_RED:
  11185. {
  11186. iRedScorePrev = ( iRedScore - TEAMPLAY_ROUND_WIN_SCORE >= 0 ) ? ( iRedScore - TEAMPLAY_ROUND_WIN_SCORE ) : 0;
  11187. if ( IsInTournamentMode() == false )
  11188. {
  11189. iBlueScore = 0;
  11190. }
  11191. break;
  11192. }
  11193. case TEAM_UNASSIGNED:
  11194. break; // stalemate; nothing to do
  11195. }
  11196. winEvent->SetInt( "panel_style", WINPANEL_BASIC );
  11197. winEvent->SetInt( "winning_team", m_iWinningTeam );
  11198. winEvent->SetInt( "winreason", m_iWinReason );
  11199. winEvent->SetString( "cappers", ( m_iWinReason == WINREASON_ALL_POINTS_CAPTURED || m_iWinReason == WINREASON_FLAG_CAPTURE_LIMIT ) ? m_szMostRecentCappers : "" );
  11200. winEvent->SetInt( "blue_score", iBlueScore );
  11201. winEvent->SetInt( "red_score", iRedScore );
  11202. winEvent->SetInt( "blue_score_prev", iBlueScorePrev );
  11203. winEvent->SetInt( "red_score_prev", iRedScorePrev );
  11204. CTFPlayerResource *pResource = dynamic_cast< CTFPlayerResource * >( g_pPlayerResource );
  11205. if ( !pResource )
  11206. return;
  11207. // build a vector of players & round scores
  11208. CUtlVector<PlayerArenaRoundScore_t> vecPlayerScore;
  11209. int iPlayerIndex;
  11210. for( iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  11211. {
  11212. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  11213. if ( !pTFPlayer || !pTFPlayer->IsConnected() )
  11214. continue;
  11215. // filter out spectators and, if not stalemate, all players not on winning team
  11216. int iPlayerTeam = pTFPlayer->GetTeamNumber();
  11217. if ( ( iPlayerTeam < FIRST_GAME_TEAM ) )
  11218. continue;
  11219. PlayerStats_t *pStats = CTF_GameStats.FindPlayerStats( pTFPlayer );
  11220. PlayerArenaRoundScore_t &playerRoundScore = vecPlayerScore[vecPlayerScore.AddToTail()];
  11221. playerRoundScore.iPlayerIndex = iPlayerIndex;
  11222. if ( pStats )
  11223. {
  11224. playerRoundScore.iTotalDamage = pStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE];
  11225. playerRoundScore.iTotalHealing = pStats->statsCurrentRound.m_iStat[TFSTAT_HEALING];
  11226. if ( pTFPlayer->IsAlive() == true )
  11227. {
  11228. playerRoundScore.iTimeAlive = (int)gpGlobals->curtime - pTFPlayer->GetSpawnTime();
  11229. }
  11230. else
  11231. {
  11232. playerRoundScore.iTimeAlive = (int)pTFPlayer->GetDeathTime() - pTFPlayer->GetSpawnTime();
  11233. }
  11234. playerRoundScore.iKillingBlows = pStats->statsCurrentRound.m_iStat[TFSTAT_KILLS];
  11235. playerRoundScore.iScore = CalcPlayerScore( &pStats->statsCurrentRound, pTFPlayer );
  11236. }
  11237. }
  11238. // sort the players by round score
  11239. vecPlayerScore.Sort( PlayerArenaRoundScoreSortFunc );
  11240. // set the top (up to) 6 players by round score in the event data
  11241. int numPlayers = 6;
  11242. int iPlayersAdded = 0;
  11243. // Add winners first
  11244. for ( int i = 0; i < vecPlayerScore.Count(); i++ )
  11245. {
  11246. if ( GetWinningTeam() == TEAM_UNASSIGNED )
  11247. {
  11248. if ( iPlayersAdded >= 6 )
  11249. break;
  11250. }
  11251. else
  11252. {
  11253. if ( iPlayersAdded >= 3 )
  11254. break;
  11255. }
  11256. int iPlayerIndex = vecPlayerScore[i].iPlayerIndex;
  11257. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  11258. if ( pTFPlayer && pTFPlayer->GetTeamNumber() != GetWinningTeam() && GetWinningTeam() != TEAM_UNASSIGNED )
  11259. continue;
  11260. winEvent->SetInt( UTIL_VarArgs( "player_%d", iPlayersAdded + 1 ), vecPlayerScore[i].iPlayerIndex );
  11261. winEvent->SetInt( UTIL_VarArgs( "player_%d_damage", iPlayersAdded + 1 ), vecPlayerScore[i].iTotalDamage );
  11262. winEvent->SetInt( UTIL_VarArgs( "player_%d_healing", iPlayersAdded + 1 ), vecPlayerScore[i].iTotalHealing );
  11263. winEvent->SetInt( UTIL_VarArgs( "player_%d_lifetime", iPlayersAdded + 1 ), vecPlayerScore[i].iTimeAlive );
  11264. winEvent->SetInt( UTIL_VarArgs( "player_%d_kills", iPlayersAdded + 1 ), vecPlayerScore[i].iKillingBlows );
  11265. iPlayersAdded++;
  11266. }
  11267. if ( GetWinningTeam() != TEAM_UNASSIGNED )
  11268. {
  11269. //Now add the rest
  11270. iPlayersAdded = 3;
  11271. for ( int i = 0; i < vecPlayerScore.Count(); i++ )
  11272. {
  11273. if ( iPlayersAdded >= numPlayers )
  11274. break;
  11275. int iIndex = iPlayersAdded + 1;
  11276. int iPlayerIndex = vecPlayerScore[i].iPlayerIndex;
  11277. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  11278. if ( pTFPlayer && pTFPlayer->GetTeamNumber() == GetWinningTeam() )
  11279. continue;
  11280. winEvent->SetInt( UTIL_VarArgs( "player_%d", iIndex ), vecPlayerScore[i].iPlayerIndex );
  11281. winEvent->SetInt( UTIL_VarArgs( "player_%d_damage", iIndex ), vecPlayerScore[i].iTotalDamage );
  11282. winEvent->SetInt( UTIL_VarArgs( "player_%d_healing", iIndex ), vecPlayerScore[i].iTotalHealing );
  11283. winEvent->SetInt( UTIL_VarArgs( "player_%d_lifetime", iIndex ), vecPlayerScore[i].iTimeAlive );
  11284. winEvent->SetInt( UTIL_VarArgs( "player_%d_kills", iIndex ), vecPlayerScore[i].iKillingBlows );
  11285. iPlayersAdded++;
  11286. }
  11287. }
  11288. // Send the event
  11289. gameeventmanager->FireEvent( winEvent );
  11290. }
  11291. }
  11292. void CTFGameRules::SendPVEWinPanelInfo( void )
  11293. {
  11294. IGameEvent *winEvent = gameeventmanager->CreateEvent( "pve_win_panel" );
  11295. if ( winEvent )
  11296. {
  11297. winEvent->SetInt( "panel_style", WINPANEL_BASIC );
  11298. winEvent->SetInt( "winning_team", m_iWinningTeam );
  11299. winEvent->SetInt( "winreason", 0 );
  11300. // Send the event
  11301. gameeventmanager->FireEvent( winEvent );
  11302. }
  11303. /*CBroadcastRecipientFilter filter;
  11304. filter.MakeReliable();
  11305. UserMessageBegin( filter, "MVMAnnouncement" );
  11306. WRITE_CHAR( TF_MVM_ANNOUNCEMENT_WAVE_FAILED );
  11307. WRITE_CHAR( -1 );
  11308. MessageEnd();*/
  11309. }
  11310. void CTFGameRules::SendWinPanelInfo( bool bGameOver )
  11311. {
  11312. if ( IsInArenaMode() == true )
  11313. {
  11314. SendArenaWinPanelInfo();
  11315. return;
  11316. }
  11317. if ( IsPVEModeActive() )
  11318. {
  11319. SendPVEWinPanelInfo();
  11320. return;
  11321. }
  11322. IGameEvent *winEvent = gameeventmanager->CreateEvent( "teamplay_win_panel" );
  11323. if ( winEvent )
  11324. {
  11325. int iBlueScore = GetGlobalTeam( TF_TEAM_BLUE )->GetScore();
  11326. int iRedScore = GetGlobalTeam( TF_TEAM_RED )->GetScore();
  11327. int iBlueScorePrev = iBlueScore;
  11328. int iRedScorePrev = iRedScore;
  11329. bool bRoundComplete = m_bForceMapReset || ( IsGameUnderTimeLimit() && ( GetTimeLeft() <= 0 ) );
  11330. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  11331. bool bScoringPerCapture = ( pMaster ) ? ( pMaster->ShouldScorePerCapture() ) : false;
  11332. if ( m_nGameType == TF_GAMETYPE_CTF )
  11333. {
  11334. if ( tf_flag_caps_per_round.GetInt() == 0 )
  11335. {
  11336. bScoringPerCapture = true;
  11337. }
  11338. }
  11339. if ( bRoundComplete && !bScoringPerCapture && m_bUseAddScoreAnim )
  11340. {
  11341. // if this is a complete round, calc team scores prior to this win
  11342. switch ( m_iWinningTeam )
  11343. {
  11344. case TF_TEAM_BLUE:
  11345. iBlueScorePrev = ( iBlueScore - TEAMPLAY_ROUND_WIN_SCORE >= 0 ) ? ( iBlueScore - TEAMPLAY_ROUND_WIN_SCORE ) : 0;
  11346. break;
  11347. case TF_TEAM_RED:
  11348. iRedScorePrev = ( iRedScore - TEAMPLAY_ROUND_WIN_SCORE >= 0 ) ? ( iRedScore - TEAMPLAY_ROUND_WIN_SCORE ) : 0;
  11349. break;
  11350. case TEAM_UNASSIGNED:
  11351. break; // stalemate; nothing to do
  11352. }
  11353. }
  11354. winEvent->SetInt( "panel_style", WINPANEL_BASIC );
  11355. winEvent->SetInt( "winning_team", m_iWinningTeam );
  11356. winEvent->SetInt( "winreason", m_iWinReason );
  11357. winEvent->SetString( "cappers", ( m_iWinReason == WINREASON_ALL_POINTS_CAPTURED || m_iWinReason == WINREASON_FLAG_CAPTURE_LIMIT ) ?
  11358. m_szMostRecentCappers : "" );
  11359. winEvent->SetInt( "flagcaplimit", IsPasstimeMode() ? tf_passtime_scores_per_round.GetInt() : tf_flag_caps_per_round.GetInt() );
  11360. winEvent->SetInt( "blue_score", iBlueScore );
  11361. winEvent->SetInt( "red_score", iRedScore );
  11362. winEvent->SetInt( "blue_score_prev", iBlueScorePrev );
  11363. winEvent->SetInt( "red_score_prev", iRedScorePrev );
  11364. winEvent->SetInt( "round_complete", bRoundComplete );
  11365. CTFPlayerResource *pResource = dynamic_cast< CTFPlayerResource * >( g_pPlayerResource );
  11366. if ( !pResource )
  11367. return;
  11368. // Highest killstreak
  11369. int nMaxStreakPlayerIndex = 0;
  11370. int nMaxStreakCount = 0;
  11371. // determine the 3 players on winning team who scored the most points this round
  11372. // build a vector of players & round scores
  11373. CUtlVector<PlayerRoundScore_t> vecPlayerScore;
  11374. int iPlayerIndex;
  11375. for( iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  11376. {
  11377. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  11378. if ( !pTFPlayer || !pTFPlayer->IsConnected() )
  11379. continue;
  11380. // filter out spectators and, if not stalemate, all players not on winning team
  11381. int iPlayerTeam = pTFPlayer->GetTeamNumber();
  11382. if ( ( iPlayerTeam < FIRST_GAME_TEAM ) || ( m_iWinningTeam != TEAM_UNASSIGNED && ( m_iWinningTeam != iPlayerTeam ) ) )
  11383. continue;
  11384. int iRoundScore = 0, iTotalScore = 0;
  11385. PlayerStats_t *pStats = CTF_GameStats.FindPlayerStats( pTFPlayer );
  11386. if ( pStats )
  11387. {
  11388. iRoundScore = CalcPlayerScore( &pStats->statsCurrentRound, pTFPlayer );
  11389. iTotalScore = CalcPlayerScore( &pStats->statsAccumulated, pTFPlayer );
  11390. }
  11391. PlayerRoundScore_t &playerRoundScore = vecPlayerScore[vecPlayerScore.AddToTail()];
  11392. playerRoundScore.iRoundScore = iRoundScore;
  11393. playerRoundScore.iPlayerIndex = iPlayerIndex;
  11394. playerRoundScore.iTotalScore = iTotalScore;
  11395. // Highest killstreak?
  11396. PlayerStats_t *pPlayerStats = CTF_GameStats.FindPlayerStats( pTFPlayer );
  11397. if ( pPlayerStats )
  11398. {
  11399. int nMax = pPlayerStats->statsCurrentRound.m_iStat[TFSTAT_KILLSTREAK_MAX];
  11400. if ( nMax > nMaxStreakCount )
  11401. {
  11402. nMaxStreakCount = nMax;
  11403. nMaxStreakPlayerIndex = iPlayerIndex;
  11404. }
  11405. }
  11406. }
  11407. // sort the players by round score
  11408. vecPlayerScore.Sort( PlayerRoundScoreSortFunc );
  11409. // set the top (up to) 6 players by round score in the event data
  11410. int numPlayers = MIN( 6, vecPlayerScore.Count() );
  11411. for ( int i = 0; i < numPlayers; i++ )
  11412. {
  11413. // only include players who have non-zero points this round; if we get to a player with 0 round points, stop
  11414. if ( 0 == vecPlayerScore[i].iRoundScore )
  11415. break;
  11416. // set the player index and their round score in the event
  11417. char szPlayerIndexVal[64]="", szPlayerScoreVal[64]="";
  11418. Q_snprintf( szPlayerIndexVal, ARRAYSIZE( szPlayerIndexVal ), "player_%d", i+ 1 );
  11419. Q_snprintf( szPlayerScoreVal, ARRAYSIZE( szPlayerScoreVal ), "player_%d_points", i+ 1 );
  11420. winEvent->SetInt( szPlayerIndexVal, vecPlayerScore[i].iPlayerIndex );
  11421. winEvent->SetInt( szPlayerScoreVal, vecPlayerScore[i].iRoundScore );
  11422. }
  11423. winEvent->SetInt( "killstreak_player_1", nMaxStreakPlayerIndex );
  11424. winEvent->SetInt( "killstreak_player_1_count", nMaxStreakCount );
  11425. #ifdef TF_RAID_MODE
  11426. if ( !bRoundComplete && ( TEAM_UNASSIGNED != m_iWinningTeam ) && !IsRaidMode() )
  11427. #else
  11428. if ( !bRoundComplete && ( TEAM_UNASSIGNED != m_iWinningTeam ) )
  11429. #endif // TF_RAID_MODE
  11430. {
  11431. // is this our new payload race game mode?
  11432. if ( ( m_nGameType == TF_GAMETYPE_ESCORT ) && ( m_bMultipleTrains == true ) )
  11433. {
  11434. if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] && g_hControlPointMasters[0]->PlayingMiniRounds() )
  11435. {
  11436. int nRoundsRemaining = g_hControlPointMasters[0]->NumPlayableControlPointRounds();
  11437. if ( nRoundsRemaining > 0 )
  11438. {
  11439. winEvent->SetInt( "rounds_remaining", nRoundsRemaining );
  11440. }
  11441. }
  11442. }
  11443. else
  11444. {
  11445. // if this was not a full round ending, include how many mini-rounds remain for winning team to win
  11446. if ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] )
  11447. {
  11448. winEvent->SetInt( "rounds_remaining", g_hControlPointMasters[0]->CalcNumRoundsRemaining( m_iWinningTeam ) );
  11449. }
  11450. }
  11451. }
  11452. winEvent->SetBool( "game_over", bGameOver );
  11453. // Send the event
  11454. gameeventmanager->FireEvent( winEvent );
  11455. }
  11456. }
  11457. //-----------------------------------------------------------------------------
  11458. // Purpose: Sorts players by round score
  11459. //-----------------------------------------------------------------------------
  11460. int CTFGameRules::PlayerRoundScoreSortFunc( const PlayerRoundScore_t *pRoundScore1, const PlayerRoundScore_t *pRoundScore2 )
  11461. {
  11462. // sort first by round score
  11463. if ( pRoundScore1->iRoundScore != pRoundScore2->iRoundScore )
  11464. return pRoundScore2->iRoundScore - pRoundScore1->iRoundScore;
  11465. // if round scores are the same, sort next by total score
  11466. if ( pRoundScore1->iTotalScore != pRoundScore2->iTotalScore )
  11467. return pRoundScore2->iTotalScore - pRoundScore1->iTotalScore;
  11468. // if scores are the same, sort next by player index so we get deterministic sorting
  11469. return ( pRoundScore2->iPlayerIndex - pRoundScore1->iPlayerIndex );
  11470. }
  11471. //-----------------------------------------------------------------------------
  11472. // Purpose: Sorts players by arena stats
  11473. //-----------------------------------------------------------------------------
  11474. int CTFGameRules::PlayerArenaRoundScoreSortFunc( const PlayerArenaRoundScore_t *pRoundScore1, const PlayerArenaRoundScore_t *pRoundScore2 )
  11475. {
  11476. //Compare Total points first
  11477. //This gives us a rough estimate of performance
  11478. if ( pRoundScore1->iScore != pRoundScore2->iScore )
  11479. return pRoundScore2->iScore - pRoundScore1->iScore;
  11480. //Compare healing
  11481. if ( pRoundScore1->iTotalHealing != pRoundScore2->iTotalHealing )
  11482. return pRoundScore2->iTotalHealing - pRoundScore1->iTotalHealing;
  11483. //Compare damage
  11484. if ( pRoundScore1->iTotalDamage != pRoundScore2->iTotalDamage )
  11485. return pRoundScore2->iTotalDamage - pRoundScore1->iTotalDamage;
  11486. //Compare time alive
  11487. if ( pRoundScore1->iTimeAlive != pRoundScore2->iTimeAlive )
  11488. return pRoundScore2->iTimeAlive - pRoundScore1->iTimeAlive;
  11489. //Compare killing blows
  11490. if ( pRoundScore1->iKillingBlows != pRoundScore2->iKillingBlows )
  11491. return pRoundScore2->iKillingBlows - pRoundScore1->iKillingBlows;
  11492. // if scores are the same, sort next by player index so we get deterministic sorting
  11493. return ( pRoundScore2->iPlayerIndex - pRoundScore1->iPlayerIndex );
  11494. }
  11495. //-----------------------------------------------------------------------------
  11496. // Purpose: Called when the teamplay_round_win event is about to be sent, gives
  11497. // this method a chance to add more data to it
  11498. //-----------------------------------------------------------------------------
  11499. void CTFGameRules::FillOutTeamplayRoundWinEvent( IGameEvent *event )
  11500. {
  11501. event->SetInt( "flagcaplimit", IsPasstimeMode() ? tf_passtime_scores_per_round.GetInt() : tf_flag_caps_per_round.GetInt() );
  11502. // determine the losing team
  11503. int iLosingTeam;
  11504. switch( event->GetInt( "team" ) )
  11505. {
  11506. case TF_TEAM_RED:
  11507. iLosingTeam = TF_TEAM_BLUE;
  11508. break;
  11509. case TF_TEAM_BLUE:
  11510. iLosingTeam = TF_TEAM_RED;
  11511. break;
  11512. case TEAM_UNASSIGNED:
  11513. default:
  11514. iLosingTeam = TEAM_UNASSIGNED;
  11515. break;
  11516. }
  11517. // set the number of caps that team got any time during the round
  11518. event->SetInt( "losing_team_num_caps", m_iNumCaps[iLosingTeam] );
  11519. }
  11520. //-----------------------------------------------------------------------------
  11521. // Purpose:
  11522. //-----------------------------------------------------------------------------
  11523. void CTFGameRules::SetupSpawnPointsForRound( void )
  11524. {
  11525. if ( !g_hControlPointMasters.Count() || !g_hControlPointMasters[0] || !g_hControlPointMasters[0]->PlayingMiniRounds() )
  11526. return;
  11527. CTeamControlPointRound *pCurrentRound = g_hControlPointMasters[0]->GetCurrentRound();
  11528. if ( !pCurrentRound )
  11529. {
  11530. return;
  11531. }
  11532. // loop through the spawn points in the map and find which ones are associated with this round or the control points in this round
  11533. for ( int i=0; i<ITFTeamSpawnAutoList::AutoList().Count(); ++i )
  11534. {
  11535. CTFTeamSpawn *pTFSpawn = static_cast< CTFTeamSpawn* >( ITFTeamSpawnAutoList::AutoList()[i] );
  11536. CHandle<CTeamControlPoint> hControlPoint = pTFSpawn->GetControlPoint();
  11537. CHandle<CTeamControlPointRound> hRoundBlue = pTFSpawn->GetRoundBlueSpawn();
  11538. CHandle<CTeamControlPointRound> hRoundRed = pTFSpawn->GetRoundRedSpawn();
  11539. if ( hControlPoint && pCurrentRound->IsControlPointInRound( hControlPoint ) )
  11540. {
  11541. // this spawn is associated with one of our control points
  11542. pTFSpawn->SetDisabled( false );
  11543. pTFSpawn->ChangeTeam( hControlPoint->GetOwner() );
  11544. }
  11545. else if ( hRoundBlue && ( hRoundBlue == pCurrentRound ) )
  11546. {
  11547. pTFSpawn->SetDisabled( false );
  11548. pTFSpawn->ChangeTeam( TF_TEAM_BLUE );
  11549. }
  11550. else if ( hRoundRed && ( hRoundRed == pCurrentRound ) )
  11551. {
  11552. pTFSpawn->SetDisabled( false );
  11553. pTFSpawn->ChangeTeam( TF_TEAM_RED );
  11554. }
  11555. else
  11556. {
  11557. // this spawn isn't associated with this round or the control points in this round
  11558. pTFSpawn->SetDisabled( true );
  11559. }
  11560. }
  11561. }
  11562. //-----------------------------------------------------------------------------
  11563. // Purpose:
  11564. //-----------------------------------------------------------------------------
  11565. int CTFGameRules::SetCurrentRoundStateBitString( void )
  11566. {
  11567. m_iPrevRoundState = m_iCurrentRoundState;
  11568. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  11569. if ( !pMaster )
  11570. {
  11571. return 0;
  11572. }
  11573. int iState = 0;
  11574. for ( int i=0; i<pMaster->GetNumPoints(); i++ )
  11575. {
  11576. CTeamControlPoint *pPoint = pMaster->GetControlPoint( i );
  11577. if ( pPoint->GetOwner() == TF_TEAM_BLUE )
  11578. {
  11579. // Set index to 1 for the point being owned by blue
  11580. iState |= ( 1<<i );
  11581. }
  11582. }
  11583. m_iCurrentRoundState = iState;
  11584. return iState;
  11585. }
  11586. //-----------------------------------------------------------------------------
  11587. // Purpose:
  11588. //-----------------------------------------------------------------------------
  11589. void CTFGameRules::SetMiniRoundBitMask( int iMask )
  11590. {
  11591. m_iCurrentMiniRoundMask = iMask;
  11592. }
  11593. //-----------------------------------------------------------------------------
  11594. // Purpose:
  11595. //-----------------------------------------------------------------------------
  11596. bool CTFGameRules::IsFirstBloodAllowed( void )
  11597. {
  11598. // Already granted
  11599. if ( m_bArenaFirstBlood )
  11600. return false;
  11601. if ( IsInArenaMode() && tf_arena_first_blood.GetBool() )
  11602. return true;
  11603. if ( IsCompetitiveMode() && ( State_Get() == GR_STATE_RND_RUNNING ) )
  11604. {
  11605. if ( IsMatchTypeCompetitive() )
  11606. return true;
  11607. }
  11608. return false;
  11609. }
  11610. //-----------------------------------------------------------------------------
  11611. // Purpose: NULL pPlayer means show the panel to everyone
  11612. //-----------------------------------------------------------------------------
  11613. void CTFGameRules::ShowRoundInfoPanel( CTFPlayer *pPlayer /* = NULL */ )
  11614. {
  11615. KeyValues *data = new KeyValues( "data" );
  11616. if ( m_iCurrentRoundState < 0 )
  11617. {
  11618. // Haven't set up the round state yet
  11619. return;
  11620. }
  11621. // if prev and cur are equal, we are starting from a fresh round
  11622. if ( m_iPrevRoundState >= 0 && pPlayer == NULL ) // we have data about a previous state
  11623. {
  11624. data->SetInt( "prev", m_iPrevRoundState );
  11625. }
  11626. else
  11627. {
  11628. // don't send a delta if this is just to one player, they are joining mid-round
  11629. data->SetInt( "prev", m_iCurrentRoundState );
  11630. }
  11631. data->SetInt( "cur", m_iCurrentRoundState );
  11632. // get bitmask representing the current miniround
  11633. data->SetInt( "round", m_iCurrentMiniRoundMask );
  11634. if ( pPlayer )
  11635. {
  11636. pPlayer->ShowViewPortPanel( PANEL_ROUNDINFO, true, data );
  11637. }
  11638. else
  11639. {
  11640. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  11641. {
  11642. CTFPlayer *pTFPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11643. if ( pTFPlayer && pTFPlayer->IsReadyToPlay() )
  11644. {
  11645. pTFPlayer->ShowViewPortPanel( PANEL_ROUNDINFO, true, data );
  11646. }
  11647. }
  11648. }
  11649. }
  11650. //-----------------------------------------------------------------------------
  11651. // Purpose:
  11652. //-----------------------------------------------------------------------------
  11653. bool CTFGameRules::TimerMayExpire( void )
  11654. {
  11655. // Prevent timers expiring while control points are contested
  11656. int iNumControlPoints = ObjectiveResource()->GetNumControlPoints();
  11657. for ( int iPoint = 0; iPoint < iNumControlPoints; iPoint ++ )
  11658. {
  11659. if ( ObjectiveResource()->GetCappingTeam(iPoint) )
  11660. {
  11661. m_flCapInProgressBuffer = gpGlobals->curtime + 0.1f;
  11662. return false;
  11663. }
  11664. }
  11665. // This delay prevents an order-of-operations issue with caps that
  11666. // fire an AddTime output, and round timers' OnFinished output
  11667. if ( m_flCapInProgressBuffer >= gpGlobals->curtime )
  11668. return false;
  11669. if ( GetOvertimeAllowedForCTF() )
  11670. {
  11671. // Prevent timers expiring while flags are stolen/dropped
  11672. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  11673. {
  11674. CCaptureFlag *pFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
  11675. if ( !pFlag->IsDisabled() && !pFlag->IsHome() )
  11676. return false;
  11677. }
  11678. }
  11679. for ( int i = 0 ; i < m_CPTimerEnts.Count() ; i++ )
  11680. {
  11681. CCPTimerLogic *pTimer = m_CPTimerEnts[i];
  11682. if ( pTimer )
  11683. {
  11684. if ( pTimer->TimerMayExpire() == false )
  11685. return false;
  11686. }
  11687. }
  11688. #ifdef TF_RAID_MODE
  11689. if ( IsRaidMode() && IsBossBattleMode() && tf_raid_allow_overtime.GetBool() )
  11690. {
  11691. CUtlVector< CTFPlayer * > alivePlayerVector;
  11692. CollectPlayers( &alivePlayerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS );
  11693. // if anyone is alive, go into overtime
  11694. if ( alivePlayerVector.Count() > 0 )
  11695. {
  11696. return false;
  11697. }
  11698. }
  11699. #endif
  11700. return BaseClass::TimerMayExpire();
  11701. }
  11702. //-----------------------------------------------------------------------------
  11703. // Purpose:
  11704. //-----------------------------------------------------------------------------
  11705. bool CTFGameRules::BHavePlayers( void )
  11706. {
  11707. CMatchInfo *pInfo = GTFGCClientSystem()->GetMatch();
  11708. if ( pInfo )
  11709. {
  11710. // When we have a match, we start reporting we're active as soon as the first person loads, and never stop.
  11711. return pInfo->m_bFirstPersonActive;
  11712. }
  11713. if ( IsInArenaMode() )
  11714. {
  11715. // At least two in queue, we're always able to play
  11716. if ( m_hArenaPlayerQueue.Count() >= 2 )
  11717. return true;
  11718. // Otherwise, return false if nobody is actually on a team, regardless of players ready-to-play state.
  11719. if ( GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() == 0 || GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers() == 0 )
  11720. return false;
  11721. // Otherwise, fall through to base logic (e.g. 1v0 but already running)
  11722. }
  11723. return BaseClass::BHavePlayers();
  11724. }
  11725. int SortPlayerSpectatorQueue( CTFPlayer* const *p1, CTFPlayer* const *p2 )
  11726. {
  11727. float flTime1 = (*p1)->GetTeamJoinTime();
  11728. float flTime2 = (*p2)->GetTeamJoinTime();
  11729. if ( flTime1 == flTime2 )
  11730. {
  11731. flTime1 = (*p1)->GetConnectionTime();
  11732. flTime2 = (*p2)->GetConnectionTime();
  11733. if ( flTime1 < flTime2 )
  11734. return -1;
  11735. }
  11736. else
  11737. {
  11738. if ( flTime1 > flTime2 )
  11739. return -1;
  11740. }
  11741. if ( flTime1 == flTime2 )
  11742. return 0;
  11743. return 1;
  11744. }
  11745. int SortPlayersScoreBased( CTFPlayer* const *p1, CTFPlayer* const *p2 )
  11746. {
  11747. CTFPlayerResource *pResource = dynamic_cast<CTFPlayerResource *>( g_pPlayerResource );
  11748. if ( pResource )
  11749. {
  11750. int nScore2 = pResource->GetTotalScore( ( *p2 )->entindex() );
  11751. int nScore1 = pResource->GetTotalScore( ( *p1 )->entindex() );
  11752. // check the priority
  11753. if ( nScore2 > nScore1 )
  11754. {
  11755. return 1;
  11756. }
  11757. }
  11758. return -1;
  11759. }
  11760. // sort function for the list of players that we're going to use to scramble the teams
  11761. int ScramblePlayersSort( CTFPlayer* const *p1, CTFPlayer* const *p2 )
  11762. {
  11763. CTFPlayerResource *pResource = dynamic_cast< CTFPlayerResource * >( g_pPlayerResource );
  11764. if ( pResource )
  11765. {
  11766. int nScore2 = pResource->GetTotalScore( (*p2)->entindex() );
  11767. int nScore1 = pResource->GetTotalScore( (*p1)->entindex() );
  11768. // check the priority
  11769. if ( nScore2 > nScore1 )
  11770. {
  11771. return 1;
  11772. }
  11773. }
  11774. return -1;
  11775. }
  11776. //-----------------------------------------------------------------------------
  11777. // Purpose:
  11778. //-----------------------------------------------------------------------------
  11779. void CTFGameRules::Arena_ClientDisconnect( const char *playername )
  11780. {
  11781. if ( IsInArenaMode() == false )
  11782. return;
  11783. if ( IsInWaitingForPlayers() == true )
  11784. return;
  11785. if ( m_iRoundState != GR_STATE_PREROUND )
  11786. return;
  11787. if ( IsInTournamentMode() == true )
  11788. return;
  11789. int iLight, iHeavy;
  11790. if ( AreTeamsUnbalanced( iHeavy, iLight ) == true )
  11791. {
  11792. CTeam *pTeamHeavy = GetGlobalTeam( iHeavy );
  11793. CTeam *pTeamLight = GetGlobalTeam( iLight );
  11794. if ( pTeamHeavy == NULL || pTeamLight == NULL )
  11795. return;
  11796. int iPlayersNeeded = pTeamHeavy->GetNumPlayers() - pTeamLight->GetNumPlayers();
  11797. if ( m_hArenaPlayerQueue.Count() == 0 )
  11798. return;
  11799. for ( int iPlayers = 0; iPlayers < iPlayersNeeded; iPlayers++ )
  11800. {
  11801. CTFPlayer *pPlayer = m_hArenaPlayerQueue[iPlayers];
  11802. if ( pPlayer && pPlayer->IsHLTV() == false && pPlayer->IsReplay() == false )
  11803. {
  11804. pPlayer->ForceChangeTeam( TF_TEAM_AUTOASSIGN );
  11805. UTIL_ClientPrintAll( HUD_PRINTTALK, "#TF_Arena_ClientDisconnect", pPlayer->GetPlayerName(), GetGlobalTeam( pPlayer->GetTeamNumber() )->GetName(), playername );
  11806. if ( pPlayer->DidPlayerJustPlay() == false )
  11807. {
  11808. pPlayer->MarkTeamJoinTime();
  11809. }
  11810. m_hArenaPlayerQueue.FindAndRemove( pPlayer );
  11811. pPlayer->PlayerJustPlayed( false );
  11812. }
  11813. }
  11814. }
  11815. }
  11816. //-----------------------------------------------------------------------------
  11817. // Purpose:
  11818. //-----------------------------------------------------------------------------
  11819. void CTFGameRules::Arena_ResetLosersScore( bool bResetAll )
  11820. {
  11821. //Winner gets to keep their score
  11822. for ( int i = 0; i < GetNumberOfTeams(); i++ )
  11823. {
  11824. if ( ( i != GetWinningTeam() && GetWinningTeam() > LAST_SHARED_TEAM ) || bResetAll == true )
  11825. {
  11826. GetGlobalTeam( i )->ResetScores();
  11827. }
  11828. }
  11829. }
  11830. //-----------------------------------------------------------------------------
  11831. // Purpose:
  11832. //-----------------------------------------------------------------------------
  11833. void CTFGameRules::Arena_PrepareNewPlayerQueue( bool bResetAll )
  11834. {
  11835. CUtlVector<CTFPlayer*> pTempPlayerQueue;
  11836. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  11837. {
  11838. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11839. if ( pPlayer && pPlayer->IsHLTV() == false && pPlayer->IsReplay() == false )
  11840. {
  11841. if ( ( GetWinningTeam() > LAST_SHARED_TEAM && pPlayer->GetTeamNumber() != GetWinningTeam() && pPlayer->IsReadyToPlay() ) || bResetAll == true )
  11842. {
  11843. pTempPlayerQueue.AddToTail( pPlayer );
  11844. }
  11845. }
  11846. }
  11847. //Sort the players going into spectator
  11848. //Players that have been longer on the server should go in front of newer players
  11849. //We have to do this since player slots are reused for new clients, so looping through players
  11850. //using their entindex doesn't work.
  11851. if ( bResetAll == true )
  11852. {
  11853. pTempPlayerQueue.Sort( ScramblePlayersSort );
  11854. }
  11855. else
  11856. {
  11857. pTempPlayerQueue.Sort( SortPlayerSpectatorQueue );
  11858. }
  11859. for ( int i = 0; i < pTempPlayerQueue.Count(); i++ )
  11860. {
  11861. CTFPlayer *pPlayer = pTempPlayerQueue[i];
  11862. if ( pPlayer && pPlayer->IsReadyToPlay() )
  11863. {
  11864. //Changing into Spectator Team puts the player at the end of the queue
  11865. //Use ForceChangeTeam if you want to move them to the FRONT of the queue
  11866. pPlayer->ChangeTeam( TEAM_SPECTATOR );
  11867. pPlayer->PlayerJustPlayed( true );
  11868. }
  11869. }
  11870. }
  11871. #define TF_ARENA_TEAM_COUNT 3
  11872. //-----------------------------------------------------------------------------
  11873. // Purpose:
  11874. //-----------------------------------------------------------------------------
  11875. int CTFGameRules::Arena_PlayersNeededForMatch( void )
  11876. {
  11877. int iMaxPlayers = gpGlobals->maxClients;
  11878. if ( HLTVDirector()->IsActive() )
  11879. {
  11880. iMaxPlayers -= 1;
  11881. }
  11882. int iTeamSize;
  11883. if ( tf_arena_override_team_size.GetInt() > 0 )
  11884. iTeamSize = tf_arena_override_team_size.GetInt();
  11885. else
  11886. iTeamSize = floor( ( (float)iMaxPlayers / TF_ARENA_TEAM_COUNT ) + 0.5f );
  11887. int iPlayersNeeded = iTeamSize * 2;
  11888. int iDesiredTeamSize = iTeamSize;
  11889. int iPlayersInWinningTeam = 0;
  11890. bool bRebalanceWinners = false;
  11891. int iPlayerNumber = 0;
  11892. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  11893. {
  11894. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11895. if ( pPlayer && pPlayer->IsReadyToPlay() )
  11896. {
  11897. iPlayerNumber++;
  11898. }
  11899. }
  11900. int iBalancedTeamSize = floor( ((m_hArenaPlayerQueue.Count() + iPlayerNumber ) * 0.5f ) );
  11901. iPlayersNeeded = (iBalancedTeamSize - iPlayerNumber) + iBalancedTeamSize;
  11902. if ( (iPlayersNeeded + iPlayerNumber) > iDesiredTeamSize*2 )
  11903. {
  11904. iPlayersNeeded = (iDesiredTeamSize*2) - iPlayerNumber;
  11905. if ( iPlayersNeeded < 0 )
  11906. {
  11907. iPlayersNeeded = 0;
  11908. }
  11909. }
  11910. // If the last round was won by a team, then let's figure out how many players we need.
  11911. // Also, if the winning team has more players than are available, then let's have one of them switch teams.
  11912. if ( GetWinningTeam() > LAST_SHARED_TEAM )
  11913. {
  11914. iPlayersInWinningTeam = GetGlobalTFTeam( GetWinningTeam() )->GetNumPlayers();
  11915. if ( iPlayersInWinningTeam > iTeamSize )
  11916. {
  11917. bRebalanceWinners = true;
  11918. iDesiredTeamSize = iTeamSize;
  11919. }
  11920. else if ( iPlayersInWinningTeam > iBalancedTeamSize )
  11921. {
  11922. bRebalanceWinners = true;
  11923. iDesiredTeamSize = iBalancedTeamSize;
  11924. }
  11925. }
  11926. // Msg( "iPlayerNumber: %d - InQueue: %d - iPlayersInWinningTeam: %d - iDesiredTeamSize: %d - iBalancedTeamSize: %d - iPlayersNeeded: %d - bRebalanceWinners %d\n", iPlayerNumber, m_hArenaPlayerQueue.Count(), iPlayersInWinningTeam, iDesiredTeamSize, iBalancedTeamSize, iPlayersNeeded, bRebalanceWinners );
  11927. if ( bRebalanceWinners == true )
  11928. {
  11929. while ( iPlayersInWinningTeam > iDesiredTeamSize )
  11930. {
  11931. CTFPlayer *pBalancedWinner = NULL;
  11932. float flShortestTeamJoinTime = 9999.9f;
  11933. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  11934. {
  11935. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11936. if ( pPlayer && pPlayer->GetTeamNumber() == GetWinningTeam() && pPlayer->IsHLTV() == false && pPlayer->IsReplay() == false )
  11937. {
  11938. if ( bRebalanceWinners == true )
  11939. {
  11940. //Find the newest guy that joined this team and flag him.
  11941. if ( (gpGlobals->curtime - pPlayer->GetTeamJoinTime()) < flShortestTeamJoinTime )
  11942. {
  11943. flShortestTeamJoinTime = (gpGlobals->curtime - pPlayer->GetTeamJoinTime());
  11944. pBalancedWinner = pPlayer;
  11945. }
  11946. }
  11947. }
  11948. }
  11949. if ( pBalancedWinner )
  11950. {
  11951. pBalancedWinner->ForceChangeTeam( TEAM_SPECTATOR );
  11952. pBalancedWinner->MarkTeamJoinTime();
  11953. if ( iPlayersNeeded < iDesiredTeamSize )
  11954. {
  11955. iPlayersNeeded++;
  11956. }
  11957. IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_teambalanced_player" );
  11958. if ( event )
  11959. {
  11960. event->SetInt( "player", pBalancedWinner->entindex() );
  11961. event->SetInt( "team", GetWinningTeam() == TF_TEAM_BLUE ? TF_TEAM_RED : TF_TEAM_BLUE );
  11962. gameeventmanager->FireEvent( event );
  11963. }
  11964. // tell people that we've switched this player
  11965. UTIL_ClientPrintAll( HUD_PRINTTALK, "#game_player_was_team_balanced", pBalancedWinner->GetPlayerName() );
  11966. }
  11967. iPlayersInWinningTeam = GetGlobalTFTeam( GetWinningTeam() )->GetNumPlayers();
  11968. }
  11969. }
  11970. return iPlayersNeeded;
  11971. }
  11972. //-----------------------------------------------------------------------------
  11973. // Purpose:
  11974. //-----------------------------------------------------------------------------
  11975. void CTFGameRules::Arena_CleanupPlayerQueue( void )
  11976. {
  11977. //One more loop to remove players that are currently playing from the queue
  11978. //And to mark everyone as not having just played.
  11979. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  11980. {
  11981. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  11982. if ( pPlayer && pPlayer->IsHLTV() == false && pPlayer->IsReplay() == false )
  11983. {
  11984. if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
  11985. {
  11986. m_hArenaPlayerQueue.FindAndRemove( pPlayer );
  11987. }
  11988. pPlayer->PlayerJustPlayed( false );
  11989. }
  11990. }
  11991. }
  11992. //-----------------------------------------------------------------------------
  11993. // Purpose:
  11994. //-----------------------------------------------------------------------------
  11995. void CTFGameRules::Arena_RunTeamLogic( void )
  11996. {
  11997. if ( IsInArenaMode() == false )
  11998. return;
  11999. if ( IsInWaitingForPlayers() == true )
  12000. return;
  12001. bool bGameNotReady = !BHavePlayers();
  12002. bool bStreaksReached = false;
  12003. if ( tf_arena_use_queue.GetBool() == false )
  12004. {
  12005. if ( bGameNotReady == true || GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() == 0 || GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers() == 0 )
  12006. {
  12007. State_Transition( GR_STATE_PREGAME );
  12008. }
  12009. return;
  12010. }
  12011. if ( tf_arena_max_streak.GetInt() > 0 )
  12012. {
  12013. if ( GetWinningTeam() != TEAM_UNASSIGNED )
  12014. {
  12015. if ( GetGlobalTFTeam( GetWinningTeam() )->GetScore() >= tf_arena_max_streak.GetInt() )
  12016. {
  12017. bStreaksReached = true;
  12018. IGameEvent *event = gameeventmanager->CreateEvent( "arena_match_maxstreak" );
  12019. if ( event )
  12020. {
  12021. event->SetInt( "team", GetWinningTeam() );
  12022. event->SetInt( "streak", tf_arena_max_streak.GetInt() );
  12023. gameeventmanager->FireEvent( event );
  12024. }
  12025. BroadcastSound( 255, "Announcer.AM_TeamScrambleRandom" );
  12026. m_iWinningTeam = TEAM_UNASSIGNED;
  12027. }
  12028. }
  12029. }
  12030. if ( IsInTournamentMode() == false )
  12031. {
  12032. Arena_ResetLosersScore( bGameNotReady || bStreaksReached );
  12033. }
  12034. Arena_PrepareNewPlayerQueue( bGameNotReady || bStreaksReached );
  12035. if ( bGameNotReady == true )
  12036. {
  12037. State_Transition( GR_STATE_PREGAME );
  12038. return;
  12039. }
  12040. int iPlayersNeeded = Arena_PlayersNeededForMatch();
  12041. //Let's add people to teams
  12042. //But only do this if there's people in the actual game and teams are unbalanced
  12043. //(which they should be since winners are in and everyone else is spectating)
  12044. int iLight, iHeavy;
  12045. if ( AreTeamsUnbalanced( iHeavy, iLight ) == true && iPlayersNeeded > 0 )
  12046. {
  12047. if ( iPlayersNeeded > m_hArenaPlayerQueue.Count() )
  12048. {
  12049. iPlayersNeeded = m_hArenaPlayerQueue.Count();
  12050. }
  12051. // Msg( "iTeamSize: %d\n", iTeamSize );
  12052. int iTeam = GetWinningTeam();
  12053. int iSwitch = floor( ((GetGlobalTFTeam( GetWinningTeam() )->GetNumPlayers() + iPlayersNeeded) * 0.5f ) - GetGlobalTFTeam( GetWinningTeam() )->GetNumPlayers() );
  12054. if ( GetWinningTeam() == TEAM_UNASSIGNED )
  12055. {
  12056. iTeam = TF_TEAM_AUTOASSIGN;
  12057. }
  12058. //Move people in the queue into a team.
  12059. for ( int iPlayers = 0; iPlayers < iPlayersNeeded; iPlayers++ )
  12060. {
  12061. CTFPlayer *pPlayer = m_hArenaPlayerQueue[iPlayers];
  12062. if ( iPlayers >= iSwitch )
  12063. {
  12064. iTeam = TF_TEAM_AUTOASSIGN;
  12065. }
  12066. if ( pPlayer )
  12067. {
  12068. pPlayer->ForceChangeTeam( iTeam );
  12069. // Msg( "Moving Player to game: %s - team: %d\n", pPlayer->GetPlayerName(), pPlayer->GetTeamNumber() );
  12070. if ( pPlayer->DidPlayerJustPlay() == false )
  12071. {
  12072. pPlayer->MarkTeamJoinTime();
  12073. }
  12074. }
  12075. }
  12076. }
  12077. // show the class composition panel
  12078. m_flSendNotificationTime = gpGlobals->curtime + 1.0f;
  12079. Arena_CleanupPlayerQueue();
  12080. Arena_NotifyTeamSizeChange();
  12081. }
  12082. //-----------------------------------------------------------------------------
  12083. // Purpose:
  12084. //-----------------------------------------------------------------------------
  12085. void CTFGameRules::Arena_NotifyTeamSizeChange( void )
  12086. {
  12087. int iTeamSize = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers();
  12088. if ( iTeamSize == m_iPreviousTeamSize )
  12089. return;
  12090. if ( m_iPreviousTeamSize == 0 )
  12091. {
  12092. m_iPreviousTeamSize = iTeamSize;
  12093. return;
  12094. }
  12095. if ( m_iPreviousTeamSize > iTeamSize )
  12096. {
  12097. UTIL_ClientPrintAll( HUD_PRINTTALK, "#TF_Arena_TeamSizeDecreased", UTIL_VarArgs( "%d", iTeamSize ) );
  12098. }
  12099. else
  12100. {
  12101. UTIL_ClientPrintAll( HUD_PRINTTALK, "#TF_Arena_TeamSizeIncreased", UTIL_VarArgs( "%d", iTeamSize ) );
  12102. }
  12103. m_iPreviousTeamSize = iTeamSize;
  12104. }
  12105. //-----------------------------------------------------------------------------
  12106. // Purpose:
  12107. //-----------------------------------------------------------------------------
  12108. void CTFGameRules::Arena_SendPlayerNotifications( void )
  12109. {
  12110. int iTeamPlayers = GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers();
  12111. int iNumPlayers = 0;
  12112. m_flSendNotificationTime = 0.0f;
  12113. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  12114. {
  12115. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  12116. if ( pPlayer && pPlayer->IsHLTV() == false && pPlayer->IsReplay() == false && pPlayer->GetDesiredPlayerClassIndex() > TF_CLASS_UNDEFINED )
  12117. {
  12118. iNumPlayers++;
  12119. if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR && pPlayer->GetPreviousTeam() != TEAM_UNASSIGNED )
  12120. {
  12121. CSingleUserRecipientFilter filter( pPlayer );
  12122. UserMessageBegin( filter, "HudArenaNotify" );
  12123. WRITE_BYTE( pPlayer->entindex() );
  12124. WRITE_BYTE( TF_ARENA_NOTIFICATION_SITOUT );
  12125. MessageEnd();
  12126. }
  12127. }
  12128. }
  12129. if ( iTeamPlayers == iNumPlayers )
  12130. return;
  12131. int iExtras = iNumPlayers - iTeamPlayers;
  12132. for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; iTeam++ )
  12133. {
  12134. CUtlVector<CTFPlayer*> pTempPlayerQueue;
  12135. CTFTeam *pTeam = GetGlobalTFTeam( iTeam );
  12136. if ( pTeam )
  12137. {
  12138. for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
  12139. {
  12140. CTFPlayer *pPlayer = ToTFPlayer( pTeam->GetPlayer( iPlayer ) );
  12141. if ( pPlayer && pPlayer->IsHLTV() == false && pPlayer->IsReplay() == false )
  12142. {
  12143. pTempPlayerQueue.AddToTail( pPlayer );
  12144. }
  12145. }
  12146. }
  12147. pTempPlayerQueue.Sort( SortPlayerSpectatorQueue );
  12148. for ( int i = pTempPlayerQueue.Count(); --i >= 0; )
  12149. {
  12150. if ( pTempPlayerQueue.Count() - i > iExtras )
  12151. continue;
  12152. CTFPlayer *pPlayer = pTempPlayerQueue[i];
  12153. CSingleUserRecipientFilter filter( pPlayer );
  12154. UserMessageBegin( filter, "HudArenaNotify" );
  12155. WRITE_BYTE( pPlayer->entindex() );
  12156. WRITE_BYTE( TF_ARENA_NOTIFICATION_CAREFUL );
  12157. MessageEnd();
  12158. }
  12159. }
  12160. }
  12161. //-----------------------------------------------------------------------------
  12162. // Purpose: Handle maps that may be in our map cycle/etc changing name or availability
  12163. //-----------------------------------------------------------------------------
  12164. #ifdef GAME_DLL
  12165. void CTFGameRules::OnWorkshopMapUpdated( PublishedFileId_t nUpdatedWorkshopID )
  12166. {
  12167. // Check if this map is in the mapcycle under a different name, reload mapcycle if so. We want the up-to-date names
  12168. // in the map cycle as it is used for user-facing things such as votes.
  12169. CTFMapsWorkshop *pWorkshop = TFMapsWorkshop();
  12170. if ( pWorkshop )
  12171. {
  12172. FOR_EACH_VEC( m_MapList, i )
  12173. {
  12174. // Check if this represents a workshop map
  12175. PublishedFileId_t nWorkshopID = pWorkshop->MapIDFromName( m_MapList[ i ] );
  12176. if ( nWorkshopID == nUpdatedWorkshopID )
  12177. {
  12178. CUtlString newName;
  12179. if ( pWorkshop->GetMapName( nWorkshopID, newName ) == CTFMapsWorkshop::eName_Canon &&
  12180. newName != m_MapList[ i ] )
  12181. {
  12182. // We can't just fixup the name here, as the primary mapcycle is also mirrored to a string
  12183. // table. This queues a reload, which will check for workshop names at that point.
  12184. m_bMapCycleNeedsUpdate = true;
  12185. break;
  12186. }
  12187. }
  12188. }
  12189. }
  12190. }
  12191. #endif
  12192. //-----------------------------------------------------------------------------
  12193. // Purpose: Hook new map cycle file loads
  12194. //-----------------------------------------------------------------------------
  12195. void CTFGameRules::LoadMapCycleFile( void )
  12196. {
  12197. BaseClass::LoadMapCycleFile();
  12198. #ifdef GAME_DLL
  12199. // The underlying LoadMapCycleFileIntoVector fixes up workshop names, but for loading the primary map cycle file, we
  12200. // also want to tell the workshop to track them. See also: TFGameRules::OnWorkshopMapChanged
  12201. TrackWorkshopMapsInMapCycle();
  12202. #endif
  12203. }
  12204. //-----------------------------------------------------------------------------
  12205. // Purpose: Hook new map cycle file loads
  12206. //-----------------------------------------------------------------------------
  12207. void CTFGameRules::TrackWorkshopMapsInMapCycle( void )
  12208. {
  12209. CTFMapsWorkshop *pWorkshop = TFMapsWorkshop();
  12210. if ( pWorkshop )
  12211. {
  12212. unsigned int nAddedMaps = 0;
  12213. unsigned int nWorkshopMaps = 0;
  12214. FOR_EACH_VEC( m_MapList, i )
  12215. {
  12216. // Check if this represents a workshop map
  12217. PublishedFileId_t nWorkshopID = pWorkshop->MapIDFromName( m_MapList[ i ] );
  12218. if ( nWorkshopID != k_PublishedFileIdInvalid )
  12219. {
  12220. nWorkshopMaps++;
  12221. // Track it if we're not
  12222. if ( pWorkshop->AddMap( nWorkshopID ) )
  12223. {
  12224. nAddedMaps++;
  12225. }
  12226. }
  12227. }
  12228. if ( nAddedMaps )
  12229. {
  12230. Msg( "Tracking %u new workshop maps from map cycle (%u already tracked)\n", nAddedMaps, nWorkshopMaps - nAddedMaps );
  12231. }
  12232. }
  12233. }
  12234. //-----------------------------------------------------------------------------
  12235. // Purpose: Hook new map cycle file loads
  12236. //-----------------------------------------------------------------------------
  12237. void CTFGameRules::LoadMapCycleFileIntoVector( const char *pszMapCycleFile, CUtlVector<char *> &mapList )
  12238. {
  12239. BaseClass::LoadMapCycleFileIntoVector( pszMapCycleFile, mapList );
  12240. #ifdef GAME_DLL
  12241. // Fixup workshop map names if known. E.g. workshop/12345 -> workshop/cp_foo.ugc12345
  12242. CTFMapsWorkshop *pWorkshop = TFMapsWorkshop();
  12243. if ( pWorkshop )
  12244. {
  12245. FOR_EACH_VEC( mapList, i )
  12246. {
  12247. // Check if this represents a workshop map
  12248. PublishedFileId_t nWorkshopID = pWorkshop->MapIDFromName( mapList[ i ] );
  12249. if ( nWorkshopID != k_PublishedFileIdInvalid )
  12250. {
  12251. // Workshop map, update name
  12252. CUtlString newName;
  12253. pWorkshop->GetMapName( nWorkshopID, newName );
  12254. if ( newName.Length() )
  12255. {
  12256. // Alloc replacement
  12257. size_t nNewSize = newName.Length() + 1;
  12258. char *pNew = new char[ nNewSize ];
  12259. V_strncpy( pNew, newName.Get(), nNewSize );
  12260. // Replace
  12261. delete [] mapList[ i ];
  12262. mapList[ i ] = pNew;
  12263. }
  12264. }
  12265. }
  12266. }
  12267. #endif
  12268. }
  12269. //-----------------------------------------------------------------------------
  12270. // Purpose: Server-side vote creation
  12271. //-----------------------------------------------------------------------------
  12272. void CTFGameRules::ManageServerSideVoteCreation( void )
  12273. {
  12274. if ( gpGlobals->curtime < m_flVoteCheckThrottle )
  12275. return;
  12276. if ( IsInTournamentMode() )
  12277. return;
  12278. if ( IsInArenaMode() )
  12279. return;
  12280. if ( IsInWaitingForPlayers() )
  12281. return;
  12282. if ( m_bInSetup )
  12283. return;
  12284. if ( IsInTraining() )
  12285. return;
  12286. if ( IsInItemTestingMode() )
  12287. return;
  12288. if ( m_MapList.Count() < 2 )
  12289. return;
  12290. // Ask players which map they would prefer to play next, based
  12291. // on "n" lowest playtime from server stats
  12292. ConVarRef sv_vote_issue_nextlevel_allowed( "sv_vote_issue_nextlevel_allowed" );
  12293. ConVarRef sv_vote_issue_nextlevel_choicesmode( "sv_vote_issue_nextlevel_choicesmode" );
  12294. if ( sv_vote_issue_nextlevel_allowed.GetBool() && sv_vote_issue_nextlevel_choicesmode.GetBool() )
  12295. {
  12296. // Don't do this if we already have a nextlevel set
  12297. if ( nextlevel.GetString() && *nextlevel.GetString() )
  12298. return;
  12299. if ( !m_bServerVoteOnReset && !m_bVoteCalled )
  12300. {
  12301. // If we have any round or win limit, ignore time
  12302. if ( mp_winlimit.GetInt() || mp_maxrounds.GetInt() )
  12303. {
  12304. int nBlueScore = TFTeamMgr()->GetTeam( TF_TEAM_BLUE )->GetScore();
  12305. int nRedScore = TFTeamMgr()->GetTeam( TF_TEAM_RED)->GetScore();
  12306. int nWinLimit = mp_winlimit.GetInt();
  12307. if ( ( nWinLimit - nBlueScore ) == 1 || ( nWinLimit - nRedScore ) == 1 )
  12308. {
  12309. m_bServerVoteOnReset = true;
  12310. }
  12311. int nRoundsPlayed = GetRoundsPlayed();
  12312. if ( ( mp_maxrounds.GetInt() - nRoundsPlayed ) == 1 )
  12313. {
  12314. m_bServerVoteOnReset = true;
  12315. }
  12316. }
  12317. else if ( mp_timelimit.GetInt() > 0 )
  12318. {
  12319. int nTimeLeft = GetTimeLeft();
  12320. if ( nTimeLeft <= 120 && !m_bServerVoteOnReset )
  12321. {
  12322. if ( g_voteController )
  12323. {
  12324. g_voteController->CreateVote( DEDICATED_SERVER, "nextlevel", "" );
  12325. }
  12326. m_bVoteCalled = true;
  12327. }
  12328. }
  12329. }
  12330. }
  12331. m_flVoteCheckThrottle = gpGlobals->curtime + 0.5f;
  12332. }
  12333. //-----------------------------------------------------------------------------
  12334. // Purpose: Figures out how much money to put in a custom currency pack drop
  12335. //-----------------------------------------------------------------------------
  12336. int CTFGameRules::CalculateCurrencyAmount_CustomPack( int nAmount )
  12337. {
  12338. // Entities and events that specify a custom currency value should pass in the amount
  12339. // they're worth, and we figure out if there's enough currency to generate a pack.
  12340. // If the amount passed in isn't enough to generate a pack, we store it in an accumulator.
  12341. int nMinDrop = kMVM_CurrencyPackMinSize;
  12342. if ( nMinDrop > 1 )
  12343. {
  12344. // If we're on the last spawn, drop everything
  12345. if ( TFObjectiveResource()->GetMannVsMachineWaveEnemyCount() == 1 )
  12346. {
  12347. nMinDrop = m_nCurrencyAccumulator + nAmount;
  12348. m_nCurrencyAccumulator = 0;
  12349. return nMinDrop;
  12350. }
  12351. // If we're passing in a value above mindrop, just drop it
  12352. if ( nAmount >= nMinDrop )
  12353. return nAmount;
  12354. // Accumulate currency if we're getting values below nMinDrop
  12355. m_nCurrencyAccumulator += nAmount;
  12356. if ( m_nCurrencyAccumulator >= nMinDrop )
  12357. {
  12358. m_nCurrencyAccumulator -= nMinDrop;
  12359. //DevMsg( "*MIN REACHED* -- %d left\n", m_nCurrencyAccumulator );
  12360. return nMinDrop;
  12361. }
  12362. else
  12363. {
  12364. // We don't have enough yet, drop nothing
  12365. //DevMsg( "*STORE* -- %d stored\n", m_nCurrencyAccumulator );
  12366. return 0;
  12367. }
  12368. }
  12369. else
  12370. {
  12371. // We don't have a PopManager - return the amount passed in
  12372. return nAmount;
  12373. }
  12374. }
  12375. //-----------------------------------------------------------------------------
  12376. // Purpose: Figures out how much to give for pre-definied events or types
  12377. //-----------------------------------------------------------------------------
  12378. int CTFGameRules::CalculateCurrencyAmount_ByType( CurrencyRewards_t nType )
  12379. {
  12380. // CUSTOM values are usually determined by CalculateCurrencyAmount_CustomPack() and set via CCurrencyPack::SetValue()
  12381. Assert ( nType != TF_CURRENCY_PACK_CUSTOM );
  12382. int nAmount = 0;
  12383. switch ( nType )
  12384. {
  12385. case TF_CURRENCY_KILLED_PLAYER:
  12386. nAmount = 40;
  12387. break;
  12388. case TF_CURRENCY_KILLED_OBJECT:
  12389. nAmount = 40;
  12390. break;
  12391. case TF_CURRENCY_ASSISTED_PLAYER:
  12392. nAmount = 20;
  12393. break;
  12394. case TF_CURRENCY_BONUS_POINTS:
  12395. nAmount = 1;
  12396. break;
  12397. case TF_CURRENCY_CAPTURED_OBJECTIVE:
  12398. nAmount = 100;
  12399. break;
  12400. case TF_CURRENCY_ESCORT_REWARD:
  12401. nAmount = 10;
  12402. break;
  12403. case TF_CURRENCY_PACK_SMALL:
  12404. nAmount = 5;
  12405. break;
  12406. case TF_CURRENCY_PACK_MEDIUM:
  12407. nAmount = 10;
  12408. break;
  12409. case TF_CURRENCY_PACK_LARGE:
  12410. nAmount = 25;
  12411. break;
  12412. case TF_CURRENCY_TIME_REWARD:
  12413. nAmount = 5;
  12414. break;
  12415. case TF_CURRENCY_WAVE_COLLECTION_BONUS:
  12416. nAmount = 100;
  12417. break;
  12418. default:
  12419. Assert( 0 ); // Unknown type
  12420. };
  12421. return nAmount;
  12422. }
  12423. //-----------------------------------------------------------------------------
  12424. // Purpose: Gives money directly to a team or specific player
  12425. //-----------------------------------------------------------------------------
  12426. int CTFGameRules::DistributeCurrencyAmount( int nAmount, CTFPlayer *pTFPlayer /* = NULL */, bool bShared /* = true */, bool bCountAsDropped /*= false */, bool bIsBonus /*= false */ )
  12427. {
  12428. // Group distribution (default)
  12429. if ( bShared )
  12430. {
  12431. CUtlVector<CTFPlayer *> playerVector;
  12432. if ( IsMannVsMachineMode() )
  12433. {
  12434. CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
  12435. }
  12436. #ifdef STAGING_ONLY
  12437. else if ( IsBountyMode() )
  12438. {
  12439. // We require a player in order to award the proper team
  12440. if ( pTFPlayer )
  12441. {
  12442. CollectPlayers( &playerVector, pTFPlayer->GetTeamNumber() );
  12443. }
  12444. }
  12445. #endif // STAGING_ONLY
  12446. // Money
  12447. FOR_EACH_VEC( playerVector, i )
  12448. {
  12449. if ( playerVector[i] )
  12450. {
  12451. #ifdef STAGING_ONLY
  12452. if ( IsBountyMode() )
  12453. {
  12454. // Check for a cap
  12455. int nLimit = tf_bountymode_currency_limit.GetInt();
  12456. if ( nLimit > 0 )
  12457. {
  12458. int nNewCurrency = nAmount + pTFPlayer->GetCurrency();
  12459. if ( nNewCurrency > nLimit )
  12460. {
  12461. int nDelta = nNewCurrency - nLimit;
  12462. if ( nDelta )
  12463. {
  12464. nAmount -= nDelta;
  12465. }
  12466. }
  12467. }
  12468. }
  12469. #endif // STAGING_ONLY
  12470. playerVector[i]->AddCurrency( nAmount );
  12471. }
  12472. }
  12473. }
  12474. // Individual distribution
  12475. else if ( pTFPlayer )
  12476. {
  12477. #ifdef STAGING_ONLY
  12478. if ( IsBountyMode() )
  12479. {
  12480. // Check for a cap
  12481. int nLimit = tf_bountymode_currency_limit.GetInt();
  12482. if ( nLimit > 0 )
  12483. {
  12484. int nNewCurrency = nAmount + pTFPlayer->GetCurrency();
  12485. if ( nNewCurrency > nLimit )
  12486. {
  12487. int nDelta = nNewCurrency - nLimit;
  12488. if ( nDelta )
  12489. {
  12490. nAmount -= nDelta;
  12491. }
  12492. }
  12493. }
  12494. }
  12495. #endif // STAGING_ONLY
  12496. pTFPlayer->AddCurrency( nAmount );
  12497. }
  12498. // Accounting
  12499. if ( IsMannVsMachineMode() && g_pPopulationManager )
  12500. {
  12501. g_pPopulationManager->OnCurrencyCollected( nAmount, bCountAsDropped, bIsBonus );
  12502. }
  12503. return nAmount;
  12504. }
  12505. //-----------------------------------------------------------------------------
  12506. // Purpose:
  12507. //-----------------------------------------------------------------------------
  12508. void CTFGameRules::RoundRespawn( void )
  12509. {
  12510. #ifdef GAME_DLL
  12511. m_hasSpawnedToy = false;
  12512. for ( int i = 0; i < TF_TEAM_COUNT; i++ )
  12513. {
  12514. m_bHasSpawnedSoccerBall[i] = false;
  12515. }
  12516. #endif // GAME_DLL
  12517. // remove any buildings, grenades, rockets, etc. the player put into the world
  12518. RemoveAllProjectilesAndBuildings();
  12519. // re-enable any sentry guns the losing team has built (and not hidden!)
  12520. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  12521. {
  12522. CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  12523. if ( pObj->ObjectType() == OBJ_SENTRYGUN && pObj->IsEffectActive( EF_NODRAW ) == false && pObj->GetTeamNumber() != m_iWinningTeam )
  12524. {
  12525. pObj->SetDisabled( false );
  12526. }
  12527. }
  12528. #ifdef TF_RAID_MODE
  12529. // Raid mode: clean up any Red buildings that might be left behind from the previous round
  12530. if ( IsRaidMode() )
  12531. {
  12532. CTFTeam *pTeam = TFTeamMgr()->GetTeam( TF_TEAM_RED );
  12533. if ( pTeam )
  12534. {
  12535. int nTeamObjectCount = pTeam->GetNumObjects();
  12536. for ( int iObject = 0; iObject < nTeamObjectCount; ++iObject )
  12537. {
  12538. CBaseObject *pObj = pTeam->GetObject( iObject );
  12539. if ( pObj )
  12540. {
  12541. pObj->SetThink( &CBaseEntity::SUB_Remove );
  12542. pObj->SetNextThink( gpGlobals->curtime );
  12543. pObj->SetTouch( NULL );
  12544. pObj->AddEffects( EF_NODRAW );
  12545. }
  12546. }
  12547. }
  12548. }
  12549. else if ( IsBossBattleMode() )
  12550. {
  12551. // unspawn entire red team
  12552. CTeam *defendingTeam = GetGlobalTeam( TF_TEAM_RED );
  12553. int i;
  12554. for( i=0; i<defendingTeam->GetNumPlayers(); ++i )
  12555. {
  12556. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", defendingTeam->GetPlayer(i)->GetUserID() ) );
  12557. }
  12558. }
  12559. #endif // TF_RAID_MODE
  12560. if ( IsInTournamentMode() == false )
  12561. {
  12562. Arena_RunTeamLogic();
  12563. }
  12564. m_bArenaFirstBlood = false;
  12565. // reset the flag captures
  12566. int nTeamCount = TFTeamMgr()->GetTeamCount();
  12567. for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam )
  12568. {
  12569. CTFTeam *pTeam = GetGlobalTFTeam( iTeam );
  12570. if ( !pTeam )
  12571. continue;
  12572. pTeam->SetFlagCaptures( 0 );
  12573. }
  12574. if ( !IsMannVsMachineMode() )
  12575. {
  12576. IGameEvent *event = gameeventmanager->CreateEvent( "scorestats_accumulated_update" );
  12577. if ( event )
  12578. {
  12579. gameeventmanager->FireEvent( event );
  12580. }
  12581. }
  12582. // reset player per-round stats
  12583. CTF_GameStats.ResetRoundStats();
  12584. BaseClass::RoundRespawn();
  12585. // ** AFTER WE'VE BEEN THROUGH THE ROUND RESPAWN, SHOW THE ROUNDINFO PANEL
  12586. if ( !IsInWaitingForPlayers() )
  12587. {
  12588. ShowRoundInfoPanel();
  12589. }
  12590. // We've hit some condition where a server-side vote should be called on respawn
  12591. if ( m_bServerVoteOnReset )
  12592. {
  12593. if ( g_voteController )
  12594. {
  12595. g_voteController->CreateVote( DEDICATED_SERVER, "nextlevel", "" );
  12596. }
  12597. m_bVoteCalled = true;
  12598. m_bServerVoteOnReset = false;
  12599. }
  12600. }
  12601. #ifdef GAME_DLL
  12602. //-----------------------------------------------------------------------------
  12603. // Purpose:
  12604. //-----------------------------------------------------------------------------
  12605. bool CTFGameRules::ShouldSwitchTeams( void )
  12606. {
  12607. if ( IsPVEModeActive() )
  12608. return false;
  12609. return BaseClass::ShouldSwitchTeams();
  12610. }
  12611. //-----------------------------------------------------------------------------
  12612. // Purpose:
  12613. //-----------------------------------------------------------------------------
  12614. bool CTFGameRules::ShouldScrambleTeams( void )
  12615. {
  12616. if ( IsPVEModeActive() )
  12617. return false;
  12618. if ( IsCompetitiveMode() )
  12619. return false;
  12620. return BaseClass::ShouldScrambleTeams();
  12621. }
  12622. //-----------------------------------------------------------------------------
  12623. // Purpose:
  12624. //-----------------------------------------------------------------------------
  12625. void CTFGameRules::ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues )
  12626. {
  12627. CTFPlayer *pTFPlayer = ToTFPlayer( CBaseEntity::Instance( pEntity ) );
  12628. if ( !pTFPlayer )
  12629. return;
  12630. char const *pszCommand = pKeyValues->GetName();
  12631. if ( pszCommand && pszCommand[0] )
  12632. {
  12633. if ( FStrEq( pszCommand, "FreezeCamTaunt" ) )
  12634. {
  12635. CTFPlayer *pAchiever = ToTFPlayer( UTIL_PlayerByUserId( pKeyValues->GetInt( "achiever" ) ) );
  12636. if ( pAchiever )
  12637. {
  12638. const char *pszCommand = pKeyValues->GetString( "command" );
  12639. if ( pszCommand && pszCommand[0] )
  12640. {
  12641. int nGibs = pKeyValues->GetInt( "gibs" );
  12642. if ( FStrEq( pszCommand, "freezecam_taunt" ) )
  12643. {
  12644. CheckTauntAchievement( pAchiever, nGibs, g_TauntCamAchievements );
  12645. CheckTauntAchievement( pAchiever, nGibs, g_TauntCamAchievements2 );
  12646. HatAndMiscEconEntities_OnOwnerKillEaterEventNoParter( pAchiever, kKillEaterEvent_KillcamTaunts );
  12647. }
  12648. else if ( FStrEq( pszCommand, "freezecam_tauntrag" ) )
  12649. {
  12650. CheckTauntAchievement( pAchiever, nGibs, g_TauntCamRagdollAchievements );
  12651. }
  12652. else if ( FStrEq( pszCommand, "freezecam_tauntgibs" ) )
  12653. {
  12654. CheckTauntAchievement( pAchiever, nGibs, g_TauntCamAchievements );
  12655. }
  12656. else if ( FStrEq( pszCommand, "freezecam_tauntsentry" ) )
  12657. {
  12658. // Maybe should also require a taunt? Currently too easy to get?
  12659. pAchiever->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_FREEZECAM_SENTRY );
  12660. }
  12661. }
  12662. }
  12663. }
  12664. else if ( FStrEq( pszCommand, "UsingVRHeadset" ) )
  12665. {
  12666. pTFPlayer->SetUsingVRHeadset( true );
  12667. }
  12668. else if ( FStrEq( pszCommand, "TestItems" ) )
  12669. {
  12670. pTFPlayer->ItemTesting_Start( pKeyValues );
  12671. }
  12672. else if ( FStrEq( pszCommand, "TestItemsBotUpdate" ) )
  12673. {
  12674. ItemTesting_SetupFromKV( pKeyValues );
  12675. }
  12676. else if ( FStrEq( pszCommand, "MVM_Upgrade" ) )
  12677. {
  12678. if ( GameModeUsesUpgrades() )
  12679. {
  12680. #ifndef STAGING_ONLY
  12681. if ( IsMannVsMachineMode() )
  12682. {
  12683. if ( sv_cheats && !sv_cheats->GetBool() && !pTFPlayer->m_Shared.IsInUpgradeZone() )
  12684. return;
  12685. }
  12686. #endif //!STAGING_ONLY
  12687. if ( g_hUpgradeEntity )
  12688. {
  12689. // First sell everything we want to sell
  12690. KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey();
  12691. while ( pSubKey )
  12692. {
  12693. int iCount = pSubKey->GetInt("count");
  12694. if ( iCount < 0 )
  12695. {
  12696. int iItemSlot = pSubKey->GetInt("itemslot");
  12697. int iUpgrade = pSubKey->GetInt("upgrade");
  12698. bool bFree = pSubKey->GetBool( "free", false );
  12699. while ( iCount < 0 )
  12700. {
  12701. g_hUpgradeEntity->PlayerPurchasingUpgrade( pTFPlayer, iItemSlot, iUpgrade, true, bFree );
  12702. ++iCount;
  12703. }
  12704. }
  12705. pSubKey = pSubKey->GetNextTrueSubKey();
  12706. }
  12707. // Now buy everything we want to buy
  12708. pSubKey = pKeyValues->GetFirstTrueSubKey();
  12709. while ( pSubKey )
  12710. {
  12711. int iCount = pSubKey->GetInt("count");
  12712. if ( iCount > 0 )
  12713. {
  12714. int iItemSlot = pSubKey->GetInt("itemslot");
  12715. int iUpgrade = pSubKey->GetInt("upgrade");
  12716. bool bFree = ( sv_cheats && sv_cheats->GetBool() ) ? pSubKey->GetBool( "free", false ) : false; // Never let a client set "free" without sv_cheats 1
  12717. while ( iCount > 0 )
  12718. {
  12719. g_hUpgradeEntity->PlayerPurchasingUpgrade( pTFPlayer, iItemSlot, iUpgrade, false, bFree );
  12720. --iCount;
  12721. }
  12722. }
  12723. pSubKey = pSubKey->GetNextTrueSubKey();
  12724. }
  12725. }
  12726. }
  12727. }
  12728. else if ( FStrEq( pszCommand, "MvM_UpgradesBegin" ) )
  12729. {
  12730. pTFPlayer->BeginPurchasableUpgrades();
  12731. }
  12732. else if ( FStrEq( pszCommand, "MvM_UpgradesDone" ) )
  12733. {
  12734. pTFPlayer->EndPurchasableUpgrades();
  12735. if ( IsMannVsMachineMode() && pKeyValues->GetInt( "num_upgrades", 0 ) > 0 )
  12736. {
  12737. pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_MVM_UPGRADE_COMPLETE );
  12738. }
  12739. }
  12740. else if ( FStrEq( pszCommand, "MVM_Revive_Response" ) )
  12741. {
  12742. CTFReviveMarker *pReviveMarker = pTFPlayer->GetReviveMarker();
  12743. if ( pReviveMarker )
  12744. {
  12745. if ( pKeyValues->GetBool( "accepted", 0 ) )
  12746. {
  12747. pReviveMarker->ReviveOwner();
  12748. }
  12749. else
  12750. {
  12751. // They hit cancel after their spawn timer was up
  12752. if ( HasPassedMinRespawnTime( pTFPlayer ) )
  12753. {
  12754. pTFPlayer->ForceRespawn();
  12755. }
  12756. UTIL_Remove( pReviveMarker );
  12757. }
  12758. }
  12759. }
  12760. else if ( FStrEq( pszCommand, "MVM_Respec" ) )
  12761. {
  12762. if ( GameModeUsesUpgrades() && IsMannVsMachineRespecEnabled() && CanPlayerUseRespec( pTFPlayer ) )
  12763. {
  12764. #ifndef STAGING_ONLY
  12765. if ( IsMannVsMachineMode() )
  12766. {
  12767. if ( sv_cheats && !sv_cheats->GetBool() && !pTFPlayer->m_Shared.IsInUpgradeZone() )
  12768. return;
  12769. }
  12770. #endif //!STAGING_ONLY
  12771. if ( g_hUpgradeEntity && g_pPopulationManager )
  12772. {
  12773. // Consume a respec credit
  12774. g_pPopulationManager->RemoveRespecFromPlayer( pTFPlayer );
  12775. // Remove the appropriate upgrade info from upgrade histories
  12776. g_pPopulationManager->RemovePlayerAndItemUpgradesFromHistory( pTFPlayer );
  12777. // Remove upgrade attributes from the player and their items
  12778. g_hUpgradeEntity->GrantOrRemoveAllUpgrades( pTFPlayer, true );
  12779. pTFPlayer->ForceRespawn();
  12780. }
  12781. }
  12782. }
  12783. else if ( FStrEq( pszCommand, "use_action_slot_item_server" ) )
  12784. {
  12785. if ( pTFPlayer->ShouldRunRateLimitedCommand( "use_action_slot_item_server" ) )
  12786. {
  12787. pTFPlayer->UseActionSlotItemPressed();
  12788. pTFPlayer->UseActionSlotItemReleased();
  12789. }
  12790. }
  12791. else if ( FStrEq( pszCommand, "+use_action_slot_item_server" ) )
  12792. {
  12793. if ( !pTFPlayer->IsUsingActionSlot() )
  12794. {
  12795. pTFPlayer->UseActionSlotItemPressed();
  12796. }
  12797. }
  12798. else if ( FStrEq( pszCommand, "-use_action_slot_item_server" ) )
  12799. {
  12800. if ( pTFPlayer->IsUsingActionSlot() )
  12801. {
  12802. pTFPlayer->UseActionSlotItemReleased();
  12803. }
  12804. }
  12805. else if ( FStrEq( pszCommand, "+inspect_server" ) )
  12806. {
  12807. pTFPlayer->InspectButtonPressed();
  12808. }
  12809. else if ( FStrEq( pszCommand, "-inspect_server" ) )
  12810. {
  12811. pTFPlayer->InspectButtonReleased();
  12812. }
  12813. else if ( FStrEq( pszCommand, "cl_drawline" ) )
  12814. {
  12815. BroadcastDrawLine( pTFPlayer, pKeyValues );
  12816. }
  12817. else if ( FStrEq( pszCommand, "AutoBalanceVolunteerReply" ) )
  12818. {
  12819. if ( TFAutoBalance() )
  12820. {
  12821. TFAutoBalance()->ReplyReceived( pTFPlayer, pKeyValues->GetBool( "response", false ) );
  12822. }
  12823. }
  12824. else
  12825. {
  12826. BaseClass::ClientCommandKeyValues( pEntity, pKeyValues );
  12827. }
  12828. }
  12829. }
  12830. //-----------------------------------------------------------------------------
  12831. // Purpose:
  12832. //-----------------------------------------------------------------------------
  12833. void CTFGameRules::BroadcastDrawLine( CTFPlayer *pTFPlayer, KeyValues *pKeyValues )
  12834. {
  12835. int paneltype = clamp( pKeyValues->GetInt( "panel", DRAWING_PANEL_TYPE_NONE ), DRAWING_PANEL_TYPE_NONE, DRAWING_PANEL_TYPE_MAX - 1 );
  12836. if ( paneltype >= DRAWING_PANEL_TYPE_MATCH_SUMMARY )
  12837. {
  12838. int linetype = clamp( pKeyValues->GetInt( "line", 0 ), 0, 1 );
  12839. float x = pKeyValues->GetFloat( "x", 0.f );
  12840. float y = pKeyValues->GetFloat( "y", 0.f );
  12841. IGameEvent *event = gameeventmanager->CreateEvent( "cl_drawline" );
  12842. if ( event )
  12843. {
  12844. event->SetInt( "player", pTFPlayer->entindex() );
  12845. event->SetInt( "panel",paneltype );
  12846. event->SetInt( "line", linetype );
  12847. event->SetFloat( "x", x );
  12848. event->SetFloat( "y", y );
  12849. gameeventmanager->FireEvent( event );
  12850. }
  12851. }
  12852. }
  12853. //-----------------------------------------------------------------------------
  12854. // Purpose:
  12855. //-----------------------------------------------------------------------------
  12856. void CTFGameRules::RespawnTeam( int iTeam )
  12857. {
  12858. BaseClass::RespawnTeam( iTeam );
  12859. }
  12860. //-----------------------------------------------------------------------------
  12861. // Purpose:
  12862. //-----------------------------------------------------------------------------
  12863. void CTFGameRules::SpawnPlayerInHell( CTFPlayer *pPlayer, const char *pszSpawnPointName )
  12864. {
  12865. Vector vTeleportPosition;
  12866. QAngle qTeleportAngles;
  12867. int iCachedLocations = m_mapTeleportLocations.Find( MAKE_STRING( pszSpawnPointName ) );
  12868. if ( m_mapTeleportLocations.IsValidIndex( iCachedLocations ) )
  12869. {
  12870. CUtlVector< TeleportLocation_t > *pLocations = m_mapTeleportLocations[iCachedLocations];
  12871. Assert( pLocations );
  12872. if ( !pLocations )
  12873. return;
  12874. const TeleportLocation_t& location = pLocations->Element( RandomInt( 0, pLocations->Count() - 1 ) );
  12875. vTeleportPosition = location.m_vecPosition;
  12876. qTeleportAngles = location.m_qAngles;
  12877. }
  12878. else
  12879. {
  12880. CUtlVector< CBaseEntity* > m_vecPossibleSpawns;
  12881. CBaseEntity* pSpawn = NULL;
  12882. while( (pSpawn = gEntList.FindEntityByName( pSpawn, pszSpawnPointName ) ) != NULL )
  12883. {
  12884. m_vecPossibleSpawns.AddToTail( pSpawn );
  12885. }
  12886. // There had better be a spawnpoint in this map!
  12887. Assert( m_vecPossibleSpawns.Count() );
  12888. if ( m_vecPossibleSpawns.Count() == 0 )
  12889. return;
  12890. // Randomly choose one
  12891. pSpawn = m_vecPossibleSpawns[ RandomInt( 0, m_vecPossibleSpawns.Count() - 1 ) ];
  12892. vTeleportPosition = pSpawn->GetAbsOrigin();
  12893. qTeleportAngles = pSpawn->GetAbsAngles();
  12894. }
  12895. // Teleport them to hell
  12896. pPlayer->Teleport( &vTeleportPosition, &qTeleportAngles, &vec3_origin );
  12897. pPlayer->pl.v_angle = qTeleportAngles;
  12898. // Send us to hell as a ghost!
  12899. pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_GHOST_MODE );
  12900. pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_IN_HELL );
  12901. }
  12902. //-----------------------------------------------------------------------------
  12903. // Purpose:
  12904. //-----------------------------------------------------------------------------
  12905. extern ISoundEmitterSystemBase *soundemitterbase;
  12906. void CTFGameRules::PlayHelltowerAnnouncerVO( int iRedLine, int iBlueLine )
  12907. {
  12908. static float flRedAnnouncerTalkingUntil = 0.00f, flBlueAnnouncerTalkingUntil = 0.00f;
  12909. // 01 is the first line for the VO
  12910. int iRandomVORed = RandomInt( 1, g_pszHelltowerAnnouncerLines[iRedLine].m_nCount );
  12911. int iRandomVOBlue = RandomInt( 1, g_pszHelltowerAnnouncerLines[iBlueLine].m_nCount );
  12912. // the misc lines should match up so we'll play the same lines to both teams
  12913. if ( ( iRedLine <= HELLTOWER_VO_BLUE_MISC_RARE ) && ( iBlueLine <= HELLTOWER_VO_BLUE_MISC_RARE ) )
  12914. {
  12915. // can we safely set blue to the same value?
  12916. if ( iRandomVORed <= g_pszHelltowerAnnouncerLines[iBlueLine].m_nCount )
  12917. {
  12918. iRandomVOBlue = iRandomVORed;
  12919. }
  12920. }
  12921. char szRedAudio[128];
  12922. char szBlueAudio[128];
  12923. V_sprintf_safe( szRedAudio, g_pszHelltowerAnnouncerLines[iRedLine].m_pszFormatString, iRandomVORed );
  12924. V_sprintf_safe( szBlueAudio, g_pszHelltowerAnnouncerLines[iBlueLine].m_pszFormatString, iRandomVOBlue );
  12925. bool bForceVO = false;
  12926. switch (iRedLine)
  12927. {
  12928. case HELLTOWER_VO_RED_WIN:
  12929. case HELLTOWER_VO_RED_WIN_RARE:
  12930. case HELLTOWER_VO_RED_LOSE:
  12931. case HELLTOWER_VO_RED_LOSE_RARE:
  12932. bForceVO = true;
  12933. }
  12934. switch (iBlueLine)
  12935. {
  12936. case HELLTOWER_VO_BLUE_WIN:
  12937. case HELLTOWER_VO_BLUE_WIN_RARE:
  12938. case HELLTOWER_VO_BLUE_LOSE:
  12939. case HELLTOWER_VO_BLUE_LOSE_RARE:
  12940. bForceVO = true;
  12941. }
  12942. CSoundParameters params;
  12943. float flSoundDuration = 0;
  12944. if ( gpGlobals->curtime > flRedAnnouncerTalkingUntil || bForceVO )
  12945. {
  12946. BroadcastSound( TF_TEAM_RED, szRedAudio );
  12947. if ( soundemitterbase->GetParametersForSound( szRedAudio, params, GENDER_NONE ) )
  12948. {
  12949. //flSoundDuration = enginesound->GetSoundDuration( params.soundname );
  12950. flRedAnnouncerTalkingUntil = gpGlobals->curtime + flSoundDuration;
  12951. }
  12952. else
  12953. {
  12954. flRedAnnouncerTalkingUntil = 0.00;
  12955. }
  12956. }
  12957. if ( gpGlobals->curtime > flBlueAnnouncerTalkingUntil || bForceVO )
  12958. {
  12959. BroadcastSound( TF_TEAM_BLUE, szBlueAudio );
  12960. if ( soundemitterbase->GetParametersForSound( szBlueAudio, params, GENDER_NONE ) )
  12961. {
  12962. //flSoundDuration = enginesound->GetSoundDuration( params.soundname );
  12963. flBlueAnnouncerTalkingUntil = gpGlobals->curtime + flSoundDuration;
  12964. }
  12965. else
  12966. {
  12967. flBlueAnnouncerTalkingUntil = 0.00;
  12968. }
  12969. }
  12970. }
  12971. //-----------------------------------------------------------------------------
  12972. // Purpose: Based on connected match players, chooses the 3 maps players can
  12973. // vote on as their next map
  12974. //-----------------------------------------------------------------------------
  12975. void CTFGameRules::ChooseNextMapVoteOptions()
  12976. {
  12977. // Copy chosen maps into the actual fields we're networking to clients
  12978. for( int i=0; i < NEXT_MAP_VOTE_OPTIONS; ++i )
  12979. {
  12980. m_nNextMapVoteOptions.Set( i, GTFGCClientSystem()->GetNextMapVoteByIndex( i )->m_nDefIndex );
  12981. }
  12982. }
  12983. //-----------------------------------------------------------------------------
  12984. // Purpose:
  12985. //-----------------------------------------------------------------------------
  12986. void CTFGameRules::CheckHelltowerCartAchievement( int iTeam )
  12987. {
  12988. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  12989. {
  12990. CUtlVector< CTFPlayer * > playerVector;
  12991. CollectPlayers( &playerVector, iTeam );
  12992. FOR_EACH_VEC( playerVector, i )
  12993. {
  12994. CTFPlayer *pPlayer = playerVector[i];
  12995. if ( pPlayer && ( pPlayer->GetObserverMode() <= OBS_MODE_DEATHCAM ) ) // they might be killed by the explosion, so check if they are OBS_MODE_NONE OR OBS_MODE_DEATHCAM
  12996. {
  12997. CTriggerAreaCapture *pAreaTrigger = pPlayer->GetControlPointStandingOn();
  12998. if ( pAreaTrigger && pAreaTrigger->TeamCanCap( iTeam ) )
  12999. {
  13000. pPlayer->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_KILL_BROTHERS );
  13001. }
  13002. }
  13003. }
  13004. }
  13005. }
  13006. //-----------------------------------------------------------------------------
  13007. // Purpose:
  13008. //-----------------------------------------------------------------------------
  13009. void CTFGameRules::HandleMapEvent( inputdata_t &inputdata )
  13010. {
  13011. if ( FStrEq( "sd_doomsday", STRING( gpGlobals->mapname ) ) )
  13012. {
  13013. // find the flag in the map
  13014. CCaptureFlag *pFlag = NULL;
  13015. for ( int i=0; i<ICaptureFlagAutoList::AutoList().Count(); ++i )
  13016. {
  13017. pFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[i] );
  13018. if ( !pFlag->IsDisabled() )
  13019. {
  13020. break;
  13021. }
  13022. }
  13023. // make sure it's being carried by one of the teams
  13024. if ( pFlag && pFlag->IsStolen() )
  13025. {
  13026. CTFPlayer *pFlagCarrier = ToTFPlayer( pFlag->GetOwnerEntity() );
  13027. if ( pFlagCarrier )
  13028. {
  13029. // let everyone know which team has opened the rocket
  13030. IGameEvent *event = gameeventmanager->CreateEvent( "doomsday_rocket_open" );
  13031. if ( event )
  13032. {
  13033. event->SetInt( "team", pFlagCarrier->GetTeamNumber() );
  13034. gameeventmanager->FireEvent( event );
  13035. }
  13036. }
  13037. }
  13038. }
  13039. else if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  13040. {
  13041. const char *pszEvent = inputdata.value.String();
  13042. if ( FStrEq( pszEvent, "midnight" ) )
  13043. {
  13044. HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_HELLTOWER_MIDNIGHT );
  13045. }
  13046. else if ( FStrEq( pszEvent, "horde" ) )
  13047. {
  13048. HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SKELETON_KING_APPEAR );
  13049. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_SKELETON_KING, HELLTOWER_VO_BLUE_SKELETON_KING );
  13050. }
  13051. else if ( FStrEq( pszEvent, "red_capture" ) )
  13052. {
  13053. CheckHelltowerCartAchievement( TF_TEAM_RED );
  13054. if ( RandomFloat( 0, 1 ) < HELLTOWER_RARE_LINE_CHANCE )
  13055. {
  13056. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_WIN_RARE, HELLTOWER_VO_BLUE_LOSE_RARE );
  13057. }
  13058. else
  13059. {
  13060. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_WIN, HELLTOWER_VO_BLUE_LOSE );
  13061. }
  13062. }
  13063. else if ( FStrEq( pszEvent, "blue_capture" ) )
  13064. {
  13065. CheckHelltowerCartAchievement( TF_TEAM_BLUE );
  13066. if ( RandomFloat( 0, 1 ) < HELLTOWER_RARE_LINE_CHANCE )
  13067. {
  13068. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_LOSE_RARE, HELLTOWER_VO_BLUE_WIN_RARE );
  13069. }
  13070. else
  13071. {
  13072. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_LOSE, HELLTOWER_VO_BLUE_WIN );
  13073. }
  13074. }
  13075. }
  13076. }
  13077. //-----------------------------------------------------------------------------
  13078. // Purpose:
  13079. //-----------------------------------------------------------------------------
  13080. void CTFGameRules::SetCustomUpgradesFile( inputdata_t &inputdata )
  13081. {
  13082. const char *pszPath = inputdata.value.String();
  13083. // Reload
  13084. g_MannVsMachineUpgrades.LoadUpgradesFileFromPath( pszPath );
  13085. // Tell future clients to load from this path
  13086. V_strncpy( m_pszCustomUpgradesFile.GetForModify(), pszPath, MAX_PATH );
  13087. // Tell connected clients to reload
  13088. IGameEvent *pEvent = gameeventmanager->CreateEvent( "upgrades_file_changed" );
  13089. if ( pEvent )
  13090. {
  13091. pEvent->SetString( "path", pszPath );
  13092. gameeventmanager->FireEvent( pEvent );
  13093. }
  13094. }
  13095. //-----------------------------------------------------------------------------
  13096. // Purpose:
  13097. //-----------------------------------------------------------------------------
  13098. bool CTFGameRules::ShouldWaitToStartRecording( void )
  13099. {
  13100. if ( IsMannVsMachineMode() )
  13101. {
  13102. // Don't wait for the WaitingForPlayers period to end if we're MvM
  13103. return false;
  13104. }
  13105. return BaseClass::ShouldWaitToStartRecording();
  13106. }
  13107. //-----------------------------------------------------------------------------
  13108. // Purpose: return true if this flag is currently allowed to be captured
  13109. //-----------------------------------------------------------------------------
  13110. bool CTFGameRules::CanFlagBeCaptured( CBaseEntity *pOther )
  13111. {
  13112. if ( pOther && ( tf_flag_return_on_touch.GetBool() || IsPowerupMode() ) )
  13113. {
  13114. for ( int i = 0; i < ICaptureFlagAutoList::AutoList().Count(); ++i )
  13115. {
  13116. CCaptureFlag *pListFlag = static_cast<CCaptureFlag*>( ICaptureFlagAutoList::AutoList()[i] );
  13117. if ( ( pListFlag->GetType() == TF_FLAGTYPE_CTF ) && !pListFlag->IsDisabled() && ( pOther->GetTeamNumber() == pListFlag->GetTeamNumber() ) && !pListFlag->IsHome() )
  13118. return false;
  13119. }
  13120. }
  13121. return true;
  13122. }
  13123. //-----------------------------------------------------------------------------
  13124. // Purpose: return true if the game is in a state where both flags are stolen and poisonous
  13125. //-----------------------------------------------------------------------------
  13126. bool CTFGameRules::PowerupModeFlagStandoffActive( void )
  13127. {
  13128. if ( IsPowerupMode() )
  13129. {
  13130. int nQualifyingFlags = 0; // All flags need to be stolen and poisonous (poisonous time delay gives the flag carriers a chance to get out of the enemy base)
  13131. int nEnabledFlags = 0; // Some flags might be in the autolist but be out of play. We don't want them included in the count
  13132. for ( int i = 0; i < ICaptureFlagAutoList::AutoList().Count(); ++i )
  13133. {
  13134. CCaptureFlag *pFlag = static_cast<CCaptureFlag*>( ICaptureFlagAutoList::AutoList()[i] );
  13135. if ( !pFlag->IsDisabled() )
  13136. nEnabledFlags++;
  13137. if ( pFlag->IsPoisonous() && pFlag->IsStolen() )
  13138. nQualifyingFlags++;
  13139. }
  13140. if ( nQualifyingFlags == nEnabledFlags )
  13141. {
  13142. return true;
  13143. }
  13144. }
  13145. return false;
  13146. }
  13147. void CTFGameRules::TeleportPlayersToTargetEntities( int iTeam, const char *pszEntTargetName, CUtlVector< CTFPlayer * > *pTeleportedPlayers )
  13148. {
  13149. CUtlVector< CTFPlayer * > vecPlayers;
  13150. CUtlVector< CTFPlayer * > vecRandomOrderPlayers;
  13151. CollectPlayers( &vecPlayers, iTeam );
  13152. FOR_EACH_VEC( vecPlayers, i )
  13153. {
  13154. CTFPlayer *pPlayer = vecPlayers[i];
  13155. // Skip players not on a team or who have not chosen a class
  13156. if ( ( pPlayer->GetTeamNumber() != TF_TEAM_RED && pPlayer->GetTeamNumber() != TF_TEAM_BLUE )
  13157. || pPlayer->IsPlayerClass( TF_CLASS_UNDEFINED ) )
  13158. continue;
  13159. vecRandomOrderPlayers.AddToTail( pPlayer );
  13160. }
  13161. // Randomize the order so players dont go to the same spot every time
  13162. vecRandomOrderPlayers.Shuffle();
  13163. string_t sName = MAKE_STRING( pszEntTargetName );
  13164. int iCachedLocationIndex = m_mapTeleportLocations.Find( sName );
  13165. CUtlVector< TeleportLocation_t > *pCachedLocations = NULL;
  13166. // is there any cached entities to teleport to
  13167. if ( m_mapTeleportLocations.IsValidIndex( iCachedLocationIndex ) )
  13168. {
  13169. pCachedLocations = m_mapTeleportLocations[iCachedLocationIndex];
  13170. }
  13171. int iCurrentTeleportLocation = 0;
  13172. CBaseEntity *pSpawnPoint = NULL;
  13173. FOR_EACH_VEC_BACK( vecRandomOrderPlayers, i )
  13174. {
  13175. // don't do anything if we run out of spawn point
  13176. Vector vTeleportPosition;
  13177. QAngle qTeleportAngles;
  13178. // if we have cached locations, use them
  13179. if ( pCachedLocations )
  13180. {
  13181. Assert( iCurrentTeleportLocation < pCachedLocations->Count() );
  13182. if ( iCurrentTeleportLocation < pCachedLocations->Count() )
  13183. {
  13184. const TeleportLocation_t& location = pCachedLocations->Element( iCurrentTeleportLocation );
  13185. vTeleportPosition = location.m_vecPosition;
  13186. qTeleportAngles = location.m_qAngles;
  13187. iCurrentTeleportLocation++;
  13188. }
  13189. else
  13190. {
  13191. // we need to add more teleport location in the map for players to teleport to
  13192. continue;
  13193. }
  13194. }
  13195. else // use old search for entities by name
  13196. {
  13197. pSpawnPoint = gEntList.FindEntityByName( pSpawnPoint, pszEntTargetName );
  13198. Assert( pSpawnPoint );
  13199. if ( pSpawnPoint )
  13200. {
  13201. vTeleportPosition = pSpawnPoint->GetAbsOrigin();
  13202. qTeleportAngles = pSpawnPoint->GetAbsAngles();
  13203. }
  13204. else
  13205. {
  13206. // we need to add more teleport location in the map for players to teleport to
  13207. continue;
  13208. }
  13209. }
  13210. CTFPlayer *pPlayer = vecRandomOrderPlayers[ i ];
  13211. pPlayer->m_Shared.RemoveAllCond();
  13212. // Respawn dead players
  13213. if ( !pPlayer->IsAlive() )
  13214. {
  13215. pPlayer->ForceRespawn();
  13216. }
  13217. // Unzoom if we are a sniper zoomed!
  13218. pPlayer->m_Shared.InstantlySniperUnzoom();
  13219. // Teleport
  13220. pPlayer->Teleport( &vTeleportPosition, &qTeleportAngles, &vec3_origin );
  13221. pPlayer->SnapEyeAngles( qTeleportAngles );
  13222. // Force client to update all view angles (including kart and taunt yaw)
  13223. pPlayer->ForcePlayerViewAngles( qTeleportAngles );
  13224. // fill in the teleported player vector
  13225. if ( pTeleportedPlayers )
  13226. {
  13227. pTeleportedPlayers->AddToTail( pPlayer );
  13228. }
  13229. }
  13230. }
  13231. #endif // GAME_DLL
  13232. //-----------------------------------------------------------------------------
  13233. // Purpose:
  13234. //-----------------------------------------------------------------------------
  13235. void CTFGameRules::InternalHandleTeamWin( int iWinningTeam )
  13236. {
  13237. // remove any spies' disguises and make them visible (for the losing team only)
  13238. // and set the speed for both teams (winners get a boost and losers have reduced speed)
  13239. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  13240. {
  13241. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  13242. if ( pPlayer )
  13243. {
  13244. if ( pPlayer->GetTeamNumber() > LAST_SHARED_TEAM )
  13245. {
  13246. if ( pPlayer->GetTeamNumber() != iWinningTeam )
  13247. {
  13248. pPlayer->RemoveInvisibility();
  13249. // pPlayer->RemoveDisguise();
  13250. if ( pPlayer->HasTheFlag() )
  13251. {
  13252. pPlayer->DropFlag();
  13253. }
  13254. }
  13255. pPlayer->TeamFortress_SetSpeed();
  13256. }
  13257. }
  13258. }
  13259. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  13260. {
  13261. CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  13262. if ( pObj->GetTeamNumber() != iWinningTeam )
  13263. {
  13264. // Stop placing any carried objects or else they will float around
  13265. // at our feet at the end of the round
  13266. if( pObj->IsPlacing() )
  13267. {
  13268. pObj->StopPlacement();
  13269. }
  13270. // Disable sentry guns that the losing team has built
  13271. if( pObj->GetType() == OBJ_SENTRYGUN )
  13272. {
  13273. pObj->SetDisabled( true );
  13274. }
  13275. }
  13276. }
  13277. if ( m_bForceMapReset )
  13278. {
  13279. m_iPrevRoundState = -1;
  13280. m_iCurrentRoundState = -1;
  13281. m_iCurrentMiniRoundMask = 0;
  13282. }
  13283. if ( IsInStopWatch() == true && GetStopWatchTimer() )
  13284. {
  13285. variant_t sVariant;
  13286. GetStopWatchTimer()->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  13287. if ( m_bForceMapReset )
  13288. {
  13289. if ( GetStopWatchTimer()->IsWatchingTimeStamps() == true )
  13290. {
  13291. m_flStopWatchTotalTime = GetStopWatchTimer()->GetStopWatchTotalTime();
  13292. }
  13293. else
  13294. {
  13295. ShouldResetScores( true, false );
  13296. UTIL_Remove( m_hStopWatchTimer );
  13297. m_hStopWatchTimer = NULL;
  13298. m_flStopWatchTotalTime = -1.0f;
  13299. m_bStopWatch = false;
  13300. m_nStopWatchState.Set( STOPWATCH_CAPTURE_TIME_NOT_SET );
  13301. }
  13302. }
  13303. }
  13304. #ifdef GAME_DLL
  13305. if ( GetHalloweenScenario() == HALLOWEEN_SCENARIO_VIADUCT )
  13306. {
  13307. // send everyone to the underworld!
  13308. BroadcastSound( 255, "Halloween.PlayerEscapedUnderworld" );
  13309. CUtlVector< CTFPlayer * > playerVector;
  13310. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  13311. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  13312. CUtlVector< CBaseEntity * > spawnVector;
  13313. CBaseEntity *spawnPoint = NULL;
  13314. while( ( spawnPoint = gEntList.FindEntityByClassname( spawnPoint, "info_target" ) ) != NULL )
  13315. {
  13316. if ( FStrEq( STRING( spawnPoint->GetEntityName() ), "spawn_warcrimes" ) )
  13317. {
  13318. spawnVector.AddToTail( spawnPoint );
  13319. }
  13320. }
  13321. if ( spawnVector.Count() > 0 )
  13322. {
  13323. // shuffle the order of the spawns
  13324. int n = spawnVector.Count();
  13325. while( n > 1 )
  13326. {
  13327. int k = RandomInt( 0, n-1 );
  13328. n--;
  13329. CBaseEntity *tmp = spawnVector[n];
  13330. spawnVector[n] = spawnVector[k];
  13331. spawnVector[k] = tmp;
  13332. }
  13333. color32 fadeColor = { 255, 255, 255, 100 };
  13334. // send players to the underworld
  13335. for( int i=0; i<playerVector.Count(); ++i )
  13336. {
  13337. CTFPlayer *player = playerVector[i];
  13338. player->SetLocalOrigin( spawnVector[n]->GetAbsOrigin() + Vector( 0, 0, 20.0f ) );
  13339. player->SetAbsVelocity( vec3_origin );
  13340. player->SetLocalAngles( spawnVector[n]->GetAbsAngles() );
  13341. player->m_Local.m_vecPunchAngle = vec3_angle;
  13342. player->m_Local.m_vecPunchAngleVel = vec3_angle;
  13343. player->SnapEyeAngles( spawnVector[n]->GetAbsAngles() );
  13344. // give them full health since purgatory damages them over time
  13345. player->SetHealth( player->GetMaxHealth() );
  13346. UTIL_ScreenFade( player, fadeColor, 0.25, 0.4, FFADE_IN );
  13347. n = ( n + 1 ) % spawnVector.Count();
  13348. }
  13349. }
  13350. }
  13351. #endif
  13352. }
  13353. //-----------------------------------------------------------------------------
  13354. // Purpose: Helper function for scramble teams
  13355. //-----------------------------------------------------------------------------
  13356. int FindScoreDifferenceBetweenTeams( CUtlVector< CTFPlayer* > &vecSource, CTFPlayerResource *pPR, int &nRedScore, int &nBlueScore )
  13357. {
  13358. if ( !pPR )
  13359. return false;
  13360. nRedScore = 0;
  13361. nBlueScore = 0;
  13362. FOR_EACH_VEC( vecSource, i )
  13363. {
  13364. if ( !vecSource[i] )
  13365. continue;
  13366. if ( vecSource[i]->GetTeamNumber() == TF_TEAM_RED )
  13367. {
  13368. nRedScore += pPR->GetTotalScore( vecSource[i]->entindex() );
  13369. }
  13370. else
  13371. {
  13372. nBlueScore += pPR->GetTotalScore( vecSource[i]->entindex() );
  13373. }
  13374. }
  13375. return abs( nRedScore - nBlueScore );
  13376. }
  13377. //-----------------------------------------------------------------------------
  13378. // Purpose: Helper function for scramble teams
  13379. //-----------------------------------------------------------------------------
  13380. bool FindAndSwapPlayersToBalanceTeams( CUtlVector< CTFPlayer* > &vecSource, int &nDelta, CTFPlayerResource *pPR )
  13381. {
  13382. if ( !pPR )
  13383. return false;
  13384. int nTeamScoreRed = 0;
  13385. int nTeamScoreBlue = 0;
  13386. FindScoreDifferenceBetweenTeams( vecSource, pPR, nTeamScoreRed, nTeamScoreBlue );
  13387. FOR_EACH_VEC( vecSource, i )
  13388. {
  13389. if ( !vecSource[i] )
  13390. continue;
  13391. if ( vecSource[i]->GetTeamNumber() != TF_TEAM_RED )
  13392. continue;
  13393. // Check against players on the other team
  13394. FOR_EACH_VEC( vecSource, j )
  13395. {
  13396. if ( !vecSource[j] )
  13397. continue;
  13398. if ( vecSource[j]->GetTeamNumber() != TF_TEAM_BLUE )
  13399. continue;
  13400. if ( vecSource[i] == vecSource[j] )
  13401. continue;
  13402. int nRedPlayerScore = pPR->GetTotalScore( vecSource[i]->entindex() );
  13403. int nBluePlayerScore = pPR->GetTotalScore( vecSource[j]->entindex() );
  13404. int nPlayerDiff = abs( nRedPlayerScore - nBluePlayerScore );
  13405. if ( nPlayerDiff )
  13406. {
  13407. int nNewRedScore = nTeamScoreRed;
  13408. int nNewBlueScore = nTeamScoreBlue;
  13409. if ( nRedPlayerScore > nBluePlayerScore )
  13410. {
  13411. nNewRedScore -= nPlayerDiff;
  13412. nNewBlueScore += nPlayerDiff;
  13413. }
  13414. else
  13415. {
  13416. nNewRedScore += nPlayerDiff;
  13417. nNewBlueScore -= nPlayerDiff;
  13418. }
  13419. int nNewDelta = abs( nNewRedScore - nNewBlueScore );
  13420. if ( nNewDelta < nDelta )
  13421. {
  13422. // Swap and recheck
  13423. vecSource[i]->ForceChangeTeam( TF_TEAM_BLUE );
  13424. vecSource[j]->ForceChangeTeam( TF_TEAM_RED );
  13425. nDelta = FindScoreDifferenceBetweenTeams( vecSource, pPR, nTeamScoreRed, nTeamScoreBlue );
  13426. return true;
  13427. }
  13428. }
  13429. }
  13430. }
  13431. return false;
  13432. }
  13433. //-----------------------------------------------------------------------------
  13434. // Purpose:
  13435. //-----------------------------------------------------------------------------
  13436. void CTFGameRules::HandleScrambleTeams( void )
  13437. {
  13438. static CUtlVector< CTFPlayer* > playerVector;
  13439. playerVector.RemoveAll();
  13440. CollectPlayers( &playerVector, TF_TEAM_RED );
  13441. CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS );
  13442. // Sort by player score.
  13443. playerVector.Sort( ScramblePlayersSort );
  13444. // Put everyone on Spectator to clear the teams (or the autoteam step won't work correctly)
  13445. FOR_EACH_VEC_BACK( playerVector, i )
  13446. {
  13447. if ( !playerVector[i] )
  13448. {
  13449. playerVector.Remove( i );
  13450. continue;
  13451. }
  13452. else if ( DuelMiniGame_IsInDuel( playerVector[i] ) ) // don't include them if they're in a duel
  13453. {
  13454. playerVector.Remove( i );
  13455. continue;
  13456. }
  13457. playerVector[i]->ForceChangeTeam( TEAM_SPECTATOR );
  13458. }
  13459. // Assign players using the original, quick method.
  13460. FOR_EACH_VEC( playerVector, i )
  13461. {
  13462. if ( !playerVector[i] )
  13463. continue;
  13464. if ( playerVector[i]->GetTeamNumber() >= FIRST_GAME_TEAM ) // are they already on a game team?
  13465. continue;
  13466. playerVector[i]->ForceChangeTeam( TF_TEAM_AUTOASSIGN );
  13467. }
  13468. // New method
  13469. if ( playerVector.Count() > 2 )
  13470. {
  13471. CTFPlayerResource *pPR = dynamic_cast< CTFPlayerResource* >( g_pPlayerResource );
  13472. if ( pPR )
  13473. {
  13474. int nTeamScoreRed = 0;
  13475. int nTeamScoreBlue = 0;
  13476. int nDelta = FindScoreDifferenceBetweenTeams( playerVector, pPR, nTeamScoreRed, nTeamScoreBlue );
  13477. #ifdef _DEBUG
  13478. if ( mp_scrambleteams_debug.GetBool() )
  13479. {
  13480. DevMsg( "FIRST PASS -- Team1: %i || Team2: %i || Diff: %i\n",
  13481. nTeamScoreRed,
  13482. nTeamScoreBlue,
  13483. nDelta );
  13484. }
  13485. #endif // _DEBUG
  13486. // Try swapping players to bring scores closer
  13487. if ( nDelta > 1 )
  13488. {
  13489. int nOrigValue = mp_teams_unbalance_limit.GetInt();
  13490. mp_teams_unbalance_limit.SetValue( 0 );
  13491. static const int nPassLimit = 8;
  13492. for ( int i = 0; i < nPassLimit && FindAndSwapPlayersToBalanceTeams( playerVector, nDelta, pPR ); ++i )
  13493. {
  13494. #ifdef _DEBUG
  13495. if ( mp_scrambleteams_debug.GetBool() )
  13496. {
  13497. nTeamScoreRed = 0;
  13498. nTeamScoreBlue = 0;
  13499. DevMsg( "EXTRA PASS -- Team1: %i || Team2: %i || Diff: %i\n",
  13500. nTeamScoreRed,
  13501. nTeamScoreBlue,
  13502. FindScoreDifferenceBetweenTeams( playerVector, pPR, nTeamScoreRed, nTeamScoreBlue ) );
  13503. }
  13504. #endif // _DEBUG
  13505. }
  13506. mp_teams_unbalance_limit.SetValue( nOrigValue );
  13507. }
  13508. }
  13509. }
  13510. // scrambleteams_auto tracking
  13511. ResetTeamsRoundWinTracking();
  13512. }
  13513. //-----------------------------------------------------------------------------
  13514. // Purpose:
  13515. //-----------------------------------------------------------------------------
  13516. void CTFGameRules::TeamPlayerCountChanged( CTFTeam *pTeam )
  13517. {
  13518. if ( m_hGamerulesProxy )
  13519. {
  13520. m_hGamerulesProxy->TeamPlayerCountChanged( pTeam );
  13521. }
  13522. }
  13523. #ifdef GAME_DLL
  13524. //-----------------------------------------------------------------------------
  13525. // Purpose: Should we attempt to roll into a new match for the current match
  13526. //-----------------------------------------------------------------------------
  13527. bool CTFGameRules::BAttemptMapVoteRollingMatch()
  13528. {
  13529. if ( IsManagedMatchEnded() )
  13530. { return false; }
  13531. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  13532. return pMatchDesc && GTFGCClientSystem()->CanRequestNewMatchForLobby() && pMatchDesc->BUsesMapVoteAfterMatchEnds();
  13533. }
  13534. //-----------------------------------------------------------------------------
  13535. // Purpose:
  13536. //-----------------------------------------------------------------------------
  13537. bool CTFGameRules::BIsManagedMatchEndImminent( void )
  13538. {
  13539. /*
  13540. if ( IsCompetitiveMode() )
  13541. {
  13542. if ( State_Get() == GR_STATE_RND_RUNNING )
  13543. {
  13544. bool bPotentiallyTheFinalRound = ( CheckWinLimit( false, 1 ) || CheckMaxRounds( false, 1 ) );
  13545. if ( bPotentiallyTheFinalRound )
  13546. {
  13547. bool bPlayingMiniRounds = ( g_hControlPointMasters.Count() && g_hControlPointMasters[0] && g_hControlPointMasters[0]->PlayingMiniRounds() );
  13548. switch( m_nGameType )
  13549. {
  13550. case TF_GAMETYPE_ESCORT:
  13551. {
  13552. if ( HasMultipleTrains() )
  13553. {
  13554. }
  13555. else
  13556. {
  13557. }
  13558. }
  13559. break;
  13560. case TF_GAMETYPE_CP:
  13561. break;
  13562. case TF_GAMETYPE_CTF:
  13563. if ( tf_flag_caps_per_round.GetInt() > 0 )
  13564. {
  13565. for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; iTeam++ )
  13566. {
  13567. C_TFTeam *pTeam = GetGlobalTFTeam( iTeam );
  13568. if ( pTeam )
  13569. {
  13570. if ( ( tf_flag_caps_per_round.GetInt() - pTeam->GetFlagCaptures() ) <= 1 )
  13571. {
  13572. CCaptureFlag *pFlag = NULL;
  13573. for ( int iFlag = 0; iFlag < ICaptureFlagAutoList::AutoList().Count(); ++iFlag )
  13574. {
  13575. pFlag = static_cast< CCaptureFlag* >( ICaptureFlagAutoList::AutoList()[iFlag] );
  13576. if ( !pFlag->IsDisabled() && ( pFlag->GetTeamNumber() == iTeam ) && !pFlag->IsHome() )
  13577. return true;
  13578. }
  13579. }
  13580. }
  13581. }
  13582. }
  13583. break;
  13584. default:
  13585. break;
  13586. }
  13587. }
  13588. }
  13589. }*/
  13590. return false;
  13591. }
  13592. #endif // GAME_DLL
  13593. //-----------------------------------------------------------------------------
  13594. // Purpose:
  13595. //-----------------------------------------------------------------------------
  13596. void CTFGameRules::PowerupTeamImbalance( int nTeam )
  13597. {
  13598. if ( m_hGamerulesProxy )
  13599. {
  13600. m_hGamerulesProxy->PowerupTeamImbalance( nTeam );
  13601. if ( nTeam == TEAM_UNASSIGNED )
  13602. {
  13603. m_bPowerupImbalanceMeasuresRunning = false;
  13604. }
  13605. else
  13606. {
  13607. m_bPowerupImbalanceMeasuresRunning = true;
  13608. m_flTimeToStopImbalanceMeasures = gpGlobals->curtime + m_flTimeToRunImbalanceMeasures;
  13609. BroadcastSound( nTeam, "Announcer.Powerup.Volume.Starting" );
  13610. CTeamRecipientFilter filter( nTeam, true );
  13611. UTIL_ClientPrintFilter( filter, HUD_PRINTCENTER, "#TF_Powerupvolume_Available" );
  13612. }
  13613. m_nPowerupKillsBlueTeam = 0; // Reset both scores
  13614. m_nPowerupKillsRedTeam = 0;
  13615. }
  13616. }
  13617. //-----------------------------------------------------------------------------
  13618. // Purpose: Restrict team human players can join
  13619. //-----------------------------------------------------------------------------
  13620. int CTFGameRules::GetAssignedHumanTeam( void )
  13621. {
  13622. if ( FStrEq( "blue", mp_humans_must_join_team.GetString() ) )
  13623. {
  13624. return TF_TEAM_BLUE;
  13625. }
  13626. else if ( FStrEq( "red", mp_humans_must_join_team.GetString() ) )
  13627. {
  13628. return TF_TEAM_RED;
  13629. }
  13630. else if ( FStrEq( "spectator", mp_humans_must_join_team.GetString() ) )
  13631. {
  13632. return TEAM_SPECTATOR;
  13633. }
  13634. else
  13635. {
  13636. return TEAM_ANY;
  13637. }
  13638. }
  13639. //-----------------------------------------------------------------------------
  13640. // Purpose:
  13641. //-----------------------------------------------------------------------------
  13642. void CTFGameRules::HandleSwitchTeams( void )
  13643. {
  13644. if ( IsPVEModeActive() )
  13645. return;
  13646. m_bTeamsSwitched.Set( !m_bTeamsSwitched );
  13647. // switch this as well
  13648. if ( FStrEq( mp_humans_must_join_team.GetString(), "blue" ) )
  13649. {
  13650. mp_humans_must_join_team.SetValue( "red" );
  13651. }
  13652. else if ( FStrEq( mp_humans_must_join_team.GetString(), "red" ) )
  13653. {
  13654. mp_humans_must_join_team.SetValue( "blue" );
  13655. }
  13656. int i = 0;
  13657. // remove everyone's projectiles and objects
  13658. RemoveAllProjectilesAndBuildings();
  13659. // respawn the players
  13660. for ( i = 1 ; i <= gpGlobals->maxClients ; i++ )
  13661. {
  13662. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  13663. if ( pPlayer )
  13664. {
  13665. if ( pPlayer->GetTeamNumber() == TF_TEAM_RED )
  13666. {
  13667. pPlayer->ForceChangeTeam( TF_TEAM_BLUE, true );
  13668. }
  13669. else if ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
  13670. {
  13671. pPlayer->ForceChangeTeam( TF_TEAM_RED, true );
  13672. }
  13673. }
  13674. }
  13675. // switch the team scores
  13676. CTFTeam *pRedTeam = GetGlobalTFTeam( TF_TEAM_RED );
  13677. CTFTeam *pBlueTeam = GetGlobalTFTeam( TF_TEAM_BLUE );
  13678. if ( pRedTeam && pBlueTeam )
  13679. {
  13680. int nRed = pRedTeam->GetScore();
  13681. int nBlue = pBlueTeam->GetScore();
  13682. pRedTeam->SetScore( nBlue );
  13683. pBlueTeam->SetScore( nRed );
  13684. if ( IsInTournamentMode() == true )
  13685. {
  13686. char szBlueName[16];
  13687. char szRedName[16];
  13688. Q_strncpy( szBlueName, mp_tournament_blueteamname.GetString(), sizeof ( szBlueName ) );
  13689. Q_strncpy( szRedName, mp_tournament_redteamname.GetString(), sizeof ( szRedName ) );
  13690. mp_tournament_redteamname.SetValue( szBlueName );
  13691. mp_tournament_blueteamname.SetValue( szRedName );
  13692. }
  13693. }
  13694. UTIL_ClientPrintAll( HUD_PRINTTALK, "#TF_TeamsSwitched" );
  13695. CMatchInfo *pMatchInfo = GTFGCClientSystem()->GetMatch();
  13696. if ( pMatchInfo )
  13697. {
  13698. CTFPlayerResource *pTFResource = dynamic_cast< CTFPlayerResource* >( g_pPlayerResource );
  13699. if ( pTFResource )
  13700. {
  13701. uint32 unEventTeamStatus = pTFResource->GetEventTeamStatus();
  13702. if ( unEventTeamStatus && m_bTeamsSwitched )
  13703. {
  13704. const uint32 unInvadersArePyro = 1u;
  13705. const uint32 unInvadersAreHeavy = 2u;
  13706. unEventTeamStatus = ( unEventTeamStatus == unInvadersArePyro ) ? unInvadersAreHeavy : unInvadersArePyro;
  13707. }
  13708. pMatchInfo->m_unEventTeamStatus = unEventTeamStatus;
  13709. }
  13710. }
  13711. }
  13712. //-----------------------------------------------------------------------------
  13713. // Purpose:
  13714. //-----------------------------------------------------------------------------
  13715. bool CTFGameRules::CanChangeClassInStalemate( void )
  13716. {
  13717. return (gpGlobals->curtime < (m_flStalemateStartTime + tf_stalematechangeclasstime.GetFloat()));
  13718. }
  13719. //-----------------------------------------------------------------------------
  13720. // Purpose:
  13721. //-----------------------------------------------------------------------------
  13722. bool CTFGameRules::CanChangeTeam( int iCurrentTeam ) const
  13723. {
  13724. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  13725. {
  13726. if ( ( iCurrentTeam == TF_TEAM_RED ) || ( iCurrentTeam == TF_TEAM_BLUE ) )
  13727. {
  13728. return !ArePlayersInHell();
  13729. }
  13730. }
  13731. return true;
  13732. }
  13733. //-----------------------------------------------------------------------------
  13734. // Purpose:
  13735. //-----------------------------------------------------------------------------
  13736. void CTFGameRules::SetRoundOverlayDetails( void )
  13737. {
  13738. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  13739. if ( pMaster && pMaster->PlayingMiniRounds() )
  13740. {
  13741. CTeamControlPointRound *pRound = pMaster->GetCurrentRound();
  13742. if ( pRound )
  13743. {
  13744. CHandle<CTeamControlPoint> pRedPoint = pRound->GetPointOwnedBy( TF_TEAM_RED );
  13745. CHandle<CTeamControlPoint> pBluePoint = pRound->GetPointOwnedBy( TF_TEAM_BLUE );
  13746. // do we have opposing points in this round?
  13747. if ( pRedPoint && pBluePoint )
  13748. {
  13749. int iMiniRoundMask = ( 1<<pBluePoint->GetPointIndex() ) | ( 1<<pRedPoint->GetPointIndex() );
  13750. SetMiniRoundBitMask( iMiniRoundMask );
  13751. }
  13752. else
  13753. {
  13754. SetMiniRoundBitMask( 0 );
  13755. }
  13756. SetCurrentRoundStateBitString();
  13757. }
  13758. }
  13759. BaseClass::SetRoundOverlayDetails();
  13760. }
  13761. //-----------------------------------------------------------------------------
  13762. // Purpose: Returns whether a team should score for each captured point
  13763. //-----------------------------------------------------------------------------
  13764. bool CTFGameRules::ShouldScorePerRound( void )
  13765. {
  13766. bool bRetVal = true;
  13767. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  13768. if ( pMaster && pMaster->ShouldScorePerCapture() )
  13769. {
  13770. bRetVal = false;
  13771. }
  13772. return bRetVal;
  13773. }
  13774. //-----------------------------------------------------------------------------
  13775. // Purpose:
  13776. //-----------------------------------------------------------------------------
  13777. bool CTFGameRules::IsValveMap( void )
  13778. {
  13779. char szCurrentMap[MAX_MAP_NAME];
  13780. Q_strncpy( szCurrentMap, STRING( gpGlobals->mapname ), sizeof( szCurrentMap ) );
  13781. if ( ::IsValveMap( szCurrentMap ) )
  13782. {
  13783. return true;
  13784. }
  13785. return BaseClass::IsValveMap();
  13786. }
  13787. void CTFGameRules::PlayTrainCaptureAlert( CTeamControlPoint *pPoint, bool bFinalPointInMap )
  13788. {
  13789. if ( !pPoint )
  13790. return;
  13791. if ( State_Get() != GR_STATE_RND_RUNNING )
  13792. return;
  13793. const char *pszAlert = TEAM_TRAIN_ALERT;
  13794. // is this the final control point in the map?
  13795. if ( bFinalPointInMap )
  13796. {
  13797. pszAlert = TEAM_TRAIN_FINAL_ALERT;
  13798. }
  13799. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) )
  13800. {
  13801. if ( bFinalPointInMap )
  13802. {
  13803. int iWinningTeam = TEAM_UNASSIGNED;
  13804. float flRedProgress = 0.0f, flBlueProgress = 0.0f;
  13805. for ( int i = 0 ; i < ITFTeamTrainWatcher::AutoList().Count() ; ++i )
  13806. {
  13807. CTeamTrainWatcher *pTrainWatcher = static_cast< CTeamTrainWatcher* >( ITFTeamTrainWatcher::AutoList()[i] );
  13808. if ( !pTrainWatcher->IsDisabled() )
  13809. {
  13810. if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_RED )
  13811. {
  13812. flRedProgress = pTrainWatcher->GetTrainDistanceAlongTrack();
  13813. }
  13814. else
  13815. {
  13816. flBlueProgress = pTrainWatcher->GetTrainDistanceAlongTrack();
  13817. }
  13818. }
  13819. }
  13820. if ( flRedProgress > flBlueProgress )
  13821. {
  13822. iWinningTeam = TF_TEAM_RED;
  13823. }
  13824. else if ( flBlueProgress > flRedProgress )
  13825. {
  13826. iWinningTeam = TF_TEAM_BLUE;
  13827. }
  13828. if ( iWinningTeam != TEAM_UNASSIGNED )
  13829. {
  13830. int iRedLine, iBlueLine;
  13831. iRedLine = ( iWinningTeam == TF_TEAM_RED ) ? HELLTOWER_VO_RED_NEAR_WIN : HELLTOWER_VO_RED_NEAR_LOSE;
  13832. iBlueLine = ( iWinningTeam == TF_TEAM_BLUE ) ? HELLTOWER_VO_BLUE_NEAR_WIN : HELLTOWER_VO_BLUE_NEAR_LOSE;
  13833. PlayHelltowerAnnouncerVO( iRedLine, iBlueLine );
  13834. }
  13835. }
  13836. return;
  13837. }
  13838. CBroadcastRecipientFilter filter;
  13839. pPoint->EmitSound( filter, pPoint->entindex(), pszAlert );
  13840. }
  13841. #endif // GAME_DLL
  13842. //-----------------------------------------------------------------------------
  13843. // Purpose:
  13844. //-----------------------------------------------------------------------------
  13845. int CTFGameRules::GetFarthestOwnedControlPoint( int iTeam, bool bWithSpawnpoints )
  13846. {
  13847. int iOwnedEnd = ObjectiveResource()->GetBaseControlPointForTeam( iTeam );
  13848. if ( iOwnedEnd == -1 )
  13849. return -1;
  13850. int iNumControlPoints = ObjectiveResource()->GetNumControlPoints();
  13851. int iWalk = 1;
  13852. int iEnemyEnd = iNumControlPoints-1;
  13853. if ( iOwnedEnd != 0 )
  13854. {
  13855. iWalk = -1;
  13856. iEnemyEnd = 0;
  13857. }
  13858. // Walk towards the other side, and find the farthest owned point that has spawn points
  13859. int iFarthestPoint = iOwnedEnd;
  13860. for ( int iPoint = iOwnedEnd; iPoint != iEnemyEnd; iPoint += iWalk )
  13861. {
  13862. // If we've hit a point we don't own, we're done
  13863. if ( ObjectiveResource()->GetOwningTeam( iPoint ) != iTeam )
  13864. break;
  13865. if ( bWithSpawnpoints && !m_bControlSpawnsPerTeam[iTeam][iPoint] )
  13866. continue;
  13867. iFarthestPoint = iPoint;
  13868. }
  13869. return iFarthestPoint;
  13870. }
  13871. //-----------------------------------------------------------------------------
  13872. // Purpose:
  13873. //-----------------------------------------------------------------------------
  13874. bool CTFGameRules::TeamMayCapturePoint( int iTeam, int iPointIndex )
  13875. {
  13876. if ( !tf_caplinear.GetBool() )
  13877. return true;
  13878. // Any previous points necessary?
  13879. int iPointNeeded = ObjectiveResource()->GetPreviousPointForPoint( iPointIndex, iTeam, 0 );
  13880. // Points set to require themselves are always cappable
  13881. if ( iPointNeeded == iPointIndex )
  13882. return true;
  13883. if ( IsInKothMode() && IsInWaitingForPlayers() )
  13884. return false;
  13885. // Is the point locked?
  13886. if ( ObjectiveResource()->GetCPLocked( iPointIndex ) )
  13887. return false;
  13888. // No required points specified? Require all previous points.
  13889. if ( iPointNeeded == -1 )
  13890. {
  13891. if ( IsInArenaMode() == true )
  13892. {
  13893. #ifdef CLIENT_DLL
  13894. if ( m_flCapturePointEnableTime - 5.0f <= gpGlobals->curtime && State_Get() == GR_STATE_STALEMATE )
  13895. return true;
  13896. #endif
  13897. if ( m_flCapturePointEnableTime <= gpGlobals->curtime && State_Get() == GR_STATE_STALEMATE )
  13898. return true;
  13899. return false;
  13900. }
  13901. if ( !ObjectiveResource()->PlayingMiniRounds() )
  13902. {
  13903. // No custom previous point, team must own all previous points
  13904. int iFarthestPoint = GetFarthestOwnedControlPoint( iTeam, false );
  13905. return (abs(iFarthestPoint - iPointIndex) <= 1);
  13906. }
  13907. else
  13908. {
  13909. // No custom previous point, team must own all previous points in the current mini-round
  13910. //tagES TFTODO: need to figure out a good algorithm for this
  13911. return true;
  13912. }
  13913. }
  13914. // Loop through each previous point and see if the team owns it
  13915. for ( int iPrevPoint = 0; iPrevPoint < MAX_PREVIOUS_POINTS; iPrevPoint++ )
  13916. {
  13917. iPointNeeded = ObjectiveResource()->GetPreviousPointForPoint( iPointIndex, iTeam, iPrevPoint );
  13918. if ( iPointNeeded != -1 )
  13919. {
  13920. if ( ObjectiveResource()->GetOwningTeam( iPointNeeded ) != iTeam )
  13921. return false;
  13922. }
  13923. }
  13924. return true;
  13925. }
  13926. //-----------------------------------------------------------------------------
  13927. // Purpose:
  13928. //-----------------------------------------------------------------------------
  13929. bool CTFGameRules::PlayerMayCapturePoint( CBasePlayer *pPlayer, int iPointIndex, char *pszReason /* = NULL */, int iMaxReasonLength /* = 0 */ )
  13930. {
  13931. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  13932. if ( !pTFPlayer )
  13933. {
  13934. return false;
  13935. }
  13936. // Disguised and invisible spies cannot capture points
  13937. if ( pTFPlayer->m_Shared.IsStealthed() )
  13938. {
  13939. if ( pszReason )
  13940. {
  13941. Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_stealthed" );
  13942. }
  13943. return false;
  13944. }
  13945. if ( ( pTFPlayer->m_Shared.IsInvulnerable() || pTFPlayer->m_Shared.InCond( TF_COND_MEGAHEAL ) ) && !pTFPlayer->m_bInPowerPlay && !IsMannVsMachineMode() )
  13946. {
  13947. if ( pszReason )
  13948. {
  13949. Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_invuln" );
  13950. }
  13951. return false;
  13952. }
  13953. if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) )
  13954. {
  13955. if ( pszReason )
  13956. {
  13957. Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_invuln" );
  13958. }
  13959. return false;
  13960. }
  13961. if ( pTFPlayer->m_Shared.IsControlStunned() )
  13962. {
  13963. if ( pszReason )
  13964. {
  13965. Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_stunned" );
  13966. }
  13967. return false;
  13968. }
  13969. // spies disguised as the enemy team cannot capture points
  13970. if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pTFPlayer->m_Shared.GetDisguiseTeam() != pTFPlayer->GetTeamNumber() )
  13971. {
  13972. if ( pszReason )
  13973. {
  13974. Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_disguised" );
  13975. }
  13976. return false;
  13977. }
  13978. #ifdef GAME_DLL
  13979. if ( IsInTraining() && pTFPlayer->IsBotOfType( TF_BOT_TYPE ) )
  13980. {
  13981. switch( GetGameType() )
  13982. {
  13983. case TF_GAMETYPE_CP:
  13984. {
  13985. // in training mode, bots cannot initiate a capture
  13986. float flCapPerc = ObjectiveResource()->GetCPCapPercentage( iPointIndex );
  13987. if ( flCapPerc <= 0.0f )
  13988. {
  13989. return false;
  13990. }
  13991. break;
  13992. }
  13993. case TF_GAMETYPE_ESCORT:
  13994. {
  13995. // in training mode, the player must push the cart for the first time
  13996. // after that, bots can start it moving again
  13997. // assume only one cart in the map
  13998. CTeamTrainWatcher *watcher = NULL;
  13999. while( ( watcher = dynamic_cast< CTeamTrainWatcher * >( gEntList.FindEntityByClassname( watcher, "team_train_watcher" ) ) ) != NULL )
  14000. {
  14001. if ( !watcher->IsDisabled() )
  14002. {
  14003. break;
  14004. }
  14005. }
  14006. if ( watcher && !watcher->IsDisabled() )
  14007. {
  14008. return !watcher->IsTrainAtStart();
  14009. }
  14010. break;
  14011. }
  14012. }
  14013. }
  14014. #ifdef TF_CREEP_MODE
  14015. if ( IsCreepWaveMode() )
  14016. {
  14017. CTFBot *bot = ToTFBot( pTFPlayer );
  14018. if ( !bot || !bot->HasAttribute( CTFBot::IS_NPC ) )
  14019. {
  14020. // only creeps can capture points
  14021. return false;
  14022. }
  14023. }
  14024. #endif
  14025. #endif
  14026. return true;
  14027. }
  14028. //-----------------------------------------------------------------------------
  14029. // Purpose:
  14030. //-----------------------------------------------------------------------------
  14031. bool CTFGameRules::PlayerMayBlockPoint( CBasePlayer *pPlayer, int iPointIndex, char *pszReason, int iMaxReasonLength )
  14032. {
  14033. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  14034. if ( !pTFPlayer )
  14035. return false;
  14036. #ifdef GAME_DLL
  14037. #ifdef TF_CREEP_MODE
  14038. if ( IsCreepWaveMode() )
  14039. {
  14040. CTFBot *bot = ToTFBot( pTFPlayer );
  14041. if ( !bot || !bot->HasAttribute( CTFBot::IS_NPC ) )
  14042. {
  14043. // only creeps can block points
  14044. return false;
  14045. }
  14046. }
  14047. #endif
  14048. #endif
  14049. // Invuln players can block points
  14050. if ( pTFPlayer->m_Shared.IsInvulnerable() )
  14051. {
  14052. if ( pszReason )
  14053. {
  14054. Q_snprintf( pszReason, iMaxReasonLength, "#Cant_cap_invuln" );
  14055. }
  14056. return true;
  14057. }
  14058. return false;
  14059. }
  14060. //-----------------------------------------------------------------------------
  14061. // Purpose: Calculates score for player
  14062. //-----------------------------------------------------------------------------
  14063. int CTFGameRules::CalcPlayerScore( RoundStats_t *pRoundStats, CTFPlayer *pPlayer )
  14064. {
  14065. Assert( pRoundStats );
  14066. if ( !pRoundStats )
  14067. return 0;
  14068. // defensive fix for the moment for bug where healing value becomes bogus sometimes: if bogus, slam it to 0
  14069. int iHealing = pRoundStats->m_iStat[TFSTAT_HEALING];
  14070. Assert( iHealing >= 0 );
  14071. Assert( iHealing <= 10000000 );
  14072. if ( iHealing < 0 || iHealing > 10000000 )
  14073. {
  14074. iHealing = 0;
  14075. }
  14076. bool bMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode();
  14077. bool bPowerupMode = TFGameRules() && TFGameRules()->IsPowerupMode();
  14078. int iScore = ( pRoundStats->m_iStat[TFSTAT_KILLS] * TF_SCORE_KILL ) +
  14079. ( pRoundStats->m_iStat[TFSTAT_KILLS_RUNECARRIER] * TF_SCORE_KILL_RUNECARRIER ) + // Kill someone who is carrying a rune
  14080. ( pRoundStats->m_iStat[TFSTAT_CAPTURES] * ( ( bPowerupMode ) ? TF_SCORE_CAPTURE_POWERUPMODE : TF_SCORE_CAPTURE ) ) +
  14081. ( pRoundStats->m_iStat[TFSTAT_FLAGRETURNS] * TF_SCORE_FLAG_RETURN ) +
  14082. ( pRoundStats->m_iStat[TFSTAT_DEFENSES] * TF_SCORE_DEFEND ) +
  14083. ( pRoundStats->m_iStat[TFSTAT_BUILDINGSDESTROYED] * TF_SCORE_DESTROY_BUILDING ) +
  14084. ( pRoundStats->m_iStat[TFSTAT_HEADSHOTS] / TF_SCORE_HEADSHOT_DIVISOR ) +
  14085. ( pRoundStats->m_iStat[TFSTAT_BACKSTABS] * TF_SCORE_BACKSTAB ) +
  14086. ( iHealing / ( ( bMvM ) ? TF_SCORE_DAMAGE : TF_SCORE_HEAL_HEALTHUNITS_PER_POINT ) ) + // MvM values healing more than PvP
  14087. ( pRoundStats->m_iStat[TFSTAT_KILLASSISTS] / TF_SCORE_KILL_ASSISTS_PER_POINT ) +
  14088. ( pRoundStats->m_iStat[TFSTAT_TELEPORTS] / TF_SCORE_TELEPORTS_PER_POINT ) +
  14089. ( pRoundStats->m_iStat[TFSTAT_INVULNS] / TF_SCORE_INVULN ) +
  14090. ( pRoundStats->m_iStat[TFSTAT_REVENGE] / TF_SCORE_REVENGE ) +
  14091. ( pRoundStats->m_iStat[TFSTAT_BONUS_POINTS] / TF_SCORE_BONUS_POINT_DIVISOR );
  14092. ( pRoundStats->m_iStat[TFSTAT_CURRENCY_COLLECTED] / TF_SCORE_CURRENCY_COLLECTED );
  14093. if ( pPlayer )
  14094. {
  14095. int nScoreboardMinigame = 0;
  14096. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, nScoreboardMinigame, scoreboard_minigame );
  14097. if ( nScoreboardMinigame > 0 )
  14098. {
  14099. // Increment Score
  14100. iScore +=
  14101. ( pRoundStats->m_iStat[TFSTAT_KILLS] ) +
  14102. ( pRoundStats->m_iStat[TFSTAT_CAPTURES] ) +
  14103. ( pRoundStats->m_iStat[TFSTAT_DEFENSES] ) +
  14104. ( pRoundStats->m_iStat[TFSTAT_BUILDINGSDESTROYED] );
  14105. // Subtract Deaths
  14106. iScore -= pRoundStats->m_iStat[TFSTAT_DEATHS] * 3;
  14107. }
  14108. }
  14109. // Previously MvM-only
  14110. const int nDivisor = ( bMvM ) ? TF_SCORE_DAMAGE : TF_SCORE_HEAL_HEALTHUNITS_PER_POINT;
  14111. iScore += ( pRoundStats->m_iStat[TFSTAT_DAMAGE] / nDivisor );
  14112. iScore += ( pRoundStats->m_iStat[TFSTAT_DAMAGE_ASSIST] / nDivisor );
  14113. iScore += ( pRoundStats->m_iStat[TFSTAT_DAMAGE_BOSS] / nDivisor );
  14114. iScore += ( pRoundStats->m_iStat[TFSTAT_HEALING_ASSIST] / nDivisor );
  14115. iScore += ( pRoundStats->m_iStat[TFSTAT_DAMAGE_BLOCKED] / nDivisor );
  14116. return Max( iScore, 0 );
  14117. }
  14118. //-----------------------------------------------------------------------------
  14119. // Purpose: Calculates score for player
  14120. //-----------------------------------------------------------------------------
  14121. int CTFGameRules::CalcPlayerSupportScore( RoundStats_t *pRoundStats, int iPlayerIdx )
  14122. {
  14123. #ifdef GAME_DLL
  14124. Assert( pRoundStats );
  14125. if ( !pRoundStats )
  14126. return 0;
  14127. return ( pRoundStats->m_iStat[TFSTAT_DAMAGE_ASSIST] +
  14128. pRoundStats->m_iStat[TFSTAT_HEALING_ASSIST] +
  14129. pRoundStats->m_iStat[TFSTAT_DAMAGE_BLOCKED] +
  14130. ( pRoundStats->m_iStat[TFSTAT_BONUS_POINTS] * 25 ) );
  14131. #else
  14132. Assert( g_TF_PR );
  14133. if ( !g_TF_PR )
  14134. return 0;
  14135. return g_TF_PR->GetDamageAssist( iPlayerIdx ) +
  14136. g_TF_PR->GetHealingAssist( iPlayerIdx ) +
  14137. g_TF_PR->GetDamageBlocked( iPlayerIdx ) +
  14138. ( g_TF_PR->GetBonusPoints( iPlayerIdx ) * 25 );
  14139. #endif
  14140. }
  14141. //-----------------------------------------------------------------------------
  14142. // Purpose:
  14143. //-----------------------------------------------------------------------------
  14144. bool CTFGameRules::IsBirthday( void ) const
  14145. {
  14146. if ( IsX360() )
  14147. return false;
  14148. return tf_birthday.GetBool() || IsHolidayActive( kHoliday_TFBirthday );
  14149. }
  14150. bool CTFGameRules::IsBirthdayOrPyroVision( void ) const
  14151. {
  14152. if ( IsBirthday() )
  14153. return true;
  14154. #ifdef CLIENT_DLL
  14155. // Use birthday fun if the local player has an item that allows them to see it (Pyro Goggles)
  14156. if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) )
  14157. {
  14158. return true;
  14159. }
  14160. #endif
  14161. return false;
  14162. }
  14163. //-----------------------------------------------------------------------------
  14164. // Purpose:
  14165. //-----------------------------------------------------------------------------
  14166. bool CTFGameRules::IsHolidayActive( /*EHoliday*/ int eHoliday ) const
  14167. {
  14168. //if ( IsPVEModeActive() )
  14169. // return false;
  14170. return TF_IsHolidayActive( eHoliday );
  14171. }
  14172. #ifndef GAME_DLL
  14173. void CTFGameRules::SetUpVisionFilterKeyValues( void )
  14174. {
  14175. m_pkvVisionFilterShadersMapWhitelist = new KeyValues( "VisionFilterShadersMapWhitelist" );
  14176. m_pkvVisionFilterShadersMapWhitelist->LoadFromFile( g_pFullFileSystem, "cfg/mtp.cfg", "MOD" );
  14177. m_pkvVisionFilterTranslations = new KeyValues( "VisionFilterTranslations" );
  14178. // **************************************************************************************************
  14179. // PARTICLES
  14180. KeyValues *pKVBlock = new KeyValues( "particles" );
  14181. m_pkvVisionFilterTranslations->AddSubKey( pKVBlock );
  14182. // No special vision
  14183. KeyValues *pKVFlag = new KeyValues( "0" );
  14184. pKVFlag->SetString( "flamethrower_rainbow", "flamethrower" );
  14185. pKVFlag->SetString( "flamethrower_rainbow_FP", "flamethrower" );
  14186. pKVBlock->AddSubKey( pKVFlag );
  14187. // Pyrovision
  14188. pKVFlag = new KeyValues( "1" ); //TF_VISION_FILTER_PYRO
  14189. pKVFlag->SetString( "flamethrower_rainbow", "flamethrower_rainbow" ); // Rainblower defaults to rainbows and we want to ensure that we use it
  14190. pKVFlag->SetString( "flamethrower_rainbow_FP", "flamethrower_rainbow_FP" );
  14191. pKVFlag->SetString( "burningplayer_blue", "burningplayer_rainbow_blue" );
  14192. pKVFlag->SetString( "burningplayer_red", "burningplayer_rainbow_red" );
  14193. pKVFlag->SetString( "burningplayer_corpse", "burningplayer_corpse_rainbow" );
  14194. pKVFlag->SetString( "water_blood_impact_red_01", "pyrovision_blood" );
  14195. pKVFlag->SetString( "blood_impact_red_01", "pyrovision_blood" );
  14196. pKVFlag->SetString( "blood_spray_red_01", "pyrovision_blood" );
  14197. pKVFlag->SetString( "blood_spray_red_01_far", "pyrovision_blood" );
  14198. pKVFlag->SetString( "ExplosionCore_wall", "pyrovision_explosion" );
  14199. pKVFlag->SetString( "ExplosionCore_buildings", "pyrovision_explosion" );
  14200. pKVFlag->SetString( "ExplosionCore_MidAir", "pyrovision_explosion" );
  14201. pKVFlag->SetString( "rockettrail", "pyrovision_rockettrail" );
  14202. pKVFlag->SetString( "rockettrail_!", "pyrovision_rockettrail" );
  14203. pKVFlag->SetString( "sentry_rocket", "pyrovision_rockettrail" );
  14204. pKVFlag->SetString( "flaregun_trail_blue", "pyrovision_flaregun_trail_blue" );
  14205. pKVFlag->SetString( "flaregun_trail_red", "pyrovision_flaregun_trail_red" );
  14206. pKVFlag->SetString( "flaregun_trail_crit_blue", "pyrovision_flaregun_trail_crit_blue" );
  14207. pKVFlag->SetString( "flaregun_trail_crit_red", "pyrovision_flaregun_trail_crit_red" );
  14208. pKVFlag->SetString( "flaregun_destroyed", "pyrovision_flaregun_destroyed" );
  14209. pKVFlag->SetString( "scorchshot_trail_blue", "pyrovision_scorchshot_trail_blue" );
  14210. pKVFlag->SetString( "scorchshot_trail_red", "pyrovision_scorchshot_trail_red" );
  14211. pKVFlag->SetString( "scorchshot_trail_crit_blue", "pyrovision_scorchshot_trail_crit_blue" );
  14212. pKVFlag->SetString( "scorchshot_trail_crit_red", "pyrovision_scorchshot_trail_crit_red" );
  14213. pKVFlag->SetString( "ExplosionCore_MidAir_Flare", "pyrovision_flaregun_destroyed" );
  14214. pKVFlag->SetString( "pyrotaunt_rainbow_norainbow", "pyrotaunt_rainbow" );
  14215. pKVFlag->SetString( "pyrotaunt_rainbow_bubbles_flame", "pyrotaunt_rainbow_bubbles" );
  14216. pKVFlag->SetString( "v_flaming_arrow", "pyrovision_v_flaming_arrow" );
  14217. pKVFlag->SetString( "flaming_arrow", "pyrovision_flaming_arrow" );
  14218. pKVFlag->SetString( "flying_flaming_arrow", "pyrovision_flying_flaming_arrow" );
  14219. pKVFlag->SetString( "taunt_pyro_balloon", "taunt_pyro_balloon_vision" );
  14220. pKVFlag->SetString( "taunt_pyro_balloon_explosion", "taunt_pyro_balloon_explosion_vision" );
  14221. pKVBlock->AddSubKey( pKVFlag );
  14222. //// Spooky Vision
  14223. //pKVFlag = new KeyValues( "2" ); //TF_VISION_FILTER_HALLOWEEN
  14224. //pKVBlock->AddSubKey( pKVFlag );
  14225. // **************************************************************************************************
  14226. // SOUNDS
  14227. pKVBlock = new KeyValues( "sounds" );
  14228. m_pkvVisionFilterTranslations->AddSubKey( pKVBlock );
  14229. // No special vision
  14230. pKVFlag = new KeyValues( "0" );
  14231. pKVFlag->SetString( "weapons/rainblower/rainblower_start.wav", "weapons/flame_thrower_start.wav" );
  14232. pKVFlag->SetString( "weapons/rainblower/rainblower_loop.wav", "weapons/flame_thrower_loop.wav" );
  14233. pKVFlag->SetString( "weapons/rainblower/rainblower_end.wav", "weapons/flame_thrower_end.wav" );
  14234. pKVFlag->SetString( "weapons/rainblower/rainblower_hit.wav", "weapons/flame_thrower_fire_hit.wav" );
  14235. pKVFlag->SetString( "weapons/rainblower/rainblower_pilot.wav", "weapons/flame_thrower_pilot.wav" );
  14236. pKVFlag->SetString( ")weapons/rainblower/rainblower_start.wav", ")weapons/flame_thrower_start.wav" );
  14237. pKVFlag->SetString( ")weapons/rainblower/rainblower_loop.wav", ")weapons/flame_thrower_loop.wav" );
  14238. pKVFlag->SetString( ")weapons/rainblower/rainblower_end.wav", ")weapons/flame_thrower_end.wav" );
  14239. pKVFlag->SetString( ")weapons/rainblower/rainblower_hit.wav", ")weapons/flame_thrower_fire_hit.wav" );
  14240. pKVFlag->SetString( ")weapons/rainblower/rainblower_pilot.wav", "weapons/flame_thrower_pilot.wav" );
  14241. pKVFlag->SetString( "Weapon_Rainblower.Fire", "Weapon_FlameThrower.Fire" );
  14242. pKVFlag->SetString( "Weapon_Rainblower.FireLoop", "Weapon_FlameThrower.FireLoop" );
  14243. pKVFlag->SetString( "Weapon_Rainblower.WindDown", "Weapon_FlameThrower.WindDown" );
  14244. pKVFlag->SetString( "Weapon_Rainblower.FireHit", "Weapon_FlameThrower.FireHit" );
  14245. pKVFlag->SetString( "Weapon_Rainblower.PilotLoop", "Weapon_FlameThrower.PilotLoop" );
  14246. pKVFlag->SetString( "Taunt.PyroBalloonicorn", "Taunt.PyroHellicorn" );
  14247. pKVFlag->SetString( ")items/pyro_music_tube.wav", "common/null.wav" );
  14248. pKVBlock->AddSubKey( pKVFlag );
  14249. // Pyrovision
  14250. pKVFlag = new KeyValues( "1" ); //TF_VISION_FILTER_PYRO
  14251. pKVFlag->SetString( "weapons/rainblower/rainblower_start.wav", "weapons/rainblower/rainblower_start.wav" ); // If we're in PyroVision, explicitly set to ensure this is saved
  14252. pKVFlag->SetString( "weapons/rainblower/rainblower_loop.wav", "weapons/rainblower/rainblower_loop.wav" );
  14253. pKVFlag->SetString( "weapons/rainblower/rainblower_end.wav", "weapons/rainblower/rainblower_end.wav" );
  14254. pKVFlag->SetString( "weapons/rainblower/rainblower_hit.wav", "weapons/rainblower/rainblower_hit.wav" );
  14255. pKVFlag->SetString( "weapons/rainblower/rainblower_pilot.wav", "weapons/rainblower/rainblower_pilot.wav" );
  14256. pKVFlag->SetString( ")weapons/rainblower/rainblower_start.wav", ")weapons/rainblower/rainblower_start.wav" );
  14257. pKVFlag->SetString( ")weapons/rainblower/rainblower_loop.wav", ")weapons/rainblower/rainblower_loop.wav" );
  14258. pKVFlag->SetString( ")weapons/rainblower/rainblower_end.wav", ")weapons/rainblower/rainblower_end.wav" );
  14259. pKVFlag->SetString( ")weapons/rainblower/rainblower_hit.wav", ")weapons/rainblower/rainblower_hit.wav" );
  14260. pKVFlag->SetString( ")weapons/rainblower/rainblower_pilot.wav", ")weapons/rainblower/rainblower_pilot.wav" );
  14261. pKVFlag->SetString( "Weapon_Rainblower.Fire", "Weapon_Rainblower.Fire" );
  14262. pKVFlag->SetString( "Weapon_Rainblower.FireLoop", "Weapon_Rainblower.FireLoop" );
  14263. pKVFlag->SetString( "Weapon_Rainblower.WindDown", "Weapon_Rainblower.WindDown" );
  14264. pKVFlag->SetString( "Weapon_Rainblower.FireHit", "Weapon_Rainblower.FireHit" );
  14265. pKVFlag->SetString( "Weapon_Rainblower.PilotLoop", "Weapon_Rainblower.PilotLoop" );
  14266. pKVFlag->SetString( "Taunt.PyroBalloonicorn", "Taunt.PyroBalloonicorn" );
  14267. pKVFlag->SetString( ")items/pyro_music_tube.wav", ")items/pyro_music_tube.wav" );
  14268. pKVFlag->SetString( "vo/demoman_PainCrticialDeath01.mp3", "vo/demoman_LaughLong01.mp3" );
  14269. pKVFlag->SetString( "vo/demoman_PainCrticialDeath02.mp3", "vo/demoman_LaughLong02.mp3" );
  14270. pKVFlag->SetString( "vo/demoman_PainCrticialDeath03.mp3", "vo/demoman_LaughLong01.mp3" );
  14271. pKVFlag->SetString( "vo/demoman_PainCrticialDeath04.mp3", "vo/demoman_LaughLong02.mp3" );
  14272. pKVFlag->SetString( "vo/demoman_PainCrticialDeath05.mp3", "vo/demoman_LaughLong01.mp3" );
  14273. pKVFlag->SetString( "vo/demoman_PainSevere01.mp3", "vo/demoman_LaughHappy01.mp3" );
  14274. pKVFlag->SetString( "vo/demoman_PainSevere02.mp3", "vo/demoman_LaughHappy02.mp3" );
  14275. pKVFlag->SetString( "vo/demoman_PainSevere03.mp3", "vo/demoman_LaughHappy01.mp3" );
  14276. pKVFlag->SetString( "vo/demoman_PainSevere04.mp3", "vo/demoman_LaughHappy02.mp3" );
  14277. pKVFlag->SetString( "vo/demoman_PainSharp01.mp3", "vo/demoman_LaughShort01.mp3" );
  14278. pKVFlag->SetString( "vo/demoman_PainSharp02.mp3", "vo/demoman_LaughShort02.mp3" );
  14279. pKVFlag->SetString( "vo/demoman_PainSharp03.mp3", "vo/demoman_LaughShort03.mp3" );
  14280. pKVFlag->SetString( "vo/demoman_PainSharp04.mp3", "vo/demoman_LaughShort04.mp3" );
  14281. pKVFlag->SetString( "vo/demoman_PainSharp05.mp3", "vo/demoman_LaughShort05.mp3" );
  14282. pKVFlag->SetString( "vo/demoman_PainSharp06.mp3", "vo/demoman_LaughShort06.mp3" );
  14283. pKVFlag->SetString( "vo/demoman_PainSharp07.mp3", "vo/demoman_LaughShort01.mp3" );
  14284. pKVFlag->SetString( "vo/demoman_AutoOnFire01.mp3", "vo/demoman_PositiveVocalization02.mp3" );
  14285. pKVFlag->SetString( "vo/demoman_AutoOnFire02.mp3", "vo/demoman_PositiveVocalization03.mp3" );
  14286. pKVFlag->SetString( "vo/demoman_AutoOnFire03.mp3", "vo/demoman_PositiveVocalization04.mp3" );
  14287. pKVFlag->SetString( "vo/engineer_PainCrticialDeath01.mp3", "vo/engineer_LaughLong01.mp3" );
  14288. pKVFlag->SetString( "vo/engineer_PainCrticialDeath02.mp3", "vo/engineer_LaughLong02.mp3" );
  14289. pKVFlag->SetString( "vo/engineer_PainCrticialDeath03.mp3", "vo/engineer_LaughLong01.mp3" );
  14290. pKVFlag->SetString( "vo/engineer_PainCrticialDeath04.mp3", "vo/engineer_LaughLong02.mp3" );
  14291. pKVFlag->SetString( "vo/engineer_PainCrticialDeath05.mp3", "vo/engineer_LaughLong01.mp3" );
  14292. pKVFlag->SetString( "vo/engineer_PainCrticialDeath06.mp3", "vo/engineer_LaughLong02.mp3" );
  14293. pKVFlag->SetString( "vo/engineer_PainSevere01.mp3", "vo/engineer_LaughHappy01.mp3" );
  14294. pKVFlag->SetString( "vo/engineer_PainSevere02.mp3", "vo/engineer_LaughHappy02.mp3" );
  14295. pKVFlag->SetString( "vo/engineer_PainSevere03.mp3", "vo/engineer_LaughHappy03.mp3" );
  14296. pKVFlag->SetString( "vo/engineer_PainSevere04.mp3", "vo/engineer_LaughHappy01.mp3" );
  14297. pKVFlag->SetString( "vo/engineer_PainSevere05.mp3", "vo/engineer_LaughHappy02.mp3" );
  14298. pKVFlag->SetString( "vo/engineer_PainSevere06.mp3", "vo/engineer_LaughHappy03.mp3" );
  14299. pKVFlag->SetString( "vo/engineer_PainSevere07.mp3", "vo/engineer_LaughHappy01.mp3" );
  14300. pKVFlag->SetString( "vo/engineer_PainSharp01.mp3", "vo/engineer_LaughShort01.mp3" );
  14301. pKVFlag->SetString( "vo/engineer_PainSharp02.mp3", "vo/engineer_LaughShort02.mp3" );
  14302. pKVFlag->SetString( "vo/engineer_PainSharp03.mp3", "vo/engineer_LaughShort03.mp3" );
  14303. pKVFlag->SetString( "vo/engineer_PainSharp04.mp3", "vo/engineer_LaughShort04.mp3" );
  14304. pKVFlag->SetString( "vo/engineer_PainSharp05.mp3", "vo/engineer_LaughShort01.mp3" );
  14305. pKVFlag->SetString( "vo/engineer_PainSharp06.mp3", "vo/engineer_LaughShort02.mp3" );
  14306. pKVFlag->SetString( "vo/engineer_PainSharp07.mp3", "vo/engineer_LaughShort03.mp3" );
  14307. pKVFlag->SetString( "vo/engineer_PainSharp08.mp3", "vo/engineer_LaughShort04.mp3" );
  14308. pKVFlag->SetString( "vo/engineer_AutoOnFire01.mp3", "vo/engineer_PositiveVocalization01.mp3" );
  14309. pKVFlag->SetString( "vo/engineer_AutoOnFire02.mp3", "vo/engineer_Cheers01.mp3" );
  14310. pKVFlag->SetString( "vo/engineer_AutoOnFire03.mp3", "vo/engineer_Cheers02.mp3" );
  14311. pKVFlag->SetString( "vo/heavy_PainCrticialDeath01.mp3", "vo/heavy_LaughLong01.mp3" );
  14312. pKVFlag->SetString( "vo/heavy_PainCrticialDeath02.mp3", "vo/heavy_LaughLong02.mp3" );
  14313. pKVFlag->SetString( "vo/heavy_PainCrticialDeath03.mp3", "vo/heavy_LaughLong02.mp3" );
  14314. pKVFlag->SetString( "vo/heavy_PainSevere01.mp3", "vo/heavy_LaughHappy01.mp3" );
  14315. pKVFlag->SetString( "vo/heavy_PainSevere02.mp3", "vo/heavy_LaughHappy02.mp3" );
  14316. pKVFlag->SetString( "vo/heavy_PainSevere03.mp3", "vo/heavy_LaughHappy03.mp3" );
  14317. pKVFlag->SetString( "vo/heavy_PainSharp01.mp3", "vo/heavy_LaughShort01.mp3" );
  14318. pKVFlag->SetString( "vo/heavy_PainSharp02.mp3", "vo/heavy_LaughShort02.mp3" );
  14319. pKVFlag->SetString( "vo/heavy_PainSharp03.mp3", "vo/heavy_LaughShort03.mp3" );
  14320. pKVFlag->SetString( "vo/heavy_PainSharp04.mp3", "vo/heavy_LaughShort01.mp3" );
  14321. pKVFlag->SetString( "vo/heavy_PainSharp05.mp3", "vo/heavy_LaughShort02.mp3" );
  14322. pKVFlag->SetString( "vo/heavy_AutoOnFire01.mp3", "vo/heavy_PositiveVocalization01.mp3" );
  14323. pKVFlag->SetString( "vo/heavy_AutoOnFire02.mp3", "vo/heavy_PositiveVocalization02.mp3" );
  14324. pKVFlag->SetString( "vo/heavy_AutoOnFire03.mp3", "vo/heavy_PositiveVocalization03.mp3" );
  14325. pKVFlag->SetString( "vo/heavy_AutoOnFire04.mp3", "vo/heavy_PositiveVocalization04.mp3" );
  14326. pKVFlag->SetString( "vo/heavy_AutoOnFire05.mp3", "vo/heavy_PositiveVocalization05.mp3" );
  14327. pKVFlag->SetString( "vo/medic_PainCrticialDeath01.mp3", "vo/medic_LaughLong01.mp3" );
  14328. pKVFlag->SetString( "vo/medic_PainCrticialDeath02.mp3", "vo/medic_LaughLong02.mp3" );
  14329. pKVFlag->SetString( "vo/medic_PainCrticialDeath03.mp3", "vo/medic_LaughLong01.mp3" );
  14330. pKVFlag->SetString( "vo/medic_PainCrticialDeath04.mp3", "vo/medic_LaughLong02.mp3" );
  14331. pKVFlag->SetString( "vo/medic_PainSevere01.mp3", "vo/medic_LaughHappy01.mp3" );
  14332. pKVFlag->SetString( "vo/medic_PainSevere02.mp3", "vo/medic_LaughHappy02.mp3" );
  14333. pKVFlag->SetString( "vo/medic_PainSevere03.mp3", "vo/medic_LaughHappy03.mp3" );
  14334. pKVFlag->SetString( "vo/medic_PainSevere04.mp3", "vo/medic_LaughHappy01.mp3" );
  14335. pKVFlag->SetString( "vo/medic_PainSharp01.mp3", "vo/medic_LaughShort01.mp3" );
  14336. pKVFlag->SetString( "vo/medic_PainSharp02.mp3", "vo/medic_LaughShort02.mp3" );
  14337. pKVFlag->SetString( "vo/medic_PainSharp03.mp3", "vo/medic_LaughShort03.mp3" );
  14338. pKVFlag->SetString( "vo/medic_PainSharp04.mp3", "vo/medic_LaughShort01.mp3" );
  14339. pKVFlag->SetString( "vo/medic_PainSharp05.mp3", "vo/medic_LaughShort02.mp3" );
  14340. pKVFlag->SetString( "vo/medic_PainSharp06.mp3", "vo/medic_LaughShort03.mp3" );
  14341. pKVFlag->SetString( "vo/medic_PainSharp07.mp3", "vo/medic_LaughShort01.mp3" );
  14342. pKVFlag->SetString( "vo/medic_PainSharp08.mp3", "vo/medic_LaughShort02.mp3" );
  14343. pKVFlag->SetString( "vo/medic_AutoOnFire01.mp3", "vo/medic_PositiveVocalization01.mp3" );
  14344. pKVFlag->SetString( "vo/medic_AutoOnFire02.mp3", "vo/medic_PositiveVocalization02.mp3" );
  14345. pKVFlag->SetString( "vo/medic_AutoOnFire03.mp3", "vo/medic_PositiveVocalization03.mp3" );
  14346. pKVFlag->SetString( "vo/medic_AutoOnFire04.mp3", "vo/medic_PositiveVocalization04.mp3" );
  14347. pKVFlag->SetString( "vo/medic_AutoOnFire05.mp3", "vo/medic_PositiveVocalization05.mp3" );
  14348. pKVFlag->SetString( "vo/pyro_PainCrticialDeath01.mp3", "vo/pyro_LaughLong01.mp3" );
  14349. pKVFlag->SetString( "vo/pyro_PainCrticialDeath02.mp3", "vo/pyro_LaughLong01.mp3" );
  14350. pKVFlag->SetString( "vo/pyro_PainCrticialDeath03.mp3", "vo/pyro_LaughLong01.mp3" );
  14351. pKVFlag->SetString( "vo/pyro_PainSevere01.mp3", "vo/pyro_LaughHappy01.mp3" );
  14352. pKVFlag->SetString( "vo/pyro_PainSevere02.mp3", "vo/pyro_LaughHappy01.mp3" );
  14353. pKVFlag->SetString( "vo/pyro_PainSevere03.mp3", "vo/pyro_LaughHappy01.mp3" );
  14354. pKVFlag->SetString( "vo/pyro_PainSevere04.mp3", "vo/pyro_LaughHappy01.mp3" );
  14355. pKVFlag->SetString( "vo/pyro_PainSevere05.mp3", "vo/pyro_LaughHappy01.mp3" );
  14356. pKVFlag->SetString( "vo/pyro_PainSevere06.mp3", "vo/pyro_LaughHappy01.mp3" );
  14357. pKVFlag->SetString( "vo/pyro_AutoOnFire01.mp3", "vo/pyro_LaughHappy01.mp3" );
  14358. pKVFlag->SetString( "vo/pyro_AutoOnFire02.mp3", "vo/pyro_LaughHappy01.mp3" );
  14359. pKVFlag->SetString( "vo/scout_PainCrticialDeath01.mp3", "vo/scout_LaughLong01.mp3" );
  14360. pKVFlag->SetString( "vo/scout_PainCrticialDeath02.mp3", "vo/scout_LaughLong02.mp3" );
  14361. pKVFlag->SetString( "vo/scout_PainCrticialDeath03.mp3", "vo/scout_LaughLong01.mp3" );
  14362. pKVFlag->SetString( "vo/scout_PainSevere01.mp3", "vo/scout_LaughHappy01.mp3" );
  14363. pKVFlag->SetString( "vo/scout_PainSevere02.mp3", "vo/scout_LaughHappy02.mp3" );
  14364. pKVFlag->SetString( "vo/scout_PainSevere03.mp3", "vo/scout_LaughHappy03.mp3" );
  14365. pKVFlag->SetString( "vo/scout_PainSevere04.mp3", "vo/scout_LaughHappy04.mp3" );
  14366. pKVFlag->SetString( "vo/scout_PainSevere05.mp3", "vo/scout_LaughHappy01.mp3" );
  14367. pKVFlag->SetString( "vo/scout_PainSevere06.mp3", "vo/scout_LaughHappy02.mp3" );
  14368. pKVFlag->SetString( "vo/scout_PainSharp01.mp3", "vo/scout_LaughShort01.mp3" );
  14369. pKVFlag->SetString( "vo/scout_PainSharp02.mp3", "vo/scout_LaughShort02.mp3" );
  14370. pKVFlag->SetString( "vo/scout_PainSharp03.mp3", "vo/scout_LaughShort03.mp3" );
  14371. pKVFlag->SetString( "vo/scout_PainSharp04.mp3", "vo/scout_LaughShort04.mp3" );
  14372. pKVFlag->SetString( "vo/scout_PainSharp05.mp3", "vo/scout_LaughShort05.mp3" );
  14373. pKVFlag->SetString( "vo/scout_PainSharp06.mp3", "vo/scout_LaughShort01.mp3" );
  14374. pKVFlag->SetString( "vo/scout_PainSharp07.mp3", "vo/scout_LaughShort02.mp3" );
  14375. pKVFlag->SetString( "vo/scout_PainSharp08.mp3", "vo/scout_LaughShort03.mp3" );
  14376. pKVFlag->SetString( "vo/scout_AutoOnFire01.mp3", "vo/scout_PositiveVocalization02.mp3" );
  14377. pKVFlag->SetString( "vo/scout_AutoOnFire02.mp3", "vo/scout_PositiveVocalization03.mp3" );
  14378. pKVFlag->SetString( "vo/sniper_PainCrticialDeath01.mp3", "vo/sniper_LaughLong01.mp3" );
  14379. pKVFlag->SetString( "vo/sniper_PainCrticialDeath02.mp3", "vo/sniper_LaughLong02.mp3" );
  14380. pKVFlag->SetString( "vo/sniper_PainCrticialDeath03.mp3", "vo/sniper_LaughLong01.mp3" );
  14381. pKVFlag->SetString( "vo/sniper_PainCrticialDeath04.mp3", "vo/sniper_LaughLong02.mp3" );
  14382. pKVFlag->SetString( "vo/sniper_PainSevere01.mp3", "vo/sniper_LaughHappy01.mp3" );
  14383. pKVFlag->SetString( "vo/sniper_PainSevere02.mp3", "vo/sniper_LaughHappy02.mp3" );
  14384. pKVFlag->SetString( "vo/sniper_PainSevere03.mp3", "vo/sniper_LaughHappy01.mp3" );
  14385. pKVFlag->SetString( "vo/sniper_PainSevere04.mp3", "vo/sniper_LaughHappy02.mp3" );
  14386. pKVFlag->SetString( "vo/sniper_PainSharp01.mp3", "vo/sniper_LaughShort01.mp3" );
  14387. pKVFlag->SetString( "vo/sniper_PainSharp02.mp3", "vo/sniper_LaughShort02.mp3" );
  14388. pKVFlag->SetString( "vo/sniper_PainSharp03.mp3", "vo/sniper_LaughShort03.mp3" );
  14389. pKVFlag->SetString( "vo/sniper_PainSharp04.mp3", "vo/sniper_LaughShort04.mp3" );
  14390. pKVFlag->SetString( "vo/sniper_AutoOnFire01.mp3", "vo/sniper_PositiveVocalization02.mp3" );
  14391. pKVFlag->SetString( "vo/sniper_AutoOnFire02.mp3", "vo/sniper_PositiveVocalization03.mp3" );
  14392. pKVFlag->SetString( "vo/sniper_AutoOnFire03.mp3", "vo/sniper_PositiveVocalization05.mp3" );
  14393. pKVFlag->SetString( "vo/soldier_PainCrticialDeath01.mp3", "vo/soldier_LaughLong01.mp3" );
  14394. pKVFlag->SetString( "vo/soldier_PainCrticialDeath02.mp3", "vo/soldier_LaughLong02.mp3" );
  14395. pKVFlag->SetString( "vo/soldier_PainCrticialDeath03.mp3", "vo/soldier_LaughLong03.mp3" );
  14396. pKVFlag->SetString( "vo/soldier_PainCrticialDeath04.mp3", "vo/soldier_LaughLong01.mp3" );
  14397. pKVFlag->SetString( "vo/soldier_PainSevere01.mp3", "vo/soldier_LaughHappy01.mp3" );
  14398. pKVFlag->SetString( "vo/soldier_PainSevere02.mp3", "vo/soldier_LaughHappy02.mp3" );
  14399. pKVFlag->SetString( "vo/soldier_PainSevere03.mp3", "vo/soldier_LaughHappy03.mp3" );
  14400. pKVFlag->SetString( "vo/soldier_PainSevere04.mp3", "vo/soldier_LaughHappy01.mp3" );
  14401. pKVFlag->SetString( "vo/soldier_PainSevere05.mp3", "vo/soldier_LaughHappy02.mp3" );
  14402. pKVFlag->SetString( "vo/soldier_PainSevere06.mp3", "vo/soldier_LaughHappy03.mp3" );
  14403. pKVFlag->SetString( "vo/soldier_PainSharp01.mp3", "vo/soldier_LaughShort01.mp3" );
  14404. pKVFlag->SetString( "vo/soldier_PainSharp02.mp3", "vo/soldier_LaughShort02.mp3" );
  14405. pKVFlag->SetString( "vo/soldier_PainSharp03.mp3", "vo/soldier_LaughShort03.mp3" );
  14406. pKVFlag->SetString( "vo/soldier_PainSharp04.mp3", "vo/soldier_LaughShort04.mp3" );
  14407. pKVFlag->SetString( "vo/soldier_PainSharp05.mp3", "vo/soldier_LaughShort01.mp3" );
  14408. pKVFlag->SetString( "vo/soldier_PainSharp06.mp3", "vo/soldier_LaughShort02.mp3" );
  14409. pKVFlag->SetString( "vo/soldier_PainSharp07.mp3", "vo/soldier_LaughShort03.mp3" );
  14410. pKVFlag->SetString( "vo/soldier_PainSharp08.mp3", "vo/soldier_LaughShort04.mp3" );
  14411. pKVFlag->SetString( "vo/soldier_AutoOnFire01.mp3", "vo/soldier_PositiveVocalization01.mp3" );
  14412. pKVFlag->SetString( "vo/soldier_AutoOnFire02.mp3", "vo/soldier_PositiveVocalization02.mp3" );
  14413. pKVFlag->SetString( "vo/soldier_AutoOnFire03.mp3", "vo/soldier_PositiveVocalization03.mp3" );
  14414. pKVFlag->SetString( "vo/spy_PainCrticialDeath01.mp3", "vo/spy_LaughLong01.mp3" );
  14415. pKVFlag->SetString( "vo/spy_PainCrticialDeath02.mp3", "vo/spy_LaughLong01.mp3" );
  14416. pKVFlag->SetString( "vo/spy_PainCrticialDeath03.mp3", "vo/spy_LaughLong01.mp3" );
  14417. pKVFlag->SetString( "vo/spy_PainSevere01.mp3", "vo/spy_LaughHappy01.mp3" );
  14418. pKVFlag->SetString( "vo/spy_PainSevere02.mp3", "vo/spy_LaughHappy02.mp3" );
  14419. pKVFlag->SetString( "vo/spy_PainSevere03.mp3", "vo/spy_LaughHappy03.mp3" );
  14420. pKVFlag->SetString( "vo/spy_PainSevere04.mp3", "vo/spy_LaughHappy01.mp3" );
  14421. pKVFlag->SetString( "vo/spy_PainSevere05.mp3", "vo/spy_LaughHappy02.mp3" );
  14422. pKVFlag->SetString( "vo/spy_PainSharp01.mp3", "vo/spy_LaughShort01.mp3" );
  14423. pKVFlag->SetString( "vo/spy_PainSharp02.mp3", "vo/spy_LaughShort02.mp3" );
  14424. pKVFlag->SetString( "vo/spy_PainSharp03.mp3", "vo/spy_LaughShort03.mp3" );
  14425. pKVFlag->SetString( "vo/spy_PainSharp04.mp3", "vo/spy_LaughShort04.mp3" );
  14426. pKVFlag->SetString( "vo/spy_AutoOnFire01.mp3", "vo/spy_PositiveVocalization02.mp3" );
  14427. pKVFlag->SetString( "vo/spy_AutoOnFire02.mp3", "vo/spy_PositiveVocalization04.mp3" );
  14428. pKVFlag->SetString( "vo/spy_AutoOnFire03.mp3", "vo/spy_PositiveVocalization05.mp3" );
  14429. pKVBlock->AddSubKey( pKVFlag );
  14430. // Spooky Vision
  14431. //pKVFlag = new KeyValues( "2" ); // TF_VISION_FILTER_HALLOWEEN
  14432. //pKVBlock->AddSubKey( pKVFlag );
  14433. // **************************************************************************************************
  14434. // WEAPONS
  14435. pKVBlock = new KeyValues( "weapons" );
  14436. m_pkvVisionFilterTranslations->AddSubKey( pKVBlock );
  14437. // No special vision
  14438. pKVFlag = new KeyValues( "0" );
  14439. pKVFlag->SetString( "models/weapons/c_models/c_rainblower/c_rainblower.mdl", "models/weapons/c_models/c_flamethrower/c_flamethrower.mdl" );
  14440. pKVFlag->SetString( "models/weapons/c_models/c_lollichop/c_lollichop.mdl", "models/weapons/w_models/w_fireaxe.mdl" );
  14441. pKVBlock->AddSubKey( pKVFlag );
  14442. // Pyrovision
  14443. pKVFlag = new KeyValues( "1" );
  14444. pKVFlag->SetString( "models/weapons/c_models/c_rainblower/c_rainblower.mdl", "models/weapons/c_models/c_rainblower/c_rainblower.mdl" ); //explicit set for pyrovision
  14445. pKVFlag->SetString( "models/weapons/c_models/c_lollichop/c_lollichop.mdl", "models/weapons/c_models/c_lollichop/c_lollichop.mdl" );
  14446. pKVFlag->SetString( "models/player/items/demo/demo_bombs.mdl", "models/player/items/all_class/mtp_bottle_demo.mdl" );
  14447. pKVFlag->SetString( "models/player/items/pyro/xms_pyro_bells.mdl", "models/player/items/all_class/mtp_bottle_pyro.mdl" );
  14448. pKVFlag->SetString( "models/player/items/soldier/ds_can_grenades.mdl", "models/player/items/all_class/mtp_bottle_soldier.mdl" );
  14449. pKVBlock->AddSubKey( pKVFlag );
  14450. // Spooky Vision
  14451. //pKVFlag = new KeyValues( "2" );
  14452. //pKVBlock->AddSubKey( pKVFlag );
  14453. }
  14454. //-----------------------------------------------------------------------------
  14455. // Purpose:
  14456. //-----------------------------------------------------------------------------
  14457. bool CTFGameRules::UseSillyGibs( void )
  14458. {
  14459. // Use silly gibs if the local player has an item that allows them to see it (Pyro Goggles)
  14460. if ( IsLocalPlayerUsingVisionFilterFlags( TF_VISION_FILTER_PYRO ) )
  14461. return true;
  14462. if ( UTIL_IsLowViolence() )
  14463. return true;
  14464. return m_bSillyGibs;
  14465. }
  14466. //-----------------------------------------------------------------------------
  14467. // Purpose:
  14468. //-----------------------------------------------------------------------------
  14469. bool CTFGameRules::AllowWeatherParticles( void )
  14470. {
  14471. return ( tf_particles_disable_weather.GetBool() == false );
  14472. }
  14473. bool CTFGameRules::AllowMapVisionFilterShaders( void )
  14474. {
  14475. if ( !m_pkvVisionFilterShadersMapWhitelist )
  14476. return false;
  14477. const char *pMapName = engine->GetLevelName();
  14478. while ( pMapName && pMapName[ 0 ] != '\\' && pMapName[ 0 ] != '/' )
  14479. {
  14480. pMapName++;
  14481. }
  14482. if ( !pMapName )
  14483. return false;
  14484. pMapName++;
  14485. return m_pkvVisionFilterShadersMapWhitelist->GetInt( pMapName, 0 ) != 0;
  14486. }
  14487. //-----------------------------------------------------------------------------
  14488. // Purpose:
  14489. //-----------------------------------------------------------------------------
  14490. const char* CTFGameRules::TranslateEffectForVisionFilter( const char *pchEffectType, const char *pchEffectName )
  14491. {
  14492. if ( !pchEffectType || !pchEffectName )
  14493. {
  14494. return pchEffectName;
  14495. }
  14496. CUtlVector<const char *> vecNames;
  14497. vecNames.AddToTail( pchEffectName );
  14498. // Swap the effect if the local player has an item that allows them to see it (Pyro Goggles)
  14499. bool bWeaponsOnly = FStrEq( pchEffectType, "weapons" );
  14500. int nVisionOptInFlags = GetLocalPlayerVisionFilterFlags( bWeaponsOnly );
  14501. KeyValues *pkvParticles = m_pkvVisionFilterTranslations->FindKey( pchEffectType );
  14502. if ( pkvParticles )
  14503. {
  14504. for ( KeyValues *pkvFlag = pkvParticles->GetFirstTrueSubKey(); pkvFlag != NULL; pkvFlag = pkvFlag->GetNextTrueSubKey() )
  14505. {
  14506. int nFlag = atoi( pkvFlag->GetName() );
  14507. // Grab any potential translations for this item
  14508. // Always grab default (No vision) names if they exist
  14509. if ( ( nVisionOptInFlags & nFlag ) != 0 || nFlag == 0 )
  14510. {
  14511. // We either have this vision flag or we have no flags and this is the no flag block
  14512. const char *pchTranslatedString = pkvFlag->GetString( pchEffectName, NULL );
  14513. if ( pchTranslatedString )
  14514. {
  14515. vecNames.AddToHead( pchTranslatedString );
  14516. }
  14517. }
  14518. }
  14519. }
  14520. // Return the top item in the list. Currently Halloween replacements would out prioritize Pyrovision if there is any overlap
  14521. return vecNames.Head();
  14522. }
  14523. //-----------------------------------------------------------------------------
  14524. // Purpose:
  14525. //-----------------------------------------------------------------------------
  14526. void CTFGameRules::ModifySentChat( char *pBuf, int iBufSize )
  14527. {
  14528. if ( IsInMedievalMode() && tf_medieval_autorp.GetBool() && english.GetBool() )
  14529. {
  14530. AutoRP()->ApplyRPTo( pBuf, iBufSize );
  14531. }
  14532. // replace all " with ' to prevent exploits related to chat text
  14533. // example: ";exit
  14534. for ( char *ch = pBuf; *ch != 0; ch++ )
  14535. {
  14536. if ( *ch == '"' )
  14537. {
  14538. *ch = '\'';
  14539. }
  14540. }
  14541. }
  14542. //-----------------------------------------------------------------------------
  14543. // Purpose:
  14544. //-----------------------------------------------------------------------------
  14545. bool CTFGameRules::AllowMapParticleEffect( const char *pszParticleEffect )
  14546. {
  14547. static const char *s_WeatherEffects[] =
  14548. {
  14549. "tf_gamerules",
  14550. "env_rain_001",
  14551. "env_rain_002_256",
  14552. "env_rain_ripples",
  14553. "env_snow_light_001",
  14554. "env_rain_gutterdrip",
  14555. "env_rain_guttersplash",
  14556. "", // END Marker
  14557. };
  14558. if ( !AllowWeatherParticles() )
  14559. {
  14560. if ( FindInList( s_WeatherEffects, pszParticleEffect ) )
  14561. return false;
  14562. }
  14563. return true;
  14564. }
  14565. //-----------------------------------------------------------------------------
  14566. // Purpose:
  14567. //-----------------------------------------------------------------------------
  14568. void CTFGameRules::GetTeamGlowColor( int nTeam, float &r, float &g, float &b )
  14569. {
  14570. if ( nTeam == TF_TEAM_RED )
  14571. {
  14572. r = 0.74f;
  14573. g = 0.23f;
  14574. b = 0.23f;
  14575. }
  14576. else if ( nTeam == TF_TEAM_BLUE )
  14577. {
  14578. r = 0.49f;
  14579. g = 0.66f;
  14580. b = 0.77f;
  14581. }
  14582. else
  14583. {
  14584. r = 0.76f;
  14585. g = 0.76f;
  14586. b = 0.76f;
  14587. }
  14588. }
  14589. //-----------------------------------------------------------------------------
  14590. // Purpose:
  14591. //-----------------------------------------------------------------------------
  14592. bool CTFGameRules::ShouldConfirmOnDisconnect()
  14593. {
  14594. // Add any game mode which uses matchmaking here. Note that the disconnect dialog checks if it should be showing abandons and such.
  14595. return ( IsMannVsMachineMode() && GTFGCClientSystem()->GetSearchPlayForBraggingRights() ) ||
  14596. ( IsCompetitiveMode() && GTFGCClientSystem()->GetLobby() );
  14597. }
  14598. //-----------------------------------------------------------------------------
  14599. // Purpose:
  14600. //-----------------------------------------------------------------------------
  14601. bool CTFGameRules::ShouldShowPreRoundDoors() const
  14602. {
  14603. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  14604. if ( pMatchDesc )
  14605. {
  14606. return pMatchDesc->m_params.m_bShowPreRoundDoors;
  14607. }
  14608. return false;
  14609. }
  14610. #endif
  14611. //-----------------------------------------------------------------------------
  14612. // Purpose:
  14613. //-----------------------------------------------------------------------------
  14614. int CTFGameRules::GetClassLimit( int iClass )
  14615. {
  14616. if ( IsInTournamentMode() || IsPasstimeMode() )
  14617. {
  14618. switch ( iClass )
  14619. {
  14620. case TF_CLASS_SCOUT: return tf_tournament_classlimit_scout.GetInt(); break;
  14621. case TF_CLASS_SNIPER: return tf_tournament_classlimit_sniper.GetInt(); break;
  14622. case TF_CLASS_SOLDIER: return tf_tournament_classlimit_soldier.GetInt(); break;
  14623. case TF_CLASS_DEMOMAN: return tf_tournament_classlimit_demoman.GetInt(); break;
  14624. case TF_CLASS_MEDIC: return tf_tournament_classlimit_medic.GetInt(); break;
  14625. case TF_CLASS_HEAVYWEAPONS: return tf_tournament_classlimit_heavy.GetInt(); break;
  14626. case TF_CLASS_PYRO: return tf_tournament_classlimit_pyro.GetInt(); break;
  14627. case TF_CLASS_SPY: return tf_tournament_classlimit_spy.GetInt(); break;
  14628. case TF_CLASS_ENGINEER: return tf_tournament_classlimit_engineer.GetInt(); break;
  14629. default:
  14630. break;
  14631. }
  14632. }
  14633. else if ( IsInHighlanderMode() )
  14634. {
  14635. return 1;
  14636. }
  14637. else if ( tf_classlimit.GetInt() )
  14638. {
  14639. return tf_classlimit.GetInt();
  14640. }
  14641. return NO_CLASS_LIMIT;
  14642. }
  14643. //-----------------------------------------------------------------------------
  14644. // Purpose:
  14645. //-----------------------------------------------------------------------------
  14646. bool CTFGameRules::CanPlayerChooseClass( CBasePlayer *pPlayer, int iClass )
  14647. {
  14648. int iClassLimit = GetClassLimit( iClass );
  14649. #ifdef TF_RAID_MODE
  14650. if ( IsRaidMode() && !pPlayer->IsBot() )
  14651. {
  14652. // bots are exempt from class limits, to allow for additional support bot "friends"
  14653. if ( pPlayer->GetTeamNumber() == TF_TEAM_BLUE )
  14654. {
  14655. if ( tf_raid_allow_all_classes.GetBool() == false )
  14656. {
  14657. if ( iClass == TF_CLASS_SCOUT )
  14658. return false;
  14659. if ( iClass == TF_CLASS_SPY )
  14660. return false;
  14661. }
  14662. if ( tf_raid_enforce_unique_classes.GetBool() )
  14663. {
  14664. // only one of each class on the raiding team
  14665. iClassLimit = 1;
  14666. }
  14667. }
  14668. }
  14669. else if ( IsBossBattleMode() )
  14670. {
  14671. return true;
  14672. }
  14673. else
  14674. #endif // TF_RAID_MODE
  14675. if ( iClassLimit == NO_CLASS_LIMIT )
  14676. return true;
  14677. if ( pPlayer->GetTeamNumber() != TF_TEAM_BLUE && pPlayer->GetTeamNumber() != TF_TEAM_RED )
  14678. return true;
  14679. #ifdef GAME_DLL
  14680. CTFTeam *pTeam = assert_cast<CTFTeam*>(pPlayer->GetTeam());
  14681. #else
  14682. C_TFTeam *pTeam = assert_cast<C_TFTeam*>(pPlayer->GetTeam());
  14683. #endif
  14684. if ( !pTeam )
  14685. return true;
  14686. int iTeamClassCount = 0;
  14687. for ( int iPlayer = 0; iPlayer < pTeam->GetNumPlayers(); iPlayer++ )
  14688. {
  14689. CTFPlayer *pTFPlayer = ToTFPlayer( pTeam->GetPlayer( iPlayer ) );
  14690. if ( pTFPlayer && pTFPlayer != pPlayer && pTFPlayer->GetPlayerClass()->GetClassIndex() == iClass )
  14691. {
  14692. iTeamClassCount++;
  14693. }
  14694. }
  14695. return ( iTeamClassCount < iClassLimit );
  14696. }
  14697. //-----------------------------------------------------------------------------
  14698. // Purpose:
  14699. //-----------------------------------------------------------------------------
  14700. bool CTFGameRules::ShouldBalanceTeams( void )
  14701. {
  14702. // never autobalance the teams for managed matches using the old system
  14703. if ( GetMatchGroupDescription( GetCurrentMatchGroup() ) )
  14704. return false;
  14705. bool bDisableBalancing = false;
  14706. #ifdef STAGING_ONLY
  14707. bDisableBalancing = IsBountyMode();
  14708. #endif // STAGING_ONLY
  14709. if ( IsPVEModeActive() || bDisableBalancing )
  14710. return false;
  14711. // don't balance the teams while players are in hell
  14712. if ( ArePlayersInHell() )
  14713. return false;
  14714. return BaseClass::ShouldBalanceTeams();
  14715. }
  14716. //-----------------------------------------------------------------------------
  14717. // Purpose:
  14718. //-----------------------------------------------------------------------------
  14719. int CTFGameRules::GetBonusRoundTime( bool bGameOver /* = false*/ )
  14720. {
  14721. if ( IsMannVsMachineMode() )
  14722. {
  14723. return 5;
  14724. }
  14725. else if ( IsCompetitiveMode() && bGameOver )
  14726. {
  14727. if ( IsMatchTypeCompetitive() )
  14728. {
  14729. return 5;
  14730. }
  14731. }
  14732. return BaseClass::GetBonusRoundTime( bGameOver );
  14733. }
  14734. #ifdef GAME_DLL
  14735. bool CTFGameRules::CanBotChangeClass( CBasePlayer* pPlayer )
  14736. {
  14737. // if there's a roster for this bot's team, check to see if the level designer has allowed the bot to change class
  14738. // used when the bot dies and wants to see if it can change class
  14739. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  14740. if ( pTFPlayer && pTFPlayer->GetPlayerClass() && pTFPlayer->GetPlayerClass()->GetClassIndex() != TF_CLASS_UNDEFINED )
  14741. {
  14742. switch ( pPlayer->GetTeamNumber() )
  14743. {
  14744. case TF_TEAM_RED: return m_hRedBotRoster ? m_hRedBotRoster->IsClassChangeAllowed() : true; break;
  14745. case TF_TEAM_BLUE: return m_hBlueBotRoster ? m_hBlueBotRoster->IsClassChangeAllowed() : true; break;
  14746. }
  14747. }
  14748. return true;
  14749. }
  14750. bool CTFGameRules::CanBotChooseClass( CBasePlayer *pPlayer, int iClass )
  14751. {
  14752. // if there's a roster for this bot's team, then check to see if the class the bot has requested is allowed by the roster
  14753. bool bCanChooseClass = CanPlayerChooseClass( pPlayer, iClass );
  14754. if ( bCanChooseClass )
  14755. {
  14756. // now check rosters...
  14757. switch ( pPlayer->GetTeamNumber() )
  14758. {
  14759. case TF_TEAM_RED:
  14760. bCanChooseClass = m_hRedBotRoster ? m_hRedBotRoster->IsClassAllowed( iClass ) : true;
  14761. break;
  14762. case TF_TEAM_BLUE:
  14763. bCanChooseClass = m_hBlueBotRoster ? m_hBlueBotRoster->IsClassAllowed( iClass ) : true;
  14764. break;
  14765. default:
  14766. // no roster - spectator team
  14767. bCanChooseClass = true;
  14768. break;
  14769. }
  14770. }
  14771. return bCanChooseClass;
  14772. }
  14773. //-----------------------------------------------------------------------------
  14774. // Populate vector with set of control points the player needs to capture
  14775. void CTFGameRules::CollectCapturePoints( CBasePlayer *player, CUtlVector< CTeamControlPoint * > *captureVector ) const
  14776. {
  14777. if ( !captureVector )
  14778. return;
  14779. captureVector->RemoveAll();
  14780. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  14781. if ( pMaster )
  14782. {
  14783. // special case hack for KotH mode to use control points that are locked at the start of the round
  14784. if ( IsInKothMode() && pMaster->GetNumPoints() == 1 )
  14785. {
  14786. captureVector->AddToTail( pMaster->GetControlPoint( 0 ) );
  14787. return;
  14788. }
  14789. for( int i=0; i<pMaster->GetNumPoints(); ++i )
  14790. {
  14791. CTeamControlPoint *point = pMaster->GetControlPoint( i );
  14792. if ( point && pMaster->IsInRound( point ) )
  14793. {
  14794. if ( ObjectiveResource()->GetOwningTeam( point->GetPointIndex() ) == player->GetTeamNumber() )
  14795. continue;
  14796. if ( player && player->IsBot() && point->ShouldBotsIgnore() )
  14797. continue;
  14798. if ( ObjectiveResource()->TeamCanCapPoint( point->GetPointIndex(), player->GetTeamNumber() ) )
  14799. {
  14800. if ( TeamplayGameRules()->TeamMayCapturePoint( player->GetTeamNumber(), point->GetPointIndex() ) )
  14801. {
  14802. // unlocked point not on our team available to capture
  14803. captureVector->AddToTail( point );
  14804. }
  14805. }
  14806. }
  14807. }
  14808. }
  14809. }
  14810. //-----------------------------------------------------------------------------
  14811. // Populate vector with set of control points the player needs to defend from capture
  14812. void CTFGameRules::CollectDefendPoints( CBasePlayer *player, CUtlVector< CTeamControlPoint * > *defendVector ) const
  14813. {
  14814. if ( !defendVector )
  14815. return;
  14816. defendVector->RemoveAll();
  14817. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  14818. if ( pMaster )
  14819. {
  14820. for( int i=0; i<pMaster->GetNumPoints(); ++i )
  14821. {
  14822. CTeamControlPoint *point = pMaster->GetControlPoint( i );
  14823. if ( point && pMaster->IsInRound( point ) )
  14824. {
  14825. if ( ObjectiveResource()->GetOwningTeam( point->GetPointIndex() ) != player->GetTeamNumber() )
  14826. continue;
  14827. if ( player && player->IsBot() && point->ShouldBotsIgnore() )
  14828. continue;
  14829. if ( ObjectiveResource()->TeamCanCapPoint( point->GetPointIndex(), GetEnemyTeam( player->GetTeamNumber() ) ) )
  14830. {
  14831. if ( TeamplayGameRules()->TeamMayCapturePoint( GetEnemyTeam( player->GetTeamNumber() ), point->GetPointIndex() ) )
  14832. {
  14833. // unlocked point on our team vulnerable to capture
  14834. defendVector->AddToTail( point );
  14835. }
  14836. }
  14837. }
  14838. }
  14839. }
  14840. }
  14841. //-----------------------------------------------------------------------------
  14842. CObjectSentrygun *CTFGameRules::FindSentryGunWithMostKills( int team ) const
  14843. {
  14844. CObjectSentrygun *dangerousSentry = NULL;
  14845. int dangerousSentryKills = -1;
  14846. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  14847. {
  14848. CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  14849. if ( pObj->ObjectType() == OBJ_SENTRYGUN && pObj->GetTeamNumber() == team && pObj->GetKills() >= dangerousSentryKills )
  14850. {
  14851. dangerousSentryKills = pObj->GetKills();
  14852. dangerousSentry = static_cast<CObjectSentrygun *>( pObj );
  14853. }
  14854. }
  14855. return dangerousSentry;
  14856. }
  14857. //-----------------------------------------------------------------------------
  14858. // Purpose:
  14859. //-----------------------------------------------------------------------------
  14860. bool CTFGameRules::ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen )
  14861. {
  14862. if ( IsMannVsMachineMode() )
  14863. {
  14864. int nCount = 0;
  14865. CTFPlayer *pPlayer;
  14866. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  14867. {
  14868. pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  14869. if ( !pPlayer || pPlayer->IsFakeClient() )
  14870. continue;
  14871. nCount++;
  14872. }
  14873. if ( nCount >= kMVM_MaxConnectedPlayers )
  14874. return false;
  14875. }
  14876. bool bRet = BaseClass::ClientConnected( pEntity, pszName, pszAddress, reject, maxrejectlen );
  14877. if ( bRet )
  14878. {
  14879. const CSteamID *steamID = engine->GetClientSteamID( pEntity );
  14880. if ( steamID && steamID->IsValid() )
  14881. {
  14882. // Invalid steamIDs wont be known to the GC system, but it has a SteamIDAllowedToConnect() hook that would
  14883. // allow it to reject the connect in the first place in a matchmaking scenario where cares.
  14884. GTFGCClientSystem()->ClientConnected( *steamID, pEntity );
  14885. }
  14886. }
  14887. return true;
  14888. }
  14889. //-----------------------------------------------------------------------------
  14890. // Purpose:
  14891. //-----------------------------------------------------------------------------
  14892. bool CTFGameRules::ShouldMakeChristmasAmmoPack( void )
  14893. {
  14894. if ( IsInTournamentMode() && !IsMatchTypeCasual() )
  14895. return false;
  14896. if ( IsMannVsMachineMode() )
  14897. return false;
  14898. if ( mp_holiday_nogifts.GetBool() == true )
  14899. return false;
  14900. if ( IsHolidayActive( kHoliday_Christmas ) == false )
  14901. return false;
  14902. return ( RandomInt( 0, 100 ) < 10 );
  14903. }
  14904. //-----------------------------------------------------------------------------
  14905. // Purpose: **** DO NOT SHIP THIS IN REL/HL2 ****
  14906. //-----------------------------------------------------------------------------
  14907. void CTFGameRules::UpdatePeriodicEvent( CTFPlayer *pPlayer, eEconPeriodicScoreEvents eEvent, uint32 nCount )
  14908. {
  14909. CSteamID steamID;
  14910. if ( !pPlayer || !pPlayer->GetSteamID( &steamID ) || !steamID.IsValid() )
  14911. return;
  14912. GCSDK::CProtoBufMsg<CMsgUpdatePeriodicEvent> msg( k_EMsgGC_UpdatePeriodicEvent );
  14913. msg.Body().set_account_id( steamID.GetAccountID() );
  14914. msg.Body().set_event_type( eEvent );
  14915. msg.Body().set_amount( nCount );
  14916. GCClientSystem()->BSendMessage( msg );
  14917. }
  14918. #endif // GAME_DLL
  14919. #ifndef CLIENT_DLL
  14920. void CTFGameRules::Status( void (*print) (const char *fmt, ...) )
  14921. {
  14922. // print( "Total Time: %d seconds\n", CTF_GameStats.m_currentMap.m_Header.m_iTotalTime );
  14923. // priprint( "Blue Team Wins: %d\n", CTF_GameStats.m_currentMap.m_Header.m_iBlueWins );
  14924. // priprint( "Red Team Wins: %d\n", CTF_GameStats.m_currentMap.m_Header.m_iRedWins );
  14925. // priprint( "Stalemates: %d\n", CTF_GameStats.m_currentMap.m_Header.m_iStalemates );
  14926. print( " Spawns Points Kills Deaths Assists\n" );
  14927. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ )
  14928. {
  14929. TF_Gamestats_ClassStats_t &Stats = CTF_GameStats.m_currentMap.m_aClassStats[ iClass ];
  14930. print( "%-8s %6d %6d %5d %6d %7d\n",
  14931. g_aPlayerClassNames_NonLocalized[ iClass ],
  14932. Stats.iSpawns, Stats.iScore, Stats.iKills, Stats.iDeaths, Stats.iAssists );
  14933. }
  14934. print( "\n" );
  14935. }
  14936. #endif // !CLIENT_DLL
  14937. //-----------------------------------------------------------------------------
  14938. // Purpose:
  14939. //-----------------------------------------------------------------------------
  14940. bool CTFGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 )
  14941. {
  14942. if ( collisionGroup0 > collisionGroup1 )
  14943. {
  14944. // swap so that lowest is always first
  14945. ::V_swap( collisionGroup0, collisionGroup1 );
  14946. }
  14947. //Don't stand on COLLISION_GROUP_WEAPONs
  14948. if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT &&
  14949. collisionGroup1 == COLLISION_GROUP_WEAPON )
  14950. {
  14951. return false;
  14952. }
  14953. // Don't stand on projectiles
  14954. if( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT &&
  14955. collisionGroup1 == COLLISION_GROUP_PROJECTILE )
  14956. {
  14957. return false;
  14958. }
  14959. // Rockets need to collide with players when they hit, but
  14960. // be ignored by player movement checks
  14961. if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER ) &&
  14962. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS || collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14963. return true;
  14964. if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ) &&
  14965. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS || collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14966. return false;
  14967. if ( ( collisionGroup0 == COLLISION_GROUP_WEAPON ) &&
  14968. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS || collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14969. return false;
  14970. if ( ( collisionGroup0 == TF_COLLISIONGROUP_GRENADES ) &&
  14971. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS || collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14972. return false;
  14973. if ( ( collisionGroup0 == COLLISION_GROUP_PROJECTILE ) &&
  14974. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKETS || collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14975. return false;
  14976. if ( ( collisionGroup0 == TFCOLLISION_GROUP_ROCKETS ) &&
  14977. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14978. return false;
  14979. if ( ( collisionGroup0 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) &&
  14980. ( collisionGroup1 == TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS ) )
  14981. return false;
  14982. // Grenades don't collide with players. They handle collision while flying around manually.
  14983. if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER ) &&
  14984. ( collisionGroup1 == TF_COLLISIONGROUP_GRENADES ) )
  14985. return false;
  14986. if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ) &&
  14987. ( collisionGroup1 == TF_COLLISIONGROUP_GRENADES ) )
  14988. return false;
  14989. // Respawn rooms only collide with players
  14990. if ( collisionGroup1 == TFCOLLISION_GROUP_RESPAWNROOMS )
  14991. return ( collisionGroup0 == COLLISION_GROUP_PLAYER ) || ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT );
  14992. /* if ( collisionGroup0 == COLLISION_GROUP_PLAYER )
  14993. {
  14994. // Players don't collide with objects or other players
  14995. if ( collisionGroup1 == COLLISION_GROUP_PLAYER )
  14996. return false;
  14997. }
  14998. if ( collisionGroup1 == COLLISION_GROUP_PLAYER_MOVEMENT )
  14999. {
  15000. // This is only for probing, so it better not be on both sides!!!
  15001. Assert( collisionGroup0 != COLLISION_GROUP_PLAYER_MOVEMENT );
  15002. // No collide with players any more
  15003. // Nor with objects or grenades
  15004. switch ( collisionGroup0 )
  15005. {
  15006. default:
  15007. break;
  15008. case COLLISION_GROUP_PLAYER:
  15009. return false;
  15010. }
  15011. }
  15012. */
  15013. // don't want caltrops and other grenades colliding with each other
  15014. // caltops getting stuck on other caltrops, etc.)
  15015. if ( ( collisionGroup0 == TF_COLLISIONGROUP_GRENADES ) &&
  15016. ( collisionGroup1 == TF_COLLISIONGROUP_GRENADES ) )
  15017. {
  15018. return false;
  15019. }
  15020. if ( collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT &&
  15021. collisionGroup1 == TFCOLLISION_GROUP_COMBATOBJECT )
  15022. {
  15023. return false;
  15024. }
  15025. if ( collisionGroup0 == COLLISION_GROUP_PLAYER &&
  15026. collisionGroup1 == TFCOLLISION_GROUP_COMBATOBJECT )
  15027. {
  15028. return false;
  15029. }
  15030. if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT ) &&
  15031. collisionGroup1 == TFCOLLISION_GROUP_TANK )
  15032. {
  15033. return false;
  15034. }
  15035. return BaseClass::ShouldCollide( collisionGroup0, collisionGroup1 );
  15036. }
  15037. //-----------------------------------------------------------------------------
  15038. // Purpose: Return the value of this player towards capturing a point
  15039. //-----------------------------------------------------------------------------
  15040. int CTFGameRules::GetCaptureValueForPlayer( CBasePlayer *pPlayer )
  15041. {
  15042. #ifdef GAME_DLL
  15043. #ifdef TF_CREEP_MODE
  15044. if ( IsCreepWaveMode() )
  15045. {
  15046. CTFBot *bot = ToTFBot( pPlayer );
  15047. if ( !bot || !bot->HasAttribute( CTFBot::IS_NPC ) )
  15048. {
  15049. // only creeps can influence points
  15050. return 0;
  15051. }
  15052. }
  15053. #endif
  15054. #endif
  15055. int nValue = BaseClass::GetCaptureValueForPlayer( pPlayer );
  15056. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  15057. if ( pTFPlayer->IsPlayerClass( TF_CLASS_SCOUT ) )
  15058. {
  15059. if ( mp_capstyle.GetInt() == 1 )
  15060. {
  15061. // Scouts count for 2 people in timebased capping.
  15062. nValue = 2;
  15063. }
  15064. else
  15065. {
  15066. // Scouts can cap all points on their own.
  15067. nValue = 10;
  15068. }
  15069. }
  15070. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, nValue, add_player_capturevalue );
  15071. return nValue;
  15072. }
  15073. //-----------------------------------------------------------------------------
  15074. // Purpose:
  15075. //-----------------------------------------------------------------------------
  15076. int CTFGameRules::GetTimeLeft( void )
  15077. {
  15078. float flTimeLimit = mp_timelimit.GetInt() * 60;
  15079. Assert( flTimeLimit > 0 && "Should not call this function when !IsGameUnderTimeLimit" );
  15080. float flMapChangeTime = m_flMapResetTime + flTimeLimit;
  15081. int iTime = (int)(flMapChangeTime - gpGlobals->curtime);
  15082. if ( iTime < 0 )
  15083. {
  15084. iTime = 0;
  15085. }
  15086. return ( iTime );
  15087. }
  15088. //-----------------------------------------------------------------------------
  15089. // Purpose:
  15090. //-----------------------------------------------------------------------------
  15091. void CTFGameRules::FireGameEvent( IGameEvent *event )
  15092. {
  15093. const char *eventName = event->GetName();
  15094. #ifdef GAME_DLL
  15095. if ( !Q_strcmp( eventName, "teamplay_point_captured" ) )
  15096. {
  15097. if ( IsMannVsMachineMode() )
  15098. return;
  15099. // keep track of how many times each team caps
  15100. int iTeam = event->GetInt( "team" );
  15101. Assert( iTeam >= FIRST_GAME_TEAM && iTeam < TF_TEAM_COUNT );
  15102. m_iNumCaps[iTeam]++;
  15103. // award a capture to all capping players
  15104. const char *cappers = event->GetString( "cappers" );
  15105. Q_strncpy( m_szMostRecentCappers, cappers, ARRAYSIZE( m_szMostRecentCappers ) );
  15106. for ( int i =0; i < Q_strlen( cappers ); i++ )
  15107. {
  15108. int iPlayerIndex = (int) cappers[i];
  15109. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  15110. if ( pPlayer )
  15111. {
  15112. CTF_GameStats.Event_PlayerCapturedPoint( pPlayer );
  15113. if ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && GetGameType() == TF_GAMETYPE_ESCORT )
  15114. {
  15115. pPlayer->AwardAchievement( ACHIEVEMENT_TF_HEAVY_PAYLOAD_CAP_GRIND );
  15116. }
  15117. // Give money and experience
  15118. int nAmount = CalculateCurrencyAmount_ByType( TF_CURRENCY_CAPTURED_OBJECTIVE );
  15119. #ifdef STAGING_ONLY
  15120. if ( GameModeUsesExperience() )
  15121. {
  15122. pPlayer->AddExperiencePoints( nAmount );
  15123. }
  15124. #endif // STAGING_ONLY
  15125. DistributeCurrencyAmount( nAmount, pPlayer, false );
  15126. }
  15127. }
  15128. // Halloween 2012 doesn't want ghosts to spawn when the point is captured
  15129. if( !IsHalloweenScenario( HALLOWEEN_SCENARIO_LAKESIDE ) )
  15130. {
  15131. // for 2011 Halloween map
  15132. BeginHaunting( 4, 25.f, 35.f );
  15133. }
  15134. }
  15135. else if ( !Q_strcmp( eventName, "teamplay_capture_blocked" ) )
  15136. {
  15137. int iPlayerIndex = event->GetInt( "blocker" );
  15138. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  15139. CTF_GameStats.Event_PlayerDefendedPoint( pPlayer );
  15140. pPlayer->m_Shared.CheckForAchievement( ACHIEVEMENT_TF_MEDIC_CHARGE_BLOCKER );
  15141. }
  15142. else if ( !Q_strcmp( eventName, "teamplay_round_win" ) )
  15143. {
  15144. int iWinningTeam = event->GetInt( "team" );
  15145. bool bFullRound = event->GetBool( "full_round" );
  15146. float flRoundTime = event->GetFloat( "round_time" );
  15147. bool bWasSuddenDeath = event->GetBool( "was_sudden_death" );
  15148. CTF_GameStats.Event_RoundEnd( iWinningTeam, bFullRound, flRoundTime, bWasSuddenDeath );
  15149. }
  15150. else if ( !Q_strcmp( eventName, "teamplay_setup_finished" ) )
  15151. {
  15152. if ( IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
  15153. {
  15154. m_doomsdaySetupTimer.Start( 1 );
  15155. }
  15156. }
  15157. else if ( !Q_strcmp( eventName, "teamplay_flag_event" ) )
  15158. {
  15159. // if this is a capture event, remember the player who made the capture
  15160. int iEventType = event->GetInt( "eventtype" );
  15161. if ( TF_FLAGEVENT_CAPTURE == iEventType )
  15162. {
  15163. int iPlayerIndex = event->GetInt( "player" );
  15164. m_szMostRecentCappers[0] = iPlayerIndex;
  15165. m_szMostRecentCappers[1] = 0;
  15166. }
  15167. }
  15168. else if ( !Q_strcmp( eventName, "player_escort_score" ) )
  15169. {
  15170. int iPlayer = event->GetInt( "player", 0 );
  15171. int iPoints = event->GetInt( "points", 0 );
  15172. if ( iPlayer > 0 && iPlayer <= MAX_PLAYERS )
  15173. {
  15174. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex(iPlayer) );
  15175. if ( pPlayer )
  15176. {
  15177. CTF_GameStats.Event_PlayerScoresEscortPoints( pPlayer, iPoints );
  15178. int nAmount = CalculateCurrencyAmount_ByType( TF_CURRENCY_ESCORT_REWARD );
  15179. #ifdef STAGING_ONLY
  15180. if ( GameModeUsesExperience() )
  15181. {
  15182. pPlayer->AddExperiencePoints( nAmount * iPoints );
  15183. }
  15184. #endif // STAGING_ONLY
  15185. DistributeCurrencyAmount( ( nAmount * iPoints ), pPlayer, false );
  15186. if ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && GetGameType() == TF_GAMETYPE_ESCORT )
  15187. {
  15188. for ( int i = 0 ; i < iPoints ; i++ )
  15189. {
  15190. pPlayer->AwardAchievement( ACHIEVEMENT_TF_HEAVY_PAYLOAD_CAP_GRIND );
  15191. }
  15192. }
  15193. }
  15194. }
  15195. }
  15196. else if ( !Q_strcmp( eventName, "player_disconnect" ) )
  15197. {
  15198. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByUserId( event->GetInt("userid") ) );
  15199. // @note Tom Bui: this really sucks, but we don't know the reason other than the string...
  15200. const char *pReason = event->GetString( "reason" );
  15201. if ( !Q_strncmp( pReason, "Kicked", ARRAYSIZE( "Kicked" ) - 1 ) )
  15202. {
  15203. if ( pPlayer )
  15204. {
  15205. DuelMiniGame_NotifyPlayerDisconnect( pPlayer, true );
  15206. }
  15207. }
  15208. }
  15209. else if ( !Q_strcmp( eventName, "teamplay_round_start" ) )
  15210. {
  15211. if ( IsMannVsMachineMode() )
  15212. {
  15213. if ( g_pPopulationManager )
  15214. {
  15215. g_pPopulationManager->RestorePlayerCurrency();
  15216. // make sure all invaders are removed
  15217. CUtlVector< CTFPlayer * > playerVector;
  15218. CollectPlayers( &playerVector, TF_TEAM_PVE_INVADERS );
  15219. for( int i=0; i<playerVector.Count(); ++i )
  15220. {
  15221. playerVector[i]->ChangeTeam( TEAM_SPECTATOR, false, true );
  15222. }
  15223. }
  15224. }
  15225. }
  15226. else if ( !Q_strcmp( eventName, "recalculate_truce" ) )
  15227. {
  15228. RecalculateTruce();
  15229. }
  15230. #else // CLIENT_DLL
  15231. if ( !Q_strcmp( eventName, "overtime_nag" ) )
  15232. {
  15233. HandleOvertimeBegin();
  15234. }
  15235. else if ( !Q_strcmp( eventName, "recalculate_holidays" ) )
  15236. {
  15237. UTIL_CalculateHolidays();
  15238. }
  15239. #endif
  15240. BaseClass::FireGameEvent( event );
  15241. }
  15242. const char *CTFGameRules::GetGameTypeName( void )
  15243. {
  15244. return ::GetGameTypeName( m_nGameType.Get() );
  15245. }
  15246. void CTFGameRules::ClientSpawned( edict_t * pPlayer )
  15247. {
  15248. }
  15249. void CTFGameRules::OnFileReceived( const char * fileName, unsigned int transferID )
  15250. {
  15251. }
  15252. //-----------------------------------------------------------------------------
  15253. // Purpose: Init ammo definitions
  15254. //-----------------------------------------------------------------------------
  15255. // shared ammo definition
  15256. // JAY: Trying to make a more physical bullet response
  15257. #define BULLET_MASS_GRAINS_TO_LB(grains) (0.002285*(grains)/16.0f)
  15258. #define BULLET_MASS_GRAINS_TO_KG(grains) lbs2kg(BULLET_MASS_GRAINS_TO_LB(grains))
  15259. // exaggerate all of the forces, but use real numbers to keep them consistent
  15260. #define BULLET_IMPULSE_EXAGGERATION 1
  15261. // convert a velocity in ft/sec and a mass in grains to an impulse in kg in/s
  15262. #define BULLET_IMPULSE(grains, ftpersec) ((ftpersec)*12*BULLET_MASS_GRAINS_TO_KG(grains)*BULLET_IMPULSE_EXAGGERATION)
  15263. CAmmoDef* GetAmmoDef()
  15264. {
  15265. static CAmmoDef def;
  15266. static bool bInitted = false;
  15267. if ( !bInitted )
  15268. {
  15269. bInitted = true;
  15270. // Start at 1 here and skip the dummy ammo type to make CAmmoDef use the same indices
  15271. // as our #defines.
  15272. for ( int i=1; i < TF_AMMO_COUNT; i++ )
  15273. {
  15274. const char *pszAmmoName = GetAmmoName( i );
  15275. def.AddAmmoType( pszAmmoName, DMG_BULLET | DMG_USEDISTANCEMOD | DMG_NOCLOSEDISTANCEMOD, TRACER_LINE, 0, 0, "ammo_max", 2400, 10, 14 );
  15276. Assert( def.Index( pszAmmoName ) == i );
  15277. }
  15278. }
  15279. return &def;
  15280. }
  15281. //-----------------------------------------------------------------------------
  15282. // Purpose:
  15283. //-----------------------------------------------------------------------------
  15284. const char *CTFGameRules::GetTeamGoalString( int iTeam )
  15285. {
  15286. if ( iTeam == TF_TEAM_RED )
  15287. return m_pszTeamGoalStringRed.Get();
  15288. if ( iTeam == TF_TEAM_BLUE )
  15289. return m_pszTeamGoalStringBlue.Get();
  15290. return NULL;
  15291. }
  15292. #ifdef GAME_DLL
  15293. Vector MaybeDropToGround(
  15294. CBaseEntity *pMainEnt,
  15295. bool bDropToGround,
  15296. const Vector &vPos,
  15297. const Vector &vMins,
  15298. const Vector &vMaxs )
  15299. {
  15300. if ( bDropToGround )
  15301. {
  15302. trace_t trace;
  15303. UTIL_TraceHull( vPos, vPos + Vector( 0, 0, -500 ), vMins, vMaxs, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &trace );
  15304. return trace.endpos;
  15305. }
  15306. else
  15307. {
  15308. return vPos;
  15309. }
  15310. }
  15311. //-----------------------------------------------------------------------------
  15312. // Purpose: This function can be used to find a valid placement location for an entity.
  15313. // Given an origin to start looking from and a minimum radius to place the entity at,
  15314. // it will sweep out a circle around vOrigin and try to find a valid spot (on the ground)
  15315. // where mins and maxs will fit.
  15316. // Input : *pMainEnt - Entity to place
  15317. // &vOrigin - Point to search around
  15318. // fRadius - Radius to search within
  15319. // nTries - Number of tries to attempt
  15320. // &mins - mins of the Entity
  15321. // &maxs - maxs of the Entity
  15322. // &outPos - Return point
  15323. // Output : Returns true and fills in outPos if it found a spot.
  15324. //-----------------------------------------------------------------------------
  15325. bool EntityPlacementTest( CBaseEntity *pMainEnt, const Vector &vOrigin, Vector &outPos, bool bDropToGround )
  15326. {
  15327. // This function moves the box out in each dimension in each step trying to find empty space like this:
  15328. //
  15329. // X
  15330. // X X
  15331. // Step 1: X Step 2: XXX Step 3: XXXXX
  15332. // X X
  15333. // X
  15334. //
  15335. Vector mins, maxs;
  15336. pMainEnt->CollisionProp()->WorldSpaceAABB( &mins, &maxs );
  15337. mins -= pMainEnt->GetAbsOrigin();
  15338. maxs -= pMainEnt->GetAbsOrigin();
  15339. // Put some padding on their bbox.
  15340. float flPadSize = 5;
  15341. Vector vTestMins = mins - Vector( flPadSize, flPadSize, flPadSize );
  15342. Vector vTestMaxs = maxs + Vector( flPadSize, flPadSize, flPadSize );
  15343. // First test the starting origin.
  15344. if ( UTIL_IsSpaceEmpty( pMainEnt, vOrigin + vTestMins, vOrigin + vTestMaxs ) )
  15345. {
  15346. outPos = MaybeDropToGround( pMainEnt, bDropToGround, vOrigin, vTestMins, vTestMaxs );
  15347. return true;
  15348. }
  15349. Vector vDims = vTestMaxs - vTestMins;
  15350. // Keep branching out until we get too far.
  15351. int iCurIteration = 0;
  15352. int nMaxIterations = 15;
  15353. int offset = 0;
  15354. do
  15355. {
  15356. for ( int iDim=0; iDim < 3; iDim++ )
  15357. {
  15358. float flCurOffset = offset * vDims[iDim];
  15359. for ( int iSign=0; iSign < 2; iSign++ )
  15360. {
  15361. Vector vBase = vOrigin;
  15362. vBase[iDim] += (iSign*2-1) * flCurOffset;
  15363. if ( UTIL_IsSpaceEmpty( pMainEnt, vBase + vTestMins, vBase + vTestMaxs ) )
  15364. {
  15365. // Ensure that there is a clear line of sight from the spawnpoint entity to the actual spawn point.
  15366. // (Useful for keeping things from spawning behind walls near a spawn point)
  15367. trace_t tr;
  15368. UTIL_TraceLine( vOrigin, vBase, MASK_SOLID, pMainEnt, COLLISION_GROUP_NONE, &tr );
  15369. if ( tr.fraction != 1.0 )
  15370. {
  15371. continue;
  15372. }
  15373. outPos = MaybeDropToGround( pMainEnt, bDropToGround, vBase, vTestMins, vTestMaxs );
  15374. return true;
  15375. }
  15376. }
  15377. }
  15378. ++offset;
  15379. } while ( iCurIteration++ < nMaxIterations );
  15380. // Warning( "EntityPlacementTest for ent %d:%s failed!\n", pMainEnt->entindex(), pMainEnt->GetClassname() );
  15381. return false;
  15382. }
  15383. #else // GAME_DLL
  15384. CTFGameRules::~CTFGameRules()
  15385. {
  15386. if ( m_pkvVisionFilterTranslations )
  15387. {
  15388. m_pkvVisionFilterTranslations->deleteThis();
  15389. m_pkvVisionFilterTranslations = NULL;
  15390. }
  15391. if ( m_pkvVisionFilterShadersMapWhitelist )
  15392. {
  15393. m_pkvVisionFilterShadersMapWhitelist->deleteThis();
  15394. m_pkvVisionFilterShadersMapWhitelist = NULL;
  15395. }
  15396. }
  15397. //-----------------------------------------------------------------------------
  15398. // Purpose:
  15399. //-----------------------------------------------------------------------------
  15400. void CTFGameRules::OnDataChanged( DataUpdateType_t updateType )
  15401. {
  15402. BaseClass::OnDataChanged( updateType );
  15403. m_bRecievedBaseline |= updateType == DATA_UPDATE_CREATED;
  15404. }
  15405. //-----------------------------------------------------------------------------
  15406. // Purpose:
  15407. //-----------------------------------------------------------------------------
  15408. void CTFGameRules::HandleOvertimeBegin()
  15409. {
  15410. C_TFPlayer *pTFPlayer = C_TFPlayer::GetLocalTFPlayer();
  15411. if ( pTFPlayer )
  15412. {
  15413. pTFPlayer->EmitSound( "Game.Overtime" );
  15414. }
  15415. }
  15416. //-----------------------------------------------------------------------------
  15417. // Purpose:
  15418. //-----------------------------------------------------------------------------
  15419. bool CTFGameRules::ShouldShowTeamGoal( void )
  15420. {
  15421. //=============================================================================
  15422. // HPE_BEGIN
  15423. // [msmith] We always show the team goal when in training.
  15424. //=============================================================================
  15425. bool showDuringSetup = InSetup();
  15426. if ( IsInTraining() || IsInItemTestingMode() )
  15427. {
  15428. showDuringSetup = false;
  15429. }
  15430. if ( State_Get() == GR_STATE_PREROUND || State_Get() == GR_STATE_RND_RUNNING || showDuringSetup )
  15431. return true;
  15432. //=============================================================================
  15433. // HPE_END
  15434. //=============================================================================
  15435. return false;
  15436. }
  15437. #endif
  15438. #ifdef GAME_DLL
  15439. //-----------------------------------------------------------------------------
  15440. // Purpose:
  15441. //-----------------------------------------------------------------------------
  15442. void CTFGameRules::ShutdownCustomResponseRulesDicts()
  15443. {
  15444. DestroyCustomResponseSystems();
  15445. if ( m_ResponseRules.Count() != 0 )
  15446. {
  15447. int nRuleCount = m_ResponseRules.Count();
  15448. for ( int iRule = 0; iRule < nRuleCount; ++iRule )
  15449. {
  15450. m_ResponseRules[iRule].m_ResponseSystems.Purge();
  15451. }
  15452. m_ResponseRules.Purge();
  15453. }
  15454. }
  15455. //-----------------------------------------------------------------------------
  15456. // Purpose:
  15457. //-----------------------------------------------------------------------------
  15458. void CTFGameRules::InitCustomResponseRulesDicts()
  15459. {
  15460. MEM_ALLOC_CREDIT();
  15461. // Clear if necessary.
  15462. ShutdownCustomResponseRulesDicts();
  15463. // Initialize the response rules for TF.
  15464. m_ResponseRules.AddMultipleToTail( TF_CLASS_COUNT_ALL );
  15465. char szName[512];
  15466. for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_CLASS_COUNT_ALL; ++iClass )
  15467. {
  15468. m_ResponseRules[iClass].m_ResponseSystems.AddMultipleToTail( MP_TF_CONCEPT_COUNT );
  15469. for ( int iConcept = 0; iConcept < MP_TF_CONCEPT_COUNT; ++iConcept )
  15470. {
  15471. AI_CriteriaSet criteriaSet;
  15472. criteriaSet.AppendCriteria( "playerclass", g_aPlayerClassNames_NonLocalized[iClass] );
  15473. criteriaSet.AppendCriteria( "Concept", g_pszMPConcepts[iConcept] );
  15474. // 1 point for player class and 1 point for concept.
  15475. float flCriteriaScore = 2.0f;
  15476. // Name.
  15477. V_sprintf_safe( szName, "%s_%s\n", g_aPlayerClassNames_NonLocalized[iClass], g_pszMPConcepts[iConcept] );
  15478. m_ResponseRules[iClass].m_ResponseSystems[iConcept] = BuildCustomResponseSystemGivenCriteria( "scripts/talker/response_rules.txt", szName, criteriaSet, flCriteriaScore );
  15479. }
  15480. }
  15481. }
  15482. //-----------------------------------------------------------------------------
  15483. // Purpose:
  15484. //-----------------------------------------------------------------------------
  15485. #ifdef _DEBUG
  15486. CON_COMMAND( hud_notify, "Show a hud notification." )
  15487. {
  15488. if ( args.ArgC() < 2 )
  15489. {
  15490. Warning( "Requires one argument, HudNotification_t, between 0 and %i\n", NUM_STOCK_NOTIFICATIONS );
  15491. return;
  15492. }
  15493. if ( !TFGameRules() )
  15494. {
  15495. Warning( "Can't do that right now\n" );
  15496. return;
  15497. }
  15498. CRecipientFilter filter;
  15499. filter.AddAllPlayers();
  15500. TFGameRules()->SendHudNotification( filter, (HudNotification_t) V_atoi(args.Arg(1)) );
  15501. }
  15502. #endif
  15503. void CTFGameRules::SendHudNotification( IRecipientFilter &filter, HudNotification_t iType, bool bForceShow /*= false*/ )
  15504. {
  15505. if ( !bForceShow && IsInWaitingForPlayers() )
  15506. return;
  15507. UserMessageBegin( filter, "HudNotify" );
  15508. WRITE_BYTE( iType );
  15509. WRITE_BOOL( bForceShow ); // Display in cl_hud_minmode
  15510. MessageEnd();
  15511. }
  15512. //-----------------------------------------------------------------------------
  15513. // Purpose:
  15514. //-----------------------------------------------------------------------------
  15515. void CTFGameRules::SendHudNotification( IRecipientFilter &filter, const char *pszText, const char *pszIcon, int iTeam /*= TEAM_UNASSIGNED*/ )
  15516. {
  15517. if ( IsInWaitingForPlayers() )
  15518. return;
  15519. UserMessageBegin( filter, "HudNotifyCustom" );
  15520. WRITE_STRING( pszText );
  15521. WRITE_STRING( pszIcon );
  15522. WRITE_BYTE( iTeam );
  15523. MessageEnd();
  15524. }
  15525. //-----------------------------------------------------------------------------
  15526. // Purpose: Is the player past the required delays for spawning
  15527. //-----------------------------------------------------------------------------
  15528. bool CTFGameRules::HasPassedMinRespawnTime( CBasePlayer *pPlayer )
  15529. {
  15530. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  15531. if ( pTFPlayer && pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_UNDEFINED )
  15532. return true;
  15533. float flMinSpawnTime = GetMinTimeWhenPlayerMaySpawn( pPlayer );
  15534. return ( gpGlobals->curtime > flMinSpawnTime );
  15535. }
  15536. //-----------------------------------------------------------------------------
  15537. // Purpose:
  15538. //-----------------------------------------------------------------------------
  15539. bool CTFGameRules::ShouldRespawnQuickly( CBasePlayer *pPlayer )
  15540. {
  15541. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  15542. if ( IsPVEModeActive() && pTFPlayer && pTFPlayer->GetTeamNumber() == TF_TEAM_PVE_DEFENDERS && pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_SCOUT )
  15543. {
  15544. return true;
  15545. }
  15546. #if defined( _DEBUG ) || defined( STAGING_ONLY )
  15547. if ( mp_developer.GetBool() )
  15548. return true;
  15549. #endif // _DEBUG || STAGING_ONLY
  15550. if ( IsCompetitiveMode() && State_Get() == GR_STATE_BETWEEN_RNDS )
  15551. return true;
  15552. return BaseClass::ShouldRespawnQuickly( pPlayer );
  15553. }
  15554. typedef bool (*BIgnoreConvarChangeFunc)(void);
  15555. struct convar_tags_t
  15556. {
  15557. const char *pszConVar;
  15558. const char *pszTag;
  15559. BIgnoreConvarChangeFunc ignoreConvarFunc;
  15560. };
  15561. //-----------------------------------------------------------------------------
  15562. // Purpose:
  15563. //-----------------------------------------------------------------------------
  15564. static bool BIgnoreConvarChangeInPVEMode(void)
  15565. {
  15566. return TFGameRules() && TFGameRules()->IsPVEModeActive();
  15567. }
  15568. // The list of convars that automatically turn on tags when they're changed.
  15569. // Convars in this list need to have the FCVAR_NOTIFY flag set on them, so the
  15570. // tags are recalculated and uploaded to the master server when the convar is changed.
  15571. convar_tags_t convars_to_check_for_tags[] =
  15572. {
  15573. { "mp_friendlyfire", "friendlyfire", NULL },
  15574. { "tf_birthday", "birthday", NULL },
  15575. { "mp_respawnwavetime", "respawntimes", NULL },
  15576. { "mp_fadetoblack", "fadetoblack", NULL },
  15577. { "tf_weapon_criticals", "nocrits", NULL },
  15578. { "mp_disable_respawn_times", "norespawntime", NULL },
  15579. { "tf_gamemode_arena", "arena", NULL },
  15580. { "tf_gamemode_cp", "cp", NULL },
  15581. { "tf_gamemode_ctf", "ctf", NULL },
  15582. { "tf_gamemode_sd", "sd", NULL },
  15583. { "tf_gamemode_mvm", "mvm", NULL },
  15584. { "tf_gamemode_payload", "payload", NULL },
  15585. { "tf_gamemode_rd", "rd", NULL },
  15586. { "tf_gamemode_pd", "pd", NULL },
  15587. { "tf_gamemode_tc", "tc", NULL },
  15588. { "tf_beta_content", "beta", NULL },
  15589. { "tf_damage_disablespread", "dmgspread", NULL },
  15590. { "mp_highlander", "highlander", NULL },
  15591. { "tf_bot_count", "bots", &BIgnoreConvarChangeInPVEMode },
  15592. { "tf_pve_mode", "pve" },
  15593. { "sv_registration_successful", "_registered", NULL },
  15594. { "tf_server_identity_disable_quickplay", "noquickplay", NULL },
  15595. { "tf_mm_strict", "hidden", NULL },
  15596. { "tf_medieval", "medieval", NULL },
  15597. { "mp_holiday_nogifts", "nogifts" },
  15598. { "tf_powerup_mode", "powerup", NULL },
  15599. { "tf_gamemode_passtime", "passtime", NULL },
  15600. { "tf_gamemode_misc", "misc", NULL }, // catch-all for matchmaking to identify sd, tc, and pd servers via sv_tags
  15601. };
  15602. //-----------------------------------------------------------------------------
  15603. // Purpose: Engine asks for the list of convars that should tag the server
  15604. //-----------------------------------------------------------------------------
  15605. void CTFGameRules::GetTaggedConVarList( KeyValues *pCvarTagList )
  15606. {
  15607. BaseClass::GetTaggedConVarList( pCvarTagList );
  15608. for ( int i = 0; i < ARRAYSIZE(convars_to_check_for_tags); i++ )
  15609. {
  15610. if ( convars_to_check_for_tags[i].ignoreConvarFunc && convars_to_check_for_tags[i].ignoreConvarFunc() )
  15611. continue;
  15612. KeyValues *pKV = new KeyValues( "tag" );
  15613. pKV->SetString( "convar", convars_to_check_for_tags[i].pszConVar );
  15614. pKV->SetString( "tag", convars_to_check_for_tags[i].pszTag );
  15615. pCvarTagList->AddSubKey( pKV );
  15616. }
  15617. }
  15618. //-----------------------------------------------------------------------------
  15619. // Purpose:
  15620. //-----------------------------------------------------------------------------
  15621. void CTFGameRules::PlaySpecialCapSounds( int iCappingTeam, CTeamControlPoint *pPoint )
  15622. {
  15623. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_LAKESIDE ) )
  15624. {
  15625. return;
  15626. }
  15627. if ( GetGameType() == TF_GAMETYPE_CP )
  15628. {
  15629. bool bPlayControlPointCappedSound = IsInKothMode();
  15630. if ( !bPlayControlPointCappedSound )
  15631. {
  15632. if ( pPoint && ShouldScorePerRound() )
  15633. {
  15634. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  15635. if ( pMaster && !pMaster->WouldNewCPOwnerWinGame( pPoint, iCappingTeam ) )
  15636. {
  15637. bPlayControlPointCappedSound = true;
  15638. }
  15639. }
  15640. }
  15641. if ( bPlayControlPointCappedSound )
  15642. {
  15643. for ( int i = FIRST_GAME_TEAM; i < GetNumberOfTeams(); i++ )
  15644. {
  15645. if ( IsInKothMode() )
  15646. {
  15647. BroadcastSound( i, "Hud.PointCaptured" );
  15648. }
  15649. if ( i == iCappingTeam )
  15650. {
  15651. BroadcastSound( i, "Announcer.Success" );
  15652. }
  15653. else
  15654. {
  15655. BroadcastSound( i, "Announcer.Failure" );
  15656. }
  15657. }
  15658. }
  15659. }
  15660. }
  15661. //-----------------------------------------------------------------------------
  15662. // Purpose: Factory to create TF-specific mission manager singleton
  15663. //-----------------------------------------------------------------------------
  15664. CTacticalMissionManager *CTFGameRules::TacticalMissionManagerFactory( void )
  15665. {
  15666. return new CTFTacticalMissionManager;
  15667. }
  15668. #endif
  15669. //-----------------------------------------------------------------------------
  15670. // Purpose: Get the video file name for the current map.
  15671. //-----------------------------------------------------------------------------
  15672. #ifdef CLIENT_DLL
  15673. const char *CTFGameRules::GetVideoFileForMap( bool bWithExtension /*= true*/ )
  15674. {
  15675. char mapname[MAX_MAP_NAME];
  15676. mapname[0] = 0;
  15677. Q_FileBase( engine->GetLevelName(), mapname, sizeof( mapname ) );
  15678. if ( mapname[0] == 0 )
  15679. {
  15680. return NULL;
  15681. }
  15682. Q_strlower( mapname );
  15683. return FormatVideoName( (const char *)mapname, bWithExtension );
  15684. }
  15685. //=============================================================================
  15686. // HPE_BEGIN
  15687. // [msmith] Used for the client to tell the server that we're watching a movie or not.
  15688. // Also contains the name of a movie if it's an in game video.
  15689. //=============================================================================
  15690. //-----------------------------------------------------------------------------
  15691. // Purpose: Format a video file name from the name passed in.
  15692. //-----------------------------------------------------------------------------
  15693. const char *CTFGameRules::FormatVideoName( const char *videoName, bool bWithExtension /*= true*/ )
  15694. {
  15695. static char strFullpath[MAX_PATH]; // this buffer is returned to the caller
  15696. #ifdef _X360
  15697. // Should we be modifying a const buffer?
  15698. // need to remove the .360 extension on the end of the map name
  15699. char *pExt = Q_stristr( videoName, ".360" );
  15700. if ( pExt )
  15701. {
  15702. *pExt = '\0';
  15703. }
  15704. #endif
  15705. Q_strncpy( strFullpath, "media/", MAX_PATH ); // Assume we must play out of the media directory
  15706. if ( Q_strstr( videoName, "arena_" ) )
  15707. {
  15708. char strTempPath[MAX_PATH];
  15709. Q_strncpy( strTempPath, "media/", MAX_PATH );
  15710. Q_strncat( strTempPath, videoName, MAX_PATH );
  15711. Q_strncat( strTempPath, FILE_EXTENSION_ANY_MATCHING_VIDEO, MAX_PATH );
  15712. VideoSystem_t vSystem = VideoSystem::NONE;
  15713. // default to arena_intro video if we can't find the specified video
  15714. if ( !g_pVideo || g_pVideo->LocatePlayableVideoFile( strTempPath, "GAME", &vSystem, strFullpath, sizeof(strFullpath), VideoSystemFeature::PLAY_VIDEO_FILE_IN_MATERIAL ) != VideoResult::SUCCESS )
  15715. {
  15716. V_strncpy( strFullpath, "media/" "arena_intro", MAX_PATH );
  15717. }
  15718. }
  15719. else if ( Q_strstr( videoName, "mvm_" ) )
  15720. {
  15721. char strTempPath[MAX_PATH];
  15722. Q_strncpy( strTempPath, "media/", MAX_PATH );
  15723. Q_strncat( strTempPath, videoName, MAX_PATH );
  15724. Q_strncat( strTempPath, FILE_EXTENSION_ANY_MATCHING_VIDEO, MAX_PATH );
  15725. VideoSystem_t vSystem = VideoSystem::NONE;
  15726. // default to mvm_intro video if we can't find the specified video
  15727. if ( !g_pVideo || g_pVideo->LocatePlayableVideoFile( strTempPath, "GAME", &vSystem, strFullpath, sizeof(strFullpath), VideoSystemFeature::PLAY_VIDEO_FILE_IN_MATERIAL ) != VideoResult::SUCCESS )
  15728. {
  15729. V_strncpy( strFullpath, "media/" "mvm_intro", MAX_PATH );
  15730. }
  15731. }
  15732. else
  15733. {
  15734. Q_strncat( strFullpath, videoName, MAX_PATH );
  15735. }
  15736. if ( bWithExtension )
  15737. {
  15738. Q_strncat( strFullpath, FILE_EXTENSION_ANY_MATCHING_VIDEO, MAX_PATH ); // Don't assume any specific video type, let the video services find it
  15739. }
  15740. return strFullpath;
  15741. }
  15742. #endif
  15743. //=============================================================================
  15744. // HPE_END
  15745. //=============================================================================
  15746. #ifdef CLIENT_DLL
  15747. //-----------------------------------------------------------------------------
  15748. // Purpose:
  15749. //-----------------------------------------------------------------------------
  15750. const char *GetMapDisplayName( const char *mapName )
  15751. {
  15752. static char szDisplayName[256];
  15753. char szTempName[256];
  15754. const char *pszSrc = NULL;
  15755. szDisplayName[0] = '\0';
  15756. if ( !mapName )
  15757. return szDisplayName;
  15758. // check our lookup table
  15759. Q_strncpy( szTempName, mapName, sizeof( szTempName ) );
  15760. Q_strlower( szTempName );
  15761. pszSrc = szTempName;
  15762. for ( int i = 0; i < ARRAYSIZE( s_ValveMaps ); ++i )
  15763. {
  15764. if ( !Q_stricmp( s_ValveMaps[i].pDiskName, pszSrc ) )
  15765. {
  15766. return s_ValveMaps[i].pDisplayName;
  15767. }
  15768. }
  15769. // check the community maps that we've featured
  15770. for ( int i = 0; i < ARRAYSIZE( s_CommunityMaps ); ++i )
  15771. {
  15772. if ( !Q_stricmp( s_CommunityMaps[i].pDiskName, pszSrc ) )
  15773. {
  15774. return s_CommunityMaps[i].pDisplayName;
  15775. }
  15776. }
  15777. char *pszFinal = Q_strstr( pszSrc, "_final" );
  15778. if ( pszFinal )
  15779. {
  15780. // truncate the _final (or _final1) part of the filename if it's at the end of the name
  15781. char *pszNextChar = pszFinal + Q_strlen( "_final" );
  15782. if ( pszNextChar )
  15783. {
  15784. if ( ( *pszNextChar == '\0' ) ||
  15785. ( ( *pszNextChar == '1' ) && ( *(pszNextChar+1) == '\0' ) ) )
  15786. {
  15787. *pszFinal = '\0';
  15788. }
  15789. }
  15790. }
  15791. // Our workshop maps will be of the format workshop/cp_somemap.ugc12345
  15792. const char szWorkshop[] = "workshop/";
  15793. if ( V_strncmp( pszSrc, szWorkshop, sizeof( szWorkshop ) - 1 ) == 0 )
  15794. {
  15795. pszSrc += sizeof( szWorkshop ) - 1;
  15796. char *pszUGC = V_strstr( pszSrc, ".ugc" );
  15797. int nUGCLen = pszUGC ? strlen( pszUGC ) : 0;
  15798. if ( pszUGC && nUGCLen > 4 )
  15799. {
  15800. int i;
  15801. for ( i = 4; i < nUGCLen; i ++ )
  15802. {
  15803. if ( pszUGC[i] < '0' || pszUGC[i] > '9' )
  15804. {
  15805. break;
  15806. }
  15807. }
  15808. if ( i == nUGCLen )
  15809. {
  15810. *pszUGC = '\0';
  15811. }
  15812. }
  15813. }
  15814. // we haven't found a "friendly" map name, so let's just clean up what we have
  15815. if ( !Q_strncmp( pszSrc, "cp_", 3 ) ||
  15816. !Q_strncmp( pszSrc, "tc_", 3 ) ||
  15817. !Q_strncmp( pszSrc, "pl_", 3 ) ||
  15818. !Q_strncmp( pszSrc, "ad_", 3 ) ||
  15819. !Q_strncmp( pszSrc, "sd_", 3 ) ||
  15820. !Q_strncmp( pszSrc, "rd_", 3 ) ||
  15821. !Q_strncmp( pszSrc, "pd_", 3 ) )
  15822. {
  15823. pszSrc += 3;
  15824. }
  15825. else if ( !Q_strncmp( pszSrc, "ctf_", 4 ) ||
  15826. !Q_strncmp( pszSrc, "plr_", 4 ) )
  15827. {
  15828. pszSrc += 4;
  15829. }
  15830. else if ( !Q_strncmp( szTempName, "koth_", 5 ) ||
  15831. !Q_strncmp( szTempName, "pass_", 5 ) )
  15832. {
  15833. pszSrc += 5;
  15834. }
  15835. #ifdef TF_RAID_MODE
  15836. else if ( !Q_strncmp( pszSrc, "raid_", 5 ) )
  15837. {
  15838. pszSrc += 5;
  15839. }
  15840. #endif // TF_RAID_MODE
  15841. else if ( !Q_strncmp( pszSrc, "mvm_", 4 ) )
  15842. {
  15843. pszSrc += 4;
  15844. }
  15845. else if ( !Q_strncmp( pszSrc, "arena_", 6 ) )
  15846. {
  15847. pszSrc += 6;
  15848. }
  15849. Q_strncpy( szDisplayName, pszSrc, sizeof( szDisplayName ) );
  15850. Q_strupr( szDisplayName );
  15851. // replace underscores with spaces
  15852. for ( char *pszUnderscore = szDisplayName ; pszUnderscore != NULL && *pszUnderscore != 0 ; pszUnderscore++ )
  15853. {
  15854. // Replace it with a space
  15855. if ( *pszUnderscore == '_' )
  15856. {
  15857. *pszUnderscore = ' ';
  15858. }
  15859. }
  15860. return szDisplayName;
  15861. }
  15862. //-----------------------------------------------------------------------------
  15863. // Purpose:
  15864. //-----------------------------------------------------------------------------
  15865. const char *GetMapType( const char *mapName )
  15866. {
  15867. int i;
  15868. if ( mapName )
  15869. {
  15870. for ( i = 0; i < ARRAYSIZE( s_ValveMaps ); ++i )
  15871. {
  15872. if ( !Q_stricmp( s_ValveMaps[i].pDiskName, mapName ) )
  15873. {
  15874. return s_ValveMaps[i].pGameType;
  15875. }
  15876. }
  15877. // check the community maps that we've featured
  15878. for ( i = 0; i < ARRAYSIZE( s_CommunityMaps ); ++i )
  15879. {
  15880. if ( !Q_stricmp( s_CommunityMaps[i].pDiskName, mapName ) )
  15881. {
  15882. return s_CommunityMaps[i].pGameType;
  15883. }
  15884. }
  15885. }
  15886. if ( !IsX360() )
  15887. {
  15888. // we haven't found a "friendly" map name, so let's just clean up what we have
  15889. if ( !Q_strnicmp( mapName, "cp_", 3 ) )
  15890. {
  15891. return "#Gametype_CP";
  15892. }
  15893. else if ( !Q_strnicmp( mapName, "tc_", 3 ) )
  15894. {
  15895. return "#TF_TerritoryControl";
  15896. }
  15897. else if ( !Q_strnicmp( mapName, "pl_", 3 ) )
  15898. {
  15899. return "#Gametype_Escort";
  15900. }
  15901. else if ( !Q_strnicmp( mapName, "plr_", 4 ) )
  15902. {
  15903. return "#Gametype_EscortRace";
  15904. }
  15905. else if ( !Q_strnicmp( mapName, "ad_", 3 ) )
  15906. {
  15907. return "#TF_AttackDefend";
  15908. }
  15909. else if ( !Q_strnicmp( mapName, "ctf_", 4 ) )
  15910. {
  15911. return "#Gametype_CTF";
  15912. }
  15913. else if ( !Q_strnicmp( mapName, "koth_", 5 ) )
  15914. {
  15915. return "#Gametype_Koth";
  15916. }
  15917. else if ( !Q_strnicmp( mapName, "arena_", 6 ) )
  15918. {
  15919. return "#Gametype_Arena";
  15920. }
  15921. else if ( !Q_strnicmp( mapName, "sd_", 3 ) )
  15922. {
  15923. return "#Gametype_SD";
  15924. }
  15925. #ifdef TF_RAID_MODE
  15926. else if ( !Q_strnicmp( mapName, "raid_", 5 ) )
  15927. {
  15928. return "#Gametype_Raid";
  15929. }
  15930. #endif // TF_RAID_MODE
  15931. else if ( !Q_strnicmp( mapName, "mvm_", 4 ) )
  15932. {
  15933. return "#Gametype_MVM";
  15934. }
  15935. else if ( !Q_strnicmp( mapName, "pass_", 5 ) )
  15936. {
  15937. return "#GameType_Passtime";
  15938. }
  15939. else if ( !Q_strnicmp( mapName, "rd_", 3 ) )
  15940. {
  15941. return "#Gametype_RobotDestruction";
  15942. }
  15943. else if ( !Q_strnicmp( mapName, "pd_", 3 ) )
  15944. {
  15945. return "#Gametype_PlayerDestruction";
  15946. }
  15947. else
  15948. {
  15949. if ( TFGameRules() )
  15950. {
  15951. return TFGameRules()->GetGameTypeName();
  15952. }
  15953. }
  15954. }
  15955. return "";
  15956. }
  15957. #endif
  15958. //Arena Mode
  15959. bool CTFGameRules::IsInArenaMode( void ) const
  15960. {
  15961. return m_nGameType == TF_GAMETYPE_ARENA;
  15962. }
  15963. #ifdef GAME_DLL
  15964. //==================================================================================================================
  15965. // ARENA LOGIC
  15966. BEGIN_DATADESC( CArenaLogic )
  15967. DEFINE_OUTPUT( m_OnArenaRoundStart, "OnArenaRoundStart" ),
  15968. DEFINE_OUTPUT( m_OnCapEnabled, "OnCapEnabled" ),
  15969. DEFINE_KEYFIELD( m_flTimeToEnableCapPoint, FIELD_FLOAT, "CapEnableDelay" ),
  15970. DEFINE_FUNCTION( ArenaLogicThink ),
  15971. END_DATADESC()
  15972. LINK_ENTITY_TO_CLASS( tf_logic_arena, CArenaLogic );
  15973. void CArenaLogic::Spawn( void )
  15974. {
  15975. BaseClass::Spawn();
  15976. SetThink( &CArenaLogic::ArenaLogicThink );
  15977. SetNextThink( gpGlobals->curtime );
  15978. }
  15979. void CArenaLogic::ArenaLogicThink( void )
  15980. {
  15981. SetNextThink( gpGlobals->curtime + 0.1f );
  15982. if ( TFGameRules()->State_Get() != GR_STATE_STALEMATE )
  15983. return;
  15984. if ( TFGameRules() && TFGameRules()->GetCapturePointTime() <= gpGlobals->curtime )
  15985. {
  15986. if ( m_bFiredOutput == false )
  15987. {
  15988. m_bFiredOutput = true;
  15989. m_OnCapEnabled.FireOutput( this, this );
  15990. }
  15991. }
  15992. else if ( TFGameRules() && TFGameRules()->GetCapturePointTime() > gpGlobals->curtime )
  15993. {
  15994. m_bFiredOutput = false;
  15995. }
  15996. }
  15997. //-----------------------------------------------------------------------------
  15998. // Purpose: COMPETITIVE LOGIC (why are we shouting?)
  15999. //-----------------------------------------------------------------------------
  16000. BEGIN_DATADESC( CCompetitiveLogic )
  16001. DEFINE_OUTPUT( m_OnSpawnRoomDoorsShouldLock, "OnSpawnRoomDoorsShouldLock" ),
  16002. DEFINE_OUTPUT( m_OnSpawnRoomDoorsShouldUnlock, "OnSpawnRoomDoorsShouldUnlock" ),
  16003. END_DATADESC()
  16004. LINK_ENTITY_TO_CLASS( tf_logic_competitive, CCompetitiveLogic );
  16005. //-----------------------------------------------------------------------------
  16006. // Purpose:
  16007. //-----------------------------------------------------------------------------
  16008. void CCompetitiveLogic::OnSpawnRoomDoorsShouldLock( void )
  16009. {
  16010. if ( !TFGameRules() || !TFGameRules()->IsCompetitiveMode() )
  16011. return;
  16012. m_OnSpawnRoomDoorsShouldLock.FireOutput( this, this );
  16013. }
  16014. //-----------------------------------------------------------------------------
  16015. // Purpose:
  16016. //-----------------------------------------------------------------------------
  16017. void CCompetitiveLogic::OnSpawnRoomDoorsShouldUnlock( void )
  16018. {
  16019. if ( !TFGameRules() || !TFGameRules()->IsCompetitiveMode() )
  16020. return;
  16021. m_OnSpawnRoomDoorsShouldUnlock.FireOutput( this, this );
  16022. }
  16023. //-----------------------------------------------------------------------------
  16024. // Purpose:
  16025. //-----------------------------------------------------------------------------
  16026. BEGIN_DATADESC( CLogicMannPower )
  16027. END_DATADESC()
  16028. LINK_ENTITY_TO_CLASS( tf_logic_mannpower, CLogicMannPower );
  16029. //=============================================================================
  16030. // Training Mode
  16031. CON_COMMAND( training_continue, "Tells training that it should continue." )
  16032. {
  16033. if ( TFGameRules() == NULL || TFGameRules()->IsInTraining() == false || TFGameRules()->GetTrainingModeLogic() == NULL )
  16034. {
  16035. return;
  16036. }
  16037. TFGameRules()->GetTrainingModeLogic()->OnPlayerWantsToContinue();
  16038. }
  16039. #define SF_TF_DYNAMICPROP_GRENADE_COLLISION 512
  16040. class CTFTrainingDynamicProp : public CDynamicProp
  16041. {
  16042. DECLARE_CLASS( CTFTrainingDynamicProp, CDynamicProp );
  16043. public:
  16044. };
  16045. LINK_ENTITY_TO_CLASS( training_prop_dynamic, CTFTrainingDynamicProp );
  16046. bool PropDynamic_CollidesWithGrenades( CBaseEntity *pBaseEntity )
  16047. {
  16048. CTFTrainingDynamicProp *pTrainingDynamicProp = dynamic_cast< CTFTrainingDynamicProp* >( pBaseEntity );
  16049. return ( pTrainingDynamicProp && pTrainingDynamicProp->HasSpawnFlags( SF_TF_DYNAMICPROP_GRENADE_COLLISION) );
  16050. }
  16051. BEGIN_DATADESC( CTrainingModeLogic )
  16052. DEFINE_KEYFIELD( m_nextMapName, FIELD_STRING, "nextMap" ),
  16053. DEFINE_INPUTFUNC( FIELD_STRING, "ShowTrainingMsg", InputShowTrainingMsg ),
  16054. DEFINE_INPUTFUNC( FIELD_STRING, "ShowTrainingObjective", InputShowTrainingObjective ),
  16055. DEFINE_INPUTFUNC( FIELD_VOID, "ForcePlayerSpawnAsClassOutput", InputForcePlayerSpawnAsClassOutput ),
  16056. DEFINE_INPUTFUNC( FIELD_VOID, "KickBots", InputKickAllBots ),
  16057. DEFINE_INPUTFUNC( FIELD_VOID, "ShowTrainingHUD", InputShowTrainingHUD ),
  16058. DEFINE_INPUTFUNC( FIELD_VOID, "HideTrainingHUD", InputHideTrainingHUD ),
  16059. DEFINE_INPUTFUNC( FIELD_STRING, "EndTraining", InputEndTraining ),
  16060. DEFINE_INPUTFUNC( FIELD_STRING, "PlaySoundOnPlayer", InputPlaySoundOnPlayer ),
  16061. DEFINE_INPUTFUNC( FIELD_STRING, "WaitForTimerOrKeypress", InputWaitForTimerOrKeypress ),
  16062. DEFINE_INPUTFUNC( FIELD_STRING, "SetNextMap", InputSetNextMap ),
  16063. DEFINE_INPUTFUNC( FIELD_STRING, "ForcePlayerSwapToWeapon", InputForcePlayerSwapToWeapon ),
  16064. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsScout, "OnPlayerSpawnAsScout" ),
  16065. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsSniper, "OnPlayerSpawnAsSniper" ),
  16066. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsSoldier, "OnPlayerSpawnAsSoldier" ),
  16067. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsDemoman, "OnPlayerSpawnAsDemoman" ),
  16068. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsMedic, "OnPlayerSpawnAsMedic" ),
  16069. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsHeavy, "OnPlayerSpawnAsHeavy" ),
  16070. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsPyro, "OnPlayerSpawnAsPyro" ),
  16071. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsSpy, "OnPlayerSpawnAsSpy" ),
  16072. DEFINE_OUTPUT( m_outputOnPlayerSpawnAsEngineer, "OnPlayerSpawnAsEngineer" ),
  16073. DEFINE_OUTPUT( m_outputOnPlayerDied, "OnPlayerDied" ),
  16074. DEFINE_OUTPUT( m_outputOnBotDied, "OnBotDied" ),
  16075. DEFINE_OUTPUT( m_outputOnPlayerSwappedToWeaponSlotPrimary, "OnPlayerSwappedToPrimary" ),
  16076. DEFINE_OUTPUT( m_outputOnPlayerSwappedToWeaponSlotSecondary, "OnPlayerSwappedToSecondary" ),
  16077. DEFINE_OUTPUT( m_outputOnPlayerSwappedToWeaponSlotMelee, "OnPlayerSwappedToMelee" ),
  16078. DEFINE_OUTPUT( m_outputOnPlayerSwappedToWeaponSlotBuilding, "OnPlayerSwappedToBuilding" ),
  16079. DEFINE_OUTPUT( m_outputOnPlayerSwappedToWeaponSlotPDA, "OnPlayerSwappedToPDA" ),
  16080. DEFINE_OUTPUT( m_outputOnPlayerBuiltOutsideSuggestedArea, "OnBuildOutsideArea" ),
  16081. DEFINE_OUTPUT( m_outputOnPlayerDetonateBuilding, "OnPlayerDetonateBuilding" ),
  16082. END_DATADESC()
  16083. LINK_ENTITY_TO_CLASS( tf_logic_training_mode, CTrainingModeLogic );
  16084. void CTrainingModeLogic::SetupOnRoundStart()
  16085. {
  16086. m_objText[0] = 0;
  16087. SetTrainingMsg( "" );
  16088. }
  16089. void CTrainingModeLogic::SetTrainingMsg(const char *msg)
  16090. {
  16091. CBroadcastRecipientFilter allusers;
  16092. allusers.MakeReliable();
  16093. UserMessageBegin( allusers, "TrainingMsg" );
  16094. WRITE_STRING( msg );
  16095. MessageEnd();
  16096. }
  16097. void CTrainingModeLogic::SetTrainingObjective(const char *text)
  16098. {
  16099. CBroadcastRecipientFilter allusers;
  16100. allusers.MakeReliable();
  16101. UserMessageBegin( allusers, "TrainingObjective" );
  16102. WRITE_STRING( text );
  16103. MessageEnd();
  16104. }
  16105. void CTrainingModeLogic::OnPlayerSpawned( CTFPlayer* pPlayer )
  16106. {
  16107. if ( pPlayer->GetDesiredPlayerClassIndex() == TF_CLASS_UNDEFINED )
  16108. {
  16109. return;
  16110. }
  16111. if ( pPlayer->IsFakeClient() )
  16112. {
  16113. return;
  16114. }
  16115. int iClass = pPlayer->GetPlayerClass()->GetClassIndex();
  16116. switch ( iClass )
  16117. {
  16118. case TF_CLASS_SCOUT: m_outputOnPlayerSpawnAsScout.FireOutput( this, this ); break;
  16119. case TF_CLASS_SNIPER: m_outputOnPlayerSpawnAsSniper.FireOutput( this, this ); break;
  16120. case TF_CLASS_SOLDIER: m_outputOnPlayerSpawnAsSoldier.FireOutput( this, this ); break;
  16121. case TF_CLASS_DEMOMAN: m_outputOnPlayerSpawnAsDemoman.FireOutput( this, this ); break;
  16122. case TF_CLASS_MEDIC: m_outputOnPlayerSpawnAsMedic.FireOutput( this, this ); break;
  16123. case TF_CLASS_HEAVYWEAPONS: m_outputOnPlayerSpawnAsHeavy.FireOutput( this, this ); break;
  16124. case TF_CLASS_PYRO: m_outputOnPlayerSpawnAsPyro.FireOutput( this, this ); break;
  16125. case TF_CLASS_SPY: m_outputOnPlayerSpawnAsSpy.FireOutput( this, this ); break;
  16126. case TF_CLASS_ENGINEER: m_outputOnPlayerSpawnAsEngineer.FireOutput( this, this ); break;
  16127. }
  16128. }
  16129. void CTrainingModeLogic::OnPlayerDied( CTFPlayer *pPlayer, CBaseEntity *pKiller )
  16130. {
  16131. m_outputOnPlayerDied.FireOutput( pKiller, this );
  16132. }
  16133. void CTrainingModeLogic::OnBotDied( CTFPlayer *pPlayer, CBaseEntity *pKiller )
  16134. {
  16135. m_outputOnBotDied.FireOutput( pKiller, this );
  16136. }
  16137. void CTrainingModeLogic::OnPlayerSwitchedWeapons( CTFPlayer *pPlayer )
  16138. {
  16139. CTFWeaponBase *pWeapon = (CTFWeaponBase*)pPlayer->GetActiveWeapon();
  16140. if ( pWeapon == NULL )
  16141. {
  16142. return;
  16143. }
  16144. switch ( pWeapon->GetTFWpnData().m_iWeaponType )
  16145. {
  16146. case TF_WPN_TYPE_PRIMARY: m_outputOnPlayerSwappedToWeaponSlotPrimary.FireOutput( this, this ); break;
  16147. case TF_WPN_TYPE_SECONDARY: m_outputOnPlayerSwappedToWeaponSlotSecondary.FireOutput( this, this ); break;
  16148. case TF_WPN_TYPE_MELEE: m_outputOnPlayerSwappedToWeaponSlotMelee.FireOutput( this, this ); break;
  16149. case TF_WPN_TYPE_BUILDING: m_outputOnPlayerSwappedToWeaponSlotBuilding.FireOutput( this, this ); break;
  16150. case TF_WPN_TYPE_PDA: m_outputOnPlayerSwappedToWeaponSlotPDA.FireOutput( this, this ); break;
  16151. }
  16152. }
  16153. void CTrainingModeLogic::OnPlayerWantsToContinue()
  16154. {
  16155. if ( m_waitingForKeypressTimer.Get() != NULL )
  16156. {
  16157. m_waitingForKeypressTimer->FireNamedOutput( "OnTimer", variant_t(), this, this );
  16158. m_waitingForKeypressTimer = NULL;
  16159. TFGameRules()->SetIsWaitingForTrainingContinue( false );
  16160. }
  16161. }
  16162. void CTrainingModeLogic::OnPlayerBuiltBuilding( CTFPlayer *pPlayer, CBaseObject *pBaseObject )
  16163. {
  16164. if ( pBaseObject && NotifyObjectBuiltInSuggestedArea( *pBaseObject ) == false )
  16165. {
  16166. m_outputOnPlayerBuiltOutsideSuggestedArea.FireOutput( pBaseObject, this );
  16167. }
  16168. }
  16169. void CTrainingModeLogic::OnPlayerUpgradedBuilding( CTFPlayer *pPlayer, CBaseObject *pBaseObject )
  16170. {
  16171. if ( pBaseObject )
  16172. {
  16173. NotifyObjectUpgradedInSuggestedArea( *pBaseObject );
  16174. }
  16175. }
  16176. void CTrainingModeLogic::OnPlayerDetonateBuilding( CTFPlayer *pPlayer, CBaseObject *pBaseObject )
  16177. {
  16178. m_outputOnPlayerDetonateBuilding.FireOutput( pPlayer, pBaseObject );
  16179. }
  16180. void CTrainingModeLogic::UpdateHUDObjective()
  16181. {
  16182. if ( m_objText[0] != 0 )
  16183. {
  16184. SetTrainingObjective( m_objText );
  16185. }
  16186. else
  16187. {
  16188. SetTrainingObjective("");
  16189. }
  16190. }
  16191. const char* CTrainingModeLogic::GetNextMap()
  16192. {
  16193. return m_nextMapName.ToCStr();
  16194. }
  16195. const char* CTrainingModeLogic::GetTrainingEndText()
  16196. {
  16197. return m_endTrainingText.ToCStr();
  16198. }
  16199. int CTrainingModeLogic::GetDesiredClass() const
  16200. {
  16201. return training_class.GetInt();
  16202. }
  16203. void CTrainingModeLogic::InputForcePlayerSpawnAsClassOutput( inputdata_t &inputdata )
  16204. {
  16205. // This is a bit weird, but we will call this for every player--bots should be ignored
  16206. CTFPlayer *pPlayer;
  16207. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  16208. {
  16209. pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  16210. if ( !pPlayer )
  16211. continue;
  16212. OnPlayerSpawned( pPlayer );
  16213. }
  16214. }
  16215. void CTrainingModeLogic::InputKickAllBots( inputdata_t &inputdata )
  16216. {
  16217. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  16218. {
  16219. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  16220. if ( pPlayer && pPlayer->IsFakeClient() )
  16221. {
  16222. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pPlayer->GetUserID() ) );
  16223. }
  16224. }
  16225. }
  16226. void CTrainingModeLogic::InputShowTrainingMsg( inputdata_t &inputdata )
  16227. {
  16228. if ( TFGameRules()->IsInTraining() )
  16229. {
  16230. SetTrainingMsg( inputdata.value.String() );
  16231. }
  16232. }
  16233. void CTrainingModeLogic::InputShowTrainingObjective( inputdata_t &inputdata )
  16234. {
  16235. if ( !TFGameRules()->IsInTraining() )
  16236. return;
  16237. //First try to find the unicode string to send over.
  16238. wchar_t *strPtr = NULL;
  16239. strPtr = g_pVGuiLocalize->Find( inputdata.value.String() );
  16240. if (NULL == strPtr)
  16241. {
  16242. V_strcpy_safe(m_objText, inputdata.value.String());
  16243. }
  16244. else
  16245. {
  16246. g_pVGuiLocalize->ConvertUnicodeToANSI(strPtr, m_objText, kMaxLengthObjectiveText);
  16247. }
  16248. UpdateHUDObjective();
  16249. }
  16250. void CTrainingModeLogic::InputShowTrainingHUD( inputdata_t &inputdata )
  16251. {
  16252. if ( !TFGameRules()->IsInTraining() )
  16253. return;
  16254. TFGameRules()->SetTrainingHUDVisible( true );
  16255. }
  16256. void CTrainingModeLogic::InputHideTrainingHUD( inputdata_t &inputdata )
  16257. {
  16258. if ( !TFGameRules()->IsInTraining() )
  16259. return;
  16260. TFGameRules()->SetTrainingHUDVisible( false );
  16261. }
  16262. void CTrainingModeLogic::InputEndTraining( inputdata_t &inputdata )
  16263. {
  16264. if ( !TFGameRules()->IsInTraining() )
  16265. return;
  16266. TFGameRules()->SetAllowTrainingAchievements( true );
  16267. m_endTrainingText = inputdata.value.StringID();
  16268. CTFPlayer* pHumanPlayer = ToTFPlayer( UTIL_GetListenServerHost() );
  16269. if (NULL == pHumanPlayer) return;
  16270. int iTeam = pHumanPlayer->GetTeamNumber();
  16271. bool force_map_reset = true;
  16272. CTeamplayRoundBasedRules *pGameRules = dynamic_cast<CTeamplayRoundBasedRules *>( GameRules() );
  16273. pGameRules->SetWinningTeam( iTeam, pGameRules->GetWinReason(), force_map_reset );
  16274. // Show a training win screen so send that event instead.
  16275. IGameEvent *winEvent = gameeventmanager->CreateEvent( "training_complete" );
  16276. if ( winEvent )
  16277. {
  16278. winEvent->SetString( "map", STRING( gpGlobals->mapname ) );
  16279. winEvent->SetString( "next_map", GetNextMap() );
  16280. winEvent->SetString( "text", GetTrainingEndText() );
  16281. gameeventmanager->FireEvent( winEvent );
  16282. }
  16283. }
  16284. void CTrainingModeLogic::InputPlaySoundOnPlayer( inputdata_t &inputdata )
  16285. {
  16286. if ( !TFGameRules()->IsInTraining() )
  16287. return;
  16288. CTFPlayer* pHumanPlayer = ToTFPlayer( UTIL_GetListenServerHost() );
  16289. if (NULL == pHumanPlayer)
  16290. return;
  16291. pHumanPlayer->EmitSound( inputdata.value.String() );
  16292. }
  16293. void CTrainingModeLogic::InputWaitForTimerOrKeypress( inputdata_t &inputdata )
  16294. {
  16295. if ( !TFGameRules()->IsInTraining() )
  16296. return;
  16297. m_waitingForKeypressTimer = gEntList.FindEntityByName( NULL, inputdata.value.String() );
  16298. TFGameRules()->SetIsWaitingForTrainingContinue( m_waitingForKeypressTimer.Get() != NULL );
  16299. }
  16300. void CTrainingModeLogic::InputSetNextMap( inputdata_t &inputdata )
  16301. {
  16302. m_nextMapName = AllocPooledString( inputdata.value.String() );
  16303. }
  16304. void CTrainingModeLogic::InputForcePlayerSwapToWeapon( inputdata_t &inputdata )
  16305. {
  16306. CTFPlayer* pHumanPlayer = ToTFPlayer( UTIL_GetListenServerHost() );
  16307. if (NULL == pHumanPlayer)
  16308. return;
  16309. CBaseCombatWeapon *pWeapon = NULL;
  16310. if ( FStrEq( inputdata.value.String(), "primary" ) )
  16311. {
  16312. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY );
  16313. }
  16314. else if ( FStrEq( inputdata.value.String(), "secondary" ) )
  16315. {
  16316. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_SECONDARY );
  16317. }
  16318. else if ( FStrEq( inputdata.value.String(), "melee" ) )
  16319. {
  16320. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_MELEE );
  16321. }
  16322. else if ( FStrEq( inputdata.value.String(), "grenade" ) )
  16323. {
  16324. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_GRENADE );
  16325. }
  16326. else if ( FStrEq( inputdata.value.String(), "building" ) )
  16327. {
  16328. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_BUILDING );
  16329. }
  16330. else if ( FStrEq( inputdata.value.String(), "pda" ) )
  16331. {
  16332. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_PDA );
  16333. }
  16334. else if ( FStrEq( inputdata.value.String(), "item1" ) )
  16335. {
  16336. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_ITEM1 );
  16337. }
  16338. else if ( FStrEq( inputdata.value.String(), "item2" ) )
  16339. {
  16340. pWeapon = pHumanPlayer->Weapon_GetSlot( TF_WPN_TYPE_ITEM2 );
  16341. }
  16342. if ( pWeapon )
  16343. {
  16344. pHumanPlayer->Weapon_Switch( pWeapon );
  16345. }
  16346. }
  16347. LINK_ENTITY_TO_CLASS( tf_logic_multiple_escort, CMultipleEscort );
  16348. LINK_ENTITY_TO_CLASS( tf_logic_hybrid_ctf_cp, CHybridMap_CTF_CP );
  16349. LINK_ENTITY_TO_CLASS( tf_logic_medieval, CMedievalLogic );
  16350. BEGIN_DATADESC(CTFHolidayEntity)
  16351. DEFINE_KEYFIELD( m_nHolidayType, FIELD_INTEGER, "holiday_type" ),
  16352. DEFINE_KEYFIELD( m_nTauntInHell, FIELD_INTEGER, "tauntInHell" ),
  16353. DEFINE_KEYFIELD( m_nAllowHaunting, FIELD_INTEGER, "allowHaunting" ),
  16354. DEFINE_INPUTFUNC( FIELD_INTEGER, "HalloweenSetUsingSpells", InputHalloweenSetUsingSpells ),
  16355. DEFINE_INPUTFUNC( FIELD_STRING, "Halloween2013TeleportToHell", InputHalloweenTeleportToHell ),
  16356. END_DATADESC();
  16357. LINK_ENTITY_TO_CLASS( tf_logic_holiday, CTFHolidayEntity );
  16358. void CTFHolidayEntity::InputHalloweenSetUsingSpells( inputdata_t &inputdata )
  16359. {
  16360. if ( !TFGameRules() )
  16361. return;
  16362. TFGameRules()->SetUsingSpells( ( inputdata.value.Int() == 0 ) ? false : true );
  16363. }
  16364. void CTFHolidayEntity::InputHalloweenTeleportToHell( inputdata_t &inputdata )
  16365. {
  16366. m_nWinningTeam = FStrEq( "red", inputdata.value.String() ) ? TF_TEAM_RED : TF_TEAM_BLUE;
  16367. CUtlVector< CTFPlayer * > vecPlayers;
  16368. CollectPlayers( &vecPlayers, TEAM_ANY, false );
  16369. FOR_EACH_VEC( vecPlayers, i )
  16370. {
  16371. CTFPlayer *pPlayer = vecPlayers[i];
  16372. // Only do these effects if the player is alive
  16373. if ( !pPlayer->IsAlive() )
  16374. continue;
  16375. // Fade to white
  16376. color32 fadeColor = {255,255,255,255};
  16377. UTIL_ScreenFade( pPlayer, fadeColor, 2.f, 0.5, FFADE_OUT | FFADE_PURGE );
  16378. // Do a zoom in effect
  16379. pPlayer->SetFOV( pPlayer, 10.f, 2.5f, 0.f );
  16380. // Rumble like something important happened
  16381. UTIL_ScreenShake(pPlayer->GetAbsOrigin(), 100.f, 150, 4.f, 0.f, SHAKE_START, true );
  16382. }
  16383. // Play a sound for all players
  16384. TFGameRules()->BroadcastSound( 255, "Halloween.hellride" );
  16385. SetContextThink( &CTFHolidayEntity::Teleport, gpGlobals->curtime + 2.5f, "TeleportToHell" );
  16386. }
  16387. void CTFHolidayEntity::Teleport()
  16388. {
  16389. RemoveAll2013HalloweenTeleportSpellsInMidFlight();
  16390. const char *pszRedString = ( m_nWinningTeam == TF_TEAM_RED ) ? "winner" : "loser";
  16391. const char *pszBlueString = ( m_nWinningTeam == TF_TEAM_BLUE ) ? "winner" : "loser";
  16392. CUtlVector< CTFPlayer* > vecTeleportedPlayers;
  16393. TFGameRules()->TeleportPlayersToTargetEntities( TF_TEAM_RED, CFmtStr( "spawn_loot_%s" , pszRedString ), &vecTeleportedPlayers );
  16394. TFGameRules()->TeleportPlayersToTargetEntities( TF_TEAM_BLUE, CFmtStr( "spawn_loot_%s" , pszBlueString ), &vecTeleportedPlayers );
  16395. // clear dancer
  16396. m_vecDancers.RemoveAll();
  16397. // remove players' projectiles and buildings from world
  16398. TFGameRules()->RemoveAllProjectilesAndBuildings();
  16399. FOR_EACH_VEC( vecTeleportedPlayers, i )
  16400. {
  16401. CTFPlayer *pPlayer = vecTeleportedPlayers[i];
  16402. // Roll a new, low-tier spell
  16403. CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
  16404. if ( pSpellBook )
  16405. {
  16406. pSpellBook->ClearSpell();
  16407. if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
  16408. {
  16409. pSpellBook->RollNewSpell( 0, true );
  16410. }
  16411. }
  16412. // Do a zoom effect
  16413. pPlayer->SetFOV( pPlayer, tf_teleporter_fov_start.GetInt() );
  16414. pPlayer->SetFOV( pPlayer, 0, 1.f, tf_teleporter_fov_start.GetInt() );
  16415. // Screen flash
  16416. color32 fadeColor = {255,255,255,100};
  16417. UTIL_ScreenFade( pPlayer, fadeColor, 0.25, 0.4, FFADE_IN );
  16418. const float flDanceTime = 6.f;
  16419. if ( ShouldTauntInHell() || ( TFGameRules()->GetHalloweenScenario() == CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
  16420. {
  16421. pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_THRILLER, flDanceTime );
  16422. }
  16423. pPlayer->m_Shared.AddCond( TF_COND_HALLOWEEN_IN_HELL );
  16424. // Losers get their health set to max. Winners get overhealed
  16425. bool bIsWinner = ( pPlayer->GetTeamNumber() == m_nWinningTeam );
  16426. float flMax = bIsWinner ? ( pPlayer->GetMaxHealth() * 1.6f ) : ( pPlayer->GetMaxHealth() * 1.1 );
  16427. float flToHeal = flMax - pPlayer->GetHealth();
  16428. // Overheal the winning team, and just restore the losing team to full health
  16429. pPlayer->m_Shared.Heal( pPlayer, flToHeal / flDanceTime, bIsWinner ? 1.5f : 1.f, 1.0f );
  16430. // Give them full ammo
  16431. pPlayer->GiveAmmo( 1000, TF_AMMO_PRIMARY );
  16432. pPlayer->GiveAmmo( 1000, TF_AMMO_SECONDARY );
  16433. pPlayer->GiveAmmo( 1000, TF_AMMO_METAL );
  16434. pPlayer->GiveAmmo( 1000, TF_AMMO_GRENADES1 );
  16435. pPlayer->GiveAmmo( 1000, TF_AMMO_GRENADES2 );
  16436. pPlayer->GiveAmmo( 1000, TF_AMMO_GRENADES3 );
  16437. // Refills weapon clips, too
  16438. for ( int i = 0; i < MAX_WEAPONS; i++ )
  16439. {
  16440. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* >( pPlayer->GetWeapon( i ) );
  16441. if ( !pWeapon )
  16442. continue;
  16443. pWeapon->GiveDefaultAmmo();
  16444. if ( pWeapon->IsEnergyWeapon() )
  16445. {
  16446. pWeapon->WeaponRegenerate();
  16447. }
  16448. }
  16449. m_vecDancers.AddToTail( pPlayer );
  16450. }
  16451. // Set this flag. Lets us check elsewhere if it's hell time
  16452. if ( TFGameRules() )
  16453. {
  16454. TFGameRules()->SetPlayersInHell( true );
  16455. }
  16456. if ( ShouldTauntInHell() || ( TFGameRules()->GetHalloweenScenario() == CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
  16457. {
  16458. const float flDanceTime = 0.5f;
  16459. const float flDanceDuration = 2.75f;
  16460. SetContextThink( &CTFHolidayEntity::HalloweenTeleportToHellDanceThink, gpGlobals->curtime + flDanceTime, "DanceThink1" );
  16461. SetContextThink( &CTFHolidayEntity::HalloweenTeleportToHellDanceThink, gpGlobals->curtime + flDanceTime + flDanceDuration, "DanceThink2" );
  16462. }
  16463. }
  16464. void CTFHolidayEntity::HalloweenTeleportToHellDanceThink( void )
  16465. {
  16466. FOR_EACH_VEC( m_vecDancers, i )
  16467. {
  16468. CTFPlayer* pPlayer = m_vecDancers[i];
  16469. if ( !pPlayer )
  16470. continue;
  16471. // Dance
  16472. pPlayer->Taunt();
  16473. }
  16474. }
  16475. void CTFHolidayEntity::FireGameEvent( IGameEvent *event )
  16476. {
  16477. const char *eventName = event->GetName();
  16478. #ifdef GAME_DLL
  16479. if ( !Q_strcmp( eventName, "player_turned_to_ghost" )
  16480. || !Q_strcmp( eventName, "player_disconnect" )
  16481. || !Q_strcmp( eventName, "player_team" ))
  16482. {
  16483. if ( TFGameRules()->ArePlayersInHell() )
  16484. {
  16485. CUtlVector< CTFPlayer * > vecPlayers;
  16486. CollectPlayers( &vecPlayers, TF_TEAM_RED, true );
  16487. CollectPlayers( &vecPlayers, TF_TEAM_BLUE, true, true );
  16488. FOR_EACH_VEC( vecPlayers, i )
  16489. {
  16490. // If everyone is a ghost
  16491. if ( !vecPlayers[i]->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
  16492. return;
  16493. }
  16494. // Everyone is a ghost. Stalemate!
  16495. TFGameRules()->SetWinningTeam( TEAM_UNASSIGNED, WINREASON_STALEMATE, true, false );
  16496. }
  16497. }
  16498. #endif
  16499. }
  16500. BEGIN_DATADESC(CKothLogic)
  16501. DEFINE_KEYFIELD( m_nTimerInitialLength, FIELD_INTEGER, "timer_length" ),
  16502. DEFINE_KEYFIELD( m_nTimeToUnlockPoint, FIELD_INTEGER, "unlock_point" ),
  16503. DEFINE_INPUTFUNC( FIELD_VOID, "RoundSpawn", InputRoundSpawn ),
  16504. DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ),
  16505. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRedTimer", InputSetRedTimer ),
  16506. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetBlueTimer", InputSetBlueTimer ),
  16507. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddRedTimer", InputAddRedTimer ),
  16508. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddBlueTimer", InputAddBlueTimer ),
  16509. END_DATADESC();
  16510. LINK_ENTITY_TO_CLASS( tf_logic_koth, CKothLogic );
  16511. //-----------------------------------------------------------------------------
  16512. // Purpose:
  16513. //-----------------------------------------------------------------------------
  16514. void CKothLogic::InputRoundSpawn( inputdata_t &input )
  16515. {
  16516. if ( TFGameRules() && TFGameRules()->IsInKothMode() )
  16517. {
  16518. // create the koth team_round_timer entities
  16519. variant_t sVariant;
  16520. sVariant.SetInt( m_nTimerInitialLength );
  16521. CTeamRoundTimer *pTimer = NULL;
  16522. pTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
  16523. if ( pTimer )
  16524. {
  16525. TFGameRules()->SetKothTeamTimer( TF_TEAM_BLUE, pTimer );
  16526. pTimer->SetName( MAKE_STRING( "zz_blue_koth_timer" ) );
  16527. pTimer->SetShowInHud( true );
  16528. pTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
  16529. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  16530. m_hBlueTimer = pTimer;
  16531. }
  16532. pTimer = (CTeamRoundTimer*)CBaseEntity::Create( "team_round_timer", vec3_origin, vec3_angle );
  16533. if ( pTimer )
  16534. {
  16535. TFGameRules()->SetKothTeamTimer( TF_TEAM_RED, pTimer );
  16536. pTimer->SetName( MAKE_STRING( "zz_red_koth_timer" ) );
  16537. pTimer->SetShowInHud( true );
  16538. pTimer->AcceptInput( "SetTime", NULL, NULL, sVariant, 0 );
  16539. pTimer->AcceptInput( "Pause", NULL, NULL, sVariant, 0 );
  16540. m_hRedTimer = pTimer;
  16541. }
  16542. }
  16543. }
  16544. //-----------------------------------------------------------------------------
  16545. // Purpose:
  16546. //-----------------------------------------------------------------------------
  16547. void CKothLogic::InputRoundActivate( inputdata_t &inputdata )
  16548. {
  16549. if ( TFGameRules() && TFGameRules()->IsInKothMode() )
  16550. {
  16551. CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
  16552. if ( pMaster )
  16553. {
  16554. variant_t sVariant;
  16555. sVariant.SetInt( m_nTimeToUnlockPoint );
  16556. for ( int i = 0 ; i < pMaster->GetNumPoints() ; i++ )
  16557. {
  16558. CTeamControlPoint *pPoint = pMaster->GetControlPoint( i );
  16559. if ( pPoint )
  16560. {
  16561. pPoint->AcceptInput( "SetLocked", NULL, NULL, sVariant, 0 );
  16562. if ( m_nTimeToUnlockPoint > 0 )
  16563. {
  16564. pPoint->AcceptInput( "SetUnlockTime", NULL, NULL, sVariant, 0 );
  16565. }
  16566. }
  16567. }
  16568. }
  16569. }
  16570. }
  16571. //-----------------------------------------------------------------------------
  16572. // Purpose:
  16573. //-----------------------------------------------------------------------------
  16574. void CKothLogic::InputSetRedTimer( inputdata_t &inputdata )
  16575. {
  16576. if ( TFGameRules() && TFGameRules()->IsInKothMode() )
  16577. {
  16578. if ( m_hRedTimer )
  16579. {
  16580. m_hRedTimer->SetTimeRemaining( inputdata.value.Int() );
  16581. }
  16582. }
  16583. }
  16584. //-----------------------------------------------------------------------------
  16585. // Purpose:
  16586. //-----------------------------------------------------------------------------
  16587. void CKothLogic::InputSetBlueTimer( inputdata_t &inputdata )
  16588. {
  16589. if ( TFGameRules() && TFGameRules()->IsInKothMode() )
  16590. {
  16591. if ( m_hBlueTimer )
  16592. {
  16593. m_hBlueTimer->SetTimeRemaining( inputdata.value.Int() );
  16594. }
  16595. }
  16596. }
  16597. //-----------------------------------------------------------------------------
  16598. // Purpose:
  16599. //-----------------------------------------------------------------------------
  16600. void CKothLogic::InputAddRedTimer( inputdata_t &inputdata )
  16601. {
  16602. if ( TFGameRules() && TFGameRules()->IsInKothMode() )
  16603. {
  16604. if ( m_hRedTimer )
  16605. {
  16606. m_hRedTimer->AddTimerSeconds( inputdata.value.Int() );
  16607. }
  16608. }
  16609. }
  16610. //-----------------------------------------------------------------------------
  16611. // Purpose:
  16612. //-----------------------------------------------------------------------------
  16613. void CKothLogic::InputAddBlueTimer( inputdata_t &inputdata )
  16614. {
  16615. if ( TFGameRules() && TFGameRules()->IsInKothMode() )
  16616. {
  16617. if ( m_hBlueTimer )
  16618. {
  16619. m_hBlueTimer->AddTimerSeconds( inputdata.value.Int() );
  16620. }
  16621. }
  16622. }
  16623. BEGIN_DATADESC(CCPTimerLogic)
  16624. DEFINE_KEYFIELD( m_iszControlPointName, FIELD_STRING, "controlpoint" ),
  16625. DEFINE_KEYFIELD( m_nTimerLength, FIELD_INTEGER, "timer_length" ),
  16626. DEFINE_INPUTFUNC( FIELD_VOID, "RoundSpawn", InputRoundSpawn ),
  16627. DEFINE_OUTPUT( m_onCountdownStart, "OnCountdownStart" ),
  16628. DEFINE_OUTPUT( m_onCountdown15SecRemain, "OnCountdown15SecRemain" ),
  16629. DEFINE_OUTPUT( m_onCountdown10SecRemain, "OnCountdown10SecRemain" ),
  16630. DEFINE_OUTPUT( m_onCountdown5SecRemain, "OnCountdown5SecRemain" ),
  16631. DEFINE_OUTPUT( m_onCountdownEnd, "OnCountdownEnd" ),
  16632. END_DATADESC();
  16633. LINK_ENTITY_TO_CLASS( tf_logic_cp_timer, CCPTimerLogic );
  16634. //-----------------------------------------------------------------------------
  16635. // Purpose:
  16636. //-----------------------------------------------------------------------------
  16637. void CCPTimerLogic::InputRoundSpawn( inputdata_t &input )
  16638. {
  16639. if ( m_iszControlPointName != NULL_STRING )
  16640. {
  16641. // We need to re-find our control point, because they're recreated over round restarts
  16642. m_hControlPoint = dynamic_cast<CTeamControlPoint*>( gEntList.FindEntityByName( NULL, m_iszControlPointName ) );
  16643. if ( !m_hControlPoint )
  16644. {
  16645. Warning( "%s failed to find control point named '%s'\n", GetClassname(), STRING(m_iszControlPointName) );
  16646. }
  16647. }
  16648. }
  16649. //-----------------------------------------------------------------------------
  16650. // Purpose:
  16651. //-----------------------------------------------------------------------------
  16652. bool CCPTimerLogic::TimerMayExpire( void )
  16653. {
  16654. if ( m_hControlPoint )
  16655. {
  16656. if ( TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, m_hControlPoint->GetPointIndex() ) )
  16657. return false;
  16658. }
  16659. return true;
  16660. }
  16661. //-----------------------------------------------------------------------------
  16662. // Purpose:
  16663. //-----------------------------------------------------------------------------
  16664. void CCPTimerLogic::Think( void )
  16665. {
  16666. if ( !TFGameRules() || !ObjectiveResource() )
  16667. return;
  16668. if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN )
  16669. {
  16670. // game has already been won, our job is done
  16671. m_pointTimer.Invalidate();
  16672. SetContextThink( &CCPTimerLogic::Think, gpGlobals->curtime + 0.15, CP_TIMER_THINK );
  16673. }
  16674. if ( m_hControlPoint )
  16675. {
  16676. if ( TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, m_hControlPoint->GetPointIndex() ) )
  16677. {
  16678. if ( !m_pointTimer.HasStarted() )
  16679. {
  16680. m_pointTimer.Start( m_nTimerLength );
  16681. m_onCountdownStart.FireOutput( this, this );
  16682. ObjectiveResource()->SetCPTimerTime( m_hControlPoint->GetPointIndex(), gpGlobals->curtime + m_nTimerLength );
  16683. }
  16684. else
  16685. {
  16686. if ( m_pointTimer.IsElapsed() )
  16687. {
  16688. // the point must be fully owned by the owner before we reset
  16689. if ( ObjectiveResource()->GetCappingTeam( m_hControlPoint->GetPointIndex() ) == TEAM_UNASSIGNED )
  16690. {
  16691. m_pointTimer.Invalidate();
  16692. m_onCountdownEnd.FireOutput( this, this );
  16693. m_bFire15SecRemain = m_bFire10SecRemain = m_bFire5SecRemain = true;
  16694. ObjectiveResource()->SetCPTimerTime( m_hControlPoint->GetPointIndex(), -1.0f );
  16695. }
  16696. }
  16697. else
  16698. {
  16699. float flRemainingTime = m_pointTimer.GetRemainingTime();
  16700. if ( flRemainingTime <= 15.0f && m_bFire15SecRemain )
  16701. {
  16702. m_bFire15SecRemain = false;
  16703. }
  16704. else if ( flRemainingTime <= 10.0f && m_bFire10SecRemain )
  16705. {
  16706. m_bFire10SecRemain = false;
  16707. }
  16708. else if ( flRemainingTime <= 5.0f && m_bFire5SecRemain )
  16709. {
  16710. m_bFire5SecRemain = false;
  16711. }
  16712. }
  16713. }
  16714. }
  16715. else
  16716. {
  16717. m_pointTimer.Invalidate();
  16718. }
  16719. }
  16720. SetContextThink( &CCPTimerLogic::Think, gpGlobals->curtime + 0.15, CP_TIMER_THINK );
  16721. }
  16722. //-----------------------------------------------------------------------------
  16723. // Purpose:
  16724. //-----------------------------------------------------------------------------
  16725. void CTFGameRules::AddPlayerToQueue( CTFPlayer *pPlayer )
  16726. {
  16727. //Already in Queue
  16728. if ( m_hArenaPlayerQueue.Find( pPlayer ) != m_hArenaPlayerQueue.InvalidIndex() )
  16729. return;
  16730. if ( pPlayer->IsArenaSpectator() == true )
  16731. return;
  16732. // Msg( "AddPlayerToQueue:: Adding to queue: %s\n", pPlayer->GetPlayerName() );
  16733. m_hArenaPlayerQueue.AddToTail( pPlayer );
  16734. }
  16735. //-----------------------------------------------------------------------------
  16736. // Purpose:
  16737. //-----------------------------------------------------------------------------
  16738. void CTFGameRules::AddPlayerToQueueHead( CTFPlayer *pPlayer )
  16739. {
  16740. //Already in Queue
  16741. if ( m_hArenaPlayerQueue.Find( pPlayer ) != m_hArenaPlayerQueue.InvalidIndex() )
  16742. return;
  16743. m_hArenaPlayerQueue.AddToHead( pPlayer );
  16744. // Msg( "AddPlayerToQueueHead:: Adding to queue: %s\n", pPlayer->GetPlayerName() );
  16745. }
  16746. //-----------------------------------------------------------------------------
  16747. // Purpose:
  16748. //-----------------------------------------------------------------------------
  16749. void CTFGameRules::RemovePlayerFromQueue( CTFPlayer *pPlayer )
  16750. {
  16751. //Not in list?
  16752. if ( m_hArenaPlayerQueue.Find( pPlayer ) == m_hArenaPlayerQueue.InvalidIndex() )
  16753. return;
  16754. m_hArenaPlayerQueue.FindAndRemove( pPlayer );
  16755. }
  16756. //-----------------------------------------------------------------------------
  16757. // Purpose:
  16758. //-----------------------------------------------------------------------------
  16759. void CTFGameRules::OnNavMeshLoad( void )
  16760. {
  16761. TheNavMesh->SetPlayerSpawnName( "info_player_teamspawn" );
  16762. }
  16763. //-----------------------------------------------------------------------------
  16764. // Purpose:
  16765. //-----------------------------------------------------------------------------
  16766. void CTFGameRules::OnDispenserBuilt( CBaseEntity *dispenser )
  16767. {
  16768. if ( !m_healthVector.HasElement( dispenser ) )
  16769. {
  16770. m_healthVector.AddToTail( dispenser );
  16771. }
  16772. if ( !m_ammoVector.HasElement( dispenser ) )
  16773. {
  16774. m_ammoVector.AddToTail( dispenser );
  16775. }
  16776. }
  16777. //-----------------------------------------------------------------------------
  16778. // Purpose:
  16779. //-----------------------------------------------------------------------------
  16780. void CTFGameRules::OnDispenserDestroyed( CBaseEntity *dispenser )
  16781. {
  16782. m_healthVector.FindAndFastRemove( dispenser );
  16783. m_ammoVector.FindAndFastRemove( dispenser );
  16784. }
  16785. //-----------------------------------------------------------------------------
  16786. // Purpose:
  16787. //-----------------------------------------------------------------------------
  16788. CPhysicsProp *CreateBeachBall( const Vector &vSpawnPos, const QAngle &qSpawnAngles );
  16789. CPhysicsProp *CreateSoccerBall( const Vector &vSpawnPos, const QAngle &qSpawnAngles );
  16790. #ifdef STAGING_ONLY
  16791. //------------------------------------------------------------------------------
  16792. // Purpose:
  16793. //------------------------------------------------------------------------------
  16794. void CC_Spawn_SoccerBall( const CCommand& args )
  16795. {
  16796. CBasePlayer *pPlayer = UTIL_GetCommandClient();
  16797. if ( pPlayer )
  16798. {
  16799. trace_t tr;
  16800. Vector forward;
  16801. pPlayer->EyeVectors( &forward );
  16802. UTIL_TraceLine( pPlayer->EyePosition(),
  16803. pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID,
  16804. pPlayer, COLLISION_GROUP_NONE, &tr );
  16805. if ( tr.fraction != 1.0 )
  16806. {
  16807. CreateSoccerBall( tr.endpos, vec3_angle );
  16808. }
  16809. }
  16810. }
  16811. ConCommand tf_spawn_soccerball( "tf_spawn_soccerball", CC_Spawn_SoccerBall, "", FCVAR_CHEAT );
  16812. #endif // STAGING_ONLY
  16813. static bool CanFindBallSpawnLocation( const Vector& vSearchOrigin, Vector *out_pvDropSpot )
  16814. {
  16815. // find clear space to drop the ball
  16816. for( float angle = 0.0f; angle < 2.0f * M_PI; angle += 0.2f )
  16817. {
  16818. Vector forward;
  16819. FastSinCos( angle, &forward.y, &forward.x );
  16820. forward.z = 0.0f;
  16821. const float ballRadius = 16.0f;
  16822. const float playerRadius = 20.0f;
  16823. Vector hullMins( -ballRadius, -ballRadius, -ballRadius );
  16824. Vector hullMaxs( ballRadius, ballRadius, ballRadius );
  16825. Vector dropSpot = vSearchOrigin + 1.2f * ( playerRadius + ballRadius ) * forward;
  16826. trace_t result;
  16827. UTIL_TraceHull( dropSpot, dropSpot, hullMins, hullMaxs, MASK_PLAYERSOLID, NULL, &result );
  16828. if ( !result.DidHit() )
  16829. {
  16830. *out_pvDropSpot = dropSpot;
  16831. return true;
  16832. }
  16833. }
  16834. return false;
  16835. }
  16836. void CTFGameRules::OnPlayerSpawned( CTFPlayer *pPlayer )
  16837. {
  16838. // coach?
  16839. CSteamID steamIDForPlayer;
  16840. if ( pPlayer->GetSteamID( &steamIDForPlayer ) )
  16841. {
  16842. // find out if we are supposed to coach
  16843. int idx = m_mapCoachToStudentMap.Find( steamIDForPlayer.GetAccountID() );
  16844. if ( m_mapCoachToStudentMap.IsValidIndex( idx ) )
  16845. {
  16846. // find student
  16847. uint32 studentAccountID = m_mapCoachToStudentMap[idx];
  16848. CSteamID steamIDForStudent;
  16849. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  16850. {
  16851. CTFPlayer *pPotentialStudent = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  16852. if ( NULL == pPotentialStudent )
  16853. {
  16854. continue;
  16855. }
  16856. // is this the student?
  16857. if ( pPotentialStudent->GetSteamID( &steamIDForStudent ) && steamIDForStudent.GetAccountID() == studentAccountID )
  16858. {
  16859. Coaching_Start( pPlayer, pPotentialStudent );
  16860. // @todo (Tom Bui): Not sure this is required--nothing seems to use it
  16861. // engine->ClientCommand( pPlayer->edict(), "cl_spec_mode %d", OBS_MODE_IN_EYE );
  16862. // finally, notify the GC
  16863. GCSDK::CProtoBufMsg< CMsgTFCoaching_CoachJoined > msg( k_EMsgGCCoaching_CoachJoined );
  16864. msg.Body().set_account_id_coach( steamIDForPlayer.GetAccountID() );
  16865. GCClientSystem()->BSendMessage( msg );
  16866. break;
  16867. }
  16868. }
  16869. // remove from the map now so the coach can join later as a normal player if they DC
  16870. m_mapCoachToStudentMap.RemoveAt( idx );
  16871. }
  16872. }
  16873. // warp coach to student?
  16874. if ( pPlayer->GetCoach() )
  16875. {
  16876. // warp the coach to student
  16877. pPlayer->GetCoach()->SetObserverTarget( pPlayer );
  16878. pPlayer->GetCoach()->StartObserverMode( OBS_MODE_CHASE );
  16879. }
  16880. // notify training
  16881. if ( m_hTrainingModeLogic )
  16882. {
  16883. m_hTrainingModeLogic->OnPlayerSpawned( pPlayer );
  16884. }
  16885. #ifdef GAME_DLL
  16886. if ( !IsInTraining() )
  16887. {
  16888. // Birthday beachball ball spawning.
  16889. if ( IsBirthday() &&
  16890. !m_hasSpawnedToy &&
  16891. pPlayer->GetTeamNumber() == TF_TEAM_BLUE && // always give ball to first blue player, since they are often trapped during setup
  16892. RandomInt( 0, 100 ) < tf_birthday_ball_chance.GetInt() )
  16893. {
  16894. Vector vDropSpot;
  16895. if ( CanFindBallSpawnLocation( pPlayer->WorldSpaceCenter(), &vDropSpot ) )
  16896. {
  16897. CPhysicsProp *ball = CreateBeachBall( vDropSpot, pPlayer->GetAbsAngles() );
  16898. if ( ball )
  16899. {
  16900. m_hasSpawnedToy = true;
  16901. // turn on the birthday skin
  16902. ball->m_nSkin = 1;
  16903. }
  16904. }
  16905. }
  16906. // Soccer ball spawning if wearing soccer cleats.
  16907. if ( !m_bHasSpawnedSoccerBall[ pPlayer->GetTeamNumber() ] )
  16908. {
  16909. enum
  16910. {
  16911. kSpawnWith_Nothing = 0,
  16912. kSpawnWith_SoccerBall = 1,
  16913. };
  16914. int iSpawnWithPhysicsToy = kSpawnWith_Nothing;
  16915. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iSpawnWithPhysicsToy, spawn_with_physics_toy );
  16916. if ( iSpawnWithPhysicsToy == kSpawnWith_SoccerBall )
  16917. {
  16918. Vector vDropSpot;
  16919. if ( CanFindBallSpawnLocation( pPlayer->WorldSpaceCenter(), &vDropSpot ) )
  16920. {
  16921. CPhysicsProp *ball = CreateSoccerBall( vDropSpot, pPlayer->GetAbsAngles() );
  16922. if ( ball )
  16923. {
  16924. m_bHasSpawnedSoccerBall[ pPlayer->GetTeamNumber() ] = true;
  16925. // turn on the birthday skin
  16926. ball->m_nSkin = pPlayer->GetTeamNumber() == TF_TEAM_BLUE ? 1 : 0;
  16927. }
  16928. }
  16929. }
  16930. }
  16931. }
  16932. #endif
  16933. }
  16934. class CGCCoaching_CoachJoining : public GCSDK::CGCClientJob
  16935. {
  16936. public:
  16937. CGCCoaching_CoachJoining( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  16938. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  16939. {
  16940. GCSDK::CProtoBufMsg< CMsgTFCoaching_CoachJoining > msg( pNetPacket );
  16941. if ( TFGameRules() )
  16942. {
  16943. TFGameRules()->OnCoachJoining( msg.Body().account_id_coach(), msg.Body().account_id_student() );
  16944. }
  16945. return true;
  16946. }
  16947. };
  16948. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_CoachJoining, "CGCCoaching_CoachJoining", k_EMsgGCCoaching_CoachJoining, GCSDK::k_EServerTypeGCClient );
  16949. class CGCCoaching_RemoveCurrentCoach : public GCSDK::CGCClientJob
  16950. {
  16951. public:
  16952. CGCCoaching_RemoveCurrentCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  16953. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  16954. {
  16955. GCSDK::CProtoBufMsg< CMsgTFCoaching_RemoveCurrentCoach > msg( pNetPacket );
  16956. if ( TFGameRules() )
  16957. {
  16958. TFGameRules()->OnRemoveCoach( msg.Body().account_id_coach() );
  16959. }
  16960. return true;
  16961. }
  16962. };
  16963. GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_RemoveCurrentCoach, "CGCCoaching_RemoveCurrentCoach", k_EMsgGCCoaching_RemoveCurrentCoach, GCSDK::k_EServerTypeGCClient );
  16964. class CGCUseServerModificationItemJob : public GCSDK::CGCClientJob
  16965. {
  16966. public:
  16967. CGCUseServerModificationItemJob( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  16968. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  16969. {
  16970. GCSDK::CProtoBufMsg<CMsgGC_GameServer_UseServerModificationItem> msg( pNetPacket );
  16971. // If this server doesn't have the capability to call a vote right now for whatever reason, we
  16972. // give up and return immediate failure to the GC. If the vote gets called, we'll send up pass/fail
  16973. // when it finishes.
  16974. if ( !g_voteController || !g_voteController->CreateVote( DEDICATED_SERVER, "eternaween", "" ) )
  16975. {
  16976. GCSDK::CProtoBufMsg<CMsgGC_GameServer_UseServerModificationItem_Response> msgResponse( k_EMsgGC_GameServer_UseServerModificationItem_Response );
  16977. msgResponse.Body().set_server_response_code( CMsgGC_GameServer_UseServerModificationItem_Response::kServerModificationItemServerResponse_NoVoteCalled );
  16978. m_pGCClient->BSendMessage( msgResponse );
  16979. }
  16980. return true;
  16981. }
  16982. };
  16983. GC_REG_JOB( GCSDK::CGCClient, CGCUseServerModificationItemJob, "CGCUseServerModificationItemJob", k_EMsgGC_GameServer_UseServerModificationItem, GCSDK::k_EServerTypeGCClient );
  16984. class CGCUpdateServerModificationItemStateJob : public GCSDK::CGCClientJob
  16985. {
  16986. public:
  16987. CGCUpdateServerModificationItemStateJob( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
  16988. virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
  16989. {
  16990. GCSDK::CProtoBufMsg<CMsgGC_GameServer_ServerModificationItem> msg( pNetPacket );
  16991. switch ( msg.Body().modification_type() )
  16992. {
  16993. case kGameServerModificationItem_Halloween:
  16994. tf_item_based_forced_holiday.SetValue( msg.Body().active() ? kHoliday_Halloween : kHoliday_None );
  16995. g_fEternaweenAutodisableTime = engine->Time() + (SERVER_MODIFICATION_ITEM_DURATION_IN_MINUTES * 60.0f);
  16996. if ( TFGameRules() )
  16997. {
  16998. TFGameRules()->FlushAllAttributeCaches();
  16999. }
  17000. break;
  17001. default:
  17002. Warning( "%s: unknown modification type %u for server item.\n", __FUNCTION__, msg.Body().modification_type() );
  17003. break;
  17004. }
  17005. return true;
  17006. }
  17007. };
  17008. GC_REG_JOB( GCSDK::CGCClient, CGCUpdateServerModificationItemStateJob, "CGCUpdateServerModificationItemStateJob", k_EMsgGC_GameServer_ModificationItemState, GCSDK::k_EServerTypeGCClient );
  17009. #ifdef _DEBUG
  17010. CON_COMMAND( coaching_stop, "Stop coaching" )
  17011. {
  17012. CTFPlayer* pCoach = ToTFPlayer( UTIL_GetListenServerHost() );
  17013. Coaching_Stop( pCoach );
  17014. }
  17015. CON_COMMAND( coaching_remove_coach, "Remove current coach" )
  17016. {
  17017. CTFPlayer* pStudent = ToTFPlayer( UTIL_GetListenServerHost() );
  17018. CTFPlayer *pCoach = pStudent->GetCoach();
  17019. if ( pCoach )
  17020. {
  17021. Coaching_Stop( pCoach );
  17022. }
  17023. }
  17024. CON_COMMAND( coaching_force_coach, "Force self as coach" )
  17025. {
  17026. CTFPlayer* pCoachPlayer = ToTFPlayer( UTIL_GetListenServerHost() );
  17027. if ( pCoachPlayer && TFGameRules() )
  17028. {
  17029. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  17030. {
  17031. CTFPlayer *pStudentPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  17032. if ( pStudentPlayer != pCoachPlayer )
  17033. {
  17034. Coaching_Start( pCoachPlayer, pStudentPlayer );
  17035. break;
  17036. }
  17037. }
  17038. }
  17039. }
  17040. CON_COMMAND( coaching_force_student, "Force self as student" )
  17041. {
  17042. CTFPlayer* pStudentPlayer = ToTFPlayer( UTIL_GetListenServerHost() );
  17043. if ( pStudentPlayer && TFGameRules() )
  17044. {
  17045. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  17046. {
  17047. CTFPlayer *pCoachPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  17048. if ( pCoachPlayer != pStudentPlayer )
  17049. {
  17050. Coaching_Start( pCoachPlayer, pStudentPlayer );
  17051. break;
  17052. }
  17053. }
  17054. }
  17055. }
  17056. #endif
  17057. //-----------------------------------------------------------------------------
  17058. // Purpose:
  17059. //-----------------------------------------------------------------------------
  17060. void CTFGameRules::OnCoachJoining( uint32 unCoachAccountID, uint32 unStudentAccountID )
  17061. {
  17062. m_mapCoachToStudentMap.Insert( unCoachAccountID, unStudentAccountID );
  17063. // see if the coach is on the server already
  17064. CSteamID steamIDForCoach;
  17065. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  17066. {
  17067. CTFPlayer *pPotentialCoach = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  17068. if ( NULL == pPotentialCoach )
  17069. {
  17070. continue;
  17071. }
  17072. // coach is here, force them to respawn, which will set them up as a coach
  17073. if ( pPotentialCoach->GetSteamID( &steamIDForCoach ) && steamIDForCoach.GetAccountID() == unCoachAccountID )
  17074. {
  17075. pPotentialCoach->ForceRespawn();
  17076. return;
  17077. }
  17078. }
  17079. }
  17080. //-----------------------------------------------------------------------------
  17081. // Purpose:
  17082. //-----------------------------------------------------------------------------
  17083. void CTFGameRules::OnRemoveCoach( uint32 unCoachAccountID )
  17084. {
  17085. m_mapCoachToStudentMap.Remove( unCoachAccountID );
  17086. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  17087. {
  17088. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  17089. CSteamID steamID;
  17090. if ( pPlayer && pPlayer->GetSteamID( &steamID ) && steamID.GetAccountID() == unCoachAccountID )
  17091. {
  17092. Coaching_Stop( pPlayer );
  17093. return;
  17094. }
  17095. }
  17096. }
  17097. //-----------------------------------------------------------------------------
  17098. // Purpose: Activates 100% crits for an entire team for a short period of time
  17099. //-----------------------------------------------------------------------------
  17100. void CTFGameRules::HandleCTFCaptureBonus( int nTeam )
  17101. {
  17102. float flBonusTime = GetCTFCaptureBonusTime();
  17103. if ( flBonusTime <= 0 )
  17104. return;
  17105. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  17106. {
  17107. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  17108. if ( pPlayer && pPlayer->IsAlive() && pPlayer->GetTeamNumber() == nTeam )
  17109. {
  17110. pPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_CTF_CAPTURE, flBonusTime );
  17111. }
  17112. }
  17113. }
  17114. #endif
  17115. int CTFGameRules::GetStatsMinimumPlayers( void )
  17116. {
  17117. if ( IsInArenaMode() == true )
  17118. {
  17119. return 1;
  17120. }
  17121. return 3;
  17122. }
  17123. int CTFGameRules::GetStatsMinimumPlayedTime( void )
  17124. {
  17125. if ( IsInArenaMode() == true )
  17126. {
  17127. return tf_arena_preround_time.GetFloat();
  17128. }
  17129. return 4 * 60; //Default of 4 minutes
  17130. }
  17131. bool CTFGameRules::IsConnectedUserInfoChangeAllowed( CBasePlayer *pPlayer )
  17132. {
  17133. CTFPlayer* pTFPlayer = NULL;
  17134. #ifdef GAME_DLL
  17135. pTFPlayer = ToTFPlayer( pPlayer );
  17136. #else
  17137. pTFPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() );
  17138. #endif
  17139. if( pTFPlayer )
  17140. {
  17141. // We can change if we're not alive
  17142. if( pTFPlayer->m_lifeState != LIFE_ALIVE )
  17143. return true;
  17144. // We can change if we're not on team red or blue
  17145. int iPlayerTeam = pTFPlayer->GetTeamNumber();
  17146. if( ( iPlayerTeam != TF_TEAM_RED ) && ( iPlayerTeam != TF_TEAM_BLUE ) )
  17147. return true;
  17148. // We can change if we've respawned/changed classes within the last 2 seconds.
  17149. // This allows for <classname>.cfg files to change these types of convars
  17150. float flRespawnTime = 0.f;
  17151. #ifdef GAME_DLL
  17152. flRespawnTime = pTFPlayer->GetSpawnTime(); // Called everytime the player respawns
  17153. #else
  17154. flRespawnTime = pTFPlayer->GetClassChangeTime(); // Called when the player changes class and respawns
  17155. #endif
  17156. if( ( gpGlobals->curtime - flRespawnTime ) < 2.f )
  17157. return true;
  17158. }
  17159. return false;
  17160. }
  17161. //========================================================================================================================
  17162. // BONUS ROUND HANDLING
  17163. //========================================================================================================================
  17164. #ifdef GAME_DLL
  17165. //-----------------------------------------------------------------------------
  17166. // Purpose:
  17167. //-----------------------------------------------------------------------------
  17168. bool CTFGameRules::ShouldGoToBonusRound( void )
  17169. {
  17170. // Only do this on a Valve official server. Use the presence of our DLL as a key.
  17171. return false;
  17172. if ( IsInTournamentMode() )
  17173. return false;
  17174. // Don't do this on empty servers
  17175. if ( !BHavePlayers() )
  17176. return false;
  17177. if ( TFTeamMgr()->GetTeam( TF_TEAM_RED )->GetNumPlayers() <= 0 )
  17178. return false;
  17179. if ( TFTeamMgr()->GetTeam( TF_TEAM_BLUE )->GetNumPlayers() <= 0 )
  17180. return false;
  17181. // Random chance per round, based on time.
  17182. float flRoundTime = gpGlobals->curtime - m_flRoundStartTime;
  17183. float flChance = RemapValClamped( flRoundTime, (3 * 60), (30 * 60), 0.0, 0.75 ); // 75% chance for > 30 min rounds, down to 0% at 3 min rounds
  17184. float flRoll = RandomFloat( 0, 1 );
  17185. return ( flRoll < flChance );
  17186. }
  17187. //-----------------------------------------------------------------------------
  17188. // Purpose:
  17189. //-----------------------------------------------------------------------------
  17190. void CTFGameRules::SetupOnBonusStart( void )
  17191. {
  17192. m_hBonusLogic.Set( dynamic_cast<CBonusRoundLogic*>(CreateEntityByName( "tf_logic_bonusround" )) );
  17193. if ( !m_hBonusLogic.Get()->InitBonusRound() )
  17194. {
  17195. State_Transition( GR_STATE_PREROUND );
  17196. return;
  17197. }
  17198. // Bring up the giveaway panel on all the players
  17199. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  17200. {
  17201. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  17202. if ( !pPlayer )
  17203. continue;
  17204. pPlayer->ShowViewPortPanel( PANEL_GIVEAWAY_ITEM );
  17205. }
  17206. }
  17207. //-----------------------------------------------------------------------------
  17208. // Purpose:
  17209. //-----------------------------------------------------------------------------
  17210. void CTFGameRules::SetupOnBonusEnd( void )
  17211. {
  17212. if ( m_hBonusLogic.Get() )
  17213. {
  17214. UTIL_Remove( m_hBonusLogic.Get() );
  17215. }
  17216. }
  17217. //-----------------------------------------------------------------------------
  17218. // Purpose:
  17219. //-----------------------------------------------------------------------------
  17220. void CTFGameRules::BonusStateThink( void )
  17221. {
  17222. }
  17223. //-----------------------------------------------------------------------------
  17224. // Purpose: We need to abort the bonus state because our item generation failed.
  17225. // Steam is probably down.
  17226. //-----------------------------------------------------------------------------
  17227. void CTFGameRules::BonusStateAbort( void )
  17228. {
  17229. if ( m_hBonusLogic.Get() )
  17230. {
  17231. m_hBonusLogic.Get()->SetBonusStateAborted( true );
  17232. }
  17233. State_Transition( GR_STATE_PREROUND );
  17234. }
  17235. //-----------------------------------------------------------------------------
  17236. // Purpose:
  17237. //-----------------------------------------------------------------------------
  17238. void CTFGameRules::BetweenRounds_Start( void )
  17239. {
  17240. SetSetup( true );
  17241. if ( IsMannVsMachineMode() )
  17242. {
  17243. mp_tournament.SetValue( true );
  17244. RestartTournament();
  17245. SetInStopWatch( false );
  17246. char szName[16];
  17247. Q_strncpy( szName, "ROBOTS", MAX_TEAMNAME_STRING + 1 );
  17248. mp_tournament_blueteamname.SetValue( szName );
  17249. Q_strncpy( szName, "MANNCO", MAX_TEAMNAME_STRING + 1 );
  17250. mp_tournament_redteamname.SetValue( szName );
  17251. SetTeamReadyState( true, TF_TEAM_PVE_INVADERS );
  17252. }
  17253. for ( int i = 0; i < IBaseObjectAutoList::AutoList().Count(); ++i )
  17254. {
  17255. CBaseObject *pObj = static_cast<CBaseObject*>( IBaseObjectAutoList::AutoList()[i] );
  17256. if ( pObj->IsDisposableBuilding() || pObj->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  17257. {
  17258. pObj->DetonateObject();
  17259. }
  17260. }
  17261. if ( m_hGamerulesProxy )
  17262. {
  17263. m_hGamerulesProxy->StateEnterBetweenRounds();
  17264. }
  17265. if ( m_hCompetitiveLogicEntity )
  17266. {
  17267. m_hCompetitiveLogicEntity->OnSpawnRoomDoorsShouldUnlock();
  17268. }
  17269. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  17270. if ( pMatchDesc && pMatchDesc->m_params.m_bAutoReady )
  17271. {
  17272. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  17273. {
  17274. CTFPlayer *pPlayer = static_cast<CTFPlayer*>( UTIL_PlayerByIndex( i ) );
  17275. if ( !pPlayer )
  17276. continue;
  17277. if ( IsValidTFTeam( pPlayer->GetTeamNumber() ) && pPlayer->GetPlayerClass() && IsValidTFPlayerClass( pPlayer->GetPlayerClass()->GetClassIndex() ) )
  17278. {
  17279. PlayerReadyStatus_UpdatePlayerState( pPlayer, true );
  17280. }
  17281. }
  17282. }
  17283. }
  17284. //-----------------------------------------------------------------------------
  17285. // Purpose:
  17286. //-----------------------------------------------------------------------------
  17287. void CTFGameRules::BetweenRounds_End( void )
  17288. {
  17289. SetInWaitingForPlayers( false );
  17290. SetSetup( false );
  17291. if ( IsMannVsMachineMode() )
  17292. {
  17293. SetInStopWatch( false );
  17294. mp_tournament_stopwatch.SetValue( false );
  17295. }
  17296. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  17297. {
  17298. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( i ) );
  17299. if ( !pPlayer )
  17300. continue;
  17301. // We don't consider inactivity during BetweenRounds as idle
  17302. pPlayer->ResetIdleCheck();
  17303. }
  17304. }
  17305. //-----------------------------------------------------------------------------
  17306. // Purpose:
  17307. //-----------------------------------------------------------------------------
  17308. void CTFGameRules::BetweenRounds_Think( void )
  17309. {
  17310. if ( UsePlayerReadyStatusMode() )
  17311. {
  17312. // Everyone is ready, or the drop-dead timer naturally ticked down to mp_tournament_readymode_countdown
  17313. bool bStartFinalCountdown = ( PlayerReadyStatus_ShouldStartCountdown() || ( m_flRestartRoundTime > 0 && (int)( m_flRestartRoundTime - gpGlobals->curtime ) == mp_tournament_readymode_countdown.GetInt() ) );
  17314. // It's the FINAL COUNTDOOOWWWNNnnnnnnnnn
  17315. float flDropDeadTime = gpGlobals->curtime + mp_tournament_readymode_countdown.GetFloat() + 0.1f;
  17316. if ( bStartFinalCountdown && ( m_flRestartRoundTime < 0 || m_flRestartRoundTime >= flDropDeadTime ) )
  17317. {
  17318. float flDelay = IsMannVsMachineMode() ? 10.f : mp_tournament_readymode_countdown.GetFloat();
  17319. m_flRestartRoundTime.Set( gpGlobals->curtime + flDelay );
  17320. ShouldResetScores( true, true );
  17321. ShouldResetRoundsPlayed( true );
  17322. if ( IsCompetitiveMode() )
  17323. {
  17324. m_flCompModeRespawnPlayersAtMatchStart = gpGlobals->curtime + 2.0;
  17325. }
  17326. }
  17327. // Required for UI state
  17328. if ( PlayerReadyStatus_HaveMinPlayersToEnable() )
  17329. {
  17330. CheckReadyRestart();
  17331. }
  17332. }
  17333. CheckRespawnWaves();
  17334. }
  17335. //-----------------------------------------------------------------------------
  17336. // Purpose:
  17337. //-----------------------------------------------------------------------------
  17338. void CTFGameRules::PreRound_Start( void )
  17339. {
  17340. if ( m_hGamerulesProxy )
  17341. {
  17342. m_hGamerulesProxy->StateEnterPreRound();
  17343. }
  17344. if ( m_hCompetitiveLogicEntity )
  17345. {
  17346. m_hCompetitiveLogicEntity->OnSpawnRoomDoorsShouldLock();
  17347. }
  17348. BaseClass::PreRound_Start();
  17349. }
  17350. //-----------------------------------------------------------------------------
  17351. // Purpose:
  17352. //-----------------------------------------------------------------------------
  17353. void CTFGameRules::PreRound_End( void )
  17354. {
  17355. if ( IsHalloweenScenario( HALLOWEEN_SCENARIO_HIGHTOWER ) && !IsInWaitingForPlayers() )
  17356. {
  17357. if ( RandomFloat( 0, 1 ) < HELLTOWER_RARE_LINE_CHANCE )
  17358. {
  17359. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_ROUNDSTART_RARE, HELLTOWER_VO_BLUE_ROUNDSTART_RARE );
  17360. }
  17361. else
  17362. {
  17363. PlayHelltowerAnnouncerVO( HELLTOWER_VO_RED_ROUNDSTART, HELLTOWER_VO_BLUE_ROUNDSTART );
  17364. }
  17365. }
  17366. if ( m_hGamerulesProxy )
  17367. {
  17368. m_hGamerulesProxy->StateExitPreRound();
  17369. }
  17370. if ( m_hCompetitiveLogicEntity )
  17371. {
  17372. m_hCompetitiveLogicEntity->OnSpawnRoomDoorsShouldUnlock();
  17373. }
  17374. BaseClass::PreRound_End();
  17375. }
  17376. //-----------------------------------------------------------------------------
  17377. // Purpose: Compute internal vectors of health and ammo locations
  17378. //-----------------------------------------------------------------------------
  17379. void CTFGameRules::ComputeHealthAndAmmoVectors( void )
  17380. {
  17381. m_ammoVector.RemoveAll();
  17382. m_healthVector.RemoveAll();
  17383. CBaseEntity *pEnt = gEntList.FirstEnt();
  17384. while( pEnt )
  17385. {
  17386. if ( pEnt->ClassMatches( "func_regenerate" ) || pEnt->ClassMatches( "item_healthkit*" ) )
  17387. {
  17388. m_healthVector.AddToTail( pEnt );
  17389. }
  17390. if ( pEnt->ClassMatches( "func_regenerate" ) || pEnt->ClassMatches( "item_ammopack*" ) )
  17391. {
  17392. m_ammoVector.AddToTail( pEnt );
  17393. }
  17394. pEnt = gEntList.NextEnt( pEnt );
  17395. }
  17396. m_areHealthAndAmmoVectorsReady = true;
  17397. }
  17398. //-----------------------------------------------------------------------------
  17399. // Purpose: Return vector of health entities
  17400. //-----------------------------------------------------------------------------
  17401. const CUtlVector< CHandle< CBaseEntity > > &CTFGameRules::GetHealthEntityVector( void )
  17402. {
  17403. // lazy-populate health and ammo vector since some maps (Dario!) move these entities around between stages
  17404. if ( !m_areHealthAndAmmoVectorsReady )
  17405. {
  17406. ComputeHealthAndAmmoVectors();
  17407. }
  17408. return m_healthVector;
  17409. }
  17410. //-----------------------------------------------------------------------------
  17411. // Purpose: Return vector of ammo entities
  17412. //-----------------------------------------------------------------------------
  17413. const CUtlVector< CHandle< CBaseEntity > > &CTFGameRules::GetAmmoEntityVector( void )
  17414. {
  17415. // lazy-populate health and ammo vector since some maps (Dario!) move these entities around between stages
  17416. if ( !m_areHealthAndAmmoVectorsReady )
  17417. {
  17418. ComputeHealthAndAmmoVectors();
  17419. }
  17420. return m_ammoVector;
  17421. }
  17422. //-----------------------------------------------------------------------------
  17423. // Purpose: Return the Payload cart the given team needs to push to win, or NULL if none currently exists
  17424. //-----------------------------------------------------------------------------
  17425. CHandle< CTeamTrainWatcher > CTFGameRules::GetPayloadToPush( int pushingTeam ) const
  17426. {
  17427. if ( TFGameRules()->GetGameType() != TF_GAMETYPE_ESCORT )
  17428. return NULL;
  17429. if ( pushingTeam == TF_TEAM_RED )
  17430. {
  17431. if ( m_redPayloadToPush == NULL )
  17432. {
  17433. // find our cart!
  17434. if ( TFGameRules()->HasMultipleTrains() )
  17435. {
  17436. // find the red cart
  17437. }
  17438. else
  17439. {
  17440. // normal Escort scenario, red always blocks
  17441. return NULL;
  17442. }
  17443. }
  17444. return m_redPayloadToPush;
  17445. }
  17446. if ( pushingTeam == TF_TEAM_BLUE )
  17447. {
  17448. if ( m_bluePayloadToPush == NULL )
  17449. {
  17450. if ( TFGameRules()->HasMultipleTrains() )
  17451. {
  17452. // find the blue cart
  17453. }
  17454. else
  17455. {
  17456. // only one cart in the map, and we need to push it
  17457. CTeamTrainWatcher *watcher = NULL;
  17458. while( ( watcher = dynamic_cast< CTeamTrainWatcher * >( gEntList.FindEntityByClassname( watcher, "team_train_watcher" ) ) ) != NULL )
  17459. {
  17460. if ( !watcher->IsDisabled() )
  17461. {
  17462. m_bluePayloadToPush = watcher;
  17463. break;
  17464. }
  17465. }
  17466. }
  17467. }
  17468. return m_bluePayloadToPush;
  17469. }
  17470. return NULL;
  17471. }
  17472. //-----------------------------------------------------------------------------
  17473. // Purpose: Return the Payload cart the given player needs to block from advancing, or NULL if none currently exists
  17474. //-----------------------------------------------------------------------------
  17475. CHandle< CTeamTrainWatcher > CTFGameRules::GetPayloadToBlock( int blockingTeam ) const
  17476. {
  17477. if ( TFGameRules()->GetGameType() != TF_GAMETYPE_ESCORT )
  17478. return NULL;
  17479. if ( blockingTeam == TF_TEAM_RED )
  17480. {
  17481. if ( m_redPayloadToBlock == NULL )
  17482. {
  17483. // find our cart!
  17484. if ( TFGameRules()->HasMultipleTrains() )
  17485. {
  17486. // find the red cart
  17487. }
  17488. else
  17489. {
  17490. // normal Escort scenario, red always blocks
  17491. CTeamTrainWatcher *watcher = NULL;
  17492. while( ( watcher = dynamic_cast< CTeamTrainWatcher * >( gEntList.FindEntityByClassname( watcher, "team_train_watcher" ) ) ) != NULL )
  17493. {
  17494. if ( !watcher->IsDisabled() )
  17495. {
  17496. m_redPayloadToBlock = watcher;
  17497. break;
  17498. }
  17499. }
  17500. }
  17501. }
  17502. return m_redPayloadToBlock;
  17503. }
  17504. if ( blockingTeam == TF_TEAM_BLUE )
  17505. {
  17506. if ( m_bluePayloadToBlock == NULL )
  17507. {
  17508. if ( TFGameRules()->HasMultipleTrains() )
  17509. {
  17510. // find the blue cart
  17511. }
  17512. else
  17513. {
  17514. // normal Payload, blue never blocks
  17515. return NULL;
  17516. }
  17517. }
  17518. return m_bluePayloadToBlock;
  17519. }
  17520. return NULL;
  17521. }
  17522. #endif // GAME_DLL
  17523. //-----------------------------------------------------------------------------
  17524. // Purpose:
  17525. //-----------------------------------------------------------------------------
  17526. void CTFGameRules::BuildBonusPlayerList( void )
  17527. {
  17528. if ( m_hBonusLogic.Get() )
  17529. {
  17530. m_hBonusLogic.Get()->BuildBonusPlayerList();
  17531. }
  17532. }
  17533. //-----------------------------------------------------------------------------
  17534. // Purpose: Item testing bot controls
  17535. //-----------------------------------------------------------------------------
  17536. void CTFGameRules::ItemTesting_SetupFromKV( KeyValues *pKV )
  17537. {
  17538. m_iItemTesting_BotAnim = pKV->GetInt( "bot_anim", TI_BOTANIM_IDLE );
  17539. int iAnimSpeed = pKV->GetInt( "bot_animspeed", 100 );
  17540. m_flItemTesting_BotAnimSpeed = ( (float)iAnimSpeed / 100.0f );
  17541. m_bItemTesting_BotForceFire = pKV->GetBool( "bot_force_fire" );
  17542. m_bItemTesting_BotTurntable = pKV->GetBool( "bot_turntable" );
  17543. m_bItemTesting_BotViewScan = pKV->GetBool( "bot_view_scan" );
  17544. }
  17545. //==================================================================================================================
  17546. // BONUS ROUND LOGIC
  17547. #ifndef CLIENT_DLL
  17548. EXTERN_SEND_TABLE( DT_ScriptCreatedItem );
  17549. #else
  17550. EXTERN_RECV_TABLE( DT_ScriptCreatedItem );
  17551. #endif
  17552. BEGIN_NETWORK_TABLE_NOBASE( CBonusRoundLogic, DT_BonusRoundLogic )
  17553. #ifdef CLIENT_DLL
  17554. RecvPropUtlVector( RECVINFO_UTLVECTOR( m_aBonusPlayerRoll ), MAX_PLAYERS, RecvPropInt( NULL, 0, 4 ) ),
  17555. RecvPropEHandle( RECVINFO( m_hBonusWinner ) ),
  17556. RecvPropDataTable(RECVINFO_DT(m_Item), 0, &REFERENCE_RECV_TABLE(DT_ScriptCreatedItem)),
  17557. #else
  17558. SendPropUtlVector( SENDINFO_UTLVECTOR( m_aBonusPlayerRoll ), MAX_PLAYERS, SendPropInt( NULL, 0, 4, 12, SPROP_UNSIGNED ) ),
  17559. SendPropEHandle( SENDINFO( m_hBonusWinner ) ),
  17560. SendPropDataTable(SENDINFO_DT(m_Item), &REFERENCE_SEND_TABLE(DT_ScriptCreatedItem)),
  17561. #endif
  17562. END_NETWORK_TABLE()
  17563. LINK_ENTITY_TO_CLASS( tf_logic_bonusround, CBonusRoundLogic );
  17564. IMPLEMENT_NETWORKCLASS_ALIASED( BonusRoundLogic, DT_BonusRoundLogic )
  17565. //-----------------------------------------------------------------------------
  17566. // Purpose:
  17567. //-----------------------------------------------------------------------------
  17568. void CBonusRoundLogic::BuildBonusPlayerList( void )
  17569. {
  17570. m_aBonusPlayerList.Purge();
  17571. for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ )
  17572. {
  17573. CTFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) );
  17574. if ( !pPlayer )
  17575. continue;
  17576. m_aBonusPlayerList.InsertNoSort( pPlayer );
  17577. }
  17578. m_aBonusPlayerList.RedoSort( true );
  17579. }
  17580. #ifdef GAME_DLL
  17581. //-----------------------------------------------------------------------------
  17582. // Purpose:
  17583. //-----------------------------------------------------------------------------
  17584. bool CBonusRoundLogic::InitBonusRound( void )
  17585. {
  17586. m_bAbortedBonusRound = false;
  17587. SetBonusItem( 0 );
  17588. m_hBonusWinner = NULL;
  17589. BuildBonusPlayerList();
  17590. // We actually precalculate the whole shebang.
  17591. m_aBonusPlayerRoll.SetSize( m_aBonusPlayerList.Count() );
  17592. for ( int i = 0; i < m_aBonusPlayerRoll.Count(); i++ )
  17593. {
  17594. m_aBonusPlayerRoll[i] = RandomInt( PLAYER_ROLL_MIN, PLAYER_ROLL_MAX );
  17595. }
  17596. // Sum up the bonus chances
  17597. int iTotal = 0;
  17598. CUtlVector<int> aBonusPlayerTotals;
  17599. aBonusPlayerTotals.SetSize( m_aBonusPlayerList.Count() );
  17600. for ( int i = 0; i < aBonusPlayerTotals.Count(); i++ )
  17601. {
  17602. aBonusPlayerTotals[i] = iTotal + m_aBonusPlayerRoll[i] + m_aBonusPlayerList[i]->m_Shared.GetItemFindBonus();
  17603. iTotal = aBonusPlayerTotals[i];
  17604. }
  17605. // Roll for who gets the item.
  17606. int iRoll = RandomInt( 0, iTotal );
  17607. for ( int i = 0; i < aBonusPlayerTotals.Count(); i++ )
  17608. {
  17609. if ( iRoll < aBonusPlayerTotals[i] )
  17610. {
  17611. m_hBonusWinner = m_aBonusPlayerList[i];
  17612. break;
  17613. }
  17614. }
  17615. if ( !m_hBonusWinner.Get() )
  17616. return false;
  17617. // Generate the item on the server for now
  17618. //CSteamID steamID;
  17619. //if ( !m_hBonusWinner.Get()->GetSteamID( &steamID ) )
  17620. const CSteamID *steamID = engine->GetGameServerSteamID();
  17621. if ( !steamID || !steamID->IsValid() )
  17622. return false;
  17623. return true;
  17624. }
  17625. //-----------------------------------------------------------------------------
  17626. // Purpose:
  17627. //-----------------------------------------------------------------------------
  17628. void CBonusRoundLogic::SetBonusItem( itemid_t iItemID )
  17629. {
  17630. m_iBonusItemID = iItemID;
  17631. if ( !m_iBonusItemID )
  17632. {
  17633. m_Item.Invalidate();
  17634. return;
  17635. }
  17636. }
  17637. //-----------------------------------------------------------------------------
  17638. // Purpose:
  17639. //-----------------------------------------------------------------------------
  17640. void CTFGameRules::SetBonusItem( itemid_t iItemID )
  17641. {
  17642. if ( m_hBonusLogic.Get() )
  17643. {
  17644. m_hBonusLogic.Get()->SetBonusItem( iItemID );
  17645. }
  17646. }
  17647. //-----------------------------------------------------------------------------
  17648. // Purpose:
  17649. //-----------------------------------------------------------------------------
  17650. void CTFGameRules::ProcessVerboseLogOutput( void )
  17651. {
  17652. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  17653. {
  17654. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  17655. if ( pPlayer && ( pPlayer->GetTeamNumber() > TEAM_UNASSIGNED ) )
  17656. {
  17657. UTIL_LogPrintf( "\"%s<%i><%s><%s>\" position_report (position \"%d %d %d\")\n",
  17658. pPlayer->GetPlayerName(),
  17659. pPlayer->GetUserID(),
  17660. pPlayer->GetNetworkIDString(),
  17661. pPlayer->GetTeam()->GetName(),
  17662. (int)pPlayer->GetAbsOrigin().x,
  17663. (int)pPlayer->GetAbsOrigin().y,
  17664. (int)pPlayer->GetAbsOrigin().z );
  17665. }
  17666. }
  17667. }
  17668. //-----------------------------------------------------------------------------
  17669. // Purpose:
  17670. //-----------------------------------------------------------------------------
  17671. void CTFGameRules::MatchSummaryTeleport()
  17672. {
  17673. bool bUseMatchSummaryStage = false;
  17674. #ifdef STAGING_ONLY
  17675. bUseMatchSummaryStage = tf_test_match_summary.GetBool();
  17676. #endif
  17677. const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( GetCurrentMatchGroup() );
  17678. if ( pMatchDesc && pMatchDesc->m_params.m_bUseMatchSummaryStage )
  17679. {
  17680. bUseMatchSummaryStage = true;
  17681. }
  17682. if ( bUseMatchSummaryStage && m_bMapHasMatchSummaryStage )
  17683. {
  17684. RespawnPlayers( true );
  17685. // find the observer target for the stage
  17686. CObserverPoint *pObserverPoint = dynamic_cast<CObserverPoint*>( gEntList.FindEntityByClassname( NULL, "info_observer_point" ) );
  17687. while( pObserverPoint )
  17688. {
  17689. if ( pObserverPoint->IsMatchSummary() )
  17690. {
  17691. pObserverPoint->SetDisabled( false );
  17692. SetRequiredObserverTarget( pObserverPoint );
  17693. break;
  17694. }
  17695. pObserverPoint = dynamic_cast<CObserverPoint*>( gEntList.FindEntityByClassname( pObserverPoint, "info_observer_point" ) );
  17696. }
  17697. // need to do this AFTER we respawn the players above or the conditions will be cleared
  17698. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  17699. {
  17700. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  17701. if ( !pPlayer )
  17702. continue;
  17703. pPlayer->AddFlag( FL_FROZEN );
  17704. if ( pPlayer->GetTeamNumber() >= FIRST_GAME_TEAM ) // spectators automatically get the RequiredObserverTarget that was set above
  17705. {
  17706. CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
  17707. if ( pTFPlayer )
  17708. {
  17709. pTFPlayer->m_Shared.AddCond( ( pTFPlayer->GetTeamNumber() == GetWinningTeam() ) ? TF_COND_COMPETITIVE_WINNER : TF_COND_COMPETITIVE_LOSER );
  17710. if ( pObserverPoint )
  17711. {
  17712. pTFPlayer->SetViewEntity( pObserverPoint );
  17713. pTFPlayer->SetViewOffset( vec3_origin );
  17714. pTFPlayer->SetFOV( pObserverPoint, pObserverPoint->m_flFOV );
  17715. }
  17716. // use this to force the client player anim to face the right direction
  17717. pTFPlayer->SetTauntYaw( pTFPlayer->GetAbsAngles()[YAW] );
  17718. }
  17719. }
  17720. }
  17721. m_bPlayersAreOnMatchSummaryStage.Set( true );
  17722. }
  17723. }
  17724. #ifdef STAGING_ONLY
  17725. //-----------------------------------------------------------------------------
  17726. // Purpose:
  17727. //-----------------------------------------------------------------------------
  17728. void CTFGameRules::MatchSummaryTest( void )
  17729. {
  17730. mp_waitingforplayers_cancel.SetValue( 1 );
  17731. tf_test_match_summary.SetValue( 1 );
  17732. g_fGameOver = true;
  17733. TFGameRules()->State_Transition( GR_STATE_GAME_OVER );
  17734. m_flStateTransitionTime = gpGlobals->curtime + 99999.f;
  17735. TFGameRules()->MatchSummaryStart();
  17736. }
  17737. CON_COMMAND ( show_match_summary, "Show the match summary" )
  17738. {
  17739. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  17740. return;
  17741. if ( args.ArgC() < 2 )
  17742. return;
  17743. if ( FStrEq( args[1], "start" ) )
  17744. {
  17745. TFGameRules()->MatchSummaryTest();
  17746. }
  17747. else if ( FStrEq( args[1], "end" ) )
  17748. {
  17749. TFGameRules()->MatchSummaryEnd();
  17750. }
  17751. }
  17752. #endif // STAGING_ONLY
  17753. //-----------------------------------------------------------------------------
  17754. // Purpose:
  17755. //-----------------------------------------------------------------------------
  17756. void CTFGameRules::MatchSummaryStart( void )
  17757. {
  17758. if ( BAttemptMapVoteRollingMatch() )
  17759. {
  17760. // Grab the final list of maps for users to vote on
  17761. UpdateNextMapVoteOptionsFromLobby();
  17762. m_eRematchState = NEXT_MAP_VOTE_STATE_WAITING_FOR_USERS_TO_VOTE;
  17763. }
  17764. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  17765. {
  17766. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  17767. if ( pPlayer )
  17768. {
  17769. pPlayer->AddFlag( FL_FROZEN );
  17770. }
  17771. }
  17772. m_bShowMatchSummary.Set( true );
  17773. m_flMatchSummaryTeleportTime = gpGlobals->curtime + 2.f;
  17774. if ( m_hGamerulesProxy )
  17775. {
  17776. m_hGamerulesProxy->MatchSummaryStart();
  17777. }
  17778. CBaseEntity *pLogicCase = NULL;
  17779. while ( ( pLogicCase = gEntList.FindEntityByName( pLogicCase, "competitive_stage_logic_case" ) ) != NULL )
  17780. {
  17781. if ( pLogicCase )
  17782. {
  17783. variant_t sVariant;
  17784. sVariant.SetInt( GetWinningTeam() );
  17785. pLogicCase->AcceptInput( "InValue", NULL, NULL, sVariant, 0 );
  17786. break;
  17787. }
  17788. }
  17789. }
  17790. //-----------------------------------------------------------------------------
  17791. // Purpose:
  17792. //-----------------------------------------------------------------------------
  17793. void CTFGameRules::MatchSummaryEnd( void )
  17794. {
  17795. m_bShowMatchSummary.Set( false );
  17796. m_bPlayersAreOnMatchSummaryStage.Set( false );
  17797. SetRequiredObserverTarget( NULL );
  17798. for ( int i = 1; i <= MAX_PLAYERS; i++ )
  17799. {
  17800. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  17801. if ( !pPlayer )
  17802. continue;
  17803. pPlayer->RemoveFlag( FL_FROZEN );
  17804. pPlayer->SetViewEntity( NULL );
  17805. pPlayer->SetFOV( pPlayer, 0 );
  17806. }
  17807. // reset bot convars here
  17808. static ConVarRef tf_bot_quota( "tf_bot_quota" );
  17809. tf_bot_quota.SetValue( tf_bot_quota.GetDefault() );
  17810. static ConVarRef tf_bot_quota_mode( "tf_bot_quota_mode" );
  17811. tf_bot_quota_mode.SetValue( tf_bot_quota_mode.GetDefault() );
  17812. }
  17813. //-----------------------------------------------------------------------------
  17814. // Purpose:
  17815. //-----------------------------------------------------------------------------
  17816. int CTFGameRules::GetTeamAssignmentOverride( CTFPlayer *pTFPlayer, int iDesiredTeam, bool bAutoBalance /*= false*/ )
  17817. {
  17818. int iTeam = iDesiredTeam;
  17819. // Look up GC managed match info
  17820. CSteamID steamID;
  17821. pTFPlayer->GetSteamID( &steamID );
  17822. CMatchInfo *pMatch = GTFGCClientSystem()->GetLiveMatch();
  17823. int nMatchPlayers = pMatch ? pMatch->GetNumActiveMatchPlayers() : 0;
  17824. CMatchInfo::PlayerMatchData_t *pMatchPlayer = ( pMatch && steamID.IsValid() ) ? pMatch->GetMatchDataForPlayer( steamID ) : NULL;
  17825. if ( IsMannVsMachineMode() )
  17826. {
  17827. if ( !pTFPlayer->IsBot() && iTeam != TEAM_SPECTATOR )
  17828. {
  17829. if ( pMatchPlayer && !pMatchPlayer->bDropped )
  17830. {
  17831. // Part of the lobby match
  17832. Log( "MVM assigned %s to defending team (player is in lobby)\n", pTFPlayer->GetPlayerName() );
  17833. return TF_TEAM_PVE_DEFENDERS;
  17834. }
  17835. // Count ad-hoc players on defenders team
  17836. int nAdHocDefenders = 0;
  17837. for ( int i = 1; i <= gpGlobals->maxClients; i++ )
  17838. {
  17839. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  17840. if ( !pPlayer || ( pPlayer->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS ) )
  17841. { continue; }
  17842. CSteamID steamID;
  17843. if ( pPlayer->GetSteamID( &steamID ) && GTFGCClientSystem()->GetLiveMatchPlayer( steamID ) )
  17844. { continue; }
  17845. // Player on defenders that doesn't have a live match entry
  17846. nAdHocDefenders++;
  17847. }
  17848. // Bootcamp mode can mix a lobby with ad-hoc joins
  17849. int nSlotsLeft = kMVM_DefendersTeamSize - nMatchPlayers - nAdHocDefenders;
  17850. if ( nSlotsLeft >= 1 )
  17851. {
  17852. Log( "MVM assigned %s to defending team (%d more slots remaining after us)\n", pTFPlayer->GetPlayerName(), nSlotsLeft-1 );
  17853. // Set Their Currency
  17854. int nRoundCurrency = MannVsMachineStats_GetAcquiredCredits();
  17855. nRoundCurrency += g_pPopulationManager->GetStartingCurrency();
  17856. // deduct any cash that has already been spent
  17857. int spentCurrency = g_pPopulationManager->GetPlayerCurrencySpent( pTFPlayer );
  17858. pTFPlayer->SetCurrency( nRoundCurrency - spentCurrency );
  17859. iTeam = TF_TEAM_PVE_DEFENDERS;
  17860. }
  17861. else
  17862. {
  17863. // no room
  17864. Log( "MVM assigned %s to spectator, all slots for defending team are in use, or reserved for lobby members\n",
  17865. pTFPlayer->GetPlayerName() );
  17866. iTeam = TEAM_SPECTATOR;
  17867. }
  17868. }
  17869. }
  17870. else if ( pMatch )
  17871. {
  17872. if ( !bAutoBalance )
  17873. {
  17874. CSteamID steamID;
  17875. if ( pMatchPlayer )
  17876. {
  17877. iTeam = GetGameTeamForGCTeam( pMatchPlayer->eGCTeam );
  17878. if ( iTeam < FIRST_GAME_TEAM )
  17879. {
  17880. // We should always have a team assigned by the GC
  17881. Warning( "Competitive mode: Lobby player with invalid GC team %i in MatchGroup %i\n", iTeam, (int)pMatch->m_eMatchGroup );
  17882. }
  17883. CheckAndSetPartyLeader( pTFPlayer, iTeam );
  17884. }
  17885. }
  17886. }
  17887. return iTeam;
  17888. }
  17889. //-----------------------------------------------------------------------------
  17890. // Purpose:
  17891. //-----------------------------------------------------------------------------
  17892. static CPhysicsProp *CreatePhysicsToy( const char* pszModelName, const Vector &vSpawnPos, const QAngle &qSpawnAngles )
  17893. {
  17894. if ( pszModelName == NULL )
  17895. return NULL;
  17896. CPhysicsProp *pProp = NULL;
  17897. MDLCACHE_CRITICAL_SECTION();
  17898. MDLHandle_t h = mdlcache->FindMDL( pszModelName );
  17899. if ( h != MDLHANDLE_INVALID )
  17900. {
  17901. // Must have vphysics to place as a physics prop
  17902. studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( h );
  17903. if ( pStudioHdr && mdlcache->GetVCollide( h ) )
  17904. {
  17905. bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
  17906. CBaseEntity::SetAllowPrecache( true );
  17907. // Try to create entity
  17908. pProp = dynamic_cast< CPhysicsProp * >( CreateEntityByName( "prop_physics_override" ) );
  17909. if ( pProp )
  17910. {
  17911. pProp->SetCollisionGroup( COLLISION_GROUP_PUSHAWAY );
  17912. // so it can be pushed by airblast
  17913. pProp->AddFlag( FL_GRENADE );
  17914. // so that it will always be interactable with the player
  17915. //pProp->SetPhysicsMode( PHYSICS_MULTIPLAYER_SOLID );//PHYSICS_MULTIPLAYER_NON_SOLID );
  17916. char buf[512];
  17917. // Pass in standard key values
  17918. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", vSpawnPos.x, vSpawnPos.y, vSpawnPos.z );
  17919. pProp->KeyValue( "origin", buf );
  17920. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", qSpawnAngles.x, qSpawnAngles.y, qSpawnAngles.z );
  17921. pProp->KeyValue( "angles", buf );
  17922. pProp->KeyValue( "model", pszModelName );
  17923. pProp->KeyValue( "fademindist", "-1" );
  17924. pProp->KeyValue( "fademaxdist", "0" );
  17925. pProp->KeyValue( "fadescale", "1" );
  17926. pProp->KeyValue( "inertiaScale", "1.0" );
  17927. pProp->KeyValue( "physdamagescale", "0.1" );
  17928. pProp->Precache();
  17929. DispatchSpawn( pProp );
  17930. pProp->m_takedamage = DAMAGE_YES; // Take damage, otherwise this can block trains
  17931. pProp->SetHealth( 5000 );
  17932. pProp->Activate();
  17933. }
  17934. CBaseEntity::SetAllowPrecache( bAllowPrecache );
  17935. }
  17936. mdlcache->Release( h ); // counterbalance addref from within FindMDL
  17937. }
  17938. return pProp;
  17939. }
  17940. CPhysicsProp *CreateBeachBall( const Vector &vSpawnPos, const QAngle &qSpawnAngles )
  17941. {
  17942. return CreatePhysicsToy( "models/props_gameplay/ball001.mdl", vSpawnPos, qSpawnAngles );
  17943. }
  17944. CPhysicsProp *CreateSoccerBall( const Vector &vSpawnPos, const QAngle &qSpawnAngles )
  17945. {
  17946. return CreatePhysicsToy( "models/player/items/scout/soccer_ball.mdl", vSpawnPos, qSpawnAngles );
  17947. }
  17948. void CTFGameRules::PushAllPlayersAway( const Vector& vFromThisPoint, float flRange, float flForce, int nTeam, CUtlVector< CTFPlayer* > *pPushedPlayers /*= NULL*/ )
  17949. {
  17950. CUtlVector< CTFPlayer * > playerVector;
  17951. CollectPlayers( &playerVector, nTeam, COLLECT_ONLY_LIVING_PLAYERS );
  17952. for( int i=0; i<playerVector.Count(); ++i )
  17953. {
  17954. CTFPlayer *pPlayer = playerVector[i];
  17955. Vector toPlayer = pPlayer->EyePosition() - vFromThisPoint;
  17956. if ( toPlayer.LengthSqr() < flRange * flRange )
  17957. {
  17958. // send the player flying
  17959. // make sure we push players up and away
  17960. toPlayer.z = 0.0f;
  17961. toPlayer.NormalizeInPlace();
  17962. toPlayer.z = 1.0f;
  17963. Vector vPush = flForce * toPlayer;
  17964. pPlayer->ApplyAbsVelocityImpulse( vPush );
  17965. if ( pPushedPlayers )
  17966. {
  17967. pPushedPlayers->AddToTail( pPlayer );
  17968. }
  17969. }
  17970. }
  17971. }
  17972. #endif // GAME_DLL
  17973. //-----------------------------------------------------------------------------
  17974. // Purpose:
  17975. //-----------------------------------------------------------------------------
  17976. bool CTFGameRules::CanUpgradeWithAttrib( CTFPlayer *pPlayer, int iWeaponSlot, attrib_definition_index_t iAttribIndex, CMannVsMachineUpgrades *pUpgrade )
  17977. {
  17978. if ( !pPlayer )
  17979. return false;
  17980. Assert ( pUpgrade );
  17981. // Upgrades on players are considered active at all times
  17982. if ( pUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER )
  17983. {
  17984. switch ( iAttribIndex )
  17985. {
  17986. case 113: // "metal regen"
  17987. {
  17988. return ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) );
  17989. }
  17990. break;
  17991. }
  17992. return true;
  17993. }
  17994. // Get the item entity. We use the entity, not the item in the loadout, because we want
  17995. // the dynamic attributes that have already been purchases and attached.
  17996. CEconEntity *pEntity;
  17997. CEconItemView *pCurItemData = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pPlayer, iWeaponSlot, &pEntity );
  17998. if ( !pCurItemData || !pEntity )
  17999. return false;
  18000. // bottles can only hold things in the appropriate ui group
  18001. if ( dynamic_cast< CTFPowerupBottle *>( pEntity ) )
  18002. {
  18003. if ( pUpgrade->nUIGroup == UIGROUP_POWERUPBOTTLE )
  18004. {
  18005. switch ( iAttribIndex )
  18006. {
  18007. case 327: // "building instant upgrade"
  18008. {
  18009. return ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) );
  18010. }
  18011. #ifndef _DEBUG
  18012. case 480: // "radius stealth"
  18013. {
  18014. return ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) );
  18015. }
  18016. #endif // !_DEBUG
  18017. }
  18018. return true;
  18019. }
  18020. return false;
  18021. }
  18022. else if ( pUpgrade->nUIGroup == UIGROUP_POWERUPBOTTLE )
  18023. {
  18024. return false;
  18025. }
  18026. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase* > ( pEntity );
  18027. CTFWeaponBaseGun *pWeaponGun = dynamic_cast< CTFWeaponBaseGun* > ( pEntity );
  18028. int iWeaponID = ( pWeapon ) ? pWeapon->GetWeaponID() : TF_WEAPON_NONE;
  18029. CTFWearableDemoShield *pShield = dynamic_cast< CTFWearableDemoShield* >( pEntity );
  18030. bool bShield = ( pShield ) ? true : false;
  18031. if ( iWeaponID == TF_WEAPON_PARACHUTE )
  18032. return false;
  18033. // Hack to simplify excluding non-weapons from damage upgrades
  18034. bool bHideDmgUpgrades = iWeaponID == TF_WEAPON_NONE ||
  18035. iWeaponID == TF_WEAPON_LASER_POINTER ||
  18036. iWeaponID == TF_WEAPON_MEDIGUN ||
  18037. iWeaponID == TF_WEAPON_BUFF_ITEM ||
  18038. iWeaponID == TF_WEAPON_BUILDER ||
  18039. iWeaponID == TF_WEAPON_PDA_ENGINEER_BUILD ||
  18040. iWeaponID == TF_WEAPON_INVIS ||
  18041. iWeaponID == TF_WEAPON_SPELLBOOK;
  18042. // What tier upgrade is it?
  18043. int nQuality = pUpgrade->nQuality;
  18044. // This is crappy, but it's hopefully more maintainable than the allowed attributes block for all current & future items
  18045. switch ( iAttribIndex )
  18046. {
  18047. case 2: // "damage bonus"
  18048. {
  18049. if ( bHideDmgUpgrades )
  18050. return false;
  18051. // Some classes get a weaker dmg upgrade
  18052. if ( nQuality == MVM_UPGRADE_QUALITY_LOW )
  18053. {
  18054. if ( pPlayer->IsPlayerClass( TF_CLASS_DEMOMAN ) && !bShield )
  18055. {
  18056. return ( iWeaponSlot == TF_WPN_TYPE_PRIMARY || iWeaponSlot == TF_WPN_TYPE_SECONDARY );
  18057. }
  18058. else
  18059. {
  18060. return false;
  18061. }
  18062. }
  18063. bool bShieldEquipped = false;
  18064. if ( pPlayer->IsPlayerClass( TF_CLASS_DEMOMAN ) )
  18065. {
  18066. for ( int i = 0; i < pPlayer->GetNumWearables(); ++i )
  18067. {
  18068. CTFWearableDemoShield *pWearableShield = dynamic_cast< CTFWearableDemoShield* >( pPlayer->GetWearable( i ) );
  18069. if ( pWearableShield )
  18070. {
  18071. bShieldEquipped = true;
  18072. }
  18073. }
  18074. }
  18075. return ( ( iWeaponSlot == TF_WPN_TYPE_PRIMARY &&
  18076. ( pPlayer->IsPlayerClass( TF_CLASS_SCOUT ) ||
  18077. pPlayer->IsPlayerClass( TF_CLASS_SNIPER ) ||
  18078. pPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) ||
  18079. pPlayer->IsPlayerClass( TF_CLASS_PYRO ) ) ) ||
  18080. ( iWeaponID == TF_WEAPON_SWORD && bShieldEquipped ) );
  18081. }
  18082. break;
  18083. case 6: // "fire rate bonus"
  18084. {
  18085. // Heavy's version of firing speed costs more
  18086. bool bMinigun = iWeaponID == TF_WEAPON_MINIGUN;
  18087. if ( nQuality == MVM_UPGRADE_QUALITY_LOW )
  18088. {
  18089. return bMinigun;
  18090. }
  18091. else if ( iWeaponID == TF_WEAPON_GRENADELAUNCHER && pWeapon && pWeapon->AutoFiresFullClipAllAtOnce() )
  18092. {
  18093. return false;
  18094. }
  18095. // Non-melee version
  18096. return ( dynamic_cast< CTFWeaponBaseMelee* >( pEntity ) == NULL &&
  18097. iWeaponID != TF_WEAPON_NONE && !bHideDmgUpgrades &&
  18098. iWeaponID != TF_WEAPON_FLAMETHROWER &&
  18099. !WeaponID_IsSniperRifleOrBow( iWeaponID ) &&
  18100. !( pWeapon && pWeapon->HasEffectBarRegeneration() ) &&
  18101. !bMinigun );
  18102. }
  18103. break;
  18104. // case 8: // "heal rate bonus"
  18105. case 10: // "ubercharge rate bonus"
  18106. case 314: // "uber duration bonus"
  18107. case 481: // "canteen specialist"
  18108. case 482: // "overheal expert"
  18109. case 483: // "medic machinery beam"
  18110. case 493: // "healing mastery"
  18111. {
  18112. return ( iWeaponID == TF_WEAPON_MEDIGUN );
  18113. }
  18114. break;
  18115. case 31: // "critboost on kill"
  18116. {
  18117. CTFWeaponBaseMelee *pMelee = dynamic_cast<CTFWeaponBaseMelee *> ( pEntity );
  18118. return ( pMelee && (
  18119. pPlayer->IsPlayerClass( TF_CLASS_DEMOMAN ) ||
  18120. pPlayer->IsPlayerClass( TF_CLASS_SPY ) ) );
  18121. }
  18122. case 71: // weapon burn dmg increased
  18123. case 73: // weapon burn time increased
  18124. {
  18125. return ( iWeaponID == TF_WEAPON_FLAMETHROWER || iWeaponID == TF_WEAPON_FLAREGUN );
  18126. }
  18127. break;
  18128. case 76: // "maxammo primary increased"
  18129. {
  18130. return ( pWeapon && pWeapon->GetPrimaryAmmoType() == TF_AMMO_PRIMARY && !pWeapon->IsEnergyWeapon() );
  18131. }
  18132. break;
  18133. case 78: // "maxammo secondary increased"
  18134. {
  18135. return ( pWeapon && pWeapon->GetPrimaryAmmoType() == TF_AMMO_SECONDARY && !pWeapon->IsEnergyWeapon() );
  18136. }
  18137. break;
  18138. case 90: // "SRifle Charge rate increased"
  18139. {
  18140. return WeaponID_IsSniperRifle( iWeaponID );
  18141. }
  18142. break;
  18143. case 103: // "Projectile speed increased"
  18144. {
  18145. if ( pWeaponGun )
  18146. {
  18147. return ( pWeaponGun->GetWeaponProjectileType() == TF_PROJECTILE_PIPEBOMB );
  18148. }
  18149. return false;
  18150. }
  18151. break;
  18152. case 149: // "bleed duration"
  18153. case 523: // "arrow mastery"
  18154. {
  18155. return ( iWeaponID == TF_WEAPON_COMPOUND_BOW );
  18156. }
  18157. break;
  18158. case 218: // "mark for death"
  18159. {
  18160. return ( iWeaponID == TF_WEAPON_BAT_WOOD );
  18161. }
  18162. break;
  18163. case 249: // "charge recharge rate increased"
  18164. case 252: // "damage force reduction"
  18165. {
  18166. return bShield;
  18167. }
  18168. break;
  18169. case 255: // "airblast pushback scale"
  18170. {
  18171. return ( iWeaponID == TF_WEAPON_FLAMETHROWER &&
  18172. pWeaponGun && assert_cast< CTFFlameThrower* >( pWeaponGun )->SupportsAirBlastFunction( TF_FUNCTION_AIRBLAST_PUSHBACK) );
  18173. }
  18174. break;
  18175. case 266: // "projectile penetration"
  18176. {
  18177. if ( pWeaponGun && !bHideDmgUpgrades )
  18178. {
  18179. if ( !( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY ) )
  18180. {
  18181. int iProjectile = pWeaponGun->GetWeaponProjectileType();
  18182. return ( iProjectile == TF_PROJECTILE_ARROW || iProjectile == TF_PROJECTILE_BULLET || iProjectile == TF_PROJECTILE_HEALING_BOLT || iProjectile == TF_PROJECTILE_FESTIVE_ARROW || iProjectile == TF_PROJECTILE_FESTIVE_HEALING_BOLT );
  18183. }
  18184. }
  18185. return false;
  18186. }
  18187. break;
  18188. case 80: // "maxammo metal increased"
  18189. case 276: // "bidirectional teleport"
  18190. case 286: // "engy building health bonus"
  18191. case 343: // "engy sentry fire rate increased"
  18192. case 345: // "engy dispenser radius increased"
  18193. case 351: // "engy disposable sentries"
  18194. {
  18195. return ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) && iWeaponID == TF_WEAPON_PDA_ENGINEER_BUILD );
  18196. }
  18197. break;
  18198. case 278: // "effect bar recharge rate increased"
  18199. {
  18200. return ( pWeapon && pWeapon->HasEffectBarRegeneration() && iWeaponID != TF_WEAPON_BUILDER && iWeaponID != TF_WEAPON_SPELLBOOK );
  18201. }
  18202. break;
  18203. case 279: // "maxammo grenades1 increased"
  18204. {
  18205. return ( iWeaponID == TF_WEAPON_BAT_WOOD || iWeaponID == TF_WEAPON_BAT_GIFTWRAP );
  18206. }
  18207. break;
  18208. case 313: // "applies snare effect"
  18209. {
  18210. return ( iWeaponID == TF_WEAPON_JAR || iWeaponID == TF_WEAPON_JAR_MILK );
  18211. }
  18212. break;
  18213. case 318: // "faster reload rate"
  18214. {
  18215. return ( ( pWeapon && pWeapon->ReloadsSingly() ) ||
  18216. WeaponID_IsSniperRifleOrBow( iWeaponID ) ||
  18217. iWeaponID == TF_WEAPON_FLAREGUN );
  18218. }
  18219. break;
  18220. case 319: // "increase buff duration"
  18221. {
  18222. return ( iWeaponID == TF_WEAPON_BUFF_ITEM );
  18223. }
  18224. break;
  18225. case 320: // "robo sapper"
  18226. #ifdef STAGING_ONLY
  18227. case 601: // "ability spy traps"
  18228. case 603: // "phase cloak"
  18229. #endif // STAGING_ONLY
  18230. {
  18231. return ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && iWeaponID == TF_WEAPON_BUILDER );
  18232. }
  18233. break;
  18234. case 323: // "attack projectiles"
  18235. {
  18236. return ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
  18237. }
  18238. break;
  18239. case 335: // "clip size bonus upgrade"
  18240. {
  18241. return ( pWeapon && !pWeapon->IsBlastImpactWeapon() && pWeapon->UsesClipsForAmmo1() && pWeapon->GetMaxClip1() > 1
  18242. && iWeaponID != TF_WEAPON_FLAREGUN_REVENGE && iWeaponID != TF_WEAPON_SPELLBOOK );
  18243. }
  18244. break;
  18245. case 375: // "generate rage on damage"
  18246. {
  18247. return ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
  18248. }
  18249. break;
  18250. case 395: // "explosive sniper shot"
  18251. {
  18252. return ( pPlayer->IsPlayerClass( TF_CLASS_SNIPER ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY &&
  18253. pWeaponGun && pWeaponGun->GetWeaponProjectileType() == TF_PROJECTILE_BULLET );
  18254. }
  18255. break;
  18256. case 396: // "melee attack rate bonus"
  18257. {
  18258. // Melee version
  18259. return ( dynamic_cast< CTFWeaponBaseMelee* >( pEntity ) != NULL && iWeaponID != TF_WEAPON_BUFF_ITEM &&
  18260. !( pWeapon && pWeapon->HasEffectBarRegeneration() && iWeaponID != TF_WEAPON_BAT_WOOD ) );
  18261. }
  18262. break;
  18263. case 397: // "projectile penetration heavy"
  18264. {
  18265. if ( !bHideDmgUpgrades )
  18266. {
  18267. return ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
  18268. }
  18269. }
  18270. break;
  18271. case 399: // "armor piercing"
  18272. {
  18273. return ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && iWeaponID == TF_WEAPON_KNIFE );
  18274. }
  18275. break;
  18276. case 440: // "clip size upgrade atomic"
  18277. {
  18278. return pWeapon && pWeapon->IsBlastImpactWeapon();
  18279. }
  18280. break;
  18281. case 484: // "mad milk syringes"
  18282. {
  18283. return ( iWeaponID == TF_WEAPON_SYRINGEGUN_MEDIC );
  18284. }
  18285. case 488: // "rocket specialist"
  18286. if ( pWeaponGun )
  18287. {
  18288. return ( pWeaponGun->GetWeaponProjectileType() == TF_PROJECTILE_ROCKET || pWeaponGun->GetWeaponProjectileType() == TF_PROJECTILE_ENERGY_BALL );
  18289. }
  18290. case 499: // generate rage on heal (shield)
  18291. case 554: // revive
  18292. case 555: // medigun specialist
  18293. {
  18294. return ( iWeaponID == TF_WEAPON_MEDIGUN );
  18295. }
  18296. #ifdef STAGING_ONLY
  18297. case 553: // rocket pack
  18298. case 558: // mod flamethrower napalm
  18299. {
  18300. return ( iWeaponID == TF_WEAPON_FLAMETHROWER );
  18301. }
  18302. case 604: // sniper cloak
  18303. case 605: // master sniper
  18304. {
  18305. return ( pPlayer->IsPlayerClass( TF_CLASS_SNIPER ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
  18306. }
  18307. case 611: // airborne infantry
  18308. {
  18309. return ( pPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
  18310. }
  18311. case 624: // construction expert
  18312. case 626: // support engineer
  18313. {
  18314. return ( pPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) && iWeaponSlot == TF_WPN_TYPE_MELEE );
  18315. }
  18316. case 631: // ability doubletap teleport
  18317. {
  18318. return ( pPlayer->IsPlayerClass( TF_CLASS_SCOUT ) && iWeaponSlot == TF_WPN_TYPE_PRIMARY );
  18319. }
  18320. #endif // STAGING_ONLY
  18321. }
  18322. // All weapon related attributes require an item that does damage
  18323. if ( pUpgrade->nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM )
  18324. {
  18325. // All guns
  18326. if ( pWeaponGun )
  18327. return ( iWeaponID != TF_WEAPON_NONE && !bHideDmgUpgrades &&
  18328. !( pWeapon && pWeapon->HasEffectBarRegeneration() ) );
  18329. CTFWeaponBaseMelee *pMelee = dynamic_cast< CTFWeaponBaseMelee* >( pEntity );
  18330. if ( pMelee )
  18331. {
  18332. // All melee weapons except buff banners
  18333. return ( iWeaponID != TF_WEAPON_BUFF_ITEM && !bHideDmgUpgrades );
  18334. }
  18335. return false;
  18336. }
  18337. return false;
  18338. }
  18339. //-----------------------------------------------------------------------------
  18340. //
  18341. //-----------------------------------------------------------------------------
  18342. int CTFGameRules::GetUpgradeTier( int iUpgrade )
  18343. {
  18344. if ( !GameModeUsesUpgrades() || iUpgrade < 0 || iUpgrade >= g_MannVsMachineUpgrades.m_Upgrades.Count() )
  18345. return 0;
  18346. return g_MannVsMachineUpgrades.m_Upgrades[iUpgrade].nTier;
  18347. }
  18348. //-----------------------------------------------------------------------------
  18349. // Given an upgrade and slot, see if its' tier is enabled/available
  18350. //-----------------------------------------------------------------------------
  18351. bool CTFGameRules::IsUpgradeTierEnabled( CTFPlayer *pTFPlayer, int iItemSlot, int iUpgrade )
  18352. {
  18353. if ( !pTFPlayer )
  18354. return false;
  18355. // If the upgrade has a tier, it's mutually exclusive with upgrades of the same tier for the same slot
  18356. int nTier = GetUpgradeTier( iUpgrade );
  18357. if ( !nTier )
  18358. return false;
  18359. bool bIsAvailable = true;
  18360. CEconItemView *pItem = NULL;
  18361. float flValue = 0.f;
  18362. // Go through the upgrades and see if it could apply, and if we already have it
  18363. for ( int i = 0; i < g_MannVsMachineUpgrades.m_Upgrades.Count(); i++ )
  18364. {
  18365. CMannVsMachineUpgrades upgrade = g_MannVsMachineUpgrades.m_Upgrades[i];
  18366. // Same upgrade
  18367. // if ( !V_strcmp( upgrade.szAttrib, g_MannVsMachineUpgrades.m_Upgrades[iUpgrade].szAttrib ) )
  18368. // continue;
  18369. // Different tier
  18370. if ( upgrade.nTier != nTier )
  18371. continue;
  18372. // Wrong type
  18373. if ( upgrade.nUIGroup != g_MannVsMachineUpgrades.m_Upgrades[iUpgrade].nUIGroup )
  18374. continue;
  18375. CEconItemAttributeDefinition *pAttribDef = ItemSystem()->GetStaticDataForAttributeByName( upgrade.szAttrib );
  18376. if ( !pAttribDef )
  18377. continue;
  18378. // Can't use
  18379. if ( !CanUpgradeWithAttrib( pTFPlayer, iItemSlot, pAttribDef->GetDefinitionIndex(), &upgrade ) )
  18380. continue;
  18381. if ( upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_ITEM )
  18382. {
  18383. pItem = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pTFPlayer, iItemSlot );
  18384. if ( pItem )
  18385. {
  18386. ::FindAttribute_UnsafeBitwiseCast< attrib_value_t >( pItem->GetAttributeList(), pAttribDef, &flValue );
  18387. }
  18388. }
  18389. else if ( upgrade.nUIGroup == UIGROUP_UPGRADE_ATTACHED_TO_PLAYER )
  18390. {
  18391. ::FindAttribute_UnsafeBitwiseCast< attrib_value_t >( pTFPlayer->GetAttributeList(), pAttribDef, &flValue );
  18392. }
  18393. if ( flValue > 0.f )
  18394. {
  18395. bIsAvailable = false;
  18396. break;
  18397. }
  18398. }
  18399. return bIsAvailable;
  18400. }
  18401. #ifdef GAME_DLL
  18402. //-----------------------------------------------------------------------------
  18403. // Helper Functions
  18404. //-----------------------------------------------------------------------------
  18405. void CTFGameRules::SetNextMvMPopfile ( const char * next )
  18406. {
  18407. s_strNextMvMPopFile = next;
  18408. }
  18409. //-----------------------------------------------------------------------------
  18410. const char * CTFGameRules::GetNextMvMPopfile ( )
  18411. {
  18412. return s_strNextMvMPopFile.Get();
  18413. }
  18414. //-----------------------------------------------------------------------------
  18415. // Purpose:
  18416. //-----------------------------------------------------------------------------
  18417. void CTFGameRules::BalanceTeams( bool bRequireSwitcheesToBeDead )
  18418. {
  18419. // are we playing a managed match via matchmaking?
  18420. if ( GetMatchGroupDescription( GetCurrentMatchGroup() ) )
  18421. return;
  18422. if ( mp_autoteambalance.GetInt() == 2 )
  18423. return;
  18424. BaseClass::BalanceTeams( bRequireSwitcheesToBeDead );
  18425. }
  18426. #endif