CS:GO Game Modes/Co-op Strike

From Valve Developer Community
Jump to: navigation, search
English Русский
Counter-Strike: Global Offensive Level Creation
Csgo icon coopstrike.png

Co-op Strike (internally: coopmission) is a cooperative game mode in Counter-Strike: Global Offensive Counter-Strike: Global Offensive.

It was first introduced as part of the Operation Wildfire Gemini Campaign. And now on the Steam Community Workshop. Like CS:GO Guardian Guardian, it is exclusively available through Operations or the Steam Workshop, but can be easily accessed through the console.

In Co-op Strike, two human players go against a staged fight against an AI controlled terrorist team in a story-driven scenario. The map prefix for these maps is coop_. It shares the same prefix as some custom community Co-op game modes.

Creating a Co-op Strike map in Valve's style is a long process and it is expected from the readers that they have basic knowledge on how to create maps and work with entities, I/O, along with a basic knowledge of VScript. It is not advised to create a Co-op Strike map if there is no prior experience in mapping.

Game Mode Description

If a standard map is loaded in this game mode, this game mode is technically comparable with CS:GO Competitive Competitive, but with the following differences:

Creating the Map

Entities used in Co-op Strike

These are entities introduced with the Co-op Strike game mode along with some other entities that will be used in this tutorial.

  • info_enemy_terrorist_spawn: The places where the enemy bots will spawn. No other spawn entities are used for them. It is the Co-op variant of info_player_terrorist, so to speak.
  • logic_script: The entity we connect out VScript to in order to be able to use functions via I/O.
  • game_coopmission_manager: This entity fires outputs when special Co-op events occur, for example when a wave is completed.
  • item_coop_coin: The secret Co-op coin for extra points at the end of the level. At the moment, exactly 3 coins need to be in a map for the Co-op coin system to work.
  • point_hiding_spot: Simply said, this is an entity that tells the bots "Here is a good hiding spot".
  • prop_exploding_barrel: The oh so great exploding barrels. Place them a few units above the ground to prevent them from getting "sucked" into it.
  • info_hostage_spawn: Rescuing the hostage is one way of completing the level.
  • game_round_end: This entity can end rounds on command and make a team win. Used in Co-op maps where rescuing a hostage is not the winning condition.

The entities above will be the entities that will be used in Co-op maps.

Environment

When starting your map keep Co-op in your mind. Make sure there are plenty of hiding spots for either the players or the bots. Think of places where you want to place new weapons for the players to use. Think of maybe nifty ways to use the environment to the players' advantage. Note that it is players vs. AI which is different from any other game mode.

Co-op is based around a "wave" system. This means that a group of enemies will spawn, usually 5, that the players has to exterminate in order to advance to the next part of the map. So when creating the map, split your map up in multiple parts. Each part will have its own enemies by your choosing. Unless you want one big linear map with 40 enemies at once. Split your map up in different parts so that you can only advance once each wave is completed.

Player Spawns

In this game mode, the idea is to place both human and bot spawn points across the map where needed and to only have a set of them enabled so players won't spawn in the wrong places. This will be managed by the VScript functions OnSpawnsReset() and OnWaveCompleted() that we will deal with later, see #Explanation of Code.

Note.png Note: Remember to place spawn point entities at least 8 units above the ground or players might not spawn properly.
Note.png Note: Player spawn entities are preserved entities. When a new round starts, their properties don't reset. That's why a Co-op VScript comes in handy.

Human Player Spawns

The human players (re)spawn at enabled info_player_counterterrorist entities.

It is a good idea to have a spawn point where the players can "relax" and setup for the fights that are about to commence. For that purpose, Valve has made spawn rooms in their Co-op maps where the players can choose weapons and grenades that they will use in the upcoming level. There can also be a way to adjust the difficulty of the bots so that the level is playable in #Hard Mode. For better gameplay, give the players "crappy" weapons at the beginning so they will look around in your map for better gear.

The way you create your spawn is up to you. You can either have a simple door divide the spawn with the enemies or you could have a fancy helicopter "cutscene" take you to the actual level. Be as creative as you can be.

If the human players can die in the mission (which will most likely be the case!) and if you don't want the human players to respawn at their very first spawn points of the map, you can place more CT spawns as "checkpoints" and leave them Disabled by default. The Co-op VScript later can access these.

Enemy Spawns

Csgo coop info enemy terrorist spawn.png

The enemy bots (re)spawn at enabled info_enemy_terrorist_spawn entities.

