#base robot_giant.pop #base robot_standard.pop #base robot_standard_red.pop #base robot_giant_red.pop #base robot_spidertank.pop WaveSchedule { Wave { WaveSpawn { Name "wave01a" TotalCount 1 WaitBeforeStarting 0 FirstSpawnOutput { Target boss_spawn_relay Action Trigger } Tank { Health 1000 Speed 150 Name "tankboss" StartingPathTrackNode "tank_path_a_1" Template SpiderTank OnKilledOutput { Target boss_dead_relay Action Trigger } OnBombDroppedOutput { Target boss_deploy_relay Action Trigger } } } } } // 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/rafmod demo popfile, showcases every new feature on Potato servers // https://drive.google.com/file/d/1cRqM3XnIReCIexOWW_4PmEkI6Wio6JWx/view?usp=sharing //potato wiki // https://sigwiki.potato.tf/index.php/Main_Page // 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 experimental thing still, so try to be forgiving with your lose condition if you use one. // Just remember that NPC escort is always terrible. 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 AllowJoinTeamBlue 1 HumansMustJoinTeam Blue SetCreditTeam 3 SniperAllowHeadshots 1 //sniper and amby don't work without this, use AimAt Body if you don't want your snipers headshotting SendBotsToSpectatorImmediately 1 //keeps the populator less clogged, bot projectiles vanish on death + causes weird killcams. Highly recommended but not required // Optional stuff RobotLimit 26 //pair with MaxTotalPlayers to disallow spectators AllowJoinTeamBlueMax 6 MaxTotalPlayers 6 // currently doesn't work in reverse? ImprovedAirblast 1 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 new spawn locations/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" PrecacheModel "models\bots\boss_bot\boss_tankred.mdl" // PrecacheSound "combine_bank_alarm.mp3" // Entities make up 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 (some brush ents like func_door will not work), but not many. // To learn more about your map of choice, 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 in hammer and copy/paste the results from the .vmf to this popfile with only small formatting changes. // Obvious reminder: recompiling and republishing maps without the authors consent is bad // Alternatively, load the map in singleplayer and type "ent_messages " and "developer 1" in console to show targetnames as floating text in the world // Example: developer 1; ent_messages info_player_teamspawn will show the names of all spawn locations. // Digging through decompiled .vmf's and learning hammer will better teach you how certain relays/entities work, many parts of a maps .vmf file can be copy/pasted directly into your popfile. PointTemplates { corelogic //all the things we want to automatically run when the popfile loads. { NoFixup 1 logic_auto { "origin" "0 0 0" "targetname" "mainrelay" //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 //not really necessary on rottenburg, but removing random decorations on more complex maps may keep you under the edict limit and avoid crashes. "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, name is map dependent but usually its this //"OnMapSpawn" "gate1_alarm*,Kill,,0,-1" //spammed by both teams if we kill the capture filter, very annoying. //AddOutput can be used to connect our own home-brewed point templates to existing map logic: "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 or _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" } 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" } 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 } trigger_multiple //Forces thirdperson while deploying bomb { "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 the 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" } game_round_win //use redwin_relay instead { "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" } // prop_dynamic //prop for show, removed because ugly // { // "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" } 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 { NoFixup 1 func_upgradestation { "mins" "-105 -100 0" "maxs" "105 100 242" "solid" "0" } 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.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" } 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 { 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 in your WaveSpawn to split spawning evenly across spawns with the same name { 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, janky and gross though ClosestPoint spawnbot //bet you forgot about ClosestPoint When { MinInterval 5 MaxInterval 30 } TFBot { Template T_TFBot_Scout_Fish Health 50 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" 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 "" //map dependent, sometimes defaults to the first info_player_teamspawn it can find (most cases red spawn) 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 TeamNum 2 Name "tankbossred" ClassIcon tank_red_lite Model "models/bots/boss_bot/boss_tankred.mdl" StartingPathTrackNode "tankpath1_1" OnKilledOutput { Target boss_dead_relay Action Trigger } } } } }