The Source engine allows for great possibilities thanks to the groundbreaking entity messaging system, the "inputs/outputs" which gives a lot of creative freedom to modders, and allow for endless possibilities for creation.
Thanks to all the "logic" entities, it is possible to even do some programming through level design, features that are commonly used in Underhell.
But one of the big problems in Underhell, is the Entity Limit of the Source Engine, which is set to 4096 including 2048 non-network entities and 2048 "edicts" which are the dynamic entities in the level.
In order to create complex events in the game, it sometimes requires multiple "logic" entities to work, plus all the "nodes" for AI navigation, and the "Map" itself, including the detailed environment and objects.
This can sometime give very little space for other entities, which is why one of our first goals when expanding the Source Engine code was to find a way to implement new uses for already existing entities, that would allow us to use only ONE master entity for all purposes.
So, it's what we did with the following :
This entity is used to display message on the player's hud.
(The Comments in The House, or Objectives in the Prologue)
It may use a "preset message" written directly into the KeyValue, or you can write a "Titles" entry there, which you can then define in the "scripts/titles.txt" that can there refer to a "Language Specific" entry in the "Resource" folder of the game.
This is how we allow for easy language translation in Underhell, all our messages are relayed through the "scripts/titles.txt" file to the "resource/Underhell_YourLanguage" file.
That said the Env_Message entity has one big flaw : It can only display one message per entity.
So if you want multiple messages, you need as many "env_message" in your map, as you need messages.
So what we did, was to add a simple input called:
where the Target Parameter is the message in question.
This input will directly display the message that you have set in the target parameter, independently of the entity's message keyvalue.
Then I realized that we could, using a similar method implement a very simple "Objective" system through this.
So we implemented another, rather complex set of inputs.
"SetMessage" "SetMessagePriority1" "SetMessagePriority2" "SetMessagePriority3" ...(1-16) "RemoveMessagePriority" "1-16"
Where the "1-16" is the Target Parameter
This set of Inputs may seem complex (since there are 33 of them) but they are actually quite simple.
The "SetMessage" input enters the Target Parameter's message into the entity memory, therefore the next time the "ShowMessage" input is sent, the entity will display the latest "SetMessage" that was entered. The "SetMessage" is the HIGHEST priority Message that can be entered, it will always take priority above any other.
As for all the other inputs, they are similar systems, but with a hard codded priority system.
"SetMessagePriority1" will set the Target Parameter message with a priority of 1.
Therefore if later in the level, another message is set with a HIGHER priority, it will replace the message set into memory, but if the priority is LOWER, nothing happens.
Imagine there is a large room with a locked Security Room.
Inside it, a console that will open large doors, allowing you to escape.
The Security Room also has a window, that can be broken if the player has a melee weapon.
The env_message will receive these outputs:
SetMessagePriority1 : "Find the key to open the Sec Room." SetMessagePriority2 : "Activate the console in the security room." SetMessagePriority3 : "Escape."
- When the Map Starts, MessagePriority1 will be entered.
- When player is INSIDE the Security Room, MessagePriority2 will be entered.
- When player activated the console, MessagePriority3 will be entered.
The interesting part here, is step 2, the player may either find the key to open the door, to get into the security room, or he may find a melee weapon to break the window and get inside.But this wasn't pointed to him through any objective/hint, it is a "secondary" route that the player may discover on his own, and the game simply supports this alternative way.
Now let's take a look at the "RemoveMessagePriority" inputs.
Imagine the exact same Map, but this time if the window is broken, an alarm will ring and a "Security" plate will block the window, and the Security Door will be locked by an additional mechanism that even the key cannot open.
If that happens, the objective system would add:
"SetMessagePriority4 : Find a way to deactivate the alarm"
When the player has successfully done so, then the "RemoveMessagePriority4" will be sent, and this message will be removed, and the "previous" message that was set in memory will return.
Side note : (If you don't want the player to get locked Inside the Security Room in case he breaks the window from the inside, I suggest you add a way to deactivate the alarm from inside as well.)
Those new Inputs for the Env_Message entity allows for multiple ways to send simple and complex message systems, using only 1 master env_message entity.
And if the need would arise, you can always use multiple, say one for "Main Objectives" and one for "Secondary Objectives".
SHARING OBJECTIVES BETWEEN MAPS
After hitting multiple issues using the default Source Engine "GlobalNames" keyvalue, we realized we had to create our own system to share messages in between maps.
So, we added a keyvalue specific to the "Env_Message" entity :
GlobalEnvMessageIndex : ( 0 - 7 )
The idea, is to set this keyvalue to the SAME value, to the env_messages in all your maps that you want to share messages in and out.
When the value is set, it actually stores the message through as a player state, so that it will transition along with the player, wherever he goes.
This entity displays a HudHint on the player's hud.
(Press "x" key to do "x" action")
Same with the env_message, this entity would allow for only ONE hint per env_hudhint entity.
So what we did, we simply added an input.
Where the Target Parameter is the hint itself.
This way we can have as many Hints in a map as we want, while using only 1 entity.
The Env_Global is used to turn Global Strings On and Off.
Global, meaning that they are shared between maps.
This entity is CRITICAL in Underhell, since many objectives go back and forth between maps.
Just as the 2 previous entities, env_global only allows for ONE global value per entity, so we added a couple of inputs :
SetGlobalOn SetGlobalOff SetGlobalDead
Where the Target Parameter is the Global Value that you want to affect.
Warning : For this entity to work properly, you MUST enter a Global Keyvalue in hammer, even if it won't be used. If there is no keyvalue set, Source Engine removes the env_global
ENV_GLOBAL COUNTER FEATURE
Since Source Engine 2007, a Counter system has been added to the env_global entity, but to the surprise of many, that feature never actually worked, many are the ones who wonder why Valve would create the entries in the Code and in the .fgd if the feature isn't operational.
After some digging around, Charly discovered that the "Counter" Output for this entity is aliased, meaning that it is given a name that has already been defined.
He took care of updating the official Env_Global wiki, and has fixed this issue, therefore enabling the Env_Global Counter system as it should be.
So there we go, 3 very useful entities that were VERY limited, these fixes were ones of the first in a very long list that we implemented to improve the Source Engine from the developer's perspective.