For each section in your map you will need to have these entities in order for bots to spawn. Also give all of these spawn points in a section the same name. Say for the first section of the map you will enter, you name all the info_enemy_terrorist_spawn entities wave_01. Do this for each section of your map in chronological order, e.g. wave_02, wave_03, and so forth.

You can give the spawn points different properties.

  • For starters you can give them weapons. glock is the default weapon but you can add weapons by using the comma character , so you can set something like glock, ak47 and the bot on that location will spawn with a weapon_glock and a weapon_ak47.
  • You can also give the bot a different player model by using the Override player model keyvalue. One example for this is tm_phoenix_heavy.mdl which is also used in coop_cementplant. More model names can be found here.

After you've chosen your properties you need to set Enabled by default to No, except for the first wave of enemies that you come across those need to be Enabled instead of Disabled.

Doorways to new Sections

Since you have to divide your map into different sections you will need to have doorways that connect these sections and can only be accesed once a wave is completed. Simply put, put down a prop_door_rotating between your sections, lock them so players cannot rush through the map without completing the waves and give them a proper name so you can find them later when you're working on your script.

Collectible Coins

If you want players to explore your map more in order to get more points you can use the item_coop_coin. Once all 3 coins are collected in a level the players will get bonus points at the end of the level. Exactly 3 coins should be placed in a map: If you have 2 coins, the players will never be able to get the bonus points; Collecting more than 3 coins has the same effect as collecting only 3.

Co-op Gear

Here is some inspiration of what you can or should supply the human players with throughout the map:

Weapons
See Category: Counter-Strike: Global Offensive Weapons for a list of weapons. There are multiple ideas to supply players with new weapons.
weapon_healthshot
Players can use healthshots to heal themselves. They are good to be given before a difficult fight. Do not give too many healthshots, otherwise your map will be to easy.
weapon_tagrenade
The tagrenade grants a short "wallhack" effect: It makes enemies close to its explosion glow through walls for a brief moment.
point_give_ammo
If this entity receives the GiveAmmo input, it gives the activator full ammo for all his weapons. In coop_cementplant they used the ammo models models\props\coop_cementplant\coop_ammo_stash\coop_ammo_stash_full.mdl and \coop_ammo_stash_empty.mdl, when once used, the model changes from the former to the latter. Make it so that players can only take ammo from that point once so they can't run back in the level to restock on ammo again.
prop_ammo_box_generic
Each time this entity is +used, the reserve ammo of the activator's active weapon is increased by one clip size. The entity can be used 4 times.
prop_weapon_refill_heavyarmor
Don't forget about armor: As the players will lose armor points with each fight, they will soon appreciate this entity which can be +used by one player to replenish his armor to 200 points.
weapon_zone_repulsor
To do

Co-op Tasks

Since you are trying to make a cooperative map, it is fun to include simple puzzles and tasks that can only be achieved if players work together. It can go from one player standing on an elevator and the other activating this elevator or a door with 2 levers to one player being blind and the other has to guide him/her through a dark room. Be creative!

Glowing props

Use glowing doors or buttons to tell the players "Go here to advance" or "Press me to advance". You can make props (the doorway to the next part of the map) glow so it's easy for players to find their way through your map.

Csgo coop map example.png

Creating the Script

Co-op Strike uses VScript written in Squirrel Squirrel to make the system function. Without the script the game mode can't work properly, so each Co-op map needs its own script.

  • Start by creating your script (text file) and give it a name, for example cooptutorial.nut. Important: Make sure that the extension of the file is .nut, otherwise the game won't be able to read the file.
  • Now place this file in the correct folder inside your game files, for example Steam/steamapps/common/Counter-Strike Global Offensive/csgo/scripts/vscripts/custom/. Make sure it is somewhere inside the vscripts folder. Subdirectories are arbitrary.

Once you've placed it inside the correct folder you can start editing it with notepad or any other text editor.

This script will be later be used for the Entity Scripts property of a logic_script, which means that the script will be executed each time a new round begins. Variables and functions ("table slots" to be precise) will be saved after the execution and can be called when firing the inputs RunScriptCode or RunScriptFunction to that logic_script.

Code used in Example

Here an example of a Co-op Script. Feel free to copy this into your own script file since much of it is useful for every Co-op Strike map script. An example vmf will be provided at the end of this guide that works together with this script.

wave <- 0;

function RoundInit()
{
	//Will do this everytime you start the map/round because we call it in the OnLevelReset
	wave = 0;
	//Reset the difficulty to normal at start of the round
	SendToConsoleServer( "mp_coopmission_bot_difficulty_offset 1" );
	ScriptCoopSetBotQuotaAndRefreshSpawns( 0 );
}

