-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(spigot): use protocollib accessors for bossbar packets
Fixes bossbars not working on MC 1.20.6 and above.
- Loading branch information
1 parent
11e6d8b
commit fdcbbc4
Showing
2 changed files
with
238 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
231 changes: 231 additions & 0 deletions
231
.../main/java/com/rexcantor64/triton/packetinterceptor/protocollib/BossBarPacketHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
package com.rexcantor64.triton.packetinterceptor.protocollib; | ||
|
||
import com.comphenix.protocol.PacketType; | ||
import com.comphenix.protocol.events.PacketContainer; | ||
import com.comphenix.protocol.events.PacketEvent; | ||
import com.comphenix.protocol.reflect.EquivalentConverter; | ||
import com.comphenix.protocol.reflect.FuzzyReflection; | ||
import com.comphenix.protocol.reflect.accessors.Accessors; | ||
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; | ||
import com.comphenix.protocol.reflect.accessors.FieldAccessor; | ||
import com.comphenix.protocol.reflect.accessors.MethodAccessor; | ||
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; | ||
import com.comphenix.protocol.utility.MinecraftReflection; | ||
import com.comphenix.protocol.utility.MinecraftVersion; | ||
import com.comphenix.protocol.wrappers.BukkitConverters; | ||
import com.comphenix.protocol.wrappers.EnumWrappers; | ||
import com.comphenix.protocol.wrappers.WrappedChatComponent; | ||
import com.rexcantor64.triton.player.SpigotLanguagePlayer; | ||
import lombok.val; | ||
import net.md_5.bungee.api.chat.BaseComponent; | ||
import net.md_5.bungee.api.chat.TranslatableComponent; | ||
import net.md_5.bungee.chat.ComponentSerializer; | ||
|
||
import java.util.Arrays; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
|
||
import static com.rexcantor64.triton.packetinterceptor.protocollib.HandlerFunction.asAsync; | ||
|
||
public class BossBarPacketHandler extends PacketHandler { | ||
|
||
private final Class<?> ACTION_ENUM_CLASS; | ||
private final Class<?> OPERATION_INTERFACE; | ||
private final MethodAccessor GET_OPERATION_ACTION_METHOD; | ||
private final FieldAccessor ADD_COMPONENT_FIELD; | ||
private final FieldAccessor UPDATE_COMPONENT_FIELD; | ||
private final ConstructorAccessor UPDATE_CONSTRUCTOR; | ||
private final EquivalentConverter<Action> ACTION_CONVERTER; | ||
private final EquivalentConverter<WrappedChatComponent> COMPONENT_CONVERTER = BukkitConverters.getWrappedChatComponentConverter(); | ||
|
||
public BossBarPacketHandler() { | ||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { // 1.17+ | ||
val innerClasses = PacketType.Play.Server.BOSS.getPacketClass().getDeclaredClasses(); | ||
ACTION_ENUM_CLASS = Arrays.stream(innerClasses) | ||
.filter(Enum.class::isAssignableFrom) | ||
.findAny() | ||
.orElseThrow(() -> new RuntimeException("Cannot find boss bar action enum class")); | ||
|
||
// the operation interface has a method that returns the operation type | ||
OPERATION_INTERFACE = Arrays.stream(innerClasses) | ||
.filter(Class::isInterface) | ||
.filter(c -> !FuzzyReflection.fromClass(c).getMethodListByParameters(ACTION_ENUM_CLASS).isEmpty()) | ||
.findAny() | ||
.orElseThrow(() -> new RuntimeException("Cannot find boss bar operation interface class")); | ||
GET_OPERATION_ACTION_METHOD = Accessors.getMethodAccessor( | ||
FuzzyReflection.fromClass(OPERATION_INTERFACE) | ||
.getMethodByReturnTypeAndParameters("getType", ACTION_ENUM_CLASS) | ||
); | ||
|
||
// the add operation class has a chat component field, but does not have a chat component constructor | ||
val addOperationClass = Arrays.stream(innerClasses) | ||
.filter(OPERATION_INTERFACE::isAssignableFrom) | ||
.filter(c -> !FuzzyReflection.fromClass(c, true).getFieldListByType(MinecraftReflection.getIChatBaseComponentClass()).isEmpty()) | ||
.filter(c -> FuzzyReflection.fromClass(c, true) | ||
.getConstructorList( | ||
FuzzyMethodContract.newBuilder() | ||
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass()) | ||
.build() | ||
) | ||
.isEmpty() | ||
) | ||
.findAny() | ||
.orElseThrow(() -> new RuntimeException("Cannot find boss bar add operation class")); | ||
ADD_COMPONENT_FIELD = Accessors.getFieldAccessor(addOperationClass, MinecraftReflection.getIChatBaseComponentClass(), true); | ||
|
||
// the update operation class has a chat component field, and also has a chat component constructor | ||
val updateOperationClass = Arrays.stream(innerClasses) | ||
.filter(OPERATION_INTERFACE::isAssignableFrom) | ||
.filter(c -> !FuzzyReflection.fromClass(c, true).getFieldListByType(MinecraftReflection.getIChatBaseComponentClass()).isEmpty()) | ||
.filter(c -> !FuzzyReflection.fromClass(c, true) | ||
.getConstructorList( | ||
FuzzyMethodContract.newBuilder() | ||
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass()) | ||
.build() | ||
) | ||
.isEmpty() | ||
) | ||
.findAny() | ||
.orElseThrow(() -> new RuntimeException("Cannot find boss bar add operation class")); | ||
UPDATE_COMPONENT_FIELD = Accessors.getFieldAccessor(updateOperationClass, MinecraftReflection.getIChatBaseComponentClass(), true); | ||
|
||
UPDATE_CONSTRUCTOR = Accessors.getConstructorAccessor( | ||
FuzzyReflection.fromClass(updateOperationClass, true) | ||
.getConstructor( | ||
FuzzyMethodContract.newBuilder() | ||
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass()) | ||
.build() | ||
) | ||
); | ||
|
||
ACTION_CONVERTER = new EnumWrappers.EnumConverter<>(ACTION_ENUM_CLASS, Action.class); | ||
} else { | ||
ACTION_ENUM_CLASS = null; | ||
OPERATION_INTERFACE = null; | ||
GET_OPERATION_ACTION_METHOD = null; | ||
ADD_COMPONENT_FIELD = null; | ||
UPDATE_COMPONENT_FIELD = null; | ||
UPDATE_CONSTRUCTOR = null; | ||
ACTION_CONVERTER = null; | ||
} | ||
} | ||
|
||
/** | ||
* @return Whether the plugin should attempt to translate boss bars | ||
*/ | ||
private boolean areBossBarsDisabled() { | ||
return !getMain().getConf().isBossbars(); | ||
} | ||
|
||
private void handleBoss_1_9(PacketEvent packet, SpigotLanguagePlayer languagePlayer) { | ||
if (areBossBarsDisabled()) return; | ||
|
||
val uuid = packet.getPacket().getUUIDs().readSafely(0); | ||
Action action = packet.getPacket().getEnumModifier(Action.class, 1).readSafely(0); | ||
if (action == Action.REMOVE) { | ||
languagePlayer.removeBossbar(uuid); | ||
return; | ||
} | ||
if (action != Action.ADD && action != Action.UPDATE_NAME) return; | ||
|
||
WrappedChatComponent component = packet.getPacket().getChatComponents().readSafely(0); | ||
try { | ||
translateAndSaveBossBar(languagePlayer, uuid, component); | ||
packet.getPacket().getChatComponents().writeSafely(0, component); | ||
} catch (RuntimeException e) { | ||
// Catch 1.16 Hover 'contents' not being parsed correctly | ||
// Has been fixed in newer versions of Spigot 1.16 | ||
logger().logError(e, "Could not parse a bossbar, so it was ignored. Bossbar: %1", component.getJson()); | ||
} | ||
} | ||
|
||
private void handleBoss_1_17(PacketEvent packet, SpigotLanguagePlayer languagePlayer) { | ||
if (areBossBarsDisabled()) return; | ||
|
||
val uuid = packet.getPacket().getUUIDs().readSafely(0); | ||
val operation = packet.getPacket().getSpecificModifier(OPERATION_INTERFACE).readSafely(0); | ||
Action action = ACTION_CONVERTER.getSpecific(GET_OPERATION_ACTION_METHOD.invoke(operation)); | ||
|
||
if (action == Action.REMOVE) { | ||
languagePlayer.removeBossbar(uuid); | ||
return; | ||
} | ||
|
||
WrappedChatComponent component; | ||
if (action == Action.ADD) { | ||
component = COMPONENT_CONVERTER.getSpecific(ADD_COMPONENT_FIELD.get(operation)); | ||
} else if (action == Action.UPDATE_NAME) { | ||
component = COMPONENT_CONVERTER.getSpecific(UPDATE_COMPONENT_FIELD.get(operation)); | ||
} else { | ||
return; | ||
} | ||
|
||
translateAndSaveBossBar(languagePlayer, uuid, component); | ||
|
||
if (action == Action.ADD) { | ||
ADD_COMPONENT_FIELD.set(operation, COMPONENT_CONVERTER.getGeneric(component)); | ||
} else { | ||
UPDATE_COMPONENT_FIELD.set(operation, COMPONENT_CONVERTER.getGeneric(component)); | ||
} | ||
} | ||
|
||
private void translateBossBar(SpigotLanguagePlayer languagePlayer, WrappedChatComponent component) { | ||
BaseComponent[] result = getLanguageParser().parseComponent( | ||
languagePlayer, | ||
getConfig().getBossbarSyntax(), | ||
ComponentSerializer.parse(component.getJson()) | ||
); | ||
if (result == null) { | ||
result = new BaseComponent[]{new TranslatableComponent("")}; | ||
} | ||
component.setJson(ComponentSerializer.toString(result)); | ||
} | ||
|
||
private void translateAndSaveBossBar(SpigotLanguagePlayer languagePlayer, UUID uuid, WrappedChatComponent component) { | ||
languagePlayer.setBossbar(uuid, component.getJson()); | ||
translateBossBar(languagePlayer, component); | ||
} | ||
|
||
public void refreshBossbar(SpigotLanguagePlayer player, UUID uuid, String json) { | ||
if (!MinecraftVersion.COMBAT_UPDATE.atOrAbove()) { | ||
// bossbar only works on 1.9+ | ||
return; | ||
} | ||
|
||
val bukkitPlayerOpt = player.toBukkit(); | ||
if (!bukkitPlayerOpt.isPresent()) return; | ||
val bukkitPlayer = bukkitPlayerOpt.get(); | ||
|
||
PacketContainer packet = createPacket(PacketType.Play.Server.BOSS); | ||
packet.getUUIDs().writeSafely(0, uuid); | ||
val msg = WrappedChatComponent.fromJson(json); | ||
translateBossBar(player, msg); | ||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { // 1.17+ | ||
Object operation = UPDATE_CONSTRUCTOR.invoke(COMPONENT_CONVERTER.getGeneric(msg)); | ||
// noinspection unchecked | ||
packet.getSpecificModifier((Class<Object>) OPERATION_INTERFACE).writeSafely(0, operation); | ||
} else { | ||
packet.getEnumModifier(Action.class, 1).writeSafely(0, Action.UPDATE_NAME); | ||
packet.getChatComponents().writeSafely(0, msg); | ||
} | ||
sendPacket(bukkitPlayer, packet, false); | ||
} | ||
|
||
@Override | ||
public void registerPacketTypes(Map<PacketType, HandlerFunction> registry) { | ||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { // 1.17+ | ||
// Rework of packet to use subclasses instead of having all data on packet itself | ||
registry.put(PacketType.Play.Server.BOSS, asAsync(this::handleBoss_1_17)); | ||
} else if (MinecraftVersion.COMBAT_UPDATE.atOrAbove()) { // 1.9+ | ||
// Bossbars were only added on MC 1.9 | ||
registry.put(PacketType.Play.Server.BOSS, asAsync(this::handleBoss_1_9)); | ||
} | ||
} | ||
|
||
/** | ||
* BossBar packet Action wrapper | ||
*/ | ||
public enum Action { | ||
ADD, REMOVE, UPDATE_PCT, UPDATE_NAME, UPDATE_STYLE, UPDATE_PROPERTIES | ||
} | ||
} |