Setting up 1v1 Warmup Arenas: Difference between revisions
m (Added link back to CS:GO Level Creation and corrected 'true' to 'false' at Testing and Debugging) |
(Added tips about instancing. Corrected arena triggers' outputs to match findings in current de_vertigo. Extended VScript example.) |
||
Line 55: | Line 55: | ||
that are designed for the management of 1v1 warmup arenas in {{game link|Counter-Strike: Global Offensive}}. | that are designed for the management of 1v1 warmup arenas in {{game link|Counter-Strike: Global Offensive}}. | ||
The | The implementation described below equals the one of ''de_vertigo'' (2021-05-05). | ||
== Gameplay Description == | == Gameplay Description == | ||
Line 81: | Line 81: | ||
=== Required Entities - General === | === Required Entities - General === | ||
These entities are required once in the map. | |||
{| class="standard-table" | {| class="standard-table" | ||
Line 146: | Line 148: | ||
|} | |} | ||
| | | | ||
|rowspan="2"| The previous triggers rely on these, because the scripts must | |rowspan="2"| The previous and the following triggers rely on these, because the scripts must differentiate between Terrorists and Counter-Terrorists. | ||
|- | |- | ||
| [[filter_activator_team]] | | [[filter_activator_team]] | ||
Line 175: | Line 177: | ||
=== Required Entities - Arenas === | === Required Entities - Arenas === | ||
To avoid VScript errors, there should be at least '''5''' arenas, which is also the maximum of arenas that the scripts support. | The following is required for every individual arena. | ||
To avoid VScript errors, there should be at least '''5''' arenas, which is also the maximum number of arenas that the scripts (currently) support. | |||
==== Geometry and Detail ==== | |||
As a guideline: Valve uses room dimensions of about 512x1024x192 units for each arena. Still, it is possible to make them smaller or slightly larger. | |||
{{Note | The arena script cleans up weapons lying around within a radius of 640 units from it. To have every dropped weapon removed, the playable area must be inside this radius. To visualize it, you can e.g. make a temporary [[skip]]-cylinder [[brush]]. Some maximum ''rectangular'' floor dimensions are 900x900, 1024x768, 1152x512, but it is not wrong to use smaller areas.}} | |||
The official maps use cover for both players, especially so that there is no direct [[line of sight]] between the two players' spawn points. The arenas are completely separated from the rest of the map, although they needn't. | |||
{{Tip | If you want identical arenas, make an [[instance]] to make making changes to all arenas a lot easier.}} | |||
==== Player Spawning Logic ==== | |||
The following entities are required '''for each arena'''. | |||
The entity names must be exact as the scripts rely on them. | The entity names must be exact as the scripts rely on them. | ||
If you are done creating one arena and you want to copy-paste it, you have to make renaming changes to these entities, which is the adjustment of the <span style="color:#FF0">yellow highlighted '''1''''s</span>. | If you are done creating one arena and you want to copy-paste it, you have to make renaming changes to these entities, which is the adjustment of the <span style="color:#FF0">yellow highlighted '''1''''s</span>. | ||
Replace them by '''<span style="color:#FF0">2</span>''', '''<span style="color:#FF0">3</span>''', '''<span style="color:#FF0">4</span>''' and '''<span style="color:#FF0">5</span>''' accordingly. | Replace them by '''<span style="color:#FF0">2</span>''', '''<span style="color:#FF0">3</span>''', '''<span style="color:#FF0">4</span>''' and '''<span style="color:#FF0">5</span>''' accordingly. | ||
{{Tip | | |||
To keep the renaming work at a minimum, make an [[instance]] only with the following five arena specific entities. | |||
* Inside this instance, omit the prefix "'''arenaX-'''" (including the dash) wherever it appears. | |||
* In the actual map, place a [[func_instance]] inside an arena, select the [[VMF]] and set the Fix Up Name (the named entities' prefix) to '''arena1''', ..., '''arena5'''. The previously omitted dash will be added automatically. | |||
* Make sure that the trigger dimensions (see below) fill the entire playable area of each arena. This can be ensured by adding [[clip brush]]es to the instance around the triggers. | |||
}} | |||
{| class="standard-table" | {| class="standard-table" | ||
Line 225: | Line 240: | ||
|} | |} | ||
| | | | ||
| This entity should be placed in the center of the arena on the ground due to the weapon clearing radius of 640 units around it. | | This entity should be placed in the center of the arena on the ground due to the weapon clearing radius of 640 units around it.<br>The Think function fires the input <code>TouchTest</code> to the following two triggers every 0.1 seconds.{{why}} | ||
|- | |- | ||
| [[trigger_multiple]] | | [[trigger_multiple]] | ||
Line 240: | Line 255: | ||
! !! My Output > !! Target Entity !! Target Input !! Parameter !! Delay !! Only Once | ! !! My Output > !! Target Entity !! Target Input !! Parameter !! Delay !! Only Once | ||
|- | |- | ||
| [[Image:Io11.png]] || | | [[Image:Io11.png]] || OnEndTouch || '''arena<span style="color:#FF0">1</span>-script''' || RunScriptCode || EnableCTSpawn() || 0.00 || No | ||
|- | |- | ||
| [[Image:Io11.png]] || | | [[Image:Io11.png]] || OnStartTouch || '''arena<span style="color:#FF0">1</span>-script''' || RunScriptCode || DisableCTSpawn() || 0.00 || No | ||
|} | |} | ||
|rowspan="2"| {{Tip | As these are two brushes with identical dimensions, use {{Key|H}} and {{Key|U}} in Hammer to [[Hammer_Map_Operations_Toolbar#QuickHide_Objects_(H)|hide and unhide]] one of them, to make selecting the other easier.}} The functions <code>DisableCTSpawn()</code> and <code>DisableTSpawn()</code> save the activator assuming it is a [[player]], so the trigger's output | |rowspan="2"| {{Tip | As these are two brushes with identical dimensions, use {{Key|H}} and {{Key|U}} in Hammer to [[Hammer_Map_Operations_Toolbar#QuickHide_Objects_(H)|hide and unhide]] one of them, to make selecting the other easier.}} The functions <code>DisableCTSpawn()</code> and <code>DisableTSpawn()</code> save the [[activator]] assuming it is a [[player]], so the trigger's output cannot be <code>OnTouching</code>, as it uses the trigger as activator, causing VScript errors. The <code>OnEndTouch</code> is exchangeable with <code>OnNotTouching</code>. | ||
|- | |- | ||
| [[trigger_multiple]] | | [[trigger_multiple]] | ||
Line 259: | Line 274: | ||
! !! My Output > !! Target Entity !! Target Input !! Parameter !! Delay !! Only Once | ! !! My Output > !! Target Entity !! Target Input !! Parameter !! Delay !! Only Once | ||
|- | |- | ||
| [[Image:Io11.png]] || | | [[Image:Io11.png]] || OnEndTouch || '''arena<span style="color:#FF0">1</span>-script''' || RunScriptCode || EnableTSpawn() || 0.00 || No | ||
|- | |- | ||
| [[Image:Io11.png]] || | | [[Image:Io11.png]] || OnStartTouch || '''arena<span style="color:#FF0">1</span>-script''' || RunScriptCode || DisableTSpawn() || 0.00 || No | ||
|} | |} | ||
|} | |} | ||
Line 306: | Line 321: | ||
* look for VScript errors. | * look for VScript errors. | ||
* invoke {{ent|sv_cheats|1}} and {{ent|ent_messages_draw|1}} to visualize [[I/O]]. | * invoke {{ent|sv_cheats|1}} and {{ent|ent_messages_draw|1}} to visualize [[I/O]]. | ||
* invoke {{ent|sv_cheats|1}} and {{ent|ent_fire|logic_script runscriptcode "DebugInfo()"}} to see the states of each arena. | * invoke {{ent|sv_cheats|1}} and {{ent|ent_fire|logic_script runscriptcode "DebugInfo()"}} to see the states of each arena, as seen on the right. | ||
** The <code>Available T/CT spawn</code> should be <code>false</code> if there is a T/CT in the arena. | ** The <code>Available T/CT spawn</code> should be <code>false</code> if there is a T/CT in the arena. | ||
** The <code>Current T/CT player</code> should either be <code>null</code> if the corresponding spawn is available or a player if not. | ** The <code>Current T/CT player</code> should either be <code>null</code> if the corresponding spawn is available or a player if not. | ||
=== | === Modifying Gameplay === | ||
You cannot or should not manipulate the orignal scripts | You cannot or should not manipulate the orignal scripts | ||
* <code>csgo/scripts/vscripts/warmup/warmup_arena.nut</code> | * <code>csgo/scripts/vscripts/warmup/warmup_arena.nut</code> | ||
* <code>csgo/scripts/vscripts/warmup/warmup_teleport.nut</code>, | * <code>csgo/scripts/vscripts/warmup/warmup_teleport.nut</code>, | ||
but you can still change gameplay per map by either firing <code>RunScriptCode</code> to the arena scripts or by writing an own | but you can still change gameplay per map by either firing <code>RunScriptCode</code> to the arena scripts or by writing an own [[VScript]] to change variables or functions in the arena scripts. | ||
For example, you could create a [[logic_script]] in your map and set its [[Entity Scripts]] to a .nut file with the following content which will change the weapons, replace [[item_kevlar|kevlar]] with [[item_assaultsuit|kevlar + helmet]] and increase the weapon clearing radius. | |||
{{ExpandBox| | |||
<source lang=cpp> | |||
// Copy-pasted from warmup_arena.nut, renamed and modified | |||
WEAPON_NEW <- [ | |||
"weapon_ak47", | |||
"weapon_p250", | |||
"weapon_mp5sd", | |||
"weapon_nova", | |||
"weapon_negev", | |||
"weapon_ssg08", | |||
"weapon_breachcharge" | |||
] | |||
// Copy-pasted from warmup_arena.nut, renamed and modified | |||
function RemoveDroppedWeaponsNew() | |||
{ | |||
local radius = 1024.0 // added radius variable and inserted it below | |||
local origin = self.GetOrigin(); | |||
local DroppedGun = null; | |||
while( ( DroppedGun = Entities.FindByClassnameWithin (DroppedGun, "weapon_*", origin, radius) ) != null ) | |||
{ | |||
if (DroppedGun.GetOwner() == null) // if it doesnt have a owner, kill it | |||
{ | |||
DroppedGun.Destroy(); | |||
} | |||
} | |||
} | |||
// Copy-pasted from warmup_arena.nut, renamed and modified | |||
function GiveGunNew( weapon ) | |||
{ | |||
local equipper = Entities.CreateByClassname( "game_player_equip" ) | |||
// set flags and keyvalues | |||
equipper.__KeyValueFromInt( "spawnflags", 5 ) | |||
equipper.__KeyValueFromInt( "weapon_knife", 0 ) | |||
equipper.__KeyValueFromInt( "item_assaultsuit", 0 ) // replaced "item_kevlar", adds helmet | |||
equipper.__KeyValueFromInt( weapon, 0 ) | |||
equipper.ValidateScriptScope() | |||
EntFireByHandle( equipper, "Use", "", 0, PLAYER_CT, null ) | |||
EntFireByHandle( equipper, "Use", "", 0, PLAYER_T, null ) | |||
EntFireByHandle( equipper, "Kill", "", 0.1, null, null ) | |||
} | |||
// Get arena script handles (just like warmup_teleport.nut) | |||
ARENA1_SCRIPT <- Entities.FindByName(null, "arena1-script") | |||
ARENA2_SCRIPT <- Entities.FindByName(null, "arena2-script") | |||
ARENA3_SCRIPT <- Entities.FindByName(null, "arena3-script") | |||
ARENA4_SCRIPT <- Entities.FindByName(null, "arena4-script") | |||
ARENA5_SCRIPT <- Entities.FindByName(null, "arena5-script") | |||
// For less redundance: We want to make the same changes to all of them, so make a list to iterate over them | |||
ARENAS <- [ | |||
ARENA1_SCRIPT, | |||
ARENA2_SCRIPT, | |||
ARENA3_SCRIPT, | |||
ARENA4_SCRIPT, | |||
ARENA5_SCRIPT | |||
] | |||
// Actual modification. This function is called automatically when all entities have spawned. | |||
function OnPostSpawn() | function OnPostSpawn() | ||
{ | { | ||
foreach (arena in ARENAS) | |||
{ | |||
// modifications to each arena script: | |||
arena.GetScriptScope().WEAPON = WEAPON_NEW // replace weapon list | |||
arena.GetScriptScope().RemoveDroppedWeapons = RemoveDroppedWeaponsNew // change weapon clear radius | |||
arena.GetScriptScope().GiveGun = GiveGunNew // change armor | |||
} | |||
} | } | ||
</ | |||
|} | </source> | ||
| Script example for gameplay modification | |||
}} | |||
[[Category:Counter-Strike: Global Offensive]] | [[Category:Counter-Strike: Global Offensive]] | ||
[[Category:Level Design]] | [[Category:Level Design]] | ||
[[Category:Tutorials]] | [[Category:Tutorials]] |
Revision as of 10:26, 5 May 2021
| ||||||||||||||||||||
|
This is the documentation of a way to use the VScripts
csgo/scripts/vscripts/warmup/warmup_arena.nut
csgo/scripts/vscripts/warmup/warmup_teleport.nut
that are designed for the management of 1v1 warmup arenas in Counter-Strike: Global Offensive .
The implementation described below equals the one of de_vertigo (2021-05-05).
Gameplay Description
Officially, the 1v1 warmup plays as follows:
- It is only active during warmup of the game modes Competitive and Wingman, i.e. if game_type is 0 and game_mode is 1 or 2.
- During warmup, respawning players are teleported to a separate room ("1v1 arena") with one enemy player (if there is one), emitting the player spawn sound used in Deathmatch.
- Both players are get 100 HP, kevlar (no helmet), their knife and a randomly chosen weapon from a hard-coded list inside
warmup_arena.nut
. - If there is only one player in an arena, he will be reset in this arena if a respawning enemy player is teleported there.
- Dropped weapons are cleared in an arena if two players spawn.
- In Competitive, 5 arenas are used. In Wingman, only 2 arenas are regarded.
Map Requirements
This section covers the requirements to a map to additionally support 1v1 warmup arenas.
Overview
This is how the following implementation will work:
- During warmup, the players actually spawn at their default spawns on the regular part of the map, but all of the spawn points are inside a big trigger_multiple that teleports them into an arena with a free slot.
- The VScript
warmup/warmup_teleport.nut
handles the assignment of players to the arenas. - Each of the 5 arenas has a logic_script with the VScript
warmup/warmup_arena.nut
keeping track of its current players. - In each arena there are two trigger_multiples inside each other with different team filters filling the entire arena. These are always testing if a (Counter-)Terrorist is in the arena and locks or unlocks the arena for other players.
Required Entities - General
These entities are required once in the map.
Class | Where? | Name | KeyValues | Outputs | Comment | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
trigger_multiple | so that all Competitive and Wingman CT spawns are inside it | not needed |
|
|
![]() | ||||||||||||||||||||||||
trigger_multiple | so that all Competitive and Wingman T spawns are inside it | not needed |
|
|
|||||||||||||||||||||||||
filter_activator_team | anywhere | @warmup.filter_ct |
|
The previous and the following triggers rely on these, because the scripts must differentiate between Terrorists and Counter-Terrorists. | |||||||||||||||||||||||||
filter_activator_team | anywhere | @warmup.filter_t |
|
||||||||||||||||||||||||||
game_player_equip | anywhere | @warmup.weapon_equip_empty |
|
This is used to clear the weapons of (re-)spawning players before they are equipped by an arena script. |
Required Entities - Arenas
The following is required for every individual arena. To avoid VScript errors, there should be at least 5 arenas, which is also the maximum number of arenas that the scripts (currently) support.
Geometry and Detail
As a guideline: Valve uses room dimensions of about 512x1024x192 units for each arena. Still, it is possible to make them smaller or slightly larger.

The official maps use cover for both players, especially so that there is no direct line of sight between the two players' spawn points. The arenas are completely separated from the rest of the map, although they needn't.

Player Spawning Logic
The following entities are required for each arena. The entity names must be exact as the scripts rely on them.
If you are done creating one arena and you want to copy-paste it, you have to make renaming changes to these entities, which is the adjustment of the yellow highlighted 1's. Replace them by 2, 3, 4 and 5 accordingly.

To keep the renaming work at a minimum, make an instance only with the following five arena specific entities.
- Inside this instance, omit the prefix "arenaX-" (including the dash) wherever it appears.
- In the actual map, place a func_instance inside an arena, select the VMF and set the Fix Up Name (the named entities' prefix) to arena1, ..., arena5. The previously omitted dash will be added automatically.
- Make sure that the trigger dimensions (see below) fill the entire playable area of each arena. This can be ensured by adding clip brushes to the instance around the triggers.
Class | Where? | Name | KeyValues | Outputs | Comment | |||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
info_target | place as CT spawn point, including orientation | arena1-ctspawn | The scripts will only call GetOrigin() and GetAngles() from these entities to teleport players to them, so one could use any entity for this if it has a name, an origin and an orientation, e.g. Player spawn entities, but these must be disabled by default, because players should never actually spawn there, especially if it's not warmup. VScript does not take the enabled state into account. Still, do not use info_deathmatch_spawns as it happens that the game uses these even if they are disabled.
| |||||||||||||||||||||||||||
info_target | place as T spawn point, including orientation | arena1-tspawn | ||||||||||||||||||||||||||||
logic_script | in the middle of the arena on the ground | arena1-script |
|
This entity should be placed in the center of the arena on the ground due to the weapon clearing radius of 640 units around it. The Think function fires the input TouchTest to the following two triggers every 0.1 seconds.[Why?]
| ||||||||||||||||||||||||||
trigger_multiple | fills the entire playable area of the arena | arena1-trigger_ct |
|
|
![]() DisableCTSpawn() and DisableTSpawn() save the activator assuming it is a player, so the trigger's output cannot be OnTouching , as it uses the trigger as activator, causing VScript errors. The OnEndTouch is exchangeable with OnNotTouching .
| |||||||||||||||||||||||||
trigger_multiple | fills the entire playable area of the arena | arena1-trigger_t |
|
|
Testing and Debugging
] ent_fire logic_script runscriptcode "DebugInfo()"
===arena1 debug info: ===
Available T spawn = false
Available CT spawn = false
Current T player = ([9] player)
Current CT player = ([6] player)
===arena1 end debug info ===
===arena2 debug info: ===
Available T spawn = false
Available CT spawn = true
Current T player = ([5] player)
Current CT player = (null : 0x00000000)
===arena2 end debug info ===
===arena3 debug info: ===
Available T spawn = false
Available CT spawn = false
Current T player = ([2] player)
Current CT player = ([3] player)
===arena3 end debug info ===
===arena4 debug info: ===
Available T spawn = false
Available CT spawn = false
Current T player = ([4] player)
Current CT player = ([1] player)
===arena4 end debug info ===
===arena5 debug info: ===
Available T spawn = true
Available CT spawn = false
Current T player = (null : 0x00000000)
Current CT player = ([7] player)
===arena5 end debug info === |
To see if everything works correctly, you can open in the console and
- look for VScript errors.
- invoke sv_cheats 1 and ent_messages_draw 1 to visualize I/O.
- invoke sv_cheats 1 and ent_fire logic_script runscriptcode "DebugInfo()" to see the states of each arena, as seen on the right.
- The
Available T/CT spawn
should befalse
if there is a T/CT in the arena. - The
Current T/CT player
should either benull
if the corresponding spawn is available or a player if not.
- The
Modifying Gameplay
You cannot or should not manipulate the orignal scripts
csgo/scripts/vscripts/warmup/warmup_arena.nut
csgo/scripts/vscripts/warmup/warmup_teleport.nut
,
but you can still change gameplay per map by either firing RunScriptCode
to the arena scripts or by writing an own VScript to change variables or functions in the arena scripts.
For example, you could create a logic_script in your map and set its Entity Scripts to a .nut file with the following content which will change the weapons, replace kevlar with kevlar + helmet and increase the weapon clearing radius.
// Copy-pasted from warmup_arena.nut, renamed and modified
WEAPON_NEW <- [
"weapon_ak47",
"weapon_p250",
"weapon_mp5sd",
"weapon_nova",
"weapon_negev",
"weapon_ssg08",
"weapon_breachcharge"
]
// Copy-pasted from warmup_arena.nut, renamed and modified
function RemoveDroppedWeaponsNew()
{
local radius = 1024.0 // added radius variable and inserted it below
local origin = self.GetOrigin();
local DroppedGun = null;
while( ( DroppedGun = Entities.FindByClassnameWithin (DroppedGun, "weapon_*", origin, radius) ) != null )
{
if (DroppedGun.GetOwner() == null) // if it doesnt have a owner, kill it
{
DroppedGun.Destroy();
}
}
}
// Copy-pasted from warmup_arena.nut, renamed and modified
function GiveGunNew( weapon )
{
local equipper = Entities.CreateByClassname( "game_player_equip" )
// set flags and keyvalues
equipper.__KeyValueFromInt( "spawnflags", 5 )
equipper.__KeyValueFromInt( "weapon_knife", 0 )
equipper.__KeyValueFromInt( "item_assaultsuit", 0 ) // replaced "item_kevlar", adds helmet
equipper.__KeyValueFromInt( weapon, 0 )
equipper.ValidateScriptScope()
EntFireByHandle( equipper, "Use", "", 0, PLAYER_CT, null )
EntFireByHandle( equipper, "Use", "", 0, PLAYER_T, null )
EntFireByHandle( equipper, "Kill", "", 0.1, null, null )
}
// Get arena script handles (just like warmup_teleport.nut)
ARENA1_SCRIPT <- Entities.FindByName(null, "arena1-script")
ARENA2_SCRIPT <- Entities.FindByName(null, "arena2-script")
ARENA3_SCRIPT <- Entities.FindByName(null, "arena3-script")
ARENA4_SCRIPT <- Entities.FindByName(null, "arena4-script")
ARENA5_SCRIPT <- Entities.FindByName(null, "arena5-script")
// For less redundance: We want to make the same changes to all of them, so make a list to iterate over them
ARENAS <- [
ARENA1_SCRIPT,
ARENA2_SCRIPT,
ARENA3_SCRIPT,
ARENA4_SCRIPT,
ARENA5_SCRIPT
]
// Actual modification. This function is called automatically when all entities have spawned.
function OnPostSpawn()
{
foreach (arena in ARENAS)
{
// modifications to each arena script:
arena.GetScriptScope().WEAPON = WEAPON_NEW // replace weapon list
arena.GetScriptScope().RemoveDroppedWeapons = RemoveDroppedWeaponsNew // change weapon clear radius
arena.GetScriptScope().GiveGun = GiveGunNew // change armor
}
}