Before I start, let's see what the class idiot has to say:
_Peaceful player.: can I have saberdef3 from ctf
The logic is the same regardless of the gametype. Gameplay differences such as the fact that disruptor primary spam usually one shots in siege and slower movement just makes it feel worse when it's actually the same.
Saber def is able to block world projectiles, disruptor hitscan shots and other sabers. I will not be covering the overly complicated (and RNG based) saber vs saber blocks. Even though projectiles and hitscan blocks have a lot of logic in common, there are some differences that are worth mentioning.
1. Impact locations
Reveal hidden contents
First of all, a word on impacts locations. Whenever the game wants to determine where a shot (or saber impact) landed on a player model, it does a special kind of trajectory tracing called "Ghoul2 Trace". Ghoul2 is what the game uses for models and their animations. The "trace" is simply going to find the first surface point that was hit on the model, and it does so by using a simplified version of the model to run checks against. In fact, the checks are only run on the bones of a skeleton that is common to all humanoid models (which you probably already saw if you ever opened the humanoid skeleton files or any model in ModView). Something similar to this:
This approach has some noticeable consequences.
Since all checks are run against the same skeleton server side regardless of the shape of the actual model, impacts are sometimes visually hard to predict. This is extremely noticeable with models that are very different from the default humanoid skeleton. For instance, this snipe won't hit:
Client side, the crosshair code uses the skeleton of the actual model you see to tell that you are looking at it. This is why you see my name above the crosshair. The client uses the reelo skeleton, and thinks I'm aiming at it, so it draws the name, but the server uses the common humanoid skeleton for impacts.
As I already mentioned, the trace is dynamically based on animations, so the results also vary based on them: https://i.imgur.com/dySgzMd.gifv
(the red box is the world collision hitbox used when moving around. you can see how the name appears/disappears even on local tracing due to the clothing on top of the skeleton here).
This means that the visual inaccuracy propagates even more when you add animations into the mix. In a dynamic game where all player animations vary greatly, it does have consequences. Most of the time, when an impact location doesn't make sense, you can't just tell what's wrong visually because you don't see what's happening exactly on the server, especially with animations.
On top of that, the problem isn't just about the trace hitting/not hitting the skeleton. For better performance, huge simplifications are done in the trace function, and when the trace actually hits, the impact location is also inaccurate due to huge roundings.
Guess what the Ghoul2 trace results (impact coordinates) are used for?
1. Location based damage, but everyone already figured how shitty that one is.
2. All projectile, missile and hitscan (conc, sniper) impacts, if d_projectileGhoul2Collision is enabled (otherwise, the red hitbox that you saw is used... like in JK2)
3. Saber impacts, if d_saberGhoul2Collision is enabled, otherwise it does a normal box trace
4. Kicks, if d_saberKickTweak is enabled, otherwise it does a normal box trace.
5. Other trivial stuff that does not require too much accuracy.
(I think d_perPlayerGhoul2 also controls whether or not the server uses the common humanoid skeleton for everyone, but don't quote me on that one)
This approach has some noticeable consequences.
Since all checks are run against the same skeleton server side regardless of the shape of the actual model, impacts are sometimes visually hard to predict. This is extremely noticeable with models that are very different from the default humanoid skeleton. For instance, this snipe won't hit:
Client side, the crosshair code uses the skeleton of the actual model you see to tell that you are looking at it. This is why you see my name above the crosshair. The client uses the reelo skeleton, and thinks I'm aiming at it, so it draws the name, but the server uses the common humanoid skeleton for impacts.
As I already mentioned, the trace is dynamically based on animations, so the results also vary based on them: https://i.imgur.com/dySgzMd.gifv
(the red box is the world collision hitbox used when moving around. you can see how the name appears/disappears even on local tracing due to the clothing on top of the skeleton here).
This means that the visual inaccuracy propagates even more when you add animations into the mix. In a dynamic game where all player animations vary greatly, it does have consequences. Most of the time, when an impact location doesn't make sense, you can't just tell what's wrong visually because you don't see what's happening exactly on the server, especially with animations.
On top of that, the problem isn't just about the trace hitting/not hitting the skeleton. For better performance, huge simplifications are done in the trace function, and when the trace actually hits, the impact location is also inaccurate due to huge roundings.
Guess what the Ghoul2 trace results (impact coordinates) are used for?
1. Location based damage, but everyone already figured how shitty that one is.
2. All projectile, missile and hitscan (conc, sniper) impacts, if d_projectileGhoul2Collision is enabled (otherwise, the red hitbox that you saw is used... like in JK2)
3. Saber impacts, if d_saberGhoul2Collision is enabled, otherwise it does a normal box trace
4. Kicks, if d_saberKickTweak is enabled, otherwise it does a normal box trace.
5. Other trivial stuff that does not require too much accuracy.
(I think d_perPlayerGhoul2 also controls whether or not the server uses the common humanoid skeleton for everyone, but don't quote me on that one)
2. Saber Def blocking logic for weapons
Reveal hidden contents
Here is the slightly simplified but accurate logic that handles projectile blocking:
That logic is pretty standard, the actual blocking condition ("we can block") will be explained later. Of course, if the projectile hits the saber directly, we don't need to check for the block condition, which is why it's not present. Side note though, there is no blocking cooldown for direct saber hits (I doubt mad spin blocks would be viable though just for the fact that you turn and expose angles that are hittable).
In general I think saber def is pretty balanced against projectiles, which are only really good up close against high saber def. You can't fire faster than these cooldowns, even the 350ms one I think, but two teammates who sync their shots with E11 for instance could easily rape even the 50ms cooldown with some luck (don't forget though that for the cooldown to be applied, the shot actually has to be blocked, so it has to pass the blocking condition explained later, which is not guaranteed against high rate of fire spam).
And the logic for disruptor:
Even more simple, same thing for primary and alt fire (the only differences is going through multiple people at once for alt fire). Interestingly though, with disruptor, impact on the saber blade itself is not detected. The saber may as well be off (just like projectile body impacts). It will simply go through. This isn't a behavior that could be easily changed, since you need to have an actual projectile to collide with the blade itself. However, the hilt skeleton is added to the body skeleton when tracing, which means it will effectively block shots. Moreover, the blocking cooldown that is created from projectile blocking is checked here, but not modified at all! This means that two teammates can sync fire with an E11 and a sniper for instance, but not two snipers.
* Force dodge takes precedence over saber blocking. You probably already know the dodging conditions (only seeing 3, not in air, not in an anim, 300ms cooldown). The only notable part is that force dodge always fails on feet and legs and always succeeds elsewhere, which means it is also subject to the impact location error described above.
** See the next section.
Run a trace (affected by d_projectileGhoul2Collision).
When a blockable projectile touches the saber blade directly (trace impact ON SABER)...
- if saber def 3 OR jumping/going backwards, reflect it
- if saber def 2, deflect it
- if saber def 1, remove the projectile
When a blockable projectile touches the player model directly (trace impact ON MODEL)...
... AND if the blocking cooldown has expired (see below)
... AND if "we can block" ** (see later)
- if jumping/going backwards (regardless of the level), reflect it but with 350ms cooldown (don't ask me why)
- if saber def 3, reflect it with 50ms cooldown
- if saber def 2, deflect it with 150ms cooldown
- if saber def 1, remove the projectile with 250ms cooldown
That logic is pretty standard, the actual blocking condition ("we can block") will be explained later. Of course, if the projectile hits the saber directly, we don't need to check for the block condition, which is why it's not present. Side note though, there is no blocking cooldown for direct saber hits (I doubt mad spin blocks would be viable though just for the fact that you turn and expose angles that are hittable).
In general I think saber def is pretty balanced against projectiles, which are only really good up close against high saber def. You can't fire faster than these cooldowns, even the 350ms one I think, but two teammates who sync their shots with E11 for instance could easily rape even the 50ms cooldown with some luck (don't forget though that for the cooldown to be applied, the shot actually has to be blocked, so it has to pass the blocking condition explained later, which is not guaranteed against high rate of fire spam).
And the logic for disruptor:
Run a trace (affected by d_projectileGhoul2Collision).
If it hits the player model...
- Dodge if the seeing dodge condition is met. * (see below)
- If not saber def 3, don't block in any case
- If the blocking cooldown has not expired, don't block in any case (!!! same cooldown as with projectiles !!!)
- Block if the blocking condition is met. ** (see later)
Even more simple, same thing for primary and alt fire (the only differences is going through multiple people at once for alt fire). Interestingly though, with disruptor, impact on the saber blade itself is not detected. The saber may as well be off (just like projectile body impacts). It will simply go through. This isn't a behavior that could be easily changed, since you need to have an actual projectile to collide with the blade itself. However, the hilt skeleton is added to the body skeleton when tracing, which means it will effectively block shots. Moreover, the blocking cooldown that is created from projectile blocking is checked here, but not modified at all! This means that two teammates can sync fire with an E11 and a sniper for instance, but not two snipers.
* Force dodge takes precedence over saber blocking. You probably already know the dodging conditions (only seeing 3, not in air, not in an anim, 300ms cooldown). The only notable part is that force dodge always fails on feet and legs and always succeeds elsewhere, which means it is also subject to the impact location error described above.
** See the next section.
3. The blocking conditions
Reveal hidden contents
Common to body projectile impacts and all sniper impacts that were not force dodged (but also saber vs saber and saber throw), this is what ultimately decides if an impact is going to be blocked.
The list of game conditions is huge and I'm only going to focus on the interesting one. The rest is just common sense (no hand animation, no broken parry, no attack...).
As explained above, body impacts are what trigger this check. How are we going to block a shot that was traced to the body...? Well, Raven opted for a very, very simple approach but which has huge consequences. Instead of using saber position data or something similar, they completely ignore the saber and just fake a saber block in case the check succeeds.
The concept is simple, here's a simple first picture so you can really see what is being compared here:
The square is the world collision hitbox that you saw in the GIF earlier, so imagine a player sitting inside that box. The model origin is always at the bottom center of this hitbox. After an impact point was determined (with questionable accuracy) from the trace, we have a new origin->impact vector that we can use to compute an angle θ with the forward angle vector (the direction we are looking at). The angle is calculated in the X/Y 2D space, so the height of impact doesn't matter (but it doesn't mean that higher ground gives no advantage, since you can hit surfaces more easily, but I will get to to that in a moment).
Here is what a 60 degrees upper arm shot would look like:
You probably guessed it, that "sophisticated check" is just a dot product between the two normalized vectors, which is equivalent to doing a simple angle check. The angle gets wider with higher saber def level. I converted the values to actual angles:
If the impact angle is greater than the threshold, the shot won't be blocked, otherwise the player will do a fake saber block animation. Since the player origin never moves within the box, only the impact vector varies. This means that we can draw at all times an effective protected area around the player model (which spans infinitely on the Z axis):
(cyan = 26°, blue = 53°, pink = 73°, dotted green = unused 87°. The vectors were moved downwards so that it reflects reality more despite the perspective the picture was taken at which moved the head down, but it's obviously still not 100% accurate).
I deliberately chose the idle staff stance because I proved several times that even with saber def 3 it's the weakest sniper blocking stance in CTF, so I hope this illustrates it well: almost half the model is vulnerable. You can aim at anything that isn't in range of the area. With saber def 3, lucky headshots are trivial, a large area of the upper right arm is vulnerable, the entire left arm can be hit (that arm is notorious for the "sniping through saber" effect. we demonstrated that it does not matter if you snipe through the blade or not. Just see how easy it is to hit that left arm, the only thing actually blocking you is the hilt).
The area is exactly the same regardless of the shooter's origin. If you are behind them and still hit a part of their body that is in the protected area, it will be blocked. Your positioning when shooting only influences your ability to hit unprotected spots, nothing else. That also means that when defending, your goal is to expose these areas as little as possible. As a rule of thumb, directly facing the opponent is what intuitively works the best, but again that is just a consequence of changing body orientation and it varies with the stances you take (since the skeleton also varies).
It becomes most interesting when you add animations into the mix. And that's where you will really see the link with section 1: the origin doesn't move within the box, the forward direction moves with you and so does the box, so the forward direction also doesn't move in the box's reference. But the positioning of your player model moves inside the box, based on its animation, without moving the origin! Different animations are spaced differently inside the box, yet the area of protection is static: this means that different animations are protected differently.
Just check the staff walk animation: it is already much more protected. The weak areas alternate between the two hands and legs as you walk:
Going through each and every animation in JKA would be a lot of work. You mostly already know the vulnerable ones just by playing for a while. Most jumping animations have the same weak spot near the hand regardless of saber type, for example. The detection system itself is very performance friendly, but when you add the whole variety of animations in the game... it starts not making sense at times. Especially when you can spam primary fire as scout and get lucky hits even w/o perfect aim.
We simply can't check or even control our animation at all times. Combined with somewhat inaccurate impact detection and the difficulty to visually match a model with what is actually being traced server side, this is why saber def looks so random at times.
NB1: as long as the animation doesn't change your camera angle, it doesn't change your view angles server side. For instance the force jump animation that kinda looks like a mid air roll doesn't force you to look anywhere, and thus it doesn't change the protected area. Yet it's easy to kill someone in such an animation because of the exposure of other areas. Other animations like wallruns and katas do change this angle and the protected area therefore varies. Crouch is also a good animation to block because of the reduced exposure (except the foot), but it's rarely useful when you are in the middle of the map going for an objective under scout fire.
NB2: all of this is also true for saber throw and saber vs saber, but with reduced angles.
*** This d_saberGhoul2Collision check is completely out of place. The function has absolutely nothing to do with saber collision, since you have to not collide with a saber in order to get there. This is most likely a derp from Raven: maybe they thought the direct saber hit block w/o cooldown was good enough to justify reducing the radius? Anyway, that cvar is necessary for actual saber gameplay, because it's used in other places, so it shouldn't be toggled just to test wider angles. But if I had to change something, I would remove that random cvar check there which would increase the radius from 73 to 87 degrees and see if that helps. I think that was the original intended behavior before someone moved code around and that check stayed.
The list of game conditions is huge and I'm only going to focus on the interesting one. The rest is just common sense (no hand animation, no broken parry, no attack...).
As explained above, body impacts are what trigger this check. How are we going to block a shot that was traced to the body...? Well, Raven opted for a very, very simple approach but which has huge consequences. Instead of using saber position data or something similar, they completely ignore the saber and just fake a saber block in case the check succeeds.
The concept is simple, here's a simple first picture so you can really see what is being compared here:
The square is the world collision hitbox that you saw in the GIF earlier, so imagine a player sitting inside that box. The model origin is always at the bottom center of this hitbox. After an impact point was determined (with questionable accuracy) from the trace, we have a new origin->impact vector that we can use to compute an angle θ with the forward angle vector (the direction we are looking at). The angle is calculated in the X/Y 2D space, so the height of impact doesn't matter (but it doesn't mean that higher ground gives no advantage, since you can hit surfaces more easily, but I will get to to that in a moment).
Here is what a 60 degrees upper arm shot would look like:
You probably guessed it, that "sophisticated check" is just a dot product between the two normalized vectors, which is equivalent to doing a simple angle check. The angle gets wider with higher saber def level. I converted the values to actual angles:
Saber Def 1: 26 degrees
Saber Def 2: 53 degrees
Saber Def 3: 73 degrees if d_saberGhoul2Collision is enabled, 87 degrees otherwise *** (see the very end of the post for more info about this)
If the impact angle is greater than the threshold, the shot won't be blocked, otherwise the player will do a fake saber block animation. Since the player origin never moves within the box, only the impact vector varies. This means that we can draw at all times an effective protected area around the player model (which spans infinitely on the Z axis):
(cyan = 26°, blue = 53°, pink = 73°, dotted green = unused 87°. The vectors were moved downwards so that it reflects reality more despite the perspective the picture was taken at which moved the head down, but it's obviously still not 100% accurate).
I deliberately chose the idle staff stance because I proved several times that even with saber def 3 it's the weakest sniper blocking stance in CTF, so I hope this illustrates it well: almost half the model is vulnerable. You can aim at anything that isn't in range of the area. With saber def 3, lucky headshots are trivial, a large area of the upper right arm is vulnerable, the entire left arm can be hit (that arm is notorious for the "sniping through saber" effect. we demonstrated that it does not matter if you snipe through the blade or not. Just see how easy it is to hit that left arm, the only thing actually blocking you is the hilt).
The area is exactly the same regardless of the shooter's origin. If you are behind them and still hit a part of their body that is in the protected area, it will be blocked. Your positioning when shooting only influences your ability to hit unprotected spots, nothing else. That also means that when defending, your goal is to expose these areas as little as possible. As a rule of thumb, directly facing the opponent is what intuitively works the best, but again that is just a consequence of changing body orientation and it varies with the stances you take (since the skeleton also varies).
It becomes most interesting when you add animations into the mix. And that's where you will really see the link with section 1: the origin doesn't move within the box, the forward direction moves with you and so does the box, so the forward direction also doesn't move in the box's reference. But the positioning of your player model moves inside the box, based on its animation, without moving the origin! Different animations are spaced differently inside the box, yet the area of protection is static: this means that different animations are protected differently.
Just check the staff walk animation: it is already much more protected. The weak areas alternate between the two hands and legs as you walk:
Going through each and every animation in JKA would be a lot of work. You mostly already know the vulnerable ones just by playing for a while. Most jumping animations have the same weak spot near the hand regardless of saber type, for example. The detection system itself is very performance friendly, but when you add the whole variety of animations in the game... it starts not making sense at times. Especially when you can spam primary fire as scout and get lucky hits even w/o perfect aim.
We simply can't check or even control our animation at all times. Combined with somewhat inaccurate impact detection and the difficulty to visually match a model with what is actually being traced server side, this is why saber def looks so random at times.
NB1: as long as the animation doesn't change your camera angle, it doesn't change your view angles server side. For instance the force jump animation that kinda looks like a mid air roll doesn't force you to look anywhere, and thus it doesn't change the protected area. Yet it's easy to kill someone in such an animation because of the exposure of other areas. Other animations like wallruns and katas do change this angle and the protected area therefore varies. Crouch is also a good animation to block because of the reduced exposure (except the foot), but it's rarely useful when you are in the middle of the map going for an objective under scout fire.
NB2: all of this is also true for saber throw and saber vs saber, but with reduced angles.
*** This d_saberGhoul2Collision check is completely out of place. The function has absolutely nothing to do with saber collision, since you have to not collide with a saber in order to get there. This is most likely a derp from Raven: maybe they thought the direct saber hit block w/o cooldown was good enough to justify reducing the radius? Anyway, that cvar is necessary for actual saber gameplay, because it's used in other places, so it shouldn't be toggled just to test wider angles. But if I had to change something, I would remove that random cvar check there which would increase the radius from 73 to 87 degrees and see if that helps. I think that was the original intended behavior before someone moved code around and that check stayed.