Co-op Strike
It is covered here for historical and technical reference.
Co-op Strike (internally: coopmission
) is a cooperative game mode in Counter-Strike: Global Offensive.
It was first introduced as part of the Operation Wildfire Gemini Campaign. And now on the Steam Community Workshop. Like 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 Competitive, but with the following differences:
- Human players are assigned to CTs and spawn at enabled info_player_counterterrorist entities as usual, whereas enemy bots spawn at enabled info_enemy_terrorist_spawn entities. Players spawn at the round beginning but also when an appropriate VScript function is called. During warmup, all players respawn if they die, as usual.
- The round does not end if the Terrorists are eliminated due to mp_use_respawn_waves 2.
- CTs always wear an item_heavyassaultsuit that has slightly different properties in this game mode.
- CTs cannot defuse planted C4 entities.
- If all human players are dead, they lose the mission and a new round begins.
- If the human players win the round (either by rescuing a hostage or via game_round_end), they complete the mission and the next map loads.
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.
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
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 likeglock, 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 istm_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.
- Use map-placed weapon entities, such as weapon_ak47, which can only be picked up once per round.
- Equip bots with them that the players will kill and loot.
- A simple "infinite" weapon pickup can be created with an invisible, non-solid func_button and a game_player_equip entity together with a prop_dynamic of the corresponding weapon.
- VScript can also handle the equipping.
- 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 modelsmodels\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
- [Todo]
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.
Creating the Script
Co-op Strike uses VScript written in 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 thevscripts
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 exampleScriptCoopMissionSpawnNextWave(5)
.ScriptCoopMissionRespawnDeadPlayers()
- When this is called, it will respawn all dead CTs.
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
.
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.
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).
These outputs call the script functions that we created in the scripting part.
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 | |
---|---|---|---|---|---|---|
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 | |
---|---|---|---|---|---|---|
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:
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 | |
---|---|---|---|---|---|---|
OnStartTouch | game_round_end | EndRound_CounterTerroristsWin | 5 | 0.00 | No |
And voilà, your map is fully playable.
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
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
|