<aside> ⚠️ Warning: Before beginning this tutorial, you should have experience with DECORATE and ACS. You will also want to have experience with mapping because this page will cover elements of that as well.
</aside>
Mega Man 8-Bit Deathmatch Version 6B introduced the ability to gain frags from activating hazards and receiving assist frags if someone dies to a non-player activated hazard.
Despite their gameplay similarity, these are actually two separate systems in play. Internally, obtaining a frag from activating a hazard (Oil Canisters, Count Bombs, oil pits) is called a hazard credit.
Obtaining an assist frag from someone dying to a passive hazard (Turbo Roaders, spikes, pits) while fighting you and seeing a special obituary is called a hazard tag. Some hazards even have both elements in play at once, such as Kyorowns.
This page will cover both of those systems as well as the tangentially related oil pits, so use the table of contents below to skip ahead if needed.
The thing that typically allows players to receive frags from projectiles when killing other players is their ownership of the projectile, which is dictated by the AAPTR_TARGET
actor pointer field. Projectiles and damagers that don’t have any actor in the AAPTR_TARGET
field will instead be owned by the world, instead causing a hazard assist kill or a lost frag if there is no player to award an assist to.
So with that mind, the goal at a basic level when creating a player-activated hazard which credits the activator is to have all damaging portions of the hazard owned by the player who activated it. There’s a few wrenches in that plan due to requiring support for more cases than you might expect, but we’ll solve those as we get into it.
For now, we’ll begin by making a basic hazard called a Charge Motor which can be shot with electric weapons to activate a damaging radius. Below is the implementation of the shootable actor itself. It has the typical flags that make it solid as well as capable of being shot to activate a pain state.
actor ChargeMotor 32702
{
//$Category Tutorial-Interactive Props
+SOLID
+SHOOTABLE
+NOBLOOD
+DONTDRAIN
+QUICKTORETALIATE
+NODAMAGE
+NODAMAGETHRUST
+DONTBLAST
+CANTSEEK
+NOTAUTOAIMED
scale 2.0
height 64
Radius 32
mass 99999
Health 9999
PainChance 256
DamageFactor "Normal", 0.0
DamageFactor "Electrify", 1.0
States
{
Spawn:
CHMT A 1
wait
Pain.Electrify:
// Disable some flags to prevent other players from
// triggering it mid animation and activation.
CHMT A 0 A_ChangeFlag(SHOOTABLE, false)
CHMT A 1 A_ChangeFlag(NOPAIN, true)
// one tic of delay to make sure that
// activating player is set as AAPTR_TARGET
// AAPTR_TARGET becomes player who shot it due to +QUICKTORETALIATE
// Disable switching of AAPTR_TARGET temporarily just in case
CHMT A 0 A_ChangeFlag(NOTARGETSWITCH, true)
// Transfer AAPTR_TARGET to spawned explosions, meaning activating player will own it
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 0, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 0, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 22.5, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 22.5, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 45, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 45, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 67.5, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 67.5, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 90, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 90, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 112.5, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 112.5, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 135, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 135, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 157.5, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 157.5, SXF_TRANSFERPOINTERS)
CHMT BCBC 2
CHMT A 0 A_PlaySoundEx("misc/chargemotorwhirr", "Body")
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", CM_DIST, 0, 32, 0, 0, 0, 0, SXF_TRANSFERPOINTERS)
CHMT B 0 A_SpawnItemEx("ChargeMotorExplosion", -CM_DIST, 0, 32, 0, 0, 0, 0, SXF_TRANSFERPOINTERS)
CHMT A 10
// Allow AAPTR_TARGET to change again for next cycle
// Make sure to clear current AAPTR_TARGET to prevent weirdness
CHMT A 0 A_ChangeFlag(NOTARGETSWITCH, false)
CHMT A 0 A_ClearTarget
// More flag fixing
CHMT A 0 A_ChangeFlag(NOPAIN, false)
CHMT A 0 A_ChangeFlag(SHOOTABLE, true)
goto Spawn
}
}
Most elemental weapons in the game actually spawn invisible explosions whenever they contact with surfaces with special damage types. Fire ones do so with a damage type of Ignition
, ice ones do so with Freeze
, water ones with Soakify
, and electric ones with Electrify
. There’s also the elemental explosions that use the damage types ConcreteCase
, PropBlower
, and ThunderClawPegHelp
.
All of these invisible explosions are set with a 0.0x damage multiplier by default, meaning that they’re unable to hurt anything. However, we can toggle them back on for a specific actor by giving that actor a damagefactor
property. So with this actor, we’ve disabled all damage with damagefactor "Normal" 0.0
, but then enabled it to be hit by electric weapons by giving a 1.0x multiplier to the Electrify
damage type.
This in addition to only having a Pain.Electrify
state (no default Pain
state) means that it will only ever enter a pain state when being hit by an electric weapon. We can then think of its pain state as an “activated” state.
Another notable property that this actor has is the flag QUICKTORETALIATE
. The name has its roots with monster actors, but what this means in our case is that whenever the actor is shot, one tic later in its pain state, it is guaranteed to have the shooter as its AAPTR_TARGET
actor pointer. We disable and enable some other flags during the pain state so that no other player can become the Charge Motor’s AAPTR_TARGET
or can restart the pain state in the middle of it.
So now we’ve got a Charge Motor that’s exploding and the player who activated it is the Charge Motor’s AAPTR_TARGET
actor pointer. How do we get that pointer to the actual damaging portion, the explosions? That’s the SXF_TRANSFERPOINTERS
-,SXF_TRANSFERPOINTERS,-%2D%20transfers%20the%20calling) flag being used with A_SpawnItemEX
, it’ll make it so that the spawned actor shares the same pointer fields as the spawning actor. Because we’re transferring the activating player as AAPTR_TARGET
to the spawned explosives, it’s as if the player shot the explosives themselves.
Below is the actual implementation of that explosion actor. The major thing to note is that it inherits from [BasicHazardExplosion](<https://mm8bdm.notion.site/BasicHazardExplosion-6bc400f98f874becb1f67ed6adef7bab>)
. This is to deal with one of those wrenches mentioned earlier. Hazard crediting is actually a toggleable server variable, so because of that, we need the means to handle cases where it’s enabled and where it’s not enabled.
[BasicHazardExplosion](<https://mm8bdm.notion.site/BasicHazardExplosion-6bc400f98f874becb1f67ed6adef7bab>)
has an Explode
state that’ll either jump to Credit
if hazard crediting is enabled. However, if hazard crediting is disabled, then it’ll clear all actor pointer fields then jump to NoCredit
. This makes the NoCredit
state fairly simple to understand, it just becomes a normal explosion owned by the world which is what we want when hazard crediting is disabled.