mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Merge remote-tracking branch 'origin/master' into jwt-changes
# Conflicts: # core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java # core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java # core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
This commit is contained in:
commit
99671960d0
141 changed files with 14840 additions and 41502 deletions
|
@ -30,7 +30,7 @@ dependencies {
|
|||
}
|
||||
|
||||
implementation(libs.raknet) {
|
||||
exclude("io.netty", "*");
|
||||
exclude("io.netty", "*")
|
||||
}
|
||||
|
||||
implementation(libs.netty.resolver.dns)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
package org.geysermc.connector;
|
||||
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.erosion.packet.Packets;
|
||||
|
@ -69,9 +69,9 @@ import org.geysermc.geyser.event.GeyserEventBus;
|
|||
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.network.netty.GeyserServer;
|
||||
import org.geysermc.geyser.pack.ResourcePack;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||
|
@ -90,6 +90,7 @@ import java.io.IOException;
|
|||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.*;
|
||||
|
@ -217,25 +218,9 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||
|
||||
boolean isGui = false;
|
||||
// This will check if we are in standalone and get the 'useGui' variable from there
|
||||
if (platformType == PlatformType.STANDALONE) {
|
||||
try {
|
||||
Class<?> cls = Class.forName("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap");
|
||||
isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap));
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed detecting if standalone is using a GUI; if this is a GeyserConnect instance this can be safely ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;
|
||||
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " ";
|
||||
if (isGui) {
|
||||
message += GeyserLocale.getLocaleStringLog("geyser.core.finish.gui");
|
||||
} else {
|
||||
message += GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
|
||||
}
|
||||
|
||||
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime));
|
||||
message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
|
||||
logger.info(message);
|
||||
|
||||
if (platformType == PlatformType.STANDALONE) {
|
||||
|
@ -258,7 +243,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
SkinProvider.registerCacheImageTask(this);
|
||||
|
||||
ResourcePack.loadPacks();
|
||||
Registries.RESOURCE_PACKS.load();
|
||||
|
||||
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
|
||||
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
|
||||
|
@ -410,7 +395,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size));
|
||||
// Prevent unwanted words best we can
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT)));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
|
||||
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.AdvancedPie("playerPlatform", () -> {
|
||||
|
@ -446,7 +431,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
if (minecraftVersion != null) {
|
||||
Map<String, Map<String, Integer>> versionMap = new HashMap<>();
|
||||
Map<String, Integer> platformMap = new HashMap<>();
|
||||
platformMap.put(platformType.getPlatformName(), 1);
|
||||
platformMap.put(platformType.platformName(), 1);
|
||||
versionMap.put(minecraftVersion, platformMap);
|
||||
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> {
|
||||
|
@ -622,7 +607,7 @@ public class GeyserImpl implements GeyserApi {
|
|||
this.erosionUnixListener.close();
|
||||
}
|
||||
|
||||
ResourcePack.PACKS.clear();
|
||||
Registries.RESOURCE_PACKS.get().clear();
|
||||
|
||||
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
||||
this.extensionManager.disableExtensions();
|
||||
|
@ -681,6 +666,24 @@ public class GeyserImpl implements GeyserApi {
|
|||
return getConfig().getBedrock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Path configDirectory() {
|
||||
return bootstrap.getConfigFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Path packDirectory() {
|
||||
return bootstrap.getConfigFolder().resolve("packs");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public PlatformType platformType() {
|
||||
return platformType;
|
||||
}
|
||||
|
||||
public int buildNumber() {
|
||||
if (!this.isProductionEnvironment()) {
|
||||
return 0;
|
||||
|
|
|
@ -29,7 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.command.CommandExecutor;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
|
|
@ -30,7 +30,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
|||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
|
|
@ -27,7 +27,7 @@ package org.geysermc.geyser.dump;
|
|||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<HorseEntity> HORSE;
|
||||
public static final EntityDefinition<ZombieEntity> HUSK;
|
||||
public static final EntityDefinition<SpellcasterIllagerEntity> ILLUSIONER; // Not present on Bedrock
|
||||
public static final EntityDefinition<InteractionEntity> INTERACTION;
|
||||
public static final EntityDefinition<IronGolemEntity> IRON_GOLEM;
|
||||
public static final EntityDefinition<ItemEntity> ITEM;
|
||||
public static final EntityDefinition<ItemFrameEntity> ITEM_FRAME;
|
||||
|
@ -133,6 +134,7 @@ public final class EntityDefinitions {
|
|||
public static final EntityDefinition<AbstractFishEntity> SALMON;
|
||||
public static final EntityDefinition<SheepEntity> SHEEP;
|
||||
public static final EntityDefinition<ShulkerEntity> SHULKER;
|
||||
public static final EntityDefinition<SnifferEntity> SNIFFER;
|
||||
public static final EntityDefinition<ThrowableEntity> SHULKER_BULLET;
|
||||
public static final EntityDefinition<MonsterEntity> SILVERFISH;
|
||||
public static final EntityDefinition<SkeletonEntity> SKELETON;
|
||||
|
@ -235,7 +237,7 @@ public final class EntityDefinitions {
|
|||
.type(EntityType.EXPERIENCE_ORB)
|
||||
.identifier("minecraft:xp_orb")
|
||||
.build();
|
||||
EVOKER_FANGS = EntityDefinition.builder(EvokerFangsEntity::new) // No entity metadata to listen to as of 1.18.1
|
||||
EVOKER_FANGS = EntityDefinition.inherited(EvokerFangsEntity::new, entityBase)
|
||||
.type(EntityType.EVOKER_FANGS)
|
||||
.height(0.8f).width(0.5f)
|
||||
.identifier("minecraft:evocation_fang")
|
||||
|
@ -318,6 +320,15 @@ public final class EntityDefinitions {
|
|||
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
|
||||
.build();
|
||||
|
||||
INTERACTION = EntityDefinition.inherited(InteractionEntity::new, entityBase)
|
||||
.type(EntityType.INTERACTION)
|
||||
.heightAndWidth(1.0f) // default size until server specifies otherwise
|
||||
.identifier("minecraft:armor_stand")
|
||||
.addTranslator(MetadataType.FLOAT, InteractionEntity::setWidth)
|
||||
.addTranslator(MetadataType.FLOAT, InteractionEntity::setHeight)
|
||||
.addTranslator(MetadataType.BOOLEAN, InteractionEntity::setResponse)
|
||||
.build();
|
||||
|
||||
EntityDefinition<FireballEntity> fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase)
|
||||
.addTranslator(null) // Item
|
||||
.build();
|
||||
|
@ -842,6 +853,12 @@ public final class EntityDefinitions {
|
|||
.height(1.3f).width(0.9f)
|
||||
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
|
||||
.build();
|
||||
SNIFFER = EntityDefinition.inherited(SnifferEntity::new, ageableEntityBase)
|
||||
.type(EntityType.SNIFFER)
|
||||
.height(1.75f).width(1.9f)
|
||||
.addTranslator(MetadataType.SNIFFER_STATE, SnifferEntity::setSnifferState)
|
||||
.addTranslator(null) // Integer, drop seed at tick
|
||||
.build();
|
||||
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
|
||||
.type(EntityType.STRIDER)
|
||||
.height(1.7f).width(0.9f)
|
||||
|
@ -884,7 +901,6 @@ public final class EntityDefinitions {
|
|||
.build();
|
||||
CAMEL = EntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase)
|
||||
.type(EntityType.CAMEL)
|
||||
.identifier("minecraft:llama") // todo 1.20
|
||||
.height(2.375f).width(1.7f)
|
||||
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
|
||||
.addTranslator(null) // Last pose change tick
|
||||
|
|
|
@ -125,8 +125,8 @@ public class BoatEntity extends Entity {
|
|||
public void setVariant(IntEntityMetadata entityMetadata) {
|
||||
variant = entityMetadata.getPrimitiveValue();
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, switch (variant) {
|
||||
case 6, 7 -> variant - 1; // Dark oak and mangrove
|
||||
case 5, 8 -> 0; // TODO temp until 1.20. Cherry and bamboo
|
||||
case 6, 7, 8 -> variant - 1; // dark_oak, mangrove, bamboo
|
||||
case 5 -> 8; // cherry
|
||||
default -> variant;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -493,9 +493,10 @@ public class Entity implements GeyserEntity {
|
|||
* Update the mount offsets of each passenger on this vehicle
|
||||
*/
|
||||
protected void updatePassengerOffsets() {
|
||||
for (Entity passenger : passengers) {
|
||||
for (int i = 0; i < passengers.size(); i++) {
|
||||
Entity passenger = passengers.get(i);
|
||||
if (passenger != null) {
|
||||
boolean rider = passengers.get(0) == this;
|
||||
boolean rider = i == 0;
|
||||
EntityUtils.updateMountOffset(passenger, this, rider, true, passengers.size() > 1);
|
||||
passenger.updateBedrockMetadata();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class InteractionEntity extends Entity {
|
||||
|
||||
/**
|
||||
* true - java client hears swing sound when attacking, and arm swings when right-clicking
|
||||
* false - java client hears no swing sound when attacking, and arm does not swing when right-clicking
|
||||
*/
|
||||
private boolean response = false;
|
||||
|
||||
public InteractionEntity(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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
|
||||
// hide the armor stand but keep the hitbox active
|
||||
setFlag(EntityFlag.INVISIBLE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
// these InteractionResults do mirror the java client
|
||||
// but the bedrock client won't arm swing itself because of our armor stand workaround
|
||||
if (response) {
|
||||
AnimatePacket animatePacket = new AnimatePacket();
|
||||
animatePacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
||||
session.sendUpstreamPacket(animatePacket);
|
||||
|
||||
session.sendDownstreamPacket(new ServerboundSwingPacket(hand));
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
public void setWidth(FloatEntityMetadata width) {
|
||||
setBoundingBoxWidth(width.getPrimitiveValue());
|
||||
}
|
||||
|
||||
public void setHeight(FloatEntityMetadata height) {
|
||||
setBoundingBoxHeight(height.getPrimitiveValue());
|
||||
}
|
||||
|
||||
public void setResponse(BooleanEntityMetadata response) {
|
||||
this.response = response.getPrimitiveValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.SnifferState;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Tickable;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SnifferEntity extends AnimalEntity implements Tickable {
|
||||
private static final float DIGGING_HEIGHT = EntityDefinitions.SNIFFER.height() - 0.4f;
|
||||
private static final int DIG_END = 120;
|
||||
private static final int DIG_START = DIG_END - 34;
|
||||
|
||||
private Pose pose = Pose.STANDING; // Needed to call setDimensions for DIGGING state
|
||||
private int digTicks;
|
||||
|
||||
public SnifferEntity(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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
this.pose = pose;
|
||||
super.setPose(pose);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDimensions(Pose pose) {
|
||||
if (getFlag(EntityFlag.DIGGING)) {
|
||||
setBoundingBoxHeight(DIGGING_HEIGHT);
|
||||
setBoundingBoxWidth(definition.width());
|
||||
} else {
|
||||
super.setDimensions(pose);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEat(Item item) {
|
||||
return session.getTagCache().isSnifferFood(item);
|
||||
}
|
||||
|
||||
public void setSnifferState(ObjectEntityMetadata<SnifferState> entityMetadata) {
|
||||
SnifferState snifferState = entityMetadata.getValue();
|
||||
|
||||
// SnifferState.SCENTING and SnifferState.IDLING not used in bedrock
|
||||
// The bedrock client does the scenting animation and sound on its own
|
||||
setFlag(EntityFlag.FEELING_HAPPY, snifferState == SnifferState.FEELING_HAPPY);
|
||||
setFlag(EntityFlag.SCENTING, snifferState == SnifferState.SNIFFING); // SnifferState.SNIFFING -> EntityFlag.SCENTING
|
||||
setFlag(EntityFlag.SEARCHING, snifferState == SnifferState.SEARCHING);
|
||||
setFlag(EntityFlag.DIGGING, snifferState == SnifferState.DIGGING);
|
||||
setFlag(EntityFlag.RISING, snifferState == SnifferState.RISING);
|
||||
|
||||
setDimensions(pose);
|
||||
|
||||
if (getFlag(EntityFlag.DIGGING)) {
|
||||
digTicks = DIG_END;
|
||||
} else {
|
||||
// Handles situations where the DIGGING state is exited earlier than expected,
|
||||
// such as hitting the sniffer or joining the game while it is digging
|
||||
digTicks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// The java client renders digging particles on its own, but bedrock does not
|
||||
if (digTicks > 0 && --digTicks < DIG_START && digTicks % 5 == 0) {
|
||||
Vector3f rot = Vector3f.createDirectionDeg(0, -getYaw()).mul(2.25f);
|
||||
Vector3f pos = getPosition().add(rot).up(0.2f).floor(); // Handle non-full blocks
|
||||
int blockId = session.getBlockMappings().getBedrockBlockId(session.getGeyser().getWorldManager().getBlockAt(session, pos.toInt().down()));
|
||||
|
||||
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
||||
levelEventPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK_NO_SOUND);
|
||||
levelEventPacket.setPosition(pos);
|
||||
levelEventPacket.setData(blockId);
|
||||
session.sendUpstreamPacket(levelEventPacket);
|
||||
|
||||
if (digTicks % 10 == 0) {
|
||||
LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket();
|
||||
levelSoundEventPacket.setSound(SoundEvent.HIT);
|
||||
levelSoundEventPacket.setPosition(pos);
|
||||
levelSoundEventPacket.setExtraData(blockId);
|
||||
levelSoundEventPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(levelSoundEventPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,8 +27,13 @@ package org.geysermc.geyser.entity.type.living.animal.horse;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
|
@ -38,16 +43,50 @@ import java.util.UUID;
|
|||
|
||||
public class CamelEntity extends AbstractHorseEntity {
|
||||
|
||||
private static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
|
||||
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
|
||||
|
||||
public CamelEntity(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);
|
||||
|
||||
dirtyMetadata.put(EntityDataTypes.CONTAINER_TYPE, (byte) ContainerType.HORSE.getId());
|
||||
|
||||
// Always tamed, but not indicated in horse flags
|
||||
setFlag(EntityFlag.TAMED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
this.dirtyMetadata.put(EntityDataTypes.VARIANT, 2); // Closest llama colour to camel
|
||||
public void setHorseFlags(ByteEntityMetadata entityMetadata) {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
boolean saddled = (xd & 0x04) == 0x04;
|
||||
setFlag(EntityFlag.SADDLED, saddled);
|
||||
setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10);
|
||||
setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20);
|
||||
|
||||
// HorseFlags
|
||||
// Bred 0x10
|
||||
// Eating 0x20
|
||||
// Open mouth 0x80
|
||||
int horseFlags = 0x0;
|
||||
horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags;
|
||||
|
||||
// Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation
|
||||
horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags;
|
||||
|
||||
// Set the flags into the horse flags
|
||||
dirtyMetadata.put(EntityDataTypes.HORSE_FLAGS, horseFlags);
|
||||
|
||||
// Send the eating particles
|
||||
// We use the wheat metadata as static particles since Java
|
||||
// doesn't send over what item was used to feed the horse
|
||||
if ((xd & 0x40) == 0x40) {
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setRuntimeEntityId(geyserId);
|
||||
entityEventPacket.setType(EntityEventType.EATING_ITEM);
|
||||
entityEventPacket.setData(session.getItemMappings().getStoredItems().wheat().getBedrockDefinition().getRuntimeId() << 16);
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
}
|
||||
|
||||
// Shows the dash meter
|
||||
setFlag(EntityFlag.CAN_DASH, saddled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,10 +94,16 @@ public class CamelEntity extends AbstractHorseEntity {
|
|||
return item == Items.CACTUS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
setFlag(EntityFlag.SITTING, pose == Pose.SITTING);
|
||||
super.setPose(pose);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDimensions(Pose pose) {
|
||||
if (pose == Pose.SITTING) {
|
||||
setBoundingBoxWidth(definition.height() - SITTING_HEIGHT_DIFFERENCE);
|
||||
setBoundingBoxHeight(definition.height() - SITTING_HEIGHT_DIFFERENCE);
|
||||
setBoundingBoxWidth(definition.width());
|
||||
} else {
|
||||
super.setDimensions(pose);
|
||||
|
|
|
@ -67,10 +67,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
*/
|
||||
@Getter
|
||||
private boolean isRidingInFront;
|
||||
/**
|
||||
* Used for villager inventory emulation.
|
||||
*/
|
||||
private int fakeTradeXp;
|
||||
|
||||
public SessionPlayerEntity(GeyserSession session) {
|
||||
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
|
||||
|
@ -175,11 +171,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||
this.isRidingInFront = position != null && position.getX() > 0;
|
||||
}
|
||||
|
||||
public void addFakeTradeExperience(int tradeXp) {
|
||||
fakeTradeXp += tradeXp;
|
||||
dirtyMetadata.put(EntityDataTypes.TRADE_EXPERIENCE, fakeTradeXp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeData createHealthAttribute() {
|
||||
// Max health must be divisible by two in bedrock
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.event.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
|
||||
|
||||
private final Map<String, ResourcePack> packs;
|
||||
|
||||
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<String, ResourcePack> packMap) {
|
||||
super(session);
|
||||
this.packs = packMap;
|
||||
}
|
||||
|
||||
public @NonNull Map<String, ResourcePack> getPacks() {
|
||||
return packs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<ResourcePack> resourcePacks() {
|
||||
return List.copyOf(packs.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NonNull ResourcePack resourcePack) {
|
||||
String packID = resourcePack.manifest().header().uuid().toString();
|
||||
if (packs.containsValue(resourcePack) || packs.containsKey(packID)) {
|
||||
return false;
|
||||
}
|
||||
packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregister(@NonNull UUID uuid) {
|
||||
return packs.remove(uuid.toString()) != null;
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.extension.event;
|
|||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.event.Event;
|
||||
import org.geysermc.event.FireResult;
|
||||
import org.geysermc.event.PostOrder;
|
||||
import org.geysermc.event.subscribe.Subscriber;
|
||||
import org.geysermc.geyser.api.event.EventBus;
|
||||
|
@ -47,10 +48,15 @@ public record GeyserExtensionEventBus(EventBus<EventRegistrar> eventBus, Extensi
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean fire(@NonNull Event event) {
|
||||
public FireResult fire(@NonNull Event event) {
|
||||
return eventBus.fire(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FireResult fireSilently(@NonNull Event event) {
|
||||
return eventBus.fireSilently(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends Event> Set<? extends EventSubscriber<EventRegistrar, T>> subscribers(@NonNull Class<T> eventClass) {
|
||||
return eventBus.subscribers(eventClass);
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade;
|
|||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
|
@ -40,6 +41,8 @@ public class MerchantContainer extends Container {
|
|||
private VillagerTrade[] villagerTrades;
|
||||
@Getter @Setter
|
||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||
@Getter @Setter
|
||||
private int tradeExperience;
|
||||
|
||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
|
@ -49,9 +52,10 @@ public class MerchantContainer extends Container {
|
|||
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
|
||||
VillagerTrade trade = villagerTrades[slot];
|
||||
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||
// TODO this logic doesn't add up
|
||||
session.getPlayerEntity().addFakeTradeExperience(trade.getXp());
|
||||
session.getPlayerEntity().updateBedrockMetadata();
|
||||
|
||||
tradeExperience += trade.getXp();
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_EXPERIENCE, tradeExperience);
|
||||
villager.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ public class StoredItemMappings {
|
|||
private final ItemMapping egg;
|
||||
private final ItemMapping shield;
|
||||
private final ItemMapping wheat;
|
||||
private final ItemMapping writableBook;
|
||||
|
||||
public StoredItemMappings(Map<Item, ItemMapping> itemMappings) {
|
||||
this.bamboo = load(itemMappings, Items.BAMBOO);
|
||||
|
@ -64,6 +65,7 @@ public class StoredItemMappings {
|
|||
this.egg = load(itemMappings, Items.EGG);
|
||||
this.shield = load(itemMappings, Items.SHIELD);
|
||||
this.wheat = load(itemMappings, Items.WHEAT);
|
||||
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hardcoded recipe information about armor trims until further improvements can be made. This information was scraped
|
||||
* from BDS 1.19.81 with a world with the next_major_update and sniffer features enabled, using ProxyPass.
|
||||
*/
|
||||
public class TrimRecipe {
|
||||
|
||||
// For TrimDataPacket, which BDS sends just before the CraftingDataPacket
|
||||
public static final List<TrimPattern> PATTERNS;
|
||||
public static final List<TrimMaterial> MATERIALS;
|
||||
|
||||
// For CraftingDataPacket
|
||||
public static final String ID = "minecraft:smithing_armor_trim";
|
||||
public static final ItemDescriptorWithCount BASE = tagDescriptor("minecraft:trimmable_armors");
|
||||
public static final ItemDescriptorWithCount ADDITION = tagDescriptor("minecraft:trim_materials");
|
||||
public static final ItemDescriptorWithCount TEMPLATE = tagDescriptor("minecraft:trim_templates");
|
||||
|
||||
static {
|
||||
List<TrimPattern> patterns = new ArrayList<>(16);
|
||||
patterns.add(new TrimPattern("minecraft:ward_armor_trim_smithing_template", "ward"));
|
||||
patterns.add(new TrimPattern("minecraft:sentry_armor_trim_smithing_template", "sentry"));
|
||||
patterns.add(new TrimPattern("minecraft:snout_armor_trim_smithing_template", "snout"));
|
||||
patterns.add(new TrimPattern("minecraft:dune_armor_trim_smithing_template", "dune"));
|
||||
patterns.add(new TrimPattern("minecraft:spire_armor_trim_smithing_template", "spire"));
|
||||
patterns.add(new TrimPattern("minecraft:tide_armor_trim_smithing_template", "tide"));
|
||||
patterns.add(new TrimPattern("minecraft:wild_armor_trim_smithing_template", "wild"));
|
||||
patterns.add(new TrimPattern("minecraft:rib_armor_trim_smithing_template", "rib"));
|
||||
patterns.add(new TrimPattern("minecraft:coast_armor_trim_smithing_template", "coast"));
|
||||
patterns.add(new TrimPattern("minecraft:shaper_armor_trim_smithing_template", "shaper"));
|
||||
patterns.add(new TrimPattern("minecraft:eye_armor_trim_smithing_template", "eye"));
|
||||
patterns.add(new TrimPattern("minecraft:vex_armor_trim_smithing_template", "vex"));
|
||||
patterns.add(new TrimPattern("minecraft:silence_armor_trim_smithing_template", "silence"));
|
||||
patterns.add(new TrimPattern("minecraft:wayfinder_armor_trim_smithing_template", "wayfinder"));
|
||||
patterns.add(new TrimPattern("minecraft:raiser_armor_trim_smithing_template", "raiser"));
|
||||
patterns.add(new TrimPattern("minecraft:host_armor_trim_smithing_template", "host"));
|
||||
PATTERNS = Collections.unmodifiableList(patterns);
|
||||
|
||||
List<TrimMaterial> materials = new ArrayList<>(10);
|
||||
materials.add(new TrimMaterial("quartz", "§h", "minecraft:quartz"));
|
||||
materials.add(new TrimMaterial("iron", "§i", "minecraft:iron_ingot"));
|
||||
materials.add(new TrimMaterial("netherite", "§j", "minecraft:netherite_ingot"));
|
||||
materials.add(new TrimMaterial("redstone", "§m", "minecraft:redstone"));
|
||||
materials.add(new TrimMaterial("copper", "§n", "minecraft:copper_ingot"));
|
||||
materials.add(new TrimMaterial("gold", "§p", "minecraft:gold_ingot"));
|
||||
materials.add(new TrimMaterial("emerald", "§q", "minecraft:emerald"));
|
||||
materials.add(new TrimMaterial("diamond", "§s", "minecraft:diamond"));
|
||||
materials.add(new TrimMaterial("lapis", "§t", "minecraft:lapis_lazuli"));
|
||||
materials.add(new TrimMaterial("amethyst", "§u", "minecraft:amethyst_shard"));
|
||||
MATERIALS = Collections.unmodifiableList(materials);
|
||||
}
|
||||
|
||||
private TrimRecipe() {
|
||||
//no-op
|
||||
}
|
||||
|
||||
private static ItemDescriptorWithCount tagDescriptor(String tag) {
|
||||
return new ItemDescriptorWithCount(new ItemTagDescriptor(tag), 1);
|
||||
}
|
||||
}
|
|
@ -84,6 +84,7 @@ public final class Items {
|
|||
public static final Item BEDROCK = register(new BlockItem("bedrock", builder()));
|
||||
public static final Item SAND = register(new BlockItem("sand", builder()));
|
||||
public static final Item SUSPICIOUS_SAND = register(new BlockItem("suspicious_sand", builder()));
|
||||
public static final Item SUSPICIOUS_GRAVEL = register(new BlockItem("suspicious_gravel", builder()));
|
||||
public static final Item RED_SAND = register(new BlockItem("red_sand", builder()));
|
||||
public static final Item GRAVEL = register(new BlockItem("gravel", builder()));
|
||||
public static final Item COAL_ORE = register(new BlockItem("coal_ore", builder()));
|
||||
|
@ -247,6 +248,7 @@ public final class Items {
|
|||
public static final Item LILY_OF_THE_VALLEY = register(new FlowerItem("lily_of_the_valley", builder()));
|
||||
public static final Item WITHER_ROSE = register(new FlowerItem("wither_rose", builder()));
|
||||
public static final Item TORCHFLOWER = register(new FlowerItem("torchflower", builder()));
|
||||
public static final Item PITCHER_PLANT = register(new BlockItem("pitcher_plant", builder()));
|
||||
public static final Item SPORE_BLOSSOM = register(new BlockItem("spore_blossom", builder()));
|
||||
public static final Item BROWN_MUSHROOM = register(new BlockItem("brown_mushroom", builder()));
|
||||
public static final Item RED_MUSHROOM = register(new BlockItem("red_mushroom", builder()));
|
||||
|
@ -302,7 +304,7 @@ public final class Items {
|
|||
public static final Item BRICKS = register(new BlockItem("bricks", builder()));
|
||||
public static final Item BOOKSHELF = register(new BlockItem("bookshelf", builder()));
|
||||
public static final Item CHISELED_BOOKSHELF = register(new BlockItem("chiseled_bookshelf", builder()));
|
||||
public static final Item DECORATED_POT = register(new BlockItem("decorated_pot", builder().stackSize(1)));
|
||||
public static final Item DECORATED_POT = register(new DecoratedPotItem("decorated_pot", builder().stackSize(1)));
|
||||
public static final Item MOSSY_COBBLESTONE = register(new BlockItem("mossy_cobblestone", builder()));
|
||||
public static final Item OBSIDIAN = register(new BlockItem("obsidian", builder()));
|
||||
public static final Item TORCH = register(new BlockItem("torch", builder()));
|
||||
|
@ -313,7 +315,7 @@ public final class Items {
|
|||
public static final Item PURPUR_PILLAR = register(new BlockItem("purpur_pillar", builder()));
|
||||
public static final Item PURPUR_STAIRS = register(new BlockItem("purpur_stairs", builder()));
|
||||
public static final Item SPAWNER = register(new BlockItem("spawner", builder()));
|
||||
public static final Item CHEST = register(new BlockItem("chest", builder()));
|
||||
public static final Item CHEST = register(new ChestItem("chest", builder()));
|
||||
public static final Item CRAFTING_TABLE = register(new BlockItem("crafting_table", builder()));
|
||||
public static final Item FARMLAND = register(new BlockItem("farmland", builder()));
|
||||
public static final Item FURNACE = register(new BlockItem("furnace", builder()));
|
||||
|
@ -395,7 +397,7 @@ public final class Items {
|
|||
public static final Item END_STONE_BRICKS = register(new BlockItem("end_stone_bricks", builder()));
|
||||
public static final Item DRAGON_EGG = register(new BlockItem("dragon_egg", builder()));
|
||||
public static final Item SANDSTONE_STAIRS = register(new BlockItem("sandstone_stairs", builder()));
|
||||
public static final Item ENDER_CHEST = register(new BlockItem("ender_chest", builder()));
|
||||
public static final Item ENDER_CHEST = register(new ChestItem("ender_chest", builder()));
|
||||
public static final Item EMERALD_BLOCK = register(new BlockItem("emerald_block", builder()));
|
||||
public static final Item OAK_STAIRS = register(new BlockItem("oak_stairs", builder()));
|
||||
public static final Item SPRUCE_STAIRS = register(new BlockItem("spruce_stairs", builder()));
|
||||
|
@ -602,6 +604,7 @@ public final class Items {
|
|||
public static final Item RED_CONCRETE_POWDER = register(new BlockItem("red_concrete_powder", builder()));
|
||||
public static final Item BLACK_CONCRETE_POWDER = register(new BlockItem("black_concrete_powder", builder()));
|
||||
public static final Item TURTLE_EGG = register(new BlockItem("turtle_egg", builder()));
|
||||
public static final Item SNIFFER_EGG = register(new BlockItem("sniffer_egg", builder()));
|
||||
public static final Item DEAD_TUBE_CORAL_BLOCK = register(new BlockItem("dead_tube_coral_block", builder()));
|
||||
public static final Item DEAD_BRAIN_CORAL_BLOCK = register(new BlockItem("dead_brain_coral_block", builder()));
|
||||
public static final Item DEAD_BUBBLE_CORAL_BLOCK = register(new BlockItem("dead_bubble_coral_block", builder()));
|
||||
|
@ -689,8 +692,9 @@ public final class Items {
|
|||
public static final Item LIGHTNING_ROD = register(new BlockItem("lightning_rod", builder()));
|
||||
public static final Item DAYLIGHT_DETECTOR = register(new BlockItem("daylight_detector", builder()));
|
||||
public static final Item SCULK_SENSOR = register(new BlockItem("sculk_sensor", builder()));
|
||||
public static final Item CALIBRATED_SCULK_SENSOR = register(new BlockItem("calibrated_sculk_sensor", builder()));
|
||||
public static final Item TRIPWIRE_HOOK = register(new BlockItem("tripwire_hook", builder()));
|
||||
public static final Item TRAPPED_CHEST = register(new BlockItem("trapped_chest", builder()));
|
||||
public static final Item TRAPPED_CHEST = register(new ChestItem("trapped_chest", builder()));
|
||||
public static final Item TNT = register(new BlockItem("tnt", builder()));
|
||||
public static final Item REDSTONE_LAMP = register(new BlockItem("redstone_lamp", builder()));
|
||||
public static final Item NOTE_BLOCK = register(new BlockItem("note_block", builder()));
|
||||
|
@ -1080,8 +1084,8 @@ public final class Items {
|
|||
public static final Item ZOMBIFIED_PIGLIN_SPAWN_EGG = register(new SpawnEggItem("zombified_piglin_spawn_egg", builder()));
|
||||
public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder()));
|
||||
public static final Item FIRE_CHARGE = register(new Item("fire_charge", builder()));
|
||||
public static final Item WRITABLE_BOOK = register(new ReadableBookItem("writable_book", builder().stackSize(1)));
|
||||
public static final Item WRITTEN_BOOK = register(new ReadableBookItem("written_book", builder().stackSize(16)));
|
||||
public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1)));
|
||||
public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16)));
|
||||
public static final Item ITEM_FRAME = register(new Item("item_frame", builder()));
|
||||
public static final Item GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder()));
|
||||
public static final Item FLOWER_POT = register(new BlockItem("flower_pot", builder()));
|
||||
|
@ -1141,6 +1145,7 @@ public final class Items {
|
|||
public static final Item CHORUS_FRUIT = register(new Item("chorus_fruit", builder()));
|
||||
public static final Item POPPED_CHORUS_FRUIT = register(new Item("popped_chorus_fruit", builder()));
|
||||
public static final Item TORCHFLOWER_SEEDS = register(new BlockItem("torchflower_seeds", builder()));
|
||||
public static final Item PITCHER_POD = register(new BlockItem("pitcher_pod", builder()));
|
||||
public static final Item BEETROOT = register(new Item("beetroot", builder()));
|
||||
public static final Item BEETROOT_SEEDS = register(new BlockItem("beetroot_seeds", builder()));
|
||||
public static final Item BEETROOT_SOUP = register(new Item("beetroot_soup", builder().stackSize(1)));
|
||||
|
@ -1168,6 +1173,7 @@ public final class Items {
|
|||
public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1)));
|
||||
public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder()));
|
||||
|
@ -1186,7 +1192,7 @@ public final class Items {
|
|||
public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1)));
|
||||
public static final Item GOAT_HORN = register(new GoatHornItem("goat_horn", builder().stackSize(1)));
|
||||
public static final Item COMPOSTER = register(new BlockItem("composter", builder()));
|
||||
public static final Item BARREL = register(new BlockItem("barrel", builder()));
|
||||
public static final Item BARREL = register(new ChestItem("barrel", builder()));
|
||||
public static final Item SMOKER = register(new BlockItem("smoker", builder()));
|
||||
public static final Item BLAST_FURNACE = register(new BlockItem("blast_furnace", builder()));
|
||||
public static final Item CARTOGRAPHY_TABLE = register(new BlockItem("cartography_table", builder()));
|
||||
|
@ -1262,10 +1268,31 @@ public final class Items {
|
|||
public static final Item SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("snout_armor_trim_smithing_template", builder()));
|
||||
public static final Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("rib_armor_trim_smithing_template", builder()));
|
||||
public static final Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("spire_armor_trim_smithing_template", builder()));
|
||||
public static final Item POTTERY_SHARD_ARCHER = register(new Item("pottery_shard_archer", builder()));
|
||||
public static final Item POTTERY_SHARD_PRIZE = register(new Item("pottery_shard_prize", builder()));
|
||||
public static final Item POTTERY_SHARD_ARMS_UP = register(new Item("pottery_shard_arms_up", builder()));
|
||||
public static final Item POTTERY_SHARD_SKULL = register(new Item("pottery_shard_skull", builder()));
|
||||
public static final Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("wayfinder_armor_trim_smithing_template", builder()));
|
||||
public static final Item SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("shaper_armor_trim_smithing_template", builder()));
|
||||
public static final Item SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("silence_armor_trim_smithing_template", builder()));
|
||||
public static final Item RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("raiser_armor_trim_smithing_template", builder()));
|
||||
public static final Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("host_armor_trim_smithing_template", builder()));
|
||||
public static final Item ANGLER_POTTERY_SHERD = register(new Item("angler_pottery_sherd", builder()));
|
||||
public static final Item ARCHER_POTTERY_SHERD = register(new Item("archer_pottery_sherd", builder()));
|
||||
public static final Item ARMS_UP_POTTERY_SHERD = register(new Item("arms_up_pottery_sherd", builder()));
|
||||
public static final Item BLADE_POTTERY_SHERD = register(new Item("blade_pottery_sherd", builder()));
|
||||
public static final Item BREWER_POTTERY_SHERD = register(new Item("brewer_pottery_sherd", builder()));
|
||||
public static final Item BURN_POTTERY_SHERD = register(new Item("burn_pottery_sherd", builder()));
|
||||
public static final Item DANGER_POTTERY_SHERD = register(new Item("danger_pottery_sherd", builder()));
|
||||
public static final Item EXPLORER_POTTERY_SHERD = register(new Item("explorer_pottery_sherd", builder()));
|
||||
public static final Item FRIEND_POTTERY_SHERD = register(new Item("friend_pottery_sherd", builder()));
|
||||
public static final Item HEART_POTTERY_SHERD = register(new Item("heart_pottery_sherd", builder()));
|
||||
public static final Item HEARTBREAK_POTTERY_SHERD = register(new Item("heartbreak_pottery_sherd", builder()));
|
||||
public static final Item HOWL_POTTERY_SHERD = register(new Item("howl_pottery_sherd", builder()));
|
||||
public static final Item MINER_POTTERY_SHERD = register(new Item("miner_pottery_sherd", builder()));
|
||||
public static final Item MOURNER_POTTERY_SHERD = register(new Item("mourner_pottery_sherd", builder()));
|
||||
public static final Item PLENTY_POTTERY_SHERD = register(new Item("plenty_pottery_sherd", builder()));
|
||||
public static final Item PRIZE_POTTERY_SHERD = register(new Item("prize_pottery_sherd", builder()));
|
||||
public static final Item SHEAF_POTTERY_SHERD = register(new Item("sheaf_pottery_sherd", builder()));
|
||||
public static final Item SHELTER_POTTERY_SHERD = register(new Item("shelter_pottery_sherd", builder()));
|
||||
public static final Item SKULL_POTTERY_SHERD = register(new Item("skull_pottery_sherd", builder()));
|
||||
public static final Item SNORT_POTTERY_SHERD = register(new Item("snort_pottery_sherd", builder()));
|
||||
|
||||
private static <T extends Item> T register(T item) {
|
||||
return register(item, Registries.JAVA_ITEMS.get().size());
|
||||
|
|
|
@ -25,7 +25,12 @@
|
|||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.ArmorMaterial;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class ArmorItem extends Item {
|
||||
private final ArmorMaterial material;
|
||||
|
@ -35,8 +40,42 @@ public class ArmorItem extends Item {
|
|||
this.material = material;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (tag.get("Trim") instanceof CompoundTag trim) {
|
||||
StringTag material = trim.remove("material");
|
||||
StringTag pattern = trim.remove("pattern");
|
||||
// bedrock has an uppercase first letter key, and the value is not namespaced
|
||||
trim.put(new StringTag("Material", stripNamespace(material.getValue())));
|
||||
trim.put(new StringTag("Pattern", stripNamespace(pattern.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToJava(@NonNull CompoundTag tag, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(tag, mapping);
|
||||
|
||||
if (tag.get("Trim") instanceof CompoundTag trim) {
|
||||
StringTag material = trim.remove("Material");
|
||||
StringTag pattern = trim.remove("Pattern");
|
||||
// java has a lowercase key, and namespaced value
|
||||
trim.put(new StringTag("material", "minecraft:" + material.getValue()));
|
||||
trim.put(new StringTag("pattern", "minecraft:" + pattern.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidRepairItem(Item other) {
|
||||
return material.getRepairIngredient() == other;
|
||||
}
|
||||
|
||||
private static String stripNamespace(String identifier) {
|
||||
int i = identifier.indexOf(':');
|
||||
if (i >= 0) {
|
||||
return identifier.substring(i + 1);
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2023 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
|
||||
|
@ -23,42 +23,31 @@
|
|||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.inventory.item;
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public abstract class NbtItemStackTranslator {
|
||||
|
||||
/**
|
||||
* Translate the item NBT to Bedrock
|
||||
* @param session the client's current session
|
||||
* @param itemTag the item's CompoundTag (cloned from Geyser's cached copy)
|
||||
* @param mapping Geyser's item mapping
|
||||
*/
|
||||
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) {
|
||||
public class ChestItem extends BlockItem {
|
||||
|
||||
public ChestItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the item NBT to Java.
|
||||
* @param itemTag the item's CompoundTag
|
||||
* @param mapping Geyser's item mapping
|
||||
*/
|
||||
public void translateToJava(CompoundTag itemTag, ItemMapping mapping) {
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
// Strip the BlockEntityTag from the chests contents
|
||||
// sent to the client. The client does not parse this
|
||||
// or use it for anything, as this tag is fully
|
||||
// server-side, so we remove it to reduce bandwidth and
|
||||
// solve potential issues with very large tags.
|
||||
|
||||
// There was a problem in the past where this would strip
|
||||
// NBT data in creative mode, however with the new server
|
||||
// authoritative inventories, this is no longer a concern.
|
||||
tag.remove("BlockEntityTag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this nbt translator takes in this item.
|
||||
*
|
||||
* @param item Geyser's item mapping
|
||||
* @return if the item should be processed under this class
|
||||
*/
|
||||
public boolean acceptItem(Item item) {
|
||||
return true;
|
||||
} // TODO
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class DecoratedPotItem extends BlockItem {
|
||||
|
||||
public DecoratedPotItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (tag.remove("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
|
||||
if (blockEntityTag.remove("sherds") instanceof ListTag sherds) {
|
||||
// bedrock wants it on the root level
|
||||
tag.put(sherds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -144,7 +144,7 @@ public class Item {
|
|||
}
|
||||
|
||||
/**
|
||||
* Takes NBT from Java Edition and converts any value that Bedrock parses differently. <br>
|
||||
* Takes NBT from Bedrock Edition and converts any value that Java parses differently. <br>
|
||||
* Do note that this method is, these days, only called in three places (as of 2023/~1.19):
|
||||
* <ul>
|
||||
* <li>Extra recipe loading</li>
|
||||
|
|
|
@ -25,13 +25,40 @@
|
|||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.components.ToolTier;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class ShieldItem extends Item {
|
||||
public ShieldItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (tag.remove("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
|
||||
if (blockEntityTag.get("Patterns") instanceof ListTag patterns) {
|
||||
for (Tag pattern : patterns) {
|
||||
if (((CompoundTag) pattern).get("Color") instanceof IntTag color) {
|
||||
color.setValue(15 - color.getValue());
|
||||
}
|
||||
}
|
||||
// Bedrock looks for patterns at the root
|
||||
tag.put(patterns);
|
||||
}
|
||||
if (blockEntityTag.get("Base") instanceof IntTag base) {
|
||||
base.setValue(15 - base.getValue());
|
||||
tag.put(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidRepairItem(Item other) {
|
||||
// Java Edition 1.19.3 checks the tag, but TODO check to see if we want it or are simulating what Bedrock is doing
|
||||
|
|
|
@ -77,9 +77,17 @@ public class ShulkerBoxItem extends BlockItem {
|
|||
itemsList.add(boxItemTag);
|
||||
}
|
||||
tag.put(itemsList);
|
||||
// Don't actually bother with removing the block entity tag. Too risky to translate
|
||||
// if the user is on creative and messing with a shulker box
|
||||
//itemTag.remove("BlockEntityTag");
|
||||
|
||||
// Strip the BlockEntityTag from the chests contents
|
||||
// sent to the client. The client does not parse this
|
||||
// or use it for anything, as this tag is fully
|
||||
// server-side, so we remove it to reduce bandwidth and
|
||||
// solve potential issues with very large tags.
|
||||
|
||||
// There was a problem in the past where this would strip
|
||||
// NBT data in creative mode, however with the new server
|
||||
// authoritative inventories, this is no longer a concern.
|
||||
tag.remove("BlockEntityTag");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -37,11 +37,8 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates written books and writable books. Customly named class to share common code.
|
||||
*/
|
||||
public class ReadableBookItem extends Item {
|
||||
public ReadableBookItem(String javaIdentifier, Builder builder) {
|
||||
public class WritableBookItem extends Item {
|
||||
public WritableBookItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WrittenBookItem extends WritableBookItem {
|
||||
public static final int MAXIMUM_PAGE_EDIT_LENGTH = 1024;
|
||||
public static final int MAXIMUM_PAGE_LENGTH = 32768;
|
||||
public static final int MAXIMUM_PAGE_COUNT = 100; // Java edition limit. Bedrock edition has a limit of 50 pages.
|
||||
public static final int MAXIMUM_TITLE_LENGTH = 16;
|
||||
|
||||
public WrittenBookItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
boolean isValid = isValidWrittenBook(tag);
|
||||
if (!isValid) {
|
||||
tag.remove("pages");
|
||||
}
|
||||
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (!isValid) {
|
||||
CompoundTag invalidTagPage = new CompoundTag("");
|
||||
invalidTagPage.put(new StringTag("photoname", ""));
|
||||
invalidTagPage.put(new StringTag(
|
||||
"text",
|
||||
MessageTranslator.convertMessage(
|
||||
Component.translatable("book.invalid.tag", NamedTextColor.DARK_RED),
|
||||
session.locale()
|
||||
)
|
||||
));
|
||||
tag.put(new ListTag("pages", List.of(invalidTagPage)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidWrittenBook(CompoundTag tag) {
|
||||
if (!(tag.get("title") instanceof StringTag title)) {
|
||||
return false;
|
||||
}
|
||||
if (title.getValue().length() > (MAXIMUM_TITLE_LENGTH * 2)) {
|
||||
// Java rejects books with titles more than 2x the maximum length allowed in the input box
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tag.get("author") instanceof StringTag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tag.get("pages") instanceof ListTag pages)) {
|
||||
return false;
|
||||
}
|
||||
for (Tag pageTag : pages) {
|
||||
if (pageTag instanceof StringTag page) {
|
||||
if (page.getValue().length() > MAXIMUM_PAGE_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ public final class BlockStateValues {
|
|||
private static final IntSet ALL_CAULDRONS = new IntOpenHashSet();
|
||||
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
|
||||
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
|
||||
private static final Int2IntMap BRUSH_PROGRESS = new Int2IntOpenHashMap();
|
||||
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
|
||||
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
|
@ -98,6 +99,15 @@ public final class BlockStateValues {
|
|||
return;
|
||||
}
|
||||
|
||||
JsonNode bedrockStates = blockData.get("bedrock_states");
|
||||
if (bedrockStates != null) {
|
||||
JsonNode brushedProgress = bedrockStates.get("brushed_progress");
|
||||
if (brushedProgress != null) {
|
||||
BRUSH_PROGRESS.put(javaBlockState, brushedProgress.intValue());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (javaId.contains("command_block")) {
|
||||
COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
|
||||
return;
|
||||
|
@ -225,6 +235,17 @@ public final class BlockStateValues {
|
|||
return BED_COLORS.getOrDefault(state, (byte) -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brush progress of suspicious sand/gravel is not sent by the java server when it updates the block entity.
|
||||
* Although brush progress is part of the bedrock block state, it must be included in the block entity update.
|
||||
*
|
||||
* @param state BlockState of the block
|
||||
* @return brush progress or 0 if the lookup failed
|
||||
*/
|
||||
public static int getBrushProgress(int state) {
|
||||
return BRUSH_PROGRESS.getOrDefault(state, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
|
||||
*
|
||||
|
|
|
@ -28,12 +28,8 @@ package org.geysermc.geyser.network;
|
|||
import com.github.steveice10.mc.protocol.codec.MinecraftCodec;
|
||||
import com.github.steveice10.mc.protocol.codec.PacketCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v557.Bedrock_v557;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v568.Bedrock_v568;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
|
@ -49,9 +45,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 = Bedrock_v582.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.81")
|
||||
.build();
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v589.CODEC;
|
||||
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
*/
|
||||
|
@ -64,20 +59,10 @@ public final class GameProtocol {
|
|||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.40/1.19.41")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v560.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.50/1.19.51")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v567.CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v568.CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v575.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.70/1.19.71/1.19.73")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v582.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.80/1.19.81")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,16 +81,8 @@ public final class GameProtocol {
|
|||
|
||||
/* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */
|
||||
|
||||
public static boolean supports1_19_50(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() >= Bedrock_v560.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
public static boolean supports1_19_60(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() >= Bedrock_v567.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
public static boolean supports1_19_80(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() >= Bedrock_v582.CODEC.getProtocolVersion();
|
||||
public static boolean isPre1_20(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() < Bedrock_v589.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,11 +47,6 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
|
|||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postInitChannel(Channel channel) throws Exception {
|
||||
super.postInitChannel(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
|
||||
try {
|
||||
|
|
|
@ -28,8 +28,6 @@ package org.geysermc.geyser.network;
|
|||
import io.netty.buffer.Unpooled;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v568.Bedrock_v568;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
|
||||
|
@ -51,9 +49,12 @@ import org.cloudburstmc.protocol.common.PacketSignal;
|
|||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.pack.ResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -63,15 +64,20 @@ import org.geysermc.geyser.util.LoginEncryptionUtils;
|
|||
import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
private Deque<String> packsToSent = new ArrayDeque<>();
|
||||
private boolean networkSettingsRequested = false;
|
||||
private final Deque<String> packsToSent = new ArrayDeque<>();
|
||||
|
||||
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
|
||||
|
||||
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
|
||||
super(geyser, session);
|
||||
|
@ -87,8 +93,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
return translateAndDefault(packet);
|
||||
}
|
||||
|
||||
private boolean newProtocol = false; // TEMPORARY
|
||||
|
||||
private boolean setCorrectCodec(int protocolVersion) {
|
||||
BedrockCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
|
||||
if (packetCodec == null) {
|
||||
|
@ -127,9 +131,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
|
||||
@Override
|
||||
public PacketSignal handle(RequestNetworkSettingsPacket packet) {
|
||||
if (setCorrectCodec(packet.getProtocolVersion())) {
|
||||
newProtocol = true;
|
||||
} else {
|
||||
if (!setCorrectCodec(packet.getProtocolVersion())) {
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
|
@ -143,6 +145,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
|
||||
session.getUpstream().getSession().setCompression(algorithm);
|
||||
session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
|
||||
networkSettingsRequested = true;
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
|
@ -154,12 +157,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
// session.getUpstream().getSession().getCodec() == null
|
||||
|
||||
if (!newProtocol) {
|
||||
if (!setCorrectCodec(loginPacket.getProtocolVersion())) { // REMOVE WHEN ONLY 1.19.30 IS SUPPORTED OR 1.20
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
if (!networkSettingsRequested) {
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", GameProtocol.getAllSupportedBedrockVersions()));
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
// Set the block translation based off of version
|
||||
|
@ -173,23 +173,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
// Hack for... whatever this is
|
||||
if (loginPacket.getProtocolVersion() == Bedrock_v567.CODEC.getProtocolVersion() && !session.getClientData().getGameVersion().equals("1.19.60")) {
|
||||
session.getUpstream().getSession().setCodec(Bedrock_v568.CODEC);
|
||||
}
|
||||
|
||||
PlayStatusPacket playStatus = new PlayStatusPacket();
|
||||
playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
|
||||
session.sendUpstreamPacket(playStatus);
|
||||
|
||||
geyser.getSessionManager().addPendingSession(session);
|
||||
|
||||
this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()));
|
||||
this.geyser.eventBus().fire(this.resourcePackLoadEvent);
|
||||
|
||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) {
|
||||
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader();
|
||||
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
|
||||
PackCodec codec = pack.codec();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
|
||||
header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(),
|
||||
resourcePack.getContentKey(), "", header.getUuid().toString(), false, false));
|
||||
header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(),
|
||||
"", header.uuid().toString(), false, false));
|
||||
}
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
||||
session.sendUpstreamPacket(resourcePacksInfo);
|
||||
|
@ -222,9 +221,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
|
||||
stackPacket.setGameVersion(session.getClientData().getGameVersion());
|
||||
|
||||
for (ResourcePack pack : ResourcePack.PACKS.values()) {
|
||||
ResourcePackManifest.Header header = pack.getManifest().getHeader();
|
||||
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), ""));
|
||||
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), ""));
|
||||
}
|
||||
|
||||
if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
|
@ -232,6 +231,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true));
|
||||
}
|
||||
|
||||
if (GameProtocol.isPre1_20(session)) {
|
||||
stackPacket.getExperiments().add(new ExperimentData("next_major_update", true));
|
||||
stackPacket.getExperiments().add(new ExperimentData("sniffer", true));
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(stackPacket);
|
||||
break;
|
||||
|
||||
|
@ -298,21 +302,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
@Override
|
||||
public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString());
|
||||
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString());
|
||||
PackCodec codec = pack.codec();
|
||||
|
||||
data.setChunkIndex(packet.getChunkIndex());
|
||||
data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE);
|
||||
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setPackVersion(packet.getPackVersion());
|
||||
data.setPackId(packet.getPackId());
|
||||
|
||||
int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE;
|
||||
long remainingSize = pack.getFile().length() - offset;
|
||||
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)];
|
||||
int offset = packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE;
|
||||
long remainingSize = codec.size() - offset;
|
||||
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, GeyserResourcePack.CHUNK_SIZE)];
|
||||
|
||||
try (InputStream inputStream = new FileInputStream(pack.getFile())) {
|
||||
inputStream.skip(offset);
|
||||
inputStream.read(packData, 0, packData.length);
|
||||
} catch (Exception e) {
|
||||
try (SeekableByteChannel channel = codec.serialize(pack)) {
|
||||
channel.position(offset);
|
||||
channel.read(ByteBuffer.wrap(packData, 0, packData.length));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
@ -321,7 +326,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
session.sendUpstreamPacket(data);
|
||||
|
||||
// Check if it is the last chunk and send next pack in queue when available.
|
||||
if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
||||
if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
||||
sendPackDataInfo(packsToSent.pop());
|
||||
}
|
||||
|
||||
|
@ -331,15 +336,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
private void sendPackDataInfo(String id) {
|
||||
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
|
||||
String[] packID = id.split("_");
|
||||
ResourcePack pack = ResourcePack.PACKS.get(packID[0]);
|
||||
ResourcePackManifest.Header header = pack.getManifest().getHeader();
|
||||
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
|
||||
PackCodec codec = pack.codec();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
|
||||
data.setPackId(header.getUuid());
|
||||
int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE);
|
||||
data.setPackId(header.uuid());
|
||||
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setChunkCount(chunkCount);
|
||||
data.setCompressedPackSize(pack.getFile().length());
|
||||
data.setMaxChunkSize(ResourcePack.CHUNK_SIZE);
|
||||
data.setHash(pack.getSha256());
|
||||
data.setCompressedPackSize(codec.size());
|
||||
data.setMaxChunkSize(GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setHash(codec.sha256());
|
||||
data.setPackVersion(packID[1]);
|
||||
data.setPremium(false);
|
||||
data.setType(ResourcePackType.RESOURCES);
|
||||
|
|
|
@ -187,7 +187,16 @@ public final class GeyserServer {
|
|||
|
||||
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
|
||||
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
|
||||
String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
|
||||
String ip;
|
||||
if (geyser.getConfig().isLogPlayerIpAddresses()) {
|
||||
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
|
||||
} else {
|
||||
ip = inetSocketAddress.toString();
|
||||
}
|
||||
} else {
|
||||
ip = "<IP address withheld>";
|
||||
}
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip));
|
||||
}
|
||||
|
||||
|
|
|
@ -73,10 +73,10 @@ public class ProxyServerHandler extends SimpleChannelInboundHandler<DatagramPack
|
|||
presentAddress = new InetSocketAddress(decoded.sourceAddress(), decoded.sourcePort());
|
||||
log.debug("Got PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||
GeyserImpl.getInstance().getGeyserServer().getProxiedAddresses().put(packet.sender(), presentAddress);
|
||||
return;
|
||||
} else {
|
||||
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||
}
|
||||
|
||||
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||
ctx.fireChannelRead(packet.retain());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
|
||||
public record GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) implements ResourcePack {
|
||||
|
||||
/**
|
||||
* The size of each chunk to use when sending the resource packs to clients in bytes
|
||||
*/
|
||||
public static final int CHUNK_SIZE = 102400;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public record GeyserResourcePackManifest(@JsonProperty("format_version") int formatVersion, Header header, Collection<Module> modules, Collection<Dependency> dependencies) implements ResourcePackManifest {
|
||||
|
||||
public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
|
||||
|
||||
public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
|
||||
|
||||
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { }
|
||||
|
||||
@JsonDeserialize(using = Version.VersionDeserializer.class)
|
||||
public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return major + "." + minor + "." + patch;
|
||||
}
|
||||
|
||||
public static class VersionDeserializer extends JsonDeserializer<Version> {
|
||||
@Override
|
||||
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
int[] version = ctxt.readValue(p, int[].class);
|
||||
return new Version(version[0], version[1], version[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* This represents a resource pack and all the data relevant to it
|
||||
*/
|
||||
public class ResourcePack {
|
||||
/**
|
||||
* The list of loaded resource packs
|
||||
*/
|
||||
public static final Map<String, ResourcePack> PACKS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The size of each chunk to use when sending the resource packs to clients in bytes
|
||||
*/
|
||||
public static final int CHUNK_SIZE = 102400;
|
||||
|
||||
private byte[] sha256;
|
||||
private File file;
|
||||
private ResourcePackManifest manifest;
|
||||
private ResourcePackManifest.Version version;
|
||||
|
||||
@Getter
|
||||
private String contentKey;
|
||||
|
||||
/**
|
||||
* Loop through the packs directory and locate valid resource pack files
|
||||
*/
|
||||
public static void loadPacks() {
|
||||
Path directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs");
|
||||
|
||||
if (!Files.exists(directory)) {
|
||||
try {
|
||||
Files.createDirectory(directory);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
||||
}
|
||||
|
||||
// As we just created the directory it will be empty
|
||||
return;
|
||||
}
|
||||
|
||||
List<Path> resourcePacks;
|
||||
try {
|
||||
resourcePacks = Files.walk(directory).collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
for (Path path : event.resourcePacks()) {
|
||||
File file = path.toFile();
|
||||
|
||||
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
|
||||
ResourcePack pack = new ResourcePack();
|
||||
|
||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
||||
|
||||
try (ZipFile zip = new ZipFile(file);
|
||||
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||
stream.forEach((x) -> {
|
||||
String name = x.getName();
|
||||
if (name.length() >= 80) {
|
||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + file.getName()
|
||||
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
||||
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
||||
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
||||
}
|
||||
if (name.contains("manifest.json")) {
|
||||
try {
|
||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
||||
// Sometimes a pack_manifest file is present and not in a valid format,
|
||||
// but a manifest file is, so we null check through that one
|
||||
if (manifest.getHeader().getUuid() != null) {
|
||||
pack.file = file;
|
||||
pack.manifest = manifest;
|
||||
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
|
||||
|
||||
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
||||
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
||||
File keyFile = new File(file.getParentFile(), file.getName() + ".key");
|
||||
pack.contentKey = keyFile.exists() ? Files.readString(keyFile.toPath(), StandardCharsets.UTF_8) : "";
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getSha256() {
|
||||
return sha256;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public ResourcePackManifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public ResourcePackManifest.Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* author: NukkitX
|
||||
* Nukkit Project
|
||||
*/
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class ResourcePackManifest {
|
||||
@JsonProperty("format_version")
|
||||
private Integer formatVersion;
|
||||
private Header header;
|
||||
private Collection<Module> modules;
|
||||
protected Collection<Dependency> dependencies;
|
||||
|
||||
public Collection<Module> getModules() {
|
||||
return Collections.unmodifiableCollection(modules);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Header {
|
||||
private String description;
|
||||
private String name;
|
||||
private UUID uuid;
|
||||
private int[] version;
|
||||
@JsonProperty("min_engine_version")
|
||||
private int[] minimumSupportedMinecraftVersion;
|
||||
|
||||
public String getVersionString() {
|
||||
return version[0] + "." + version[1] + "." + version[2];
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Module {
|
||||
private String description;
|
||||
private String name;
|
||||
private UUID uuid;
|
||||
private int[] version;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Dependency {
|
||||
private UUID uuid;
|
||||
private int[] version;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class Version {
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
|
||||
public static Version fromString(String ver) {
|
||||
String[] split = ver.replace(']', ' ')
|
||||
.replace('[', ' ')
|
||||
.replaceAll(" ", "").split(",");
|
||||
|
||||
return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
|
||||
}
|
||||
|
||||
public static Version fromArray(int[] ver) {
|
||||
return new Version(ver[0], ver[1], ver[2]);
|
||||
}
|
||||
|
||||
private Version(int major, int minor, int patch) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return major + "." + minor + "." + patch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack.path;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserPathPackCodec extends PathPackCodec {
|
||||
private final Path path;
|
||||
private FileTime lastModified;
|
||||
|
||||
private byte[] sha256;
|
||||
private long size = -1;
|
||||
|
||||
@Override
|
||||
public @NonNull Path path() {
|
||||
this.checkLastModified();
|
||||
return this.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NonNull [] sha256() {
|
||||
this.checkLastModified();
|
||||
if (this.sha256 != null) {
|
||||
return this.sha256;
|
||||
}
|
||||
|
||||
return this.sha256 = FileUtils.calculateSHA256(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
this.checkLastModified();
|
||||
if (this.size != -1) {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.size = Files.size(this.path);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not get file size of path " + this.path, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
|
||||
return FileChannel.open(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull ResourcePack create() {
|
||||
return ResourcePackLoader.readPack(this.path);
|
||||
}
|
||||
|
||||
private void checkLastModified() {
|
||||
try {
|
||||
FileTime lastModified = Files.getLastModifiedTime(this.path);
|
||||
if (this.lastModified == null) {
|
||||
this.lastModified = lastModified;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastModified.toInstant().isAfter(this.lastModified.toInstant())) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Detected a change in the resource pack " + path + ". This is likely to cause undefined behavior for new clients joining. It is suggested you restart Geyser.");
|
||||
this.lastModified = lastModified;
|
||||
this.sha256 = null;
|
||||
this.size = -1;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoader;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A deferred registry is a registry that is not loaded until it is needed.
|
||||
* This is useful for registries that are not needed until after other parts
|
||||
* of the lifecycle have been completed.
|
||||
* <p>
|
||||
* This class is slightly different from other registries in that it acts as
|
||||
* a wrapper around another registry. This is to allow for any kind of registry
|
||||
* type to be deferred.
|
||||
*
|
||||
* @param <M> the value being held by the registry
|
||||
*/
|
||||
public final class DeferredRegistry<M> implements IRegistry<M> {
|
||||
private final Registry<M> backingRegistry;
|
||||
private final Supplier<M> loader;
|
||||
|
||||
private boolean loaded;
|
||||
|
||||
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
|
||||
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.load(null);
|
||||
}
|
||||
|
||||
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.get().load(null);
|
||||
}
|
||||
|
||||
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
|
||||
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.load(input);
|
||||
}
|
||||
|
||||
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.get().load(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying value held by this registry.
|
||||
*
|
||||
* @return the underlying value held by this registry
|
||||
* @throws IllegalStateException if this deferred registry has not been loaded yet
|
||||
*/
|
||||
@Override
|
||||
public M get() {
|
||||
if (!this.loaded) {
|
||||
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||
}
|
||||
|
||||
return this.backingRegistry.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(M mappings) {
|
||||
this.backingRegistry.set(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers what is specified in the given {@link Consumer} into the underlying value.
|
||||
*
|
||||
* @param consumer the consumer
|
||||
* @throws IllegalStateException if this deferred registry has not been loaded yet
|
||||
*/
|
||||
@Override
|
||||
public void register(Consumer<M> consumer) {
|
||||
if (!this.loaded) {
|
||||
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||
}
|
||||
|
||||
this.backingRegistry.register(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the registry.
|
||||
*/
|
||||
public void load() {
|
||||
this.backingRegistry.set(this.loader.get());
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryLoader the registry loader
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
|
||||
return new DeferredRegistry<>(registryLoader, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryLoader the registry loader
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
return new DeferredRegistry<>(registryLoader, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryInitializer the registry initializer
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
|
||||
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryInitializer the registry initializer
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry initializer.
|
||||
*
|
||||
* @param <M> the registry type
|
||||
*/
|
||||
interface RegistryInitializer<M> {
|
||||
|
||||
/**
|
||||
* Initializes the registry.
|
||||
*
|
||||
* @param input the input
|
||||
* @param registryLoader the registry loader
|
||||
* @param <I> the input type
|
||||
* @return the initialized registry
|
||||
*/
|
||||
<I> Registry<M> initialize(I input, RegistryLoader<I, M> registryLoader);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a registry.
|
||||
*
|
||||
* @param <M> the value being held by the registry
|
||||
*/
|
||||
interface IRegistry<M> {
|
||||
|
||||
/**
|
||||
* Gets the underlying value held by this registry.
|
||||
*
|
||||
* @return the underlying value held by this registry.
|
||||
*/
|
||||
M get();
|
||||
|
||||
/**
|
||||
* Sets the underlying value held by this registry.
|
||||
* Clears any existing data associated with the previous
|
||||
* value.
|
||||
*
|
||||
* @param mappings the underlying value held by this registry
|
||||
*/
|
||||
void set(M mappings);
|
||||
|
||||
/**
|
||||
* Registers what is specified in the given
|
||||
* {@link Consumer} into the underlying value.
|
||||
*
|
||||
* @param consumer the consumer
|
||||
*/
|
||||
void register(Consumer<M> consumer);
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDelimiterPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket;
|
||||
import io.netty.channel.EventLoop;
|
||||
|
@ -44,6 +45,7 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
|
|||
static {
|
||||
IGNORED_PACKETS.add(ClientboundLightUpdatePacket.class); // Light is handled on Bedrock for us
|
||||
IGNORED_PACKETS.add(ClientboundTabListPacket.class); // Cant be implemented in Bedrock
|
||||
IGNORED_PACKETS.add(ClientboundDelimiterPacket.class); // Not implemented, spams logs
|
||||
}
|
||||
|
||||
protected PacketTranslatorRegistry() {
|
||||
|
|
|
@ -41,6 +41,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
|
@ -158,6 +160,11 @@ public final class Registries {
|
|||
*/
|
||||
public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.
|
||||
*/
|
||||
public static final DeferredRegistry<Map<String, ResourcePack>> RESOURCE_PACKS = DeferredRegistry.create(GeyserImpl.getInstance().packDirectory(), SimpleMappedRegistry::create, RegistryLoaders.RESOURCE_PACKS);
|
||||
|
||||
/**
|
||||
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
|
||||
*/
|
||||
|
|
|
@ -64,7 +64,7 @@ import java.util.function.Consumer;
|
|||
*
|
||||
* @param <M> the value being held by the registry
|
||||
*/
|
||||
public abstract class Registry<M> {
|
||||
public abstract class Registry<M> implements IRegistry<M> {
|
||||
protected M mappings;
|
||||
|
||||
/**
|
||||
|
@ -85,6 +85,7 @@ public abstract class Registry<M> {
|
|||
*
|
||||
* @return the underlying value held by this registry.
|
||||
*/
|
||||
@Override
|
||||
public M get() {
|
||||
return this.mappings;
|
||||
}
|
||||
|
@ -96,6 +97,7 @@ public abstract class Registry<M> {
|
|||
*
|
||||
* @param mappings the underlying value held by this registry
|
||||
*/
|
||||
@Override
|
||||
public void set(M mappings) {
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
@ -106,6 +108,7 @@ public abstract class Registry<M> {
|
|||
*
|
||||
* @param consumer the consumer
|
||||
*/
|
||||
@Override
|
||||
public void register(Consumer<M> consumer) {
|
||||
consumer.accept(this.mappings);
|
||||
}
|
||||
|
|
|
@ -31,13 +31,16 @@ import org.geysermc.geyser.api.extension.Extension;
|
|||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.registry.provider.ProviderSupplier;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -47,11 +50,15 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
|||
|
||||
@Override
|
||||
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
||||
// misc
|
||||
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
|
||||
|
||||
// items
|
||||
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
|
||||
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
|
||||
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
|
||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
|
|
@ -36,7 +36,12 @@ public final class RegistryLoaders {
|
|||
/**
|
||||
* The {@link RegistryLoader} responsible for loading NBT.
|
||||
*/
|
||||
public static NbtRegistryLoader NBT = new NbtRegistryLoader();
|
||||
public static final NbtRegistryLoader NBT = new NbtRegistryLoader();
|
||||
|
||||
/**
|
||||
* The {@link RegistryLoader} responsible for loading resource packs.
|
||||
*/
|
||||
public static final ResourcePackLoader RESOURCE_PACKS = new ResourcePackLoader();
|
||||
|
||||
/**
|
||||
* Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does
|
||||
|
@ -51,10 +56,14 @@ public final class RegistryLoaders {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RegistryLoader} which has not taken
|
||||
* in any input value.
|
||||
*
|
||||
* @param <I> the input
|
||||
* @param <V> the value
|
||||
* @return a RegistryLoader that is yet to contain a value.
|
||||
*/
|
||||
public static <V> RegistryLoader<Object, V> uninitialized() {
|
||||
public static <I, V> RegistryLoader<I, V> uninitialized() {
|
||||
return input -> null;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePackManifest;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserLoadResourcePacksEvent}.
|
||||
*/
|
||||
public class ResourcePackLoader implements RegistryLoader<Path, Map<String, ResourcePack>> {
|
||||
|
||||
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
||||
|
||||
private static final boolean SHOW_RESOURCE_PACK_LENGTH_WARNING = Boolean.parseBoolean(System.getProperty("Geyser.ShowResourcePackLengthWarning", "true"));
|
||||
|
||||
/**
|
||||
* Loop through the packs directory and locate valid resource pack files
|
||||
*/
|
||||
@Override
|
||||
public Map<String, ResourcePack> load(Path directory) {
|
||||
Map<String, ResourcePack> packMap = new HashMap<>();
|
||||
|
||||
if (!Files.exists(directory)) {
|
||||
try {
|
||||
Files.createDirectory(directory);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
List<Path> resourcePacks;
|
||||
try (Stream<Path> stream = Files.walk(directory)) {
|
||||
resourcePacks = stream.filter(PACK_MATCHER::matches)
|
||||
.collect(Collectors.toCollection(ArrayList::new)); // toList() does not guarantee mutability
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
||||
|
||||
// Ensure the event is fired even if there was an issue reading
|
||||
// from our own resource pack directory. External projects may have
|
||||
// resource packs located at different locations.
|
||||
resourcePacks = new ArrayList<>();
|
||||
}
|
||||
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
for (Path path : event.resourcePacks()) {
|
||||
try {
|
||||
GeyserResourcePack pack = readPack(path);
|
||||
packMap.put(pack.manifest().header().uuid().toString(), pack);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return packMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a resource pack at the given file. Also searches for a file in the same directory, with the same name
|
||||
* but suffixed by ".key", containing the content key. If such file does not exist, no content key is stored.
|
||||
*
|
||||
* @param path the file to read from, in ZIP format
|
||||
* @return a {@link ResourcePack} representation
|
||||
* @throws IllegalArgumentException if the pack manifest was invalid or there was any processing exception
|
||||
*/
|
||||
public static GeyserResourcePack readPack(Path path) throws IllegalArgumentException {
|
||||
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
|
||||
throw new IllegalArgumentException("Resource pack " + path.getFileName() + " must be a .zip or .mcpack file!");
|
||||
}
|
||||
|
||||
AtomicReference<GeyserResourcePackManifest> manifestReference = new AtomicReference<>();
|
||||
|
||||
try (ZipFile zip = new ZipFile(path.toFile());
|
||||
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||
stream.forEach(x -> {
|
||||
String name = x.getName();
|
||||
if (SHOW_RESOURCE_PACK_LENGTH_WARNING && name.length() >= 80) {
|
||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + path.getFileName()
|
||||
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
||||
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
||||
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
||||
}
|
||||
if (name.contains("manifest.json")) {
|
||||
try {
|
||||
GeyserResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), GeyserResourcePackManifest.class);
|
||||
if (manifest.header().uuid() != null) {
|
||||
manifestReference.set(manifest);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
GeyserResourcePackManifest manifest = manifestReference.get();
|
||||
if (manifest == null) {
|
||||
throw new IllegalArgumentException(path.getFileName() + " does not contain a valid pack_manifest.json or manifest.json");
|
||||
}
|
||||
|
||||
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
||||
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
||||
Path keyFile = path.resolveSibling(path.getFileName().toString() + ".key");
|
||||
String contentKey = Files.exists(keyFile) ? Files.readString(path, StandardCharsets.UTF_8) : "";
|
||||
|
||||
return new GeyserResourcePack(new GeyserPathPackCodec(path), manifest, contentKey);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path.getFileName()), e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,11 +32,8 @@ import com.google.common.collect.Interner;
|
|||
import com.google.common.collect.Interners;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.cloudburstmc.nbt.*;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
|
@ -70,41 +67,54 @@ public final class BlockRegistryPopulator {
|
|||
}
|
||||
|
||||
private static void registerBedrockBlocks() {
|
||||
BiFunction<String, NbtMapBuilder, String> woolMapper = (bedrockIdentifier, statesBuilder) -> {
|
||||
if (bedrockIdentifier.equals("minecraft:wool")) {
|
||||
String color = (String) statesBuilder.remove("color");
|
||||
if ("silver".equals(color)) {
|
||||
color = "light_gray";
|
||||
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
|
||||
|
||||
// We are using mappings that directly support 1.20, so this maps it back to 1.19.80
|
||||
BiFunction<String, NbtMapBuilder, String> legacyMapper = (bedrockIdentifier, statesBuilder) -> {
|
||||
if (bedrockIdentifier.endsWith("pumpkin")) {
|
||||
String direction = statesBuilder.remove("minecraft:cardinal_direction").toString();
|
||||
statesBuilder.putInt("direction", switch (direction) {
|
||||
case "north" -> 2;
|
||||
case "east" -> 3;
|
||||
case "west" -> 1;
|
||||
default -> 0; // south
|
||||
});
|
||||
} else if (bedrockIdentifier.endsWith("carpet") && !bedrockIdentifier.startsWith("minecraft:moss")) {
|
||||
String color = bedrockIdentifier.replace("minecraft:", "").replace("_carpet", "");
|
||||
if (color.equals("light_gray")) {
|
||||
color = "silver";
|
||||
}
|
||||
return "minecraft:" + color + "_wool";
|
||||
statesBuilder.putString("color", color);
|
||||
return "minecraft:carpet";
|
||||
} else if (bedrockIdentifier.equals("minecraft:sniffer_egg")) {
|
||||
statesBuilder.remove("cracked_state");
|
||||
return "minecraft:dragon_egg";
|
||||
} else if (bedrockIdentifier.endsWith("coral")) {
|
||||
statesBuilder.putString("coral_color", "blue"); // all blue
|
||||
statesBuilder.putBoolean("dead_bit", bedrockIdentifier.startsWith("minecraft:dead"));
|
||||
return "minecraft:coral";
|
||||
} else if (bedrockIdentifier.endsWith("sculk_sensor")) {
|
||||
int phase = (int) statesBuilder.remove("sculk_sensor_phase");
|
||||
statesBuilder.putBoolean("powered_bit", phase != 0);
|
||||
} else if (bedrockIdentifier.endsWith("pitcher_plant")) {
|
||||
statesBuilder.putString("double_plant_type", "sunflower");
|
||||
return "minecraft:double_plant";
|
||||
} else if (bedrockIdentifier.endsWith("pitcher_crop")) {
|
||||
statesBuilder.remove("growth");
|
||||
if (((byte) statesBuilder.remove("upper_block_bit")) == 1){
|
||||
statesBuilder.putString("flower_type", "orchid");
|
||||
return "minecraft:red_flower"; // top
|
||||
}
|
||||
statesBuilder.putBoolean("update_bit", false);
|
||||
return "minecraft:flower_pot"; // bottom
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
|
||||
|
||||
ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> blockMappers = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
|
||||
.put(ObjectIntPair.of("1_19_20", Bedrock_v544.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_19_50", Bedrock_v560.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_19_60", Bedrock_v567.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_19_70", Bedrock_v575.CODEC.getProtocolVersion()), woolMapper)
|
||||
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> {
|
||||
String identifier = woolMapper.apply(bedrockIdentifier, statesBuilder);
|
||||
if (identifier != null) {
|
||||
return identifier;
|
||||
}
|
||||
switch (bedrockIdentifier) {
|
||||
case "minecraft:log", "minecraft:log2" -> {
|
||||
String woodType = (String) statesBuilder.remove(bedrockIdentifier.equals("minecraft:log") ? "old_log_type" : "new_log_type");
|
||||
return "minecraft:" + woodType + "_log";
|
||||
}
|
||||
case "minecraft:fence" -> {
|
||||
String woodType = (String) statesBuilder.remove("wood_type");
|
||||
return "minecraft:" + woodType + "_fence";
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), legacyMapper)
|
||||
.put(ObjectIntPair.of("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.build();
|
||||
|
||||
// We can keep this strong as nothing should be garbage collected
|
||||
|
@ -167,8 +177,8 @@ public final class BlockRegistryPopulator {
|
|||
|
||||
GeyserBedrockBlock bedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
if (bedrockDefinition == null) {
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition! Built NBT tag: \n" +
|
||||
buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition on version "
|
||||
+ palette.getKey().key() + "! Built NBT tag: \n" + buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
}
|
||||
|
||||
switch (javaId) {
|
||||
|
|
|
@ -54,18 +54,18 @@ public class CreativeItemRegistryPopulator {
|
|||
(identifier, data) -> identifier.equals("minecraft:bordure_indented_banner_pattern") || identifier.equals("minecraft:field_masoned_banner_pattern")
|
||||
);
|
||||
|
||||
static void populate(Map.Entry<String, ItemRegistryPopulator.PaletteVersion> version, Map<String, ItemDefinition> definitions, Consumer<ItemData.Builder> itemConsumer) {
|
||||
static void populate(ItemRegistryPopulator.PaletteVersion palette, Map<String, ItemDefinition> definitions, Consumer<ItemData.Builder> itemConsumer) {
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
// Load creative items
|
||||
JsonNode creativeItemEntries;
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", version.getKey()))) {
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", palette.version()))) {
|
||||
creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load creative items", e);
|
||||
}
|
||||
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(version.getValue().protocolVersion());
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
|
||||
for (JsonNode itemNode : creativeItemEntries) {
|
||||
ItemData.Builder itemBuilder = createItemData(itemNode, blockMappings, definitions);
|
||||
if (itemBuilder == null) {
|
||||
|
|
|
@ -34,14 +34,12 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
|||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
|
||||
|
@ -70,22 +68,40 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
*/
|
||||
public class ItemRegistryPopulator {
|
||||
|
||||
record PaletteVersion(int protocolVersion, Map<Item, String> additionalTranslatedItems) {
|
||||
record PaletteVersion(String version, int protocolVersion, Map<Item, String> javaOnlyItems, Remapper remapper) {
|
||||
|
||||
public PaletteVersion(String version, int protocolVersion) {
|
||||
this(version, protocolVersion, Collections.emptyMap(), (item, mapping) -> mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Remapper {
|
||||
@NonNull
|
||||
GeyserMappingItem remap(Item item, GeyserMappingItem mapping);
|
||||
}
|
||||
|
||||
public static void populate() {
|
||||
Map<Item, String> manualFallback = new HashMap<>();
|
||||
manualFallback.put(Items.ENDER_DRAGON_SPAWN_EGG, "minecraft:enderman_spawn_egg");
|
||||
manualFallback.put(Items.WITHER_SPAWN_EGG, "minecraft:wither_skeleton_spawn_egg");
|
||||
manualFallback.put(Items.SNOW_GOLEM_SPAWN_EGG, "minecraft:polar_bear_spawn_egg");
|
||||
manualFallback.put(Items.IRON_GOLEM_SPAWN_EGG, "minecraft:villager_spawn_egg");
|
||||
Map<Item, String> legacyJavaOnly = new HashMap<>();
|
||||
legacyJavaOnly.put(Items.MUSIC_DISC_RELIC, "minecraft:music_disc_wait");
|
||||
legacyJavaOnly.put(Items.PITCHER_PLANT, "minecraft:chorus_flower");
|
||||
legacyJavaOnly.put(Items.PITCHER_POD, "minecraft:beetroot");
|
||||
legacyJavaOnly.put(Items.SNIFFER_EGG, "minecraft:sniffer_spawn_egg"); // the BlockItem of the sniffer egg block
|
||||
|
||||
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
||||
paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.CODEC.getProtocolVersion(), manualFallback));
|
||||
paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.CODEC.getProtocolVersion(), manualFallback));
|
||||
paletteVersions.put("1_19_60", new PaletteVersion(Bedrock_v567.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
paletteVersions.put("1_19_70", new PaletteVersion(Bedrock_v575.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
paletteVersions.put("1_19_80", new PaletteVersion(Bedrock_v582.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
List<PaletteVersion> paletteVersions = new ArrayList<>(2);
|
||||
paletteVersions.add(new PaletteVersion("1_19_80", Bedrock_v582.CODEC.getProtocolVersion(), legacyJavaOnly, (item, mapping) -> {
|
||||
String id = item.javaIdentifier();
|
||||
if (id.endsWith("pottery_sherd")) {
|
||||
return mapping.withBedrockIdentifier(id.replace("sherd", "shard"));
|
||||
} else if (id.endsWith("carpet") && !id.startsWith("minecraft:moss")) {
|
||||
return mapping.withBedrockIdentifier("minecraft:carpet");
|
||||
} else if (id.endsWith("coral")) {
|
||||
return mapping.withBedrockIdentifier("minecraft:coral");
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}));
|
||||
paletteVersions.add(new PaletteVersion("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()));
|
||||
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
|
@ -115,11 +131,11 @@ public class ItemRegistryPopulator {
|
|||
boolean firstMappingsPass = true;
|
||||
|
||||
/* Load item palette */
|
||||
for (Map.Entry<String, PaletteVersion> palette : paletteVersions.entrySet()) {
|
||||
for (PaletteVersion palette : paletteVersions) {
|
||||
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
|
||||
|
||||
List<PaletteItem> itemEntries;
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey()))) {
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.version()))) {
|
||||
itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load Bedrock runtime item IDs", e);
|
||||
|
@ -177,22 +193,16 @@ public class ItemRegistryPopulator {
|
|||
}
|
||||
});
|
||||
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.getValue().protocolVersion());
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
|
||||
|
||||
Set<Item> javaOnlyItems = new ObjectOpenHashSet<>();
|
||||
Collections.addAll(javaOnlyItems, Items.SPECTRAL_ARROW, Items.DEBUG_STICK,
|
||||
Items.KNOWLEDGE_BOOK, Items.TIPPED_ARROW, Items.BUNDLE);
|
||||
// these spawn eggs exist in 1.19.60+;
|
||||
if (palette.getValue().protocolVersion() < Bedrock_v567.CODEC.getProtocolVersion()) {
|
||||
Collections.addAll(javaOnlyItems, Items.IRON_GOLEM_SPAWN_EGG, Items.SNOW_GOLEM_SPAWN_EGG,
|
||||
Items.WITHER_SPAWN_EGG, Items.ENDER_DRAGON_SPAWN_EGG);
|
||||
}
|
||||
javaOnlyItems.add(Items.DECORATED_POT);
|
||||
if (!customItemsAllowed) {
|
||||
javaOnlyItems.add(Items.FURNACE_MINECART);
|
||||
}
|
||||
// Java-only items for this version
|
||||
javaOnlyItems.addAll(palette.getValue().additionalTranslatedItems().keySet());
|
||||
javaOnlyItems.addAll(palette.javaOnlyItems().keySet());
|
||||
|
||||
Int2ObjectMap<String> customIdMappings = new Int2ObjectOpenHashMap<>();
|
||||
Set<String> registeredItemNames = new ObjectOpenHashSet<>(); // This is used to check for duplicate item names
|
||||
|
@ -203,12 +213,12 @@ public class ItemRegistryPopulator {
|
|||
throw new RuntimeException("Extra item in mappings? " + entry.getKey());
|
||||
}
|
||||
GeyserMappingItem mappingItem;
|
||||
String replacementItem = palette.getValue().additionalTranslatedItems().get(javaItem);
|
||||
String replacementItem = palette.javaOnlyItems().get(javaItem);
|
||||
if (replacementItem != null) {
|
||||
mappingItem = items.get(replacementItem);
|
||||
mappingItem = items.get(replacementItem); // java only item, a java id fallback has been provided
|
||||
} else {
|
||||
// This items has a mapping specifically for this version of the game
|
||||
mappingItem = entry.getValue();
|
||||
// check if any mapping changes need to be made on this version
|
||||
mappingItem = palette.remapper().remap(javaItem, entry.getValue());
|
||||
}
|
||||
|
||||
if (customItemsAllowed && javaItem == Items.FURNACE_MINECART) {
|
||||
|
@ -217,26 +227,10 @@ public class ItemRegistryPopulator {
|
|||
continue;
|
||||
}
|
||||
|
||||
String bedrockIdentifier;
|
||||
// 1.19.70+
|
||||
if (palette.getValue().protocolVersion() >= Bedrock_v575.CODEC.getProtocolVersion() && mappingItem.getBedrockIdentifier().equals("minecraft:wool")) {
|
||||
bedrockIdentifier = javaItem.javaIdentifier();
|
||||
} else {
|
||||
bedrockIdentifier = mappingItem.getBedrockIdentifier();
|
||||
}
|
||||
|
||||
//1.19.80+
|
||||
if (palette.getValue().protocolVersion >= Bedrock_v582.CODEC.getProtocolVersion()) {
|
||||
if (mappingItem.getBedrockIdentifier().equals("minecraft:log") ||
|
||||
mappingItem.getBedrockIdentifier().equals("minecraft:log2") ||
|
||||
mappingItem.getBedrockIdentifier().equals("minecraft:fence")) {
|
||||
bedrockIdentifier = javaItem.javaIdentifier();
|
||||
}
|
||||
}
|
||||
|
||||
String bedrockIdentifier = mappingItem.getBedrockIdentifier();
|
||||
ItemDefinition definition = definitions.get(bedrockIdentifier);
|
||||
if (definition == null) {
|
||||
throw new RuntimeException("Missing Bedrock ItemDefinition in mappings: " + bedrockIdentifier);
|
||||
throw new RuntimeException("Missing Bedrock ItemDefinition in version " + palette.version() + " for mapping: " + mappingItem);
|
||||
}
|
||||
|
||||
BlockDefinition bedrockBlock = null;
|
||||
|
@ -430,7 +424,7 @@ public class ItemRegistryPopulator {
|
|||
} else if (javaItem.javaIdentifier().startsWith("minecraft:music_disc_")) {
|
||||
// The Java record level event uses the item ID as the "key" to play the record
|
||||
Registries.RECORDS.register(javaItem.javaId(), SoundEvent.valueOf("RECORD_" +
|
||||
javaItem.javaIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH)));
|
||||
mapping.getBedrockIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH)));
|
||||
}
|
||||
|
||||
mappings.add(mapping);
|
||||
|
@ -521,7 +515,7 @@ public class ItemRegistryPopulator {
|
|||
.customIdMappings(customIdMappings)
|
||||
.build();
|
||||
|
||||
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);
|
||||
Registries.ITEMS.register(palette.protocolVersion(), itemMappings);
|
||||
|
||||
firstMappingsPass = false;
|
||||
}
|
||||
|
|
|
@ -26,12 +26,22 @@
|
|||
package org.geysermc.geyser.registry.type;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.With;
|
||||
|
||||
/**
|
||||
* Represents Geyser's own serialized item information before being processed per-version
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@With
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class GeyserMappingItem {
|
||||
@JsonProperty("bedrock_identifier") String bedrockIdentifier;
|
||||
@JsonProperty("bedrock_data") int bedrockData;
|
||||
|
|
|
@ -56,7 +56,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server
|
|||
import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket;
|
||||
import com.github.steveice10.packetlib.BuiltinFlags;
|
||||
import com.github.steveice10.packetlib.Session;
|
||||
import com.github.steveice10.packetlib.event.session.*;
|
||||
import com.github.steveice10.packetlib.event.session.ConnectedEvent;
|
||||
import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
|
||||
import com.github.steveice10.packetlib.event.session.PacketErrorEvent;
|
||||
import com.github.steveice10.packetlib.event.session.PacketSendingEvent;
|
||||
import com.github.steveice10.packetlib.event.session.SessionAdapter;
|
||||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import com.github.steveice10.packetlib.tcp.TcpClientSession;
|
||||
import com.github.steveice10.packetlib.tcp.TcpSession;
|
||||
|
@ -81,6 +85,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
|||
import org.checkerframework.common.value.qual.IntRange;
|
||||
import org.cloudburstmc.math.vector.*;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
||||
import org.cloudburstmc.protocol.bedrock.data.*;
|
||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
|
||||
|
@ -93,7 +98,7 @@ import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
|||
import org.geysermc.api.util.BedrockPlatform;
|
||||
import org.geysermc.api.util.InputMode;
|
||||
import org.geysermc.api.util.UiProfile;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
|
@ -103,6 +108,7 @@ import org.geysermc.geyser.GeyserImpl;
|
|||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
@ -123,6 +129,7 @@ import org.geysermc.geyser.item.Items;
|
|||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.network.netty.LocalSession;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||
|
@ -877,6 +884,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
* After getting whatever credentials needed, we attempt to join the Java server.
|
||||
*/
|
||||
private void connectDownstream() {
|
||||
SessionLoginEvent loginEvent = new SessionLoginEvent(this, remoteServer);
|
||||
GeyserImpl.getInstance().eventBus().fire(loginEvent);
|
||||
if (loginEvent.isCancelled()) {
|
||||
String disconnectReason = loginEvent.disconnectReason() == null ?
|
||||
BedrockDisconnectReasons.DISCONNECTED : loginEvent.disconnectReason();
|
||||
disconnect(disconnectReason);
|
||||
return;
|
||||
}
|
||||
|
||||
this.remoteServer = loginEvent.remoteServer();
|
||||
boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE;
|
||||
|
||||
// Start ticking
|
||||
|
@ -1539,6 +1556,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
startGamePacket.setRewindHistorySize(0);
|
||||
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
||||
|
||||
if (GameProtocol.isPre1_20(this)) {
|
||||
startGamePacket.getExperiments().add(new ExperimentData("next_major_update", true));
|
||||
startGamePacket.getExperiments().add(new ExperimentData("sniffer", true));
|
||||
}
|
||||
|
||||
upstream.sendPacket(startGamePacket);
|
||||
}
|
||||
|
||||
|
@ -1942,8 +1964,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
}
|
||||
|
||||
EmotePacket packet = new EmotePacket();
|
||||
packet.setEmoteId(emoteId);
|
||||
packet.setRuntimeEntityId(entity.getGeyserId());
|
||||
packet.setXuid("");
|
||||
packet.setPlatformId(""); // BDS sends empty
|
||||
packet.setEmoteId(emoteId);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ public class BookEditCache {
|
|||
if ((System.currentTimeMillis() - lastBookUpdate) < 1000) {
|
||||
return;
|
||||
}
|
||||
// Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions
|
||||
// Don't send the update if the player is not holding a book, shouldn't happen if we catch all interactions
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
|
||||
if (itemStack == null || itemStack.asItem() != Items.WRITABLE_BOOK) {
|
||||
packet = null;
|
||||
|
|
|
@ -64,6 +64,7 @@ public class TagCache {
|
|||
private IntList foxFood;
|
||||
private IntList piglinLoved;
|
||||
private IntList smallFlowers;
|
||||
private IntList snifferFood;
|
||||
|
||||
public TagCache() {
|
||||
// Ensure all lists are non-null
|
||||
|
@ -101,6 +102,7 @@ public class TagCache {
|
|||
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
|
||||
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
|
||||
this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers"));
|
||||
this.snifferFood = load(itemTags.get("minecraft:sniffer_food"));
|
||||
|
||||
// Hack btw
|
||||
boolean emulatePost1_13Logic = itemTags.get("minecraft:signs").length > 1;
|
||||
|
@ -137,6 +139,7 @@ public class TagCache {
|
|||
this.foxFood = IntLists.emptyList();
|
||||
this.piglinLoved = IntLists.emptyList();
|
||||
this.smallFlowers = IntLists.emptyList();
|
||||
this.snifferFood = IntLists.emptyList();
|
||||
}
|
||||
|
||||
public boolean isAxolotlTemptItem(Item item) {
|
||||
|
@ -167,6 +170,10 @@ public class TagCache {
|
|||
return smallFlowers.contains(itemStack.getJavaId());
|
||||
}
|
||||
|
||||
public boolean isSnifferFood(Item item) {
|
||||
return snifferFood.contains(item.javaId());
|
||||
}
|
||||
|
||||
public boolean isAxeEffective(BlockMapping blockMapping) {
|
||||
return axeEffective.contains(blockMapping.getJavaBlockId());
|
||||
}
|
||||
|
|
|
@ -61,6 +61,10 @@ public final class WorldCache {
|
|||
private int currentSequence;
|
||||
private final Object2IntMap<Vector3i> unverifiedPredictions = new Object2IntOpenHashMap<>(1);
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean editingSignOnFront;
|
||||
|
||||
public WorldCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
this.scoreboard = new Scoreboard(session);
|
||||
|
|
|
@ -30,10 +30,9 @@ import org.geysermc.geyser.util.AssetUtils;
|
|||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
@ -80,14 +79,14 @@ public final class ProvidedSkins {
|
|||
.resolve(slim ? "slim" : "wide");
|
||||
String assetName = asset.substring(asset.lastIndexOf('/') + 1);
|
||||
|
||||
File location = folder.resolve(assetName).toFile();
|
||||
AssetUtils.addTask(!location.exists(), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
|
||||
Path location = folder.resolve(assetName);
|
||||
AssetUtils.addTask(!Files.exists(location), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
|
||||
(stream) -> AssetUtils.saveFile(location, stream),
|
||||
() -> {
|
||||
try {
|
||||
// TODO lazy initialize?
|
||||
BufferedImage image;
|
||||
try (InputStream stream = new FileInputStream(location)) {
|
||||
try (InputStream stream = Files.newInputStream(location)) {
|
||||
image = ImageIO.read(stream);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.geysermc.geyser.util.WebUtils;
|
|||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
@ -57,8 +58,8 @@ public class MinecraftLocale {
|
|||
}
|
||||
|
||||
public static void ensureEN_US() {
|
||||
File localeFile = getFile("en_us");
|
||||
AssetUtils.addTask(!localeFile.exists(), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
|
||||
Path localeFile = getPath("en_us");
|
||||
AssetUtils.addTask(!Files.exists(localeFile), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
|
||||
(stream) -> AssetUtils.saveFile(localeFile, stream),
|
||||
() -> {
|
||||
if ("en_us".equals(GeyserLocale.getDefaultLocale())) {
|
||||
|
@ -106,10 +107,10 @@ public class MinecraftLocale {
|
|||
if (locale.equals("en_us")) {
|
||||
return;
|
||||
}
|
||||
File localeFile = getFile(locale);
|
||||
Path localeFile = getPath(locale);
|
||||
|
||||
// Check if we have already downloaded the locale file
|
||||
if (localeFile.exists()) {
|
||||
if (Files.exists(localeFile)) {
|
||||
String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
||||
String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash();
|
||||
|
||||
|
@ -130,8 +131,8 @@ public class MinecraftLocale {
|
|||
}
|
||||
}
|
||||
|
||||
private static File getFile(String locale) {
|
||||
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile();
|
||||
private static Path getPath(String locale) {
|
||||
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -94,7 +94,7 @@ public abstract class InventoryTranslator {
|
|||
put(ContainerType.LOOM, new LoomInventoryTranslator());
|
||||
put(ContainerType.MERCHANT, new MerchantInventoryTranslator());
|
||||
put(ContainerType.SHULKER_BOX, new ShulkerInventoryTranslator());
|
||||
put(ContainerType.LEGACY_SMITHING, new SmithingInventoryTranslator());
|
||||
put(ContainerType.SMITHING, new SmithingInventoryTranslator());
|
||||
put(ContainerType.STONECUTTER, new StonecutterInventoryTranslator());
|
||||
|
||||
/* Lectern */
|
||||
|
@ -525,10 +525,28 @@ public abstract class InventoryTranslator {
|
|||
|
||||
int remainder = transferAction.getCount() % resultSize;
|
||||
int timesToCraft = transferAction.getCount() / resultSize;
|
||||
for (int i = 0; i < timesToCraft; i++) {
|
||||
plan.add(Click.LEFT, sourceSlot);
|
||||
plan.add(Click.LEFT, destSlot);
|
||||
|
||||
if (plan.getCursor().isEmpty()) {
|
||||
// No carried items - move to destination
|
||||
for (int i = 0; i < timesToCraft; i++) {
|
||||
plan.add(Click.LEFT, sourceSlot);
|
||||
plan.add(Click.LEFT, destSlot);
|
||||
}
|
||||
} else {
|
||||
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
||||
int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot);
|
||||
if (tempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
|
||||
for (int i = 0; i < timesToCraft; i++) {
|
||||
plan.add(Click.LEFT, sourceSlot); //pick up source item
|
||||
plan.add(Click.LEFT, destSlot); //place source item into dest slot
|
||||
}
|
||||
plan.add(Click.LEFT, tempSlot); //pick up original item
|
||||
}
|
||||
|
||||
if (remainder > 0) {
|
||||
plan.add(Click.LEFT, 0);
|
||||
for (int i = 0; i < remainder; i++) {
|
||||
|
|
|
@ -33,15 +33,16 @@ import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
|||
|
||||
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||
public SmithingInventoryTranslator() {
|
||||
super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
|
||||
super(4, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) {
|
||||
return switch (slotInfoData.getContainer()) {
|
||||
case SMITHING_TABLE_INPUT -> 0;
|
||||
case SMITHING_TABLE_MATERIAL -> 1;
|
||||
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 2;
|
||||
case SMITHING_TABLE_TEMPLATE -> 0;
|
||||
case SMITHING_TABLE_INPUT -> 1;
|
||||
case SMITHING_TABLE_MATERIAL -> 2;
|
||||
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 3;
|
||||
default -> super.bedrockSlotToJava(slotInfoData);
|
||||
};
|
||||
}
|
||||
|
@ -49,9 +50,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
|
|||
@Override
|
||||
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
|
||||
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
|
||||
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
|
||||
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
|
||||
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
|
||||
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
|
||||
case 3 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
|
||||
default -> super.javaSlotToBedrockContainer(slot);
|
||||
};
|
||||
}
|
||||
|
@ -59,9 +61,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
|
|||
@Override
|
||||
public int javaSlotToBedrock(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> 51;
|
||||
case 1 -> 52;
|
||||
case 2 -> 50;
|
||||
case 0 -> 53;
|
||||
case 1 -> 51;
|
||||
case 2 -> 52;
|
||||
case 3 -> 50;
|
||||
default -> super.javaSlotToBedrock(slot);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -62,19 +62,20 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
|
|||
}
|
||||
|
||||
StonecutterContainer container = (StonecutterContainer) inventory;
|
||||
ItemStack javaOutput = craftingData.output();
|
||||
int button = craftingData.buttonId();
|
||||
|
||||
// If we've already pressed the button with this item, no need to press it again!
|
||||
if (container.getStonecutterButton() != button) {
|
||||
ItemStack javaOutput = craftingData.output();
|
||||
|
||||
// Getting the index of the item in the Java stonecutter list
|
||||
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), button);
|
||||
session.sendDownstreamPacket(packet);
|
||||
container.setStonecutterButton(button);
|
||||
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
|
||||
// We don't know there is an output here, so we tell ourselves that there is
|
||||
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
|
||||
}
|
||||
}
|
||||
|
||||
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
|
||||
// We don't know there is an output here, so we tell ourselves that there is
|
||||
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
|
||||
}
|
||||
|
||||
return translateRequest(session, inventory, request);
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.BRUSHABLE_BLOCK)
|
||||
public class BrushableBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
|
||||
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
if (!(tag.remove("item") instanceof CompoundTag itemTag)) {
|
||||
return;
|
||||
}
|
||||
Tag hitDirection = tag.get("hit_direction");
|
||||
if (hitDirection == null) {
|
||||
// java server sends no direction when the item recedes back into the block (if player stops brushing)
|
||||
return;
|
||||
}
|
||||
|
||||
String id = ((StringTag) itemTag.get("id")).getValue();
|
||||
if (Items.AIR.javaIdentifier().equals(id)) {
|
||||
return; // server sends air when the block contains nothing
|
||||
}
|
||||
|
||||
ItemMapping mapping = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(id);
|
||||
if (mapping == null) {
|
||||
return;
|
||||
}
|
||||
NbtMapBuilder itemBuilder = NbtMap.builder()
|
||||
.putString("Name", mapping.getBedrockIdentifier())
|
||||
.putByte("Count", (byte) itemTag.get("Count").getValue());
|
||||
|
||||
builder.putCompound("item", itemBuilder.build());
|
||||
// controls which side the item protrudes from
|
||||
builder.putByte("brush_direction", ((Number) hitDirection.getValue()).byteValue());
|
||||
// controls how much the item protrudes
|
||||
builder.putInt("brush_count", BlockStateValues.getBrushProgress(blockState));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.DECORATED_POT)
|
||||
public class DecoratedPotBlockEntityTranslator extends BlockEntityTranslator {
|
||||
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
// exact same format
|
||||
if (tag.get("sherds") instanceof ListTag sherds) {
|
||||
List<String> translated = new ArrayList<>(4);
|
||||
for (Tag sherd : sherds) {
|
||||
translated.add(((StringTag) sherd).getValue());
|
||||
}
|
||||
builder.putList("sherds", NbtType.STRING, translated);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2023 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
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import org.geysermc.geyser.util.SignUtils;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.HANGING_SIGN)
|
||||
public class HangingSignBlockEntityTranslator extends SignBlockEntityTranslator {
|
||||
|
||||
@Override
|
||||
public int signWidthMax() {
|
||||
return SignUtils.HANGING_SIGN_WIDTH_MAX; // Smaller than that for BlockEntityType.SIGN
|
||||
}
|
||||
}
|
|
@ -36,13 +36,12 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
|||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.Axis;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
|
@ -622,10 +621,6 @@ public class PistonBlockEntity {
|
|||
Vector3i movement = getMovement();
|
||||
attachedBlocks.forEach((blockPos, javaId) -> {
|
||||
blockPos = blockPos.add(movement);
|
||||
if (!GameProtocol.supports1_19_50(session)) {
|
||||
// Send a final block entity packet to detach blocks for clients older than 1.19.50
|
||||
BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos);
|
||||
}
|
||||
// Don't place blocks that collide with the player
|
||||
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
|
||||
ChunkUtils.updateBlock(session, javaId, blockPos);
|
||||
|
|
|
@ -27,12 +27,15 @@ package org.geysermc.geyser.translator.level.block.entity;
|
|||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.SignUtils;
|
||||
|
||||
@BlockEntity(type = {BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN})
|
||||
@BlockEntity(type = BlockEntityType.SIGN)
|
||||
public class SignBlockEntityTranslator extends BlockEntityTranslator {
|
||||
/**
|
||||
* Maps a color stored in a sign's Color tag to its ARGB value.
|
||||
|
@ -64,54 +67,80 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
|
|||
return dyeColor | (255 << 24);
|
||||
}
|
||||
|
||||
public int signWidthMax() {
|
||||
return SignUtils.SIGN_WIDTH_MAX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
StringBuilder signText = new StringBuilder();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int currentLine = i + 1;
|
||||
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
|
||||
signLine = MessageTranslator.convertMessageLenient(signLine);
|
||||
builder.putCompound("FrontText", translateSide(tag.get("front_text")));
|
||||
builder.putCompound("BackText", translateSide(tag.get("back_text")));
|
||||
var waxed = tag.get("is_waxed");
|
||||
builder.putBoolean("IsWaxed", waxed != null && waxed.getValue() instanceof Number number && number.byteValue() != 0);
|
||||
}
|
||||
|
||||
// Check the character width on the sign to ensure there is no overflow that is usually hidden
|
||||
// to Java Edition clients but will appear to Bedrock clients
|
||||
int signWidth = 0;
|
||||
StringBuilder finalSignLine = new StringBuilder();
|
||||
boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width
|
||||
for (char c : signLine.toCharArray()) {
|
||||
if (c == '\u00a7') {
|
||||
// Don't count this character
|
||||
previousCharacterWasFormatting = true;
|
||||
} else if (previousCharacterWasFormatting) {
|
||||
// Don't count this character either
|
||||
previousCharacterWasFormatting = false;
|
||||
} else {
|
||||
signWidth += SignUtils.getCharacterWidth(c);
|
||||
private NbtMap translateSide(Tag tag) {
|
||||
if (!(tag instanceof CompoundTag signData)) {
|
||||
return NbtMap.EMPTY;
|
||||
}
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
|
||||
StringBuilder signText = new StringBuilder();
|
||||
Tag messages = signData.get("messages");
|
||||
if (messages instanceof ListTag listTag) {
|
||||
var it = listTag.iterator();
|
||||
while (it.hasNext()) {
|
||||
String signLine = (String) it.next().getValue();
|
||||
signLine = MessageTranslator.convertMessageLenient(signLine);
|
||||
|
||||
// Check the character width on the sign to ensure there is no overflow that is usually hidden
|
||||
// to Java Edition clients but will appear to Bedrock clients
|
||||
int signWidth = 0;
|
||||
StringBuilder finalSignLine = new StringBuilder();
|
||||
boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width
|
||||
for (char c : signLine.toCharArray()) {
|
||||
if (c == ChatColor.ESCAPE) {
|
||||
// Don't count this character
|
||||
previousCharacterWasFormatting = true;
|
||||
} else if (previousCharacterWasFormatting) {
|
||||
// Don't count this character either
|
||||
previousCharacterWasFormatting = false;
|
||||
} else {
|
||||
signWidth += SignUtils.getCharacterWidth(c);
|
||||
}
|
||||
|
||||
if (signWidth <= signWidthMax()) {
|
||||
finalSignLine.append(c);
|
||||
} else {
|
||||
// Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// todo 1.20: update for hanging signs (smaller width). Currently OK because bedrock sees hanging signs as normal signs
|
||||
if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
|
||||
finalSignLine.append(c);
|
||||
} else {
|
||||
// Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to
|
||||
break;
|
||||
signText.append(finalSignLine);
|
||||
if (it.hasNext()) {
|
||||
signText.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signText.append(finalSignLine);
|
||||
signText.append("\n");
|
||||
// Trim extra newlines - this makes editing difficult if preserved because the cursor starts at the bottom,
|
||||
// Which can easily go over the screen
|
||||
while (!signText.isEmpty() && signText.charAt(signText.length() - 1) == '\n') {
|
||||
signText.deleteCharAt(signText.length() - 1);
|
||||
}
|
||||
|
||||
builder.putString("Text", signText.toString());
|
||||
|
||||
// Java Edition 1.14 added the ability to change the text color of the whole sign using dye
|
||||
Tag color = tag.get("Color");
|
||||
Tag color = signData.get("color");
|
||||
if (color != null) {
|
||||
builder.putInt("SignTextColor", getBedrockSignColor(color.getValue().toString()));
|
||||
}
|
||||
|
||||
// Glowing text
|
||||
boolean isGlowing = getOrDefault(tag.getValue().get("GlowingText"), (byte) 0) != (byte) 0;
|
||||
boolean isGlowing = getOrDefault(signData.get("has_glowing_text"), (byte) 0) != (byte) 0;
|
||||
builder.putBoolean("IgnoreLighting", isGlowing);
|
||||
builder.putBoolean("TextIgnoreLegacyBugResolved", isGlowing); // ??? required
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.Serverb
|
|||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -44,16 +43,12 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
|||
public void translate(GeyserSession session, BlockEntityDataPacket packet) {
|
||||
NbtMap tag = packet.getData();
|
||||
String id = tag.getString("id");
|
||||
if (id.equals("Sign")) {
|
||||
String text;
|
||||
if (GameProtocol.supports1_19_80(session)) {
|
||||
// The other side is called... you guessed it... BackText
|
||||
text = tag.getCompound("FrontText")
|
||||
.getString("Text");
|
||||
} else {
|
||||
text = tag.getString("Text");
|
||||
}
|
||||
text = MessageTranslator.convertToPlainText(text);
|
||||
if (id.endsWith("Sign")) {
|
||||
// Hanging signs are narrower
|
||||
int widthMax = SignUtils.getSignWidthMax(id.startsWith("Hanging"));
|
||||
|
||||
String text = MessageTranslator.convertToPlainText(
|
||||
tag.getCompound(session.getWorldCache().isEditingSignOnFront() ? "FrontText" : "BackText").getString("Text"));
|
||||
// Note: as of 1.18.30, only one packet is sent from Bedrock when the sign is finished.
|
||||
// Previous versions did not have this behavior.
|
||||
StringBuilder newMessage = new StringBuilder();
|
||||
|
@ -68,13 +63,10 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
|||
for (char character : text.toCharArray()) {
|
||||
widthCount += SignUtils.getCharacterWidth(character);
|
||||
|
||||
// todo 1.20: update for hanging signs (smaller width). Currently bedrock thinks hanging signs are normal,
|
||||
// so it thinks hanging signs have more width than they actually do. Seems like JE just truncates it.
|
||||
|
||||
// If we get a return in Bedrock, or go over the character width max, that signals to use the next line.
|
||||
if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) {
|
||||
if (character == '\n' || widthCount > widthMax) {
|
||||
// We need to apply some more logic if we went over the character width max
|
||||
boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n';
|
||||
boolean wentOverMax = widthCount > widthMax && character != '\n';
|
||||
widthCount = 0;
|
||||
// Saves if we're moving a word to the next line
|
||||
String word = null;
|
||||
|
@ -115,7 +107,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
|||
// Put the final line on since it isn't done in the for loop
|
||||
if (iterator < lines.length) lines[iterator] = newMessage.toString();
|
||||
Vector3i pos = Vector3i.from(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
|
||||
ServerboundSignUpdatePacket signUpdatePacket = new ServerboundSignUpdatePacket(pos, lines);
|
||||
ServerboundSignUpdatePacket signUpdatePacket = new ServerboundSignUpdatePacket(pos, lines, session.getWorldCache().isEditingSignOnFront());
|
||||
session.sendDownstreamPacket(signUpdatePacket);
|
||||
|
||||
} else if (id.equals("JigsawBlock")) {
|
||||
|
|
|
@ -33,12 +33,12 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
|||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BookEditPacket;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.type.WrittenBookItem;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
@ -46,12 +46,10 @@ import java.util.List;
|
|||
|
||||
@Translator(packet = BookEditPacket.class)
|
||||
public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket> {
|
||||
private static final int MAXIMUM_PAGE_LENGTH = 8192 * 4;
|
||||
private static final int MAXIMUM_TITLE_LENGTH = 128 * 4;
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, BookEditPacket packet) {
|
||||
if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_PAGE_LENGTH) {
|
||||
if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().length() > WrittenBookItem.MAXIMUM_PAGE_EDIT_LENGTH) {
|
||||
session.getGeyser().getLogger().warning("Page length greater than server allowed!");
|
||||
return;
|
||||
}
|
||||
|
@ -63,6 +61,10 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
|||
List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
|
||||
|
||||
int page = packet.getPageNumber();
|
||||
if (page < 0 || WrittenBookItem.MAXIMUM_PAGE_COUNT <= page) {
|
||||
session.getGeyser().getLogger().warning("Edited page is out of acceptable bounds!");
|
||||
return;
|
||||
}
|
||||
switch (packet.getAction()) {
|
||||
case ADD_PAGE: {
|
||||
// Add empty pages in between
|
||||
|
@ -129,7 +131,7 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
|||
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
|
||||
// Add title to packet so the server knows we're signing
|
||||
title = MessageTranslator.convertToPlainText(packet.getTitle());
|
||||
if (title.getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) {
|
||||
if (title.length() > WrittenBookItem.MAXIMUM_TITLE_LENGTH) {
|
||||
session.getGeyser().getLogger().warning("Book title larger than server allows!");
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -26,10 +26,9 @@
|
|||
package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
@ -46,7 +45,9 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
|
|||
return;
|
||||
}
|
||||
|
||||
session.sendCommand(command.substring(1));
|
||||
// running commands via Bedrock's command select menu adds a trailing whitespace which Java doesn't like
|
||||
// https://github.com/GeyserMC/Geyser/issues/3877
|
||||
session.sendCommand(command.substring(1).stripTrailing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryActionData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventorySource;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryTransactionType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.LegacySetItemSlotData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket;
|
||||
|
@ -93,6 +94,15 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
|
||||
@Override
|
||||
public void translate(GeyserSession session, InventoryTransactionPacket packet) {
|
||||
if (packet.getTransactionType() == InventoryTransactionType.NORMAL && packet.getActions().size() == 3) {
|
||||
InventoryActionData containerAction = packet.getActions().get(0);
|
||||
if (containerAction.getSource().getType() == InventorySource.Type.CONTAINER &&
|
||||
session.getPlayerInventory().getHeldItemSlot() == containerAction.getSlot() &&
|
||||
containerAction.getFromItem().getDefinition() == session.getItemMappings().getStoredItems().writableBook().getBedrockDefinition()) {
|
||||
// Ignore InventoryTransactions related to editing books as that is handled in BedrockBookEditTranslator
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Send book updates before opening inventories
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetLocalPlayerAsInitializedPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionJoinEvent;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
|
@ -68,6 +70,8 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
|
|||
|
||||
// What am I to expect - as of Bedrock 1.18
|
||||
session.getFormCache().resendAllForms();
|
||||
|
||||
GeyserImpl.getInstance().eventBus().fire(new SessionJoinEvent(session));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,21 +56,38 @@ public class BedrockEmoteTranslator extends PacketTranslator<EmotePacket> {
|
|||
}
|
||||
|
||||
int javaId = session.getPlayerEntity().getEntityId();
|
||||
String xuid = session.getAuthData().xuid();
|
||||
String emote = packet.getEmoteId();
|
||||
for (GeyserSession otherSession : session.getGeyser().getSessionManager().getSessions().values()) {
|
||||
if (otherSession != session) {
|
||||
if (otherSession.isClosed()) continue;
|
||||
if (otherSession.getEventLoop().inEventLoop()) {
|
||||
playEmote(otherSession, javaId, packet.getEmoteId());
|
||||
playEmote(otherSession, javaId, xuid, emote);
|
||||
} else {
|
||||
otherSession.executeInEventLoop(() -> playEmote(otherSession, javaId, packet.getEmoteId()));
|
||||
otherSession.executeInEventLoop(() -> playEmote(otherSession, javaId, xuid, emote));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void playEmote(GeyserSession otherSession, int javaId, String emoteId) {
|
||||
Entity otherEntity = otherSession.getEntityCache().getEntityByJavaId(javaId); // Must be ran on same thread
|
||||
if (!(otherEntity instanceof PlayerEntity otherPlayer)) return;
|
||||
otherSession.showEmote(otherPlayer, emoteId);
|
||||
/**
|
||||
* Play an emote by an emoter to the given session.
|
||||
* This method must be called within the session's event loop.
|
||||
*
|
||||
* @param session the session to show the emote to
|
||||
* @param emoterJavaId the java id of the emoter
|
||||
* @param emoterXuid the xuid of the emoter
|
||||
* @param emoteId the emote to play
|
||||
*/
|
||||
private static void playEmote(GeyserSession session, int emoterJavaId, String emoterXuid, String emoteId) {
|
||||
Entity emoter = session.getEntityCache().getEntityByJavaId(emoterJavaId); // Must be ran on same thread
|
||||
if (emoter instanceof PlayerEntity) {
|
||||
EmotePacket packet = new EmotePacket();
|
||||
packet.setRuntimeEntityId(emoter.getGeyserId());
|
||||
packet.setXuid(emoterXuid);
|
||||
packet.setPlatformId(""); // BDS sends empty
|
||||
packet.setEmoteId(emoteId);
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||
CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", values, false);
|
||||
|
||||
// Build the completed command and add it to the final list
|
||||
CommandData data = new CommandData(commandName, entry.getKey().description(), flags, (byte) 0, aliases, entry.getKey().paramData());
|
||||
CommandData data = new CommandData(commandName, entry.getKey().description(), flags, CommandPermission.ANY, aliases, entry.getKey().paramData());
|
||||
commandData.add(data);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.login.clientbound.ClientboundLog
|
|||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
|
|
@ -29,9 +29,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
|||
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.LegacyUpgradeRecipeData;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.SmithingTransformRecipeData;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateRecipesPacket;
|
||||
import it.unimi.dsi.fastutil.ints.*;
|
||||
|
@ -41,15 +41,16 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.MultiRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTrimRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.TrimDataPacket;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserStonecutterData;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
@ -145,28 +146,29 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||
data.add(stoneCuttingData);
|
||||
// Save for processing after all recipes have been received
|
||||
}
|
||||
case SMITHING -> {
|
||||
// Required to translate these as of 1.18.10, or else they cannot be crafted
|
||||
LegacyUpgradeRecipeData recipeData = (LegacyUpgradeRecipeData) recipe.getData();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, recipeData.getResult());
|
||||
for (ItemStack base : recipeData.getBase().getOptions()) {
|
||||
ItemDescriptorWithCount bedrockBase = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, base));
|
||||
case SMITHING_TRANSFORM -> {
|
||||
SmithingTransformRecipeData data = (SmithingTransformRecipeData) recipe.getData();
|
||||
ItemData output = ItemTranslator.translateToBedrock(session, data.getResult());
|
||||
|
||||
for (ItemStack addition : recipeData.getAddition().getOptions()) {
|
||||
ItemDescriptorWithCount bedrockAddition = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, addition));
|
||||
for (ItemStack template : data.getTemplate().getOptions()) {
|
||||
ItemDescriptorWithCount bedrockTemplate = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, template));
|
||||
|
||||
for (ItemStack base : data.getBase().getOptions()) {
|
||||
ItemDescriptorWithCount bedrockBase = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, base));
|
||||
|
||||
for (ItemStack addition : data.getAddition().getOptions()) {
|
||||
ItemDescriptorWithCount bedrockAddition = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, addition));
|
||||
|
||||
if (GameProtocol.supports1_19_60(session)) {
|
||||
// Note: vanilla inputs use aux value of Short.MAX_VALUE
|
||||
craftingDataPacket.getCraftingData().add(SmithingTransformRecipeData.of(recipe.getIdentifier(),
|
||||
ItemDescriptorWithCount.EMPTY, bedrockBase, bedrockAddition, output, "smithing_table", netId++));
|
||||
} else {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapelessRecipeData.shapeless(uuid.toString(),
|
||||
List.of(bedrockBase, bedrockAddition),
|
||||
Collections.singletonList(output), uuid, "smithing_table", 2, netId++));
|
||||
craftingDataPacket.getCraftingData().add(org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData.of(recipe.getIdentifier(),
|
||||
bedrockTemplate, bedrockBase, bedrockAddition, output, "smithing_table", netId++));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
case SMITHING_TRIM -> {
|
||||
// ignored currently - see below
|
||||
}
|
||||
default -> {
|
||||
List<RecipeData> craftingData = recipeTypes.get(recipe.getType());
|
||||
|
@ -212,6 +214,19 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: if the server/viaversion doesn't send trim recipes then we shouldn't either.
|
||||
|
||||
// BDS sends armor trim templates and materials before the CraftingDataPacket
|
||||
TrimDataPacket trimDataPacket = new TrimDataPacket();
|
||||
trimDataPacket.getPatterns().addAll(TrimRecipe.PATTERNS);
|
||||
trimDataPacket.getMaterials().addAll(TrimRecipe.MATERIALS);
|
||||
session.sendUpstreamPacket(trimDataPacket);
|
||||
|
||||
// Identical smithing_trim recipe sent by BDS that uses tag-descriptors, as the client seems to ignore the
|
||||
// approach of using many default-descriptors (which we do for smithing_transform)
|
||||
craftingDataPacket.getCraftingData().add(SmithingTrimRecipeData.of(TrimRecipe.ID,
|
||||
TrimRecipe.BASE, TrimRecipe.ADDITION, TrimRecipe.TEMPLATE, "smithing_table", netId++));
|
||||
|
||||
session.sendUpstreamPacket(craftingDataPacket);
|
||||
session.setCraftingRecipes(recipeMap);
|
||||
session.setStonecutterRecipes(stonecutterRecipeMap);
|
||||
|
|
|
@ -108,7 +108,7 @@ public class JavaSetPassengersTranslator extends PacketTranslator<ClientboundSet
|
|||
|
||||
switch (entity.getDefinition().entityType()) {
|
||||
case HORSE, SKELETON_HORSE, DONKEY, MULE, RAVAGER -> {
|
||||
entity.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET, 181.0f);
|
||||
entity.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET_DEGREES, 181.0f);
|
||||
entity.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,9 +45,15 @@ public class JavaUpdateMobEffectTranslator extends PacketTranslator<ClientboundU
|
|||
if (entity == null)
|
||||
return;
|
||||
|
||||
int duration = packet.getDuration();
|
||||
if (duration < 0) {
|
||||
// java edition uses -1 for infinite, but bedrock doesn't have infinite
|
||||
duration = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
MobEffectPacket mobEffectPacket = new MobEffectPacket();
|
||||
mobEffectPacket.setAmplifier(packet.getAmplifier());
|
||||
mobEffectPacket.setDuration(packet.getDuration());
|
||||
mobEffectPacket.setDuration(duration);
|
||||
mobEffectPacket.setEvent(MobEffectPacket.Event.ADD);
|
||||
mobEffectPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
mobEffectPacket.setParticles(packet.isShowParticles());
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.cloudburstmc.nbt.NbtType;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateEquipPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.ChestedHorseEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
|
@ -118,6 +119,10 @@ public class JavaHorseScreenOpenTranslator extends PacketTranslator<ClientboundH
|
|||
} else if (entity instanceof ChestedHorseEntity) {
|
||||
inventoryTranslator = new DonkeyInventoryTranslator(packet.getNumberOfSlots());
|
||||
slots.add(SADDLE_SLOT);
|
||||
} else if (entity instanceof CamelEntity) {
|
||||
// The camel has an invisible armor slot and needs special handling, same as the donkey
|
||||
inventoryTranslator = new DonkeyInventoryTranslator(packet.getNumberOfSlots());
|
||||
slots.add(SADDLE_SLOT);
|
||||
} else {
|
||||
inventoryTranslator = new HorseInventoryTranslator(packet.getNumberOfSlots());
|
||||
slots.add(SADDLE_SLOT);
|
||||
|
|
|
@ -73,9 +73,17 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
|
|||
public static void openMerchant(GeyserSession session, ClientboundMerchantOffersPacket packet, MerchantContainer merchantInventory) {
|
||||
// Retrieve the fake villager involved in the trade, and update its metadata to match with the window information
|
||||
merchantInventory.setVillagerTrades(packet.getTrades());
|
||||
merchantInventory.setTradeExperience(packet.getExperience());
|
||||
|
||||
Entity villager = merchantInventory.getVillager();
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_TIER, packet.getVillagerLevel() - 1);
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.MAX_TRADE_TIER, 4);
|
||||
if (packet.isRegularVillager()) {
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_TIER, packet.getVillagerLevel() - 1);
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.MAX_TRADE_TIER, 4);
|
||||
} else {
|
||||
// Don't show trade level for wandering traders
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_TIER, 0);
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.MAX_TRADE_TIER, 0);
|
||||
}
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_EXPERIENCE, packet.getExperience());
|
||||
villager.updateBedrockMetadata();
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BlockEventPacket;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
|
|
@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.Clientb
|
|||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.type.BlockMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
|
|
@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.level;
|
|||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundOpenSignEditorPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.OpenSignPacket;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
|
@ -37,11 +36,11 @@ public class JavaOpenSignEditorTranslator extends PacketTranslator<ClientboundOp
|
|||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundOpenSignEditorPacket packet) {
|
||||
if (GameProtocol.supports1_19_80(session)) {
|
||||
OpenSignPacket openSignPacket = new OpenSignPacket();
|
||||
openSignPacket.setPosition(packet.getPosition());
|
||||
openSignPacket.setFrontSide(true); // Will be remedied in 1.20
|
||||
session.sendUpstreamPacket(openSignPacket);
|
||||
}
|
||||
OpenSignPacket openSignPacket = new OpenSignPacket();
|
||||
openSignPacket.setPosition(packet.getPosition());
|
||||
openSignPacket.setFrontSide(packet.isFrontText());
|
||||
session.sendUpstreamPacket(openSignPacket);
|
||||
|
||||
session.getWorldCache().setEditingSignOnFront(packet.isFrontText());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -192,8 +192,8 @@ public final class AssetUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static void saveFile(File location, InputStream fileStream) throws IOException {
|
||||
try (FileOutputStream outStream = new FileOutputStream(location)) {
|
||||
public static void saveFile(Path location, InputStream fileStream) throws IOException {
|
||||
try (OutputStream outStream = Files.newOutputStream(location)) {
|
||||
|
||||
// Write the file to the locale dir
|
||||
byte[] buf = new byte[fileStream.available()];
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.StopSoundPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.level.BedrockDimension;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.Set;
|
||||
|
@ -122,18 +121,16 @@ public class DimensionUtils {
|
|||
stopSoundPacket.setSoundName("");
|
||||
session.sendUpstreamPacket(stopSoundPacket);
|
||||
|
||||
// Kind of silly but Bedrock 1.19.50 requires an acknowledgement after the
|
||||
// Kind of silly but Bedrock 1.19.50 and later requires an acknowledgement after the
|
||||
// initial chunks are sent, prior to the client acknowledgement
|
||||
if (GameProtocol.supports1_19_50(session)) {
|
||||
// Note: send this before chunks are sent. Fixed https://github.com/GeyserMC/Geyser/issues/3421
|
||||
PlayerActionPacket ackPacket = new PlayerActionPacket();
|
||||
ackPacket.setRuntimeEntityId(player.getGeyserId());
|
||||
ackPacket.setAction(PlayerActionType.DIMENSION_CHANGE_SUCCESS);
|
||||
ackPacket.setBlockPosition(Vector3i.ZERO);
|
||||
ackPacket.setResultPosition(Vector3i.ZERO);
|
||||
ackPacket.setFace(0);
|
||||
session.sendUpstreamPacket(ackPacket);
|
||||
}
|
||||
// Note: send this before chunks are sent. Fixed https://github.com/GeyserMC/Geyser/issues/3421
|
||||
PlayerActionPacket ackPacket = new PlayerActionPacket();
|
||||
ackPacket.setRuntimeEntityId(player.getGeyserId());
|
||||
ackPacket.setAction(PlayerActionType.DIMENSION_CHANGE_SUCCESS);
|
||||
ackPacket.setBlockPosition(Vector3i.ZERO);
|
||||
ackPacket.setResultPosition(Vector3i.ZERO);
|
||||
ackPacket.setFace(0);
|
||||
session.sendUpstreamPacket(ackPacket);
|
||||
|
||||
// TODO - fix this hack of a fix by sending the final dimension switching logic after sections have been sent.
|
||||
// The client wants sections sent to it before it can successfully respawn.
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.geysermc.geyser.entity.type.BoatEntity;
|
|||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.living.ArmorStandEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
|
||||
|
@ -82,19 +83,28 @@ public final class EntityUtils {
|
|||
float height = mount.getBoundingBoxHeight();
|
||||
float mountedHeightOffset = height * 0.75f;
|
||||
switch (mount.getDefinition().entityType()) {
|
||||
case CHICKEN, SPIDER -> mountedHeightOffset = height * 0.5f;
|
||||
case CAMEL -> {
|
||||
boolean isBaby = mount.getFlag(EntityFlag.BABY);
|
||||
mountedHeightOffset = height - (isBaby ? 0.35f : 0.6f);
|
||||
}
|
||||
case CAVE_SPIDER, CHICKEN, SPIDER -> mountedHeightOffset = height * 0.5f;
|
||||
case DONKEY, MULE -> mountedHeightOffset -= 0.25f;
|
||||
case TRADER_LLAMA, LLAMA -> mountedHeightOffset = height * 0.6f;
|
||||
case MINECART, HOPPER_MINECART, TNT_MINECART, CHEST_MINECART, FURNACE_MINECART, SPAWNER_MINECART,
|
||||
COMMAND_BLOCK_MINECART -> mountedHeightOffset = 0;
|
||||
case BOAT, CHEST_BOAT -> mountedHeightOffset = -0.1f;
|
||||
case BOAT, CHEST_BOAT -> {
|
||||
boolean isBamboo = ((BoatEntity) mount).getVariant() == 8;
|
||||
mountedHeightOffset = isBamboo ? 0.25f : -0.1f;
|
||||
}
|
||||
case HOGLIN, ZOGLIN -> {
|
||||
boolean isBaby = mount.getFlag(EntityFlag.BABY);
|
||||
mountedHeightOffset = height - (isBaby ? 0.2f : 0.15f);
|
||||
}
|
||||
case PIGLIN -> mountedHeightOffset = height * 0.92f;
|
||||
case PHANTOM -> mountedHeightOffset = height * 0.35f;
|
||||
case RAVAGER -> mountedHeightOffset = 2.1f;
|
||||
case SKELETON_HORSE -> mountedHeightOffset -= 0.1875f;
|
||||
case SNIFFER -> mountedHeightOffset = 1.8f;
|
||||
case STRIDER -> mountedHeightOffset = height - 0.19f;
|
||||
}
|
||||
return mountedHeightOffset;
|
||||
|
@ -103,9 +113,9 @@ public final class EntityUtils {
|
|||
private static float getHeightOffset(Entity passenger) {
|
||||
boolean isBaby;
|
||||
switch (passenger.getDefinition().entityType()) {
|
||||
case SKELETON:
|
||||
case STRAY:
|
||||
case WITHER_SKELETON:
|
||||
case ALLAY, VEX:
|
||||
return 0.4f;
|
||||
case SKELETON, STRAY, WITHER_SKELETON:
|
||||
return -0.6f;
|
||||
case ARMOR_STAND:
|
||||
if (((ArmorStandEntity) passenger).isMarker()) {
|
||||
|
@ -113,26 +123,23 @@ public final class EntityUtils {
|
|||
} else {
|
||||
return 0.1f;
|
||||
}
|
||||
case ENDERMITE:
|
||||
case SILVERFISH:
|
||||
case ENDERMITE, SILVERFISH:
|
||||
return 0.1f;
|
||||
case PIGLIN:
|
||||
case PIGLIN_BRUTE:
|
||||
case ZOMBIFIED_PIGLIN:
|
||||
case PIGLIN, PIGLIN_BRUTE, ZOMBIFIED_PIGLIN:
|
||||
isBaby = passenger.getFlag(EntityFlag.BABY);
|
||||
return isBaby ? -0.05f : -0.45f;
|
||||
case ZOMBIE:
|
||||
case DROWNED, HUSK, ZOMBIE_VILLAGER, ZOMBIE:
|
||||
isBaby = passenger.getFlag(EntityFlag.BABY);
|
||||
return isBaby ? 0.0f : -0.45f;
|
||||
case EVOKER:
|
||||
case ILLUSIONER:
|
||||
case PILLAGER:
|
||||
case RAVAGER:
|
||||
case VINDICATOR:
|
||||
case WITCH:
|
||||
case EVOKER, ILLUSIONER, PILLAGER, RAVAGER, VINDICATOR, WITCH:
|
||||
return -0.45f;
|
||||
case PLAYER:
|
||||
return -0.35f;
|
||||
case SHULKER:
|
||||
Entity vehicle = passenger.getVehicle();
|
||||
if (vehicle instanceof BoatEntity || vehicle.getDefinition() == EntityDefinitions.MINECART) {
|
||||
return 0.1875f - getMountedHeightOffset(vehicle);
|
||||
}
|
||||
}
|
||||
if (passenger instanceof AnimalEntity) {
|
||||
return 0.14f;
|
||||
|
@ -156,39 +163,59 @@ public final class EntityUtils {
|
|||
switch (mount.getDefinition().entityType()) {
|
||||
case BOAT -> {
|
||||
// Without the X offset, more than one entity on a boat is stacked on top of each other
|
||||
if (rider && moreThanOneEntity) {
|
||||
xOffset = 0.2f;
|
||||
} else if (moreThanOneEntity) {
|
||||
xOffset = -0.6f;
|
||||
if (moreThanOneEntity) {
|
||||
if (rider) {
|
||||
xOffset = 0.2f;
|
||||
} else {
|
||||
xOffset = -0.6f;
|
||||
}
|
||||
if (passenger instanceof AnimalEntity) {
|
||||
xOffset += 0.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
case CAMEL -> {
|
||||
zOffset = 0.5f;
|
||||
if (moreThanOneEntity) {
|
||||
if (!rider) {
|
||||
zOffset = -0.7f;
|
||||
}
|
||||
if (passenger instanceof AnimalEntity) {
|
||||
zOffset += 0.2f;
|
||||
}
|
||||
}
|
||||
if (mount.getFlag(EntityFlag.SITTING)) {
|
||||
if (mount.getFlag(EntityFlag.BABY)) {
|
||||
yOffset += CamelEntity.SITTING_HEIGHT_DIFFERENCE * 0.5f;
|
||||
} else {
|
||||
yOffset += CamelEntity.SITTING_HEIGHT_DIFFERENCE;
|
||||
}
|
||||
}
|
||||
}
|
||||
case CHEST_BOAT -> xOffset = 0.15F;
|
||||
case CHICKEN -> zOffset = -0.1f;
|
||||
case TRADER_LLAMA, LLAMA -> zOffset = -0.3f;
|
||||
}
|
||||
if (passenger.getDefinition().entityType() == EntityType.SHULKER) {
|
||||
switch (mount.getDefinition().entityType()) {
|
||||
case MINECART, HOPPER_MINECART, TNT_MINECART, CHEST_MINECART, FURNACE_MINECART, SPAWNER_MINECART,
|
||||
COMMAND_BLOCK_MINECART, BOAT, CHEST_BOAT -> yOffset = 0.1875f;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Bedrock Differences
|
||||
* Zoglin & Hoglin seem to be taller in Bedrock edition
|
||||
* Horses are tinier
|
||||
* Players, Minecarts, and Boats have different origins
|
||||
*/
|
||||
if (mount.getDefinition().entityType() == EntityType.PLAYER) {
|
||||
yOffset -= EntityDefinitions.PLAYER.offset();
|
||||
}
|
||||
if (passenger.getDefinition().entityType() == EntityType.PLAYER) {
|
||||
if (mount.getDefinition().entityType() != EntityType.PLAYER && mount.getDefinition().entityType() != EntityType.AREA_EFFECT_CLOUD) {
|
||||
yOffset += EntityDefinitions.PLAYER.offset();
|
||||
}
|
||||
yOffset += EntityDefinitions.PLAYER.offset();
|
||||
}
|
||||
switch (mount.getDefinition().entityType()) {
|
||||
case MINECART, HOPPER_MINECART, TNT_MINECART, CHEST_MINECART, FURNACE_MINECART, SPAWNER_MINECART,
|
||||
COMMAND_BLOCK_MINECART, BOAT, CHEST_BOAT -> yOffset -= mount.getDefinition().height() * 0.5f;
|
||||
}
|
||||
if (passenger.getDefinition().entityType() == EntityType.FALLING_BLOCK) {
|
||||
yOffset += 0.5f;
|
||||
switch (passenger.getDefinition().entityType()) {
|
||||
case MINECART, HOPPER_MINECART, TNT_MINECART, CHEST_MINECART, FURNACE_MINECART, SPAWNER_MINECART,
|
||||
COMMAND_BLOCK_MINECART, BOAT, CHEST_BOAT -> yOffset += passenger.getDefinition().height() * 0.5f;
|
||||
case FALLING_BLOCK -> yOffset += 0.5f;
|
||||
}
|
||||
if (mount instanceof ArmorStandEntity armorStand) {
|
||||
yOffset -= armorStand.getYOffset();
|
||||
|
@ -203,12 +230,12 @@ public final class EntityUtils {
|
|||
// Head rotation is locked while riding in a boat
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, true);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 90f);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET, 1f);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_HAS_ROTATION, true);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET_DEGREES, -90f);
|
||||
} else {
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION, false);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_LOCK_RIDER_ROTATION_DEGREES, 0f);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET, 0f);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_HAS_ROTATION, false);
|
||||
passenger.getDirtyMetadata().put(EntityDataTypes.SEAT_ROTATION_OFFSET_DEGREES, 0f);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,14 +129,14 @@ public class FileUtils {
|
|||
/**
|
||||
* Calculate the SHA256 hash of a file
|
||||
*
|
||||
* @param file File to calculate the hash for
|
||||
* @param path Path to calculate the hash for
|
||||
* @return A byte[] representation of the hash
|
||||
*/
|
||||
public static byte[] calculateSHA256(File file) {
|
||||
public static byte[] calculateSHA256(Path path) {
|
||||
byte[] sha256;
|
||||
|
||||
try {
|
||||
sha256 = MessageDigest.getInstance("SHA-256").digest(readAllBytes(file));
|
||||
sha256 = MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(path));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not calculate pack hash", e);
|
||||
}
|
||||
|
@ -147,14 +147,14 @@ public class FileUtils {
|
|||
/**
|
||||
* Calculate the SHA1 hash of a file
|
||||
*
|
||||
* @param file File to calculate the hash for
|
||||
* @param path Path to calculate the hash for
|
||||
* @return A byte[] representation of the hash
|
||||
*/
|
||||
public static byte[] calculateSHA1(File file) {
|
||||
public static byte[] calculateSHA1(Path path) {
|
||||
byte[] sha1;
|
||||
|
||||
try {
|
||||
sha1 = MessageDigest.getInstance("SHA-1").digest(readAllBytes(file));
|
||||
sha1 = MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(path));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Could not calculate pack hash", e);
|
||||
}
|
||||
|
@ -162,20 +162,6 @@ public class FileUtils {
|
|||
return sha1;
|
||||
}
|
||||
|
||||
/**
|
||||
* An android compatible version of {@link Files#readAllBytes}
|
||||
*
|
||||
* @param file File to read bytes of
|
||||
* @return The byte array of the file
|
||||
*/
|
||||
public static byte[] readAllBytes(File file) {
|
||||
try (InputStream stream = new FileInputStream(file)) {
|
||||
return stream.readAllBytes();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read " + file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource the internal resource to read off from
|
||||
* @return the byte array of an InputStream
|
||||
|
|
|
@ -96,7 +96,7 @@ public class LoginEncryptionUtils {
|
|||
startEncryptionHandshake(session, identityPublicKey);
|
||||
} catch (Throwable e) {
|
||||
// An error can be thrown on older Java 8 versions about an invalid key
|
||||
if (true || geyser.getConfig().isDebugMode()) {
|
||||
if (geyser.getConfig().isDebugMode()) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
package org.geysermc.geyser.util;
|
||||
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
|
||||
/**
|
||||
* Provides utilities for interacting with signs. Mainly, it deals with the widths of each character.
|
||||
* Since Bedrock auto-wraps signs and Java does not, we have to take this into account when translating signs.
|
||||
|
@ -33,14 +35,15 @@ public class SignUtils {
|
|||
|
||||
// TODO: If we send the Java font via resource pack, does width change?
|
||||
/**
|
||||
* The maximum character width that a sign can hold in Bedrock
|
||||
* The maximum character width that a non-hanging sign can hold in both Java and Bedrock
|
||||
*/
|
||||
public static final int BEDROCK_CHARACTER_WIDTH_MAX = 88;
|
||||
public static final int SIGN_WIDTH_MAX = 90;
|
||||
|
||||
/**
|
||||
* The maximum character width that a sign can hold in Java
|
||||
* The maximum character width that a hanging sign can hold in both Java and Bedrock. Hanging signs are narrower.
|
||||
*/
|
||||
public static final int JAVA_CHARACTER_WIDTH_MAX = 90;
|
||||
public static final int HANGING_SIGN_WIDTH_MAX = 60;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the Minecraft width of a character
|
||||
|
@ -58,4 +61,10 @@ public class SignUtils {
|
|||
};
|
||||
}
|
||||
|
||||
public static int getSignWidthMax(boolean hanging) {
|
||||
if (hanging) {
|
||||
return HANGING_SIGN_WIDTH_MAX;
|
||||
}
|
||||
return SIGN_WIDTH_MAX;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,9 +78,10 @@ public final class SoundUtils {
|
|||
}
|
||||
|
||||
private static String trim(String identifier) {
|
||||
// Drop the Minecraft namespace if applicable
|
||||
if (identifier.startsWith("minecraft:")) {
|
||||
return identifier.substring("minecraft:".length());
|
||||
// Drop any namespace if applicable
|
||||
int i = identifier.indexOf(':');
|
||||
if (i >= 0) {
|
||||
return identifier.substring(i + 1);
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/src/main/resources/bedrock/block_palette.1_20_0.nbt
Normal file
BIN
core/src/main/resources/bedrock/block_palette.1_20_0.nbt
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue