Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-19 22:40:18 +01:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/configurate
Dieser Commit ist enthalten in:
Commit
c4c88089b4
@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
|||||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.20 and Minecraft Java Server 1.21/1.21.1. For more info please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
Geyser is currently supporting Minecraft Bedrock 1.20.80 - 1.21.21 and Minecraft Java 1.21/1.21.1. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
|
||||||
|
|
||||||
## Setting Up
|
## Setting Up
|
||||||
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
|
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
|
||||||
|
@ -118,3 +118,12 @@ open class DownloadFilesTask : DefaultTask() {
|
|||||||
private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String =
|
private fun calcExclusion(section: String, bit: Int, excludedOn: Int): String =
|
||||||
if (excludedOn and bit > 0) section else ""
|
if (excludedOn and bit > 0) section else ""
|
||||||
|
|
||||||
|
fun projectVersion(project: Project): String =
|
||||||
|
project.version.toString().replace("SNAPSHOT", "b" + buildNumber())
|
||||||
|
|
||||||
|
fun versionName(project: Project): String =
|
||||||
|
"Geyser-" + project.name.replaceFirstChar { it.uppercase() } + "-" + projectVersion(project)
|
||||||
|
|
||||||
|
fun buildNumber(): Int =
|
||||||
|
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ tasks {
|
|||||||
register("remapModrinthJar", RemapJarTask::class) {
|
register("remapModrinthJar", RemapJarTask::class) {
|
||||||
dependsOn(shadowJar)
|
dependsOn(shadowJar)
|
||||||
inputFile.set(shadowJar.get().archiveFile)
|
inputFile.set(shadowJar.get().archiveFile)
|
||||||
archiveVersion.set(project.version.toString() + "+build." + System.getenv("BUILD_NUMBER"))
|
archiveVersion.set(versionName(project))
|
||||||
archiveClassifier.set("")
|
archiveClassifier.set("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@ tasks.modrinth.get().dependsOn(tasks.modrinthSyncBody)
|
|||||||
modrinth {
|
modrinth {
|
||||||
token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
|
token.set(System.getenv("MODRINTH_TOKEN") ?: "") // Even though this is the default value, apparently this prevents GitHub Actions caching the token?
|
||||||
projectId.set("geyser")
|
projectId.set("geyser")
|
||||||
versionNumber.set(project.version as String + "-" + System.getenv("BUILD_NUMBER"))
|
versionName.set(versionName(project))
|
||||||
|
versionNumber.set(projectVersion(project))
|
||||||
versionType.set("beta")
|
versionType.set("beta")
|
||||||
changelog.set(System.getenv("CHANGELOG") ?: "")
|
changelog.set(System.getenv("CHANGELOG") ?: "")
|
||||||
gameVersions.addAll("1.21", libs.minecraft.get().version as String)
|
gameVersions.addAll("1.21", libs.minecraft.get().version as String)
|
||||||
|
@ -112,9 +112,6 @@ sourceSets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildNumber(): Int =
|
|
||||||
(System.getenv("BUILD_NUMBER"))?.let { Integer.parseInt(it) } ?: -1
|
|
||||||
|
|
||||||
fun isDevBuild(branch: String, repository: String): Boolean {
|
fun isDevBuild(branch: String, repository: String): Boolean {
|
||||||
return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not()
|
return branch != "master" || repository.equals("https://github.com/GeyserMC/Geyser", ignoreCase = true).not()
|
||||||
}
|
}
|
||||||
@ -148,7 +145,7 @@ inner class GitInfo {
|
|||||||
|
|
||||||
buildNumber = buildNumber()
|
buildNumber = buildNumber()
|
||||||
isDev = isDevBuild(branch, repository)
|
isDev = isDevBuild(branch, repository)
|
||||||
val projectVersion = if (isDev) project.version else project.version.toString().replace("SNAPSHOT", "b${buildNumber}")
|
val projectVersion = if (isDev) project.version else projectVersion(project)
|
||||||
version = "$projectVersion ($gitVersion)"
|
version = "$projectVersion ($gitVersion)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,11 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
|||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
import org.cloudburstmc.protocol.bedrock.packet.AddPlayerPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
|
||||||
|
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.api.entity.type.player.GeyserPlayerEntity;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||||
@ -278,7 +282,13 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPosition(Vector3f position) {
|
public void setPosition(Vector3f position) {
|
||||||
super.setPosition(position.add(0, definition.offset(), 0));
|
if (this.bedPosition != null) {
|
||||||
|
// As of Bedrock 1.21.22 and Fabric 1.21.1
|
||||||
|
// Messes with Bedrock if we send this to the client itself, though.
|
||||||
|
super.setPosition(position.up(0.2f));
|
||||||
|
} else {
|
||||||
|
super.setPosition(position.add(0, definition.offset(), 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,7 +140,7 @@ public class SessionPlayerEntity extends PlayerEntity {
|
|||||||
if (valid) { // Don't update during session init
|
if (valid) { // Don't update during session init
|
||||||
session.getCollisionManager().updatePlayerBoundingBox(position);
|
session.getCollisionManager().updatePlayerBoundingBox(position);
|
||||||
}
|
}
|
||||||
super.setPosition(position);
|
this.position = position.add(0, definition.offset(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,6 +162,27 @@ public final class ClickPlan {
|
|||||||
finished = true;
|
finished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Inventory getInventory() {
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the item stacks with another item in the specified slot.
|
||||||
|
* This will check the simulated inventory without copying.
|
||||||
|
*/
|
||||||
|
public boolean canStack(int slot, GeyserItemStack item) {
|
||||||
|
GeyserItemStack slotItem = simulatedItems.getOrDefault(slot, inventory.getItem(slot));
|
||||||
|
return InventoryUtils.canStack(slotItem, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the specified slot is empty.
|
||||||
|
* This will check the simulated inventory without copying.
|
||||||
|
*/
|
||||||
|
public boolean isEmpty(int slot) {
|
||||||
|
return simulatedItems.getOrDefault(slot, inventory.getItem(slot)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public GeyserItemStack getItem(int slot) {
|
public GeyserItemStack getItem(int slot) {
|
||||||
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
|
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ public final class GameProtocol {
|
|||||||
* release of the game that Geyser supports.
|
* release of the game that Geyser supports.
|
||||||
*/
|
*/
|
||||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v712.CODEC.toBuilder()
|
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = CodecProcessor.processCodec(Bedrock_v712.CODEC.toBuilder()
|
||||||
.minecraftVersion("1.21.20")
|
.minecraftVersion("1.21.20/1.21.21")
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +29,6 @@ import io.netty.buffer.Unpooled;
|
|||||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||||
import org.cloudburstmc.protocol.bedrock.codec.compat.BedrockCompat;
|
import org.cloudburstmc.protocol.bedrock.codec.compat.BedrockCompat;
|
||||||
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
|
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
|
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
|
||||||
@ -120,10 +119,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||||||
session.disconnect(disconnectMessage);
|
session.disconnect(disconnectMessage);
|
||||||
return false;
|
return false;
|
||||||
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
} else if (protocolVersion < GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||||
if (protocolVersion < Bedrock_v622.CODEC.getProtocolVersion()) {
|
// A note on the following line: various older client versions have different forms of DisconnectPacket.
|
||||||
// https://github.com/GeyserMC/Geyser/issues/4378
|
// Using only the latest BedrockCompat for such clients leads to inaccurate disconnect messages: https://github.com/GeyserMC/Geyser/issues/4378
|
||||||
session.getUpstream().getSession().setCodec(BedrockCompat.CODEC_LEGACY);
|
// This updates the BedrockCompat protocol if necessary:
|
||||||
}
|
session.getUpstream().getSession().setCodec(BedrockCompat.disconnectCompat(protocolVersion));
|
||||||
|
|
||||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,10 +88,11 @@ public final class GeyserServer {
|
|||||||
/*
|
/*
|
||||||
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
|
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
|
||||||
*/
|
*/
|
||||||
private static final int MINECRAFT_VERSION_BYTES_LENGTH = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length;
|
private static final String PING_VERSION = pingVersion();
|
||||||
|
private static final int PING_VERSION_BYTES_LENGTH = PING_VERSION.getBytes(StandardCharsets.UTF_8).length;
|
||||||
private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length;
|
private static final int BRAND_BYTES_LENGTH = GeyserImpl.NAME.getBytes(StandardCharsets.UTF_8).length;
|
||||||
/**
|
/**
|
||||||
* The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length.
|
* The MOTD, sub-MOTD and Minecraft version ({@link #PING_VERSION_BYTES_LENGTH}) combined cannot reach this length.
|
||||||
*/
|
*/
|
||||||
private static final int MAGIC_RAKNET_LENGTH = 338;
|
private static final int MAGIC_RAKNET_LENGTH = 338;
|
||||||
|
|
||||||
@ -320,7 +321,7 @@ public final class GeyserServer {
|
|||||||
.gameType("Survival") // Can only be Survival or Creative as of 1.16.210.59
|
.gameType("Survival") // Can only be Survival or Creative as of 1.16.210.59
|
||||||
.nintendoLimited(false)
|
.nintendoLimited(false)
|
||||||
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
|
.protocolVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion())
|
||||||
.version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
|
.version(PING_VERSION)
|
||||||
.ipv4Port(this.broadcastPort)
|
.ipv4Port(this.broadcastPort)
|
||||||
.ipv6Port(this.broadcastPort)
|
.ipv6Port(this.broadcastPort)
|
||||||
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID));
|
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID));
|
||||||
@ -371,15 +372,15 @@ public final class GeyserServer {
|
|||||||
// We don't know why, though
|
// We don't know why, though
|
||||||
byte[] motdArray = pong.motd().getBytes(StandardCharsets.UTF_8);
|
byte[] motdArray = pong.motd().getBytes(StandardCharsets.UTF_8);
|
||||||
int subMotdLength = pong.subMotd().getBytes(StandardCharsets.UTF_8).length;
|
int subMotdLength = pong.subMotd().getBytes(StandardCharsets.UTF_8).length;
|
||||||
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
|
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - PING_VERSION_BYTES_LENGTH)) {
|
||||||
// Shorten the sub-MOTD first since that only appears locally
|
// Shorten the sub-MOTD first since that only appears locally
|
||||||
if (subMotdLength > BRAND_BYTES_LENGTH) {
|
if (subMotdLength > BRAND_BYTES_LENGTH) {
|
||||||
pong.subMotd(GeyserImpl.NAME);
|
pong.subMotd(GeyserImpl.NAME);
|
||||||
subMotdLength = BRAND_BYTES_LENGTH;
|
subMotdLength = BRAND_BYTES_LENGTH;
|
||||||
}
|
}
|
||||||
if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) {
|
if (motdArray.length > (MAGIC_RAKNET_LENGTH - PING_VERSION_BYTES_LENGTH - subMotdLength)) {
|
||||||
// If the top MOTD is still too long, we chop it down
|
// If the top MOTD is still too long, we chop it down
|
||||||
byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength];
|
byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - PING_VERSION_BYTES_LENGTH - subMotdLength];
|
||||||
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
||||||
pong.motd(new String(newMotdArray, StandardCharsets.UTF_8));
|
pong.motd(new String(newMotdArray, StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
@ -394,6 +395,17 @@ public final class GeyserServer {
|
|||||||
return pong;
|
return pong;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String pingVersion() {
|
||||||
|
// BedrockPong version is required to not be empty as of 1.16.210.59.
|
||||||
|
// Can only contain . and numbers, so use the latest version instead of sending all
|
||||||
|
var version = GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
|
||||||
|
var versionSplit = version.split("/");
|
||||||
|
if (versionSplit.length > 1) {
|
||||||
|
version = versionSplit[versionSplit.length - 1];
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
private List<CIDRMatcher> whitelistedIPsMatchers = null;
|
private List<CIDRMatcher> whitelistedIPsMatchers = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -191,6 +191,11 @@ public class ItemRegistryPopulator {
|
|||||||
List<ItemData> creativeItems = new ArrayList<>();
|
List<ItemData> creativeItems = new ArrayList<>();
|
||||||
Set<String> noBlockDefinitions = new ObjectOpenHashSet<>();
|
Set<String> noBlockDefinitions = new ObjectOpenHashSet<>();
|
||||||
|
|
||||||
|
// Fix: Usage of structure blocks/voids in recipes
|
||||||
|
// https://github.com/GeyserMC/Geyser/issues/2890
|
||||||
|
noBlockDefinitions.add("minecraft:structure_block");
|
||||||
|
noBlockDefinitions.add("minecraft:structure_void");
|
||||||
|
|
||||||
AtomicInteger creativeNetId = new AtomicInteger();
|
AtomicInteger creativeNetId = new AtomicInteger();
|
||||||
CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
|
CreativeItemRegistryPopulator.populate(palette, definitions, itemBuilder -> {
|
||||||
ItemData item = itemBuilder.netId(creativeNetId.incrementAndGet()).build();
|
ItemData item = itemBuilder.netId(creativeNetId.incrementAndGet()).build();
|
||||||
|
@ -201,6 +201,9 @@ public abstract class InventoryTranslator {
|
|||||||
public ItemStackResponse translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
public ItemStackResponse translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||||
ClickPlan plan = new ClickPlan(session, this, inventory);
|
ClickPlan plan = new ClickPlan(session, this, inventory);
|
||||||
IntSet affectedSlots = new IntOpenHashSet();
|
IntSet affectedSlots = new IntOpenHashSet();
|
||||||
|
int pendingOutput = 0;
|
||||||
|
int savedTempSlot = -1;
|
||||||
|
|
||||||
for (ItemStackRequestAction action : request.getActions()) {
|
for (ItemStackRequestAction action : request.getActions()) {
|
||||||
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
||||||
switch (action.getType()) {
|
switch (action.getType()) {
|
||||||
@ -241,6 +244,65 @@ public abstract class InventoryTranslator {
|
|||||||
return rejectRequest(request, false);
|
return rejectRequest(request, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle partial transfer of output slot
|
||||||
|
if (pendingOutput == 0 && !isSourceCursor && getSlotType(sourceSlot) == SlotType.OUTPUT
|
||||||
|
&& transferAction.getCount() < plan.getItem(sourceSlot).getAmount()) {
|
||||||
|
// Cursor as dest should always be full transfer.
|
||||||
|
if (isDestCursor) {
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plan.getCursor().isEmpty()) {
|
||||||
|
savedTempSlot = findTempSlot(plan, plan.getCursor(), true);
|
||||||
|
if (savedTempSlot == -1) {
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
plan.add(Click.LEFT, savedTempSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pickup entire stack from output
|
||||||
|
pendingOutput = plan.getItem(sourceSlot).getAmount();
|
||||||
|
plan.add(Click.LEFT, sourceSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue transferring items from output that is currently stored in the cursor
|
||||||
|
if (pendingOutput > 0) {
|
||||||
|
if (isSourceCursor || getSlotType(sourceSlot) != SlotType.OUTPUT
|
||||||
|
|| transferAction.getCount() > pendingOutput
|
||||||
|
|| destSlot == savedTempSlot
|
||||||
|
|| isDestCursor) {
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure item can be placed here
|
||||||
|
GeyserItemStack destItem = plan.getItem(destSlot);
|
||||||
|
if (!destItem.isEmpty() && !InventoryUtils.canStack(destItem, plan.getCursor())) {
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Optimize using max stack size
|
||||||
|
if (pendingOutput == transferAction.getCount()) {
|
||||||
|
plan.add(Click.LEFT, destSlot);
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < transferAction.getCount(); i++) {
|
||||||
|
plan.add(Click.RIGHT, destSlot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingOutput -= transferAction.getCount();
|
||||||
|
if (pendingOutput != plan.getCursor().getAmount()) {
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingOutput == 0 && savedTempSlot != -1) {
|
||||||
|
plan.add(Click.LEFT, savedTempSlot);
|
||||||
|
savedTempSlot = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip to next action
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSourceCursor && isDestCursor) { //???
|
if (isSourceCursor && isDestCursor) { //???
|
||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
} else if (isSourceCursor) { //releasing cursor
|
} else if (isSourceCursor) { //releasing cursor
|
||||||
@ -271,7 +333,7 @@ public abstract class InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
if (transferAction.getCount() != sourceAmount) {
|
if (transferAction.getCount() != sourceAmount) {
|
||||||
int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot);
|
int tempSlot = findTempSlot(plan, cursor, false, sourceSlot);
|
||||||
if (tempSlot == -1) {
|
if (tempSlot == -1) {
|
||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
@ -292,7 +354,7 @@ public abstract class InventoryTranslator {
|
|||||||
} else { //transfer from one slot to another
|
} else { //transfer from one slot to another
|
||||||
int tempSlot = -1;
|
int tempSlot = -1;
|
||||||
if (!plan.getCursor().isEmpty()) {
|
if (!plan.getCursor().isEmpty()) {
|
||||||
tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot);
|
tempSlot = findTempSlot(plan, cursor, getSlotType(sourceSlot) != SlotType.NORMAL, sourceSlot, destSlot);
|
||||||
if (tempSlot == -1) {
|
if (tempSlot == -1) {
|
||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
@ -440,6 +502,11 @@ public abstract class InventoryTranslator {
|
|||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pendingOutput != 0) {
|
||||||
|
return rejectRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
plan.execute(false);
|
plan.execute(false);
|
||||||
affectedSlots.addAll(plan.getAffectedSlots());
|
affectedSlots.addAll(plan.getAffectedSlots());
|
||||||
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
|
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
|
||||||
@ -536,7 +603,7 @@ public abstract class InventoryTranslator {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
||||||
int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot);
|
int tempSlot = findTempSlot(plan, cursor, true, sourceSlot, destSlot);
|
||||||
if (tempSlot == -1) {
|
if (tempSlot == -1) {
|
||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
@ -699,7 +766,7 @@ public abstract class InventoryTranslator {
|
|||||||
int javaSlot = bedrockSlotToJava(transferAction.getDestination());
|
int javaSlot = bedrockSlotToJava(transferAction.getDestination());
|
||||||
if (isCursor(transferAction.getDestination())) { //TODO
|
if (isCursor(transferAction.getDestination())) { //TODO
|
||||||
if (timesCrafted > 1) {
|
if (timesCrafted > 1) {
|
||||||
tempSlot = findTempSlot(inventory, GeyserItemStack.from(output), true);
|
tempSlot = findTempSlot(plan, GeyserItemStack.from(output), true);
|
||||||
if (tempSlot == -1) {
|
if (tempSlot == -1) {
|
||||||
return rejectRequest(request);
|
return rejectRequest(request);
|
||||||
}
|
}
|
||||||
@ -836,49 +903,68 @@ public abstract class InventoryTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to find a slot that can temporarily store the given item.
|
* Try to find a slot that is preferably empty, or does not stack with a given item.
|
||||||
* Only looks in the main inventory and hotbar (excluding offhand).
|
* Only looks in the main inventory and hotbar (excluding offhand).
|
||||||
* Only slots that are empty or contain a different type of item are valid.
|
* <p>
|
||||||
|
* Slots are searched in the reverse order that the bedrock client uses for quick moving.
|
||||||
*
|
*
|
||||||
* @return java id for the temporary slot, or -1 if no viable slot was found
|
* @param plan used to check the simulated inventory
|
||||||
|
* @param item the item to temporarily store
|
||||||
|
* @param emptyOnly if only empty slots should be considered
|
||||||
|
* @param slotBlacklist list of slots to exclude; the items contained in these slots will also be checked for stacking
|
||||||
|
* @return the temp slot, or -1 if no suitable slot was found
|
||||||
*/
|
*/
|
||||||
//TODO: compatibility for simulated inventory (ClickPlan)
|
private static int findTempSlot(ClickPlan plan, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) {
|
||||||
private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) {
|
IntSortedSet potentialSlots = new IntLinkedOpenHashSet(PLAYER_INVENTORY_SIZE);
|
||||||
int offset = inventory.getJavaId() == 0 ? 1 : 0; //offhand is not a viable temp slot
|
int hotbarOffset = plan.getInventory().getOffsetForHotbar(0);
|
||||||
HashSet<GeyserItemStack> itemBlacklist = new HashSet<>(slotBlacklist.length + 1);
|
|
||||||
itemBlacklist.add(item);
|
|
||||||
|
|
||||||
IntSet potentialSlots = new IntOpenHashSet(36);
|
// Add main inventory slots in reverse
|
||||||
for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) {
|
for (int i = hotbarOffset - 1; i >= hotbarOffset - 27; i--) {
|
||||||
potentialSlots.add(i);
|
potentialSlots.add(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add hotbar slots in reverse
|
||||||
|
for (int i = hotbarOffset + 8; i >= hotbarOffset; i--) {
|
||||||
|
potentialSlots.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i : slotBlacklist) {
|
for (int i : slotBlacklist) {
|
||||||
potentialSlots.remove(i);
|
potentialSlots.remove(i);
|
||||||
GeyserItemStack blacklistedItem = inventory.getItem(i);
|
}
|
||||||
if (!blacklistedItem.isEmpty()) {
|
|
||||||
itemBlacklist.add(blacklistedItem);
|
// Prefer empty slots
|
||||||
|
IntIterator it = potentialSlots.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
int slot = it.nextInt();
|
||||||
|
if (plan.isEmpty(slot)) {
|
||||||
|
return slot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i : potentialSlots) {
|
if (emptyOnly) {
|
||||||
GeyserItemStack testItem = inventory.getItem(i);
|
return -1;
|
||||||
if ((emptyOnly && !testItem.isEmpty())) {
|
}
|
||||||
|
|
||||||
|
// No empty slots. Look for a slot that does not stack
|
||||||
|
it = potentialSlots.iterator();
|
||||||
|
|
||||||
|
outer:
|
||||||
|
while (it.hasNext()) {
|
||||||
|
int slot = it.nextInt();
|
||||||
|
if (plan.canStack(slot, item)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean viable = true;
|
for (int blacklistedSlot : slotBlacklist) {
|
||||||
for (GeyserItemStack blacklistedItem : itemBlacklist) {
|
GeyserItemStack blacklistedItem = plan.getItem(blacklistedSlot);
|
||||||
if (InventoryUtils.canStack(testItem, blacklistedItem)) {
|
if (plan.canStack(slot, blacklistedItem)) {
|
||||||
viable = false;
|
continue outer;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!viable) {
|
|
||||||
continue;
|
return slot;
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
//could not find a viable temp slot
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
|||||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||||
slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK.apply(session.getUpstream().getProtocolVersion()));
|
slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK.apply(session.getUpstream().getProtocolVersion()));
|
||||||
} else {
|
} else {
|
||||||
slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack()));
|
slotPacket.setItem(inventory.getItem(i).getItemData(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
session.sendUpstreamPacket(slotPacket);
|
session.sendUpstreamPacket(slotPacket);
|
||||||
|
@ -58,6 +58,14 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
|
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity.getBedPosition() != null) {
|
||||||
|
// https://github.com/GeyserMC/Geyser/issues/5001
|
||||||
|
// Bedrock 1.21.22 started sending a MovePlayerPacket as soon as it got into a bed.
|
||||||
|
// This trips up Fabric.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float yaw = packet.getRotation().getY();
|
float yaw = packet.getRotation().getY();
|
||||||
float pitch = packet.getRotation().getX();
|
float pitch = packet.getRotation().getX();
|
||||||
float headYaw = packet.getRotation().getY();
|
float headYaw = packet.getRotation().getY();
|
||||||
|
@ -88,6 +88,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
if (a == null || b == null) return false;
|
if (a == null || b == null) return false;
|
||||||
if ("help".equals(a.name()) && !"help".equals(b.name())) {
|
if ("help".equals(a.name()) && !"help".equals(b.name())) {
|
||||||
// Merging this causes Bedrock to fallback to its own help command
|
// Merging this causes Bedrock to fallback to its own help command
|
||||||
|
// Tested on Paper 1.20.4 with Essentials and Bedrock 1.21
|
||||||
// https://github.com/GeyserMC/Geyser/issues/2573
|
// https://github.com/GeyserMC/Geyser/issues/2573
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -124,8 +125,9 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
if (!session.getGeyser().config().commandSuggestions()) {
|
if (!session.getGeyser().config().commandSuggestions()) {
|
||||||
session.getGeyser().getLogger().debug("Not sending translated command suggestions as they are disabled.");
|
session.getGeyser().getLogger().debug("Not sending translated command suggestions as they are disabled.");
|
||||||
|
|
||||||
// Send an empty packet so Bedrock doesn't override /help with its own, built-in help command.
|
// Send a mostly empty packet so Bedrock doesn't override /help with its own, built-in help command.
|
||||||
AvailableCommandsPacket emptyPacket = new AvailableCommandsPacket();
|
AvailableCommandsPacket emptyPacket = new AvailableCommandsPacket();
|
||||||
|
emptyPacket.getCommands().add(createFakeHelpCommand());
|
||||||
session.sendUpstreamPacket(emptyPacket);
|
session.sendUpstreamPacket(emptyPacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -182,6 +184,8 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
// The command flags, set to NOT_CHEAT so known commands can be used while achievements are enabled.
|
// The command flags, set to NOT_CHEAT so known commands can be used while achievements are enabled.
|
||||||
Set<CommandData.Flag> flags = Set.of(CommandData.Flag.NOT_CHEAT);
|
Set<CommandData.Flag> flags = Set.of(CommandData.Flag.NOT_CHEAT);
|
||||||
|
|
||||||
|
boolean helpAdded = false;
|
||||||
|
|
||||||
// Loop through all the found commands
|
// Loop through all the found commands
|
||||||
for (Map.Entry<BedrockCommandInfo, Set<String>> entry : commands.entrySet()) {
|
for (Map.Entry<BedrockCommandInfo, Set<String>> entry : commands.entrySet()) {
|
||||||
String commandName = entry.getValue().iterator().next(); // We know this has a value
|
String commandName = entry.getValue().iterator().next(); // We know this has a value
|
||||||
@ -198,6 +202,15 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
// Build the completed command and add it to the final list
|
// Build the completed command and add it to the final list
|
||||||
CommandData data = new CommandData(commandName, entry.getKey().description(), flags, CommandPermission.ANY, aliases, Collections.emptyList(), entry.getKey().paramData());
|
CommandData data = new CommandData(commandName, entry.getKey().description(), flags, CommandPermission.ANY, aliases, Collections.emptyList(), entry.getKey().paramData());
|
||||||
commandData.add(data);
|
commandData.add(data);
|
||||||
|
|
||||||
|
if (commandName.equals("help")) {
|
||||||
|
helpAdded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!helpAdded) {
|
||||||
|
// https://github.com/GeyserMC/Geyser/issues/2573 if Brigadier does not send the help command.
|
||||||
|
commandData.add(createFakeHelpCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add our commands to the AvailableCommandsPacket for the bedrock client
|
// Add our commands to the AvailableCommandsPacket for the bedrock client
|
||||||
@ -286,6 +299,11 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CommandData createFakeHelpCommand() {
|
||||||
|
CommandEnumData aliases = new CommandEnumData("helpAliases", Map.of("help", EnumSet.of(CommandEnumConstraint.ALLOW_ALIASES)), false);
|
||||||
|
return new CommandData("help", "", Set.of(CommandData.Flag.NOT_CHEAT), CommandPermission.ANY, aliases, Collections.emptyList(), new CommandOverloadData[0]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the command description and parameter data for best optimizing the Bedrock commands packet.
|
* Stores the command description and parameter data for best optimizing the Bedrock commands packet.
|
||||||
*/
|
*/
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||||
|
import org.cloudburstmc.protocol.bedrock.packet.LevelEventGenericPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.ChatColor;
|
import org.geysermc.geyser.text.ChatColor;
|
||||||
@ -34,18 +36,45 @@ import org.geysermc.geyser.text.GeyserLocale;
|
|||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
||||||
|
|
||||||
@Translator(packet = ClientboundSystemChatPacket.class)
|
@Translator(packet = ClientboundSystemChatPacket.class)
|
||||||
public class JavaSystemChatTranslator extends PacketTranslator<ClientboundSystemChatPacket> {
|
public class JavaSystemChatTranslator extends PacketTranslator<ClientboundSystemChatPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundSystemChatPacket packet) {
|
public void translate(GeyserSession session, ClientboundSystemChatPacket packet) {
|
||||||
if (packet.getContent() instanceof TranslatableComponent component && component.key().equals("chat.disabled.missingProfileKey")) {
|
if (packet.getContent() instanceof TranslatableComponent component) {
|
||||||
// We likely got this message as a response to a player trying to chat
|
if (component.key().equals("chat.disabled.missingProfileKey")) {
|
||||||
// As there SHOULD be no false flags for this, print every time it shows up in chat.
|
// We likely got this message as a response to a player trying to chat
|
||||||
if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) {
|
// As there SHOULD be no false flags for this, print every time it shows up in chat.
|
||||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale()));
|
if (Boolean.parseBoolean(System.getProperty("Geyser.PrintSecureChatInformation", "true"))) {
|
||||||
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat"));
|
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_1", session.locale()));
|
||||||
|
session.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.chat.secure_info_2", session.locale(), "https://geysermc.link/secure-chat"));
|
||||||
|
}
|
||||||
|
} else if (component.key().equals("sleep.players_sleeping")) {
|
||||||
|
if (component.arguments().size() == 2) {
|
||||||
|
// Hack FYI, but it allows Bedrock players to easily understand this information
|
||||||
|
// without it being covered up or saying the night is being slept through.
|
||||||
|
int numPlayersSleeping = ((Number) component.arguments().get(0).value()).intValue();
|
||||||
|
int totalPlayersNeeded = ((Number) component.arguments().get(1).value()).intValue();
|
||||||
|
LevelEventGenericPacket sleepInfoPacket = new LevelEventGenericPacket();
|
||||||
|
sleepInfoPacket.setType(LevelEvent.SLEEPING_PLAYERS);
|
||||||
|
sleepInfoPacket.setTag(NbtMap.builder()
|
||||||
|
.putInt("ableToSleep", totalPlayersNeeded)
|
||||||
|
.putInt("overworldPlayerCount", totalPlayersNeeded)
|
||||||
|
.putInt("sleepingPlayerCount", numPlayersSleeping)
|
||||||
|
.build());
|
||||||
|
session.sendUpstreamPacket(sleepInfoPacket);
|
||||||
|
}
|
||||||
|
} else if (component.key().equals("sleep.skipping_night")) {
|
||||||
|
LevelEventGenericPacket sleepInfoPacket = new LevelEventGenericPacket();
|
||||||
|
sleepInfoPacket.setType(LevelEvent.SLEEPING_PLAYERS);
|
||||||
|
sleepInfoPacket.setTag(NbtMap.builder()
|
||||||
|
.putInt("ableToSleep", 1)
|
||||||
|
.putInt("overworldPlayerCount", 1)
|
||||||
|
.putInt("sleepingPlayerCount", 1)
|
||||||
|
.build());
|
||||||
|
session.sendUpstreamPacket(sleepInfoPacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,9 @@ guava = "29.0-jre"
|
|||||||
gson = "2.3.1" # Provided by Spigot 1.8.8 TODO bump to 2.8.1 or similar (Spigot 1.16.5 version) after Merge
|
gson = "2.3.1" # Provided by Spigot 1.8.8 TODO bump to 2.8.1 or similar (Spigot 1.16.5 version) after Merge
|
||||||
gson-runtime = "2.10.1"
|
gson-runtime = "2.10.1"
|
||||||
websocket = "1.5.1"
|
websocket = "1.5.1"
|
||||||
protocol = "3.0.0.Beta3-20240814.133201-7"
|
protocol-connection = "3.0.0.Beta4-20240828.162251-1"
|
||||||
|
protocol-common = "3.0.0.Beta4-20240828.162251-1"
|
||||||
|
protocol-codec = "3.0.0.Beta4-20240828.162251-1"
|
||||||
raknet = "1.0.0.CR3-20240416.144209-1"
|
raknet = "1.0.0.CR3-20240416.144209-1"
|
||||||
minecraftauth = "4.1.1-20240806.235051-7"
|
minecraftauth = "4.1.1-20240806.235051-7"
|
||||||
mcprotocollib = "1.21-20240725.013034-16"
|
mcprotocollib = "1.21-20240725.013034-16"
|
||||||
@ -132,9 +134,9 @@ viaproxy = { group = "net.raphimc", name = "ViaProxy", version.ref = "viaproxy"
|
|||||||
viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }
|
viaversion = { group = "com.viaversion", name = "viaversion", version.ref = "viaversion" }
|
||||||
websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" }
|
websocket = { group = "org.java-websocket", name = "Java-WebSocket", version.ref = "websocket" }
|
||||||
|
|
||||||
protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol" }
|
protocol-common = { group = "org.cloudburstmc.protocol", name = "common", version.ref = "protocol-common" }
|
||||||
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol" }
|
protocol-codec = { group = "org.cloudburstmc.protocol", name = "bedrock-codec", version.ref = "protocol-codec" }
|
||||||
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol" }
|
protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-connection", version.ref = "protocol-connection" }
|
||||||
|
|
||||||
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }
|
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }
|
||||||
|
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren