Tutorial: Vanilla difficulty/skill detection Last edited 1 month ago2025-02-26 14:56:07 UTC

This tutorial will show you how to detect the skill level in vanilla Half-Life (requiring no custom code), and using the information to change the map's entity composition e.g. higher skill == more monsters == less items.

This tutorial assumes that you are familiar with the following concepts:

Overview

Suppose we want to set up a map or campaign that has the following entity composition:
Skill level Easy Medium Hard
"easy" monsters ✔️ ✔️ ✔️
"medium" monsters ✔️ ✔️
"hard" monsters ✔️
Skill level Easy Medium Hard
"easy" items ✔️
"medium" items ✔️ ✔️
"hard" items ✔️ ✔️ ✔️
Legend
✔️ - keep
❌ - remove
Whereas Half-Life entities have a reserved global spawnflag value: 2048 - Not in deathmatch, which removes the entity if the map is run in deathmatch, unfortunately there is no flag to make entities spawn only at certain difficulty/skill level in the vanilla codebase.

However, we can set up some entities to detect the skill range i.e. whether we're at X skill or higher/lower depending on the method used. The detection mechanism depends on a monster having differing health based on skill level (set in the skill.cfg file). The output of the setup is then used to do some other map logic; in this case removing certain monsters and items.

We'll be using the alien grunt, the only monster in vanilla Half-Life with differing health per skill level. The following snippet from skill.cfg sets their health:
// Alien Grunt
sk_agrunt_health1    "60"
sk_agrunt_health2    "90"
sk_agrunt_health3    "120"
The skill detection setup hinges on a monster's ability to trigger targets according to set condition (the TriggerTarget and TriggerCondition attributes); specifically when their health drops to 50% or 0. For this we will be inflicting the monsters with specific damage values that either trips or skips the set triggers depending on the skill level.

Methods

Method 1: Upper bound detection (X or lower)

This method is used to detect if the skill is below a certain level. Suitable for having more monsters in harder skill levels.

In the starting map (i.e. the starting map when you start a new game), place 2 alien grunts in a room, separate from the rest of the map (far away from the info_player_start or you'll hear them cry in pain or die). They will be set to trigger specific target when they die.
monster_alien_grunt #1
  • TriggerCondition: Death
  • TriggerTarget: BelowMedium
monster_alien_grunt #2
  • TriggerCondition: Death
  • TriggerTarget: BelowHard
Next, we will set up the strategic pain inflictors that does, um, inflict specific amount of damage. In this case, two doors each set to deal damage slightly below the medium and hard health levels, respectively. We will also be placing trigger_auto's to activate these doors as soon as the level starts. Note that each door name has to be unique – for some reason this setup will not work correctly if these pain-inflicting doors share the same name. Needless to say, the door must be set to move in a direction that crushes the monsters.
func_door #1
  • Name: DetectBelowMedium
  • Damage inflicted when blocked: 89
  • Speed: 1000
trigger_auto #1
  • Target: DetectBelowMedium
  • Flag: ☑️Remove on fire (1)
func_door #2
  • Name: DetectBelowHard
  • Damage inflicted when blocked: 119
  • Speed: 1000
trigger_auto #2
  • Target: DetectBelowHard
  • Flag: ☑️Remove on fire (1)
Compiling and running the map at this point in our setup should produce the following outcome at the start of the map: Now we can capture the events that fire based on the skill level or range. To fire the events on the same level, add the following entities:
trigger_relay #1
  • Name: BelowMedium
  • KillTarget: (medium-level entities)
  • Target: (entities to fire in easy skill)
  • Flag: ☑️Remove on fire (1)
trigger_relay #2
  • Name: BelowHard
  • KillTarget: (hard-level entities)
  • Target: (entities to fire in medium skill)
  • Flag: ☑️Remove on fire (1)
To store and use the result on other maps in the campaign, we will need to use global states. In the same map as the setup, add these entities:
env_global #1
  • Name: BelowMedium
  • Global State to Set: SkillBelowMedium
  • Trigger Mode: On
  • Initial State: Off
  • Flag: ☑️Set Initial State (1)
env_global #2
  • Name: BelowHard
  • Global State to Set: SkillBelowHard
  • Trigger Mode: On
  • Initial State: Off
  • Flag: ☑️Set Initial State (1)
Then in all other maps, add these entities:
trigger_auto #1
  • Global state to read: SkillBelowMedium
  • KillTarget: (medium-level entities)
  • Target: (entities to fire in easy skill)
  • Flag: ☑️Remove on fire (1)
trigger_auto #2
  • Global state to read: SkillBelowHard
  • KillTarget: (hard-level entities)
  • Target: (entities to fire in medium skill)
  • Flag: ☑️Remove on fire (1)

Method 2: Lower bound detection (X or higher)

This method is used to detect if the skill is above a certain level. Suitable for having less pickups on higher skill levels.

Add these alien grunts:
monster_alien_grunt #1
  • TriggerCondition: 50% Health Remaining
  • TriggerTarget: AboveEasy
monster_alien_grunt #2
  • TriggerCondition: 50% Health Remaining
  • TriggerTarget: AboveMedium
The doors should inflict damage slightly higher than the alien grunt's health in easy and medium skills.
func_door #1
  • Name: DetectAboveEasy
  • Damage inflicted when blocked: 65
  • Speed: 1000
trigger_auto #1
  • Target: DetectAboveEasy
  • Flag: ☑️Remove on fire (1)
func_door #2
  • Name: DetectAboveMedium
  • Damage inflicted when blocked: 95
  • Speed: 1000
trigger_auto #2
  • Target: DetectAboveMedium
  • Flag: ☑️Remove on fire (1)
The setup outcome should be as follows: Use and store the outcome in the same map as the setup like this:
trigger_relay #1
  • Name: AboveEasy
  • KillTarget: (easy-level entities)
  • Target: (entities to fire in medium skill)
  • Flag: ☑️Remove on fire (1)
trigger_relay #2
  • Name: AboveMedium
  • KillTarget: (medium-level entities)
  • Target: (entities to fire in hard skill)
  • Flag: ☑️Remove on fire (1)
env_global #1
  • Name: AboveEasy
  • Global State to Set: SkillAboveEasy
  • Trigger Mode: On
  • Initial State: Off
  • Flag: ☑️Set Initial State (1)
env_global #2
  • Name: AboveMedium
  • Global State to Set: SkillAboveMedium
  • Trigger Mode: On
  • Initial State: Off
  • Flag: ☑️Set Initial State (1)
Use the global state in the other maps as follows:
trigger_auto #1
  • Global state to read: SkillAboveEasy
  • KillTarget: (easy-level entities)
  • Target: (entities to fire in medium skill)
  • Flag: ☑️Remove on fire (1)
trigger_auto #2
  • Global state to read: SkillAboveMedium
  • KillTarget: (medium-level entities)
  • Target: (entities to fire in hard skill)
  • Flag: ☑️Remove on fire (1)

Combination

The two methods above can be combined. Do note that both methods have trigger_relay's and trigger_auto's that can fire targets on medium skill, so use them from only one of the methods, or you'll end up firing those targets twice on medium skill.

Notes

Vault

Loading embedded content: Vault Item #7001

See also

Comments

You must log in to post a comment. You can login or register a new account.

Shoutbox

Log in to add shouts of your own