From 5151c25e1a28a9096a411711a9e6ae57b8314654 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Tue, 16 Apr 2024 20:52:46 -0700 Subject: [PATCH 01/12] Support RakNet Cookies (#4554) * Support cookies Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> * Use cloudburst upstream raknet * Comment out mavenLocal() --------- Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com> --- .../kotlin/geyser.modded-conventions.gradle.kts | 1 + core/build.gradle.kts | 4 ++++ .../java/org/geysermc/geyser/dump/DumpInfo.java | 3 +++ .../geyser/network/netty/GeyserServer.java | 17 +++++++++-------- gradle/libs.versions.toml | 2 +- settings.gradle.kts | 3 ++- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts index 91bde525e..e011b7139 100644 --- a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts @@ -107,6 +107,7 @@ dependencies { } repositories { + // mavenLocal() maven("https://repo.opencollab.dev/maven-releases/") maven("https://repo.opencollab.dev/maven-snapshots/") maven("https://jitpack.io") diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 054f4f0ae..93c9f4f13 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -6,6 +6,10 @@ plugins { } dependencies { + constraints { + implementation(libs.raknet) // Ensure protocol does not override the RakNet version + } + api(projects.common) api(projects.api) diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 818607314..e54bbc1eb 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -78,6 +78,7 @@ public class DumpInfo { private final GeyserConfiguration config; private final Floodgate floodgate; private final Object2IntMap userPlatforms; + private final int connectionAttempts; private final HashInfo hashInfo; private final RamInfo ramInfo; private LogsInfo logsInfo; @@ -129,6 +130,8 @@ public class DumpInfo { userPlatforms.put(device, userPlatforms.getOrDefault(device, 0) + 1); } + this.connectionAttempts = GeyserImpl.getInstance().getGeyserServer().getConnectionAttempts(); + this.bootstrapInfo = GeyserImpl.getInstance().getBootstrap().getDumpInfo(); this.flagsInfo = new FlagsInfo(); diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index db103d10e..8ead16623 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -74,7 +74,6 @@ import java.util.function.IntFunction; import java.util.function.Supplier; import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_GLOBAL_PACKET_LIMIT; -import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_OFFLINE_PACKET_LIMIT; import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_PACKET_LIMIT; public final class GeyserServer { @@ -111,6 +110,10 @@ public final class GeyserServer { private ChannelFuture[] bootstrapFutures; + // Keep track of connection attempts for dump info + @Getter + private int connectionAttempts = 0; + /** * The port to broadcast in the pong. This can be different from the port the server is bound to, e.g. due to port forwarding. */ @@ -217,11 +220,6 @@ public final class GeyserServer { int rakPacketLimit = positivePropOrDefault("Geyser.RakPacketLimit", DEFAULT_PACKET_LIMIT); this.geyser.getLogger().debug("Setting RakNet packet limit to " + rakPacketLimit); - boolean isWhitelistedProxyProtocol = this.geyser.getConfig().getBedrock().isEnableProxyProtocol() - && !this.geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs().isEmpty(); - int rakOfflinePacketLimit = positivePropOrDefault("Geyser.RakOfflinePacketLimit", isWhitelistedProxyProtocol ? Integer.MAX_VALUE : DEFAULT_OFFLINE_PACKET_LIMIT); - this.geyser.getLogger().debug("Setting RakNet offline packet limit to " + rakOfflinePacketLimit); - int rakGlobalPacketLimit = positivePropOrDefault("Geyser.RakGlobalPacketLimit", DEFAULT_GLOBAL_PACKET_LIMIT); this.geyser.getLogger().debug("Setting RakNet global packet limit to " + rakGlobalPacketLimit); @@ -231,8 +229,8 @@ public final class GeyserServer { .option(RakChannelOption.RAK_HANDLE_PING, true) .option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu()) .option(RakChannelOption.RAK_PACKET_LIMIT, rakPacketLimit) - .option(RakChannelOption.RAK_OFFLINE_PACKET_LIMIT, rakOfflinePacketLimit) .option(RakChannelOption.RAK_GLOBAL_PACKET_LIMIT, rakGlobalPacketLimit) + .option(RakChannelOption.RAK_SEND_COOKIE, true) .childHandler(serverInitializer); } @@ -248,6 +246,7 @@ public final class GeyserServer { } if (!isWhitelistedIP) { + connectionAttempts++; return false; } } @@ -270,10 +269,12 @@ public final class GeyserServer { geyser.eventBus().fire(requestEvent); if (requestEvent.isCancelled()) { geyser.getLogger().debug("Connection request from " + ip + " was cancelled using the API!"); + connectionAttempts++; return false; } - geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip)); + geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip)); + connectionAttempts++; return true; } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04c83ac78..3111750a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ gson = "2.3.1" # Provided by Spigot 1.8.8 websocket = "1.5.1" protocol = "3.0.0.Beta1-20240313.120922-126" protocol-connection = "3.0.0.Beta1-20240313.120922-125" -raknet = "1.0.0.CR1-20240330.103819-16" +raknet = "1.0.0.CR3-20240416.144209-1" blockstateupdater="1.20.70-20240303.125052-2" mcauthlib = "d9d773e" mcprotocollib = "1.20.4-2-20240116.220521-7" diff --git a/settings.gradle.kts b/settings.gradle.kts index 68848bca4..a39bfa3d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,8 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") dependencyResolutionManagement { repositories { + // mavenLocal() + // Floodgate, Cumulus etc. maven("https://repo.opencollab.dev/main") @@ -30,7 +32,6 @@ dependencyResolutionManagement { mavenContent { releasesOnly() } } - mavenLocal() mavenCentral() // ViaVersion From c8475d8100f6272a9c892ac81635f720c4d0ee58 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 17 Apr 2024 03:18:17 -0400 Subject: [PATCH 02/12] Always remove entities from cache (#4577) * Always remove entities from cache * Despawn throwable entities in the void --- .../geysermc/geyser/entity/type/Entity.java | 7 ++----- .../geyser/entity/type/FireballEntity.java | 3 +++ .../geyser/entity/type/FishingHookEntity.java | 3 +++ .../geyser/entity/type/ItemEntity.java | 2 +- .../geyser/entity/type/ItemFrameEntity.java | 3 +-- .../geyser/entity/type/ThrowableEntity.java | 20 +++++++++++++++++-- .../entity/type/living/ArmorStandEntity.java | 4 ++-- .../living/monster/EnderDragonEntity.java | 4 ++-- .../geyser/session/cache/EntityCache.java | 12 ++++++----- .../entity/JavaRemoveEntitiesTranslator.java | 2 +- 10 files changed, 40 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index a2b7cc6ec..306c244b3 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -200,11 +200,9 @@ public class Entity implements GeyserEntity { /** * Despawns the entity - * - * @return can be deleted */ - public boolean despawnEntity() { - if (!valid) return true; + public void despawnEntity() { + if (!valid) return; for (Entity passenger : passengers) { // Make sure all passengers on the despawned entity are updated if (passenger == null) continue; @@ -218,7 +216,6 @@ public class Entity implements GeyserEntity { session.sendUpstreamPacket(removeEntityPacket); valid = false; - return true; } public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java index 3db032f0f..904596b3a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FireballEntity.java @@ -72,6 +72,9 @@ public class FireballEntity extends ThrowableEntity { @Override public void tick() { + if (removedInVoid()) { + return; + } moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false); } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java index bcbff16ce..0de11c382 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/FishingHookEntity.java @@ -133,6 +133,9 @@ public class FishingHookEntity extends ThrowableEntity { @Override public void tick() { + if (removedInVoid()) { + return; + } if (hooked || !isInAir() && !isInWater() || isOnGround()) { motion = Vector3f.ZERO; return; diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java index bb67a60f6..69fb3faab 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemEntity.java @@ -74,7 +74,7 @@ public class ItemEntity extends ThrowableEntity { @Override public void tick() { - if (isInWater()) { + if (removedInVoid() || isInWater()) { return; } if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java index 295972200..ad1d4b928 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ItemFrameEntity.java @@ -148,7 +148,7 @@ public class ItemFrameEntity extends Entity { } @Override - public boolean despawnEntity() { + public void despawnEntity() { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); @@ -161,7 +161,6 @@ public class ItemFrameEntity extends Entity { session.getItemFrameCache().remove(bedrockPosition, this); valid = false; - return true; } private NbtMap getDefaultTag() { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java index 60840e65b..47884e60a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableEntity.java @@ -55,6 +55,9 @@ public class ThrowableEntity extends Entity implements Tickable { */ @Override public void tick() { + if (removedInVoid()) { + return; + } moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false); float drag = getDrag(); float gravity = getGravity(); @@ -170,14 +173,14 @@ public class ThrowableEntity extends Entity implements Tickable { } @Override - public boolean despawnEntity() { + public void despawnEntity() { if (definition.entityType() == EntityType.ENDER_PEARL) { LevelEventPacket particlePacket = new LevelEventPacket(); particlePacket.setType(LevelEvent.PARTICLE_TELEPORT); particlePacket.setPosition(position); session.sendUpstreamPacket(particlePacket); } - return super.despawnEntity(); + super.despawnEntity(); } @Override @@ -191,4 +194,17 @@ public class ThrowableEntity extends Entity implements Tickable { moveAbsoluteImmediate(position, yaw, pitch, headYaw, isOnGround, teleported); lastJavaPosition = position; } + + /** + * Removes the entity if it is 64 blocks below the world. + * + * @return true if the entity was removed + */ + public boolean removedInVoid() { + if (position.getY() < session.getDimensionType().minY() - 64) { + session.getEntityCache().removeEntity(this); + return true; + } + return false; + } } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index c64776f18..9c56568c8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -99,11 +99,11 @@ public class ArmorStandEntity extends LivingEntity { } @Override - public boolean despawnEntity() { + public void despawnEntity() { if (secondEntity != null) { secondEntity.despawnEntity(); } - return super.despawnEntity(); + super.despawnEntity(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java index bb09a23f4..8c00d065a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/monster/EnderDragonEntity.java @@ -148,11 +148,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable { } @Override - public boolean despawnEntity() { + public void despawnEntity() { for (EnderDragonPartEntity part : allParts) { part.despawnEntity(); } - return super.despawnEntity(); + super.despawnEntity(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java index 8a4b9cb6c..6524e1ddc 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java @@ -85,27 +85,29 @@ public class EntityCache { return false; } - public boolean removeEntity(Entity entity, boolean force) { + public void removeEntity(Entity entity) { if (entity instanceof PlayerEntity player) { session.getPlayerWithCustomHeads().remove(player.getUuid()); } - if (entity != null && entity.isValid() && (force || entity.despawnEntity())) { + if (entity != null) { + if (entity.isValid()) { + entity.despawnEntity(); + } + long geyserId = entityIdTranslations.remove(entity.getEntityId()); entities.remove(geyserId); if (entity instanceof Tickable) { tickableEntities.remove(entity); } - return true; } - return false; } public void removeAllEntities() { List entities = new ArrayList<>(this.entities.values()); for (Entity entity : entities) { - removeEntity(entity, false); + removeEntity(entity); } session.getPlayerWithCustomHeads().clear(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java index 65229f29d..776cfb4d7 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/JavaRemoveEntitiesTranslator.java @@ -39,7 +39,7 @@ public class JavaRemoveEntitiesTranslator extends PacketTranslator Date: Wed, 17 Apr 2024 23:21:25 +0200 Subject: [PATCH 03/12] Feature: Structure block translation (#4521) * ported camotoy's attempt of implementing structure blocks, removal of a few TODO's * no more parsing of java structure templates * Don't attempt to re-request structure size * ensure we can load structures in even if we know the size * init: send correct structure size/offset/rotation to java, not fully working yet * restore offsets so we are sending correct values to the java server regarding where we want the structure to be placed * something something mirror * attempt at proper offsets for mirroring AND rotations. this was not fun at all * rotation, mirror, offsetting all seem to work * undo import changes * fix NPE * Proper handling of empty structures, ensure that we can clear the structure block outline when a zero vector was sent for structure size * oops * Update core/src/main/java/org/geysermc/geyser/session/cache/StructureBlockCache.java Co-authored-by: rtm516 * Update core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java Co-authored-by: rtm516 * Update core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureBlockUpdateTranslator.java Co-authored-by: rtm516 * Update core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureTemplateDataRequestTranslator.java Co-authored-by: rtm516 * consolidate java structure sending into one method * fix merge conflict --------- Co-authored-by: rtm516 --- README.md | 1 - .../populator/BlockRegistryPopulator.java | 16 +- .../geyser/registry/type/BlockMappings.java | 5 + .../geyser/session/GeyserSession.java | 2 + .../session/cache/StructureBlockCache.java | 59 +++++++ .../StructureBlockBlockEntityTranslator.java | 138 +++++++++++++++ ...BedrockInventoryTransactionTranslator.java | 7 + ...BedrockStructureBlockUpdateTranslator.java | 65 +++++++ ...tructureTemplateDataRequestTranslator.java | 91 ++++++++++ .../level/JavaBlockEntityDataTranslator.java | 59 +++++++ .../geysermc/geyser/util/InventoryUtils.java | 2 +- .../geyser/util/StructureBlockUtils.java | 164 ++++++++++++++++++ 12 files changed, 606 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/session/cache/StructureBlockCache.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureBlockUpdateTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureTemplateDataRequestTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/util/StructureBlockUtils.java diff --git a/README.md b/README.md index ce2b67af1..0aa9d009a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Ge ## What's Left to be Added/Fixed - Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) - Some Entity Flags -- Structure block UI ## What can't be fixed There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page. diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java index 4c52a1530..c54431fbe 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java @@ -144,7 +144,7 @@ public final class BlockRegistryPopulator { builder.remove("version"); // Remove all nbt tags which are not needed for differentiating states builder.remove("name_hash"); // Quick workaround - was added in 1.19.20 builder.remove("network_id"); // Added in 1.19.80 - ???? - builder.remove("block_id"); // Added in 1.20.60 //TODO verify this can be just removed + builder.remove("block_id"); // Added in 1.20.60 //noinspection UnstableApiUsage builder.putCompound("states", statesInterner.intern((NbtMap) builder.remove("states"))); vanillaBlockStates.set(i, builder.build()); @@ -229,6 +229,7 @@ public final class BlockRegistryPopulator { Map itemFrames = new Object2ObjectOpenHashMap<>(); Set jigsawDefinitions = new ObjectOpenHashSet<>(); + Map structureBlockDefinitions = new Object2ObjectOpenHashMap<>(); BlockMappings.BlockMappingsBuilder builder = BlockMappings.builder(); while (blocksIterator.hasNext()) { @@ -272,6 +273,18 @@ public final class BlockRegistryPopulator { jigsawDefinitions.add(bedrockDefinition); } + if (javaId.contains("structure_block")) { + int modeIndex = javaId.indexOf("mode="); + if (modeIndex != -1) { + int startIndex = modeIndex + 5; // Length of "mode=" is 5 + int endIndex = javaId.indexOf("]", startIndex); + if (endIndex != -1) { + String modeValue = javaId.substring(startIndex, endIndex); + structureBlockDefinitions.put(modeValue.toUpperCase(), bedrockDefinition); + } + } + } + boolean waterlogged = entry.getKey().contains("waterlogged=true") || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); @@ -358,6 +371,7 @@ public final class BlockRegistryPopulator { .itemFrames(itemFrames) .flowerPotBlocks(flowerPotBlocks) .jigsawStates(jigsawDefinitions) + .structureBlockStates(structureBlockDefinitions) .remappedVanillaIds(remappedVanillaIds) .blockProperties(customBlockProperties) .customBlockStateDefinitions(customBlockStateDefinitions) diff --git a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java index 8c0414a6d..c76f024af 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java +++ b/core/src/main/java/org/geysermc/geyser/registry/type/BlockMappings.java @@ -61,6 +61,7 @@ public class BlockMappings implements DefinitionRegistry { Map flowerPotBlocks; Set jigsawStates; + Map structureBlockStates; List blockProperties; Object2ObjectMap customBlockStateDefinitions; @@ -96,6 +97,10 @@ public class BlockMappings implements DefinitionRegistry { return false; } + public BlockDefinition getStructureBlockFromMode(String mode) { + return structureBlockStates.get(mode); + } + @Override public @Nullable GeyserBedrockBlock getDefinition(int bedrockId) { if (bedrockId < 0 || bedrockId >= this.bedrockRuntimeMap.length) { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 7a4a8ff6f..c0749400a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -214,6 +214,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private final PistonCache pistonCache; private final PreferencesCache preferencesCache; private final SkullCache skullCache; + private final StructureBlockCache structureBlockCache; private final TagCache tagCache; private final WorldCache worldCache; @@ -625,6 +626,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { this.pistonCache = new PistonCache(this); this.preferencesCache = new PreferencesCache(this); this.skullCache = new SkullCache(this); + this.structureBlockCache = new StructureBlockCache(); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); this.cameraData = new GeyserCameraData(this); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/StructureBlockCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/StructureBlockCache.java new file mode 100644 index 000000000..170f80d0a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/StructureBlockCache.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2024 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.session.cache; + +import lombok.Getter; +import lombok.Setter; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.math.vector.Vector3i; + +@Setter +@Getter +public final class StructureBlockCache { + + /** + * Stores the current structure's name to be able to detect changes in the loaded structure + */ + private @Nullable String currentStructureName; + + /** + * Stores the offset changes added by Geyser that ensure that structure bounds + * are the same for Java and Bedrock + */ + private @Nullable Vector3i bedrockOffset; + + /** + * Stores the current structure block position while we're waiting on the Java + * server to send the data we need. + */ + private @Nullable Vector3i currentStructureBlock; + + public void clear() { + this.currentStructureName = null; + this.currentStructureBlock = null; + this.bedrockOffset = null; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java new file mode 100644 index 000000000..a24d010b7 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/StructureBlockBlockEntityTranslator.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2019-2024 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 org.cloudburstmc.math.vector.Vector3i; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation; +import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.StructureBlockUtils; + +@BlockEntity(type = BlockEntityType.STRUCTURE_BLOCK) +public class StructureBlockBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public NbtMap getBlockEntityTag(GeyserSession session, BlockEntityType type, int x, int y, int z, CompoundTag tag, int blockState) { + // Sending a structure with size 0 doesn't clear the outline. Hence, we have to force it by replacing the block :/ + int xStructureSize = getOrDefault(tag.get("sizeX"), 0); + int yStructureSize = getOrDefault(tag.get("sizeY"), 0); + int zStructureSize = getOrDefault(tag.get("sizeZ"), 0); + + Vector3i size = Vector3i.from(xStructureSize, yStructureSize, zStructureSize); + + if (size.equals(Vector3i.ZERO)) { + Vector3i position = Vector3i.from(x, y, z); + String mode = getOrDefault(tag.get("mode"), ""); + + // Set to air and back to reset the structure block + UpdateBlockPacket emptyBlockPacket = new UpdateBlockPacket(); + emptyBlockPacket.setDataLayer(0); + emptyBlockPacket.setBlockPosition(position); + emptyBlockPacket.setDefinition(session.getBlockMappings().getBedrockAir()); + session.sendUpstreamPacket(emptyBlockPacket); + + UpdateBlockPacket spawnerBlockPacket = new UpdateBlockPacket(); + spawnerBlockPacket.setDataLayer(0); + spawnerBlockPacket.setBlockPosition(position); + spawnerBlockPacket.setDefinition(session.getBlockMappings().getStructureBlockFromMode(mode)); + session.sendUpstreamPacket(spawnerBlockPacket); + } + + return super.getBlockEntityTag(session, type, x, y, z, tag, blockState); + } + + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + if (tag.size() < 5) { + return; // These values aren't here + } + + builder.putString("structureName", getOrDefault(tag.get("name"), "")); + + String mode = getOrDefault(tag.get("mode"), ""); + int bedrockData = switch (mode) { + case "LOAD" -> 2; + case "CORNER" -> 3; + case "DATA" -> 4; + default -> 1; // SAVE + }; + + builder.putInt("data", bedrockData); + builder.putString("dataField", ""); // ??? possibly related to Java's "metadata" + + // Mirror behaves different in Java and Bedrock - it requires modifying the position in space as well + String mirror = getOrDefault(tag.get("mirror"), ""); + StructureMirror bedrockMirror = switch (mirror) { + case "FRONT_BACK" -> StructureMirror.X; + case "LEFT_RIGHT" -> StructureMirror.Z; + default -> StructureMirror.NONE; + }; + builder.putByte("mirror", (byte) bedrockMirror.ordinal()); + + builder.putByte("ignoreEntities", getOrDefault(tag.get("ignoreEntities"), (byte) 0)); + builder.putByte("isPowered", getOrDefault(tag.get("powered"), (byte) 0)); + builder.putLong("seed", getOrDefault(tag.get("seed"), 0L)); + builder.putByte("showBoundingBox", getOrDefault(tag.get("showboundingbox"), (byte) 0)); + + String rotation = getOrDefault(tag.get("rotation"), ""); + StructureRotation bedrockRotation = switch (rotation) { + case "CLOCKWISE_90" -> StructureRotation.ROTATE_90; + case "CLOCKWISE_180" -> StructureRotation.ROTATE_180; + case "COUNTERCLOCKWISE_90" -> StructureRotation.ROTATE_270; + default -> StructureRotation.NONE; + }; + builder.putByte("rotation", (byte) bedrockRotation.ordinal()); + + int xStructureSize = getOrDefault(tag.get("sizeX"), 0); + int yStructureSize = getOrDefault(tag.get("sizeY"), 0); + int zStructureSize = getOrDefault(tag.get("sizeZ"), 0); + + // The "positions" are also offsets on Java + int posX = getOrDefault(tag.get("posX"), 0); + int posY = getOrDefault(tag.get("posY"), 0); + int posZ = getOrDefault(tag.get("posZ"), 0); + + Vector3i offset = StructureBlockUtils.calculateOffset(bedrockRotation, bedrockMirror, + xStructureSize, zStructureSize); + + builder.putInt("xStructureOffset", posX + offset.getX()); + builder.putInt("yStructureOffset", posY); + builder.putInt("zStructureOffset", posZ + offset.getZ()); + + builder.putInt("xStructureSize", xStructureSize); + builder.putInt("yStructureSize", yStructureSize); + builder.putInt("zStructureSize", zStructureSize); + + builder.putFloat("integrity", getOrDefault(tag.get("integrity"), 0f)); // Is 1.0f by default on Java but 100.0f on Bedrock + + // Java's "showair" is unrepresented + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 101035cb5..4ac835268 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -348,6 +348,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, StructureBlockUpdatePacket packet) { + StructureEditorData data = packet.getEditorData(); + + UpdateStructureBlockAction action = UpdateStructureBlockAction.UPDATE_DATA; + if (packet.isPowered()) { + if (data.getType() == StructureBlockType.LOAD) { + action = UpdateStructureBlockAction.LOAD_STRUCTURE; + } else if (data.getType() == StructureBlockType.SAVE) { + action = UpdateStructureBlockAction.SAVE_STRUCTURE; + } + } + + UpdateStructureBlockMode mode = switch (data.getType()) { + case CORNER -> UpdateStructureBlockMode.CORNER; + case DATA -> UpdateStructureBlockMode.DATA; + case LOAD -> UpdateStructureBlockMode.LOAD; + default -> UpdateStructureBlockMode.SAVE; + }; + + StructureBlockUtils.sendJavaStructurePacket(session, packet.getBlockPosition(), data.getSettings().getSize(), mode, action, data.getSettings(), + data.isBoundingBoxVisible(), data.getName()); + session.getStructureBlockCache().clear(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureTemplateDataRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureTemplateDataRequestTranslator.java new file mode 100644 index 000000000..947946f36 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockStructureTemplateDataRequestTranslator.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019-2024 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.protocol.bedrock; + +import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction; +import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode; +import org.cloudburstmc.math.vector.Vector3i; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureSettings; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureTemplateRequestOperation; +import org.cloudburstmc.protocol.bedrock.packet.StructureTemplateDataRequestPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.StructureBlockUtils; + +/** + * Packet used in Bedrock to load structure size into the structure block GUI. It is sent every time the GUI is opened. + * Or, if the player updates the structure name. Which we can use to request the structure size from the Java server! + *

+ * Java does not have this preview, instead, Java clients are forced out of the GUI to look at the area. + */ +@Translator(packet = StructureTemplateDataRequestPacket.class) +public class BedrockStructureTemplateDataRequestTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, StructureTemplateDataRequestPacket packet) { + // All other operation types are ignored by Geyser since we do not support exporting/importing structures + if (packet.getOperation().equals(StructureTemplateRequestOperation.QUERY_SAVED_STRUCTURE)) { + Vector3i size = packet.getSettings().getSize(); + StructureSettings settings = packet.getSettings(); + + // If we send a load packet to the Java server when the structure size is known, it would place the structure. + String currentStructureName = session.getStructureBlockCache().getCurrentStructureName(); + + // Case 1: Opening a structure block with information about structure size, but not yet saved by us + // Case 2: Getting an update from Bedrock with new information, doesn't bother us if it's the same structure + if (!packet.getSettings().getSize().equals(Vector3i.ZERO)) { + if (currentStructureName == null) { + Vector3i offset = StructureBlockUtils.calculateOffset(settings.getRotation(), settings.getMirror(), + settings.getSize().getX(), settings.getSize().getZ()); + session.getStructureBlockCache().setBedrockOffset(offset); + session.getStructureBlockCache().setCurrentStructureName(packet.getName()); + StructureBlockUtils.sendStructureData(session, size, packet.getName()); + return; + } else if (packet.getName().equals(currentStructureName)) { + StructureBlockUtils.sendStructureData(session, size, packet.getName()); + return; + } + } + + // Request a "structure load" from Java server, so it sends us the structure's size + // See the block entity translator for more info + session.getStructureBlockCache().setCurrentStructureBlock(packet.getPosition()); + + StructureBlockUtils.sendJavaStructurePacket(session, + packet.getPosition(), + Vector3i.ZERO, // We expect the Java server to tell us the size + UpdateStructureBlockMode.LOAD, + UpdateStructureBlockAction.LOAD_STRUCTURE, + settings, + true, + packet.getName() + ); + } else { + StructureBlockUtils.sendEmptyStructureData(session); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java index c67a6dee4..325bae593 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaBlockEntityDataTranslator.java @@ -28,9 +28,13 @@ package org.geysermc.geyser.translator.protocol.java.level; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundBlockEntityDataPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureMirror; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation; import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.level.block.BlockStateValues; @@ -41,6 +45,7 @@ import org.geysermc.geyser.translator.level.block.entity.SkullBlockEntityTransla import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.geyser.util.BlockEntityUtils; +import org.geysermc.geyser.util.StructureBlockUtils; @Translator(packet = ClientboundBlockEntityDataPacket.class) public class JavaBlockEntityDataTranslator extends PacketTranslator { @@ -95,5 +100,59 @@ public class JavaBlockEntityDataTranslator extends PacketTranslator 5 + ) { + CompoundTag map = packet.getNbt(); + + String mode = getOrDefault(map.get("mode"), ""); + if (!mode.equalsIgnoreCase("LOAD")) { + return; + } + + String mirror = getOrDefault(map.get("mirror"), ""); + StructureMirror bedrockMirror = switch (mirror) { + case "FRONT_BACK" -> StructureMirror.X; + case "LEFT_RIGHT" -> StructureMirror.Z; + default -> StructureMirror.NONE; + }; + + String rotation = getOrDefault(map.get("rotation"), ""); + StructureRotation bedrockRotation = switch (rotation) { + case "CLOCKWISE_90" -> StructureRotation.ROTATE_90; + case "CLOCKWISE_180" -> StructureRotation.ROTATE_180; + case "COUNTERCLOCKWISE_90" -> StructureRotation.ROTATE_270; + default -> StructureRotation.NONE; + }; + + String name = getOrDefault(map.get("name"), ""); + int sizeX = getOrDefault(map.get("sizeX"), 0); + int sizeY = getOrDefault(map.get("sizeY"), 0); + int sizeZ = getOrDefault(map.get("sizeZ"), 0); + + session.getStructureBlockCache().setCurrentStructureBlock(null); + + Vector3i size = Vector3i.from(sizeX, sizeY, sizeZ); + if (size.equals(Vector3i.ZERO)) { + StructureBlockUtils.sendEmptyStructureData(session); + return; + } + + Vector3i offset = StructureBlockUtils.calculateOffset(bedrockRotation, bedrockMirror, + sizeX, sizeZ); + session.getStructureBlockCache().setBedrockOffset(offset); + session.getStructureBlockCache().setCurrentStructureName(name); + StructureBlockUtils.sendStructureData(session, size, name); + } + } + + + protected T getOrDefault(Tag tag, T defaultValue) { + //noinspection unchecked + return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; } } diff --git a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java index 30b2907cc..25976f0f5 100644 --- a/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/InventoryUtils.java @@ -125,7 +125,7 @@ public class InventoryUtils { InventoryTranslator translator = session.getInventoryTranslator(); translator.closeInventory(session, inventory); if (confirm && inventory.isDisplayed() && !inventory.isPending() - && !(translator instanceof LecternInventoryTranslator) // TODO: double-check + && !(translator instanceof LecternInventoryTranslator) // Closing lecterns is not followed with a close confirmation ) { session.setClosingInventory(true); } diff --git a/core/src/main/java/org/geysermc/geyser/util/StructureBlockUtils.java b/core/src/main/java/org/geysermc/geyser/util/StructureBlockUtils.java new file mode 100644 index 000000000..9b3cfb53f --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/util/StructureBlockUtils.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2019-2024 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.util; + +import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockAction; +import com.github.steveice10.mc.protocol.data.game.inventory.UpdateStructureBlockMode; +import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSetStructureBlockPacket; +import org.cloudburstmc.math.vector.Vector3i; +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.structure.StructureMirror; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureRotation; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureSettings; +import org.cloudburstmc.protocol.bedrock.data.structure.StructureTemplateResponseType; +import org.cloudburstmc.protocol.bedrock.packet.StructureTemplateDataResponsePacket; +import org.geysermc.geyser.session.GeyserSession; + +public class StructureBlockUtils { + + private static final NbtMap EMPTY_STRUCTURE_DATA; + + static { + NbtMapBuilder builder = NbtMap.builder(); + builder.putInt("format_version", 1); + builder.putCompound("structure", NbtMap.builder() + .putList("block_indices", NbtType.LIST, NbtList.EMPTY, NbtList.EMPTY) + .putList("entities", NbtType.COMPOUND) + .putCompound("palette", NbtMap.EMPTY) + .build()); + builder.putList("structure_world_origin", NbtType.INT, 0, 0, 0); + EMPTY_STRUCTURE_DATA = builder.build(); + } + + public static void sendEmptyStructureData(GeyserSession session) { + StructureTemplateDataResponsePacket responsePacket = new StructureTemplateDataResponsePacket(); + responsePacket.setName(""); + responsePacket.setSave(false); + responsePacket.setType(StructureTemplateResponseType.QUERY); + session.sendUpstreamPacket(responsePacket); + } + + public static void sendStructureData(GeyserSession session,Vector3i size, String name) { + StructureTemplateDataResponsePacket responsePacket = new StructureTemplateDataResponsePacket(); + responsePacket.setName(name); + responsePacket.setSave(true); + responsePacket.setTag(EMPTY_STRUCTURE_DATA.toBuilder() + // Bedrock does not like negative sizes here + .putList("size", NbtType.INT, Math.abs(size.getX()), size.getY(), Math.abs(size.getZ())) + .build()); + responsePacket.setType(StructureTemplateResponseType.QUERY); + session.sendUpstreamPacket(responsePacket); + } + + public static Vector3i calculateOffset(StructureRotation structureRotation, StructureMirror structureMirror, + int sizeX, int sizeZ) { + int newOffsetX = 0; + int newOffsetZ = 0; + + switch (structureRotation) { + case ROTATE_90 -> { + switch (structureMirror) { + case NONE -> newOffsetX -= sizeZ - 1; + case X -> { + newOffsetZ -= sizeX - 1; + newOffsetX -= sizeZ - 1; + } + } + } + case ROTATE_180 -> { + switch (structureMirror) { + case NONE -> { + newOffsetX -= sizeX - 1; + newOffsetZ -= sizeZ - 1; + } + case Z -> newOffsetX -= sizeX - 1; + case X -> newOffsetZ -= sizeZ - 1; + } + } + case ROTATE_270 -> { + switch (structureMirror) { + case NONE -> newOffsetZ -= sizeX - 1; + case Z -> { + newOffsetZ -= sizeX - 1; + newOffsetX -= sizeZ - 1; + } + } + } + default -> { + switch (structureMirror) { + case Z -> newOffsetZ -= sizeZ - 1; + case X -> newOffsetX -= sizeX - 1; + } + } + } + + return Vector3i.from(newOffsetX, 0, newOffsetZ); + } + + public static void sendJavaStructurePacket(GeyserSession session, Vector3i blockPosition, Vector3i size, UpdateStructureBlockMode mode, UpdateStructureBlockAction action, + StructureSettings settings, boolean boundingBoxVisible, String structureName) { + + com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror mirror = switch (settings.getMirror()) { + case X -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.FRONT_BACK; + case Z -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.LEFT_RIGHT; + default -> com.github.steveice10.mc.protocol.data.game.level.block.StructureMirror.NONE; + }; + + com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation rotation = switch (settings.getRotation()) { + case ROTATE_90 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.CLOCKWISE_90; + case ROTATE_180 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.CLOCKWISE_180; + case ROTATE_270 -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.COUNTERCLOCKWISE_90; + default -> com.github.steveice10.mc.protocol.data.game.level.block.StructureRotation.NONE; + }; + + Vector3i offset = settings.getOffset(); + if (session.getStructureBlockCache().getBedrockOffset() != null) { + offset = settings.getOffset().sub(session.getStructureBlockCache().getBedrockOffset()); + } + + ServerboundSetStructureBlockPacket structureBlockPacket = new ServerboundSetStructureBlockPacket( + blockPosition, + action, + mode, + structureName, + offset, + settings.getSize(), + mirror, + rotation, + "", + settings.getIntegrityValue(), + settings.getIntegritySeed(), + settings.isIgnoringEntities(), + false, + boundingBoxVisible + ); + + session.sendDownstreamPacket(structureBlockPacket); + } +} From 576a1b7d7a6360bfffc4923c708446f4a92f7c96 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 17 Apr 2024 17:53:56 -0400 Subject: [PATCH 04/12] Remove map cache (#4576) --- .../geyser/session/GeyserSession.java | 2 - .../BedrockMapInfoRequestTranslator.java | 50 ------------------- .../java/level/JavaMapItemDataTranslator.java | 17 ++----- 3 files changed, 5 insertions(+), 64 deletions(-) delete mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index c0749400a..2a7b9b3fc 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -262,8 +262,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Setter private ItemMappings itemMappings; - private final Long2ObjectMap storedMaps = new Long2ObjectOpenHashMap<>(); - /** * Required to decode biomes correctly. */ diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java deleted file mode 100644 index 3e885b63f..000000000 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMapInfoRequestTranslator.java +++ /dev/null @@ -1,50 +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.translator.protocol.bedrock; - -import org.cloudburstmc.protocol.bedrock.packet.ClientboundMapItemDataPacket; -import org.cloudburstmc.protocol.bedrock.packet.MapInfoRequestPacket; -import org.geysermc.geyser.session.GeyserSession; -import org.geysermc.geyser.translator.protocol.PacketTranslator; -import org.geysermc.geyser.translator.protocol.Translator; - -import java.util.concurrent.TimeUnit; - -@Translator(packet = MapInfoRequestPacket.class) -public class BedrockMapInfoRequestTranslator extends PacketTranslator { - - @Override - public void translate(GeyserSession session, MapInfoRequestPacket packet) { - long mapId = packet.getUniqueMapId(); - - ClientboundMapItemDataPacket mapPacket = session.getStoredMaps().remove(mapId); - if (mapPacket != null) { - // Delay the packet 100ms to prevent the client from ignoring the packet - session.scheduleInEventLoop(() -> session.sendUpstreamPacket(mapPacket), - 100, TimeUnit.MILLISECONDS); - } - } -} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java index 34fbf2d9c..01b0f324f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaMapItemDataTranslator.java @@ -44,7 +44,6 @@ public class JavaMapItemDataTranslator extends PacketTranslator Date: Thu, 18 Apr 2024 00:16:41 -0400 Subject: [PATCH 05/12] Reset metadata and attributes if needed when respawning (#4566) * Reset metadata and attributes if needed when respawning * Minor edits * Reset attributes in JavaLoginTranslator * Fix client bug when updating absorption and health in the same tick --- .../entity/attribute/GeyserAttributeType.java | 6 ++- .../entity/type/player/PlayerEntity.java | 5 ++- .../type/player/SessionPlayerEntity.java | 39 +++++++++++++++++++ .../geyser/session/GeyserSession.java | 2 +- .../protocol/java/JavaLoginTranslator.java | 2 +- .../protocol/java/JavaRespawnTranslator.java | 8 ++++ 6 files changed, 57 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java index 234c1afe9..88d493275 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java +++ b/core/src/main/java/org/geysermc/geyser/entity/attribute/GeyserAttributeType.java @@ -51,7 +51,7 @@ public enum GeyserAttributeType { MAX_HEALTH("minecraft:generic.max_health", null, 0f, 1024f, 20f), // Bedrock Attributes - ABSORPTION(null, "minecraft:absorption", 0f, Float.MAX_VALUE, 0f), + ABSORPTION(null, "minecraft:absorption", 0f, 1024f, 0f), EXHAUSTION(null, "minecraft:player.exhaustion", 0f, 5f, 0f), EXPERIENCE(null, "minecraft:player.experience", 0f, 1f, 0f), EXPERIENCE_LEVEL(null, "minecraft:player.level", 0f, 24791.00f, 0f), @@ -66,6 +66,10 @@ public enum GeyserAttributeType { private final float maximum; private final float defaultValue; + public AttributeData getAttribute() { + return getAttribute(defaultValue); + } + public AttributeData getAttribute(float value) { return getAttribute(value, maximum); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 9e3888138..20819f75e 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -55,6 +55,7 @@ import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity; import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.entity.attribute.GeyserAttributeType; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.living.animal.tameable.ParrotEntity; @@ -283,7 +284,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { attributesPacket.setRuntimeEntityId(geyserId); // Setting to a higher maximum since plugins/datapacks can probably extend the Bedrock soft limit attributesPacket.setAttributes(Collections.singletonList( - new AttributeData("minecraft:absorption", 0.0f, 1024f, entityMetadata.getPrimitiveValue(), 0.0f))); + GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue()))); session.sendUpstreamPacket(attributesPacket); } @@ -307,7 +308,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { * Sets the parrot occupying the shoulder. Bedrock Edition requires a full entity whereas Java Edition just * spawns it from the NBT data provided */ - private void setParrot(CompoundTag tag, boolean isLeft) { + protected void setParrot(CompoundTag tag, boolean isLeft) { if (tag != null && !tag.isEmpty()) { if ((isLeft && leftParrot != null) || (!isLeft && rightParrot != null)) { // No need to update a parrot's data when it already exists diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java index 751a24871..89aa540d8 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/SessionPlayerEntity.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeTyp import com.github.steveice10.mc.protocol.data.game.entity.metadata.GlobalPos; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; @@ -255,13 +256,51 @@ public class SessionPlayerEntity extends PlayerEntity { return session.getAuthData().uuid(); } + @Override + public void setAbsorptionHearts(FloatEntityMetadata entityMetadata) { + // The bedrock client can glitch when sending a health and absorption attribute in the same tick + // This can happen when switching servers. Resending the absorption attribute fixes the issue + attributes.put(GeyserAttributeType.ABSORPTION, GeyserAttributeType.ABSORPTION.getAttribute(entityMetadata.getPrimitiveValue())); + super.setAbsorptionHearts(entityMetadata); + } + public void resetMetadata() { // Reset all metadata to their default values // This is used when a player respawns + this.flags.clear(); this.initializeMetadata(); // Reset air this.resetAir(); + + // Explicitly reset all metadata not handled by initializeMetadata + setParrot(null, true); + setParrot(null, false); + + // Absorption is metadata in java edition + attributes.remove(GeyserAttributeType.ABSORPTION); + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.setAttributes(Collections.singletonList( + GeyserAttributeType.ABSORPTION.getAttribute(0f))); + session.sendUpstreamPacket(attributesPacket); + + dirtyMetadata.put(EntityDataTypes.EFFECT_COLOR, 0); + dirtyMetadata.put(EntityDataTypes.EFFECT_AMBIENCE, (byte) 0); + dirtyMetadata.put(EntityDataTypes.FREEZING_EFFECT_STRENGTH, 0f); + + silent = false; + } + + public void resetAttributes() { + attributes.clear(); + maxHealth = GeyserAttributeType.MAX_HEALTH.getDefaultValue(); + + UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); + attributesPacket.setRuntimeEntityId(geyserId); + attributesPacket.setAttributes(Collections.singletonList( + GeyserAttributeType.MOVEMENT_SPEED.getAttribute())); + session.sendUpstreamPacket(attributesPacket); } public void resetAir() { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 2a7b9b3fc..aff21182e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -712,7 +712,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Default move speed // Bedrock clients move very fast by default until they get an attribute packet correcting the speed attributesPacket.setAttributes(Collections.singletonList( - new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f))); + GeyserAttributeType.MOVEMENT_SPEED.getAttribute())); upstream.sendPacket(attributesPacket); GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java index 23c19e84f..4a15157f9 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaLoginTranslator.java @@ -71,7 +71,7 @@ public class JavaLoginTranslator extends PacketTranslator Date: Thu, 18 Apr 2024 07:29:40 -0700 Subject: [PATCH 06/12] Update release for preview builds (#4575) --- .github/workflows/build.yml | 47 +++++++++++++++----- .github/workflows/preview.yml | 73 +++++++++++++++++++++++++++++++ .github/workflows/pullrequest.yml | 18 ++++++++ 3 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/preview.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d70d9948..c7b439182 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,7 @@ on: paths-ignore: - '.github/ISSUE_TEMPLATE/*.yml' - '.github/actions/pullrequest.yml' + - '.github/actions/preview.yml' - '.idea/copyright/*.xml' - '.gitignore' - 'CONTRIBUTING.md' @@ -19,6 +20,8 @@ on: jobs: build: runs-on: ubuntu-latest + env: + PROJECT: 'geyser' steps: - name: Checkout repository and submodules # See https://github.com/actions/checkout/commits @@ -103,6 +106,34 @@ jobs: with: arguments: publish + - name: Get Release Metadata + if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} + uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # https://github.com/Kas-tle/base-release-action/releases/tag/main-11 + with: + appID: ${{ secrets.RELEASE_APP_ID }} + appPrivateKey: ${{ secrets.RELEASE_APP_PK }} + files: | + bungeecord:bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar + fabric:bootstrap/mod/fabric/build/libs/Geyser-Fabric.jar + neoforge:bootstrap/mod/neoforge/build/libs/Geyser-NeoForge.jar + spigot:bootstrap/spigot/build/libs/Geyser-Spigot.jar + standalone:bootstrap/standalone/build/libs/Geyser-Standalone.jar + velocity:bootstrap/velocity/build/libs/Geyser-Velocity.jar + viaproxy:bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar + releaseEnabled: false + saveMetadata: true + - name: Update Generated Metadata + if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} + run: | + cat metadata.json + echo + version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2) + cat metadata.json | jq --arg project "${PROJECT}" --arg version "${version}" ' + . + | .changes |= map({"commit", "summary", "message"}) + | .downloads |= map_values({"name", "sha256"}) + | {$project, "repo", $version, "number": .build, "changes", "downloads"} + ' | tee metadata.json - name: Publish to Downloads API if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} shell: bash @@ -114,19 +145,13 @@ jobs: # Save the private key to a file echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa chmod 600 id_ecdsa - # Set the project - project=geyser - # Get the version from gradle.properties - version=$(cat gradle.properties | grep -o "version=[0-9\\.]*" | cut -d"=" -f2) # Create the build folder - ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$project/$GITHUB_RUN_NUMBER/" + ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/" # Copy over artifacts - rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ - rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/mod/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/ + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" bootstrap/mod/**/build/libs/Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/ # Run the build script - # Push the metadata - echo "{\"project\": \"$project\", \"version\": \"$version\", \"id\": $GITHUB_RUN_NUMBER, \"commit\": \"$GITHUB_SHA\"}" > metadata.json - rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/ + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$GITHUB_RUN_NUMBER/ - name: Publish to Modrinth (Fabric) uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 @@ -152,4 +177,4 @@ jobs: uses: Tim203/actions-git-discord-webhook@70f38ded3aca51635ec978ab4e1a58cd4cd0c2ff with: webhook_url: ${{ secrets.DISCORD_WEBHOOK }} - status: ${{ job.status }} + status: ${{ job.status }} \ No newline at end of file diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 000000000..794ade4d4 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,73 @@ +name: Upload Preview + +on: + workflow_dispatch: + inputs: + runId: + required: true + description: 'ID of the action to pull artifacts from' + build: + required: true + description: 'Build number for the release' + version: + required: true + description: 'Version under which to upload to the Downloads API' + +jobs: + upload: + runs-on: ubuntu-latest + env: + PROJECT: 'geyser-preview' + BUILD: ${{ github.event.inputs.build }} + VERSION: ${{ github.event.inputs.version }} + steps: + - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # https://github.com/actions/download-artifact/releases/tag/v4.1.4 + with: + run-id: ${{ github.event.inputs.runId }} + github-token: ${{ secrets.GITHUB_TOKEN }} + merge-multiple: true + - name: Get Preview Metadata + if: success() + uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # https://github.com/Kas-tle/base-release-action/releases/tag/main-11 + with: + appID: ${{ secrets.RELEASE_APP_ID }} + appPrivateKey: ${{ secrets.RELEASE_APP_PK }} + files: | + bungeecord:Geyser-BungeeCord.jar + fabric:Geyser-Fabric.jar + neoforge:Geyser-NeoForge.jar + spigot:Geyser-Spigot.jar + standalone:Geyser-Standalone.jar + velocity:Geyser-Velocity.jar + viaproxy:Geyser-ViaProxy.jar + releaseEnabled: false + saveMetadata: true + updateReleaseData: false + - name: Update Generated Metadata + if: success() + run: | + cat metadata.json + echo + cat metadata.json | jq --arg project "${PROJECT}" --arg version "${VERSION}" --arg number "${BUILD}" ' + . + | .downloads |= map_values({"name", "sha256"}) + | {$project, "repo", $version, "number": $number | tonumber, "changes": [], "downloads"} + ' | tee metadata.json + - name: Publish to Downloads API + if: success() + shell: bash + env: + DOWNLOADS_USERNAME: ${{ vars.DOWNLOADS_USERNAME }} + DOWNLOADS_PRIVATE_KEY: ${{ secrets.DOWNLOADS_PRIVATE_KEY }} + DOWNLOADS_SERVER_IP: ${{ secrets.DOWNLOADS_SERVER_IP }} + run: | + # Save the private key to a file + echo "$DOWNLOADS_PRIVATE_KEY" > id_ecdsa + chmod 600 id_ecdsa + # Create the build folder + ssh -o StrictHostKeyChecking=no -i id_ecdsa $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP mkdir -p "~/uploads/$PROJECT/$BUILD/" + # Copy over artifacts + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/ + # Run the build script + # Push the metadata + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/ \ No newline at end of file diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 93be4711e..18223e7dc 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -102,3 +102,21 @@ jobs: name: Geyser ViaProxy path: geyser/bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar if-no-files-found: error + + - name: Trigger Preview Deployment + if: >- + contains(github.event.pull_request.labels.*.name, 'PR: Needs Testing') + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'preview.yml', + ref: 'master', + inputs: { + runId: '${{ github.run_id }}', + build: '${{ github.run_number }}', + version: 'pr.${{ github.event.pull_request.number }}' + } + }); \ No newline at end of file From ae96ad2ec4afd81a7c1478ea8f52c10af0350ab0 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 18 Apr 2024 17:18:05 +0100 Subject: [PATCH 07/12] Change preview builds project --- .github/workflows/preview.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 794ade4d4..a2955a081 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -17,7 +17,7 @@ jobs: upload: runs-on: ubuntu-latest env: - PROJECT: 'geyser-preview' + PROJECT: 'geyserpreview' BUILD: ${{ github.event.inputs.build }} VERSION: ${{ github.event.inputs.version }} steps: @@ -70,4 +70,4 @@ jobs: rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" Geyser-*.jar $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/ # Run the build script # Push the metadata - rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/ \ No newline at end of file + rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$PROJECT/$BUILD/ From 0cc2921eda30e2fe44951d1886bdb7ec6c3acbdf Mon Sep 17 00:00:00 2001 From: ookiegajwa <91029880+ookiegajwa@users.noreply.github.com> Date: Thu, 18 Apr 2024 20:23:33 -0500 Subject: [PATCH 08/12] Translate scoreboard display name and number format (#4567) The following caveats apply because selectively removing or modifying the score numbers on Bedrock cannot be done without removing all of them: * "blank" and "styled" number formats are ignored for "list" and "sidebar" display slots * The "fixed" number format is appended to the end of the "list" and "sidebar" entry names instead of replacing the score number * The "below_name" slot has no limitations and displays identically to Java --- .../entity/type/player/PlayerEntity.java | 54 +++++++++++++--- .../geysermc/geyser/scoreboard/Objective.java | 34 +++++++++- .../org/geysermc/geyser/scoreboard/Score.java | 62 ++++++++++++++++++- .../geyser/scoreboard/Scoreboard.java | 4 +- .../java/scoreboard/JavaResetScorePacket.java | 4 +- .../JavaSetObjectiveTranslator.java | 16 ++++- .../scoreboard/JavaSetScoreTranslator.java | 18 ++---- 7 files changed, 157 insertions(+), 35 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 20819f75e..b957a0243 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -25,6 +25,11 @@ package org.geysermc.geyser.entity.type.player; +import com.github.steveice10.mc.protocol.codec.NbtComponentSerializer; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.BlankFormat; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.FixedFormat; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.StyledFormat; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; @@ -33,6 +38,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEnt import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; @@ -64,6 +70,7 @@ import org.geysermc.geyser.scoreboard.Score; import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.scoreboard.UpdateType; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.text.ChatColor; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ChunkUtils; @@ -415,14 +422,36 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { public void setBelowNameText(Objective objective) { if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) { - int amount; Score score = objective.getScores().get(username); + String numberString; + NumberFormat numberFormat; + int amount; if (score != null) { - amount = score.getCurrentData().getScore(); + amount = score.getScore(); + numberFormat = score.getNumberFormat(); + if (numberFormat == null) { + numberFormat = objective.getNumberFormat(); + } } else { amount = 0; + numberFormat = objective.getNumberFormat(); } - String displayString = amount + " " + objective.getDisplayName(); + + if (numberFormat instanceof BlankFormat) { + numberString = ""; + } else if (numberFormat instanceof FixedFormat fixedFormat) { + numberString = MessageTranslator.convertMessage(fixedFormat.getValue()); + } else if (numberFormat instanceof StyledFormat styledFormat) { + CompoundTag styledAmount = styledFormat.getStyle().clone(); + styledAmount.put(new StringTag("text", String.valueOf(amount))); + + numberString = MessageTranslator.convertJsonMessage( + NbtComponentSerializer.tagComponentToJson(styledAmount).toString()); + } else { + numberString = String.valueOf(amount); + } + + String displayString = numberString + " " + ChatColor.RESET + objective.getDisplayName(); if (valid) { // Already spawned - we still need to run the rest of this code because the spawn packet will be @@ -431,13 +460,22 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity { packet.setRuntimeEntityId(geyserId); packet.getMetadata().put(EntityDataTypes.SCORE, displayString); session.sendUpstreamPacket(packet); + } else { + // Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity + dirtyMetadata.put(EntityDataTypes.SCORE, displayString); + } + } else { + if (valid) { + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityDataTypes.SCORE, ""); + session.sendUpstreamPacket(packet); + } else { + // Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity + dirtyMetadata.put(EntityDataTypes.SCORE, ""); } - } else if (valid) { - SetEntityDataPacket packet = new SetEntityDataPacket(); - packet.setRuntimeEntityId(geyserId); - packet.getMetadata().put(EntityDataTypes.SCORE, ""); - session.sendUpstreamPacket(packet); } + } /** diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java index fd9e0fbf3..c00c69660 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Objective.java @@ -25,13 +25,16 @@ package org.geysermc.geyser.scoreboard; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import lombok.Getter; import lombok.Setter; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @Getter @@ -47,6 +50,7 @@ public final class Objective { private ScoreboardPosition displaySlot; private String displaySlotName; private String displayName = "unknown"; + private NumberFormat numberFormat; private int type = 0; // 0 = integer, 1 = heart private Map scores = new ConcurrentHashMap<>(); @@ -85,25 +89,29 @@ public final class Objective { }; } - public void registerScore(String id, int score) { + public void registerScore(String id, int score, Component displayName, NumberFormat numberFormat) { if (!scores.containsKey(id)) { long scoreId = scoreboard.getNextId().getAndIncrement(); Score scoreObject = new Score(scoreId, id) .setScore(score) .setTeam(scoreboard.getTeamFor(id)) + .setDisplayName(displayName) + .setNumberFormat(numberFormat) .setUpdateType(UpdateType.ADD); scores.put(id, scoreObject); } } - public void setScore(String id, int score) { + public void setScore(String id, int score, Component displayName, NumberFormat numberFormat) { Score stored = scores.get(id); if (stored != null) { stored.setScore(score) + .setDisplayName(displayName) + .setNumberFormat(numberFormat) .setUpdateType(UpdateType.UPDATE); return; } - registerScore(id, score); + registerScore(id, score, displayName, numberFormat); } public void removeScore(String id) { @@ -128,6 +136,26 @@ public final class Objective { return this; } + public Objective setNumberFormat(NumberFormat numberFormat) { + if (Objects.equals(this.numberFormat, numberFormat)) { + return this; + } + + this.numberFormat = numberFormat; + if (updateType == UpdateType.NOTHING) { + updateType = UpdateType.UPDATE; + } + + // Update the number format for scores that are following this objective's number format + for (Score score : scores.values()) { + if (score.getNumberFormat() == null) { + score.setUpdateType(UpdateType.UPDATE); + } + } + + return this; + } + public Objective setType(int type) { this.type = type; if (updateType == UpdateType.NOTHING) { diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java index 0a6623e97..ec17818c4 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Score.java @@ -25,9 +25,16 @@ package org.geysermc.geyser.scoreboard; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.FixedFormat; +import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat; +import net.kyori.adventure.text.Component; import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; import lombok.Getter; import lombok.experimental.Accessors; +import org.geysermc.geyser.text.ChatColor; +import org.geysermc.geyser.translator.text.MessageTranslator; + +import java.util.Objects; @Getter @Accessors(chain = true) @@ -52,6 +59,10 @@ public final class Score { } public String getDisplayName() { + String displayName = cachedData.displayName; + if (displayName != null) { + return displayName; + } Team team = cachedData.team; if (team != null) { return team.getDisplayName(name); @@ -88,6 +99,35 @@ public final class Score { return this; } + public Score setDisplayName(Component displayName) { + if (currentData.displayName != null && displayName != null) { + String convertedDisplayName = MessageTranslator.convertMessage(displayName); + if (!currentData.displayName.equals(convertedDisplayName)) { + currentData.displayName = convertedDisplayName; + setUpdateType(UpdateType.UPDATE); + } + return this; + } + // simplified from (this.displayName != null && displayName == null) || (this.displayName == null && displayName != null) + if (currentData.displayName != null || displayName != null) { + currentData.displayName = MessageTranslator.convertMessage(displayName); + setUpdateType(UpdateType.UPDATE); + } + return this; + } + + public NumberFormat getNumberFormat() { + return currentData.numberFormat; + } + + public Score setNumberFormat(NumberFormat numberFormat) { + if (!Objects.equals(currentData.numberFormat, numberFormat)) { + currentData.numberFormat = numberFormat; + setUpdateType(UpdateType.UPDATE); + } + return this; + } + public UpdateType getUpdateType() { return currentData.updateType; } @@ -105,7 +145,7 @@ public final class Score { (currentData.team != null && currentData.team.shouldUpdate()); } - public void update(String objectiveName) { + public void update(Objective objective) { if (cachedData == null) { cachedData = new ScoreData(); cachedData.updateType = UpdateType.ADD; @@ -119,13 +159,26 @@ public final class Score { currentData.changed = false; cachedData.team = currentData.team; cachedData.score = currentData.score; + cachedData.displayName = currentData.displayName; + cachedData.numberFormat = currentData.numberFormat; String name = this.name; - if (cachedData.team != null) { + if (cachedData.displayName != null) { + name = cachedData.displayName; + } else if (cachedData.team != null) { cachedData.team.prepareUpdate(); name = cachedData.team.getDisplayName(name); } - cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name); + + NumberFormat numberFormat = cachedData.numberFormat; + if (numberFormat == null) { + numberFormat = objective.getNumberFormat(); + } + if (numberFormat instanceof FixedFormat fixedFormat) { + name += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue()); + } + + cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), cachedData.score, name); } @Getter @@ -136,6 +189,9 @@ public final class Score { private Team team; private int score; + private String displayName; + private NumberFormat numberFormat; + private ScoreData() { updateType = UpdateType.ADD; } diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index 216370bde..dfd74a79e 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -240,7 +240,7 @@ public final class Scoreboard { boolean update = score.shouldUpdate(); if (update) { - score.update(objective.getObjectiveName()); + score.update(objective); } if (score.getUpdateType() != REMOVE && update) { @@ -281,7 +281,7 @@ public final class Scoreboard { } if (score.shouldUpdate()) { - score.update(objective.getObjectiveName()); + score.update(objective); add = true; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaResetScorePacket.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaResetScorePacket.java index 418037760..01b4fddea 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaResetScorePacket.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaResetScorePacket.java @@ -54,7 +54,7 @@ public class JavaResetScorePacket extends PacketTranslator objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName())) - .setType(packet.getType().ordinal()); + case ADD, UPDATE -> { + objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName())) + .setNumberFormat(packet.getNumberFormat()) + .setType(packet.getType().ordinal()); + if (objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) { + // Update the score tag of all players + for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { + if (entity.isValid()) { + entity.setBelowNameText(objective); + } + } + } + } case REMOVE -> { scoreboard.unregisterObjective(packet.getName()); if (objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java index 594e2cbed..6bffee3d3 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java @@ -28,8 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; import org.checkerframework.checker.nullness.qual.Nullable; -import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; -import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.entity.type.player.PlayerEntity; @@ -54,7 +52,6 @@ public class JavaSetScoreTranslator extends PacketTranslator Date: Fri, 19 Apr 2024 05:41:06 -0400 Subject: [PATCH 09/12] Fix default wolf and cat collar color (#4582) --- .../living/animal/tameable/CatEntity.java | 11 +++++----- .../living/animal/tameable/WolfEntity.java | 22 ++++++++++++++----- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java index 412157b5d..93cb92c7d 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/CatEntity.java @@ -45,7 +45,7 @@ import java.util.UUID; public class CatEntity extends TameableEntity { - private byte collarColor; + private byte collarColor = 14; // Red - default public CatEntity(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); @@ -76,10 +76,7 @@ public class CatEntity extends TameableEntity { @Override public void setTameableFlags(ByteEntityMetadata entityMetadata) { super.setTameableFlags(entityMetadata); - // Update collar color if tamed - if (getFlag(EntityFlag.TAMED)) { - dirtyMetadata.put(EntityDataTypes.COLOR, collarColor); - } + updateCollarColor(); } public void setCatVariant(IntEntityMetadata entityMetadata) { @@ -101,6 +98,10 @@ public class CatEntity extends TameableEntity { public void setCollarColor(IntEntityMetadata entityMetadata) { collarColor = (byte) entityMetadata.getPrimitiveValue(); + updateCollarColor(); + } + + private void updateCollarColor() { // Needed or else wild cats are a red color if (getFlag(EntityFlag.TAMED)) { dirtyMetadata.put(EntityDataTypes.COLOR, collarColor); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java index 0f5b36ec3..c75247fdf 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -32,6 +32,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; +import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; @@ -41,6 +42,7 @@ import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InteractiveTag; +import java.util.Collections; import java.util.Set; import java.util.UUID; @@ -54,7 +56,7 @@ public class WolfEntity extends TameableEntity { Items.PORKCHOP, Items.BEEF, Items.RABBIT, Items.COOKED_PORKCHOP, Items.COOKED_BEEF, Items.ROTTEN_FLESH, Items.MUTTON, Items.COOKED_MUTTON, Items.COOKED_RABBIT); - private byte collarColor; + private byte collarColor = 14; // Red - default public WolfEntity(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); @@ -64,19 +66,27 @@ public class WolfEntity extends TameableEntity { public void setTameableFlags(ByteEntityMetadata entityMetadata) { super.setTameableFlags(entityMetadata); // Reset wolf color - byte xd = entityMetadata.getPrimitiveValue(); - boolean angry = (xd & 0x02) == 0x02; - if (angry) { + if (getFlag(EntityFlag.ANGRY)) { dirtyMetadata.put(EntityDataTypes.COLOR, (byte) 0); + } else if (getFlag(EntityFlag.TAMED)) { + updateCollarColor(); + + // This fixes tail angle when taming + UpdateAttributesPacket packet = new UpdateAttributesPacket(); + packet.setRuntimeEntityId(geyserId); + packet.setAttributes(Collections.singletonList(createHealthAttribute())); + session.sendUpstreamPacket(packet); } } public void setCollarColor(IntEntityMetadata entityMetadata) { collarColor = (byte) entityMetadata.getPrimitiveValue(); - if (getFlag(EntityFlag.ANGRY)) { - return; + if (!getFlag(EntityFlag.ANGRY) && getFlag(EntityFlag.TAMED)) { + updateCollarColor(); } + } + private void updateCollarColor() { dirtyMetadata.put(EntityDataTypes.COLOR, collarColor); if (ownerBedrockId == 0) { // If a color is set and there is no owner entity ID, set one. From 94f664ad8d065df32d0ce23cabd1e698ea7faf1e Mon Sep 17 00:00:00 2001 From: chris Date: Fri, 19 Apr 2024 11:50:40 +0200 Subject: [PATCH 10/12] Fix: Properly check whether the `so_reuseport` socket option is available (#4579) * Try to properly check if so_reuseport is available * io_uring "support" * comment out io_uring, for now * Make IO_uring opt-in via `-DGeyser.io_uring=true` flag * dont include io_uring * oops - bungee editing on mobile is hard * oops - spigot * oops - velocity * properly exclude all io_uring on all platforms except standalone --------- Co-authored-by: Kas-tle <26531652+Kas-tle@users.noreply.github.com> --- bootstrap/bungeecord/build.gradle.kts | 1 + bootstrap/spigot/build.gradle.kts | 1 + bootstrap/velocity/build.gradle.kts | 4 +- bootstrap/viaproxy/build.gradle.kts | 1 + .../geyser.modded-conventions.gradle.kts | 2 + core/build.gradle.kts | 7 +--- .../geyser/network/netty/Bootstraps.java | 17 +++++++- .../geyser/network/netty/GeyserServer.java | 40 ++++++++++++++----- gradle/libs.versions.toml | 2 + 9 files changed, 56 insertions(+), 19 deletions(-) diff --git a/bootstrap/bungeecord/build.gradle.kts b/bootstrap/bungeecord/build.gradle.kts index 1b57ffa5a..e93c096a1 100644 --- a/bootstrap/bungeecord/build.gradle.kts +++ b/bootstrap/bungeecord/build.gradle.kts @@ -22,6 +22,7 @@ tasks.withType { dependencies { exclude(dependency("com.google.*:.*")) + exclude(dependency("io.netty.incubator:.*")) exclude(dependency("io.netty:netty-transport-native-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) exclude(dependency("io.netty:netty-handler:.*")) diff --git a/bootstrap/spigot/build.gradle.kts b/bootstrap/spigot/build.gradle.kts index 41da1a0de..1d135c33d 100644 --- a/bootstrap/spigot/build.gradle.kts +++ b/bootstrap/spigot/build.gradle.kts @@ -41,6 +41,7 @@ tasks.withType { // We cannot shade Netty, or else native libraries will not load // Needed because older Spigot builds do not provide the haproxy module + exclude(dependency("io.netty.incubator:.*")) exclude(dependency("io.netty:netty-transport-classes-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-epoll:.*")) exclude(dependency("io.netty:netty-transport-native-unix-common:.*")) diff --git a/bootstrap/velocity/build.gradle.kts b/bootstrap/velocity/build.gradle.kts index 97e9d1f57..da826803c 100644 --- a/bootstrap/velocity/build.gradle.kts +++ b/bootstrap/velocity/build.gradle.kts @@ -12,7 +12,8 @@ platformRelocate("org.yaml") exclude("com.google.*:*") -// Needed because Velocity provides every dependency except netty-resolver-dns +// Needed because Velocity provides every dependency except netty-resolver-dns +exclude("io.netty.incubator:.*") exclude("io.netty:netty-transport-native-epoll:*") exclude("io.netty:netty-transport-native-unix-common:*") exclude("io.netty:netty-transport-native-kqueue:*") @@ -57,6 +58,7 @@ tasks.withType { exclude(dependency("io.netty:netty-transport:.*")) exclude(dependency("io.netty:netty-codec:.*")) exclude(dependency("io.netty:netty-codec-haproxy:.*")) + exclude(dependency("io.netty.incubator:.*")) exclude(dependency("org.slf4j:.*")) exclude(dependency("org.ow2.asm:.*")) // Exclude all Kyori dependencies except the legacy NBT serializer diff --git a/bootstrap/viaproxy/build.gradle.kts b/bootstrap/viaproxy/build.gradle.kts index 01c5b5b34..6eadc790f 100644 --- a/bootstrap/viaproxy/build.gradle.kts +++ b/bootstrap/viaproxy/build.gradle.kts @@ -22,6 +22,7 @@ tasks.withType { dependencies { exclude(dependency("com.google.*:.*")) exclude(dependency("io.netty:.*")) + exclude(dependency("io.netty.incubator:.*")) exclude(dependency("org.slf4j:.*")) exclude(dependency("org.ow2.asm:.*")) } diff --git a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts index e011b7139..3d41dbbb4 100644 --- a/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/geyser.modded-conventions.gradle.kts @@ -26,6 +26,8 @@ provided("io.netty", "netty-transport-native-epoll") provided("io.netty", "netty-transport-native-unix-common") provided("io.netty", "netty-transport-classes-kqueue") provided("io.netty", "netty-transport-native-kqueue") +provided("io.netty.incubator", "netty-incubator-transport-native-io_uring") +provided("io.netty.incubator", "netty-incubator-transport-classes-io_uring") provided("io.netty", "netty-handler") provided("io.netty", "netty-common") provided("io.netty", "netty-buffer") diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 93c9f4f13..b1244d55d 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -47,6 +47,8 @@ dependencies { implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-x86_64" } } implementation(libs.netty.transport.native.epoll) { artifact { classifier = "linux-aarch_64" } } implementation(libs.netty.transport.native.kqueue) { artifact { classifier = "osx-x86_64" } } + implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-x86_64" } } + implementation(libs.netty.transport.native.io.uring) { artifact { classifier = "linux-aarch_64" } } // Adventure text serialization api(libs.bundles.adventure) @@ -66,11 +68,6 @@ dependencies { api(libs.events) } -configurations.api { - // This is still experimental - additionally, it could only really benefit standalone - exclude(group = "io.netty.incubator", module = "netty-incubator-transport-native-io_uring") -} - tasks.processResources { // This is solely for backwards compatibility for other programs that used this file before the switch to gradle. // It used to be generated by the maven Git-Commit-Id-Plugin diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/Bootstraps.java b/core/src/main/java/org/geysermc/geyser/network/netty/Bootstraps.java index 9f889a6e7..fbc333106 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/Bootstraps.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/Bootstraps.java @@ -49,6 +49,7 @@ public final class Bootstraps { String kernelVersion; try { kernelVersion = Native.KERNEL_VERSION; + GeyserImpl.getInstance().getLogger().debug("Kernel version: " + kernelVersion); } catch (Throwable e) { GeyserImpl.getInstance().getLogger().debug("Could not determine kernel version! " + e.getMessage()); kernelVersion = null; @@ -67,10 +68,22 @@ public final class Bootstraps { } @SuppressWarnings({"rawtypes, unchecked"}) - public static void setupBootstrap(AbstractBootstrap bootstrap) { + public static boolean setupBootstrap(AbstractBootstrap bootstrap) { + boolean success = true; if (REUSEPORT_AVAILABLE) { - bootstrap.option(UnixChannelOption.SO_REUSEPORT, true); + // Guessing whether so_reuseport is available based on kernel version is cool, but unreliable. + Channel channel = bootstrap.register().channel(); + if (channel.config().setOption(UnixChannelOption.SO_REUSEPORT, true)) { + bootstrap.option(UnixChannelOption.SO_REUSEPORT, true); + } else { + // If this occurs, we guessed wrong and reuseport is not available + GeyserImpl.getInstance().getLogger().debug("so_reuseport is not available despite version being " + Native.KERNEL_VERSION); + success = false; + } + // Now yeet that channel + channel.close(); } + return success; } private static int[] fromString(String input) { diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index 8ead16623..652901f36 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -25,7 +25,6 @@ package org.geysermc.geyser.network.netty; -import com.github.steveice10.packetlib.helper.TransportHelper; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -39,6 +38,9 @@ import io.netty.channel.kqueue.KQueueEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.incubator.channel.uring.IOUring; +import io.netty.incubator.channel.uring.IOUringDatagramChannel; +import io.netty.incubator.channel.uring.IOUringEventLoopGroup; import io.netty.util.concurrent.Future; import lombok.Getter; import net.jodah.expiringmap.ExpirationPolicy; @@ -106,7 +108,7 @@ public final class GeyserServer { @Getter private final ExpiringMap proxiedAddresses; - private final int listenCount; + private int listenCount; private ChannelFuture[] bootstrapFutures; @@ -127,8 +129,11 @@ public final class GeyserServer { this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount); this.bootstrap = this.createBootstrap(); - // setup SO_REUSEPORT if exists - Bootstraps.setupBootstrap(this.bootstrap); + // setup SO_REUSEPORT if exists - or, if the option does not actually exist, reset listen count + // otherwise, we try to bind multiple times which wont work if so_reuseport is not valid + if (!Bootstraps.setupBootstrap(this.bootstrap)) { + this.listenCount = 1; + } if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) { this.proxiedAddresses = ExpiringMap.builder() @@ -415,22 +420,35 @@ public final class GeyserServer { } private static Transport compatibleTransport() { - TransportHelper.TransportMethod transportMethod = TransportHelper.determineTransportMethod(); - if (transportMethod == TransportHelper.TransportMethod.EPOLL) { + if (isClassAvailable("io.netty.incubator.channel.uring.IOUring") + && IOUring.isAvailable() + && Boolean.parseBoolean(System.getProperty("Geyser.io_uring"))) { + return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new); + } + + if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) { return new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new); } - if (transportMethod == TransportHelper.TransportMethod.KQUEUE) { + if (isClassAvailable("io.netty.channel.kqueue.KQueue") && KQueue.isAvailable()) { return new Transport(KQueueDatagramChannel.class, KQueueEventLoopGroup::new); } - // if (transportMethod == TransportHelper.TransportMethod.IO_URING) { - // return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new); - // } - return new Transport(NioDatagramChannel.class, NioEventLoopGroup::new); } private record Transport(Class datagramChannel, IntFunction eventLoopGroupFactory) { } + + /** + * Used so implementations can opt to remove these dependencies if so desired + */ + private static boolean isClassAvailable(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3111750a8..7feec84e0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,7 @@ events = "1.1-SNAPSHOT" jackson = "2.17.0" fastutil = "8.5.2" netty = "4.1.107.Final" +netty-io-uring = "0.0.25.Final-SNAPSHOT" guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 websocket = "1.5.1" @@ -75,6 +76,7 @@ netty-codec-haproxy = { group = "io.netty", name = "netty-codec-haproxy", versio netty-handler = { group = "io.netty", name = "netty-handler", version.ref = "netty" } netty-transport-native-epoll = { group = "io.netty", name = "netty-transport-native-epoll", version.ref = "netty" } netty-transport-native-kqueue = { group = "io.netty", name = "netty-transport-native-kqueue", version.ref = "netty" } +netty-transport-native-io_uring = { group = "io.netty.incubator", name = "netty-incubator-transport-native-io_uring", version.ref = "netty-io-uring" } log4j-api = { group = "org.apache.logging.log4j", name = "log4j-api", version.ref = "log4j" } log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4j" } From 525a9ccec4f3dfdcbec328c9b382a782540cb7d9 Mon Sep 17 00:00:00 2001 From: Kas-tle <26531652+Kas-tle@users.noreply.github.com> Date: Fri, 19 Apr 2024 18:47:00 -0700 Subject: [PATCH 11/12] Fix Preview Workflow (#4583) * Fix preview workflow * Correct path to artifacts * Uncomment repo checks * Don't enforce repo/branch on preview deploy * Correct paths-ignore for build --- .../{pullrequest.yml => build-remote.yml} | 68 +++++++------------ .github/workflows/build.yml | 22 ++++-- .github/workflows/preview.yml | 33 +++++++-- .github/workflows/pull-request.yml | 24 +++++++ core/build.gradle.kts | 5 +- 5 files changed, 92 insertions(+), 60 deletions(-) rename .github/workflows/{pullrequest.yml => build-remote.yml} (65%) create mode 100644 .github/workflows/pull-request.yml diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/build-remote.yml similarity index 65% rename from .github/workflows/pullrequest.yml rename to .github/workflows/build-remote.yml index 18223e7dc..75bcfaff5 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/build-remote.yml @@ -1,13 +1,27 @@ -name: Build Pull Request +name: Build Remote -on: - pull_request: - merge_group: +on: + workflow_call: + inputs: + repository: + required: true + description: 'The repo of the remote' + type: string + ref: + required: true + description: 'The ref of the remote' + type: string + +permissions: {} jobs: build: runs-on: ubuntu-latest steps: + - name: Set Build Number + run: | + echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV + - name: Set up JDK 17 # See https://github.com/actions/setup-java/commits uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0 @@ -15,29 +29,12 @@ jobs: java-version: 17 distribution: temurin - - name: Check if the author has forked the API repo - # See https://github.com/Kas-tle/find-forks-action/commits - uses: Kas-tle/find-forks-action@1b5447d1e3c7a8ed79583dd817cc5399686eed3a - id: find_forks - with: - owner: GeyserMC - repo: api - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Use author's API repo if it exists - if: ${{ steps.find_forks.outputs.target_branch_found == 'true' }} - env: - API_FORK_URL: ${{ steps.find_forks.outputs.user_fork_url }} - API_FORK_BRANCH: ${{ github.event.pull_request.head.ref }} - run: | - git clone "${API_FORK_URL}" --single-branch --branch "${API_FORK_BRANCH}" api - cd api - ./gradlew publishToMavenLocal - - name: Checkout repository and submodules # See https://github.com/actions/checkout/commits uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: + repository: ${{ inputs.repository }} + ref: ${{ inputs.ref }} submodules: recursive path: geyser @@ -46,11 +43,12 @@ jobs: uses: gradle/wrapper-validation-action@699bb18358f12c5b78b37bb0111d3a0e2276e0e2 # v2.1.1 - name: Build Geyser - # See https://github.com/gradle/gradle-build-action/commits - uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 from https://github.com/gradle/actions/commits + # See https://github.com/gradle/actions/commits + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 with: arguments: build build-root-directory: geyser + cache-read-only: true - name: Archive artifacts (Geyser Fabric) # See https://github.com/actions/upload-artifact/commits @@ -101,22 +99,4 @@ jobs: with: name: Geyser ViaProxy path: geyser/bootstrap/viaproxy/build/libs/Geyser-ViaProxy.jar - if-no-files-found: error - - - name: Trigger Preview Deployment - if: >- - contains(github.event.pull_request.labels.*.name, 'PR: Needs Testing') - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea - with: - script: | - github.rest.actions.createWorkflowDispatch({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: 'preview.yml', - ref: 'master', - inputs: { - runId: '${{ github.run_id }}', - build: '${{ github.run_number }}', - version: 'pr.${{ github.event.pull_request.number }}' - } - }); \ No newline at end of file + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7b439182..9582df3e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,9 @@ on: - 'gh-readonly-queue/**' paths-ignore: - '.github/ISSUE_TEMPLATE/*.yml' - - '.github/actions/pullrequest.yml' - - '.github/actions/preview.yml' + - '.github/actions/workflows/build-remote.yml' + - '.github/actions/workflows/preview.yml' + - '.github/actions/workflows/pull-request.yml' - '.idea/copyright/*.xml' - '.gitignore' - 'CONTRIBUTING.md' @@ -23,6 +24,13 @@ jobs: env: PROJECT: 'geyser' steps: + - name: Set Build Number + env: + BUILD_JSON: ${{ vars.RELEASEACTION_PREVRELEASE }} + run: | + BUILD_NUMBER=$(echo $BUILD_JSON | jq --arg branch "${GITHUB_REF_NAME}" 'if .[$branch] == null then 1 else .[$branch] | .t | tonumber + 1 end // 1') + echo "BUILD_NUMBER=${BUILD_NUMBER}" >> $GITHUB_ENV + - name: Checkout repository and submodules # See https://github.com/actions/checkout/commits uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -40,8 +48,8 @@ jobs: distribution: temurin - name: Build - # See https://github.com/gradle/gradle-build-action/commits - uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 from https://github.com/gradle/actions/commits + # See https://github.com/gradle/actions/commits + uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 with: arguments: build gradle-home-cache-cleanup: true @@ -108,7 +116,8 @@ jobs: - name: Get Release Metadata if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} - uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # https://github.com/Kas-tle/base-release-action/releases/tag/main-11 + # See https://github.com/Kas-tle/base-release-action/releases/tag/main-11 + uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # main-11 with: appID: ${{ secrets.RELEASE_APP_ID }} appPrivateKey: ${{ secrets.RELEASE_APP_PK }} @@ -134,6 +143,7 @@ jobs: | .downloads |= map_values({"name", "sha256"}) | {$project, "repo", $version, "number": .build, "changes", "downloads"} ' | tee metadata.json + echo - name: Publish to Downloads API if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }} shell: bash @@ -177,4 +187,4 @@ jobs: uses: Tim203/actions-git-discord-webhook@70f38ded3aca51635ec978ab4e1a58cd4cd0c2ff with: webhook_url: ${{ secrets.DISCORD_WEBHOOK }} - status: ${{ job.status }} \ No newline at end of file + status: ${{ job.status }} diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index a2955a081..ccac3ba85 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -12,23 +12,44 @@ on: version: required: true description: 'Version under which to upload to the Downloads API' + workflow_call: + inputs: + build: + required: true + description: 'Build number for the release' + type: string + version: + required: true + description: 'Version under which to upload to the Downloads API' + type: string jobs: upload: runs-on: ubuntu-latest env: - PROJECT: 'geyserpreview' - BUILD: ${{ github.event.inputs.build }} - VERSION: ${{ github.event.inputs.version }} + PROJECT: 'geyser-preview' steps: - - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # https://github.com/actions/download-artifact/releases/tag/v4.1.4 + - name: Set Variables + id: setvars + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "BUILD=${{ github.event.inputs.build }}" >> $GITHUB_ENV + echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV + echo "RUN=${{ github.event.inputs.runId }}" >> $GITHUB_OUTPUT + else + echo "BUILD=${{ inputs.build }}" >> $GITHUB_ENV + echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV + echo "RUN=${{ github.run_id }}" >> $GITHUB_OUTPUT + fi + - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 with: - run-id: ${{ github.event.inputs.runId }} + run-id: ${{ steps.setvars.outputs.RUN }} github-token: ${{ secrets.GITHUB_TOKEN }} merge-multiple: true - name: Get Preview Metadata if: success() - uses: Kas-tle/base-release-action@b863fa0f89bd15267a96a72efb84aec25f168d4c # https://github.com/Kas-tle/base-release-action/releases/tag/main-11 + # See https://github.com/Kas-tle/base-release-action/releases/tag/main-11 + uses: Kas-tle/base-release-action@664c39985eb9d0d393ce98e7eb8414d3d98e762a # main-11 with: appID: ${{ secrets.RELEASE_APP_ID }} appPrivateKey: ${{ secrets.RELEASE_APP_PK }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 000000000..bc5e57b6b --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,24 @@ +name: Process Pull Request + +on: + pull_request_target: + +jobs: + build: + # Forbid access to secrets nor GH Token perms while building the PR + permissions: {} + secrets: {} + uses: ./.github/workflows/build-remote.yml + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.sha }} + preview: + needs: [build] + if: >- + contains(github.event.pull_request.labels.*.name, 'PR: Needs Testing') + # Allow access to secrets if we are uploading a preview + secrets: inherit + uses: ./.github/workflows/preview.yml + with: + build: ${{ github.run_number }} + version: pr.${{ github.event.pull_request.number }} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index b1244d55d..42d4e13c5 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -98,7 +98,7 @@ configure { } fun Project.buildNumber(): Int = - (System.getenv("GITHUB_RUN_NUMBER") ?: jenkinsBuildNumber())?.let { Integer.parseInt(it) } ?: -1 + (System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1 inner class GitInfo { val branch: String @@ -131,9 +131,6 @@ inner class GitInfo { } } -// todo remove this when we're not using Jenkins anymore -fun jenkinsBuildNumber(): String? = System.getenv("BUILD_NUMBER") - // Manual task to download the bedrock data files from the CloudburstMC/Data repository // Invoke with ./gradlew :core:downloadBedrockData --suffix=1_20_70 // Set suffix to the current Bedrock version From 1bca6421d30966fb4f1b7703df2885da43517d6c Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 20 Apr 2024 03:12:27 +0100 Subject: [PATCH 12/12] Update preview action to use correct project --- .github/workflows/preview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index ccac3ba85..13712d5ef 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -27,7 +27,7 @@ jobs: upload: runs-on: ubuntu-latest env: - PROJECT: 'geyser-preview' + PROJECT: 'geyserpreview' steps: - name: Set Variables id: setvars