Entities can receive damage from many different sources: player's weapons, puppets (NPCs), the environment, or scripts. However, all this incoming damage is ultimately handled by a single function in native game code - OnReceiveDamage(), which deals with deducing the actual amount of damage taken, reducing the entity's health (if the entity has a health property), producing blood splatter or similar effects, handling transition from alive into dead state, producing destruction effects, etc, etc.
However, the very first thing done in game code just before calling OnReceiveDamage() is to check if a ReceiveDamage script event should be sent. These events need to be specifically enabled for each required entity by using the function EnableReceiveDamageScriptEvent() on the entity. If a script has enabled these events and is listening for them, then the native game code will only send the required event describing the incoming damage and skip actual damage processing. Now, the script has a chance to react to this event, and adjust incoming damage as desired. This is done through the received event's script interface. Once the damage has been adjusted, a call to HandleDamage() on the received event will resume normal damage processing. Or, the script can choose to ignore the damage completely and never call HandleDamage().
Therefore, it is not possible to completely override native damage handling, but it is possible to intercept it and inject custom adjustments on the incoming damage before letting native code handle it, or simply ignore the damage.
For this example, we'll showcase dynamic binding to, unbinding from, and augmenting entities. If you are not familiar with using RunHandled for creating event handlers please read this introductory article first: Lua World Scripts Docs.
In this particular case, we'll start by listening for PlayerBorn events. Note that we could also listen for the generic EntitySpawned events, retrieve the class name of the spawned entity using GetClassName() and check if it matches "CPlayerPuppetEntity" - however, it's much more efficient to just wait for PlayerBorn events which guarantees we'll collect only players.
Once we've caught a newly spawned player entity, we can create a new RunHandled block from within the PlayerBorn event handler function. This nested RunHandled block will be used to catch all the events sent by that particular player. In effect, we'll have bound the player to our script. There are two important events we will be listening for in player's RunHandled block: EntityDeleted, and ReceiveDamage.
The former is important in order to unbind the player from our script and terminate its RunHandled block once it has been deleted in game code. This can happen for many reasons: player disconnected, switched to spectator mode, etc. Of course, this will only happen once per player's life time, so we'll wait on EntityDeleted from within RunHandled's main function.
The latter is the actual notification of received damage, and we expect it to occur many times during player's life time, so we'll write an event handler to catch every such event. Doing this allows us to modify entity's behavior dynamically, which is referred to as augmenting the entity.
RunHandled( WaitForever, OnEvery(Event(worldInfo.PlayerBorn)), function(eePlayerBorn) -- player : CPlayerPuppetEntity local player = eePlayerBorn:GetBornPlayer() player:EnableReceiveDamageScriptEvent(true) RunHandled( function() Wait(Event(player.EntityDeleted)) end, OnEvery(Event(player.ReceiveDamage)), -- eeDmg : CReceiveDamageScriptEvent function(eeDmg) local dmgAmount = eeDmg:GetDamageAmount() dmgAmount = dmgAmount * 0.5 eeDmg:SetDamageAmount(dmgAmount) eeDmg:HandleDamage() end ) end )
Tip: Damage filtering only makes sense if done by the server, as clients are unable to process their own damage. Make sure that damage filtering scripts are run only on the server!