function ChangeGameModeToCoopIfNotCorrect()
{
	// This will change the game mode and game type if the player has not initialized this before starting the map.
        local game_mode = ScriptGetGameMode();
        local game_type = ScriptGetGameType();
        local map = GetMapName();

	if (game_mode != 1 || game_type != 4)
	{
		SendToConsole("game_mode 1; game_type 4; changelevel " + map);
	}
}

function SpawnFirstEnemies( amount )
{
	ScriptCoopMissionSpawnFirstEnemies( amount );	
	ScriptCoopResetRoundStartTime();
	wave++;
}

function SpawnNextWave( amount )
{
	ScriptCoopMissionSpawnNextWave( amount );
	wave++;
}

function OnMissionCompleted()
{
	//what will happen once you've completed the mission (you could play a sound)
	
}

function OnRoundLostKilled()
{
	//what will happen if you loose the round because you died (you could tell the players that your grandma is better than them)
	
}

function OnRoundLostTime()
{
	//what will happen if you loose the round because the time runs out (you could tell the player that they are like turtles)
	
}

function OnRoundReset() 
{
	//called when the round resets
	// IMPORTANT: you need a game_coopmission_manager that has the output 'OnLevelReset' when this is called you NEED to call this function
	// in order for the level to work properly every round!
	RoundInit();
}

function OnSpawnsReset()
{
	//called right before the round resets (usually used for correcting stuff when on a new round other stuff is immediately called)
	//enabled/disabled the correct spawns for the start. * means every group going from Terrorist_00 to infinite enemygroup_example
	EntFire( "wave_*", "SetDisabled", "", 0 );
	EntFire( "wave_01", "SetEnabled", "", 0 );
	EntFire( "CT_*", "SetDisabled", "", 0 );
	EntFire( "CT_1", "SetEnabled", "", 0 );
}

function OnWaveCompleted()
{	
	//Check which wave the player is and do stuff
	if ( wave == 1 )
	{
		EntFire( "wave_*", "SetDisabled", "", 0 );
		EntFire( "wave_02", "SetEnabled", "", 0 );
		EntFire( "door_wave_01", "Unlock", "", 1 );
		EntFire( "door_wave_01", "SetGlowEnabled", "", 1 );
	}
	else if ( wave == 2 )
	{
		EntFire( "wave_*", "SetDisabled", "", 0 );
		EntFire( "wave_03", "SetEnabled", "", 0 );
		EntFire( "door_wave_02", "Unlock", "", 1 );
		EntFire( "door_wave_02", "SetGlowEnabled", "", 1 );
	}
	else if ( wave == 3 )
	{
		EntFire( "door_wave_03", "Unlock", "", 1 );
		EntFire( "door_wave_03", "SetGlowEnabled", "", 1 );
	}
}

Explanation of Code

We will go over everything inside the script and explain what it all does.

wave <- 0;

function RoundInit()
{
	//Will do this everytime you start the map/round because we call it in the OnLevelReset
	wave = 0;
	//Reset the difficulty to normal at start of the round
	SendToConsoleServer( "mp_coopmission_bot_difficulty_offset 1" );
	ScriptCoopSetBotQuotaAndRefreshSpawns( 0 );
}

First wave <- 0;: What we do here is define and initialize a variable that functions can call later. We have the function RoundInit() that sets the variable wave to 0. After that it tells the console to set the bot difficulty to 1, the easiest. After this it refreshes all the bot spawns.

function ChangeGameModeToCoopIfNotCorrect()
{
	// This will change the game mode and game type if the player has not initialized this before starting the map.
        local game_mode = ScriptGetGameMode();
        local game_type = ScriptGetGameType();
        local map = GetMapName();

	if (game_type != 4 || game_mode != 1)
	{
		SendToConsole("game_type 4; game_mode 1; changelevel " + map);
	}
}

The function ChangeGameModeToCoopIfNotCorrect() changes the game_type and game_mode to 4 and 1 so that the game knows its playing a Co-op map. The coop_ map prefix is not sufficient to get the game mode right. You don't really have to know how this works; Just call this function when the map is started with the OnMapSpawn output of a logic_auto. More on this in #Making your Script work with your Map.

function SpawnFirstEnemies( amount )
{
	ScriptCoopMissionSpawnFirstEnemies( amount );	
	ScriptCoopResetRoundStartTime(); // sets round time to 45 minutes
	wave++;
}

