#base robot_giant.pop #base robot_standard.pop #base robot_standard_red.pop #base robot_giant_red.pop // Reverse MvM example mission for Potato's Custom MvM Servers // Made by Braindawg // Intended mostly for experienced mission makers // Mapping experience helps tremendously //Useful references: //Developer wiki for source/TF2 ents: // https://developer.valvesoftware.com/wiki/List_of_base_entities // https://developer.valvesoftware.com/wiki/List_of_Team_Fortress_2_Entities //BSPSource download: // https://github.com/ata4/bspsrc //Valve maps decompiled: // https://github.com/sigsegv-mvm/mvm-maps //sigmod demo popfile, showcases every new feature // https://drive.google.com/file/d/1cRqM3XnIReCIexOWW_4PmEkI6Wio6JWx/view?usp=sharing // Linear maps with long, winding bomb paths that can be sectioned off generally flow the best and are the easiest missions to balance. Gatebot works great for progression, however these can get pretty complex with point templates. Rottenburg, Mannworks, and Mannhattan are good example maps. // Open maps with sandbox-style missions are also fine and can be less heavy on custom logic. These are far more difficult to structure and make cheese-proof, but can be tons of fun. If you're up for the challenge, Bigrock and Decoy are good example maps // It's up to you if you want to implement a lose condition for players like a timer or VIP or whatnot // This is a pretty new concept, so try to be forgiving with your lose condition if you use one. // Just know that NPC escort is terrible. Don't do it. WaveSchedule { StartingCurrency 20000 CanBotsAttackWhileInSpawnRoom Yes //keep this so companion bots can combat spawn camping RespawnWaveTimeBlue 1 //replaces the normal respawn kv's FixedRespawnWaveTimeBlue 1 // Essential stuff ReverseWinConditions 1 AllowJoinTeamBlueMax 6 AllowJoinTeamBlue 1 HumansMustJoinTeam Blue SetCreditTeam 3 SniperAllowHeadshots 1 //sniper and amby un-exist without this, use AimAt Body if you don't want your sniper bots headshotting // MaxTotalPlayers 6 // currently doesn't work in reverse // Optional stuff ImprovedAirblast 1 SendBotsToSpectatorImmediately 1 //keeps the populator less clogged, bot projectiles vanish on death + causes weird killcams RobotLimit 27 BluHumanInfiniteAmmo 1 FlagCarrierMovementPenalty 1 // (default: 0.5) BluHumanFlagCapture 1 BluHumanFlagPickup 1 BluPlayersAreRobots 1 // Engi-bot style teleporter. Not recommended for gatebot, highly recommended for non-gatebot // Entrances will also act like bot tele exits BotTeleportUberDuration 1 BluHumanTeleportOnSpawn 1 BluHumanBotTeleporter 1 // BotsRandomCrit 1 // BotPushaway 0 //helpful for tele's but clumps squads into a ball // SentryBusterFriendlyFire 0 // CustomUpgradesFile "mvm_upgrades_example.txt" // Precache all custom models and sounds PrecacheModel "models\props_mvm\mvm_upgrade_blu_center.mdl" PrecacheModel "models\props_mvm\mvm_upgrade_blu_tools.mdl" PrecacheModel "models\bots\boss_bot\boss_tankred.mdl" // PrecacheSound "combine_bank_alarm.mp3" // Entities make up essentially every important part of a source engine map. // Ambient sounds, gates and doors, the stuff that tells gates and doors to open, etc. // All of it is a tangible "thing" that can be spawned, interacted with, and connected together like virtual lego blocks. // PointTemplates are a way to spawn custom entities in your mission, as well as modify existing map entities // There are limits to what can be spawned this way (many brush ents like func_door will not work), but not many. // To learn about your map, decompile the bsp using BSPSource and open the resulting .vmf file in notepad. Ctrl + F is your new best friend // You can also make all of your custom logic and ents in hammer and copy/paste the results from the .vmf to this popfile with only small formatting changes, however hammer kinda scares me. // Obvious reminder: compiling and republishing these maps without the authors consent is wrong and you will be criticized for doing so // Alternatively, load the map in singleplayer and type "ent_messages " and "developer 1" in console to show targetnames as floating text in the world // i.e. ent_messages info_player_teamspawn // Digging through decompiled .vmf's and/or learning hammer will better teach you how certain relays/entities work, many parts of a vmf look almost exactly like this PointTemplates { corelogic //all the things we want to automatically run when the popfile loads. Use this the most to avoid spaghetti { NoFixup 1 logic_auto { "origin" "0 0 0" "targetname" "mainrelay" //delete as much unnecessary/unwanted stuff as you can from your map of choice. //you probably won't risk crashes on simpler mvm maps, but the further away from the edict limit you can be the better "OnMapSpawn" "item_ammopack*,Kill,,0,-1" //might want to comment this out if players have limited ammo "OnMapSpawn" "Barricade*,Kill,,0,-1" //rottenburg specific, deletes front tank barricade //if there is no targetname for wasteful ents, you can also delete them by classname //deleting these decoration ropes frees up nearly 100 edicts on mvm_yiresa "OnMapSpawn" "move_rope,Kill,,0,-1" "OnMapSpawn" "keyframe_rope,Kill,,0,-1" //"OnMapSpawn" "filter_redteam,Kill,,0,-1" //red team filters might cause problems, deleting them might cause more problems //"OnMapSpawn" "trigger_push,Disable,,0,-1" //some maps use trigger_pushes intended to un-stick bots //Gatebot related: //"OnMapSpawn" "bot_stun_*,Kill,,0,-1" //some gates may stun players //"OnMapSpawn" "filter_blue_bombhat,Kill,,0,-1" //gatebot maps will need their capture filter deleted for players to cap //"OnMapSpawn" "gate1_alarm*,Kill,,0,-1" //gets spammed by both red and blu team //AddOutput can be used to connect our own home-brewed point templates to existing map logic like so: "OnMapSpawn" "wave_start_relay*,AddOutput,OnTrigger spawnbarrier*:Disable:0:-1" "OnMapSpawn" "wave_finished_relay,AddOutput,OnTrigger spawnbarrier*:Enable:0:-1" //What we're doing: //"wave_start_relay*,AddOutput, //wave_start_relay_classic/endurance is the targetname for a logic_relay that is triggered on wave start, logic_relays (along with most other ents) accept AddOutput as an input. // * will trigger our relay alongside any others with wave_start_relay in the name, this only works for suffixes (*_start_relay* will not work) //OnTrigger spawnbarrier*:Disable:0:-1" //When wave_start_relay_classic/endurance triggers, it will trigger Disable on 'spawnbarrierA' and 'spawnbarrierA1' with a 0 second delay, -1 means this relay can be triggered an infinite amount of times. //We can also use AddOutput to change an ent rather than just latch onto it: "OnMapSpawn" "hint,AddOutput,display_text test,10,-1" //change annotation text to test 10s after map spawn "OnMapSpawn" "wave_start_relay_classic,AddOutput,OnTrigger hint:Show:0:-1" "OnMapSpawn" "tankpath1_1,AddOutput,OnPass red_tank_relay:Trigger:0:-1" } NoFixup 1 logic_relay //trigger this to kill all players and buildings { "targetname" "kill_relay" "OnTrigger" "obj_dispenser,RemoveHealth,5000,0,-1" "OnTrigger" "obj_sentrygun,RemoveHealth,5000,0,-1" "OnTrigger" "obj_teleporter,RemoveHealth,5000,0,-1" "OnTrigger" "player,SetHealth,-10000,0,-1" } NoFixup 1 logic_relay //trigger this to lose { "origin" "0 0 0" "targetname" "redwin_relay" "OnTrigger" "bots_win_red,RoundWin,,0,-1" "OnTrigger" "pit_explosion_wav,PlaySound,,0,-1" //map dependent } NoFixup 1 logic_relay { "targetname" "red_tank_relay" "OnTrigger" "tankbossred,Setteam,2,0.25,-1" "OnTrigger" "tankbossred,AddCaptureDestroyPostfix,destroy_mvm_cactus_valley3,0,-1" //cool explodey effect, doesn't work :( } NoFixup 1 trigger_multiple { "targetname" "thirdperson" "StartDisabled" "1" "spawnflags" "3" "origin" "1542.488770 739.029175 -143.968689" "maxs" "50 50 50" "mins" "-50 -50 -50" "filtername" "filter_blue" "OnStartTouch" "!activator,setforcedtauntcam,1,0.1,-1" //!activator is any entity currently inside of trigger_multiple "OnStartTouch" "!activator,SetHUDVisibility,0,0.1,-1" "OnStartTouch" "!activator,DisableDamageForces,,0,-1" //doesn't work? "OnEndTouch" "!activator,setforcedtauntcam,0,0,-1" "OnEndTouch" "!activator,SetHUDVisibility,1,0,-1" "OnEndTouch" "!activator,EnableDamageForces,,0,-1" } func_nav_prerequisite //tells bots with a tag to defend this area, like telling gatebots to stay at the gate { "targetname" "towernav" "mins" "-1000 -1000 -1000" "maxs" "1000 1000 1000" "Entity" "towerspawn" "filtername" "filter_tower" "origin" "1542.488770 739.029175 -143.968689" "spawnflags" "1" "start_disabled" "0" "StartDisabled" "0" "Task" "2" "Value" "-1" } filter_tf_bot_has_tag //our tag { "Negated" "0" "require_all_tags" "1" "tags" "bot_tower" "targetname" "filter_tower" } func_nav_prerequisite //repeated for hatch { "targetname" "hatchnav" "mins" "-100 -100 -100" "maxs" "100 100 100" "Entity" "hatchspawn" "filtername" "filter_hatch" "origin" "1542.488770 739.029175 -143.968689" "spawnflags" "1" "start_disabled" "0" "StartDisabled" "0" "Task" "2" "Value" "-1" } filter_tf_bot_has_tag { "Negated" "0" "require_all_tags" "1" "tags" "bot_hatch" "targetname" "filter_hatch" } NoFixup 1 game_round_win //do not interact with this { "origin" "0 0 0" "TeamNum" "2" "targetname" "bots_win_red" "switch_teams" "0" "force_map_reset" "1" "classname" "game_round_win" } } annotations { NoFixup 1 training_annotation { "targetname" "hint" "display_text" "This is an annotation! Use these to convey important information to players" "lifetime" "10" "origin" "2926.356201 -3175.968750 -61.9686" } } barriers //spawn blocker { NoFixup 1 func_forcefield { "disablereceiveshadows" "0" "origin" "0 0 0" "renderamt" "255" "rendercolor" "255 255 255" "renderfx" "0" "rendermode" "10" "TeamNum" "2" "targetname" "spawnbarrierA1" "parentname" "spawnbarrierA" "mins" "-300 -100 -300" "maxs" "300 100 1100" "StartDisabled" "0" } NoFixup 1 prop_dynamic //prop for show { "targetname" "spawnbarrierA" "angles" "0 0 0" "DisableBoneFollowers" "1" "disablereceiveshadows" "1" "model" "models/props_vehicles/train_flatcar_container.mdl" "disableshadows" "1" "ExplodeDamage" "0" "ExplodeRadius" "0" "fademaxdist" "0" "fademindist" "-1" "fadescale" "1" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "modelscale" "1.5" "PerformanceMode" "0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "0" "renderfx" "16" //hologram effect, see renderfx section on the developer wiki for more "rendermode" "0" "SetBodyGroup" "0" "skin" "0" //set to 1 for red traincar, many props separate different variants through skins "CollisionGroup" "0" "solid" "0" "spawnflags" "0" "StartDisabled" "0" "origin" "0 0 0" } } barriers2 //tunnel blocker { NoFixup 1 func_forcefield //what actually blocks players { "disablereceiveshadows" "0" "origin" "0 0 0" "renderamt" "255" "rendercolor" "255 255 255" "renderfx" "0" "rendermode" "10" "TeamNum" "2" "targetname" "barrierB1" "parentname" "barrierB" "mins" "-100 -100 -100" "maxs" "300 200 100" "StartDisabled" "0" } NoFixup 1 prop_dynamic //prop for show { "targetname" "barrierB" "angles" "0 0 0" "DisableBoneFollowers" "1" "disablereceiveshadows" "1" "model" "models/props_vehicles/train_flatcar_container.mdl" "disableshadows" "1" "ExplodeDamage" "0" "ExplodeRadius" "0" "fademaxdist" "0" "fademindist" "-1" "fadescale" "1" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "modelscale" "1.5" "PerformanceMode" "0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "0" "renderfx" "16" //hologram "rendermode" "0" "SetBodyGroup" "0" "skin" "0" "CollisionGroup" "0" "solid" "0" "spawnflags" "0" "StartDisabled" "0" "origin" "0 0 0" } } station { func_upgradestation { "mins" "-105 -100 0" "maxs" "105 100 242" "solid" "0" } NoFixup 1 prop_dynamic { "targetname" "upgradestation" "angles" "0 0 0" "DisableBoneFollowers" "0" "disablereceiveshadows" "0" "disableshadows" "1" "ExplodeDamage" "0" "ExplodeRadius" "0" "fademaxdist" "0" "fademindist" "-1" "fadescale" "1" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "model" "models\props_mvm\mvm_upgrade_blu_center.mdl" "modelscale" "1" "PerformanceMode" "0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "255" "renderfx" "0" "rendermode" "0" "SetBodyGroup" "0" "skin" "0" "solid" "0" "spawnflags" "0" "origin" "0 0 0" } NoFixup 1 prop_dynamic { "targetname" "upgradestation1" "angles" "0 0 0" "DisableBoneFollowers" "0" "disablereceiveshadows" "0" "disableshadows" "1" "ExplodeDamage" "0" "ExplodeRadius" "0" "fademaxdist" "0" "fademindist" "-1" "fadescale" "1" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "model" "models\props_mvm\mvm_upgrade_blu_tools.mdl" "modelscale" "1" "PerformanceMode" "0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "255" "renderfx" "0" "rendermode" "0" "SetBodyGroup" "0" "skin" "0" "solid" "0" "spawnflags" "0" "origin" "0 0 0" } func_upgradestation { "mins" "-100 -100 0" "maxs" "90 60 100" "parentname" "upgradestation" } NoFixup 1 prop_dynamic { "targetname" "shopcollision" "angles" "0 -90 0" "DisableBoneFollowers" "1" "disablereceiveshadows" "1" "model" "models/props_vehicles/train_flatcar_container.mdl" "disableshadows" "1" "ExplodeDamage" "0" "ExplodeRadius" "0" "fademaxdist" "0" "fademindist" "-1" "fadescale" "1" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "modelscale" "1" "PerformanceMode" "0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "0" "renderfx" "0" "rendermode" "10" "SetBodyGroup" "0" "skin" "0" "CollisionGroup" "5" "solid" "6" "spawnflags" "0" "StartDisabled" "0" "origin" "0 0 0" } } blocker //giant building for blocking stuff { NoFixup 1 prop_dynamic { "id" "3" "targetname" "barrierinvis" "classname" "prop_dynamic" "angles" "0 0 0" "DisableBoneFollowers" "0" "disablereceiveshadows" "0" "disableshadows" "1" "ExplodeDamage" "0" "ExplodeRadius" "0" "fademaxdist" "0" "fademindist" "-1" "fadescale" "1" "MaxAnimTime" "10" "maxdxlevel" "0" "MinAnimTime" "5" "mindxlevel" "0" "model" "models\props_buildings\building_002a.mdl" "modelscale" "1" "PerformanceMode" "0" "pressuredelay" "0" "RandomAnimation" "0" "renderamt" "255" "renderfx" "0" "rendermode" "0" // "rendermode" "10" un-comment this and comment rendermode 0 to make it invisible "SetBodyGroup" "0" "skin" "0" "solid" "6" "spawnflags" "0" "origin" "0 0 0" } } } SpawnTemplate "corelogic" SpawnTemplate "annotations" SpawnTemplate //specify origin/angles here for templates we use multiple times { Name "station" "origin" "2924.840088 -3275.968750 -161.96868" "angles" "0 90 0" } SpawnTemplate { Name "station" "origin" "1325.108643 -3090.374756 -50.714088" "angles" "0 -190 0" } SpawnTemplate { Name "barriers" //use these if your map has no spawn door "origin" "2724.365479 -2303.941650 -143.139458" "angles" "0 90 0" } SpawnTemplate { Name "barriers" "origin" "381.125824 -2462.805176 -125.37920" "angles" "0 0 0" } // SpawnTemplate //temporarily comented out due to a collision bug on potato servers, otherwise works // { // Name "barriers2" // "origin" "1526.642578 -19.171825 -425.004791" // "angles" "0 0 0" // } // SpawnTemplate // { // Name "barriers2" // "origin" "1726.642578 -19.171825 -425.004791" // "angles" "0 0 0" // } ExtraSpawnPoint { Name "tunnelspawn" TeamNum 2 //2 for red 3 for blu X "1530" Y "199" Z "-340" } ExtraSpawnPoint { Name "towerspawn" TeamNum 2 X "1534.326782" Y "770.423157" Z "-143.968689" } ExtraSpawnPoint //use RandomSpawn 1 to split spawns evenly { Name "towerspawn" TeamNum 2 X "1535.212036" Y "1306.657837" Z "-143.968689" } ExtraSpawnPoint { Name "hatchspawn" TeamNum 2 X "-1377.705200" Y "1534.423157" Z "-90.968689" } ExtraSpawnPoint { Name "RedSpawn_giant" //giants can't fit out of bot spawn TeamNum 2 X "-1231" Y "2213.423157" Z "-60.968689" } ExtraTankPath //Adds tank path to follow { Name "tankpath1" //name of the starting path node prefix. First tank node would be tankpath1_1 Node "1514.066284 305.011200 -354.974762" // note XYZ coordinates. First node is the starting point Node "1521.085693 -1548.762329 -535.97607" Node "2456.468750 -1615.824707 -589.37457" } PeriodicSpawn { //Useful if you want the same bot across the whole mission ClosestPoint spawnbot //bet you forgot about ClosestPoint When { MinInterval 5 MaxInterval 30 } TFBot { Template T_TFBot_Scout_Fish Health 50 // No MaxActive = janky hp drain failsafe NoBombUpgrades 1 CharacterAttributes {"health drain" -3 "force distribute currency on death" 1 "move speed bonus" 2} } } Wave { InitWaveOutput { //showing annotations Target hint Action Show } StartWaveOutput { Target wave_start_relay_classic Action Trigger } DoneOutput { Target wave_finished_relay Action Trigger } Explanation { Line "{yellow}Explanation Text {blue}is useful for conveying information" } //-------------------------- //RED WAVESPAWNS //-------------------------- WaveSpawn { Name "rWave1a" Where tunnelspawn RandomSpawn 1 TotalCount 3 MaxActive 1 SpawnCount 1 //recommended that you give DisableDodge to giants with Mobber AI TFBot { Template T_TFBot_Red_Giant_Demo_RapidFire Attributes DisableDodge } } WaveSpawn { Name "rWave1a" Where tunnelspawn RandomSpawn 1 TotalCount 20 MaxActive 8 SpawnCount 2 RandomChoice { TFBot { Template T_TFBot_Red_Soldier ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Scout ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Demoman ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Pyro ClassIcon red2_lite } } } WaveSpawn { //tower defender Name "rWave1b" Where towerspawn RandomSpawn 1 TotalCount 1 MaxActive 1 SpawnCount 1 WaitForAllDead "rWave1a" //comented out due to a collision bug on potato servers, otherwise works // FirstSpawnOutput { //disable tunnel barrier forcefield // Target barrierB1 // Action Disable // } // LastSpawnOutput { //disable tunnel barrier prop // Target barrierB // Action Disable // } TFBot { Template T_TFBot_Red_Giant_Demo_Burst Attributes AlwaysCrit Attributes DisableDodge Tag bot_tower //our tag } } WaveSpawn { Name "rWave1b" Where towerspawn RandomSpawn 1 TotalCount 20 MaxActive 8 SpawnCount 2 WaitForAllDead "rWave1a" RandomChoice { TFBot { Template T_TFBot_Red_Soldier ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Scout ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Demoman ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Pyro ClassIcon red2_lite } } } WaveSpawn { Name "rWave1c" Where RedSpawn_giant RandomSpawn 1 TotalCount 3 MaxActive 1 SpawnCount 1 WaitBetweenSpawnsAfterDeath 5 WaitForAllSpawned "rWave1b" TFBot { Template T_TFBot_Red_Giant_Heavyweapons_Shotgun Attributes DisableDodge Tag bot_hatch //our tag } } WaveSpawn { //hatch defender Name "rWave1c" Where hatchspawn RandomSpawn 1 TotalCount 1 MaxActive 1 SpawnCount 1 WaitForAllDead "rWave1a" TFBot { Template T_TFBot_Red_Giant_Pyro Attributes DisableDodge Attributes AlwaysCrit Tag bot_hatch //our tag } } WaveSpawn { Name "rWave1c" Where red_player_teamspawn // Where "" //not sure if this trick works for red bots, also map dependent RandomSpawn 1 TotalCount 959 //nice clean 999 on the wavebar MaxActive 8 SpawnCount 2 WaitForAllSpawned "rWave1b" RandomChoice { TFBot { Template T_TFBot_Red_Soldier ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Scout ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Demoman ClassIcon red2_lite } TFBot { Template T_TFBot_Red_Pyro ClassIcon red2_lite } } } //-------------------------- //BLU WAVESPAWNS //-------------------------- WaveSpawn { Name "bWave1a" Where spawnbot MaxActive 1 SpawnCount 1 RandomSpawn 1 Support 1 WaitBetweenSpawnsAfterDeath 6 TFBot { Template T_TFBot_Giant_Scout_FAN ClassIcon scout_fan Action Mobber } } WaveSpawn { Name "bWave1a1" Where spawnbot TotalCount 999 MaxActive 2 SpawnCount 1 RandomSpawn 1 Support Limited //Support Limited and TotalCount 999 for RandomChoice/Squadded support WaitBetweenSpawns 3 RandomChoice { TFBot { Class Scout Skill Normal Action Mobber } TFBot { Template T_TFBot_Scout_FAN Action Mobber } } } } Wave { StartWaveOutput { Target wave_start_relay_endurance Action Trigger } DoneOutput { Target wave_finished_relay Action Trigger } WaveSpawn { Name "redtank" TotalCount 1 WaitBeforeStarting 3 Tank { Health 40000 Name "tankbossred" ClassIcon tank_red_lite Model "models/bots/boss_bot/boss_tankred.mdl" StartingPathTrackNode "tankpath1_1" OnKilledOutput { Target boss_dead_relay Action Trigger } } } } Wave { InitWaveOutput { Target boss_deploy_relay Action Trigger } } //Bug: final wave doesn't reset correctly, use this workaround for now }