Merge pull request #3817 from GeyserMC/feature/1.20

This commit is contained in:
Redned 2023-06-07 10:47:44 -05:00 committed by GitHub
commit b78ca431b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 12483 additions and 40804 deletions

View File

@ -6,7 +6,7 @@ plugins {
allprojects {
group = "org.geysermc.geyser"
version = "2.1.0-SNAPSHOT"
version = "2.1.1-SNAPSHOT"
description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers."
tasks.withType<JavaCompile> {

View File

@ -133,6 +133,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 +236,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")
@ -842,6 +843,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 +891,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

View File

@ -35,7 +35,7 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;

View File

@ -41,7 +41,7 @@ import lombok.Setter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;

View File

@ -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);
}
}
}
}

View File

@ -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;
@ -42,12 +47,46 @@ public class CamelEntity extends AbstractHorseEntity {
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,6 +94,12 @@ 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) {

View File

@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanE
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;

View File

@ -33,7 +33,7 @@ import org.cloudburstmc.math.vector.Vector3i;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;

View File

@ -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);
}
}

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.item;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;

View File

@ -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()));
@ -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,6 +692,7 @@ 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 TNT = register(new BlockItem("tnt", 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()));
@ -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());

View File

@ -28,7 +28,7 @@ package org.geysermc.geyser.item.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.item.Potion;

View File

@ -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

View File

@ -45,6 +45,7 @@ import java.util.Locale;
*/
public final class BlockStateValues {
private static final IntSet ALL_CAULDRONS = new IntOpenHashSet();
private static final IntSet HANGING_SIGNS = new IntOpenHashSet();
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
@ -86,6 +87,12 @@ public final class BlockStateValues {
* @param blockData JsonNode of info about the block from blocks.json
*/
public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
if (javaId.contains("_hanging_sign")) {
// covers hanging_sign and wall_hanging_sign
HANGING_SIGNS.add(javaBlockState);
return;
}
JsonNode bannerColor = blockData.get("banner_color");
if (bannerColor != null) {
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
@ -203,6 +210,17 @@ public final class BlockStateValues {
}
}
/**
* Hanging signs have a different maximum text width than "normal" signs. As a result, when the client
* updates the text of a sign without indication of the sign type, we must determine it.
*
* @param state BlockState of the block
* @return true if the sign is any hanging variant
*/
public static boolean isHangingSign(int state) {
return HANGING_SIGNS.contains(state);
}
/**
* Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock.
* This gives an integer color that Bedrock can use.

View File

@ -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();
}
/**

View File

@ -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 {

View File

@ -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;
@ -173,11 +171,6 @@ 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);
@ -232,6 +225,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;

View File

@ -32,12 +32,9 @@ 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.data.defintions.BlockDefinition;
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;
import org.geysermc.geyser.level.physics.PistonBehavior;
@ -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) {

View File

@ -28,7 +28,7 @@ package org.geysermc.geyser.registry.populator;
import com.fasterxml.jackson.databind.JsonNode;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtUtils;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;

View File

@ -30,8 +30,8 @@ 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.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.defintions.SimpleItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.item.custom.CustomItemData;

View File

@ -34,17 +34,15 @@ 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.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.defintions.SimpleItemDefinition;
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;
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
@ -70,22 +68,40 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public class ItemRegistryPopulator {
record PaletteVersion(int protocolVersion, Map<Item, String> additionalTranslatedItems) {
record PaletteVersion(int protocolVersion, Map<Item, String> javaOnlyItems, Remapper remapper) {
public PaletteVersion(int protocolVersion) {
this(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()));
paletteVersions.put("1_19_80", new PaletteVersion(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.put("1_20_0", new PaletteVersion(Bedrock_v589.CODEC.getProtocolVersion()));
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
@ -182,17 +198,11 @@ public class ItemRegistryPopulator {
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.getValue().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.getValue().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.getValue().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.getKey() + " 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);

View File

@ -28,7 +28,7 @@ package org.geysermc.geyser.registry.type;
import lombok.Builder;
import lombok.Value;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.common.DefinitionRegistry;
import java.util.Map;

View File

@ -26,7 +26,7 @@
package org.geysermc.geyser.registry.type;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
public class GeyserBedrockBlock implements BlockDefinition {
private final int runtimeId;

View File

@ -25,7 +25,7 @@
package org.geysermc.geyser.registry.type;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.geysermc.geyser.item.type.Item;
/**

View File

@ -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;

View File

@ -30,8 +30,8 @@ import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.type.Item;

View File

@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.Builder;
import lombok.Value;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ComponentItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.common.DefinitionRegistry;

View File

@ -124,6 +124,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;
@ -1540,6 +1541,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);
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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 */

View File

@ -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);
};
}

View File

@ -28,7 +28,7 @@ package org.geysermc.geyser.translator.inventory.chest;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.ContainerClosePacket;

View File

@ -30,7 +30,7 @@ import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import it.unimi.dsi.fastutil.Pair;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.registry.type.ItemMapping;

