mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Merge remote-tracking branch 'upstream/master' into feature/1.21.6
# Conflicts: # README.md # core/src/main/java/org/geysermc/geyser/network/GameProtocol.java # core/src/main/java/org/geysermc/geyser/network/UpstreamPacketHandler.java # core/src/main/java/org/geysermc/geyser/session/GeyserSession.java # core/src/main/java/org/geysermc/geyser/session/cache/tags/GeyserHolderSet.java # core/src/main/resources/bedrock/entity_identifiers.dat # gradle.properties
This commit is contained in:
@@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
## Supported Versions
|
||||
Geyser is currently supporting Minecraft Bedrock 1.21.50 - 1.21.80 and Minecraft Java 1.21.6. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
Geyser is currently supporting Minecraft Bedrock 1.21.50 - 1.21.90 and Minecraft Java 1.21.6. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
|
||||
|
||||
@@ -113,4 +113,14 @@ public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent {
|
||||
* @since 2.1.1
|
||||
*/
|
||||
public abstract boolean unregister(@NonNull UUID uuid);
|
||||
|
||||
/**
|
||||
* Whether to forcefully disable vibrant visuals for joining clients.
|
||||
* While vibrant visuals are nice to look at, they can cause issues with
|
||||
* some resource packs.
|
||||
*
|
||||
* @param enabled Whether vibrant visuals are allowed. This is true by default.
|
||||
* @since 2.7.2
|
||||
*/
|
||||
public abstract void allowVibrantVisuals(boolean enabled);
|
||||
}
|
||||
|
||||
@@ -479,6 +479,8 @@ public class GeyserImpl implements GeyserApi, EventRegistrar {
|
||||
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::platformName));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("javaHaProxyProtocol", () -> String.valueOf(config.getRemote().isUseProxyProtocol())));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("bedrockHaProxyProtocol", () -> String.valueOf(config.getBedrock().isEnableProxyProtocol())));
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (GeyserSession session : sessionManager.getAllSessions()) {
|
||||
|
||||
@@ -99,6 +99,9 @@ public class CommandRegistry implements EventRegistrar {
|
||||
|
||||
private static final String GEYSER_ROOT_PERMISSION = "geyser.command";
|
||||
|
||||
public final static boolean STANDALONE_COMMAND_MANAGER = GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE ||
|
||||
GeyserImpl.getInstance().getPlatformType() == PlatformType.VIAPROXY;
|
||||
|
||||
protected final GeyserImpl geyser;
|
||||
private final CommandManager<GeyserCommandSource> cloud;
|
||||
private final boolean applyRootPermission;
|
||||
@@ -279,12 +282,15 @@ public class CommandRegistry implements EventRegistrar {
|
||||
|
||||
cloud.command(builder.handler(context -> {
|
||||
GeyserCommandSource source = context.sender();
|
||||
if (!source.hasPermission(help.permission())) {
|
||||
// delegate if possible - otherwise we have nothing else to offer the user.
|
||||
source.sendLocaleString(ExceptionHandlers.PERMISSION_FAIL_LANG_KEY);
|
||||
return;
|
||||
}
|
||||
if (source.hasPermission(help.permission())) {
|
||||
// Delegate to help if possible
|
||||
help.execute(source);
|
||||
} else if (STANDALONE_COMMAND_MANAGER && source instanceof GeyserSession session) {
|
||||
// If we are on an appropriate platform, forward the command to the backend
|
||||
session.sendCommand(context.rawInput().input());
|
||||
} else {
|
||||
source.sendLocaleString(ExceptionHandlers.PERMISSION_FAIL_LANG_KEY);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,12 @@ package org.geysermc.geyser.command;
|
||||
import io.leangen.geantyref.GenericTypeReflector;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
import org.incendo.cloud.CommandManager;
|
||||
import org.incendo.cloud.context.CommandContext;
|
||||
import org.incendo.cloud.exception.ArgumentParseException;
|
||||
import org.incendo.cloud.exception.CommandExecutionException;
|
||||
import org.incendo.cloud.exception.InvalidCommandSenderException;
|
||||
@@ -71,36 +73,51 @@ final class ExceptionHandlers {
|
||||
controller.clearHandlers();
|
||||
|
||||
registerExceptionHandler(InvalidSyntaxException.class,
|
||||
(src, e) -> src.sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax()));
|
||||
(ctx, e) -> ctx.sender().sendLocaleString("geyser.command.invalid_syntax", e.correctSyntax()));
|
||||
|
||||
registerExceptionHandler(InvalidCommandSenderException.class, (src, e) -> {
|
||||
registerExceptionHandler(InvalidCommandSenderException.class, (ctx, e) -> {
|
||||
// We currently don't use cloud sender type requirements anywhere.
|
||||
// This can be implemented better in the future if necessary.
|
||||
Type type = e.requiredSenderTypes().iterator().next(); // just grab the first
|
||||
String typeString = GenericTypeReflector.getTypeName(type);
|
||||
src.sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), typeString);
|
||||
ctx.sender().sendLocaleString("geyser.command.invalid_sender", e.commandSender().getClass().getSimpleName(), typeString);
|
||||
});
|
||||
|
||||
registerExceptionHandler(NoPermissionException.class, ExceptionHandlers::handleNoPermission);
|
||||
|
||||
registerExceptionHandler(NoSuchCommandException.class,
|
||||
(src, e) -> src.sendLocaleString("geyser.command.not_found"));
|
||||
(ctx, e) -> {
|
||||
// Let backend server receive & handle the command
|
||||
if (CommandRegistry.STANDALONE_COMMAND_MANAGER && ctx.sender() instanceof GeyserSession session) {
|
||||
session.sendCommand(ctx.rawInput().input());
|
||||
} else {
|
||||
ctx.sender().sendLocaleString("geyser.command.not_found");
|
||||
}
|
||||
});
|
||||
|
||||
registerExceptionHandler(ArgumentParseException.class,
|
||||
(src, e) -> src.sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage()));
|
||||
(ctx, e) -> ctx.sender().sendLocaleString("geyser.command.invalid_argument", e.getCause().getMessage()));
|
||||
|
||||
registerExceptionHandler(CommandExecutionException.class,
|
||||
(src, e) -> handleUnexpectedThrowable(src, e.getCause()));
|
||||
(ctx, e) -> handleUnexpectedThrowable(ctx.sender(), e.getCause()));
|
||||
|
||||
registerExceptionHandler(Throwable.class,
|
||||
(src, e) -> handleUnexpectedThrowable(src, e.getCause()));
|
||||
(ctx, e) -> handleUnexpectedThrowable(ctx.sender(), e.getCause()));
|
||||
}
|
||||
|
||||
private <E extends Throwable> void registerExceptionHandler(Class<E> type, BiConsumer<GeyserCommandSource, E> handler) {
|
||||
controller.registerHandler(type, context -> handler.accept(context.context().sender(), context.exception()));
|
||||
private <E extends Throwable> void registerExceptionHandler(Class<E> type, BiConsumer<CommandContext<GeyserCommandSource>, E> handler) {
|
||||
controller.registerHandler(type, context -> handler.accept(context.context(), context.exception()));
|
||||
}
|
||||
|
||||
private static void handleNoPermission(CommandContext<GeyserCommandSource> context, NoPermissionException exception) {
|
||||
GeyserCommandSource source = context.sender();
|
||||
|
||||
// Let backend server receive & handle the command
|
||||
if (CommandRegistry.STANDALONE_COMMAND_MANAGER && source instanceof GeyserSession session) {
|
||||
session.sendCommand(context.rawInput().input());
|
||||
return;
|
||||
}
|
||||
|
||||
private static void handleNoPermission(GeyserCommandSource source, NoPermissionException exception) {
|
||||
// custom handling if the source can't use the command because of additional requirements
|
||||
if (exception.permissionResult() instanceof GeyserPermission.Result result) {
|
||||
if (result.meta() == GeyserPermission.Result.Meta.NOT_BEDROCK) {
|
||||
|
||||
@@ -56,6 +56,8 @@ public class AreaEffectCloudEntity extends Entity {
|
||||
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_RADIUS, 3.0f);
|
||||
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, Float.MIN_VALUE);
|
||||
|
||||
//noinspection deprecation - still needed for these to show up
|
||||
dirtyMetadata.put(EntityDataTypes.AREA_EFFECT_CLOUD_CHANGE_RATE, Float.MIN_VALUE);
|
||||
setFlag(EntityFlag.FIRE_IMMUNE, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket;
|
||||
@@ -42,7 +43,12 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponen
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class ThrowableEggEntity extends ThrowableItemEntity {
|
||||
|
||||
// Used for egg break particles
|
||||
private GeyserItemStack itemStack;
|
||||
|
||||
public ThrowableEggEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
@@ -58,6 +64,7 @@ public class ThrowableEggEntity extends ThrowableItemEntity {
|
||||
GeyserItemStack stack = GeyserItemStack.from(entityMetadata.getValue());
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, getVariantOrFallback(session, stack));
|
||||
updateBedrockEntityProperties();
|
||||
this.itemStack = stack;
|
||||
}
|
||||
|
||||
private static String getVariantOrFallback(GeyserSession session, GeyserItemStack stack) {
|
||||
@@ -71,6 +78,6 @@ public class ThrowableEggEntity extends ThrowableItemEntity {
|
||||
}
|
||||
}
|
||||
|
||||
return TemperatureVariantAnimal.BuiltInVariant.TEMPERATE.name().toLowerCase(Locale.ROOT);
|
||||
return TemperatureVariantAnimal.BuiltInVariant.TEMPERATE.toBedrock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +54,17 @@ public abstract class TemperatureVariantAnimal extends AnimalEntity implements V
|
||||
|
||||
@Override
|
||||
public void setBedrockVariant(BuiltInVariant variant) {
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, variant.name().toLowerCase(Locale.ROOT));
|
||||
propertyManager.add(VanillaEntityProperties.CLIMATE_VARIANT_ID, variant.toBedrock());
|
||||
updateBedrockEntityProperties();
|
||||
}
|
||||
|
||||
public enum BuiltInVariant implements VariantHolder.BuiltIn {
|
||||
COLD,
|
||||
TEMPERATE,
|
||||
WARM
|
||||
WARM;
|
||||
|
||||
public String toBedrock() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,11 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
*/
|
||||
private final Map<UUID, OptionHolder> sessionPackOptionOverrides;
|
||||
|
||||
private final GeyserSession session;
|
||||
|
||||
public SessionLoadResourcePacksEventImpl(GeyserSession session) {
|
||||
super(session);
|
||||
this.session = session;
|
||||
this.packs = new Object2ObjectLinkedOpenHashMap<>(Registries.RESOURCE_PACKS.get());
|
||||
this.sessionPackOptionOverrides = new Object2ObjectOpenHashMap<>();
|
||||
}
|
||||
@@ -160,6 +163,11 @@ public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksE
|
||||
return packs.remove(uuid) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void allowVibrantVisuals(boolean enabled) {
|
||||
session.setAllowVibrantVisuals(enabled);
|
||||
}
|
||||
|
||||
private void attemptRegisterOptions(@NonNull GeyserResourcePack pack, @Nullable ResourcePackOption<?>... options) {
|
||||
if (options == null) {
|
||||
return;
|
||||
|
||||
@@ -139,8 +139,8 @@ public abstract class Inventory {
|
||||
public abstract int getOffsetForHotbar(@Range(from = 0, to = 8) int slot);
|
||||
|
||||
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
|
||||
if (slot > this.size) {
|
||||
session.getGeyser().getLogger().debug("Tried to set an item out of bounds! " + this);
|
||||
if (slot < 0 || slot >= this.size) {
|
||||
session.getGeyser().getLogger().debug("Tried to set an item out of bounds (slot was " + slot + ")! " + this);
|
||||
return;
|
||||
}
|
||||
GeyserItemStack oldItem = items[slot];
|
||||
|
||||
@@ -160,8 +160,10 @@ public final class InventoryHolder<T extends Inventory> {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InventoryHolder[" +
|
||||
"session=" + session + ", " +
|
||||
"session=" + session.bedrockUsername() + ", " +
|
||||
"inventory=" + inventory + ", " +
|
||||
"translator=" + translator + ']';
|
||||
"pending= " + pending + ", " +
|
||||
"containerOpenAttempts=" + containerOpenAttempts + ", " +
|
||||
"translator=" + translator.getClass().getSimpleName() + ']';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ public class StoredItemMappings {
|
||||
private final ItemMapping carrotOnAStick;
|
||||
private final ItemMapping compass;
|
||||
private final ItemMapping crossbow;
|
||||
private final ItemMapping egg;
|
||||
private final ItemMapping glassBottle;
|
||||
private final ItemMapping milkBucket;
|
||||
private final ItemMapping powderSnowBucket;
|
||||
@@ -65,7 +64,6 @@ public class StoredItemMappings {
|
||||
this.carrotOnAStick = load(itemMappings, Items.CARROT_ON_A_STICK);
|
||||
this.compass = load(itemMappings, Items.COMPASS);
|
||||
this.crossbow = load(itemMappings, Items.CROSSBOW);
|
||||
this.egg = load(itemMappings, Items.EGG);
|
||||
this.glassBottle = load(itemMappings, Items.GLASS_BOTTLE);
|
||||
this.milkBucket = load(itemMappings, Items.MILK_BUCKET);
|
||||
this.powderSnowBucket = load(itemMappings, Items.POWDER_SNOW_BUCKET);
|
||||
|
||||
@@ -259,8 +259,6 @@ class CodecProcessor {
|
||||
.updateSerializer(CreatePhotoPacket.class, ILLEGAL_SERIALIZER)
|
||||
.updateSerializer(NpcRequestPacket.class, ILLEGAL_SERIALIZER)
|
||||
.updateSerializer(PhotoInfoRequestPacket.class, ILLEGAL_SERIALIZER)
|
||||
// Unused serverbound packets for featured servers, which is for some reason still occasionally sent
|
||||
.updateSerializer(PurchaseReceiptPacket.class, IGNORED_SERIALIZER)
|
||||
// Illegal unused serverbound packets that are deprecated
|
||||
.updateSerializer(ClientCheatAbilityPacket.class, ILLEGAL_SERIALIZER)
|
||||
.updateSerializer(CraftingEventPacket.class, ILLEGAL_SERIALIZER)
|
||||
@@ -276,7 +274,6 @@ class CodecProcessor {
|
||||
.updateSerializer(MapInfoRequestPacket.class, IGNORED_SERIALIZER)
|
||||
.updateSerializer(SettingsCommandPacket.class, IGNORED_SERIALIZER)
|
||||
.updateSerializer(AnvilDamagePacket.class, IGNORED_SERIALIZER)
|
||||
.updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER)
|
||||
// Illegal when serverbound due to Geyser specific setup
|
||||
.updateSerializer(InventoryContentPacket.class, INVENTORY_CONTENT_SERIALIZER_V748)
|
||||
.updateSerializer(InventorySlotPacket.class, INVENTORY_SLOT_SERIALIZER_V748)
|
||||
@@ -308,6 +305,11 @@ class CodecProcessor {
|
||||
.updateSerializer(PlayerInputPacket.class, ILLEGAL_SERIALIZER);
|
||||
}
|
||||
|
||||
if (!Boolean.getBoolean("Geyser.ReceiptPackets")) {
|
||||
codecBuilder.updateSerializer(RefreshEntitlementsPacket.class, IGNORED_SERIALIZER);
|
||||
codecBuilder.updateSerializer(PurchaseReceiptPacket.class, IGNORED_SERIALIZER);
|
||||
}
|
||||
|
||||
return codecBuilder.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,11 @@ package org.geysermc.geyser.network;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v766.Bedrock_v766;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v776.Bedrock_v776;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec;
|
||||
@@ -47,8 +50,8 @@ public final class GameProtocol {
|
||||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
*/
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v800.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.80")
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v818.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.90")
|
||||
.build());
|
||||
|
||||
/**
|
||||
@@ -63,15 +66,18 @@ public final class GameProtocol {
|
||||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
//SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v766.CODEC.toBuilder()
|
||||
// .minecraftVersion("1.21.50 - 1.21.51")
|
||||
// .build()));
|
||||
//SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v776.CODEC.toBuilder()
|
||||
// .minecraftVersion("1.21.60 - 1.21.62")
|
||||
// .build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v766.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.50 - 1.21.51")
|
||||
.build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v776.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.60 - 1.21.62")
|
||||
.build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v786.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.70 - 1.21.73")
|
||||
.build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add(CodecProcessor.processCodec(Bedrock_v800.CODEC.toBuilder()
|
||||
.minecraftVersion("1.21.80 - 1.21.84")
|
||||
.build()));
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
}
|
||||
|
||||
@@ -107,8 +113,8 @@ public final class GameProtocol {
|
||||
return session.protocolVersion() >= Bedrock_v800.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
public static boolean is1_21_80(GeyserSession session) {
|
||||
return session.protocolVersion() == Bedrock_v800.CODEC.getProtocolVersion();
|
||||
public static boolean is1_21_90orHigher(GeyserSession session) {
|
||||
return session.protocolVersion() >= Bedrock_v818.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -208,6 +208,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||
resourcePacksInfo.getResourcePackInfos().addAll(this.resourcePackLoadEvent.infoPacketEntries());
|
||||
resourcePacksInfo.setVibrantVisualsForceDisabled(!session.isAllowVibrantVisuals());
|
||||
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
||||
resourcePacksInfo.setWorldTemplateId(UUID.randomUUID());
|
||||
@@ -241,11 +242,13 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
stackPacket.setGameVersion(session.getClientData().getGameVersion());
|
||||
stackPacket.getResourcePacks().addAll(this.resourcePackLoadEvent.orderedPacks());
|
||||
// Allows Vibrant Visuals to be toggled in the settings
|
||||
if (session.isAllowVibrantVisuals() && !GameProtocol.is1_21_90orHigher(session)) {
|
||||
stackPacket.getExperiments().add(new ExperimentData("experimental_graphics", true));
|
||||
// Enables 2025 Content Drop 2 features
|
||||
stackPacket.getExperiments().add(new ExperimentData("y_2025_drop_2", true));
|
||||
}
|
||||
|
||||
if (GameProtocol.is1_21_80(session)) {
|
||||
// Support happy ghasts in .80
|
||||
stackPacket.getExperiments().add(new ExperimentData("y_2025_drop_2", true));
|
||||
// Enables the locator bar for 1.21.80 clients
|
||||
stackPacket.getExperiments().add(new ExperimentData("locator_bar", true));
|
||||
}
|
||||
|
||||
@@ -73,17 +73,11 @@ public class GeyserUrlPackCodec extends UrlPackCodec {
|
||||
@Override
|
||||
protected GeyserResourcePack.@NonNull Builder createBuilder() {
|
||||
if (this.fallback == null) {
|
||||
try {
|
||||
ResourcePackLoader.downloadPack(url, false).whenComplete((pack, throwable) -> {
|
||||
if (throwable != null) {
|
||||
throw new IllegalArgumentException(throwable);
|
||||
} else if (pack != null) {
|
||||
this.fallback = pack;
|
||||
}
|
||||
ResourcePackLoader.downloadPack(url, false)
|
||||
.thenAccept(pack -> this.fallback = pack)
|
||||
.exceptionally(throwable -> {
|
||||
throw new IllegalStateException(throwable.getCause());
|
||||
}).join(); // Needed to ensure that we don't attempt to read a pack before downloading/checking it
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to download pack from the url %s (%s)!".formatted(url, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
return ResourcePackLoader.readPack(this);
|
||||
|
||||
@@ -82,6 +82,11 @@ public class BlockRegistries {
|
||||
*/
|
||||
public static final MappedRegistry<String, Integer, Object2IntMap<String>> JAVA_IDENTIFIER_TO_ID = MappedRegistry.create(RegistryLoaders.empty(Object2IntOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A registry containing non-vanilla block IDS.
|
||||
*/
|
||||
public static final SimpleRegistry<BitSet> NON_VANILLA_BLOCK_IDS = SimpleRegistry.create(RegistryLoaders.empty(BitSet::new));
|
||||
|
||||
/**
|
||||
* A registry containing all the waterlogged blockstates.
|
||||
* Properties.WATERLOGGED should not be relied on for two reasons:
|
||||
|
||||
@@ -213,7 +213,6 @@ public final class Registries {
|
||||
PARTICLES.load();
|
||||
// load potion mixes later
|
||||
//RECIPES.load();
|
||||
RESOURCE_PACKS.load();
|
||||
SOUNDS.load();
|
||||
SOUND_LEVEL_EVENTS.load();
|
||||
SOUND_TRANSLATORS.load();
|
||||
|
||||
@@ -28,7 +28,7 @@ package org.geysermc.geyser.registry.loader;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
@@ -61,6 +61,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -288,10 +289,6 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
}
|
||||
}
|
||||
|
||||
if (pathPackCodec == null) {
|
||||
return; // Already warned about
|
||||
}
|
||||
|
||||
GeyserResourcePack newPack = readPack(pathPackCodec.path()).build();
|
||||
if (newPack.uuid().equals(packId)) {
|
||||
if (packVersion.equals(newPack.manifest().header().version().toString())) {
|
||||
@@ -337,13 +334,13 @@ public class ResourcePackLoader implements RegistryLoader<Path, Map<UUID, Resour
|
||||
}
|
||||
}
|
||||
|
||||
public static CompletableFuture<@Nullable PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
|
||||
public static CompletableFuture<@NonNull PathPackCodec> downloadPack(String url, boolean testing) throws IllegalArgumentException {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Path path = WebUtils.downloadRemotePack(url, testing);
|
||||
|
||||
// Already warned about these above
|
||||
if (path == null) {
|
||||
return null;
|
||||
Path path;
|
||||
try {
|
||||
path = WebUtils.downloadRemotePack(url, testing);
|
||||
} catch (Throwable e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
|
||||
// Check if the pack is a .zip or .mcpack file
|
||||
|
||||
@@ -303,6 +303,7 @@ public class CustomBlockRegistryPopulator {
|
||||
|
||||
BlockRegistries.JAVA_BLOCKS.registerWithAnyIndex(javaBlockState.stateGroupId(), block, Blocks.AIR);
|
||||
BlockRegistries.JAVA_IDENTIFIER_TO_ID.register(javaId, stateRuntimeId);
|
||||
BlockRegistries.NON_VANILLA_BLOCK_IDS.register(set -> set.set(stateRuntimeId));
|
||||
|
||||
// TODO register different collision types?
|
||||
BoundingBox[] geyserCollisions = Arrays.stream(javaBlockState.collision())
|
||||
|
||||
@@ -35,6 +35,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIntPair;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v786.Bedrock_v786;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v800.Bedrock_v800;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v818.Bedrock_v818;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
@@ -71,7 +72,8 @@ public final class TagRegistryPopulator {
|
||||
// ObjectIntPair.of("1_21_60", Bedrock_v776.CODEC.getProtocolVersion()),
|
||||
ObjectIntPair.of("1_21_70", Bedrock_v786.CODEC.getProtocolVersion()),
|
||||
// Not a typo, they're the same file
|
||||
ObjectIntPair.of("1_21_70", Bedrock_v800.CODEC.getProtocolVersion())
|
||||
ObjectIntPair.of("1_21_70", Bedrock_v800.CODEC.getProtocolVersion()),
|
||||
ObjectIntPair.of("1_21_70", Bedrock_v818.CODEC.getProtocolVersion())
|
||||
);
|
||||
Type type = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024-2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -61,19 +61,18 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
|
||||
// remove is handled in #remove()
|
||||
if (updateType == UpdateType.ADD) {
|
||||
for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) {
|
||||
playerRegistered(player);
|
||||
}
|
||||
session.getEntityCache().forEachPlayerEntity(this::playerRegistered);
|
||||
return;
|
||||
}
|
||||
if (updateType == UpdateType.UPDATE) {
|
||||
for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) {
|
||||
session.getEntityCache().forEachPlayerEntity(player -> {
|
||||
setBelowNameText(player, scoreFor(player.getUsername()));
|
||||
}
|
||||
});
|
||||
updateType = UpdateType.NOTHING;
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (displayScores) {
|
||||
for (var score : displayScores.values()) {
|
||||
// we don't have to worry about a score not existing, because that's handled by both
|
||||
// this method when an objective is added and addScore/playerRegistered.
|
||||
@@ -92,13 +91,12 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
setBelowNameText(score.player(), score.reference());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
updateType = UpdateType.REMOVE;
|
||||
for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) {
|
||||
clearBelowNameText(player);
|
||||
}
|
||||
session.getEntityCache().forEachPlayerEntity(this::clearBelowNameText);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,8 +117,10 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
|
||||
@Override
|
||||
public void playerRemoved(PlayerEntity player) {
|
||||
synchronized (displayScores) {
|
||||
displayScores.remove(player.getGeyserId());
|
||||
}
|
||||
}
|
||||
|
||||
private void addDisplayScore(ScoreReference reference) {
|
||||
var players = session.getEntityCache().getPlayersByName(reference.name());
|
||||
@@ -131,7 +131,9 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
|
||||
private BelownameDisplayScore addDisplayScore(PlayerEntity player, ScoreReference reference) {
|
||||
var score = new BelownameDisplayScore(this, objective.getScoreboard().nextId(), reference, player);
|
||||
synchronized (displayScores) {
|
||||
displayScores.put(player.getGeyserId(), score);
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024-2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -26,7 +26,6 @@
|
||||
package org.geysermc.geyser.scoreboard.display.slot;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -41,8 +40,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
|
||||
|
||||
public class PlayerlistDisplaySlot extends DisplaySlot {
|
||||
private final Long2ObjectMap<PlayerlistDisplayScore> displayScores =
|
||||
Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
|
||||
private final Long2ObjectMap<PlayerlistDisplayScore> displayScores = new Long2ObjectOpenHashMap<>();
|
||||
private final List<PlayerlistDisplayScore> removedScores = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
public PlayerlistDisplaySlot(GeyserSession session, Objective objective) {
|
||||
@@ -71,6 +69,7 @@ public class PlayerlistDisplaySlot extends DisplaySlot {
|
||||
removedScores.clear();
|
||||
}
|
||||
|
||||
synchronized (displayScores) {
|
||||
for (var score : displayScores.values()) {
|
||||
if (score.referenceRemoved()) {
|
||||
ScoreInfo cachedInfo = score.cachedInfo();
|
||||
@@ -102,6 +101,7 @@ public class PlayerlistDisplaySlot extends DisplaySlot {
|
||||
removeScores.add(score.cachedInfo());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (objectiveUpdate) {
|
||||
sendRemoveObjective();
|
||||
@@ -124,16 +124,17 @@ public class PlayerlistDisplaySlot extends DisplaySlot {
|
||||
players.add(selfPlayer);
|
||||
}
|
||||
|
||||
synchronized (displayScores) {
|
||||
for (PlayerEntity player : players) {
|
||||
var score =
|
||||
new PlayerlistDisplayScore(this, objective.getScoreboard().nextId(), reference, player.getGeyserId());
|
||||
var score = new PlayerlistDisplayScore(this, objective.getScoreboard().nextId(), reference, player.getGeyserId());
|
||||
displayScores.put(player.getGeyserId(), score);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerExisting() {
|
||||
playerRegistered(session.getPlayerEntity());
|
||||
session.getEntityCache().getAllPlayerEntities().forEach(this::playerRegistered);
|
||||
session.getEntityCache().forEachPlayerEntity(this::playerRegistered);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,14 +143,20 @@ public class PlayerlistDisplaySlot extends DisplaySlot {
|
||||
if (reference == null) {
|
||||
return;
|
||||
}
|
||||
var score =
|
||||
new PlayerlistDisplayScore(this, objective.getScoreboard().nextId(), reference, player.getGeyserId());
|
||||
|
||||
var score = new PlayerlistDisplayScore(this, objective.getScoreboard().nextId(), reference, player.getGeyserId());
|
||||
synchronized (displayScores) {
|
||||
displayScores.put(player.getGeyserId(), score);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerRemoved(PlayerEntity player) {
|
||||
var score = displayScores.remove(player.getGeyserId());
|
||||
PlayerlistDisplayScore score;
|
||||
synchronized (displayScores) {
|
||||
score = displayScores.remove(player.getGeyserId());
|
||||
}
|
||||
|
||||
if (score == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,11 @@ public final class SidebarDisplaySlot extends DisplaySlot {
|
||||
.thenComparing(ScoreReference::name, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private List<SidebarDisplayScore> displayScores = new ArrayList<>(SCORE_DISPLAY_LIMIT);
|
||||
/// A copy of displayScores which can be modified by the render0 method for its calculation of the scores to
|
||||
/// display. This was done to not add locks to displayScores, as that list can be used by multiple threads because
|
||||
/// of the setTeamFor method. Additionally, there is a brief period in render0 where scores are not present in the
|
||||
/// list which could lead to bugs that are hard to reproduce.
|
||||
private final List<SidebarDisplayScore> displayScoresCopy = new ArrayList<>(SCORE_DISPLAY_LIMIT);
|
||||
|
||||
public SidebarDisplaySlot(GeyserSession session, Objective objective, ScoreboardPosition position) {
|
||||
super(session, objective, position);
|
||||
@@ -56,8 +61,8 @@ public final class SidebarDisplaySlot extends DisplaySlot {
|
||||
|
||||
@Override
|
||||
protected void render0(List<ScoreInfo> addScores, List<ScoreInfo> removeScores) {
|
||||
// while one could argue that we may not have to do this fancy Java filter when there are fewer scores than the
|
||||
// line limit, we would lose the correct order of the scores if we don't
|
||||
// While one could argue that we may not have to do this fancy Java filter when there are fewer scores than the
|
||||
// line limit, it is also responsible for making sure that the scores are in the correct order.
|
||||
var newDisplayScores =
|
||||
objective.getScores().values().stream()
|
||||
.filter(score -> !score.hidden())
|
||||
@@ -65,7 +70,7 @@ public final class SidebarDisplaySlot extends DisplaySlot {
|
||||
.limit(SCORE_DISPLAY_LIMIT)
|
||||
.map(reference -> {
|
||||
// pretty much an ArrayList#remove
|
||||
var iterator = this.displayScores.iterator();
|
||||
var iterator = displayScoresCopy.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var score = iterator.next();
|
||||
if (score.name().equals(reference.name())) {
|
||||
@@ -78,32 +83,42 @@ public final class SidebarDisplaySlot extends DisplaySlot {
|
||||
return new SidebarDisplayScore(this, objective.getScoreboard().nextId(), reference);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
// in newDisplayScores we removed the items that were already present from displayScores,
|
||||
// meaning that the items that remain are items that are no longer displayed
|
||||
for (var score : this.displayScores) {
|
||||
// Make sure that we set the displayScores as early as possible, because setTeamFor relies on these potential
|
||||
// changes. And even if no scores were added or removed, the order could've changed.
|
||||
displayScores = newDisplayScores;
|
||||
|
||||
// In newDisplayScores we removed the items that were already present from displayScoresCopy,
|
||||
// meaning that the items that remain are items that are no longer displayed.
|
||||
for (var score : displayScoresCopy) {
|
||||
removeScores.add(score.cachedInfo());
|
||||
}
|
||||
|
||||
// preserves the new order
|
||||
this.displayScores = newDisplayScores;
|
||||
// The newDisplayScores have to be copied over to displayScoresCopy for the next render.
|
||||
for (int i = 0; i < newDisplayScores.size(); i++) {
|
||||
if (i < displayScoresCopy.size()) {
|
||||
displayScoresCopy.set(i, newDisplayScores.get(i));
|
||||
} else {
|
||||
displayScoresCopy.add(newDisplayScores.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// fixes ordering issues with multiple entries with same score
|
||||
if (!this.displayScores.isEmpty()) {
|
||||
if (!displayScores.isEmpty()) {
|
||||
SidebarDisplayScore lastScore = null;
|
||||
int count = 0;
|
||||
for (var score : this.displayScores) {
|
||||
for (var score : displayScores) {
|
||||
if (lastScore == null) {
|
||||
lastScore = score;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (score.score() == lastScore.score()) {
|
||||
// something to keep in mind is that Bedrock doesn't support some legacy color codes and adds some
|
||||
// codes as well, so if the line limit is every increased keep that in mind
|
||||
// Bedrock doesn't support some legacy color codes and adds some codes as well.
|
||||
// Keep this in mind if the line limit is ever increased.
|
||||
if (count == 0) {
|
||||
lastScore.order(ChatColor.styleOrder(count++));
|
||||
lastScore.order(ChatColor.colorDisplayOrder(count++));
|
||||
}
|
||||
score.order(ChatColor.styleOrder(count++));
|
||||
score.order(ChatColor.colorDisplayOrder(count++));
|
||||
} else {
|
||||
if (count == 0) {
|
||||
lastScore.order(null);
|
||||
@@ -121,7 +136,7 @@ public final class SidebarDisplaySlot extends DisplaySlot {
|
||||
boolean objectiveAdd = updateType == UpdateType.ADD;
|
||||
boolean objectiveUpdate = updateType == UpdateType.UPDATE;
|
||||
|
||||
for (var score : this.displayScores) {
|
||||
for (var score : displayScores) {
|
||||
Team team = score.team();
|
||||
boolean add = objectiveAdd || objectiveUpdate;
|
||||
boolean exists = score.exists();
|
||||
|
||||
@@ -715,6 +715,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
@Setter
|
||||
private int stepTicks = 0;
|
||||
|
||||
@Setter
|
||||
private boolean allowVibrantVisuals = true;
|
||||
|
||||
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) {
|
||||
this.geyser = geyser;
|
||||
this.upstream = new UpstreamSession(bedrockServerSession);
|
||||
@@ -1342,7 +1345,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
}
|
||||
|
||||
private void switchPose(boolean value, EntityFlag flag, Pose pose) {
|
||||
this.pose = value ? pose : Pose.STANDING;
|
||||
this.pose = value ? pose : this.pose == pose ? Pose.STANDING : this.pose;
|
||||
playerEntity.setDimensionsFromPose(this.pose);
|
||||
playerEntity.setFlag(flag, value);
|
||||
playerEntity.updateBedrockMetadata();
|
||||
@@ -1693,10 +1696,12 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
// Needed for certain molang queries used in blocks and items
|
||||
startGamePacket.getExperiments().add(new ExperimentData("experimental_molang_features", true));
|
||||
// Allows Vibrant Visuals to appear in the settings menu
|
||||
if (allowVibrantVisuals && !GameProtocol.is1_21_90orHigher(this)) {
|
||||
startGamePacket.getExperiments().add(new ExperimentData("experimental_graphics", true));
|
||||
}
|
||||
// Enables 2025 Content Drop 2 features
|
||||
startGamePacket.getExperiments().add(new ExperimentData("y_2025_drop_2", true));
|
||||
if (GameProtocol.is1_21_80(this)) {
|
||||
startGamePacket.getExperiments().add(new ExperimentData("y_2025_drop_2", true));
|
||||
// Enables the locator bar for 1.21.80 clients
|
||||
startGamePacket.getExperiments().add(new ExperimentData("locator_bar", true));
|
||||
}
|
||||
@@ -1717,6 +1722,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
startGamePacket.setServerId("");
|
||||
startGamePacket.setWorldId("");
|
||||
startGamePacket.setScenarioId("");
|
||||
startGamePacket.setOwnerId("");
|
||||
|
||||
upstream.sendPacket(startGamePacket);
|
||||
}
|
||||
@@ -1759,7 +1765,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
/**
|
||||
* Queue a packet to be sent to player.
|
||||
*
|
||||
* @param packet the bedrock packet from the NukkitX protocol lib
|
||||
* @param packet the bedrock packet from the Cloudburst protocol lib
|
||||
*/
|
||||
public void sendUpstreamPacket(BedrockPacket packet) {
|
||||
upstream.sendPacket(packet);
|
||||
@@ -1768,7 +1774,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
/**
|
||||
* Send a packet immediately to the player.
|
||||
*
|
||||
* @param packet the bedrock packet from the NukkitX protocol lib
|
||||
* @param packet the bedrock packet from the Cloudburst protocol lib
|
||||
*/
|
||||
public void sendUpstreamPacketImmediately(BedrockPacket packet) {
|
||||
upstream.sendPacketImmediately(packet);
|
||||
@@ -2266,6 +2272,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
@Override
|
||||
public int ping() {
|
||||
// Can otherwise cause issues if the player isn't logged in yet / already left
|
||||
if (!getUpstream().isInitialized() || getUpstream().isClosed()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
RakSessionCodec rakSessionCodec = ((RakChildChannel) getUpstream().getSession().getPeer().getChannel()).rakPipeline().get(RakSessionCodec.class);
|
||||
return (int) Math.floor(rakSessionCodec.getPing());
|
||||
}
|
||||
|
||||
@@ -55,12 +55,12 @@ import java.util.UUID;
|
||||
public class GeyserSessionAdapter extends SessionAdapter {
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private final GeyserSession geyserSession;
|
||||
private final GeyserSession session;
|
||||
private final boolean floodgate;
|
||||
private final String locale;
|
||||
|
||||
public GeyserSessionAdapter(GeyserSession session) {
|
||||
this.geyserSession = session;
|
||||
this.session = session;
|
||||
this.floodgate = session.remoteServer().authType() == AuthType.FLOODGATE;
|
||||
this.geyser = GeyserImpl.getInstance();
|
||||
this.locale = session.locale();
|
||||
@@ -69,7 +69,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
@Override
|
||||
public void packetSending(PacketSendingEvent event) {
|
||||
if (event.getPacket() instanceof ClientIntentionPacket intentionPacket) {
|
||||
BedrockClientData clientData = geyserSession.getClientData();
|
||||
BedrockClientData clientData = session.getClientData();
|
||||
|
||||
String addressSuffix;
|
||||
if (floodgate) {
|
||||
@@ -79,7 +79,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
FloodgateSkinUploader skinUploader = geyser.getSkinUploader();
|
||||
FloodgateCipher cipher = geyser.getCipher();
|
||||
|
||||
String bedrockAddress = geyserSession.getUpstream().getAddress().getAddress().getHostAddress();
|
||||
String bedrockAddress = session.getUpstream().getAddress().getAddress().getHostAddress();
|
||||
// both BungeeCord and Velocity remove the IPv6 scope (if there is one) for Spigot
|
||||
int ipv6ScopeIndex = bedrockAddress.indexOf('%');
|
||||
if (ipv6ScopeIndex != -1) {
|
||||
@@ -88,8 +88,8 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
|
||||
encryptedData = cipher.encryptFromString(BedrockData.of(
|
||||
clientData.getGameVersion(),
|
||||
geyserSession.bedrockUsername(),
|
||||
geyserSession.xuid(),
|
||||
session.bedrockUsername(),
|
||||
session.xuid(),
|
||||
clientData.getDeviceOs().ordinal(),
|
||||
clientData.getLanguageCode(),
|
||||
clientData.getUiProfile().ordinal(),
|
||||
@@ -100,7 +100,7 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
).toString());
|
||||
} catch (Exception e) {
|
||||
geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
geyserSession.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encrypt_fail", locale));
|
||||
session.disconnect(GeyserLocale.getPlayerLocaleString("geyser.auth.floodgate.encrypt_fail", locale));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,38 +122,38 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
|
||||
@Override
|
||||
public void connected(ConnectedEvent event) {
|
||||
geyserSession.loggingIn = false;
|
||||
geyserSession.loggedIn = true;
|
||||
session.loggingIn = false;
|
||||
session.loggedIn = true;
|
||||
|
||||
if (geyserSession.getDownstream().getSession() instanceof LocalSession) {
|
||||
if (session.getDownstream().getSession() instanceof LocalSession) {
|
||||
// Connected directly to the server
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect_internal",
|
||||
geyserSession.bedrockUsername(), geyserSession.getProtocol().getProfile().getName()));
|
||||
session.bedrockUsername(), session.getProtocol().getProfile().getName()));
|
||||
} else {
|
||||
// Connected to an IP address
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect",
|
||||
geyserSession.bedrockUsername(), geyserSession.getProtocol().getProfile().getName(), geyserSession.remoteServer().address()));
|
||||
session.bedrockUsername(), session.getProtocol().getProfile().getName(), session.remoteServer().address()));
|
||||
}
|
||||
|
||||
UUID uuid = geyserSession.getProtocol().getProfile().getId();
|
||||
UUID uuid = session.getProtocol().getProfile().getId();
|
||||
if (uuid == null) {
|
||||
// Set what our UUID *probably* is going to be
|
||||
if (geyserSession.remoteServer().authType() == AuthType.FLOODGATE) {
|
||||
uuid = new UUID(0, Long.parseLong(geyserSession.xuid()));
|
||||
if (session.remoteServer().authType() == AuthType.FLOODGATE) {
|
||||
uuid = new UUID(0, Long.parseLong(session.xuid()));
|
||||
} else {
|
||||
uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + geyserSession.getProtocol().getProfile().getName()).getBytes(StandardCharsets.UTF_8));
|
||||
uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + session.getProtocol().getProfile().getName()).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
geyserSession.getPlayerEntity().setUuid(uuid);
|
||||
geyserSession.getPlayerEntity().setUsername(geyserSession.getProtocol().getProfile().getName());
|
||||
session.getPlayerEntity().setUuid(uuid);
|
||||
session.getPlayerEntity().setUsername(session.getProtocol().getProfile().getName());
|
||||
|
||||
String locale = geyserSession.getClientData().getLanguageCode();
|
||||
String locale = session.getClientData().getLanguageCode();
|
||||
|
||||
// Let the user know there locale may take some time to download
|
||||
// as it has to be extracted from a JAR
|
||||
if (locale.equalsIgnoreCase("en_us") && !MinecraftLocale.LOCALE_MAPPINGS.containsKey("en_us")) {
|
||||
// This should probably be left hardcoded as it will only show for en_us clients
|
||||
geyserSession.sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
|
||||
session.sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
|
||||
}
|
||||
|
||||
// Download and load the language for the player
|
||||
@@ -162,12 +162,12 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
|
||||
@Override
|
||||
public void disconnected(DisconnectedEvent event) {
|
||||
geyserSession.loggingIn = false;
|
||||
session.loggingIn = false;
|
||||
|
||||
String disconnectMessage, customDisconnectMessage = null;
|
||||
Throwable cause = event.getCause();
|
||||
if (cause instanceof UnexpectedEncryptionException) {
|
||||
if (geyserSession.remoteServer().authType() != AuthType.FLOODGATE) {
|
||||
if (session.remoteServer().authType() != AuthType.FLOODGATE) {
|
||||
// Server expects online mode
|
||||
customDisconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale);
|
||||
// Explain that they may be looking for Floodgate.
|
||||
@@ -192,10 +192,10 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
// Use our helpful disconnect message whenever possible
|
||||
disconnectMessage = customDisconnectMessage != null ? customDisconnectMessage : MessageTranslator.convertMessage(event.getReason());;
|
||||
|
||||
if (geyserSession.getDownstream().getSession() instanceof LocalSession) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect_internal", geyserSession.bedrockUsername(), disconnectMessage));
|
||||
if (session.getDownstream().getSession() instanceof LocalSession) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect_internal", session.bedrockUsername(), disconnectMessage));
|
||||
} else {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect", geyserSession.bedrockUsername(), geyserSession.remoteServer().address(), disconnectMessage));
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.disconnect", session.bedrockUsername(), session.remoteServer().address(), disconnectMessage));
|
||||
}
|
||||
if (cause != null) {
|
||||
if (cause.getMessage() != null) {
|
||||
@@ -207,24 +207,24 @@ public class GeyserSessionAdapter extends SessionAdapter {
|
||||
cause.printStackTrace();
|
||||
}
|
||||
}
|
||||
if ((!geyserSession.isClosed() && geyserSession.loggedIn) || cause != null) {
|
||||
if ((!session.isClosed() && session.loggedIn) || cause != null) {
|
||||
// GeyserSession is disconnected via session.disconnect() called indirectly be the server
|
||||
// This needs to be "initiated" here when there is an exception, but also when the Netty connection
|
||||
// is closed without a disconnect packet - in this case, closed will still be false, but loggedIn
|
||||
// will also be true as GeyserSession#disconnect will not have been called.
|
||||
if (customDisconnectMessage != null) {
|
||||
geyserSession.disconnect(customDisconnectMessage);
|
||||
session.disconnect(customDisconnectMessage);
|
||||
} else {
|
||||
geyserSession.disconnect(event.getReason());
|
||||
session.disconnect(event.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
geyserSession.loggedIn = false;
|
||||
session.loggedIn = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetReceived(Session session, Packet packet) {
|
||||
Registries.JAVA_PACKET_TRANSLATORS.translate(packet.getClass(), packet, geyserSession, true);
|
||||
Registries.JAVA_PACKET_TRANSLATORS.translate(packet.getClass(), packet, this.session, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -32,11 +32,11 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.Tickable;
|
||||
@@ -136,11 +136,13 @@ public class EntityCache {
|
||||
}
|
||||
|
||||
public void addPlayerEntity(PlayerEntity entity) {
|
||||
synchronized (playerEntities) {
|
||||
// putIfAbsent matches the behavior of playerInfoMap in Java as of 1.19.3
|
||||
boolean exists = playerEntities.putIfAbsent(entity.getUuid(), entity) != null;
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// notify scoreboard for new entity
|
||||
var scoreboard = session.getWorldCache().getScoreboard();
|
||||
@@ -148,21 +150,29 @@ public class EntityCache {
|
||||
}
|
||||
|
||||
public PlayerEntity getPlayerEntity(UUID uuid) {
|
||||
synchronized (playerEntities) {
|
||||
return playerEntities.get(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PlayerEntity> getPlayersByName(String name) {
|
||||
var list = new ArrayList<PlayerEntity>();
|
||||
synchronized (playerEntities) {
|
||||
for (PlayerEntity player : playerEntities.values()) {
|
||||
if (name.equals(player.getUsername())) {
|
||||
list.add(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public PlayerEntity removePlayerEntity(UUID uuid) {
|
||||
var player = playerEntities.remove(uuid);
|
||||
PlayerEntity player;
|
||||
synchronized (playerEntities) {
|
||||
player = playerEntities.remove(uuid);
|
||||
}
|
||||
|
||||
if (player != null) {
|
||||
// notify scoreboard
|
||||
session.getWorldCache().getScoreboard().playerRemoved(player);
|
||||
@@ -170,13 +180,21 @@ public class EntityCache {
|
||||
return player;
|
||||
}
|
||||
|
||||
public Collection<PlayerEntity> getAllPlayerEntities() {
|
||||
return playerEntities.values();
|
||||
/**
|
||||
* Run a specific bit of code for each cached player entity.
|
||||
* As usual with synchronized, try to minimize the amount of work you because you block the PlayerList collection.
|
||||
*/
|
||||
public void forEachPlayerEntity(Consumer<PlayerEntity> player) {
|
||||
synchronized (playerEntities) {
|
||||
playerEntities.values().forEach(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllPlayerEntities() {
|
||||
synchronized (playerEntities) {
|
||||
playerEntities.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void addBossBar(UUID uuid, BossBar bossBar) {
|
||||
bossBars.put(uuid, bossBar);
|
||||
|
||||
@@ -139,7 +139,8 @@ public final class InputCache {
|
||||
case PERSIST_SNEAK -> {
|
||||
// Ignoring start/stop sneaking while in scaffolding on purpose to ensure
|
||||
// that we don't spam both cases for every block we went down
|
||||
if (session.getPlayerEntity().isInsideScaffolding()) {
|
||||
// Consoles would also send persist sneak; but don't send the descend_block flag
|
||||
if (inputMode == InputMode.TOUCH && session.getPlayerEntity().isInsideScaffolding()) {
|
||||
return authInputData.contains(PlayerAuthInputData.DESCEND_BLOCK) &&
|
||||
authInputData.contains(PlayerAuthInputData.SNEAK_CURRENT_RAW);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ public final class GeyserHolderSet<T> {
|
||||
*/
|
||||
public static <T> GeyserHolderSet<T> fromHolderSet(JavaRegistryKey<T> registry, @NonNull HolderSet holderSet) {
|
||||
// MCPL HolderSets don't have to support inline elements... for now (TODO CHECK ME)
|
||||
return new GeyserHolderSet<>(registry, new Tag<>(registry, holderSet.getLocation()), holderSet.getHolders(), null);
|
||||
Tag<T> tag = holderSet.getLocation() == null ? null : new Tag<>(registry, holderSet.getLocation());
|
||||
return new GeyserHolderSet<>(registry, tag, holderSet.getHolders(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,7 +87,7 @@ public class ChatColor {
|
||||
return string;
|
||||
}
|
||||
|
||||
public static String styleOrder(int index) {
|
||||
public static String colorDisplayOrder(int index) {
|
||||
// https://bugs.mojang.com/browse/MCPE-41729
|
||||
// strikethrough and underlined do not exist on Bedrock
|
||||
return switch (index) {
|
||||
|
||||
@@ -47,9 +47,13 @@ public class MinecraftLocale {
|
||||
|
||||
public static final Map<String, Map<String, String>> LOCALE_MAPPINGS = new HashMap<>();
|
||||
|
||||
private static final Path LOCALE_FOLDER = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales");
|
||||
// Check instance availability to avoid exception during testing
|
||||
private static final boolean IN_INSTANCE = GeyserImpl.getInstance() != null;
|
||||
|
||||
private static final Path LOCALE_FOLDER = (IN_INSTANCE) ? GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales") : null;
|
||||
|
||||
static {
|
||||
if (IN_INSTANCE) {
|
||||
try {
|
||||
// Create the locales folder
|
||||
Files.createDirectories(LOCALE_FOLDER);
|
||||
@@ -58,6 +62,7 @@ public class MinecraftLocale {
|
||||
throw new RuntimeException("Unable to create locale folders! " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ensureEN_US() {
|
||||
Path localeFile = getPath("en_us");
|
||||
|
||||
@@ -41,6 +41,7 @@ import java.util.regex.Pattern;
|
||||
public class MinecraftTranslationRegistry extends TranslatableComponentRenderer<String> {
|
||||
private final Pattern stringReplacement = Pattern.compile("%s");
|
||||
private final Pattern positionalStringReplacement = Pattern.compile("%([0-9]+)\\$s");
|
||||
private final Pattern escapeBraces = Pattern.compile("\\{+['{]+\\{+|\\{+");
|
||||
|
||||
// Exists to maintain compatibility with Velocity's older Adventure version
|
||||
@Override
|
||||
@@ -66,14 +67,19 @@ public class MinecraftTranslationRegistry extends TranslatableComponentRenderer<
|
||||
// replace single quote instances which get lost in MessageFormat otherwise
|
||||
localeString = localeString.replace("'", "''");
|
||||
|
||||
// Wrap all curly brackets with single quote inserts - fixes https://github.com/GeyserMC/Geyser/issues/4662
|
||||
localeString = localeString.replace("{", "'{")
|
||||
.replace("}", "'}");
|
||||
|
||||
// Replace the `%s` with numbered inserts `{0}`
|
||||
Pattern p = stringReplacement;
|
||||
// Escape all left curly brackets with single quote - fixes https://github.com/GeyserMC/Geyser/issues/4662
|
||||
Pattern p = escapeBraces;
|
||||
Matcher m = p.matcher(localeString);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (m.find()) {
|
||||
m.appendReplacement(sb, "'" + m.group() + "'");
|
||||
}
|
||||
m.appendTail(sb);
|
||||
|
||||
// Replace the `%s` with numbered inserts `{0}`
|
||||
p = stringReplacement;
|
||||
m = p.matcher(sb.toString());
|
||||
sb = new StringBuilder();
|
||||
int i = 0;
|
||||
while (m.find()) {
|
||||
m.appendReplacement(sb, "{" + (i++) + "}");
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSetBeaconPacket;
|
||||
|
||||
@@ -60,12 +59,9 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator<
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Container container) {
|
||||
if (!container.isUsingRealBlock()) {
|
||||
InventoryUtils.closeInventory(session, container.getJavaId(), false);
|
||||
return;
|
||||
}
|
||||
super.openInventory(session, container);
|
||||
public boolean prepareInventory(GeyserSession session, Container container) {
|
||||
// Virtual beacon inventories aren't possible - we don't want to spawn a whole pyramid!
|
||||
return super.canUseRealBlock(session, container);
|
||||
}
|
||||
}, UIInventoryUpdater.INSTANCE);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.command.CommandRegistry;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
@@ -44,10 +43,12 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
|
||||
}
|
||||
|
||||
static void handleCommand(GeyserSession session, String command) {
|
||||
if (session.getGeyser().getPlatformType() == PlatformType.STANDALONE ||
|
||||
session.getGeyser().getPlatformType() == PlatformType.VIAPROXY) {
|
||||
// try to handle the command within the standalone/viaproxy command manager
|
||||
if (MessageTranslator.isTooLong(command, session)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CommandRegistry.STANDALONE_COMMAND_MANAGER) {
|
||||
// try to handle the command within the standalone/viaproxy command manager
|
||||
String[] args = command.split(" ");
|
||||
if (args.length > 0) {
|
||||
String root = args[0];
|
||||
@@ -55,14 +56,12 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
|
||||
CommandRegistry registry = GeyserImpl.getInstance().commandRegistry();
|
||||
if (registry.rootCommands().contains(root)) {
|
||||
registry.runCommand(session, command);
|
||||
return; // don't pass the command to the java server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (MessageTranslator.isTooLong(command, session)) {
|
||||
// don't pass the command to the java server here
|
||||
// will pass it through later if the user lacks permission
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session.sendCommand(command);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
@@ -101,7 +102,7 @@ final class BedrockBlockActions {
|
||||
SkullCache.Skull skull = session.getSkullCache().getSkulls().get(vector);
|
||||
|
||||
session.setBlockBreakStartTime(0);
|
||||
if (blockStateOverride != null || customItem != null || (skull != null && skull.getBlockDefinition() != null)) {
|
||||
if (BlockRegistries.NON_VANILLA_BLOCK_IDS.get().get(blockState) || blockStateOverride != null || customItem != null || (skull != null && skull.getBlockDefinition() != null)) {
|
||||
session.setBlockBreakStartTime(System.currentTimeMillis());
|
||||
}
|
||||
startBreak.setData((int) (65535 / breakTime));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2024-2025 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -29,7 +29,6 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.MultiRec
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
@@ -59,9 +58,9 @@ public class JavaFinishConfigurationTranslator extends PacketTranslator<Clientbo
|
||||
public void translate(GeyserSession session, ClientboundFinishConfigurationPacket packet) {
|
||||
// Clear the player list, as on Java the player list is cleared after transitioning from config to play phase
|
||||
List<PlayerListPacket.Entry> entries = new ArrayList<>();
|
||||
for (PlayerEntity otherEntity : session.getEntityCache().getAllPlayerEntities()) {
|
||||
entries.add(new PlayerListPacket.Entry(otherEntity.getTabListUuid()));
|
||||
}
|
||||
session.getEntityCache().forEachPlayerEntity(otherPlayer -> {
|
||||
entries.add(new PlayerListPacket.Entry(otherPlayer.getTabListUuid()));
|
||||
});
|
||||
PlayerListUtils.batchSendPlayerList(session, entries, PlayerListPacket.Action.REMOVE);
|
||||
session.getEntityCache().removeAllPlayerEntities();
|
||||
|
||||
|
||||
@@ -78,6 +78,11 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
||||
// Remove extra hearts, hunger, etc.
|
||||
entity.resetAttributes();
|
||||
entity.resetMetadata();
|
||||
|
||||
// Reset inventories; just in case. Might resolve some issues where inventories get stuck?
|
||||
session.setInventoryHolder(null);
|
||||
session.setPendingOrCurrentBedrockInventoryId(-1);
|
||||
session.setClosingInventory(false);
|
||||
}
|
||||
|
||||
session.setDimensionType(newDimension);
|
||||
|
||||
@@ -44,12 +44,14 @@ import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.EvokerFangsEntity;
|
||||
import org.geysermc.geyser.entity.type.FishingHookEntity;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.entity.type.ThrowableEggEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.ArmadilloEntity;
|
||||
import org.geysermc.geyser.entity.type.living.monster.CreakingEntity;
|
||||
import org.geysermc.geyser.entity.type.living.monster.WardenEntity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
@@ -99,10 +101,10 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
|
||||
|
||||
case LIVING_DEATH:
|
||||
entityEventPacket.setType(EntityEventType.DEATH);
|
||||
if (entity.getDefinition() == EntityDefinitions.EGG) {
|
||||
if (entity instanceof ThrowableEggEntity egg) {
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(ParticleType.ICON_CRACK);
|
||||
particlePacket.setData(session.getItemMappings().getStoredItems().egg().getBedrockDefinition().getRuntimeId() << 16);
|
||||
particlePacket.setData(ItemTranslator.getBedrockItemDefinition(session, egg.getItemStack()).getRuntimeId() << 16);
|
||||
particlePacket.setPosition(entity.getPosition());
|
||||
for (int i = 0; i < 6; i++) {
|
||||
session.sendUpstreamPacket(particlePacket);
|
||||
|
||||
@@ -70,10 +70,10 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
|
||||
Inventory inventory = holder.inventory();
|
||||
int slot = packet.getSlot();
|
||||
if (slot >= inventory.getSize()) {
|
||||
if (slot < 0 || slot >= inventory.getSize()) {
|
||||
GeyserLogger logger = session.getGeyser().getLogger();
|
||||
logger.warning("ClientboundContainerSetSlotPacket sent to " + session.bedrockUsername()
|
||||
+ " that exceeds inventory size!");
|
||||
logger.warning("Slot of ClientboundContainerSetSlotPacket sent to " + session.bedrockUsername()
|
||||
+ " is out of bounds! Was: " + slot + " for container: " + packet.getContainerId());
|
||||
if (logger.isDebug()) {
|
||||
logger.debug(packet.toString());
|
||||
logger.debug(inventory.toString());
|
||||
|
||||
@@ -508,7 +508,7 @@ public class MessageTranslator {
|
||||
} else {
|
||||
String translateKey = map.getString("translate", null);
|
||||
if (translateKey != null) {
|
||||
String fallback = map.getString("fallback", "");
|
||||
String fallback = map.getString("fallback", null);
|
||||
List<Component> args = new ArrayList<>();
|
||||
|
||||
Object with = map.get("with");
|
||||
|
||||
@@ -29,6 +29,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode;
|
||||
import org.cloudburstmc.protocol.bedrock.data.auth.AuthPayload;
|
||||
import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult;
|
||||
@@ -58,14 +60,14 @@ public class LoginEncryptionUtils {
|
||||
private static boolean HAS_SENT_ENCRYPTION_MESSAGE = false;
|
||||
|
||||
public static void encryptPlayerConnection(GeyserSession session, LoginPacket loginPacket) {
|
||||
encryptConnectionWithCert(session, loginPacket.getExtra(), loginPacket.getChain());
|
||||
encryptConnectionWithCert(session, loginPacket.getAuthPayload(), loginPacket.getClientJwt());
|
||||
}
|
||||
|
||||
private static void encryptConnectionWithCert(GeyserSession session, String clientData, List<String> certChainData) {
|
||||
private static void encryptConnectionWithCert(GeyserSession session, AuthPayload authPayload, String jwt) {
|
||||
try {
|
||||
GeyserImpl geyser = session.getGeyser();
|
||||
|
||||
ChainValidationResult result = EncryptionUtils.validateChain(certChainData);
|
||||
ChainValidationResult result = EncryptionUtils.validatePayload(authPayload);
|
||||
|
||||
geyser.getLogger().debug(String.format("Is player data signed? %s", result.signed()));
|
||||
|
||||
@@ -75,19 +77,25 @@ public class LoginEncryptionUtils {
|
||||
}
|
||||
|
||||
IdentityData extraData = result.identityClaims().extraData;
|
||||
// TODO!!! identity won't persist
|
||||
session.setAuthData(new AuthData(extraData.displayName, extraData.identity, extraData.xuid));
|
||||
session.setCertChainData(certChainData);
|
||||
if (authPayload instanceof CertificateChainPayload certificateChainPayload) {
|
||||
session.setCertChainData(certificateChainPayload.getChain());
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().warning("Received new auth payload!");
|
||||
session.setCertChainData(List.of());
|
||||
}
|
||||
|
||||
PublicKey identityPublicKey = result.identityClaims().parsedIdentityPublicKey();
|
||||
|
||||
byte[] clientDataPayload = EncryptionUtils.verifyClientData(clientData, identityPublicKey);
|
||||
byte[] clientDataPayload = EncryptionUtils.verifyClientData(jwt, identityPublicKey);
|
||||
if (clientDataPayload == null) {
|
||||
throw new IllegalStateException("Client data isn't signed by the given chain data");
|
||||
}
|
||||
|
||||
JsonNode clientDataJson = JSON_MAPPER.readTree(clientDataPayload);
|
||||
BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class);
|
||||
data.setOriginalString(clientData);
|
||||
data.setOriginalString(jwt);
|
||||
session.setClientData(data);
|
||||
|
||||
try {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
package org.geysermc.geyser.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
@@ -110,7 +111,7 @@ public class WebUtils {
|
||||
* @return Path to the downloaded pack file, or null if it was unable to be loaded
|
||||
*/
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public static @Nullable Path downloadRemotePack(String url, boolean force) {
|
||||
public static @NonNull Path downloadRemotePack(String url, boolean force) throws IOException {
|
||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||
try {
|
||||
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
|
||||
@@ -137,6 +138,9 @@ public class WebUtils {
|
||||
"Bedrock Edition only supports the application/zip content type.", url, type));
|
||||
}
|
||||
|
||||
// Ensure remote pack cache dir exists
|
||||
Files.createDirectories(REMOTE_PACK_CACHE);
|
||||
|
||||
Path packMetadata = REMOTE_PACK_CACHE.resolve(url.hashCode() + ".metadata");
|
||||
Path downloadLocation;
|
||||
|
||||
@@ -190,23 +194,20 @@ public class WebUtils {
|
||||
));
|
||||
packMetadata.toFile().setLastModified(System.currentTimeMillis());
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Failed to write cached pack metadata: " + e.getMessage());
|
||||
Files.delete(packMetadata);
|
||||
Files.delete(downloadLocation);
|
||||
return null;
|
||||
throw new IllegalStateException("Failed to write cached pack metadata: " + e.getMessage());
|
||||
}
|
||||
|
||||
downloadLocation.toFile().setLastModified(System.currentTimeMillis());
|
||||
logger.debug("Successfully downloaded remote pack! URL: %s (to: %s )".formatted(url, downloadLocation));
|
||||
return downloadLocation;
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Unable to download resource pack from malformed URL %s! ".formatted(url));
|
||||
throw new IllegalArgumentException("Unable to download resource pack from malformed URL %s".formatted(url));
|
||||
} catch (SocketTimeoutException | ConnectException e) {
|
||||
logger.error("Unable to download pack from url %s due to network error! ( %s )".formatted(url, e.getMessage()));
|
||||
logger.debug(e);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to download and save remote resource pack from: %s ( %s )!".formatted(url, e.getMessage()));
|
||||
throw new IllegalArgumentException("Unable to download pack from url %s due to network error ( %s )".formatted(url, e.toString()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
BIN
core/src/main/resources/bedrock/block_palette.1_21_90.nbt
Normal file
BIN
core/src/main/resources/bedrock/block_palette.1_21_90.nbt
Normal file
Binary file not shown.
9044
core/src/main/resources/bedrock/creative_items.1_21_90.json
Normal file
9044
core/src/main/resources/bedrock/creative_items.1_21_90.json
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
10904
core/src/main/resources/bedrock/runtime_item_states.1_21_90.json
Normal file
10904
core/src/main/resources/bedrock/runtime_item_states.1_21_90.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -50,9 +50,9 @@
|
||||
"spawns_warm_variant_frogs"
|
||||
]
|
||||
},
|
||||
"snowy_slopes": {
|
||||
"temperature": -0.3,
|
||||
"downfall": 0.9,
|
||||
"grove": {
|
||||
"temperature": -0.2,
|
||||
"downfall": 0.8,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
@@ -70,17 +70,17 @@
|
||||
"id": null,
|
||||
"tags": [
|
||||
"mountains",
|
||||
"cold",
|
||||
"monster",
|
||||
"overworld",
|
||||
"frozen",
|
||||
"grove",
|
||||
"spawns_cold_variant_farm_animals",
|
||||
"spawns_cold_variant_frogs",
|
||||
"spawns_snow_foxes",
|
||||
"spawns_white_rabbits",
|
||||
"snowy_slopes",
|
||||
"spawns_cold_variant_farm_animals"
|
||||
"spawns_white_rabbits"
|
||||
]
|
||||
},
|
||||
"jagged_peaks": {
|
||||
"frozen_peaks": {
|
||||
"temperature": -0.7,
|
||||
"downfall": 0.9,
|
||||
"redSporeDensity": 0.0,
|
||||
@@ -103,7 +103,7 @@
|
||||
"monster",
|
||||
"overworld",
|
||||
"frozen",
|
||||
"jagged_peaks",
|
||||
"frozen_peaks",
|
||||
"spawns_cold_variant_farm_animals",
|
||||
"spawns_cold_variant_frogs",
|
||||
"spawns_snow_foxes",
|
||||
@@ -338,6 +338,32 @@
|
||||
"has_structure_trail_ruins"
|
||||
]
|
||||
},
|
||||
"meadow": {
|
||||
"temperature": 0.3,
|
||||
"downfall": 0.8,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 166,
|
||||
"r": 96,
|
||||
"g": 183,
|
||||
"b": 255
|
||||
},
|
||||
"rain": true,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"mountains",
|
||||
"monster",
|
||||
"overworld",
|
||||
"meadow",
|
||||
"bee_habitat"
|
||||
]
|
||||
},
|
||||
"jungle_mutated": {
|
||||
"temperature": 0.95,
|
||||
"downfall": 0.9,
|
||||
@@ -365,6 +391,36 @@
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"jagged_peaks": {
|
||||
"temperature": -0.7,
|
||||
"downfall": 0.9,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 166,
|
||||
"r": 96,
|
||||
"g": 183,
|
||||
"b": 255
|
||||
},
|
||||
"rain": true,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"mountains",
|
||||
"monster",
|
||||
"overworld",
|
||||
"frozen",
|
||||
"jagged_peaks",
|
||||
"spawns_cold_variant_farm_animals",
|
||||
"spawns_cold_variant_frogs",
|
||||
"spawns_snow_foxes",
|
||||
"spawns_white_rabbits"
|
||||
]
|
||||
},
|
||||
"flower_forest": {
|
||||
"temperature": 0.7,
|
||||
"downfall": 0.8,
|
||||
@@ -586,6 +642,32 @@
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"lush_caves": {
|
||||
"temperature": 0.9,
|
||||
"downfall": 0.0,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 166,
|
||||
"r": 96,
|
||||
"g": 183,
|
||||
"b": 255
|
||||
},
|
||||
"rain": true,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"caves",
|
||||
"lush_caves",
|
||||
"overworld",
|
||||
"monster",
|
||||
"spawns_tropical_fish_at_any_height"
|
||||
]
|
||||
},
|
||||
"deep_frozen_ocean": {
|
||||
"temperature": 0.0,
|
||||
"downfall": 0.5,
|
||||
@@ -834,33 +916,6 @@
|
||||
"spawns_cold_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"crimson_forest": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
"redSporeDensity": 0.25,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 165,
|
||||
"r": 144,
|
||||
"g": 89,
|
||||
"b": 87
|
||||
},
|
||||
"rain": false,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"nether",
|
||||
"netherwart_forest",
|
||||
"crimson_forest",
|
||||
"spawn_few_zombified_piglins",
|
||||
"spawn_piglin",
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"mesa": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
@@ -998,6 +1053,32 @@
|
||||
"spawns_cold_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"warped_forest": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.25,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 165,
|
||||
"r": 144,
|
||||
"g": 89,
|
||||
"b": 87
|
||||
},
|
||||
"rain": false,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"nether",
|
||||
"netherwart_forest",
|
||||
"warped_forest",
|
||||
"spawn_endermen",
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"mesa_plateau_stone": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
@@ -1385,32 +1466,6 @@
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"meadow": {
|
||||
"temperature": 0.3,
|
||||
"downfall": 0.8,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 166,
|
||||
"r": 96,
|
||||
"g": 183,
|
||||
"b": 255
|
||||
},
|
||||
"rain": true,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"mountains",
|
||||
"monster",
|
||||
"overworld",
|
||||
"meadow",
|
||||
"bee_habitat"
|
||||
]
|
||||
},
|
||||
"jungle_hills": {
|
||||
"temperature": 0.95,
|
||||
"downfall": 0.9,
|
||||
@@ -1467,36 +1522,6 @@
|
||||
"spawns_cold_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"frozen_peaks": {
|
||||
"temperature": -0.7,
|
||||
"downfall": 0.9,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 166,
|
||||
"r": 96,
|
||||
"g": 183,
|
||||
"b": 255
|
||||
},
|
||||
"rain": true,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"mountains",
|
||||
"monster",
|
||||
"overworld",
|
||||
"frozen",
|
||||
"frozen_peaks",
|
||||
"spawns_cold_variant_farm_animals",
|
||||
"spawns_cold_variant_frogs",
|
||||
"spawns_snow_foxes",
|
||||
"spawns_white_rabbits"
|
||||
]
|
||||
},
|
||||
"taiga": {
|
||||
"temperature": 0.25,
|
||||
"downfall": 0.8,
|
||||
@@ -1633,6 +1658,33 @@
|
||||
"warm"
|
||||
]
|
||||
},
|
||||
"crimson_forest": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
"redSporeDensity": 0.25,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 165,
|
||||
"r": 144,
|
||||
"g": 89,
|
||||
"b": 87
|
||||
},
|
||||
"rain": false,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"nether",
|
||||
"netherwart_forest",
|
||||
"crimson_forest",
|
||||
"spawn_few_zombified_piglins",
|
||||
"spawn_piglin",
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"ice_plains": {
|
||||
"temperature": 0.0,
|
||||
"downfall": 0.5,
|
||||
@@ -1958,32 +2010,6 @@
|
||||
"spawns_cold_variant_frogs"
|
||||
]
|
||||
},
|
||||
"warped_forest": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.25,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 165,
|
||||
"r": 144,
|
||||
"g": 89,
|
||||
"b": 87
|
||||
},
|
||||
"rain": false,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"nether",
|
||||
"netherwart_forest",
|
||||
"warped_forest",
|
||||
"spawn_endermen",
|
||||
"spawns_warm_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"mesa_plateau_stone_mutated": {
|
||||
"temperature": 2.0,
|
||||
"downfall": 0.0,
|
||||
@@ -2037,9 +2063,9 @@
|
||||
"spawns_without_patrols"
|
||||
]
|
||||
},
|
||||
"grove": {
|
||||
"temperature": -0.2,
|
||||
"downfall": 0.8,
|
||||
"snowy_slopes": {
|
||||
"temperature": -0.3,
|
||||
"downfall": 0.9,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
@@ -2057,14 +2083,14 @@
|
||||
"id": null,
|
||||
"tags": [
|
||||
"mountains",
|
||||
"cold",
|
||||
"monster",
|
||||
"overworld",
|
||||
"grove",
|
||||
"spawns_cold_variant_farm_animals",
|
||||
"frozen",
|
||||
"spawns_cold_variant_frogs",
|
||||
"spawns_snow_foxes",
|
||||
"spawns_white_rabbits"
|
||||
"spawns_white_rabbits",
|
||||
"snowy_slopes",
|
||||
"spawns_cold_variant_farm_animals"
|
||||
]
|
||||
},
|
||||
"warm_ocean": {
|
||||
@@ -2312,32 +2338,6 @@
|
||||
"overworld"
|
||||
]
|
||||
},
|
||||
"lush_caves": {
|
||||
"temperature": 0.9,
|
||||
"downfall": 0.0,
|
||||
"redSporeDensity": 0.0,
|
||||
"blueSporeDensity": 0.0,
|
||||
"ashDensity": 0.0,
|
||||
"whiteAshDensity": 0.0,
|
||||
"depth": 0.1,
|
||||
"scale": 0.2,
|
||||
"mapWaterColor": {
|
||||
"a": 166,
|
||||
"r": 96,
|
||||
"g": 183,
|
||||
"b": 255
|
||||
},
|
||||
"rain": true,
|
||||
"chunkGenData": null,
|
||||
"id": null,
|
||||
"tags": [
|
||||
"caves",
|
||||
"lush_caves",
|
||||
"overworld",
|
||||
"monster",
|
||||
"spawns_tropical_fish_at_any_height"
|
||||
]
|
||||
},
|
||||
"frozen_ocean": {
|
||||
"temperature": 0.0,
|
||||
"downfall": 0.5,
|
||||
|
||||
@@ -69,6 +69,12 @@ public class MessageTranslatorTest {
|
||||
"§e All participants will receive a reward\n" +
|
||||
"§e and the top 3 will get extra bonus prizes!");
|
||||
|
||||
// Escape curly braces in translatable strings (make MessageFormat ignore them)
|
||||
messages.put("{\"translate\":\"tt{tt%stt}tt\",\"with\":[\"AA\"]}", "tt{ttAAtt}tt");
|
||||
messages.put("{\"translate\":\"tt{'tt%stt'{tt\",\"with\":[\"AA\"]}", "tt{'ttAAtt'{tt");
|
||||
messages.put("{\"translate\":\"tt{''{tt\"}", "tt{''{tt");
|
||||
messages.put("{\"translate\":\"tt{{''}}tt\"}", "tt{{''}}tt");
|
||||
|
||||
MessageTranslator.init();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ netty = "4.2.1.Final"
|
||||
guava = "29.0-jre"
|
||||
gson = "2.3.1" # Provided by Spigot 1.8.8
|
||||
websocket = "1.5.1"
|
||||
protocol-connection = "3.0.0.Beta6-20250506.012145-17"
|
||||
protocol-common = "3.0.0.Beta6-20250506.012145-17"
|
||||
protocol-codec = "3.0.0.Beta6-20250506.012145-17"
|
||||
protocol-connection = "3.0.0.Beta7-20250616.124609-6"
|
||||
protocol-common = "3.0.0.Beta7-20250616.124609-6"
|
||||
protocol-codec = "3.0.0.Beta7-20250616.124609-6"
|
||||
raknet = "1.0.0.CR3-20250218.160705-18"
|
||||
minecraftauth = "4.1.1"
|
||||
mcprotocollib = "1.21.6-SNAPSHOT"
|
||||
|
||||
Reference in New Issue
Block a user