-
Notifications
You must be signed in to change notification settings - Fork 53
Expand file tree
/
Copy pathFairKnockbackFeature.java
More file actions
138 lines (120 loc) · 5.56 KB
/
Copy pathFairKnockbackFeature.java
File metadata and controls
138 lines (120 loc) · 5.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package io.github.togar2.pvp.feature.knockback;
import io.github.togar2.pvp.events.EntityKnockbackEvent;
import io.github.togar2.pvp.feature.FeatureType;
import io.github.togar2.pvp.feature.config.DefinedFeature;
import io.github.togar2.pvp.feature.config.FeatureConfiguration;
import io.github.togar2.pvp.player.CombatPlayer;
import net.minestom.server.ServerFlag;
import net.minestom.server.collision.Aerodynamics;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.LivingEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.Nullable;
/**
* Extension of {@link VanillaKnockbackFeature} which tries to make the playing field more even for players with high latency.
* <p>
* Rising knockback is applied usually when a player is on the ground.
* This feature determines if the player would be on the ground <i>client side</i> instead of just server side.
* To have just this change, use {@link FairKnockbackFeature#ONLY_RISING}.
* <p>
* The other option is {@link FairKnockbackFeature#RISING_AND_FALLING}, which, along with rising knockback,
* also compensates for latency with falling knockback. It will use the (estimated) velocity of when the packet will arrive at the client,
* possibly making falling knockback feel more natural.
* <p>
* The changes made by this feature only apply to players with more than 25 ms ping.
*/
public class FairKnockbackFeature extends VanillaKnockbackFeature {
/**
* @see FairKnockbackFeature
*/
public static final DefinedFeature<FairKnockbackFeature> ONLY_RISING = new DefinedFeature<>(
FeatureType.KNOCKBACK, configuration -> new FairKnockbackFeature(configuration, false),
FeatureType.VERSION
);
/**
* @see FairKnockbackFeature
*/
public static final DefinedFeature<FairKnockbackFeature> RISING_AND_FALLING = new DefinedFeature<>(
FeatureType.KNOCKBACK, configuration -> new FairKnockbackFeature(configuration, true),
FeatureType.VERSION
);
private static final int PING_OFFSET = 25;
protected final boolean compensateFallKnockback;
public FairKnockbackFeature(FeatureConfiguration configuration, boolean compensateFallKnockback) {
super(configuration);
this.compensateFallKnockback = compensateFallKnockback;
}
@Override
protected boolean applyKnockback(LivingEntity target, Entity attacker, @Nullable Entity source,
EntityKnockbackEvent.KnockbackType type, int extraKnockback,
double dx, double dz, boolean legacy) {
if (!(target instanceof Player player) || player.getLatency() < PING_OFFSET)
return super.applyKnockback(target, attacker, source, type, extraKnockback, dx, dz, legacy);
KnockbackValues values = prepareKnockback(target, attacker, source, type, extraKnockback, dx, dz, legacy);
if (values == null) return false;
Vec velocity = target.getVelocity();
if (legacy && type == EntityKnockbackEvent.KnockbackType.ATTACK) {
// For legacy versions, extra knockback is added directly on top of the original velocity
target.setVelocity(velocity.add(
-values.horizontalModifier().x(),
values.vertical(),
-values.horizontalModifier().z()
));
} else {
// For modern versions and legacy non-attack knockback, the velocity is first divided by 2
int latencyTicks = getLatencyTicks(player.getLatency());
double vertical;
if (isOnGroundClientSide(player, latencyTicks)) {
vertical = Math.min(values.verticalLimit(), velocity.y() / 2d + values.vertical());
} else if (compensateFallKnockback) {
vertical = getCompensatedVerticalVelocity(player.getAerodynamics(), velocity.y(), latencyTicks);
} else {
vertical = velocity.y();
}
target.setVelocity(new Vec(
velocity.x() / 2d - values.horizontalModifier().x(),
vertical,
velocity.z() / 2d - values.horizontalModifier().z()
));
}
if (values.animationType() == EntityKnockbackEvent.AnimationType.DIRECTIONAL) {
// Send player a packet with its hurt direction
sendDirectionalEvent(player, dx, dz);
}
return true;
}
protected boolean isOnGroundClientSide(Player player, int latencyTicks) {
if (player.isOnGround() || !(player instanceof CombatPlayer combatPlayer)) return true;
if (player.getGravityTickCount() > 30) return false; // Very uncertain, default to false
// These are all cases in which isOnGroundAfterTicks() will not be accurate
Block block = player.getInstance().getBlock(player.getPosition());
if (player.isFlyingWithElytra()
|| block.compare(Block.WATER)
|| block.compare(Block.LAVA)
|| block.compare(Block.COBWEB)
|| block.compare(Block.SCAFFOLDING))
return false;
return combatPlayer.isOnGroundAfterTicks(latencyTicks);
}
/**
* Compensates the given vertical velocity for gravity calculations for a given amount of ticks.
* This means for every tick, it will be affected by gravity and vertical air resistance.
*
* @param aerodynamics the aerodynamics of the player
* @param velocity the velocity to compensate
* @param ticks the amount of ticks to compensate for
* @return the compensated vertical velocity
*/
protected static double getCompensatedVerticalVelocity(Aerodynamics aerodynamics, double velocity, int ticks) {
for (int i = 0; i < ticks; i++) {
velocity -= aerodynamics.gravity();
velocity *= aerodynamics.verticalAirResistance();
}
return velocity;
}
private static int getLatencyTicks(int latencyMillis) {
return Math.ceilDiv(latencyMillis * ServerFlag.SERVER_TICKS_PER_SECOND, 1000) + 2;
}
}