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 |
✔️ |
✔️ |
✔️ |
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:
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.
- TriggerCondition: Death
- TriggerTarget:
BelowMedium
- 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.
- Name:
DetectBelowMedium
- Damage inflicted when blocked:
89
- Speed:
1000
- Target:
DetectBelowMedium
- Flag: ☑️Remove on fire (1)
- Name:
DetectBelowHard
- Damage inflicted when blocked:
119
- Speed:
1000
- 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:
- On easy, both alien grunts die (60HP < 89dmg < 119dmg), triggering both TriggerTargets (
BelowMedium
, BelowHard
)
- On medium:
- Monster #1 survives (90HP - 89dmg = 1HP), skipping
BelowMedium
- Monster #2 dies (90HP - 119dmg < 0HP), triggering
BelowHard
- On hard, both alien grunts survives (120HP > 119dmg > 89dmg), skipping both TriggerTargets
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:
- Name:
BelowMedium
- KillTarget: (medium-level entities)
- Target: (entities to fire in easy skill)
- Flag: ☑️Remove on fire (1)
- 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:
- Name:
BelowMedium
- Global State to Set:
SkillBelowMedium
- Trigger Mode: On
- Initial State: Off
- Flag: ☑️Set Initial State (1)
- 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:
- Global state to read:
SkillBelowMedium
- KillTarget: (medium-level entities)
- Target: (entities to fire in easy skill)
- Flag: ☑️Remove on fire (1)
- 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:
- TriggerCondition: 50% Health Remaining
- TriggerTarget:
AboveEasy
- TriggerCondition: 50% Health Remaining
- TriggerTarget:
AboveMedium
The doors should inflict damage
slightly higher than the alien grunt's health
in easy and medium skills.
- Name:
DetectAboveEasy
- Damage inflicted when blocked:
65
- Speed:
1000
- Target:
DetectAboveEasy
- Flag: ☑️Remove on fire (1)
- Name:
DetectAboveMedium
- Damage inflicted when blocked:
95
- Speed:
1000
- Target:
DetectAboveMedium
- Flag: ☑️Remove on fire (1)
The setup outcome should be as follows:
- On easy, both alien grunts are killed (60HP < 65dmg < 95dmg), skipping both TriggerTargets
- On medium:
- Monster #1's health gets reduced (90HP - 65dmg = 25HP) to below 50% health, triggering
AboveEasy
- Monster #2 dies (90HP - 95dmg < 0HP) , skipping
AboveMedium
- On hard:
- Monster #1's health gets reduced (120HP - 65dmg = 55HP) to below 50% health, triggering
AboveEasy
- Monster #2's health gets reduced (120HP - 95dmg = 25HP) to below 50% health, triggering
AboveMedium
Use and store the outcome in the same map as the setup like this:
- Name:
AboveEasy
- KillTarget: (easy-level entities)
- Target: (entities to fire in medium skill)
- Flag: ☑️Remove on fire (1)
- Name:
AboveMedium
- KillTarget: (medium-level entities)
- Target: (entities to fire in hard skill)
- Flag: ☑️Remove on fire (1)
- Name:
AboveEasy
- Global State to Set:
SkillAboveEasy
- Trigger Mode: On
- Initial State: Off
- Flag: ☑️Set Initial State (1)
- 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:
- Global state to read:
SkillAboveEasy
- KillTarget: (easy-level entities)
- Target: (entities to fire in medium skill)
- Flag: ☑️Remove on fire (1)
- 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
- Starting map is set in liblist.gam.
- Having the setup as global states makes this setup unaffected by players manually changing the skill level partway through the campaign via console commands (which they shouldn't be doing anyway.)
- The global states reset if a map has "New Level Unit" set to yes. If you need to set a new level unit, you need to add the setup again.
- Alternatives to basic killtargetting of the entities:
- Use the
trigger_relay
's and trigger_auto
's in Method 1 to target a monstermaker
that spawns monsters/weapons/ammos/items in its stead.
- Use Method 2 and the killtargetting of
func_breakable
's which in turn spawn the desired weapons/ammos/items.
- Allow for all the entities for all skill levels to exist for a split second at the start of the level, as the setup takes that fraction of a second to do its thing. Have the player spawn facing none of the affected entities, for instance, or make the level fade in in
worldspawn
.
Vault
Loading embedded content: Vault Item #7001
See also