View File

@ -37,7 +37,7 @@ import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack;

View File

@ -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
}
}

View File

@ -42,7 +42,6 @@ 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);

View File

@ -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("GlowingText"), (byte) 0) != (byte) 0;
builder.putBoolean("IgnoreLighting", isGlowing);
builder.putBoolean("TextIgnoreLegacyBugResolved", isGlowing); // ??? required
return builder.build();
}
}

View File

@ -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;
@ -45,15 +44,11 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
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);
// Hanging signs are narrower
int widthMax = SignUtils.getSignWidthMax(session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()));
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")) {

View File

@ -39,7 +39,7 @@ import org.cloudburstmc.math.vector.Vector3d;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
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;

View File

@ -29,27 +29,28 @@ 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.*;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
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);

View File

@ -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);

View File

@ -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());
}
}

View File

@ -33,7 +33,7 @@ import lombok.experimental.UtilityClass;
import org.cloudburstmc.math.GenericMath;
import org.cloudburstmc.math.vector.Vector2i;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.packet.LevelChunkPacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;

View File

@ -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.

View File

@ -35,7 +35,7 @@ import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;

View File

@ -57,9 +57,11 @@ import javax.crypto.SecretKey;
import java.net.URI;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@ -70,6 +72,18 @@ public class LoginEncryptionUtils {
private static boolean HAS_SENT_ENCRYPTION_MESSAGE = false;
private static final ECPublicKey MOJANG_PUBLIC_KEY_OLD;
private static final ECPublicKey MOJANG_PUBLIC_KEY;
static {
try {
MOJANG_PUBLIC_KEY_OLD = EncryptionUtils.generateKey("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V");
MOJANG_PUBLIC_KEY = EncryptionUtils.generateKey("MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp");
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new AssertionError("Unable to initialize required encryption", e);
}
}
private static boolean validateChainData(List<SignedJWT> chain) throws Exception {
if (chain.size() != 3) {
return false;
@ -105,7 +119,7 @@ public class LoginEncryptionUtils {
return !iterator.hasNext();
}
if (lastKey.equals(EncryptionUtils.getMojangPublicKey())) {
if (verifyMojangPublicKey(lastKey)) {
mojangSigned = true;
}
@ -120,6 +134,10 @@ public class LoginEncryptionUtils {
return mojangSigned;
}
public static boolean verifyMojangPublicKey(ECPublicKey key) {
return MOJANG_PUBLIC_KEY.equals(key) || MOJANG_PUBLIC_KEY_OLD.equals(key);
}
public static void encryptPlayerConnection(GeyserSession session, LoginPacket loginPacket) {
encryptConnectionWithCert(session, loginPacket.getExtra().getParsedString(), loginPacket.getChain());
}

View File

@ -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(int javaBlockState) {
if (BlockStateValues.isHangingSign(javaBlockState)) {
return HANGING_SIGN_WIDTH_MAX;
}
return SIGN_WIDTH_MAX;
}
}

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

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

@ -1 +1 @@
Subproject commit 56c3eee7a5241b5609d1936f2a11b05dd1a3d568
Subproject commit f69b4db9a6f0e8fff8b29564195c76074210b924

View File

@ -30,8 +30,8 @@ package org.geysermc.geyser.translator.inventory.item;
//import com.github.steveice10.opennbt.tag.builtin.IntTag;
//import it.unimi.dsi.fastutil.Pair;
//import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
//import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
//import org.cloudburstmc.protocol.bedrock.data.defintions.SimpleItemDefinition;
//import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
//import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
//import org.geysermc.geyser.api.item.custom.CustomItemOptions;
//import org.geysermc.geyser.api.util.TriState;
//import org.geysermc.geyser.item.GeyserCustomItemOptions;

View File

@ -5,7 +5,7 @@ org.gradle.caching=true
org.gradle.parallel=true
group=org.geysermc
version=2.1.0-SNAPSHOT
version=2.1.1-SNAPSHOT
org.gradle.caching=true
org.gradle.parallel=true

View File

@ -9,11 +9,11 @@ netty = "4.1.80.Final"
guava = "29.0-jre"
gson = "2.3.1" # Provided by Spigot 1.8.8
websocket = "1.5.1"
protocol = "3.0.0.Beta1-20230507.200054-78"
protocol-connection = "3.0.0.Beta1-20230507.200054-77"
protocol = "3.0.0.Beta1-20230604.143616-86"
protocol-connection = "3.0.0.Beta1-20230604.143616-85"
raknet = "1.0.0.CR1-20230430.211932-7"
mcauthlib = "d9d773e"
mcprotocollib = "1.19.4-2-20230503.145414-3"
mcprotocollib = "1.20-1-20230607.135651-6" # Temporary hack - needs to be updated to release once publishing is fixed
adventure = "4.14.0-20230424.215040-7"
adventure-platform = "4.1.2"
junit = "5.9.2"