function SpawnNextWave( amount )
{
	ScriptCoopMissionSpawnNextWave( amount );
	wave++;
}

These are the 2 functions that will make the bots spawn. SpawnFirstEnemies(amount) will spawn the first wave of your level and reset the timer to 45 minutes. From within Hammer we will call this function and replace amount with a number of enemies to spawn. So if we want 3 bots to spawn we call SpawnFirstEnemies(3). What it also does is wave++ which means that it adds 1 to the variable wave so when the level starts wave is 0 but once a wave is spawned, it adds 1 so that wave == 1. SpawnNextWave(amount) is used in every subsequent wave. We use the variable wave to let the map know at which wave the players currently are.

function OnMissionCompleted()
{
	//what will happen once you've completed the mission (you could play a sound)
	
}

function OnRoundLostKilled()
{
	//what will happen if you loose the round because you died (you could tell the players that your grandma is better than them)
	
}

function OnRoundLostTime()
{
	//what will happen if you loose the round because the time runs out (you could tell the player that they are like turtles)
	
}

The names of the functions say it all. As in coop_cementplant, Valve made it so that your "Boss" gives a little message.

function OnRoundReset() 
{
	//called when the round resets
	// IMPORTANT: you need a game_coopmission_manager that has the output 'OnLevelReset' when this is called you NEED to call this function
	// in order for the level to work properly every round!
	RoundInit();
}

function OnSpawnsReset()
{
	//called right before the round resets (usually used for correcting stuff when on a new round other stuff is immediately called)
	//enabled/disabled the correct spawns for the start. * means every group going from Terrorist_00 to infinite enemygroup_example
	EntFire( "wave_*", "SetDisabled", "", 0 );
	EntFire( "wave_01", "SetEnabled", "", 0 );
	EntFire( "CT_*", "SetDisabled", "", 0 );
	EntFire( "CT_1", "SetEnabled", "", 0 );
}

OnRoundReset() is called when the players have died and the round "resets". It calls the function RoundInit(), which is explained above.

Then we have OnSpawnsReset(). This is called just before the round resets. This gives the map time to reset the player and enemy spawns to its original state. Here we Disable every entity whose targetname starts with wave_ and enable only the first wave for enemies. You can do the same for CT spawns. Multiple CT spawns are used in bigger levels so that the players don't have to walk from their first spawn to the point where one player died. EntFire is used for I/O like in Hammer, but here we don't need to specify an output. Normally, the output defines "when" an input should be fired, but in VScript, that point in time is when the EntFire line is executed. For example EntFire( "wave_*", "SetDisabled", "", 0 ) fires the input SetDisabled to all entities starting with "wave_", with an empty parameter override ("") and no delay (0).

function OnWaveCompleted()
{	
	//Check which wave the player is and do stuff
	if ( wave == 1 )
	{
		EntFire( "wave_*", "SetDisabled", "", 0 );
		EntFire( "wave_02", "SetEnabled", "", 0 );
		EntFire( "door_wave_01", "Unlock", "", 1 );
		EntFire( "door_wave_01", "SetGlowEnabled", "", 1 );
	}
	else if ( wave == 2 )
	{
		EntFire( "wave_*", "SetDisabled", "", 0 );
		EntFire( "wave_03", "SetEnabled", "", 0 );
		EntFire( "door_wave_02", "Unlock", "", 1 );
		EntFire( "door_wave_02", "SetGlowEnabled", "", 1 );
	}
	else if ( wave == 3 )
	{
		EntFire( "door_wave_03", "Unlock", "", 1 );
		EntFire( "door_wave_03", "SetGlowEnabled", "", 1 );
	}
}

Last we have OnWaveCompleted. This is called when you've killed every enemy of a wave. Because we keep track on which wave we are with the variable wave, we can do different things for different waves. Here we say: "If wave equals 1" then it disables all the enemy spawns but the spawn point for wave 2. We also unlock the door to the next section of the map and set the door to glow so it is easier for the players to find their way.

This is all the code that is needed for a working Co-op map. If you didn't quite understand a lot of this, try copying the code from here into your own.

Info about Code

