Skip to content

Commit 049fe17

Browse files
committed
Version 1.2: Refactors & implement always-add-sprint
1 parent 7ab6d19 commit 049fe17

File tree

7 files changed

+162
-41
lines changed

7 files changed

+162
-41
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ plugins {
33
}
44

55
group = "me.beanes"
6-
version = "1.0-SNAPSHOT"
6+
version = "1.2"
77

88

99
repositories {

src/main/java/me/beanes/betterslowdown/FallbackMode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
public enum FallbackMode {
44
SERVER,
55
SPRINT,
6-
NO_SPRINT
6+
NO_SPRINT,
7+
CLIENT
78
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package me.beanes.betterslowdown.data;
2+
3+
public class PlayerData {
4+
private byte lastUsefulBitmask;
5+
private double lastSpeed;
6+
private boolean clientSprintingState;
7+
8+
public PlayerData() {
9+
this.reset();
10+
}
11+
12+
public void reset() {
13+
this.lastUsefulBitmask = (byte) -1;
14+
this.lastSpeed = -1.0D;
15+
this.clientSprintingState = false;
16+
}
17+
18+
public double getLastSpeed() {
19+
return lastSpeed;
20+
}
21+
22+
public byte getLastUsefulBitmask() {
23+
return lastUsefulBitmask;
24+
}
25+
26+
public boolean isClientSprintingState() {
27+
return clientSprintingState;
28+
}
29+
30+
public void setLastUsefulBitmask(byte lastUsefulBitmask) {
31+
this.lastUsefulBitmask = lastUsefulBitmask;
32+
}
33+
34+
public void setLastSpeed(double lastSpeed) {
35+
this.lastSpeed = lastSpeed;
36+
}
37+
38+
public void setClientSprintingState(boolean clientSprintingState) {
39+
this.clientSprintingState = clientSprintingState;
40+
}
41+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package me.beanes.betterslowdown.data;
2+
3+
import com.github.retrooper.packetevents.protocol.player.User;
4+
5+
import java.util.*;
6+
7+
public class PlayerDataManager {
8+
9+
// A thread local variable so we don't need a ConcurrentHashMap (which does useless locking!) for the player data
10+
private final ThreadLocal<Map<User, PlayerData>> cacheThreadLocal = ThreadLocal.withInitial(HashMap::new);
11+
// This could even use a FastThreadLocal https://netty.io/4.0/api/io/netty/util/concurrent/FastThreadLocal.html
12+
13+
public void cache(User user, PlayerData data) {
14+
Map<User, PlayerData> cache = cacheThreadLocal.get();
15+
cache.put(user, data);
16+
}
17+
18+
public void remove(User user) {
19+
Map<User, PlayerData> cache = cacheThreadLocal.get();
20+
cache.remove(user);
21+
}
22+
23+
public PlayerData get(User user) {
24+
Map<User, PlayerData> cache = cacheThreadLocal.get();
25+
26+
PlayerData data = cache.get(user);
27+
28+
if (data == null) {
29+
data = new PlayerData();
30+
cache.put(user, data);
31+
}
32+
33+
return data;
34+
}
35+
}
Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,53 @@
11
package me.beanes.betterslowdown.listener;
22

3-
import com.github.retrooper.packetevents.event.PacketListener;
4-
import com.github.retrooper.packetevents.event.PacketSendEvent;
5-
import com.github.retrooper.packetevents.event.UserDisconnectEvent;
3+
import com.github.retrooper.packetevents.event.*;
4+
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
65
import com.github.retrooper.packetevents.protocol.entity.data.EntityData;
76
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
87
import com.github.retrooper.packetevents.protocol.player.User;
8+
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
99
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEntityMetadata;
1010
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerUpdateAttributes;
1111
import me.beanes.betterslowdown.BetterSlowdown;
1212
import me.beanes.betterslowdown.FallbackMode;
13+
import me.beanes.betterslowdown.data.PlayerData;
14+
import me.beanes.betterslowdown.data.PlayerDataManager;
1315

14-
import java.util.HashMap;
1516
import java.util.Iterator;
16-
import java.util.Map;
1717
import java.util.UUID;
1818

1919
public class FilterListener implements PacketListener {
2020
private static final UUID SPRINT_MODIFIER_UUID = UUID.fromString("662A6B8D-DA3E-4C1C-8813-96EA6097278D");
21-
private static final WrapperPlayServerUpdateAttributes.PropertyModifier SPRINT_MODIFIER = new WrapperPlayServerUpdateAttributes.PropertyModifier(SPRINT_MODIFIER_UUID, 0.30000001192092896D, WrapperPlayServerUpdateAttributes.PropertyModifier.Operation.MULTIPLY_TOTAL);
21+
private static final WrapperPlayServerUpdateAttributes.PropertyModifier SPRINT_MODIFIER = new WrapperPlayServerUpdateAttributes.PropertyModifier(
22+
SPRINT_MODIFIER_UUID,
23+
0.30000001192092896D,
24+
WrapperPlayServerUpdateAttributes.PropertyModifier.Operation.MULTIPLY_TOTAL
25+
);
2226
private final BetterSlowdown plugin;
23-
private final ThreadLocal<Map<User, Byte>> lastUsefulBitmaskThreadLocal;
24-
private final ThreadLocal<Map<User, Double>> lastSpeedThreadLocal;
27+
private final PlayerDataManager manager;
2528

2629
public FilterListener(BetterSlowdown plugin) {
2730
this.plugin = plugin;
28-
this.lastUsefulBitmaskThreadLocal = ThreadLocal.withInitial(HashMap::new);
29-
this.lastSpeedThreadLocal = ThreadLocal.withInitial(HashMap::new);
31+
this.manager = new PlayerDataManager();
3032
}
3133

3234

35+
@Override
36+
public void onPacketReceive(PacketReceiveEvent event) {
37+
// We keep track of the current client sprinting metadata
38+
if (event.getPacketType() == PacketType.Play.Client.ENTITY_ACTION) {
39+
WrapperPlayClientEntityAction wrapper = new WrapperPlayClientEntityAction(event);
40+
41+
PlayerData data = manager.get(event.getUser());
42+
43+
if (wrapper.getAction() == WrapperPlayClientEntityAction.Action.START_SPRINTING) {
44+
data.setClientSprintingState(true);
45+
} else if (wrapper.getAction() == WrapperPlayClientEntityAction.Action.STOP_SPRINTING) {
46+
data.setClientSprintingState(false);
47+
}
48+
}
49+
}
50+
3351
@Override
3452
public void onPacketSend(PacketSendEvent event) {
3553
// Listen for metadata packets
@@ -42,26 +60,35 @@ public void onPacketSend(PacketSendEvent event) {
4260
Iterator<EntityData> iterator = wrapper.getEntityMetadata().iterator();
4361

4462
while (iterator.hasNext()) {
45-
EntityData data = iterator.next();
63+
EntityData entityData = iterator.next();
4664

4765
// Check if the entity metadata bitmask is in this packet
48-
if (data.getIndex() == 0) {
49-
byte bitmask = (byte) data.getValue();
66+
if (entityData.getIndex() == MetadataValues.BITMASK_INDEX) {
67+
byte bitmask = (byte) entityData.getValue();
5068
// Calculate the useful bitmask
51-
bitmask &= ~0x02; // Remove crouching
52-
bitmask &= ~0x08; // Remove sprinting
53-
bitmask &= ~0x10; // Remove using
69+
bitmask &= ~MetadataValues.FLAG_CROUCHING; // Remove crouching
70+
bitmask &= ~MetadataValues.FLAG_SPRINTING; // Remove sprinting
71+
bitmask &= ~MetadataValues.FLAG_USING; // Remove using
5472

5573
// Check if the "useful" bitmask has changed, which means either the client is on fire or invisible
56-
Map<User, Byte> lastUsefulBitmask = lastUsefulBitmaskThreadLocal.get();
57-
if (lastUsefulBitmask.getOrDefault(user, (byte) 0) != bitmask) {
58-
lastUsefulBitmask.put(user, bitmask);
59-
60-
if (plugin.getMode() != FallbackMode.SERVER) {
61-
if (plugin.getMode() == FallbackMode.SPRINT) {
62-
data.setValue((byte) ((byte) data.getValue() | 0x08)); // Rewrite with sprinting = true
63-
} else if (plugin.getMode() == FallbackMode.NO_SPRINT) {
64-
data.setValue((byte) ((byte) data.getValue() & ~0x08)); // Rewrite with sprinting = false
74+
PlayerData data = manager.get(event.getUser());
75+
byte lastUsefulBitmask = data.getLastUsefulBitmask();
76+
77+
if (lastUsefulBitmask != bitmask) {
78+
data.setLastUsefulBitmask(bitmask);
79+
80+
// Get the fallback mode as we are 100% sending the bitmask
81+
FallbackMode mode = plugin.getMode();
82+
83+
if (mode != FallbackMode.SERVER) {
84+
if (mode == FallbackMode.SPRINT
85+
|| (mode == FallbackMode.CLIENT && data.isClientSprintingState())) {
86+
// Rewrite with sprinting = true
87+
entityData.setValue((byte) ((byte) entityData.getValue() | MetadataValues.FLAG_SPRINTING));
88+
} else if (mode == FallbackMode.NO_SPRINT
89+
|| (mode == FallbackMode.CLIENT && !data.isClientSprintingState())) {
90+
// Rewrite with sprinting = false
91+
entityData.setValue((byte) ((byte) entityData.getValue() & ~MetadataValues.FLAG_SPRINTING));
6592
}
6693

6794
// Needs re-encode
@@ -87,7 +114,7 @@ public void onPacketSend(PacketSendEvent event) {
87114

88115
if (wrapper.getEntityId() == user.getEntityId()) {
89116
for (WrapperPlayServerUpdateAttributes.Property snapshot : wrapper.getProperties()) {
90-
if (snapshot.getAttribute().getName().getKey().equals("movement_speed")) {
117+
if (snapshot.getAttribute().equals(Attributes.MOVEMENT_SPEED)) {
91118
// Calculate the movement speed without sprint
92119
boolean exists = snapshot.getModifiers().removeIf(modifier -> modifier.getUUID().equals(SPRINT_MODIFIER_UUID));
93120
if (exists) {
@@ -96,19 +123,21 @@ public void onPacketSend(PacketSendEvent event) {
96123

97124
double speed = snapshot.calcValue();
98125

99-
Map<User, Double> lastSpeed = lastSpeedThreadLocal.get();
126+
PlayerData data = manager.get(user);
127+
double lastSpeed = data.getLastSpeed();
100128

101-
if (lastSpeed.getOrDefault(user, -1.0D) == speed) {
129+
if (lastSpeed == speed) {
102130
// Cancel this attribute packet as it only changes the sprint attribute
103131
event.setCancelled(true);
104132
} else {
105133
// Update the speed
106-
lastSpeed.put(user, speed);
134+
data.setLastSpeed(speed);
107135

108136
if (exists) {
109-
snapshot.addModifier(SPRINT_MODIFIER); // Re-add the modifier if packetevents is on default re-encode
110-
} else if (plugin.isAlwaysAddSprint()) {
111-
// TODO: check if the player is "allowed" to sprint, keep food value, and check START_SPRINTING and STOP_SPRINTING to prevent omnisprint?
137+
// Re-add the modifier if packetevents is on default re-encode
138+
snapshot.addModifier(SPRINT_MODIFIER);
139+
} else if (plugin.isAlwaysAddSprint() && data.isClientSprintingState()) {
140+
// Add sprint modifier is the client claimed he was sprinting and always add sprint is enabled
112141
snapshot.addModifier(SPRINT_MODIFIER);
113142
event.markForReEncode(true);
114143
}
@@ -120,14 +149,17 @@ public void onPacketSend(PacketSendEvent event) {
120149
User user = event.getUser();
121150

122151
// Reset last as the entity is recreated
123-
this.lastUsefulBitmaskThreadLocal.get().remove(user);
124-
this.lastSpeedThreadLocal.get().remove(user);
152+
manager.get(user).reset();
125153
}
126154
}
127155

156+
@Override
157+
public void onUserConnect(UserConnectEvent event) {
158+
manager.cache(event.getUser(), new PlayerData());
159+
}
160+
128161
@Override
129162
public void onUserDisconnect(UserDisconnectEvent event) {
130-
this.lastUsefulBitmaskThreadLocal.get().remove(event.getUser());
131-
this.lastSpeedThreadLocal.get().remove(event.getUser());
163+
manager.remove(event.getUser());
132164
}
133165
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package me.beanes.betterslowdown.listener;
2+
3+
/**
4+
* https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Entity_metadata
5+
*/
6+
public class MetadataValues {
7+
public static final int BITMASK_INDEX = 0;
8+
public static final byte FLAG_CROUCHING = 0x02;
9+
public static final byte FLAG_SPRINTING = 0x08;
10+
public static final byte FLAG_USING = 0x10;
11+
}

src/main/resources/config.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
# If the player turns invisible or is set on fire we still have to send the bitmask
1313
# There are different types of fallback modes for this:
1414
# SERVER - this mode will just use the server sent value
15+
# CLIENT - this mode will use the client sprinting state (latency dependent)
1516
# SPRINT - always set sprinting true, causes the client to force the hit slowdown (if the client hit) for a single tick when it receives the updated metadata
16-
# NO_SPRINT - always set sprinting false, causes the client to not do the hit slowdown (if the client hit) even when its sprinting when it receives the updated metadata
17-
mode: SPRINT
17+
# NO_SPRINT - always set sprinting false, causes the client to not do the hit slowdown (if the client hit) for a single tick when it receives the updated metadata
18+
mode: CLIENT
1819

1920
# Should the server always apply the sprint attribute when attributes are being overridden by server (and the plugin can't cancel it due to speed change)
2021
# This is useful for HCF servers when you get speed 3 / speed 2, and your sprint suddenly disappears
21-
# This is still in beta as this can cause the player to sprint while not having the food for it and it can cause omnisprint
22+
# Worst case scenario, this can cause OmniSprint due to using client state
2223
always-add-sprint: false

0 commit comments

Comments
 (0)