Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5df8a1e8e7 | ||
|
|
a3d124b552 | ||
|
|
6d95af3963 | ||
|
|
c08a38ff21 | ||
|
|
4a804bae7c | ||
|
|
fd42c7eba6 | ||
|
|
1af86d1354 | ||
|
|
1b77e1c89f | ||
|
|
c42b9351b8 | ||
|
|
ab8c8cd7c8 | ||
|
|
83518b92fe | ||
|
|
1bf1e8981c | ||
|
|
b1f076936d | ||
|
|
319e793cd6 | ||
|
|
c9e1360951 | ||
|
|
c11ad7930f | ||
|
|
7b36a9de51 | ||
|
|
d5e9170109 | ||
|
|
47b447f195 | ||
|
|
e2d27df1a8 | ||
|
|
9a781d940f | ||
|
|
fed36c9487 | ||
|
|
463327c2a7 | ||
|
|
b0050d4a8d |
No files matched your search
@@ -1,4 +1,4 @@
|
||||
# ViaVersion 0.5.9
|
||||
# ViaVersion 0.6.2
|
||||
[](https://travis-ci.org/MylesIsCool/ViaVersion)
|
||||
**Allows the connection of 1.9 clients to 1.8**
|
||||
|
||||
@@ -40,6 +40,8 @@ Contributors:
|
||||
|
||||
**fillefilip8**
|
||||
|
||||
**lenis0012**
|
||||
|
||||
|
||||
|
||||
Releases / Dev Builds:
|
||||
@@ -55,6 +57,9 @@ You can find unofficial dev builds here:
|
||||
|
||||
**fillefilip8's Build Server:** http://build.fillecraft.com:8080/job/ViaVersion
|
||||
|
||||
**sgdc3's Build Server:** http://ci.xephi.fr/job/ViaVersion/
|
||||
**sgdc3's Maven:** http://ci.xephi.fr/plugin/repository/everything/
|
||||
|
||||
License:
|
||||
--------
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>us.myles</groupId>
|
||||
<artifactId>viaversion</artifactId>
|
||||
<version>0.6.0-SNAPSHOT</version>
|
||||
<version>0.6.4-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ViaVersion</name>
|
||||
|
||||
@@ -12,6 +12,9 @@ import us.myles.ViaVersion.packets.State;
|
||||
@Getter
|
||||
@Setter
|
||||
public class ConnectionInfo {
|
||||
private static final long IDLE_PACKET_DELAY = 50L; // Update every 50ms (20tps)
|
||||
private static final long IDLE_PACKET_LIMIT = 20; // Max 20 ticks behind
|
||||
|
||||
private final SocketChannel channel;
|
||||
private Object lastPacket;
|
||||
private java.util.UUID UUID;
|
||||
@@ -22,6 +25,7 @@ public class ConnectionInfo {
|
||||
private int entityID;
|
||||
private boolean active = true;
|
||||
private String username;
|
||||
private long nextIdlePacket = 0L;
|
||||
|
||||
public ConnectionInfo(SocketChannel socketChannel) {
|
||||
this.channel = socketChannel;
|
||||
@@ -33,6 +37,7 @@ public class ConnectionInfo {
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
this.nextIdlePacket = System.currentTimeMillis() + IDLE_PACKET_DELAY; // Update every 50 ticks
|
||||
}
|
||||
|
||||
public void sendRawPacket(final ByteBuf packet, boolean currentThread) {
|
||||
@@ -56,4 +61,10 @@ public class ConnectionInfo {
|
||||
public void closeWindow() {
|
||||
this.openWindow = null;
|
||||
}
|
||||
|
||||
public void incrementIdlePacket() {
|
||||
// Notify of next update
|
||||
// Allow a maximum lag spike of 1 second (20 ticks/updates)
|
||||
this.nextIdlePacket = Math.max(nextIdlePacket + IDLE_PACKET_DELAY, System.currentTimeMillis() - IDLE_PACKET_DELAY * IDLE_PACKET_LIMIT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package us.myles.ViaVersion;
|
||||
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import us.myles.ViaVersion.api.ViaVersion;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ViaIdleThread extends BukkitRunnable {
|
||||
private final Map<UUID, ConnectionInfo> portedPlayers;
|
||||
private final Class<?> idlePacketClass;
|
||||
|
||||
public ViaIdleThread(Map<UUID, ConnectionInfo> portedPlayers) {
|
||||
this.portedPlayers = portedPlayers;
|
||||
try {
|
||||
this.idlePacketClass = ReflectionUtil.nms("PacketPlayInFlying");
|
||||
} catch(ClassNotFoundException e) {
|
||||
throw new RuntimeException("Couldn't find player idle packet, help!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for(ConnectionInfo info : portedPlayers.values()) {
|
||||
long nextIdleUpdate = info.getNextIdlePacket();
|
||||
if(nextIdleUpdate <= System.currentTimeMillis()) {
|
||||
try {
|
||||
Object packet = idlePacketClass.newInstance();
|
||||
info.getChannel().pipeline().fireChannelRead(packet);
|
||||
} catch(InstantiationException | IllegalAccessException e) {
|
||||
System.out.println("Failed to create idle packet.");
|
||||
if(ViaVersion.getInstance().isDebug()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
info.incrementIdlePacket();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,12 @@ import us.myles.ViaVersion.handlers.ViaVersionInitializer;
|
||||
import us.myles.ViaVersion.listeners.CommandBlockListener;
|
||||
import us.myles.ViaVersion.update.UpdateListener;
|
||||
import us.myles.ViaVersion.update.UpdateUtil;
|
||||
import us.myles.ViaVersion.util.Configuration;
|
||||
import us.myles.ViaVersion.util.ListWrapper;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
@@ -36,6 +39,7 @@ import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
|
||||
@@ -64,7 +68,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
ViaVersion.setInstance(this);
|
||||
saveDefaultConfig();
|
||||
generateConfig();
|
||||
if (System.getProperty("ViaVersion") != null) {
|
||||
getLogger().severe("ViaVersion is already loaded, we don't support reloads. Please reboot if you wish to update.");
|
||||
getLogger().severe("Some features may not work.");
|
||||
@@ -73,6 +77,8 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
|
||||
getLogger().info("ViaVersion " + getDescription().getVersion() + " is now enabled, injecting. (Allows 1.8 to be accessed via 1.9)");
|
||||
injectPacketHandler();
|
||||
if (getConfig().getBoolean("simulate-pt", true))
|
||||
new ViaIdleThread(portedPlayers).runTaskTimerAsynchronously(this, 1L, 1L); // Updates player's idle status
|
||||
|
||||
if (getConfig().getBoolean("checkforupdates"))
|
||||
UpdateUtil.sendUpdateMessage(this);
|
||||
@@ -91,6 +97,28 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
getCommand("viaversion").setExecutor(new ViaVersionCommand(this));
|
||||
}
|
||||
|
||||
public void generateConfig() {
|
||||
File file = new File(getDataFolder(), "config.yml");
|
||||
if(file.exists()) {
|
||||
// Update config options
|
||||
Configuration oldConfig = new Configuration(file);
|
||||
oldConfig.reload(false); // Load current options from config
|
||||
file.delete(); // Delete old config
|
||||
saveDefaultConfig(); // Generate new config
|
||||
Configuration newConfig = new Configuration(file);
|
||||
newConfig.reload(true); // Load default options
|
||||
for(String key : oldConfig.getKeys(false)) {
|
||||
// Set option in new config if exists
|
||||
if(newConfig.contains(key)) {
|
||||
newConfig.set(key, oldConfig.get(key));
|
||||
}
|
||||
}
|
||||
newConfig.save();
|
||||
} else {
|
||||
saveDefaultConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public void injectPacketHandler() {
|
||||
try {
|
||||
Class<?> serverClazz = ReflectionUtil.nms("MinecraftServer");
|
||||
@@ -116,23 +144,28 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
if (connection != null) {
|
||||
for (Field field : connection.getClass().getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(connection);
|
||||
final Object value = field.get(connection);
|
||||
if (value instanceof List) {
|
||||
// Inject the list
|
||||
field.set(connection, new ListWrapper((List) value) {
|
||||
List wrapper = new ListWrapper((List) value) {
|
||||
@Override
|
||||
public void handleAdd(Object o) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
public synchronized void handleAdd(Object o) {
|
||||
synchronized (this) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
field.set(connection, wrapper);
|
||||
// Iterate through current list
|
||||
for (Object o : (List) value) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
} else {
|
||||
break; // not the right list.
|
||||
synchronized (wrapper) {
|
||||
for (Object o : (List) value) {
|
||||
if (o instanceof ChannelFuture) {
|
||||
inject((ChannelFuture) o);
|
||||
} else {
|
||||
break; // not the right list.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,11 +241,6 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
this.debug = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSyncedChunks() {
|
||||
return getConfig().getBoolean("sync-chunks", true);
|
||||
}
|
||||
|
||||
public boolean isPreventCollision() {
|
||||
return getConfig().getBoolean("prevent-collision", true);
|
||||
}
|
||||
@@ -221,6 +249,26 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
|
||||
return getConfig().getBoolean("suppress-metadata-errors", false);
|
||||
}
|
||||
|
||||
public boolean isShieldBlocking() {
|
||||
return getConfig().getBoolean("shield-blocking", true);
|
||||
}
|
||||
|
||||
public boolean isHologramPatch() {
|
||||
return getConfig().getBoolean("hologram-patch", false);
|
||||
}
|
||||
|
||||
public boolean isBossbarPatch() {
|
||||
return getConfig().getBoolean("bossbar-patch", true);
|
||||
}
|
||||
|
||||
public boolean isBossbarAntiflicker() {
|
||||
return getConfig().getBoolean("bossbar-anti-flicker", false);
|
||||
}
|
||||
|
||||
public double getHologramYOffset() {
|
||||
return getConfig().getDouble("hologram-y", -1D);
|
||||
}
|
||||
|
||||
public boolean isAutoTeam() {
|
||||
// Collision has to be enabled first
|
||||
if (!isPreventCollision()) return false;
|
||||
|
||||
@@ -77,11 +77,4 @@ public interface ViaVersionAPI {
|
||||
* @return true if debug is enabled
|
||||
*/
|
||||
boolean isDebug();
|
||||
|
||||
/**
|
||||
* Obtains if syncing chunks is on
|
||||
*
|
||||
* @return true if it is
|
||||
*/
|
||||
boolean isSyncedChunks();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package us.myles.ViaVersion.handlers;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
import us.myles.ViaVersion.ConnectionInfo;
|
||||
import us.myles.ViaVersion.util.ReflectionUtil;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
public class ViaChunkHandler extends MessageToMessageEncoder {
|
||||
private final ConnectionInfo info;
|
||||
|
||||
public ViaChunkHandler(ConnectionInfo info) {
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Object o, List list) throws Exception {
|
||||
// Split chunk bulk packet up in to single chunk packets before it reached the encoder.
|
||||
// This will prevent issues with several plugins and other protocol handlers due to the chunk being sent twice.
|
||||
// It also sends the chunk in the right order possible resolving some issues with added chunk/block/entity data.
|
||||
if (!(o instanceof ByteBuf)) {
|
||||
info.setLastPacket(o);
|
||||
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
||||
if(o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
||||
final int[] locX = ReflectionUtil.get(o, "a", int[].class);
|
||||
final int[] locZ = ReflectionUtil.get(o, "b", int[].class);
|
||||
final Object world = ReflectionUtil.get(o, "world", ReflectionUtil.nms("World"));
|
||||
Class<?> mapChunk = ReflectionUtil.nms("PacketPlayOutMapChunk");
|
||||
final Constructor constructor = mapChunk.getDeclaredConstructor(ReflectionUtil.nms("Chunk"), boolean.class, int.class);
|
||||
for(int i = 0; i < locX.length; i++) {
|
||||
int x = locX[i];
|
||||
int z = locZ[i];
|
||||
// world invoke function
|
||||
try {
|
||||
Object chunk = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class).invoke(world, x, z);
|
||||
Object packet = constructor.newInstance(chunk, true, 65535);
|
||||
list.add(packet);
|
||||
} catch(InstantiationException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
list.add(o);
|
||||
}
|
||||
}
|
||||
@@ -28,38 +28,6 @@ public class ViaEncodeHandler extends MessageToByteEncoder {
|
||||
protected void encode(final ChannelHandlerContext ctx, Object o, final ByteBuf bytebuf) throws Exception {
|
||||
// handle the packet type
|
||||
if (!(o instanceof ByteBuf)) {
|
||||
info.setLastPacket(o);
|
||||
/* This transformer is more for fixing issues which we find hard at packet level :) */
|
||||
if (o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk") && info.isActive()) {
|
||||
final int[] locX = ReflectionUtil.get(o, "a", int[].class);
|
||||
final int[] locZ = ReflectionUtil.get(o, "b", int[].class);
|
||||
|
||||
final Object world = ReflectionUtil.get(o, "world", ReflectionUtil.nms("World"));
|
||||
Class<?> mapChunk = ReflectionUtil.nms("PacketPlayOutMapChunk");
|
||||
final Constructor constructor = mapChunk.getDeclaredConstructor(ReflectionUtil.nms("Chunk"), boolean.class, int.class);
|
||||
Runnable chunks = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
for (int i = 0; i < locX.length; i++) {
|
||||
int x = locX[i];
|
||||
int z = locZ[i];
|
||||
// world invoke function
|
||||
try {
|
||||
Object chunk = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class).invoke(world, x, z);
|
||||
Object packet = constructor.newInstance(chunk, true, 65535);
|
||||
ctx.pipeline().writeAndFlush(packet);
|
||||
} catch (InstantiationException | InvocationTargetException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
chunks.run();
|
||||
bytebuf.clear();
|
||||
throw new CancelException();
|
||||
}
|
||||
// call minecraft encoder
|
||||
PacketUtil.callEncode(this.minecraftEncoder, ctx, o, bytebuf);
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ public class ViaVersionInitializer extends ChannelInitializer<SocketChannel> {
|
||||
// Add our transformers
|
||||
ViaEncodeHandler encoder = new ViaEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder"));
|
||||
ViaDecodeHandler decoder = new ViaDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder"));
|
||||
ViaChunkHandler chunkHandler = new ViaChunkHandler(info);
|
||||
|
||||
socketChannel.pipeline().replace("encoder", "encoder", encoder);
|
||||
socketChannel.pipeline().replace("decoder", "decoder", decoder);
|
||||
socketChannel.pipeline().addAfter("packet_handler", "viaversion_chunk_handler", chunkHandler);
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,8 @@ public enum MetaIndex {
|
||||
WITHER_TARGET2(Wither.class, 18, Type.Int, 12, NewType.VarInt),
|
||||
WITHER_TARGET3(Wither.class, 19, Type.Int, 13, NewType.VarInt),
|
||||
WITHER_INVULN_TIME(Wither.class, 20, Type.Int, 14, NewType.VarInt),
|
||||
WITHER_PROPERTIES(Wither.class, 10, Type.Byte, NewType.Byte),
|
||||
WITHER_UNKNOWN(Wither.class, 11, Type.Byte, NewType.Discontinued),
|
||||
// wither skull
|
||||
WITHERSKULL_INVULN(WitherSkull.class, 10, Type.Byte, 5, NewType.Boolean),
|
||||
// guardian
|
||||
@@ -123,7 +125,13 @@ public enum MetaIndex {
|
||||
ITEMFRAME_ITEM(ItemFrame.class, 8, Type.Slot, 5, NewType.Slot),
|
||||
ITEMFRAME_ROTATION(ItemFrame.class, 9, Type.Byte, 6, NewType.VarInt),
|
||||
// ender crystal
|
||||
ENDERCRYSTAL_HEALTH(EnderCrystal.class, 8, Type.Int, NewType.Discontinued),;
|
||||
ENDERCRYSTAL_HEALTH(EnderCrystal.class, 8, Type.Int, NewType.Discontinued),
|
||||
// Ender dragon boss bar issues
|
||||
ENDERDRAGON_UNKNOWN(EnderDragon.class, 5, Type.Byte, NewType.Discontinued),
|
||||
ENDERDRAGON_NAME(EnderDragon.class, 10, Type.String, NewType.Discontinued),
|
||||
// Normal Ender dragon
|
||||
ENDERDRAGON_FLAG(EnderDragon.class, 15, Type.Byte, NewType.Discontinued),
|
||||
ENDERDRAGON_PHASE(EnderDragon.class, 11, Type.Byte, NewType.VarInt);
|
||||
|
||||
private Class<?> clazz;
|
||||
private int newIndex;
|
||||
|
||||
@@ -207,7 +207,7 @@ public class MetadataRewriter {
|
||||
private MetaIndex index;
|
||||
private Object value;
|
||||
|
||||
private Entry(MetaIndex index, Object value, int id) {
|
||||
public Entry(MetaIndex index, Object value, int id) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.oldID = id;
|
||||
|
||||
@@ -16,6 +16,7 @@ import us.myles.ViaVersion.util.PacketUtil;
|
||||
import java.io.IOException;
|
||||
|
||||
public class IncomingTransformer {
|
||||
private final ViaVersionPlugin plugin = (ViaVersionPlugin) ViaVersion.getInstance();
|
||||
private final ConnectionInfo info;
|
||||
private boolean startedBlocking = false;
|
||||
|
||||
@@ -34,7 +35,7 @@ public class IncomingTransformer {
|
||||
if (packet.getPacketID() != -1) {
|
||||
packetID = packet.getPacketID();
|
||||
}
|
||||
if (ViaVersion.getInstance().isDebug()) {
|
||||
if (plugin.isDebug()) {
|
||||
if (packet != PacketType.PLAY_PLAYER_POSITION_LOOK_REQUEST && packet != PacketType.PLAY_KEEP_ALIVE_REQUEST && packet != PacketType.PLAY_PLAYER_POSITION_REQUEST && packet != PacketType.PLAY_PLAYER_LOOK_REQUEST) {
|
||||
System.out.println("Direction " + packet.getDirection().name() + " Packet Type: " + packet + " New ID: " + packetID + " Original: " + original + " Size: " + input.readableBytes());
|
||||
}
|
||||
@@ -42,6 +43,11 @@ public class IncomingTransformer {
|
||||
if (packet == PacketType.PLAY_TP_CONFIRM || packet == PacketType.PLAY_VEHICLE_MOVE_REQUEST) { //TODO handle client-sided horse riding
|
||||
throw new CancelException();
|
||||
}
|
||||
// Handle movement increment
|
||||
// Update idle status (player, position, look, positionandlook)
|
||||
if(packet == PacketType.PLAY_PLAYER || packet == PacketType.PLAY_PLAYER_POSITION_REQUEST || packet == PacketType.PLAY_PLAYER_LOOK_REQUEST || packet == PacketType.PLAY_PLAYER_POSITION_LOOK_REQUEST) {
|
||||
info.incrementIdlePacket();
|
||||
}
|
||||
PacketUtil.writeVarInt(packetID, output);
|
||||
if (packet == PacketType.HANDSHAKE) {
|
||||
int protVer = PacketUtil.readVarInt(input);
|
||||
@@ -267,7 +273,7 @@ public class IncomingTransformer {
|
||||
output.writeByte(face);
|
||||
PacketUtil.readVarInt(input);
|
||||
|
||||
ItemStack inHand = ViaVersionPlugin.getHandItem(info);
|
||||
ItemStack inHand = plugin.getHandItem(info);
|
||||
try {
|
||||
ItemSlotRewriter.ItemStack item = ItemSlotRewriter.ItemStack.fromBukkit(inHand);
|
||||
ItemSlotRewriter.fixIdsFrom1_9To1_8(item);
|
||||
@@ -291,8 +297,8 @@ public class IncomingTransformer {
|
||||
output.writeLong(-1L);
|
||||
output.writeByte(255);
|
||||
// write item in hand
|
||||
ItemStack inHand = ViaVersionPlugin.getHandItem(info);
|
||||
if (inHand != null) {
|
||||
ItemStack inHand = plugin.getHandItem(info);
|
||||
if (inHand != null && plugin.isShieldBlocking()) {
|
||||
if (inHand.getType().name().endsWith("SWORD")) {
|
||||
// blocking?
|
||||
if (hand == 0) {
|
||||
|
||||
@@ -15,7 +15,12 @@ import us.myles.ViaVersion.CancelException;
|
||||
import us.myles.ViaVersion.ConnectionInfo;
|
||||
import us.myles.ViaVersion.ViaVersionPlugin;
|
||||
import us.myles.ViaVersion.api.ViaVersion;
|
||||
import us.myles.ViaVersion.api.boss.BossBar;
|
||||
import us.myles.ViaVersion.api.boss.BossColor;
|
||||
import us.myles.ViaVersion.api.boss.BossStyle;
|
||||
import us.myles.ViaVersion.metadata.MetaIndex;
|
||||
import us.myles.ViaVersion.metadata.MetadataRewriter;
|
||||
import us.myles.ViaVersion.metadata.MetadataRewriter.Entry;
|
||||
import us.myles.ViaVersion.packets.PacketType;
|
||||
import us.myles.ViaVersion.packets.State;
|
||||
import us.myles.ViaVersion.slot.ItemSlotRewriter;
|
||||
@@ -39,6 +44,8 @@ public class OutgoingTransformer {
|
||||
private final Map<Integer, EntityType> clientEntityTypes = new HashMap<>();
|
||||
private final Map<Integer, Integer> vehicleMap = new HashMap<>();
|
||||
private final Set<Integer> validBlocking = new HashSet<>();
|
||||
private final Set<Integer> knownHolograms = new HashSet<>();
|
||||
private final Map<Integer, BossBar> bossBarMap = new HashMap<>();
|
||||
private boolean autoTeam = false;
|
||||
|
||||
public OutgoingTransformer(ConnectionInfo info) {
|
||||
@@ -216,13 +223,15 @@ public class OutgoingTransformer {
|
||||
return;
|
||||
}
|
||||
if (packet == PacketType.PLAY_ENTITY_TELEPORT) {
|
||||
// Port this so that it relative moves :P
|
||||
int id = PacketUtil.readVarInt(input);
|
||||
PacketUtil.writeVarInt(id, output);
|
||||
|
||||
int x = input.readInt();
|
||||
output.writeDouble(x / 32D);
|
||||
int y = input.readInt();
|
||||
if(plugin.isHologramPatch() & knownHolograms.contains(id)){
|
||||
y = (int) ((y) + (plugin.getHologramYOffset() * 32D));
|
||||
}
|
||||
output.writeDouble(y / 32D);
|
||||
int z = input.readInt();
|
||||
output.writeDouble(z / 32D);
|
||||
@@ -243,6 +252,9 @@ public class OutgoingTransformer {
|
||||
int x = input.readByte();
|
||||
output.writeShort(x * 128);
|
||||
int y = input.readByte();
|
||||
if(plugin.isHologramPatch() & knownHolograms.contains(id)){
|
||||
y = (int) ((y) + plugin.getHologramYOffset());
|
||||
}
|
||||
output.writeShort(y * 128);
|
||||
int z = input.readByte();
|
||||
output.writeShort(z * 128);
|
||||
@@ -263,12 +275,16 @@ public class OutgoingTransformer {
|
||||
short x = (short) (input.readByte());
|
||||
output.writeShort(x * 128);
|
||||
short y = (short) (input.readByte());
|
||||
if(plugin.isHologramPatch() & knownHolograms.contains(id)){
|
||||
y = (short) (y - 1);
|
||||
}
|
||||
output.writeShort(y * 128);
|
||||
short z = (short) (input.readByte());
|
||||
output.writeShort(z * 128);
|
||||
|
||||
boolean onGround = input.readBoolean();
|
||||
output.writeBoolean(onGround);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
@@ -372,7 +388,14 @@ public class OutgoingTransformer {
|
||||
int[] toDestroy = PacketUtil.readVarInts(count, input);
|
||||
for (int entityID : toDestroy) {
|
||||
clientEntityTypes.remove(entityID);
|
||||
knownHolograms.remove(entityID);
|
||||
PacketUtil.writeVarInt(entityID, output);
|
||||
|
||||
// Remvoe boss bar
|
||||
BossBar bar = bossBarMap.remove(entityID);
|
||||
if(bar != null) {
|
||||
bar.hide();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -821,6 +844,32 @@ public class OutgoingTransformer {
|
||||
for (MetadataRewriter.Entry entry : list) {
|
||||
handleMetadata(entityID, entry, type);
|
||||
}
|
||||
// Fix: wither (crash fix)
|
||||
if(type == EntityType.WITHER) {
|
||||
// Remove custom value if already exist
|
||||
Iterator<Entry> it = list.iterator();
|
||||
while(it.hasNext()) {
|
||||
Entry e = it.next();
|
||||
if(e.getOldID() == 10) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
list.add(new Entry(MetaIndex.WITHER_PROPERTIES, (byte) 0, 10));
|
||||
}
|
||||
// Fix: Dragon (crash fix)
|
||||
if(type == EntityType.ENDER_DRAGON) {
|
||||
// Remove custom value if already exist
|
||||
Iterator<Entry> it = list.iterator();
|
||||
while(it.hasNext()) {
|
||||
Entry e = it.next();
|
||||
if(e.getOldID() == 11) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
list.add(new Entry(MetaIndex.ENDERDRAGON_PHASE, (byte) 0, 11));
|
||||
}
|
||||
MetadataRewriter.writeMetadata1_9(type, list, output);
|
||||
}
|
||||
|
||||
@@ -830,17 +879,70 @@ public class OutgoingTransformer {
|
||||
if (entry.getOldID() == 0) {
|
||||
// Byte
|
||||
byte data = (byte) entry.getValue();
|
||||
|
||||
if ((data & 0x10) == 0x10) {
|
||||
if (validBlocking.contains(entityID)) {
|
||||
ItemSlotRewriter.ItemStack shield = new ItemSlotRewriter.ItemStack();
|
||||
shield.id = 442;
|
||||
shield.amount = 1;
|
||||
shield.data = 0;
|
||||
sendSecondHandItem(entityID, shield);
|
||||
if (entityID != info.getEntityID() && plugin.isShieldBlocking()) {
|
||||
if ((data & 0x10) == 0x10) {
|
||||
if (validBlocking.contains(entityID)) {
|
||||
ItemSlotRewriter.ItemStack shield = new ItemSlotRewriter.ItemStack();
|
||||
shield.id = 442;
|
||||
shield.amount = 1;
|
||||
shield.data = 0;
|
||||
sendSecondHandItem(entityID, shield);
|
||||
}
|
||||
} else {
|
||||
sendSecondHandItem(entityID, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(type == EntityType.ARMOR_STAND && plugin.isHologramPatch()){
|
||||
if(entry.getOldID() == 0){
|
||||
byte data = (byte) entry.getValue();
|
||||
if((data & 0x20) == 0x20){
|
||||
if(!knownHolograms.contains(entityID)) {
|
||||
knownHolograms.add(entityID);
|
||||
// Send movement
|
||||
ByteBuf buf = info.getChannel().alloc().buffer();
|
||||
PacketUtil.writeVarInt(PacketType.PLAY_ENTITY_RELATIVE_MOVE.getNewPacketID(), buf);
|
||||
PacketUtil.writeVarInt(entityID, buf);
|
||||
buf.writeShort(0);
|
||||
buf.writeShort((short) (128D * (plugin.getHologramYOffset() * 32D)));
|
||||
buf.writeShort(0);
|
||||
buf.writeBoolean(true);
|
||||
info.sendRawPacket(buf, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Boss bar
|
||||
if(plugin.isBossbarPatch()) {
|
||||
if (type == EntityType.ENDER_DRAGON || type == EntityType.WITHER) {
|
||||
if (entry.getOldID() == 2) {
|
||||
BossBar bar = bossBarMap.get(entityID);
|
||||
String title = (String) entry.getValue();
|
||||
title = title.isEmpty() ? (type == EntityType.ENDER_DRAGON ? "Ender Dragon" : "Wither") : title;
|
||||
if (bar == null) {
|
||||
bar = ViaVersion.getInstance().createBossBar(title, BossColor.PINK, BossStyle.SOLID);
|
||||
bossBarMap.put(entityID, bar);
|
||||
bar.addPlayer(info.getPlayer());
|
||||
bar.show();
|
||||
} else {
|
||||
bar.setTitle(title);
|
||||
}
|
||||
} else if (entry.getOldID() == 6 && !plugin.isBossbarAntiflicker()) { // If anti flicker is enabled, don't update health
|
||||
BossBar bar = bossBarMap.get(entityID);
|
||||
// Make health range between 0 and 1
|
||||
float maxHealth = type == EntityType.ENDER_DRAGON ? 200.0f : 300.0f;
|
||||
float health = Math.max(0.0f, Math.min(((float) entry.getValue()) / maxHealth, 1.0f));
|
||||
if (bar == null) {
|
||||
String title = type == EntityType.ENDER_DRAGON ? "Ender Dragon" : "Wither";
|
||||
bar = ViaVersion.getInstance().createBossBar(title, health, BossColor.PINK, BossStyle.SOLID);
|
||||
bossBarMap.put(entityID, bar);
|
||||
bar.addPlayer(info.getPlayer());
|
||||
bar.show();
|
||||
} else {
|
||||
bar.setHealth(health);
|
||||
}
|
||||
} else {
|
||||
sendSecondHandItem(entityID, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
package us.myles.ViaVersion.util;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Configuration extends YamlConfiguration {
|
||||
private List<String> mainHeader = Lists.newArrayList();
|
||||
private final Map<String, List<String>> headers = Maps.newConcurrentMap();
|
||||
private final File file;
|
||||
private boolean loadHeaders;
|
||||
|
||||
public Configuration(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the main header displayed at top of config.
|
||||
*
|
||||
* @param header header
|
||||
*/
|
||||
public void mainHeader(String... header) {
|
||||
mainHeader = Arrays.asList(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main header displayed at top of config.
|
||||
*
|
||||
* @return header
|
||||
*/
|
||||
public List<String> mainHeader() {
|
||||
return mainHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set option header.
|
||||
*
|
||||
* @param key of option (or section)
|
||||
* @param header of option (or section)
|
||||
*/
|
||||
public void header(String key, String... header) {
|
||||
// String value = Joiner.on('\n').join(header);
|
||||
headers.put(key, Arrays.asList(header));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get header of option
|
||||
*
|
||||
* @param key of option (or section)
|
||||
* @return Header
|
||||
*/
|
||||
public List<String> header(String key) {
|
||||
return headers.get(key);
|
||||
}
|
||||
|
||||
public <T> T get(String key, Class<T> type) {
|
||||
return type.cast(get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload config from file.
|
||||
*/
|
||||
public void reload() {
|
||||
reload(headers.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload config from file.
|
||||
*
|
||||
* @param loadHeaders Whether or not to load headers.
|
||||
*/
|
||||
public void reload(boolean loadHeaders) {
|
||||
this.loadHeaders = loadHeaders;
|
||||
try {
|
||||
load(file);
|
||||
} catch(Exception e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "failed to reload file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFromString(String contents) throws InvalidConfigurationException {
|
||||
if(!loadHeaders) {
|
||||
super.loadFromString(contents);
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder memoryData = new StringBuilder();
|
||||
|
||||
// Parse headers
|
||||
final int indentLength = options().indent();
|
||||
final String pathSeparator = Character.toString(options().pathSeparator());
|
||||
int currentIndents = 0;
|
||||
String key = "";
|
||||
List<String> headers = Lists.newArrayList();
|
||||
for(String line : contents.split("\n")) {
|
||||
if(line.isEmpty()) continue; // Skip empty lines
|
||||
int indent = getSuccessiveCharCount(line, ' ');
|
||||
String subline = indent > 0 ? line.substring(indent) : line;
|
||||
if(subline.startsWith("#")) {
|
||||
if(subline.startsWith("#>")) {
|
||||
String txt = subline.startsWith("#> ") ? subline.substring(3) : subline.substring(2);
|
||||
mainHeader.add(txt);
|
||||
continue; // Main header, handled by bukkit
|
||||
}
|
||||
|
||||
// Add header to list
|
||||
String txt = subline.startsWith("# ") ? subline.substring(2) : subline.substring(1);
|
||||
headers.add(txt);
|
||||
continue;
|
||||
}
|
||||
|
||||
int indents = indent / indentLength;
|
||||
if(indents <= currentIndents) {
|
||||
// Remove last section of key
|
||||
String[] array = key.split(Pattern.quote(pathSeparator));
|
||||
int backspace = currentIndents - indents + 1;
|
||||
key = join(array, options().pathSeparator(), 0, array.length - backspace);
|
||||
}
|
||||
|
||||
// Add new section to key
|
||||
String separator = key.length() > 0 ? pathSeparator : "";
|
||||
String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line;
|
||||
key += separator + lineKey.substring(indent);
|
||||
|
||||
currentIndents = indents;
|
||||
|
||||
memoryData.append(line).append('\n');
|
||||
if(!headers.isEmpty()) {
|
||||
this.headers.put(key, headers);
|
||||
headers = Lists.newArrayList();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse remaining text
|
||||
super.loadFromString(memoryData.toString());
|
||||
|
||||
// Clear bukkit header
|
||||
options().header(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config to file
|
||||
*/
|
||||
public void save() {
|
||||
if(headers.isEmpty() && mainHeader.isEmpty()) {
|
||||
try {
|
||||
super.save(file);
|
||||
} catch(IOException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Custom save
|
||||
final int indentLength = options().indent();
|
||||
final String pathSeparator = Character.toString(options().pathSeparator());
|
||||
String content = saveToString();
|
||||
StringBuilder fileData = new StringBuilder(buildHeader());
|
||||
int currentIndents = 0;
|
||||
String key = "";
|
||||
for(String h : mainHeader) {
|
||||
// Append main header to top of file
|
||||
fileData.append("#> ").append(h).append('\n');
|
||||
}
|
||||
|
||||
for(String line : content.split("\n")) {
|
||||
if(line.isEmpty()) continue; // Skip empty lines
|
||||
int indent = getSuccessiveCharCount(line, ' ');
|
||||
int indents = indent / indentLength;
|
||||
String indentText = indent > 0 ? line.substring(0, indent) : "";
|
||||
if(indents <= currentIndents) {
|
||||
// Remove last section of key
|
||||
String[] array = key.split(Pattern.quote(pathSeparator));
|
||||
int backspace = currentIndents - indents + 1;
|
||||
key = join(array, options().pathSeparator(), 0, array.length - backspace);
|
||||
}
|
||||
|
||||
// Add new section to key
|
||||
String separator = key.length() > 0 ? pathSeparator : "";
|
||||
String lineKey = line.contains(":") ? line.split(Pattern.quote(":"))[0] : line;
|
||||
key += separator + lineKey.substring(indent);
|
||||
|
||||
currentIndents = indents;
|
||||
|
||||
List<String> header = headers.get(key);
|
||||
String headerText = header != null ? addHeaderTags(header, indentText) : "";
|
||||
fileData.append(headerText).append(line).append('\n');
|
||||
}
|
||||
|
||||
// Write data to file
|
||||
FileWriter writer = null;
|
||||
try {
|
||||
writer = new FileWriter(file);
|
||||
writer.write(fileData.toString());
|
||||
writer.flush();
|
||||
} catch(IOException e) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "Failed to save file", e);
|
||||
} finally {
|
||||
if(writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch(IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String addHeaderTags(List<String> header, String indent) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for(String line : header) {
|
||||
builder.append(indent).append("# ").append(line).append('\n');
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String join(String[] array, char joinChar, int start, int length) {
|
||||
String[] copy = new String[length - start];
|
||||
System.arraycopy(array, start, copy, 0, length - start);
|
||||
return Joiner.on(joinChar).join(copy);
|
||||
}
|
||||
|
||||
private int getSuccessiveCharCount(String text, char key) {
|
||||
int count = 0;
|
||||
for(int i = 0; i < text.length(); i++) {
|
||||
if(text.charAt(i) == key) {
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -15,43 +15,43 @@ public abstract class ListWrapper implements List {
|
||||
public abstract void handleAdd(Object o);
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
public synchronized int size() {
|
||||
return this.list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
public synchronized boolean isEmpty() {
|
||||
return this.list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
public synchronized boolean contains(Object o) {
|
||||
return this.list.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator iterator() {
|
||||
public synchronized Iterator iterator() {
|
||||
return this.list.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
public synchronized Object[] toArray() {
|
||||
return this.list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Object o) {
|
||||
public synchronized boolean add(Object o) {
|
||||
handleAdd(o);
|
||||
return this.list.add(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
public synchronized boolean remove(Object o) {
|
||||
return this.list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection c) {
|
||||
public synchronized boolean addAll(Collection c) {
|
||||
for (Object o : c) {
|
||||
handleAdd(o);
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public abstract class ListWrapper implements List {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection c) {
|
||||
public synchronized boolean addAll(int index, Collection c) {
|
||||
for (Object o : c) {
|
||||
handleAdd(o);
|
||||
}
|
||||
@@ -67,72 +67,72 @@ public abstract class ListWrapper implements List {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
public synchronized void clear() {
|
||||
this.list.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(int index) {
|
||||
public synchronized Object get(int index) {
|
||||
return this.list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object set(int index, Object element) {
|
||||
public synchronized Object set(int index, Object element) {
|
||||
return this.list.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, Object element) {
|
||||
public synchronized void add(int index, Object element) {
|
||||
this.list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(int index) {
|
||||
public synchronized Object remove(int index) {
|
||||
return this.list.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
public synchronized int indexOf(Object o) {
|
||||
return this.list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
public synchronized int lastIndexOf(Object o) {
|
||||
return this.list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator listIterator() {
|
||||
public synchronized ListIterator listIterator() {
|
||||
return this.list.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator listIterator(int index) {
|
||||
return this.listIterator(index);
|
||||
public synchronized ListIterator listIterator(int index) {
|
||||
return this.list.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List subList(int fromIndex, int toIndex) {
|
||||
public synchronized List subList(int fromIndex, int toIndex) {
|
||||
return this.list.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection c) {
|
||||
public synchronized boolean retainAll(Collection c) {
|
||||
return this.list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection c) {
|
||||
public synchronized boolean removeAll(Collection c) {
|
||||
return this.list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection c) {
|
||||
public synchronized boolean containsAll(Collection c) {
|
||||
return this.list.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray(Object[] a) {
|
||||
public synchronized Object[] toArray(Object[] a) {
|
||||
return this.list.toArray(a);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,22 @@
|
||||
# Should ViaVersion check for updates?
|
||||
checkforupdates: true
|
||||
# Should we send any bulk chunks, in sync (may be slower but fixes timings)
|
||||
# Currently broken due to sign updates, possibly will return
|
||||
sync-chunks: true
|
||||
# No collide options, these allow you to configure how collision works.
|
||||
# Do you want us to prevent collision?
|
||||
prevent-collision: true
|
||||
# If the above is true, should we automatically team players until you do?
|
||||
auto-team: true
|
||||
# When enabled if certain metadata can't be read we won't tell you about it
|
||||
suppress-metadata-errors: false
|
||||
suppress-metadata-errors: false
|
||||
# When enabled 1.9 will be able to block by using shields
|
||||
shield-blocking: true
|
||||
# Should we enable our hologram patch?
|
||||
# If they're in the wrong place enable this
|
||||
hologram-patch: false
|
||||
# This is the offset, should work as default when enabled.
|
||||
hologram-y: -1
|
||||
# Enable player tick simulation, this fixes eating, drinking, nether portals.
|
||||
simulate-pt: true
|
||||
# Should we patch boss bars so they work? (Default: true, disable if you're having issues)
|
||||
bossbar-patch: true
|
||||
# If your boss bar flickers on 1.9, set this to 'true'. It will keep all boss bars on 100% (not recommended)
|
||||
bossbar-anti-flicker: false
|
||||
Reference in new issue
Block a user