There are a few functions that can be used to do different things.

  • ScriptCoopResetRoundStartTime() - Resets the roundtime to 45 minutes.
  • ScriptCoopMissionSpawnFirstEnemies(#) - Spawns the first # enemies.
  • ScriptCoopMissionSpawnNextWave(#) - Spawns a new wave, where # is the number of enemies you want to spawn, for example ScriptCoopMissionSpawnNextWave(5).
  • ScriptCoopMissionRespawnDeadPlayers() - When this is called, it will respawn all dead CTs.
Note.png Note: There are way more, see List of CS:GO Script Functions. To learn more on how coding works, see VScript or Squirrel Squirrel for more links.

Making your Script work with your Map

We now have created a map and our script but your map won't work without connecting them.

Setting up the entities to work with the script

If you haven't already, create a logic_script and give it a simple name such as logic_script.

Note.png Note: The targetname logic_script can be disadvantageous because outputs targeting logic_script will target all logic_script entities in the map. A targetname such as coop_script would avoid this because it is not a classname.

Go to the property Entity Scripts, click on Manage..., click on the + and search for your script which should be placed in a subfolder of csgo/scripts/vscripts/. Add it and the logic_script is done.

Csgo coop logic script.png

There should also be a game_coopmission_manager entity in the map. Go to its outputs and create one for every output that exists (except for the OnUser ones).

Csgo coop game manager outputs.png

These outputs call the script functions that we created in the scripting part.

Note.png Note: The script function names are arbitrary, it's just that they must be the same in the script file and in the parameter value of these outputs.

Setting up the doorways between sections

Since Co-op Strike is a wave based system, we usually want to divide the map into sections with each section being unlocked when a wave is completed. Now we need to make sure that enemies will spawn once you enter a new section. So for example, we want to spawn bots when the players open the door to the next section. An output would look like this:

  My Output Target Entity Target Input Parameter Delay Only Once
Io11.png OnOpen logic_script RunScriptCode SpawnFirstEnemies(3) 0.00 No

Do this for the first wave. For the other waves, use this:

  My Output Target Entity Target Input Parameter Delay Only Once
Io11.png OnOpen logic_script RunScriptCode SpawnNextWave(5) 0.00 No

Of course you do not have to use a door - you can use whatever entity you'd like, such as trigger_once (OnTrigger), func_button (OnPressed), etc.

Finishing touches

Just a few more things to think about to complete the map.

Ending the Map

If you want the map to reload in the right game_type and game_mode if they were wrong, create a logic_auto and add the output:

Csgo coop change game mode.png

Secondly, if you have a hostage_entity in the map, the players can win the map by bringing it to a func_hostage_rescue. If you want the players to win by getting to an end point you'll have to use a game_round_end. Create a trigger at the end of your map with the output:

  My Output Target Entity Target Input Parameter Delay Only Once
Io11.png OnStartTouch game_round_end EndRound_CounterTerroristsWin 5 0.00 No

And voilà, your map is fully playable.

Proper bot navigation

Since you are playing against bots you'll have to make a proper navigation file so your bots don't look as much like bots. With the commands listed at Navigation Mesh Commands and Navigation Mesh Editing, you can do many things to improve the bot navigation. You can also put point_hiding_spots in your map to control where bots should hide.

Hard Mode

As with coop_cementplant and all succeeding Co-op maps by Valve, there was a secret lever to play the map in Hard Mode to get extra points at the end of the game. To do that, you'll have to set the console variable mp_coopmission_bot_difficulty_offset with a point_servercommand. The value 1 stands for the easiest mode; If set to 5, the game will register that it's in Hard Mode. If you really want to give your players a challenge, use an offset of 7. You can also give your info_enemy_terrorist_spawn entities a difficulty which stacks on top of the one that is set with mp_coopmission_bot_difficulty_offset.

Testing your Map

Now that your map is complete you'll have to test it in-game. All you have to do is open up your console, type in map <mapname> coop and it will load your map. If coop is omitted, the game_type and/or game_mode are most likely not correct, so if you have set up the script function ChangeGameModeToCoopIfNotCorrect, the map should reload itself and set those to the correct values.

Make sure to check the console for VScript errors whenever code will be executed. Examples:

<filename>.nut line = (1) column = (26) : error expression expected
FAILED to compile and execute script file named scripts/vscripts/<path>/<filename>.nut
AN ERROR HAS OCCURED [the index 'wave' does not exist]

CALLSTACK
*FUNCTION [main()] <filename>.nut line [1]

LOCALS
[this] TABLE
Tip.png Tip: For debugging, it can make sense to add lines in the code that print a message or the current value of a variable, such as println("...") or ScriptPrintMessageChatAll("...").


I hope this guide will help a lot of people. Happy Mapping :)

External Links

Here is an example of a working simple Co-op map with its script: https://www.dropbox.com/s/val79djogsjzw9z/Creating%20a%20Co-op%20Strike%20Map%20Example.rar?dl=0