mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-28 03:19:14 +00:00
@@ -9,6 +9,7 @@ repositories {
|
||||
maven("https://repo.momirealms.net/releases/")
|
||||
maven("https://mvn.lumine.io/repository/maven-public/") // model engine
|
||||
maven("https://nexus.phoenixdevt.fr/repository/maven-public/") // mmoitems
|
||||
maven("https://repo.viaversion.com") // via
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -34,6 +35,8 @@ dependencies {
|
||||
compileOnly("io.lumine:MythicLib-dist:1.6.2-SNAPSHOT")
|
||||
// LuckPerms
|
||||
compileOnly("net.luckperms:api:5.4")
|
||||
// viaversion
|
||||
compileOnly("com.viaversion:viaversion-api:5.3.2")
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.momirealms.craftengine.bukkit.compatibility.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.ViaAPI;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
public class ViaVersionProtocol {
|
||||
private final boolean hasPlugin;
|
||||
private final ViaAPI<?> viaAPI;
|
||||
|
||||
public ViaVersionProtocol(boolean hasPlugin) {
|
||||
this.hasPlugin = hasPlugin;
|
||||
this.viaAPI = hasPlugin ? Via.getAPI() : null;
|
||||
}
|
||||
|
||||
public int getPlayerProtocolVersion(UUID uuid) {
|
||||
if (!hasPlugin) return -1;
|
||||
System.out.println(this.viaAPI.getPlayerProtocolVersion(uuid).getVersion());
|
||||
return this.viaAPI.getPlayerProtocolVersion(uuid).getVersion();
|
||||
}
|
||||
|
||||
public boolean hasPlugin() {
|
||||
return hasPlugin;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerResourcePackStatusEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -55,16 +54,6 @@ public class BukkitPackManager extends AbstractPackManager implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onResourcePackStatus(PlayerResourcePackStatusEvent event) {
|
||||
// for 1.20.1 servers, not recommended to use
|
||||
if (Config.sendPackOnJoin() && Config.kickOnDeclined() && !VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED || event.getStatus() == PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD) {
|
||||
event.getPlayer().kick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (ReloadCommand.RELOAD_PACK_FLAG || CraftEngine.instance().isInitializing()) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.network;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
import io.netty.util.internal.logging.InternalLogger;
|
||||
import io.netty.util.internal.logging.InternalLoggerFactory;
|
||||
import net.momirealms.craftengine.bukkit.compatibility.viaversion.ViaVersionProtocol;
|
||||
import net.momirealms.craftengine.bukkit.nms.FastNMS;
|
||||
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
|
||||
import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20;
|
||||
@@ -16,10 +18,7 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetworkManager;
|
||||
import net.momirealms.craftengine.core.util.FriendlyByteBuf;
|
||||
import net.momirealms.craftengine.core.util.ListMonitor;
|
||||
import net.momirealms.craftengine.core.util.TriConsumer;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -57,6 +56,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
private final BiConsumer<Object, Object> packetConsumer;
|
||||
private final BiConsumer<Object, Object> immediatePacketConsumer;
|
||||
private final BukkitCraftEngine plugin;
|
||||
private final ViaVersionProtocol viaVersionProtocol;
|
||||
|
||||
private final Map<ChannelPipeline, BukkitServerPlayer> users = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, BukkitServerPlayer> onlineUsers = new ConcurrentHashMap<>();
|
||||
@@ -77,6 +77,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
instance = this;
|
||||
hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null;
|
||||
this.plugin = plugin;
|
||||
// hook via
|
||||
this.viaVersionProtocol = new ViaVersionProtocol(Bukkit.getPluginManager().getPlugin("ViaVersion") != null);
|
||||
// set up packet id
|
||||
this.packetIds = setupPacketIds();
|
||||
// register packet handlers
|
||||
@@ -101,6 +103,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
// set up mod channel
|
||||
this.plugin.bootstrap().getServer().getMessenger().registerIncomingPluginChannel(this.plugin.bootstrap(), MOD_CHANNEL, this);
|
||||
this.plugin.bootstrap().getServer().getMessenger().registerOutgoingPluginChannel(this.plugin.bootstrap(), MOD_CHANNEL);
|
||||
// 配置via频道
|
||||
this.plugin.bootstrap().getServer().getMessenger().registerIncomingPluginChannel(this.plugin.bootstrap(), VIA_CHANNEL, this);
|
||||
// Inject server channel
|
||||
try {
|
||||
Object server = Reflections.method$MinecraftServer$getServer.invoke(null);
|
||||
@@ -147,6 +151,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, Reflections.clazz$ServerboundEditBookPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD, Reflections.clazz$ServerboundCustomPayloadPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_PUSH, Reflections.clazz$ClientboundResourcePackPushPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.HANDSHAKE_C2S, Reflections.clazz$ClientIntentionPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.LOGIN_ACKNOWLEDGED, Reflections.clazz$ServerboundLoginAcknowledgedPacket);
|
||||
registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_RESPONSE, Reflections.clazz$ServerboundResourcePackPacket);
|
||||
registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket());
|
||||
registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket());
|
||||
registerByteBufPacketConsumer(VersionHelper.isVersionNewerThan1_21_3() ? PacketConsumers.LEVEL_PARTICLE_1_21_3 : (VersionHelper.isVersionNewerThan1_20_5() ? PacketConsumers.LEVEL_PARTICLE_1_20_5 : PacketConsumers.LEVEL_PARTICLE_1_20), this.packetIds.clientboundLevelParticlesPacket());
|
||||
@@ -202,9 +209,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
return this.onlineUserArray;
|
||||
}
|
||||
|
||||
// 保留仅注册入频道用
|
||||
@Override
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) {}
|
||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) {
|
||||
if (channel.equals(VIA_CHANNEL)) {
|
||||
BukkitServerPlayer user = plugin.adapt(player);
|
||||
if (user != null) {
|
||||
JsonObject payload = GsonHelper.get().fromJson(new String(message), JsonObject.class);
|
||||
int version = payload.get("version").getAsInt();
|
||||
user.setProtocolVersion(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
@@ -620,4 +635,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
public ViaVersionProtocol viaVersionProtocol() {
|
||||
return this.viaVersionProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent;
|
||||
import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent;
|
||||
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
|
||||
import net.momirealms.craftengine.bukkit.compatibility.modelengine.ModelEngineUtils;
|
||||
import net.momirealms.craftengine.bukkit.compatibility.viaversion.ViaVersionProtocol;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager;
|
||||
import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture;
|
||||
import net.momirealms.craftengine.bukkit.item.behavior.FurnitureItemBehavior;
|
||||
@@ -1248,6 +1249,10 @@ public class PacketConsumers {
|
||||
player.setConnectionState(ConnectionState.PLAY);
|
||||
Object dimensionKey;
|
||||
if (!VersionHelper.isVersionNewerThan1_20_2()) {
|
||||
ViaVersionProtocol viaVersionProtocol = BukkitNetworkManager.instance().viaVersionProtocol();
|
||||
if (viaVersionProtocol.hasPlugin()) {
|
||||
user.setProtocolVersion(viaVersionProtocol.getPlayerProtocolVersion(player.uuid()));
|
||||
}
|
||||
dimensionKey = Reflections.field$ClientboundLoginPacket$dimension.get(packet);
|
||||
} else {
|
||||
Object commonInfo = Reflections.field$ClientboundLoginPacket$commonPlayerSpawnInfo.get(packet);
|
||||
@@ -2178,4 +2183,46 @@ public class PacketConsumers {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundResourcePackPushPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> HANDSHAKE_C2S = (user, event, packet) -> {
|
||||
try {
|
||||
if (BukkitNetworkManager.instance().viaVersionProtocol().hasPlugin()) return;
|
||||
int protocolVersion = Reflections.field$ClientIntentionPacket$protocolVersion.getInt(packet);
|
||||
user.setProtocolVersion(protocolVersion);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientIntentionPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> LOGIN_ACKNOWLEDGED = (user, event, packet) -> {
|
||||
try {
|
||||
ViaVersionProtocol viaVersionProtocol = BukkitNetworkManager.instance().viaVersionProtocol();
|
||||
if (viaVersionProtocol.hasPlugin()) {
|
||||
user.setProtocolVersion(viaVersionProtocol.getPlayerProtocolVersion(user.uuid()));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundLoginAcknowledgedPacket", e);
|
||||
}
|
||||
};
|
||||
|
||||
public static final TriConsumer<NetWorkUser, NMSPacketEvent, Object> RESOURCE_PACK_RESPONSE = (user, event, packet) -> {
|
||||
try {
|
||||
if (user.sentResourcePack() || !Config.sendPackOnJoin() || !Config.kickOnDeclined()) return;
|
||||
Object action = Reflections.field$ServerboundResourcePackPacket$action.get(packet);
|
||||
if (action == null) return;
|
||||
if (action == Reflections.instance$ServerboundResourcePackPacket$Action$DECLINED
|
||||
|| action == Reflections.instance$ServerboundResourcePackPacket$Action$FAILED_DOWNLOAD) {
|
||||
Object kickPacket = Reflections.constructor$ClientboundDisconnectPacket.newInstance(
|
||||
ComponentUtils.adventureToMinecraft(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")));
|
||||
user.nettyChannel().writeAndFlush(kickPacket);
|
||||
user.nettyChannel().disconnect();
|
||||
return;
|
||||
}
|
||||
if (action == Reflections.instance$ServerboundResourcePackPacket$Action$SUCCESSFULLY_LOADED) {
|
||||
user.setSentResourcePack(true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ServerboundResourcePackPacket", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
|
||||
import net.momirealms.craftengine.core.plugin.network.ProtocolVersion;
|
||||
import net.momirealms.craftengine.core.util.Direction;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.VersionHelper;
|
||||
@@ -39,6 +40,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BukkitServerPlayer extends Player {
|
||||
private final BukkitCraftEngine plugin;
|
||||
// handshake
|
||||
private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN;
|
||||
// connection state
|
||||
private final Channel channel;
|
||||
private String name;
|
||||
@@ -46,6 +49,7 @@ public class BukkitServerPlayer extends Player {
|
||||
private ConnectionState decoderState;
|
||||
private ConnectionState encoderState;
|
||||
private final Set<UUID> resourcePackUUID = Collections.synchronizedSet(new HashSet<>());
|
||||
private boolean sentResourcePack = !Config.sendPackOnJoin();
|
||||
// some references
|
||||
private Reference<org.bukkit.entity.Player> playerRef;
|
||||
private Reference<Object> serverPlayerRef;
|
||||
@@ -758,6 +762,26 @@ public class BukkitServerPlayer extends Player {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtocolVersion protocolVersion() {
|
||||
return this.protocolVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = ProtocolVersion.getById(protocolVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sentResourcePack() {
|
||||
return this.sentResourcePack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSentResourcePack(boolean sentResourcePack) {
|
||||
this.sentResourcePack = sentResourcePack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearView() {
|
||||
this.entityTypeView.clear();
|
||||
|
||||
@@ -6499,12 +6499,33 @@ public class Reflections {
|
||||
)
|
||||
);
|
||||
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$SUCCESSFULLY_LOADED;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$DECLINED;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$FAILED_DOWNLOAD;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$ACCEPTED;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$DOWNLOADED;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$INVALID_URL;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$FAILED_RELOAD;
|
||||
public static final Object instance$ServerboundResourcePackPacket$Action$DISCARDED;
|
||||
|
||||
static {
|
||||
try {
|
||||
Object[] values = (Object[]) method$ServerboundResourcePackPacket$Action$values.invoke(null);
|
||||
instance$ServerboundResourcePackPacket$Action$SUCCESSFULLY_LOADED = values[0];
|
||||
instance$ServerboundResourcePackPacket$Action$DECLINED = values[1];
|
||||
instance$ServerboundResourcePackPacket$Action$FAILED_DOWNLOAD = values[2];
|
||||
instance$ServerboundResourcePackPacket$Action$ACCEPTED = values[3];
|
||||
if (VersionHelper.isVersionNewerThan1_20_3()) {
|
||||
instance$ServerboundResourcePackPacket$Action$DOWNLOADED = values[4];
|
||||
instance$ServerboundResourcePackPacket$Action$INVALID_URL = values[5];
|
||||
instance$ServerboundResourcePackPacket$Action$FAILED_RELOAD = values[6];
|
||||
instance$ServerboundResourcePackPacket$Action$DISCARDED = values[7];
|
||||
} else {
|
||||
instance$ServerboundResourcePackPacket$Action$DOWNLOADED = null;
|
||||
instance$ServerboundResourcePackPacket$Action$INVALID_URL = null;
|
||||
instance$ServerboundResourcePackPacket$Action$FAILED_RELOAD = null;
|
||||
instance$ServerboundResourcePackPacket$Action$DISCARDED = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -6521,4 +6542,29 @@ public class Reflections {
|
||||
"core.component.DataComponentType"
|
||||
)
|
||||
);
|
||||
|
||||
public static final Class<?> clazz$ClientIntentionPacket = requireNonNull(
|
||||
ReflectionUtils.getClazz(
|
||||
BukkitReflectionUtils.assembleMCClass("network.protocol.handshake.ClientIntentionPacket"),
|
||||
BukkitReflectionUtils.assembleMCClass("network.protocol.handshake.PacketHandshakingInSetProtocol")
|
||||
)
|
||||
);
|
||||
|
||||
public static final Field field$ClientIntentionPacket$protocolVersion = requireNonNull(
|
||||
ReflectionUtils.getDeclaredField(
|
||||
clazz$ClientIntentionPacket, int.class, VersionHelper.isVersionNewerThan1_20_2() ? 0 : 1
|
||||
)
|
||||
);
|
||||
|
||||
// 1.20.2+
|
||||
public static final Class<?> clazz$ServerboundLoginAcknowledgedPacket =
|
||||
ReflectionUtils.getClazz(
|
||||
BukkitReflectionUtils.assembleMCClass("network.protocol.login.ServerboundLoginAcknowledgedPacket")
|
||||
);
|
||||
|
||||
public static final Field field$ServerboundResourcePackPacket$action = requireNonNull(
|
||||
ReflectionUtils.getDeclaredField(
|
||||
clazz$ServerboundResourcePackPacket, clazz$ServerboundResourcePackPacket$Action, 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,4 +51,12 @@ public interface NetWorkUser {
|
||||
void setClientModState(boolean enable);
|
||||
|
||||
void addResourcePackUUID(UUID uuid);
|
||||
|
||||
ProtocolVersion protocolVersion();
|
||||
|
||||
void setProtocolVersion(int protocolVersion);
|
||||
|
||||
boolean sentResourcePack();
|
||||
|
||||
void setSentResourcePack(boolean sentResourcePack);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.List;
|
||||
|
||||
public interface NetworkManager extends Manageable {
|
||||
String MOD_CHANNEL = "craftengine:payload";
|
||||
String VIA_CHANNEL = "vv:proxy_details";
|
||||
|
||||
void setUser(Channel channel, NetWorkUser user);
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.momirealms.craftengine.core.plugin.network;
|
||||
|
||||
public enum ProtocolVersion {
|
||||
UNKNOWN(-1, "Unknown"),
|
||||
V1_20(763, "1.20"),
|
||||
V1_20_1(763, "1.20.1"),
|
||||
V1_20_2(764, "1.20.2"),
|
||||
V1_20_3(765, "1.20.3"),
|
||||
V1_20_4(765, "1.20.4"),
|
||||
V1_20_5(766, "1.20.5"),
|
||||
V1_20_6(766, "1.20.6"),
|
||||
V1_21(767, "1.21"),
|
||||
V1_21_1(767, "1.21.1"),
|
||||
V1_21_2(768, "1.21.2"),
|
||||
V1_21_3(768, "1.21.3"),
|
||||
V1_21_4(769, "1.21.4"),
|
||||
V1_21_5(770, "1.21.5");
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
|
||||
ProtocolVersion(int id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static ProtocolVersion getByName(String name) {
|
||||
for (ProtocolVersion version : values()) {
|
||||
if (version.getName().equals(name)) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public static ProtocolVersion getById(int id) {
|
||||
for (ProtocolVersion version : values()) {
|
||||
if (version.getId() == id) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.momirealms.craftengine.core.util;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.network.ProtocolVersion;
|
||||
|
||||
public class ProtocolVersionUtils {
|
||||
|
||||
public static boolean isVersionNewerThan(ProtocolVersion version, ProtocolVersion targetVersion) {
|
||||
return version.getId() >= targetVersion.getId();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user