3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-07-31 09:38:06 +02:00

Merge branch 'floodgate-2.0' of https://github.com/GeyserMC/Geyser into floodgate-2.0

Dieser Commit ist enthalten in:
Camotoy 2021-03-31 11:52:50 -04:00
Commit 25600ebc1c
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 7EEFB66FE798081F
249 geänderte Dateien mit 9873 neuen und 3586 gelöschten Zeilen

Datei anzeigen

@ -1,57 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!--- DELETING THIS TEMPLATE WILL GET YOUR ISSUE CLOSED! --->
<!--- Please follow this format COMPLETELY and make sure the bug you are reporting has not been reported yet. Reports should contain as much information or context as possible to help us find the problem. Simply creating an issue on a vague topic will not help us at all, and if you are unsure if something should belong here, please contact us on [Discord](http://discord.geysermc.org).-->
<!--- Issues pertaining to connection problems, or anything of that covered on the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues) do not belong here and only clutter this issue tracker. -->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots / Videos**
If applicable, add screenshots to help explain your problem.
**Server Version and Plugins**
If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
If you're running a multi-server instance, or using Geyser Standalone:
- Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
- Please list all plugins on all servers involved.
If this bug occurs on a server you do not control, please fill this in to the best of your knowledge.
**Geyser Dump**
If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you use the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
**Minecraft: Bedrock Edition Version**
The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...).
**Additional Context**
Add any other context about the problem here.

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normale Datei
Datei anzeigen

@ -0,0 +1,64 @@
name: Bug report
about: Create a report to help us improve
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report for Geyser! Fill out the following form to your best ability to help us fix the problem.
Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: Steps to reproduce this behaviour
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Screenshots / Videos
description: If applicable, add screenshots to help explain your problem.
- type: textarea
attributes:
label: Server Version and Plugins
description: |
If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
If you're running a multi-server instance or using Geyser Standalone:
* Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
* Please list all plugins on all servers involved.
If this bug occurs on a server you do not control, please full this in to the best of your knowledge.
- type: input
attributes:
label: Geyser Dump
description: If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you're using the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
- type: input
attributes:
label: Geyser Version
description: What version of Geyser are you running?
placeholder: "For example: 1.2.0-SNAPSHOT (git-master-2d9baf1)"
validations:
required: true
- type: input
attributes:
label: "Minecraft: Bedrock Edition Version"
description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition."
placeholder: "For example: 1.16.201"
- type: textarea
attributes:
label: Additional Context
description: Add any other context about the problem here

Datei anzeigen

@ -1,5 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: GeyserMC Discord
url: http://discord.geysermc.org/
- name: Common Issues
url: https://github.com/GeyserMC/Geyser/wiki/Common-Issues
about: Check the common issues to see if you are not alone with that issue and see how you can fix them.
- name: Frequently Asked Questions
url: https://github.com/GeyserMC/Geyser/wiki/FAQ
about: Look at the FAQ page for answers for frequently asked questions.
- name: Get help on the GeyserMC Discord server
url: https://discord.gg/geysermc
about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord.

Datei anzeigen

@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**What feature do you want?**
Add a description
**Alternatives?**
List any alternatives you might have tried

21
.github/ISSUE_TEMPLATE/feature_request.yml vendored Normale Datei
Datei anzeigen

@ -0,0 +1,21 @@
name: Feature request
about: Suggest an idea for this project
labels: "Feature Request"
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request for Geyser! Fill out the following form to your best ability to help us understand your feature request and greately improve the change of it getting added.
For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or [the Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
- type: textarea
attributes:
label: What feature do you want to see added?
description: A clear and concise description of your feature request.
validations:
required: true
- type: textarea
attributes:
label: Are there any alternatives?
description: List any alternatives you might have tried
validations:
required: true

9
Jenkinsfile vendored
Datei anzeigen

@ -22,7 +22,10 @@ pipeline {
stage ('Deploy') {
when {
branch "master"
anyOf {
branch "master"
branch "floodgate-2.0"
}
}
steps {
@ -35,8 +38,8 @@ pipeline {
rtMavenResolver(
id: "maven-resolver",
serverId: "opencollab-artifactory",
releaseRepo: "release",
snapshotRepo: "snapshot"
releaseRepo: "maven-deploy-release",
snapshotRepo: "maven-deploy-snapshot"
)
rtMavenRun(
pom: 'pom.xml',

Datei anzeigen

@ -18,7 +18,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 now joined us here!
### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4 - v1.16.5.
### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.210 and Minecraft Java v1.16.4 - v1.16.5.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.
@ -34,20 +34,10 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
- Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock
## What's Left to be Added/Fixed
- Lecterns
- Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you)
- Resource pack conversion/CustomModelData
- Some Entity Flags
- The Following Inventories
- Enchantment Table (as a proper GUI)
- Beacon
- Cartography Table
- Stonecutter
- Structure Block
- Horse Inventory
- Loom
- Smithing Table
- Grindstone
- Structure block UI
## What can't be fixed
The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now.

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-bungeecord</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>connector</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-parent</artifactId>
<packaging>pom</packaging>
@ -16,13 +16,9 @@
<id>spigot-public</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/public/</url>
</repository>
<repository>
<id>bukkit-public</id>
<url>https://repo.md-5.net/content/repositories/public/</url>
</repository>
<repository>
<id>sponge-repo</id>
<url>https://repo.spongepowered.org/maven</url>
<url>https://repo.spongepowered.org/repository/maven-public/</url>
</repository>
<repository>
<id>bungeecord-repo</id>

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-spigot</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>connector</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -30,9 +30,9 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc.adapters</groupId>
<groupId>org.geysermc.geyser.adapters</groupId>
<artifactId>spigot-all</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>

Datei anzeigen

@ -28,7 +28,6 @@ package org.geysermc.platform.spigot;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.common.PlatformType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
@ -40,9 +39,11 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor;
import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager;
import org.geysermc.platform.spigot.command.SpigotCommandSender;
import org.geysermc.platform.spigot.world.GeyserSpigot1_11CraftingListener;
import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener;
import org.geysermc.platform.spigot.world.manager.*;
import us.myles.ViaVersion.api.Pair;
@ -69,6 +70,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
private GeyserConnector connector;
/**
* The Minecraft server version, formatted as <code>1.#.#</code>
*/
private String minecraftVersion;
@Override
public void onEnable() {
// This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed
@ -118,6 +124,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserConfig.loadFloodgate(this);
// Turn "(MC: 1.16.4)" into 1.16.4.
this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0];
this.connector = GeyserConnector.start(PlatformType.SPIGOT, this);
if (geyserConfig.isLegacyPingPassthrough()) {
@ -146,8 +155,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes.");
}
boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0");
// Set if we need to use a different method for getting a player's locale
SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0"));
SpigotCommandSender.setUseLegacyLocaleMethod(isPre1_12);
if (connector.getConfig().isUseAdapters()) {
try {
@ -157,14 +167,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
if (isViaVersion && isViaVersionNeeded()) {
if (isLegacy) {
// Pre-1.13
this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager();
this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this);
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes);
}
} else {
// No ViaVersion
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes);
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, use3dBiomes);
}
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
} catch (Exception e) {
@ -180,20 +190,24 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// No NMS adapter
if (isLegacy && isViaVersion) {
// Use ViaVersion for converting pre-1.13 block states
this.geyserWorldManager = new GeyserSpigot1_12WorldManager();
this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this);
} else if (isLegacy) {
// Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air
this.geyserWorldManager = new GeyserSpigotFallbackWorldManager();
this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this);
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes);
this.geyserWorldManager = new GeyserSpigotWorldManager(this, use3dBiomes);
}
geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass());
}
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
if (isPre1_12) {
// Register events needed to send all recipes to the client
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(connector), this);
}
this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(connector));
}
@ -239,6 +253,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return new GeyserSpigotDumpInfo();
}
@Override
public String getMinecraftServerVersion() {
return this.minecraftVersion;
}
public boolean isCompatible(String version, String whichVersion) {
int[] currentVersion = parseVersion(version);
int[] otherVersion = parseVersion(whichVersion);
@ -277,10 +296,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
* @return the server version before ViaVersion finishes initializing
*/
public ProtocolVersion getServerProtocolVersion() {
String bukkitVersion = Bukkit.getServer().getVersion();
// Turn "(MC: 1.16.4)" into 1.16.4.
String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0];
return ProtocolVersion.getClosest(version);
return ProtocolVersion.getClosest(this.minecraftVersion);
}
/**

Datei anzeigen

@ -0,0 +1,205 @@
/*
* Copyright (c) 2019-2021 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.platform.spigot.world;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.item.RecipeRegistry;
import us.myles.ViaVersion.api.Pair;
import us.myles.ViaVersion.api.data.MappingData;
import us.myles.ViaVersion.api.protocol.Protocol;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.api.protocol.ProtocolVersion;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2;
import java.util.*;
/**
* Used to send all available recipes from the server to the client, as a valid recipe book packet won't be sent by the server.
* Requires ViaVersion.
*/
public class GeyserSpigot1_11CraftingListener implements Listener {
private final GeyserConnector connector;
/**
* Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 item into 1.13.
*/
private final MappingData mappingData1_12to1_13;
/**
* The list of all protocols from the client's version to 1.13.
*/
private final List<Pair<Integer, Protocol>> protocolList;
public GeyserSpigot1_11CraftingListener(GeyserConnector connector) {
this.connector = connector;
this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData();
this.protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION,
ProtocolVersion.v1_13.getVersion());
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
GeyserSession session = null;
for (GeyserSession otherSession : connector.getPlayers()) {
if (otherSession.getName().equals(event.getPlayer().getName())) {
session = otherSession;
break;
}
}
if (session == null) {
return;
}
sendServerRecipes(session);
}
public void sendServerRecipes(GeyserSession session) {
int netId = RecipeRegistry.LAST_RECIPE_NET_ID;
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
craftingDataPacket.setCleanRecipes(true);
Iterator<Recipe> recipeIterator = Bukkit.getServer().recipeIterator();
while (recipeIterator.hasNext()) {
Recipe recipe = recipeIterator.next();
Pair<ItemStack, ItemData> outputs = translateToBedrock(session, recipe.getResult());
ItemStack javaOutput = outputs.getKey();
ItemData output = outputs.getValue();
if (output.getId() == 0) continue; // If items make air we don't want that
boolean isNotAllAir = false; // Check for all-air recipes
if (recipe instanceof ShapedRecipe) {
ShapedRecipe shapedRecipe = (ShapedRecipe) recipe;
int size = shapedRecipe.getShape().length * shapedRecipe.getShape()[0].length();
Ingredient[] ingredients = new Ingredient[size];
ItemData[] input = new ItemData[size];
for (int i = 0; i < input.length; i++) {
// Index is converting char to integer, adding i then converting back to char based on ASCII code
Pair<ItemStack, ItemData> result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i)));
ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()});
input[i] = result.getValue();
isNotAllAir |= input[i].getId() != 0;
}
if (!isNotAllAir) continue;
UUID uuid = UUID.randomUUID();
// Add recipe to our internal cache
ShapedRecipeData data = new ShapedRecipeData(shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length,
"", ingredients, javaOutput);
session.getCraftingRecipes().put(netId,
new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data));
// Add recipe for Bedrock
craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(),
shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input),
Collections.singletonList(output), uuid, "crafting_table", 0, netId++));
} else if (recipe instanceof ShapelessRecipe) {
ShapelessRecipe shapelessRecipe = (ShapelessRecipe) recipe;
Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()];
ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()];
for (int i = 0; i < input.length; i++) {
Pair<ItemStack, ItemData> result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i));
ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()});
input[i] = result.getValue();
isNotAllAir |= input[i].getId() != 0;
}
if (!isNotAllAir) continue;
UUID uuid = UUID.randomUUID();
// Add recipe to our internal cache
ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput);
session.getCraftingRecipes().put(netId,
new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data));
// Add recipe for Bedrock
craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++));
}
}
session.sendUpstreamPacket(craftingDataPacket);
}
@SuppressWarnings("deprecation")
private Pair<ItemStack, ItemData> translateToBedrock(GeyserSession session, org.bukkit.inventory.ItemStack itemStack) {
if (itemStack != null && itemStack.getData() != null) {
if (itemStack.getType().getId() == 0) {
return new Pair<>(null, ItemData.AIR);
}
int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF);
if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12
legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF);
}
// old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on
int itemId;
if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) {
itemId = mappingData1_12to1_13.getNewItemId(legacyId);
} else if (mappingData1_12to1_13.getItemMappings().containsKey((itemStack.getType().getId() << 4) | (0))) {
itemId = mappingData1_12to1_13.getNewItemId((itemStack.getType().getId() << 4) | (0));
} else {
// No ID found, just send back air
return new Pair<>(null, ItemData.AIR);
}
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getValue().getMappingData();
if (mappingData != null) {
itemId = mappingData.getNewItemId(itemId);
}
}
ItemStack mcItemStack = new ItemStack(itemId, itemStack.getAmount());
ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack);
return new Pair<>(mcItemStack, finalData);
}
// Empty slot, most likely
return new Pair<>(null, ItemData.AIR);
}
}

Datei anzeigen

@ -53,11 +53,11 @@ public class GeyserSpigotBlockPlaceListener implements Listener {
placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()));
placeBlockSoundPacket.setBabySound(false);
if (worldManager.isLegacy()) {
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session,
placeBlockSoundPacket.setExtraData(session.getBlockTranslator().getBedrockBlockId(worldManager.getBlockAt(session,
event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())));
} else {
String javaBlockId = event.getBlockPlaced().getBlockData().getAsString();
placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID)));
placeBlockSoundPacket.setExtraData(session.getBlockTranslator().getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID)));
}
placeBlockSoundPacket.setIdentifier(":");
session.sendUpstreamPacket(placeBlockSoundPacket);

Datei anzeigen

@ -27,10 +27,11 @@ package org.geysermc.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
import us.myles.ViaVersion.api.Via;
import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
@ -40,7 +41,8 @@ import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage;
public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager {
private final SpigotWorldAdapter adapter;
public GeyserSpigot1_12NativeWorldManager() {
public GeyserSpigot1_12NativeWorldManager(Plugin plugin) {
super(plugin);
this.adapter = SpigotAdapters.getWorldAdapter();
// Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion
}

Datei anzeigen

@ -30,6 +30,7 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import us.myles.ViaVersion.api.Pair;
@ -61,8 +62,8 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
*/
private final List<Pair<Integer, Protocol>> protocolList;
public GeyserSpigot1_12WorldManager() {
super(false);
public GeyserSpigot1_12WorldManager(Plugin plugin) {
super(plugin, false);
this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData();
this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getVersion());
@ -75,6 +76,10 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
if (player == null) {
return BlockTranslator.JAVA_AIR_ID;
}
if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) {
// Prevent nasty async errors if a player is loading in
return BlockTranslator.JAVA_AIR_ID;
}
// Get block entity storage
BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class);
Block block = player.getWorld().getBlockAt(x, y, z);

Datei anzeigen

@ -26,6 +26,7 @@
package org.geysermc.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@ -35,9 +36,9 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator;
* If this occurs to you somehow, please let us know!!
*/
public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager {
public GeyserSpigotFallbackWorldManager() {
public GeyserSpigotFallbackWorldManager(Plugin plugin) {
// Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes.
super(false);
super(plugin, false);
}
@Override
@ -51,7 +52,7 @@ public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager {
}
@Override
public boolean hasMoreBlockDataThanChunkCache() {
public boolean hasOwnChunkCache() {
return false;
}

Datei anzeigen

@ -47,7 +47,7 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl
private final Int2IntMap oldToNewBlockId;
public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) {
super(use3dBiomes);
super(plugin, use3dBiomes);
IntList allBlockStates = adapter.getAllBlockStates();
oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size());
ProtocolVersion serverVersion = plugin.getServerProtocolVersion();

Datei anzeigen

@ -27,16 +27,17 @@ package org.geysermc.platform.spigot.world.manager;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.geysermc.adapters.spigot.SpigotAdapters;
import org.geysermc.adapters.spigot.SpigotWorldAdapter;
import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter;
public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager {
protected final SpigotWorldAdapter adapter;
public GeyserSpigotNativeWorldManager(boolean use3dBiomes) {
super(use3dBiomes);
public GeyserSpigotNativeWorldManager(Plugin plugin, boolean use3dBiomes) {
super(plugin, use3dBiomes);
adapter = SpigotAdapters.getWorldAdapter();
}

Datei anzeigen

@ -28,23 +28,35 @@ package org.geysermc.platform.spigot.world.manager;
import com.fasterxml.jackson.databind.JsonNode;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.block.Lectern;
import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.plugin.Plugin;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockEntityUtils;
import org.geysermc.connector.utils.FileUtils;
import org.geysermc.connector.utils.GameRule;
import org.geysermc.connector.utils.LanguageUtils;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* The base world manager to use when there is no supported NMS revision
@ -72,8 +84,11 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
*/
private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length);
public GeyserSpigotWorldManager(boolean use3dBiomes) {
private final Plugin plugin;
public GeyserSpigotWorldManager(Plugin plugin, boolean use3dBiomes) {
this.use3dBiomes = use3dBiomes;
this.plugin = plugin;
// Load the values into the biome-to-ID map
InputStream biomeStream = FileUtils.getResource("biomes.json");
@ -125,16 +140,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
}
@Override
public boolean hasMoreBlockDataThanChunkCache() {
public boolean hasOwnChunkCache() {
return true;
}
@Override
@SuppressWarnings("deprecation")
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
if (session.getPlayerEntity() == null) {
return new int[1024];
}
int[] biomeData = new int[1024];
World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld();
int chunkX = x << 4;
@ -167,6 +179,77 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
return biomeData;
}
@Override
public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) {
// Run as a task to prevent async issues
Runnable lecternInfoGet = () -> {
Player bukkitPlayer;
if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
return;
}
Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z);
if (!(block.getState() instanceof Lectern)) {
session.getConnector().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString());
return;
}
Lectern lectern = (Lectern) block.getState();
ItemStack itemStack = lectern.getInventory().getItem(0);
if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta)) {
if (!isChunkLoad) {
// We need to update the lectern since it's not going to be updated otherwise
BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z));
}
// We don't care; return
return;
}
BookMeta bookMeta = (BookMeta) itemStack.getItemMeta();
// On the count: allow the book to show/open even there are no pages. We know there is a book here, after all, and this matches Java behavior
boolean hasBookPages = bookMeta.getPageCount() > 0;
NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? bookMeta.getPageCount() : 1);
lecternTag.putInt("page", lectern.getPage() / 2);
NbtMapBuilder bookTag = NbtMap.builder()
.putByte("Count", (byte) itemStack.getAmount())
.putShort("Damage", (short) 0)
.putString("Name", "minecraft:writable_book");
List<NbtMap> pages = new ArrayList<>(bookMeta.getPageCount());
if (hasBookPages) {
for (String page : bookMeta.getPages()) {
NbtMapBuilder pageBuilder = NbtMap.builder()
.putString("photoname", "")
.putString("text", page);
pages.add(pageBuilder.build());
}
} else {
// Empty page
NbtMapBuilder pageBuilder = NbtMap.builder()
.putString("photoname", "")
.putString("text", "");
pages.add(pageBuilder.build());
}
bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build());
lecternTag.putCompound("book", bookTag.build());
NbtMap blockEntityTag = lecternTag.build();
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z));
};
if (isChunkLoad) {
// Delay to ensure the chunk is sent first, and then the lectern data
Bukkit.getScheduler().runTaskLater(this.plugin, lecternInfoGet, 5);
} else {
Bukkit.getScheduler().runTask(this.plugin, lecternInfoGet);
}
return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); // Will be updated later
}
@Override
public boolean shouldExpectLecternHandled() {
return true;
}
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
}

Datei anzeigen

@ -3,7 +3,7 @@ name: ${outputName}-Spigot
author: ${project.organization.name}
website: ${project.organization.url}
version: ${project.version}
softdepend: ["ViaVersion"]
softdepend: ["ViaVersion", "floodgate"]
api-version: 1.13
commands:
geyser:

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-sponge</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>connector</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>

Datei anzeigen

@ -101,7 +101,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
}
}
if (geyserConfig.getBedrock().isCloneRemotePort()){
if (geyserConfig.getBedrock().isCloneRemotePort()) {
geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort());
}
@ -163,4 +163,9 @@ public class GeyserSpongePlugin implements GeyserBootstrap {
public BootstrapDumpInfo getDumpInfo() {
return new GeyserSpongeDumpInfo();
}
@Override
public String getMinecraftServerVersion() {
return Sponge.getPlatform().getMinecraftVersion().getName();
}
}

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-standalone</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>connector</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>

Datei anzeigen

@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import lombok.Getter;
import net.minecrell.terminalconsole.TerminalConsoleAppender;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Logger;
@ -167,11 +168,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
this.onEnable();
}
public void onEnable(boolean useGui) {
this.useGui = useGui;
this.onEnable();
}
@Override
public void onEnable() {
Logger logger = (Logger) LogManager.getRootLogger();
@ -213,6 +209,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
}
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
// Allow libraries like Protocol to have their debug information passthrough
logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO);
connector = GeyserConnector.start(PlatformType.STANDALONE, this);
geyserCommandManager = new GeyserCommandManager(connector);

Datei anzeigen

@ -55,7 +55,7 @@ public class LoopbackUtil {
if (!result.contains("minecraftuwp")) {
Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]);
process = Runtime.getRuntime().exec(startScript);
Runtime.getRuntime().exec(startScript);
geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added"));
}

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-velocity</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>connector</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>

Datei anzeigen

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>

Datei anzeigen

@ -26,8 +26,8 @@
package org.geysermc.floodgate.util;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This
@ -35,9 +35,9 @@ import lombok.Getter;
* present in the API module of the Floodgate repo)
*/
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class BedrockData implements Cloneable {
public static final int EXPECTED_LENGTH = 10;
public static final int EXPECTED_LENGTH = 13;
private final String version;
private final String username;
@ -50,19 +50,26 @@ public final class BedrockData implements Cloneable {
private final LinkedPlayer linkedPlayer;
private final boolean fromProxy;
private final int subscribeId;
private final String verifyCode;
private final long timestamp;
private final int dataLength;
public static BedrockData of(String version, String username, String xuid, int deviceOs,
String languageCode, int uiProfile, int inputMode, String ip,
LinkedPlayer linkedPlayer, boolean fromProxy) {
LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId,
String verifyCode) {
return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode,
uiProfile, ip, linkedPlayer, fromProxy, EXPECTED_LENGTH);
uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode,
System.currentTimeMillis(), EXPECTED_LENGTH);
}
public static BedrockData of(String version, String username, String xuid, int deviceOs,
String languageCode, int uiProfile, int inputMode, String ip) {
return of(version, username, xuid, deviceOs, languageCode,
uiProfile, inputMode, ip, null, false);
String languageCode, int uiProfile, int inputMode, String ip,
int subscribeId, String verifyCode) {
return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null,
false, subscribeId, verifyCode);
}
public static BedrockData fromString(String data) {
@ -75,13 +82,14 @@ public final class BedrockData implements Cloneable {
// The format is the same as the order of the fields in this class
return new BedrockData(
split[0], split[1], split[2], Integer.parseInt(split[3]), split[4],
Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7],
linkedPlayer, "1".equals(split[8]), split.length
Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer,
"1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length
);
}
private static BedrockData emptyData(int dataLength) {
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, dataLength);
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1,
dataLength);
}
public boolean hasPlayerLink() {
@ -94,7 +102,8 @@ public final class BedrockData implements Cloneable {
return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' +
languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' +
(fromProxy ? 1 : 0) + '\0' +
(linkedPlayer != null ? linkedPlayer.toString() : "null");
(linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' +
subscribeId + '\0' + verifyCode + '\0' + timestamp;
}
@Override

Datei anzeigen

@ -29,7 +29,7 @@ package org.geysermc.floodgate.util;
public enum InputMode {
UNKNOWN,
KEYBOARD_MOUSE,
TOUCH, // I guess Touch?
TOUCH,
CONTROLLER,
VR;

Datei anzeigen

@ -57,7 +57,7 @@ public final class LinkedPlayer implements Cloneable {
return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId);
}
static LinkedPlayer fromString(String data) {
public static LinkedPlayer fromString(String data) {
String[] split = data.split(";");
if (split.length != 3) {
return null;

Datei anzeigen

@ -1,106 +0,0 @@
/*
* Copyright (c) 2019-2020 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.floodgate.util;
import lombok.AllArgsConstructor;
import java.nio.ByteBuffer;
import java.util.Base64;
import static java.lang.String.format;
@AllArgsConstructor
public final class RawSkin {
public int width;
public int height;
public byte[] data;
public boolean alex;
private RawSkin() {
}
public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException {
if (data == null || offset < 0 || data.length <= offset) {
return null;
}
if (offset == 0) {
return decode(data);
}
byte[] rawSkin = new byte[data.length - offset];
System.arraycopy(data, offset, rawSkin, 0, rawSkin.length);
return decode(rawSkin);
}
public static RawSkin decode(byte[] data) throws InvalidFormatException {
// offset is an amount of bytes before the Base64 starts
if (data == null) {
return null;
}
int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9);
// if the RawSkin is longer then the max Java Edition skin length
if (data.length > maxEncodedLength) {
throw new InvalidFormatException(format(
"Encoded data cannot be longer then %s bytes! Got %s",
maxEncodedLength, data.length
));
}
// if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex
if (data.length < Base64Utils.getEncodedLength(9)) {
throw new InvalidFormatException("Encoded data must be at least 16 bytes long!");
}
data = Base64.getDecoder().decode(data);
ByteBuffer buffer = ByteBuffer.wrap(data);
RawSkin skin = new RawSkin();
skin.width = buffer.getInt();
skin.height = buffer.getInt();
if (buffer.remaining() - 1 != (skin.width * skin.height * 4)) {
throw new InvalidFormatException(format(
"Expected skin length to be %s, got %s",
(skin.width * skin.height * 4), buffer.remaining()
));
}
skin.data = new byte[buffer.remaining() - 1];
buffer.get(skin.data);
skin.alex = buffer.get() == 1;
return skin;
}
public byte[] encode() {
// 2 x int + 1 = 9 bytes
ByteBuffer buffer = ByteBuffer.allocate(9 + data.length);
buffer.putInt(width);
buffer.putInt(height);
buffer.put(data);
buffer.put((byte) (alex ? 1 : 0));
return Base64.getEncoder().encode(buffer.array());
}
}

Datei anzeigen

@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2021 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.floodgate.util;
public enum WebsocketEventType {
SUBSCRIBER_CREATED,
SUBSCRIBERS_COUNT,
ADDED_TO_QUEUE,
SKIN_UPLOADED,
CREATOR_DISCONNECTED;
public static final WebsocketEventType[] VALUES = values();
public static WebsocketEventType getById(int id) {
return VALUES.length > id ? VALUES[id] : null;
}
}

Datei anzeigen

@ -6,39 +6,46 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>1.2.0-SNAPSHOT</version>
<version>1.3.0-SNAPSHOT</version>
</parent>
<artifactId>connector</artifactId>
<properties>
<netty.version>4.1.59.Final</netty.version>
<fastutil.version>8.5.2</fastutil.version>
<adventure.version>4.7.0</adventure.version>
</properties>
<dependencies>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>common</artifactId>
<version>1.2.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.8</version>
<version>1.3.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.8</version>
<version>2.10.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.10.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.1</version>
</dependency>
<dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<artifactId>bedrock-v422</artifactId>
<version>d41b84e86c</version>
<artifactId>bedrock-v428</artifactId>
<version>42da92f</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>net.sf.trove4j</groupId>
<artifactId>trove</artifactId>
</exclusion>
<!-- Stay on the older version of Network while it's rewritten -->
<exclusion>
<groupId>com.nukkitx.network</groupId>
<artifactId>raknet</artifactId>
@ -48,67 +55,73 @@
<dependency>
<groupId>com.nukkitx.network</groupId>
<artifactId>raknet</artifactId>
<version>1.6.20</version>
<version>1.6.26-20210217.205834-2</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-int-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-float-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-long-long-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-long-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-byte-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-double-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-boolean-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-int-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-byte-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-object-maps</artifactId>
<version>8.3.1</version>
<version>${fastutil.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -138,9 +151,9 @@
</exclusions>
</dependency>
<dependency>
<groupId>com.github.steveice10</groupId>
<groupId>com.github.GeyserMC</groupId>
<artifactId>PacketLib</artifactId>
<version>54f761c</version>
<version>b77a427</version>
<scope>compile</scope>
<exclusions>
<exclusion> <!-- Move this exclusion back to MCProtocolLib it gets the latest PacketLib -->
@ -152,15 +165,51 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns</artifactId>
<version>4.1.43.Final</version>
<version>${netty.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns-native-macos</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
<classifier>osx-x86_64</classifier>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
<version>4.1.56.Final</version>
<version>${netty.version}</version>
<scope>compile</scope>
</dependency>
<!-- Network dependencies we are updating ourselves -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
<classifier>linux-aarch_64</classifier>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-kqueue</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
<classifier>osx-x86_64</classifier>
</dependency>
<!-- End -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
@ -174,25 +223,25 @@
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId>
<version>4.3.0</version>
<version>${adventure.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId>
<version>4.3.0</version>
<version>${adventure.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-legacy</artifactId>
<version>4.3.0</version>
<version>${adventure.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
<version>4.3.0</version>
<version>${adventure.version}</version>
<scope>compile</scope>
</dependency>
<dependency>

Datei anzeigen

@ -33,9 +33,18 @@ import java.nio.file.Path;
public class FloodgateKeyLoader {
public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) {
if (!config.getRemote().getAuthType().equals("floodgate")) {
return geyserDataFolder.resolve(config.getFloodgateKeyFile());
}
Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile());
if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) {
if (config.getFloodgateKeyFile().equals("public-key.pem")) {
logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead");
floodgateKey = geyserDataFolder.resolve("key.pem");
}
if (!Files.exists(floodgateKey)) {
if (floodgate != null) {
Path autoKey = floodgateDataFolder.resolve("key.pem");
if (Files.exists(autoKey)) {

Datei anzeigen

@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nukkitx.network.raknet.RakNetConstants;
import com.nukkitx.network.util.EventLoops;
import com.nukkitx.protocol.bedrock.BedrockServer;
import lombok.Getter;
import lombok.Setter;
@ -39,7 +40,6 @@ import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.metrics.Metrics;
import org.geysermc.connector.network.ConnectorServerEventHandler;
import org.geysermc.connector.network.remote.RemoteServer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.BiomeTranslator;
import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
@ -56,10 +56,8 @@ import org.geysermc.connector.network.translators.world.WorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator;
import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.utils.ResourcePack;
import org.geysermc.connector.skin.FloodgateSkinUploader;
import org.geysermc.connector.utils.*;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
@ -85,7 +83,8 @@ public class GeyserConnector {
.enable(JsonParser.Feature.IGNORE_UNDEFINED)
.enable(JsonParser.Feature.ALLOW_COMMENTS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
public static final String NAME = "Geyser";
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
@ -103,11 +102,11 @@ public class GeyserConnector {
private static GeyserConnector instance;
private RemoteServer remoteServer;
@Setter
private AuthType authType;
private AuthType defaultAuthType;
private FloodgateCipher cipher;
private FloodgateSkinUploader skinUploader;
private boolean shuttingDown = false;
@ -174,7 +173,7 @@ public class GeyserConnector {
String remoteAddress = config.getRemote().getAddress();
int remotePort = config.getRemote().getPort();
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
if ((config.isLegacyPingPassthrough() || platformType == PlatformType.STANDALONE) && !remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) {
if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) {
try {
// Searches for a server address and a port from a SRV record of the specified host name
InitialDirContext ctx = new InitialDirContext();
@ -194,20 +193,21 @@ public class GeyserConnector {
}
}
remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort);
authType = AuthType.getByName(config.getRemote().getAuthType());
defaultAuthType = AuthType.getByName(config.getRemote().getAuthType());
if (authType == AuthType.FLOODGATE) {
if (defaultAuthType == AuthType.FLOODGATE) {
try {
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath());
cipher = new AesCipher(new Base64Topping());
cipher.init(key);
logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
skinUploader = new FloodgateSkinUploader(this).start();
} catch (Exception exception) {
logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception);
}
}
CooldownUtils.setShowCooldown(config.isShowCooldown());
DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls();
@ -215,7 +215,13 @@ public class GeyserConnector {
RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu();
logger.debug("Setting MTU to " + config.getMtu());
bedrockServer = new BedrockServer(new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()));
boolean enableProxyProtocol = config.getBedrock().isEnableProxyProtocol();
bedrockServer = new BedrockServer(
new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()),
1,
EventLoops.commonGroup(),
enableProxyProtocol
);
bedrockServer.setHandler(new ConnectorServerEventHandler(this));
bedrockServer.bind().whenComplete((avoid, throwable) -> {
if (throwable == null) {
@ -262,6 +268,20 @@ public class GeyserConnector {
}
return valueMap;
}));
String minecraftVersion = bootstrap.getMinecraftServerVersion();
if (minecraftVersion != null) {
Map<String, Map<String, Integer>> versionMap = new HashMap<>();
Map<String, Integer> platformMap = new HashMap<>();
platformMap.put(platformType.getPlatformName(), 1);
versionMap.put(minecraftVersion, platformMap);
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> {
// By the end, we should return, for example:
// 1.16.5 => (Spigot, 1)
return versionMap;
}));
}
}
boolean isGui = false;
@ -332,8 +352,7 @@ public class GeyserConnector {
generalThreadPool.shutdown();
bedrockServer.close();
players.clear();
remoteServer = null;
authType = null;
defaultAuthType = null;
this.getCommandManager().getCommands().clear();
bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.done"));
@ -369,6 +388,7 @@ public class GeyserConnector {
* @param xuid the Xbox user identifier
* @return the player or <code>null</code> if there is no player online with this xuid
*/
@SuppressWarnings("unused") // API usage
public GeyserSession getPlayerByXuid(String xuid) {
for (GeyserSession session : players) {
if (session.getAuthData() != null && session.getAuthData().getXboxUUID().equals(xuid)) {

Datei anzeigen

@ -33,6 +33,7 @@ import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.WorldManager;
import javax.annotation.Nullable;
import java.nio.file.Path;
public interface GeyserBootstrap {
@ -99,4 +100,18 @@ public interface GeyserBootstrap {
* @return The info about the bootstrap
*/
BootstrapDumpInfo getDumpInfo();
/**
* Returns the Minecraft version currently being used on the server. This should be only be implemented on platforms
* that have direct server access - platforms such as proxies always have to be on their latest version to support
* the newest Minecraft version, but older servers can use ViaVersion to enable newer versions to join.
* <br>
* If used, this should not be null before {@link org.geysermc.connector.GeyserConnector} initialization.
*
* @return the Minecraft version being used on the server, or <code>null</code> if not applicable
*/
@Nullable
default String getMinecraftServerVersion() {
return null;
}
}

Datei anzeigen

@ -52,7 +52,7 @@ public class IGeyserMain {
* @return The formatted message
*/
private String createMessage() {
String message = "";
StringBuilder message = new StringBuilder();
InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt");
@ -68,10 +68,10 @@ public class IGeyserMain {
line = line.replace("${plugin_type}", this.getPluginType());
line = line.replace("${plugin_folder}", this.getPluginFolder());
message += line + "\n";
message.append(line).append("\n");
}
return message;
return message.toString();
}
/**

Datei anzeigen

@ -27,9 +27,11 @@ package org.geysermc.connector.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.network.CIDRMatcher;
import org.geysermc.connector.utils.LanguageUtils;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
public interface GeyserConfiguration {
@ -59,6 +61,8 @@ public interface GeyserConfiguration {
int getPingPassthroughInterval();
boolean isForwardPlayerPing();
int getMaxPlayers();
boolean isDebugMode();
@ -104,6 +108,15 @@ public interface GeyserConfiguration {
String getMotd2();
String getServerName();
boolean isEnableProxyProtocol();
List<String> getProxyProtocolWhitelistedIPs();
/**
* @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()}
*/
List<CIDRMatcher> getWhitelistedIPsMatchers();
}
interface IRemoteConfiguration {

Datei anzeigen

@ -25,16 +25,21 @@
package org.geysermc.connector.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.serializer.AsteriskSerializer;
import org.geysermc.connector.network.CIDRMatcher;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
@ -74,6 +79,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("ping-passthrough-interval")
private int pingPassthroughInterval = 3;
@JsonProperty("forward-player-ping")
private boolean forwardPlayerPing = false;
@JsonProperty("max-players")
private int maxPlayers = 100;
@ -119,6 +127,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
private MetricsInfo metrics = new MetricsInfo();
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BedrockConfiguration implements IBedrockConfiguration {
@AsteriskSerializer.Asterisk(sensitive = true)
private String address = "0.0.0.0";
@ -134,9 +143,33 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("server-name")
private String serverName = GeyserConnector.NAME;
@JsonProperty("enable-proxy-protocol")
private boolean enableProxyProtocol = false;
@JsonProperty("proxy-protocol-whitelisted-ips")
private List<String> proxyProtocolWhitelistedIPs = Collections.emptyList();
@JsonIgnore
private List<CIDRMatcher> whitelistedIPsMatchers = null;
@Override
public List<CIDRMatcher> getWhitelistedIPsMatchers() {
// Effective Java, Third Edition; Item 83: Use lazy initialization judiciously
List<CIDRMatcher> matchers = this.whitelistedIPsMatchers;
if (matchers == null) {
synchronized (this) {
this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream()
.map(CIDRMatcher::new)
.collect(Collectors.toList());
}
}
return Collections.unmodifiableList(matchers);
}
}
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public static class RemoteConfiguration implements IRemoteConfiguration {
@Setter
@AsteriskSerializer.Asterisk(sensitive = true)
@ -170,6 +203,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
}
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MetricsInfo implements IMetricsInfo {
private boolean enabled = true;

Datei anzeigen

@ -35,6 +35,8 @@ public class AbstractArrowEntity extends Entity {
public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
setMotion(motion);
}
@Override
@ -47,4 +49,20 @@ public class AbstractArrowEntity extends Entity {
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void setRotation(Vector3f rotation) {
// Ignore the rotation sent by the Java server since the
// Java client calculates the rotation from the motion
}
@Override
public void setMotion(Vector3f motion) {
super.setMotion(motion);
double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ());
float yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()));
float pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed));
rotation = Vector3f.from(yaw, pitch, yaw);
}
}

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -43,7 +44,7 @@ public class BoatEntity extends Entity {
*/
private static final String BUOYANCY_DATA = "{\"apply_gravity\":true,\"base_buoyancy\":1.0,\"big_wave_probability\":0.02999999932944775," +
"\"big_wave_speed\":10.0,\"drag_down_on_buoyancy_removed\":0.0,\"liquid_blocks\":[\"minecraft:water\"," +
"\"minecraft:flowing_water\"],\"simulate_waves\":false}}";
"\"minecraft:flowing_water\"],\"simulate_waves\":false}";
private boolean isPaddlingLeft;
private float paddleTimeLeft;
@ -105,27 +106,38 @@ public class BoatEntity extends Entity {
metadata.put(EntityData.VARIANT, entityMetadata.getValue());
} else if (entityMetadata.getId() == 11) {
isPaddlingLeft = (boolean) entityMetadata.getValue();
if (!isPaddlingLeft) {
metadata.put(EntityData.ROW_TIME_LEFT, 0f);
}
else {
if (isPaddlingLeft) {
// Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
// This is an asynchronous method that emulates Bedrock rowing until "false" is sent.
paddleTimeLeft = 0f;
session.getConnector().getGeneralThreadPool().execute(() ->
updateLeftPaddle(session, entityMetadata)
);
if (!this.passengers.isEmpty()) {
// Get the entity by the first stored passenger and convey motion in this manner
Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong());
if (entity != null) {
session.getConnector().getGeneralThreadPool().execute(() ->
updateLeftPaddle(session, entity)
);
}
}
} else {
// Indicate that the row position should be reset
metadata.put(EntityData.ROW_TIME_LEFT, 0.0f);
}
}
else if (entityMetadata.getId() == 12) {
isPaddlingRight = (boolean) entityMetadata.getValue();
if (!isPaddlingRight) {
metadata.put(EntityData.ROW_TIME_RIGHT, 0f);
} else {
if (isPaddlingRight) {
paddleTimeRight = 0f;
session.getConnector().getGeneralThreadPool().execute(() ->
updateRightPaddle(session, entityMetadata)
);
if (!this.passengers.isEmpty()) {
Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong());
if (entity != null) {
session.getConnector().getGeneralThreadPool().execute(() ->
updateRightPaddle(session, entity)
);
}
}
} else {
metadata.put(EntityData.ROW_TIME_RIGHT, 0.0f);
}
} else if (entityMetadata.getId() == 13) {
// Possibly - I don't think this does anything?
@ -135,27 +147,46 @@ public class BoatEntity extends Entity {
super.updateBedrockMetadata(entityMetadata, session);
}
public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) {
@Override
public void updateBedrockMetadata(GeyserSession session) {
super.updateBedrockMetadata(session);
// As these indicate to reset rowing, remove them until it is time to send them out again.
metadata.remove(EntityData.ROW_TIME_LEFT);
metadata.remove(EntityData.ROW_TIME_RIGHT);
}
private void updateLeftPaddle(GeyserSession session, Entity rower) {
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;
metadata.put(EntityData.ROW_TIME_LEFT, paddleTimeLeft);
super.updateBedrockMetadata(entityMetadata, session);
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft);
session.getConnector().getGeneralThreadPool().schedule(() ->
updateLeftPaddle(session, entityMetadata),
updateLeftPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
);
}}
}
}
public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) {
private void updateRightPaddle(GeyserSession session, Entity rower) {
if (isPaddlingRight) {
paddleTimeRight += ROWING_SPEED;
metadata.put(EntityData.ROW_TIME_RIGHT, paddleTimeRight);
super.updateBedrockMetadata(entityMetadata, session);
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight);
session.getConnector().getGeneralThreadPool().schedule(() ->
updateRightPaddle(session, entityMetadata),
updateRightPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
);
}}
}
}
private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) {
AnimatePacket packet = new AnimatePacket();
packet.setRuntimeEntityId(rower.getGeyserId());
packet.setAction(action);
packet.setRowingTime(rowTime);
session.sendUpstreamPacket(packet);
}
}

Datei anzeigen

@ -31,7 +31,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import net.kyori.adventure.text.Component;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
@ -60,8 +59,8 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
* By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange.
*/
@Override
public void updateDefaultBlockMetadata() {
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID);
public void updateDefaultBlockMetadata(GeyserSession session) {
metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockRuntimeCommandBlockId());
metadata.put(EntityData.DISPLAY_OFFSET, 6);
}
}

Datei anzeigen

@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
/**
* This class is used as a base for minecarts with a default block to display like furnaces and spawners
@ -44,10 +43,15 @@ public class DefaultBlockMinecartEntity extends MinecartEntity {
public DefaultBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
updateDefaultBlockMetadata();
metadata.put(EntityData.CUSTOM_DISPLAY, (byte) 1);
}
@Override
public void spawnEntity(GeyserSession session) {
updateDefaultBlockMetadata(session);
super.spawnEntity(session);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
@ -56,7 +60,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity {
customBlock = (int) entityMetadata.getValue();
if (showCustomBlock) {
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock));
metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock));
}
}
@ -73,16 +77,16 @@ public class DefaultBlockMinecartEntity extends MinecartEntity {
if (entityMetadata.getId() == 12) {
if ((boolean) entityMetadata.getValue()) {
showCustomBlock = true;
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock));
metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock));
metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset);
} else {
showCustomBlock = false;
updateDefaultBlockMetadata();
updateDefaultBlockMetadata(session);
}
}
super.updateBedrockMetadata(entityMetadata, session);
}
public void updateDefaultBlockMetadata() { }
public void updateDefaultBlockMetadata(GeyserSession session) { }
}

Datei anzeigen

@ -28,12 +28,6 @@ package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
@ -51,9 +45,8 @@ import org.geysermc.connector.entity.living.ArmorStandEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.AttributeUtils;
import java.util.ArrayList;
import java.util.HashMap;
@ -273,35 +266,9 @@ public class Entity {
metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers
metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80);
if ((xd & 0x20) == 0x20) {
// Armour stands are handled in their own class
if (!this.is(ArmorStandEntity.class)) {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
}
} else {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
}
// Shield code
if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) {
if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) ||
(session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD.getJavaId())) {
ClientPlayerUseItemPacket useItemPacket;
metadata.getFlags().setFlag(EntityFlag.BLOCKING, true);
if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) {
useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
}
// Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent
else {
useItemPacket = new ClientPlayerUseItemPacket(Hand.OFF_HAND);
}
session.sendDownstreamPacket(useItemPacket);
}
} else if (session.getPlayerEntity().getEntityId() == entityId && !metadata.getFlags().getFlag(EntityFlag.SNEAKING) && metadata.getFlags().getFlag(EntityFlag.BLOCKING)) {
metadata.getFlags().setFlag(EntityFlag.BLOCKING, false);
metadata.getFlags().setFlag(EntityFlag.IS_AVOIDING_BLOCK, true);
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0, 0, 0), BlockFace.DOWN);
session.sendDownstreamPacket(releaseItemPacket);
// Armour stands are handled in their own class
if (!this.is(ArmorStandEntity.class)) {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20);
}
}
break;
@ -333,15 +300,12 @@ public class Entity {
case 6: // Pose change
if (entityMetadata.getValue().equals(Pose.SLEEPING)) {
metadata.getFlags().setFlag(EntityFlag.SLEEPING, true);
// Has to be a byte or it does not work
metadata.put(EntityData.PLAYER_FLAGS, (byte) 2);
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f);
} else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) {
metadata.getFlags().setFlag(EntityFlag.SLEEPING, false);
metadata.put(EntityData.BOUNDING_BOX_WIDTH, getEntityType().getWidth());
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, getEntityType().getHeight());
metadata.put(EntityData.PLAYER_FLAGS, (byte) 0);
}
break;
}

Datei anzeigen

@ -31,14 +31,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class FallingBlockEntity extends Entity {
private final int javaId;
public FallingBlockEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, int javaId) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.javaId = javaId;
}
this.metadata.put(EntityData.VARIANT, BlockTranslator.getBedrockBlockId(javaId));
@Override
public void spawnEntity(GeyserSession session) {
this.metadata.put(EntityData.VARIANT, session.getBlockTranslator().getBedrockBlockId(javaId));
super.spawnEntity(session);
}
@Override

Datei anzeigen

@ -26,43 +26,163 @@
package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.GeyserConnector;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.collision.CollisionTranslator;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class FishingHookEntity extends Entity {
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) {
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class FishingHookEntity extends ThrowableEntity {
private boolean hooked = false;
private final BoundingBox boundingBox;
private boolean inWater = false;
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, PlayerEntity owner) {
super(entityId, geyserId, entityType, position, motion, rotation);
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId());
if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) {
entity = session.getPlayerEntity();
}
this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25);
if (entity != null) {
this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId());
return;
}
}
// In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change.
// This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock,
// so that it can be handled by moveAbsoluteImmediate.
this.metadata.putFloat(EntityData.BOUNDING_BOX_HEIGHT, 128);
this.metadata.put(EntityData.OWNER_EID, owner.getGeyserId());
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 7) {
Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1);
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) {
if (entityMetadata.getId() == 7) { // Hooked entity
int hookedEntityId = (int) entityMetadata.getValue() - 1;
Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId);
if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) {
entity = session.getPlayerEntity();
}
if (entity != null) {
metadata.put(EntityData.TARGET_EID, entity.getGeyserId());
hooked = true;
} else {
hooked = false;
}
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
boundingBox.setMiddleX(position.getX());
boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2);
boundingBox.setMiddleZ(position.getZ());
CollisionManager collisionManager = session.getCollisionManager();
List<Vector3i> collidableBlocks = collisionManager.getCollidableBlocks(boundingBox);
boolean touchingWater = false;
boolean collided = false;
for (Vector3i blockPos : collidableBlocks) {
int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos);
BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ());
if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) {
// TODO Push bounding box out of collision to improve movement
collided = true;
}
int waterLevel = BlockStateValues.getWaterLevel(blockID);
if (BlockTranslator.isWaterlogged(blockID)) {
waterLevel = 0;
}
if (waterLevel >= 0) {
double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0;
// Falling water is a full block
if (waterLevel >= 8) {
waterMaxY = blockPos.getY() + 1;
}
if (position.getY() <= waterMaxY) {
touchingWater = true;
}
}
}
if (!inWater && touchingWater) {
sendSplashSound(session);
}
inWater = touchingWater;
if (!collided) {
super.moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
} else {
super.moveAbsoluteImmediate(session, this.position, rotation, true, true);
}
}
private void sendSplashSound(GeyserSession session) {
if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY()));
if (volume > 1) {
volume = 1;
}
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
playSoundPacket.setSound("random.splash");
playSoundPacket.setPosition(position);
playSoundPacket.setVolume(volume);
playSoundPacket.setPitch(1f + ThreadLocalRandom.current().nextFloat() * 0.3f);
session.sendUpstreamPacket(playSoundPacket);
}
}
@Override
public void tick(GeyserSession session) {
if (hooked || !isInAir(session) && !isInWater(session) || isOnGround()) {
motion = Vector3f.ZERO;
return;
}
float gravity = getGravity(session);
motion = motion.down(gravity);
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
float drag = getDrag(session);
motion = motion.mul(drag);
}
@Override
protected float getGravity(GeyserSession session) {
if (!isInWater(session) && !onGround) {
return 0.03f;
}
return 0;
}
/**
* @param session the session of the Bedrock client.
* @return true if this entity is currently in air.
*/
protected boolean isInAir(GeyserSession session) {
if (session.getConnector().getConfig().isCacheChunks()) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return block == BlockTranslator.JAVA_AIR_ID;
}
return false;
}
@Override
protected float getDrag(GeyserSession session) {
return 0.92f;
}
}

Datei anzeigen

@ -44,15 +44,15 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 13 && !showCustomBlock) {
hasFuel = (boolean) entityMetadata.getValue();
updateDefaultBlockMetadata();
updateDefaultBlockMetadata(session);
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void updateDefaultBlockMetadata() {
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID));
public void updateDefaultBlockMetadata(GeyserSession session) {
metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID));
metadata.put(EntityData.DISPLAY_OFFSET, 6);
}
}

Datei anzeigen

@ -40,7 +40,6 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import java.util.concurrent.TimeUnit;
@ -49,15 +48,19 @@ import java.util.concurrent.TimeUnit;
*/
public class ItemFrameEntity extends Entity {
/**
* Used to construct the block entity tag on spawning.
*/
private final HangingDirection direction;
/**
* Used for getting the Bedrock block position.
* Blocks deal with integers whereas entities deal with floats.
*/
private final Vector3i bedrockPosition;
private Vector3i bedrockPosition;
/**
* Specific block 'state' we are emulating in Bedrock.
*/
private final int bedrockRuntimeId;
private int bedrockRuntimeId;
/**
* Rotation of item in frame.
*/
@ -69,19 +72,21 @@ public class ItemFrameEntity extends Entity {
public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) {
super(entityId, geyserId, entityType, position, motion, rotation);
NbtMapBuilder blockBuilder = NbtMap.builder()
.putString("name", "minecraft:frame")
.putInt("version", BlockTranslator.getBlockStateVersion());
blockBuilder.put("states", NbtMap.builder()
.putInt("facing_direction", direction.ordinal())
.putByte("item_frame_map_bit", (byte) 0)
.build());
bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build());
bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ());
this.direction = direction;
}
@Override
public void spawnEntity(GeyserSession session) {
NbtMapBuilder blockBuilder = NbtMap.builder()
.putString("name", "minecraft:frame")
.putInt("version", session.getBlockTranslator().getBlockStateVersion());
blockBuilder.put("states", NbtMap.builder()
.putInt("facing_direction", direction.ordinal())
.putByte("item_frame_map_bit", (byte) 0)
.build());
bedrockRuntimeId = session.getBlockTranslator().getItemFrame(blockBuilder.build());
bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ());
session.getItemFrameCache().put(bedrockPosition, entityId);
// Delay is required, or else loading in frames on chunk load is sketchy at best
session.getConnector().getGeneralThreadPool().schedule(() -> {
@ -136,7 +141,7 @@ public class ItemFrameEntity extends Entity {
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(bedrockPosition);
updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID);
updateBlockPacket.setRuntimeId(session.getBlockTranslator().getBedrockAirId());
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK);
updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS);

Datei anzeigen

@ -32,19 +32,44 @@ import org.geysermc.connector.network.session.GeyserSession;
public class ItemedFireballEntity extends ThrowableEntity {
private final Vector3f acceleration;
/**
* The number of ticks to advance movement before sending to Bedrock
*/
protected int futureTicks = 3;
public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation);
acceleration = motion;
float magnitude = motion.length();
if (magnitude != 0) {
acceleration = motion.div(magnitude).mul(0.1f);
} else {
acceleration = Vector3f.ZERO;
}
}
private Vector3f tickMovement(GeyserSession session, Vector3f position) {
position = position.add(motion);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);
return position;
}
@Override
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Advance the position by a few ticks before sending it to Bedrock
Vector3f lastMotion = motion;
Vector3f newPosition = position;
for (int i = 0; i < futureTicks; i++) {
newPosition = tickMovement(session, newPosition);
}
super.moveAbsoluteImmediate(session, newPosition, rotation, isOnGround, teleported);
this.position = position;
this.motion = lastMotion;
}
@Override
public void tick(GeyserSession session) {
position = position.add(motion);
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
// TODO: movement is incredibly stiff.
// TODO: Only use this laggy movement for fireballs that be reflected
moveAbsoluteImmediate(session, position, rotation, false, true);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);
moveAbsoluteImmediate(session, tickMovement(session, position), rotation, false, false);
}
}

Datei anzeigen

@ -99,6 +99,13 @@ public class LivingEntity extends Entity {
// Bed has to be updated, or else player is floating in the air
ChunkUtils.updateBlock(session, bed, bedPosition);
}
// Indicate that the player should enter the sleep cycle
// Has to be a byte or it does not work
// (Bed position is what actually triggers sleep - "pose" is only optional)
metadata.put(EntityData.PLAYER_FLAGS, (byte) 2);
} else {
// Player is no longer sleeping
metadata.put(EntityData.PLAYER_FLAGS, (byte) 0);
}
break;
}

Datei anzeigen

@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class MinecartEntity extends Entity {
@ -58,7 +57,7 @@ public class MinecartEntity extends Entity {
if (!(this instanceof DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class
// Custom block
if (entityMetadata.getId() == 10) {
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue()));
metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue()));
}
// Custom block offset

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.connector.entity;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity {
@ -37,8 +38,8 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity {
}
@Override
public void updateDefaultBlockMetadata() {
metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID));
public void updateDefaultBlockMetadata(GeyserSession session) {
metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID));
metadata.put(EntityData.DISPLAY_OFFSET, 6);
}
}

Datei anzeigen

@ -29,20 +29,21 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
/**
* Used as a class for any object-like entity that moves as a projectile
*/
public class ThrowableEntity extends Entity implements Tickable {
private Vector3f lastPosition;
protected Vector3f lastJavaPosition;
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.lastPosition = position;
this.lastJavaPosition = position;
}
/**
@ -52,22 +53,65 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
@Override
public void tick(GeyserSession session) {
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
float drag = getDrag(session);
float gravity = getGravity();
float gravity = getGravity(session);
motion = motion.mul(drag).down(gravity);
}
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
moveEntityDeltaPacket.setRuntimeEntityId(geyserId);
if (isOnGround) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
}
setOnGround(isOnGround);
if (teleported) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING);
}
if (this.position.getX() != position.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
moveEntityDeltaPacket.setX(position.getX());
}
if (this.position.getY() != position.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
moveEntityDeltaPacket.setY(position.getY());
}
if (this.position.getZ() != position.getZ()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
moveEntityDeltaPacket.setZ(position.getZ());
}
setPosition(position);
if (this.rotation.getX() != rotation.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityDeltaPacket.setYaw(rotation.getX());
}
if (this.rotation.getY() != rotation.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
moveEntityDeltaPacket.setPitch(rotation.getY());
}
if (this.rotation.getZ() != rotation.getZ()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
moveEntityDeltaPacket.setHeadYaw(rotation.getZ());
}
setRotation(rotation);
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
session.sendUpstreamPacket(moveEntityDeltaPacket);
}
}
/**
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
*
* @param session the session of the Bedrock client.
* @return the amount of gravity to apply to this entity while in motion.
*/
protected float getGravity() {
protected float getGravity(GeyserSession session) {
if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) {
switch (entityType) {
case THROWN_POTION:
@ -76,11 +120,14 @@ public class ThrowableEntity extends Entity implements Tickable {
case THROWN_EXP_BOTTLE:
return 0.07f;
case FIREBALL:
case SHULKER_BULLET:
return 0;
case SNOWBALL:
case THROWN_EGG:
case THROWN_ENDERPEARL:
return 0.03f;
case LLAMA_SPIT:
return 0.06f;
}
}
return 0;
@ -101,11 +148,14 @@ public class ThrowableEntity extends Entity implements Tickable {
case SNOWBALL:
case THROWN_EGG:
case THROWN_ENDERPEARL:
case LLAMA_SPIT:
return 0.99f;
case FIREBALL:
case SMALL_FIREBALL:
case DRAGON_FIREBALL:
return 0.95f;
case SHULKER_BULLET:
return 1;
}
}
return 1;
@ -117,8 +167,10 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
protected boolean isInWater(GeyserSession session) {
if (session.getConnector().getConfig().isCacheChunks()) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return block == BlockTranslator.BEDROCK_WATER_ID;
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return BlockStateValues.getWaterLevel(block) != -1;
}
}
return false;
}
@ -136,14 +188,13 @@ public class ThrowableEntity extends Entity implements Tickable {
@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
position = lastPosition;
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
lastPosition = position;
moveAbsoluteImmediate(session, lastJavaPosition.add(relX, relY, relZ), rotation, isOnGround, false);
lastJavaPosition = position;
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
lastPosition = position;
moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
lastJavaPosition = position;
}
}

Datei anzeigen

@ -35,6 +35,8 @@ public class WitherSkullEntity extends ItemedFireballEntity {
public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.futureTicks = 1;
}
@Override

Datei anzeigen

@ -29,6 +29,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import lombok.Getter;
import org.geysermc.connector.entity.LivingEntity;
import org.geysermc.connector.entity.type.EntityType;
@ -42,15 +45,68 @@ public class ArmorStandEntity extends LivingEntity {
private boolean isInvisible = false;
private boolean isSmall = false;
/**
* On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock.
* By having a second entity, we can allow an invisible entity with the name tag.
* (This lets armor on armor stands still show)
*/
private ArmorStandEntity secondEntity = null;
/**
* Whether this is the primary armor stand that holds the armor and not the name tag.
*/
private boolean primaryEntity = true;
/**
* Whether the entity's position must be updated to included the offset.
*
* This should be true when the Java server marks the armor stand as invisible, but we shrink the entity
* to allow the nametag to appear. Basically:
* - Is visible: this is irrelevant (false)
* - Has armor, no name: false
* - Has armor, has name: false, with a second entity
* - No armor, no name: false
* - No armor, yes name: true
*/
private boolean positionRequiresOffset = false;
/**
* Whether we should update the position of this armor stand after metadata updates.
*/
private boolean positionUpdateRequired = false;
private GeyserSession session;
public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void spawnEntity(GeyserSession session) {
this.session = session;
this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX());
super.spawnEntity(session);
}
@Override
public boolean despawnEntity(GeyserSession session) {
if (secondEntity != null) {
secondEntity.despawnEntity(session);
}
return super.despawnEntity(session);
}
@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
if (secondEntity != null) {
secondEntity.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
}
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands
if (!isMarker && isInvisible && passengers.isEmpty()) {
position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
if (secondEntity != null) {
secondEntity.moveAbsolute(session, applyOffsetToPosition(position), rotation, isOnGround, teleported);
} else if (positionRequiresOffset) {
// Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands
position = applyOffsetToPosition(position);
}
super.moveAbsolute(session, position, Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()), isOnGround, teleported);
@ -58,47 +114,250 @@ public class ArmorStandEntity extends LivingEntity {
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 0 && entityMetadata.getType() == MetadataType.BYTE) {
byte xd = (byte) entityMetadata.getValue();
// Check if the armour stand is invisible and store accordingly
if ((xd & 0x20) == 0x20) {
metadata.put(EntityData.SCALE, 0.0f);
isInvisible = true;
if (primaryEntity) {
isInvisible = (xd & 0x20) == 0x20;
updateSecondEntityStatus(false);
}
} else if (entityMetadata.getId() == 2) {
updateSecondEntityStatus(false);
} else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) {
byte xd = (byte) entityMetadata.getValue();
// isSmall
if ((xd & 0x01) == 0x01) {
isSmall = true;
if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) {
metadata.put(EntityData.SCALE, 0.55f);
boolean newIsSmall = (xd & 0x01) == 0x01;
if (newIsSmall != isSmall) {
if (positionRequiresOffset) {
// Fix new inconsistency with offset
this.position = fixOffsetForSize(position, newIsSmall);
positionUpdateRequired = true;
}
if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f);
isSmall = newIsSmall;
if (!isMarker) {
toggleSmallStatus();
}
} else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth());
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight());
}
// setMarker
if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
isMarker = true;
boolean oldIsMarker = isMarker;
isMarker = (xd & 0x10) == 0x10;
if (oldIsMarker != isMarker) {
if (isMarker) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
metadata.put(EntityData.SCALE, 0f);
} else {
toggleSmallStatus();
}
updateSecondEntityStatus(false);
}
}
super.updateBedrockMetadata(entityMetadata, session);
if (secondEntity != null) {
secondEntity.updateBedrockMetadata(entityMetadata, session);
}
}
@Override
public void spawnEntity(GeyserSession session) {
this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX());
super.spawnEntity(session);
public void updateBedrockMetadata(GeyserSession session) {
if (secondEntity != null) {
secondEntity.updateBedrockMetadata(session);
}
super.updateBedrockMetadata(session);
if (positionUpdateRequired) {
positionUpdateRequired = false;
updatePosition();
}
}
@Override
public void setHelmet(ItemData helmet) {
super.setHelmet(helmet);
updateSecondEntityStatus(true);
}
@Override
public void setChestplate(ItemData chestplate) {
super.setChestplate(chestplate);
updateSecondEntityStatus(true);
}
@Override
public void setLeggings(ItemData leggings) {
super.setLeggings(leggings);
updateSecondEntityStatus(true);
}
@Override
public void setBoots(ItemData boots) {
super.setBoots(boots);
updateSecondEntityStatus(true);
}
@Override
public void setHand(ItemData hand) {
super.setHand(hand);
updateSecondEntityStatus(true);
}
@Override
public void setOffHand(ItemData offHand) {
super.setOffHand(offHand);
updateSecondEntityStatus(true);
}
/**
* Determine if we need to load or unload the second entity.
*
* @param sendMetadata whether to send a metadata update after a change.
*/
private void updateSecondEntityStatus(boolean sendMetadata) {
// A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows.
if (!primaryEntity) return;
if (!isInvisible || isMarker) {
// It is either impossible to show armor, or the armor stand isn't invisible. We good.
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
updateOffsetRequirement(false);
if (positionUpdateRequired) {
positionUpdateRequired = false;
updatePosition();
}
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
return;
}
//boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty() || metadata.getByte(EntityData.NAMETAG_ALWAYS_SHOW, (byte) -1) == (byte) 0; - may not be necessary?
boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty();
if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR)
|| !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) {
// If the second entity exists, no need to recreate it.
// We can't stuff this check above or else it'll fall into another else case and delete the second entity
if (secondEntity != null) return;
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
// the metadata as it will hold the name tag.
secondEntity = new ArmorStandEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(),
EntityType.ARMOR_STAND, position, motion, rotation);
secondEntity.primaryEntity = false;
if (!this.positionRequiresOffset) {
// Ensure the offset is applied for the 0 scale
secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position);
}
// Copy metadata
secondEntity.isSmall = isSmall;
secondEntity.getMetadata().putAll(metadata);
// Copy the flags so they aren't the same object in memory
secondEntity.getMetadata().putFlags(metadata.getFlags().copy());
// Guarantee this copy is NOT invisible
secondEntity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
// Scale to 0 to show nametag
secondEntity.getMetadata().put(EntityData.SCALE, 0.0f);
// No bounding box as we don't want to interact with this entity
secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
secondEntity.spawnEntity(session);
// Reset scale of the proper armor stand
this.metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
// Set the proper armor stand to invisible to show armor
this.metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
// Update the position of the armor stand
updateOffsetRequirement(false);
} else if (isNametagEmpty) {
// We can just make an invisible entity
// Reset scale of the proper armor stand
metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
// Set the proper armor stand to invisible to show armor
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
// Update offset
updateOffsetRequirement(false);
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
} else {
// Nametag is not empty and there is no armor
// We don't need to make a new entity
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
metadata.put(EntityData.SCALE, 0.0f);
// As the above is applied, we need an offset
updateOffsetRequirement(true);
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
}
if (sendMetadata) {
this.updateBedrockMetadata(session);
}
}
/**
* If this armor stand is not a marker, set its bounding box size and scale.
*/
private void toggleSmallStatus() {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, isSmall ? 0.25f : entityType.getWidth());
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, isSmall ? 0.9875f : entityType.getHeight());
metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
}
/**
* @return the selected position with the position offset applied.
*/
private Vector3f applyOffsetToPosition(Vector3f position) {
return position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
}
/**
* @return an adjusted offset for the new small status.
*/
private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) {
position = removeOffsetFromPosition(position);
return position.add(0d, entityType.getHeight() * (isNowSmall ? 0.55d : 1d), 0d);
}
/**
* @return the selected position with the position offset removed.
*/
private Vector3f removeOffsetFromPosition(Vector3f position) {
return position.sub(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
}
/**
* Set the offset to a new value; if it changed, update the position, too.
*/
private void updateOffsetRequirement(boolean newValue) {
if (newValue != positionRequiresOffset) {
this.positionRequiresOffset = newValue;
if (positionRequiresOffset) {
this.position = applyOffsetToPosition(position);
} else {
this.position = removeOffsetFromPosition(position);
}
positionUpdateRequired = true;
}
}
/**
* Updates position without calling movement code.
*/
private void updatePosition() {
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
moveEntityPacket.setPosition(position);
moveEntityPacket.setRotation(Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()));
moveEntityPacket.setOnGround(onGround);
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}
}

Datei anzeigen

@ -44,7 +44,7 @@ public class RabbitEntity extends AnimalEntity {
if (entityMetadata.getId() == 15) {
metadata.put(EntityData.SCALE, .55f);
boolean isBaby = (boolean) entityMetadata.getValue();
if(isBaby) {
if (isBaby) {
metadata.put(EntityData.SCALE, .35f);
metadata.getFlags().setFlag(EntityFlag.BABY, true);
}

Datei anzeigen

@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.type.EntityType;
@ -40,6 +41,9 @@ public class AbstractHorseEntity extends AnimalEntity {
public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
// Specifies the size of the entity's inventory. Required to place slots in the entity.
metadata.put(EntityData.CONTAINER_BASE_SIZE, 2);
}
@Override
@ -75,6 +79,9 @@ public class AbstractHorseEntity extends AnimalEntity {
entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16);
session.sendUpstreamPacket(entityEventPacket);
}
// Set container type if tamed
metadata.put(EntityData.CONTAINER_TYPE, ((xd & 0x02) == 0x02) ? (byte) ContainerType.HORSE.getId() : (byte) 0);
}
// Needed to control horses

Datei anzeigen

@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -35,6 +36,8 @@ public class ChestedHorseEntity extends AbstractHorseEntity {
public ChestedHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
metadata.put(EntityData.CONTAINER_BASE_SIZE, 16);
}
@Override

Datei anzeigen

@ -38,6 +38,8 @@ public class LlamaEntity extends ChestedHorseEntity {
public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
metadata.put(EntityData.CONTAINER_STRENGTH_MODIFIER, 3); // Presumably 3 slots for every 1 strength
}
@Override
@ -56,7 +58,7 @@ public class LlamaEntity extends ChestedHorseEntity {
// The damage value is the dye color that Java sends us
// Always going to be a carpet so we can hardcode 171 in BlockTranslator
// The int then short conversion is required or we get a ClassCastException
equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short)((int) entityMetadata.getValue()), 1));
equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short) ((int) entityMetadata.getValue()), 1));
} else {
equipmentPacket.setChestplate(ItemData.AIR);
}

Datei anzeigen

@ -26,7 +26,6 @@
package org.geysermc.connector.entity.living.merchant;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
@ -101,11 +100,17 @@ public class VillagerEntity extends AbstractMerchantEntity {
@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) {
// No need to worry about extra processing to compensate for sleeping
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
return;
}
int z = 0;
int bedId = 0;
float bedPositionSubtractorW = 0;
float bedPositionSubtractorN = 0;
Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION);
Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION, null);
if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) {
bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition);
}
@ -117,39 +122,33 @@ public class VillagerEntity extends AbstractMerchantEntity {
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
//Sets Villager position and rotation when sleeping
if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) {
moveEntityPacket.setPosition(position);
moveEntityPacket.setRotation(getBedrockRotation());
} else {
//String Setup
Pattern r = Pattern.compile("facing=([a-z]+)");
Matcher m = r.matcher(bedRotationZ);
if (m.find()) {
switch (m.group(0)){
case "facing=south":
//bed is facing south
z = 180;
bedPositionSubtractorW = -.5f;
break;
case "facing=east":
//bed is facing east
z = 90;
bedPositionSubtractorW = -.5f;
break;
case "facing=west":
//bed is facing west
z = 270;
bedPositionSubtractorW = .5f;
break;
case "facing=north":
//rotation does not change because north is 0
bedPositionSubtractorN = .5f;
break;
}
Pattern r = Pattern.compile("facing=([a-z]+)");
Matcher m = r.matcher(bedRotationZ);
if (m.find()) {
switch (m.group(0)) {
case "facing=south":
//bed is facing south
z = 180;
bedPositionSubtractorW = -.5f;
break;
case "facing=east":
//bed is facing east
z = 90;
bedPositionSubtractorW = -.5f;
break;
case "facing=west":
//bed is facing west
z = 270;
bedPositionSubtractorW = .5f;
break;
case "facing=north":
//rotation does not change because north is 0
bedPositionSubtractorN = .5f;
break;
}
moveEntityPacket.setRotation(Vector3f.from(0, 0, z));
moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN));
}
moveEntityPacket.setRotation(Vector3f.from(0, 0, z));
moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN));
moveEntityPacket.setOnGround(isOnGround);
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);

Datei anzeigen

@ -33,7 +33,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class EndermanEntity extends MonsterEntity {
@ -45,7 +44,7 @@ public class EndermanEntity extends MonsterEntity {
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Held block
if (entityMetadata.getId() == 15) {
metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue()));
metadata.put(EntityData.CARRIED_BLOCK, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue()));
}
// "Is screaming" - controls sound
if (entityMetadata.getId() == 16) {

Datei anzeigen

@ -46,7 +46,7 @@ public class WitherEntity extends MonsterEntity {
if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) {
Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue());
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) {
if (entity == null && session.getPlayerEntity().getEntityId() == (int) entityMetadata.getValue()) {
entity = session.getPlayerEntity();
}
@ -62,7 +62,7 @@ public class WitherEntity extends MonsterEntity {
} else if (entityMetadata.getId() == 17) {
metadata.put(EntityData.WITHER_TARGET_3, targetID);
} else if (entityMetadata.getId() == 18) {
metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, (int) entityMetadata.getValue());
metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, entityMetadata.getValue());
// Show the shield for the first few seconds of spawning (like Java)
if ((int) entityMetadata.getValue() >= 165) {

Datei anzeigen

@ -30,8 +30,11 @@ import org.geysermc.connector.entity.*;
import org.geysermc.connector.entity.living.*;
import org.geysermc.connector.entity.living.animal.*;
import org.geysermc.connector.entity.living.animal.horse.*;
import org.geysermc.connector.entity.living.animal.tameable.*;
import org.geysermc.connector.entity.living.merchant.*;
import org.geysermc.connector.entity.living.animal.tameable.CatEntity;
import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity;
import org.geysermc.connector.entity.living.animal.tameable.WolfEntity;
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
import org.geysermc.connector.entity.living.merchant.VillagerEntity;
import org.geysermc.connector.entity.living.monster.*;
import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity;
import org.geysermc.connector.entity.living.monster.raid.PillagerEntity;
@ -39,6 +42,9 @@ import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity;
import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import java.util.ArrayList;
import java.util.List;
@Getter
public enum EntityType {
@ -112,7 +118,7 @@ public enum EntityType {
TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"),
TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f),
CAT(CatEntity.class, 75, 0.35f, 0.3f),
SHULKER_BULLET(Entity.class, 76, 0.3125f),
SHULKER_BULLET(ThrowableEntity.class, 76, 0.3125f),
FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"),
CHALKBOARD(Entity.class, 78, 0f),
DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f),
@ -139,7 +145,7 @@ public enum EntityType {
MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"),
MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"),
LINGERING_POTION(ThrowableEntity.class, 101, 0f),
LLAMA_SPIT(Entity.class, 102, 0.25f),
LLAMA_SPIT(ThrowableEntity.class, 102, 0.25f),
EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"),
EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"),
VEX(VexEntity.class, 105, 0.8f, 0.4f),
@ -174,17 +180,33 @@ public enum EntityType {
*/
ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand");
/**
* A list of all Java identifiers for use with command suggestions
*/
public static final String[] ALL_JAVA_IDENTIFIERS;
private static final EntityType[] VALUES = values();
private Class<? extends Entity> entityClass;
static {
List<String> allJavaIdentifiers = new ArrayList<>();
for (EntityType type : values()) {
if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) {
continue;
}
allJavaIdentifiers.add("minecraft:" + type.name().toLowerCase());
}
ALL_JAVA_IDENTIFIERS = allJavaIdentifiers.toArray(new String[0]);
}
private final Class<? extends Entity> entityClass;
private final int type;
private final float height;
private final float width;
private final float length;
private final float offset;
private String identifier;
private final String identifier;
EntityType(Class<? extends Entity> entityClass, int type, float height) {
//noinspection SuspiciousNameCombination
this(entityClass, type, height, height);
}
@ -198,8 +220,6 @@ public enum EntityType {
EntityType(Class<? extends Entity> entityClass, int type, float height, float width, float length, float offset) {
this(entityClass, type, height, width, length, offset, null);
this.identifier = "minecraft:" + name().toLowerCase();
}
EntityType(Class<? extends Entity> entityClass, int type, float height, float width, float length, float offset, String identifier) {
@ -209,7 +229,7 @@ public enum EntityType {
this.width = width;
this.length = length;
this.offset = offset + 0.00001f;
this.identifier = identifier;
this.identifier = identifier == null ? "minecraft:" + name().toLowerCase() : identifier;
}
public static EntityType getFromIdentifier(String identifier) {

Datei anzeigen

@ -23,16 +23,15 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.action;
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam;
import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam;
import lombok.AllArgsConstructor;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
@AllArgsConstructor
enum Click {
LEFT(ClickItemParam.LEFT_CLICK),
RIGHT(ClickItemParam.RIGHT_CLICK);
public final WindowActionParam actionParam;
/**
* Used to determine if rename packets should be sent.
*/
public class AnvilContainer extends Container {
public AnvilContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
}

Datei anzeigen

@ -0,0 +1,41 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class BeaconContainer extends Container {
private int primaryId;
private int secondaryId;
public BeaconContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
}

Datei anzeigen

@ -0,0 +1,34 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
public class CartographyContainer extends Container {
public CartographyContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
}

Datei anzeigen

@ -0,0 +1,85 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import lombok.Getter;
import lombok.NonNull;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
/**
* Combination of {@link Inventory} and {@link PlayerInventory}
*/
@Getter
public class Container extends Inventory {
private final PlayerInventory playerInventory;
private final int containerSize;
/**
* Whether we are using a real block when opening this inventory.
*/
private boolean isUsingRealBlock = false;
public Container(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType);
this.playerInventory = playerInventory;
this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE;
}
@Override
public GeyserItemStack getItem(int slot) {
if (slot < this.size) {
return super.getItem(slot);
} else {
return playerInventory.getItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET);
}
}
@Override
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
if (slot < this.size) {
super.setItem(slot, newItem, session);
} else {
playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session);
}
}
@Override
public int getSize() {
return this.containerSize;
}
/**
* Will be overwritten for droppers.
*
* @param usingRealBlock whether this container is using a real container or not
* @param javaBlockId the Java block string of the block, if real
*/
public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) {
isUsingRealBlock = usingRealBlock;
}
}

Datei anzeigen

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
import lombok.Getter;
public class EnchantingContainer extends Container {
/**
* A cache of what Bedrock sees
*/
@Getter
private final EnchantOptionData[] enchantOptions;
/**
* A mutable cache of what the server sends us
*/
@Getter
private final GeyserEnchantOption[] geyserEnchantOptions;
public EnchantingContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
enchantOptions = new EnchantOptionData[3];
geyserEnchantOptions = new GeyserEnchantOption[3];
for (int i = 0; i < geyserEnchantOptions.length; i++) {
geyserEnchantOptions[i] = new GeyserEnchantOption(i);
// Options cannot be null, so we build initial options
// GeyserSession can be safely null here because it's only needed for net IDs
enchantOptions[i] = geyserEnchantOptions[i].build(null);
}
}
}

Datei anzeigen

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import lombok.Getter;
import org.geysermc.connector.network.session.GeyserSession;
public class Generic3X3Container extends Container {
/**
* Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER}.
*
* Used at {@link org.geysermc.connector.network.translators.inventory.translators.Generic3X3InventoryTranslator#openInventory(GeyserSession, Inventory)}
*/
@Getter
private boolean isDropper = false;
public Generic3X3Container(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
@Override
public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) {
super.setUsingRealBlock(usingRealBlock, javaBlockId);
if (usingRealBlock) {
isDropper = javaBlockId.startsWith("minecraft:dropper");
}
}
}

Datei anzeigen

@ -0,0 +1,74 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantData;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A mutable "wrapper" around {@link EnchantOptionData}
*/
@Setter
public class GeyserEnchantOption {
private static final List<EnchantData> EMPTY = Collections.emptyList();
/**
* This: https://cdn.discordapp.com/attachments/613168850925649981/791030657169227816/unknown.png
* is controlled by the server.
* So, of course, we have to throw in some easter eggs. ;)
*/
private static final List<String> ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better",
"explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa",
"tim two zero three", "fast walk nether", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned",
"more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft",
"strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this",
"stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out",
"xp heals tools", "dragon proxy waz here");
@Getter
private final int javaIndex;
private int xpCost = 0;
private int javaEnchantIndex = -1;
private int bedrockEnchantIndex = -1;
private int enchantLevel = -1;
public GeyserEnchantOption(int javaIndex) {
this.javaIndex = javaIndex;
}
public EnchantOptionData build(GeyserSession session) {
return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY,
enchantLevel == -1 ? EMPTY : Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY,
javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId());
}
}

Datei anzeigen

@ -0,0 +1,122 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import lombok.Data;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
@Data
public class GeyserItemStack {
public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null);
private final int javaId;
private int amount;
private CompoundTag nbt;
private int netId;
public GeyserItemStack(int javaId) {
this(javaId, 1);
}
public GeyserItemStack(int javaId, int amount) {
this(javaId, amount, null);
}
public GeyserItemStack(int javaId, int amount, CompoundTag nbt) {
this(javaId, amount, nbt, 1);
}
public GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) {
this.javaId = javaId;
this.amount = amount;
this.nbt = nbt;
this.netId = netId;
}
public static GeyserItemStack from(ItemStack itemStack) {
return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt());
}
public int getJavaId() {
return isEmpty() ? 0 : javaId;
}
public int getAmount() {
return isEmpty() ? 0 : amount;
}
public CompoundTag getNbt() {
return isEmpty() ? null : nbt;
}
public int getNetId() {
return isEmpty() ? 0 : netId;
}
public void add(int add) {
amount += add;
}
public void sub(int sub) {
amount -= sub;
}
public ItemStack getItemStack() {
return getItemStack(amount);
}
public ItemStack getItemStack(int newAmount) {
return isEmpty() ? null : new ItemStack(javaId, newAmount, nbt);
}
public ItemData getItemData(GeyserSession session) {
ItemData itemData = ItemTranslator.translateToBedrock(session, getItemStack());
itemData.setNetId(getNetId());
return itemData;
}
public ItemEntry getItemEntry() {
return ItemRegistry.ITEM_ENTRIES.get(getJavaId());
}
public boolean isEmpty() {
return amount <= 0 || javaId == 0;
}
public GeyserItemStack copy() {
return copy(amount);
}
public GeyserItemStack copy(int newAmount) {
return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, nbt == null ? null : nbt.clone(), netId);
}
}

Datei anzeigen

@ -25,36 +25,39 @@
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.math.vector.Vector3i;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Arrays;
public class Inventory {
@Getter
protected int id;
@Getter
@Setter
protected boolean open;
@Getter
protected WindowType windowType;
protected final int id;
@Getter
protected final int size;
/**
* Used for smooth transitions between two windows of the same type.
*/
@Getter
protected final WindowType windowType;
@Getter
@Setter
protected String title;
@Setter
protected ItemStack[] items;
protected GeyserItemStack[] items;
/**
* The location of the inventory block. Will either be a fake block above the player's head, or the actual block location
*/
@Getter
@Setter
protected Vector3i holderPosition = Vector3i.ZERO;
@ -64,27 +67,67 @@ public class Inventory {
protected long holderId = -1;
@Getter
protected AtomicInteger transactionId = new AtomicInteger(1);
protected short transactionId = 0;
public Inventory(int id, WindowType windowType, int size) {
this("Inventory", id, windowType, size);
@Getter
@Setter
private boolean pending = false;
protected Inventory(int id, int size, WindowType windowType) {
this("Inventory", id, size, windowType);
}
public Inventory(String title, int id, WindowType windowType, int size) {
protected Inventory(String title, int id, int size, WindowType windowType) {
this.title = title;
this.id = id;
this.windowType = windowType;
this.size = size;
this.items = new ItemStack[size];
this.windowType = windowType;
this.items = new GeyserItemStack[size];
Arrays.fill(items, GeyserItemStack.EMPTY);
}
public ItemStack getItem(int slot) {
public GeyserItemStack getItem(int slot) {
if (slot > this.size) {
GeyserConnector.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this.toString());
return GeyserItemStack.EMPTY;
}
return items[slot];
}
public void setItem(int slot, ItemStack item) {
if (item != null && (item.getId() == 0 || item.getAmount() < 1))
item = null;
items[slot] = item;
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
if (slot > this.size) {
session.getConnector().getLogger().debug("Tried to set an item out of bounds! " + this.toString());
return;
}
GeyserItemStack oldItem = items[slot];
updateItemNetId(oldItem, newItem, session);
items[slot] = newItem;
}
protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
if (!newItem.isEmpty()) {
if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) {
newItem.setNetId(oldItem.getNetId());
} else {
newItem.setNetId(session.getNextItemNetId());
}
}
}
public short getNextTransactionId() {
return ++transactionId;
}
@Override
public String toString() {
return "Inventory{" +
"id=" + id +
", size=" + size +
", title='" + title + '\'' +
", items=" + Arrays.toString(items) +
", holderPosition=" + holderPosition +
", holderId=" + holderId +
", transactionId=" + transactionId +
'}';
}
}

Datei anzeigen

@ -0,0 +1,45 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import lombok.Getter;
import lombok.Setter;
public class LecternContainer extends Container {
@Getter @Setter
private int currentBedrockPage = 0;
@Getter @Setter
private NbtMap blockEntityTag;
@Getter @Setter
private Vector3i position;
public LecternContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
}

Datei anzeigen

@ -0,0 +1,43 @@
/*
* Copyright (c) 2019-2021 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.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.entity.Entity;
@Getter
@Setter
public class MerchantContainer extends Container {
private Entity villager;
private VillagerTrade[] villagerTrades;
public MerchantContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
}

Datei anzeigen

@ -25,9 +25,11 @@
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
public class PlayerInventory extends Inventory {
@ -40,20 +42,36 @@ public class PlayerInventory extends Inventory {
private int heldItemSlot;
@Getter
private ItemStack cursor;
@NonNull
private GeyserItemStack cursor = GeyserItemStack.EMPTY;
public PlayerInventory() {
super(0, null, 46);
super(0, 46, null);
heldItemSlot = 0;
}
public void setCursor(ItemStack stack) {
if (stack != null && (stack.getId() == 0 || stack.getAmount() < 1))
stack = null;
cursor = stack;
public void setCursor(@NonNull GeyserItemStack newCursor, GeyserSession session) {
updateItemNetId(cursor, newCursor, session);
cursor = newCursor;
}
public ItemStack getItemInHand() {
public GeyserItemStack getItemInHand() {
if (36 + heldItemSlot > this.size) {
GeyserConnector.getInstance().getLogger().debug("Held item slot was larger than expected!");
return GeyserItemStack.EMPTY;
}
return items[36 + heldItemSlot];
}
public void setItemInHand(@NonNull GeyserItemStack item) {
if (36 + heldItemSlot > this.size) {
GeyserConnector.getInstance().getLogger().debug("Held item slot was larger than expected!");
return;
}
items[36 + heldItemSlot] = item;
}
public GeyserItemStack getOffhand() {
return items[45];
}
}

Datei anzeigen

@ -23,39 +23,32 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.session.cache;
package org.geysermc.connector.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
public class InventoryCache {
private GeyserSession session;
public class StonecutterContainer extends Container {
/**
* The button that has currently been pressed Java-side
*/
@Getter
@Setter
private Inventory openInventory;
private int stonecutterButton = -1;
@Getter
private Int2ObjectMap<Inventory> inventories = new Int2ObjectOpenHashMap<>();
public InventoryCache(GeyserSession session) {
this.session = session;
public StonecutterContainer(String title, int id, int size, WindowType windowType, PlayerInventory playerInventory) {
super(title, id, size, windowType, playerInventory);
}
public Inventory getPlayerInventory() {
return inventories.get(0);
}
public void cacheInventory(Inventory inventory) {
inventories.put(inventory.getId(), inventory);
}
public void uncacheInventory(int id) {
inventories.remove(id);
@Override
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
if (slot == 0 && newItem.getJavaId() != items[slot].getJavaId()) {
// The pressed stonecutter button output resets whenever the input item changes
this.stonecutterButton = -1;
}
super.setItem(slot, newItem, session);
}
}

Datei anzeigen

@ -36,6 +36,7 @@ import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -75,7 +76,7 @@ public class Metrics {
private final static ObjectMapper mapper = new ObjectMapper();
private GeyserConnector connector;
private final GeyserConnector connector;
/**
* Class constructor.
@ -156,6 +157,7 @@ public class Metrics {
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
String osVersion = System.getProperty("os.version");
String javaVersion = System.getProperty("java.version");
int coreCount = Runtime.getRuntime().availableProcessors();
ObjectNode data = mapper.createObjectNode();
@ -163,6 +165,7 @@ public class Metrics {
data.put("serverUUID", serverUUID);
data.put("playerAmount", playerAmount);
data.put("javaVersion", javaVersion);
data.put("osName", osName);
data.put("osArch", osArch);
data.put("osVersion", osVersion);
@ -241,7 +244,7 @@ public class Metrics {
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
gzip.write(str.getBytes("UTF-8"));
gzip.write(str.getBytes(StandardCharsets.UTF_8));
gzip.close();
return outputStream.toByteArray();
}

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.connector.network;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.v419.Bedrock_v419;
import com.nukkitx.protocol.bedrock.v422.Bedrock_v422;
import com.nukkitx.protocol.bedrock.v428.Bedrock_v428;
import java.util.ArrayList;
import java.util.List;
@ -40,9 +41,7 @@ public class BedrockProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC.toBuilder()
.minecraftVersion("1.16.201")
.build();
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v428.V428_CODEC;
/**
* A list of all supported Bedrock versions that can join Geyser
*/
@ -52,9 +51,10 @@ public class BedrockProtocol {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder()
.minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v422.V422_CODEC.toBuilder()
.minecraftVersion("1.16.200/1.16.201")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
}
/**

Datei anzeigen

@ -0,0 +1,95 @@
/*
* Copyright (c) 2019-2021 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.connector.network;
import java.net.InetAddress;
import java.net.UnknownHostException;
/*
* Taken & modified from TCPShield, licensed under MIT. See https://github.com/TCPShield/RealIP/blob/master/LICENSE
*
* https://github.com/TCPShield/RealIP/blob/32d422a9523cb6e25b571072851f3306bb8bbc4f/src/main/java/net/tcpshield/tcpshield/validation/cidr/CIDRMatcher.java
*/
public class CIDRMatcher {
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
public CIDRMatcher(String ipAddress) {
String[] split = ipAddress.split("/", 2);
String parsedIPAddress;
if (split.length == 2) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = ipAddress;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
public boolean matches(InetAddress inetAddress) {
// check if IP is IPv4 or IPv6
if (cidrAddress.getClass() != inetAddress.getClass()) {
return false;
}
// check for equality if it's a simple CIDR
if (simpleCIDR) {
return inetAddress.equals(cidrAddress);
}
byte[] inetAddressBytes = inetAddress.getAddress();
byte[] requiredAddressBytes = cidrAddress.getAddress();
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
for (int i = 0; i < maskBytes; i++) {
if (inetAddressBytes[i] != requiredAddressBytes[i]) {
return false;
}
}
if (finalByte != 0) {
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
}
return true;
}
}

Datei anzeigen

@ -40,8 +40,18 @@ import org.geysermc.connector.utils.LanguageUtils;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
/*
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 = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length;
private static final int BRAND_BYTES_LENGTH = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8).length;
/**
* The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length.
*/
private static final int MAGIC_RAKNET_LENGTH = 338;
private final GeyserConnector connector;
@ -51,6 +61,21 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
@Override
public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
List<String> allowedProxyIPs = connector.getConfig().getBedrock().getProxyProtocolWhitelistedIPs();
if (connector.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) {
boolean isWhitelistedIP = false;
for (CIDRMatcher matcher : connector.getConfig().getBedrock().getWhitelistedIPsMatchers()) {
if (matcher.matches(inetSocketAddress.getAddress())) {
isWhitelistedIP = true;
break;
}
}
if (!isWhitelistedIP) {
return false;
}
}
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress));
return true;
}
@ -69,16 +94,16 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
BedrockPong pong = new BedrockPong();
pong.setEdition("MCPE");
pong.setGameType("Default");
pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59
pong.setNintendoLimited(false);
pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion());
pong.setVersion(null); // Server tries to connect either way and it looks better
pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
pong.setIpv4Port(config.getBedrock().getPort());
if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
String mainMotd = motd[0]; // First line of the motd.
String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank.
String subMotd = (motd.length != 1) ? motd[1] : GeyserConnector.NAME; // Second line of the motd if present, otherwise default.
pong.setMotd(mainMotd.trim());
pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
@ -95,15 +120,28 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setMaximumPlayerCount(config.getMaxPlayers());
}
// Fallbacks to prevent errors and allow Bedrock to see the server
if (pong.getMotd() == null || pong.getMotd().trim().isEmpty()) {
pong.setMotd(GeyserConnector.NAME);
}
if (pong.getSubMotd() == null || pong.getSubMotd().trim().isEmpty()) {
// Sub-MOTD cannot be empty as of 1.16.210.59
pong.setSubMotd(GeyserConnector.NAME);
}
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
// Remove the sub-MOTD first since that only appears locally
pong.setSubMotd("");
if (motdArray.length > 338) {
int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length;
if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) {
// Shorten the sub-MOTD first since that only appears locally
if (subMotdLength > BRAND_BYTES_LENGTH) {
pong.setSubMotd(GeyserConnector.NAME);
subMotdLength = BRAND_BYTES_LENGTH;
}
if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) {
// If the top MOTD is still too long, we chop it down
byte[] newMotdArray = new byte[339];
byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength];
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
}

Datei anzeigen

@ -64,7 +64,7 @@ public class QueryPacketHandler {
* @param buffer The Query data
*/
public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) {
if(!isQueryPacket(buffer))
if (!isQueryPacket(buffer))
return;
this.connector = connector;
@ -225,7 +225,7 @@ public class QueryPacketHandler {
query.write(new byte[] { 0x00, 0x00 });
// Fill player names
if(pingInfo != null) {
if (pingInfo != null) {
for (String username : pingInfo.getPlayerList()) {
query.write(username.getBytes());
query.write((byte) 0x00);

Datei anzeigen

@ -26,15 +26,18 @@
package org.geysermc.connector.network;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.data.ResourcePackType;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.data.ResourcePackType;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.v428.Bedrock_v428;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.AdvancementsCache;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_100;
import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LoginEncryptionUtils;
import org.geysermc.connector.utils.MathUtils;
@ -72,6 +75,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
session.getUpstream().getSession().setPacketCodec(packetCodec);
// Set the block translation based off of version
session.setBlockTranslator(packetCodec.getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion()
? BlockTranslator1_16_210.INSTANCE : BlockTranslator1_16_100.INSTANCE);
LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket);
PlayStatusPacket playStatus = new PlayStatusPacket();
@ -94,7 +101,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
public boolean handle(ResourcePackClientResponsePacket packet) {
switch (packet.getStatus()) {
case COMPLETED:
session.connect(connector.getRemoteServer());
session.connect();
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName()));
break;
@ -169,7 +176,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
LanguageUtils.loadGeyserLocale(session.getLocale());
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) {
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) {
// TODO it is safer to key authentication on something that won't change (UUID, not username)
if (!couldLoginUserByName(session.getAuthData().getName())) {
LoginEncryptionUtils.buildAndShowLoginWindow(session);

Datei anzeigen

@ -36,12 +36,13 @@ import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponsePacket;
import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.Client;
@ -57,24 +58,27 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.player.SessionPlayerEntity;
import org.geysermc.connector.entity.player.SkullPlayerEntity;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.remote.RemoteServer;
import org.geysermc.connector.network.session.auth.AuthData;
import org.geysermc.connector.network.session.auth.BedrockClientData;
import org.geysermc.connector.network.session.cache.*;
@ -83,8 +87,10 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.skin.FloodgateSkinUploader;
import org.geysermc.connector.skin.SkinManager;
import org.geysermc.connector.utils.*;
import org.geysermc.cumulus.Form;
@ -92,47 +98,83 @@ import org.geysermc.cumulus.util.FormBuilder;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.BedrockData;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@Getter
public class GeyserSession implements CommandSender {
private final GeyserConnector connector;
private final UpstreamSession upstream;
private RemoteServer remoteServer;
private Client downstream;
@Setter
private AuthData authData;
@Setter
private BedrockClientData clientData;
/* Setter for GeyserConnect */
@Setter
private String remoteAddress;
@Setter
private int remotePort;
@Setter
private AuthType remoteAuthType;
/* Setter for GeyserConnect */
@Deprecated
@Setter
private boolean microsoftAccount;
private final SessionPlayerEntity playerEntity;
private PlayerInventory inventory;
private AdvancementsCache advancementsCache;
private BookEditCache bookEditCache;
private ChunkCache chunkCache;
private EntityCache entityCache;
private EntityEffectCache effectCache;
private InventoryCache inventoryCache;
private WorldCache worldCache;
private FormCache formCache;
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
private final PlayerInventory playerInventory;
@Setter
private Inventory openInventory;
@Setter
private boolean closingInventory;
@Setter
private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
/**
* Use {@link #getNextItemNetId()} instead for consistency
*/
@Getter(AccessLevel.NONE)
private final AtomicInteger itemNetId = new AtomicInteger(2);
@Getter(AccessLevel.NONE)
private final Object inventoryLock = new Object();
@Getter(AccessLevel.NONE)
private CompletableFuture<Void> inventoryFuture;
@Setter
private ScheduledFuture<?> craftingGridFuture;
/**
* Stores session collision
*/
private final CollisionManager collisionManager;
/**
* Stores the block translations for this specific version.
*/
@Setter
private BlockTranslator blockTranslator;
private final Map<Vector3i, SkullPlayerEntity> skullCache = new ConcurrentHashMap<>();
private final Long2ObjectMap<ClientboundMapItemDataPacket> storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
@ -142,6 +184,16 @@ public class GeyserSession implements CommandSender {
*/
private final Object2LongMap<Vector3i> itemFrameCache = new Object2LongOpenHashMap<>();
/**
* Stores a list of all lectern locations and their block entity tags.
* See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
* for more information.
*/
private final Set<Vector3i> lecternCache = new ObjectOpenHashSet<>();
@Setter
private boolean droppingLecternBook;
@Setter
private Vector2i lastChunkPosition = null;
private int renderDistance;
@ -167,6 +219,9 @@ public class GeyserSession implements CommandSender {
@Setter
private boolean sprinting;
/**
* Not updated if cache chunks is enabled.
*/
@Setter
private boolean jumping;
@ -195,26 +250,36 @@ public class GeyserSession implements CommandSender {
* Initialized as (0, 0, 0) so it is always not-null.
*/
@Setter
private Vector3i lastInteractionPosition = Vector3i.ZERO;
private Vector3i lastInteractionBlockPosition = Vector3i.ZERO;
/**
* Stores the position of the player the last time they interacted.
* Used to verify that the player did not move since their last interaction. <br>
* Initialized as (0, 0, 0) so it is always not-null.
*/
@Setter
private Vector3f lastInteractionPlayerPosition = Vector3f.ZERO;
@Setter
private Entity ridingVehicleEntity;
/**
* The entity that the client is currently looking at.
*/
@Setter
private int craftSlot = 0;
private Entity mouseoverEntity;
@Setter
private long lastWindowCloseTime = 0;
@Setter
private VillagerTrade[] villagerTrades;
@Setter
private long lastInteractedVillagerEid;
private Int2ObjectMap<Recipe> craftingRecipes;
private final Set<String> unlockedRecipes;
private final AtomicInteger lastRecipeNetId;
/**
* Stores the enchantment information the client has received if they are in an enchantment table GUI
* Saves a list of all stonecutter recipes, for use in a stonecutter inventory.
* The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier
*/
private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3];
@Setter
private Int2ObjectMap<IntList> stonecutterRecipes;
/**
* The current attack speed of the player. Used for sending proper cooldown timings.
@ -350,35 +415,43 @@ public class GeyserSession implements CommandSender {
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.effectCache = new EntityEffectCache();
this.inventoryCache = new InventoryCache(this);
this.worldCache = new WorldCache(this);
this.formCache = new FormCache(this);
this.collisionManager = new CollisionManager(this);
this.playerEntity = new SessionPlayerEntity(this);
this.inventory = new PlayerInventory();
this.playerInventory = new PlayerInventory();
this.openInventory = null;
this.inventoryFuture = CompletableFuture.completedFuture(null);
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.unlockedRecipes = new ObjectOpenHashSet<>();
this.lastRecipeNetId = new AtomicInteger(1);
this.spawned = false;
this.loggedIn = false;
this.inventoryCache.getInventories().put(0, inventory);
// Make a copy to prevent ConcurrentModificationException
final List<GeyserSession> tmpPlayers = new ArrayList<>(connector.getPlayers());
tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason));
InetAddress address = bedrockServerSession.getRealAddress().getAddress();
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason));
disconnect(disconnectReason.name());
connector.removePlayer(this);
});
}
public void connect(RemoteServer remoteServer) {
/**
* Send all necessary packets to load Bedrock into the server
*/
public void connect() {
startGame();
this.remoteServer = remoteServer;
this.remoteAddress = connector.getConfig().getRemote().getAddress();
this.remotePort = connector.getConfig().getRemote().getPort();
this.remoteAuthType = connector.getDefaultAuthType();
// Set the hardcoded shield ID to the ID we just defined in StartGamePacket
upstream.getSession().getHardcodedBlockingId().set(ItemRegistry.SHIELD.getBedrockId());
@ -423,8 +496,8 @@ public class GeyserSession implements CommandSender {
}
public void login() {
if (connector.getAuthType() != AuthType.ONLINE) {
if (connector.getAuthType() == AuthType.OFFLINE) {
if (this.remoteAuthType != AuthType.ONLINE) {
if (this.remoteAuthType == AuthType.OFFLINE) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.offline"));
} else {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.floodgate"));
@ -533,18 +606,21 @@ public class GeyserSession implements CommandSender {
* After getting whatever credentials needed, we attempt to join the Java server.
*/
private void connectDownstream() {
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE;
// Start ticking
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
downstream = new Client(this.remoteAddress, this.remotePort, protocol, new TcpSessionFactory());
disableSrvResolving();
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
}
// Let Geyser handle sending the keep alive
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
if (connector.getConfig().isForwardPlayerPing()) {
// Let Geyser handle sending the keep alive
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
}
downstream.getSession().addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
@ -553,7 +629,9 @@ public class GeyserSession implements CommandSender {
byte[] encryptedData;
try {
FloodgateSkinUploader skinUploader = connector.getSkinUploader();
FloodgateCipher cipher = connector.getCipher();
encryptedData = cipher.encryptFromString(BedrockData.of(
clientData.getGameVersion(),
authData.getName(),
@ -562,7 +640,9 @@ public class GeyserSession implements CommandSender {
clientData.getLanguageCode(),
clientData.getUiProfile().ordinal(),
clientData.getCurrentInputMode().ordinal(),
upstream.getSession().getAddress().getAddress().getHostAddress()
upstream.getAddress().getAddress().getHostAddress(),
skinUploader.getId(),
skinUploader.getVerifyCode()
).toString());
} catch (Exception e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
@ -570,13 +650,7 @@ public class GeyserSession implements CommandSender {
return;
}
byte[] rawSkin = clientData.getAndTransformImage("Skin").encode();
byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1];
System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length);
finalData[encryptedData.length] = 0x21; // splitter
System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length);
String finalDataString = new String(finalData, StandardCharsets.UTF_8);
String finalDataString = new String(encryptedData, StandardCharsets.UTF_8);
HandshakePacket handshakePacket = event.getPacket();
event.setPacket(new HandshakePacket(
@ -597,7 +671,7 @@ public class GeyserSession implements CommandSender {
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
return;
}
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteAddress));
playerEntity.setUuid(protocol.getProfile().getId());
playerEntity.setUsername(protocol.getProfile().getName());
@ -618,7 +692,7 @@ public class GeyserSession implements CommandSender {
public void disconnected(DisconnectedEvent event) {
loggingIn = false;
loggedIn = false;
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteAddress, event.getReason()));
if (event.getCause() != null) {
event.getCause().printStackTrace();
}
@ -636,9 +710,13 @@ public class GeyserSession implements CommandSender {
playerEntity.setUuid(profile.getId());
// Check if they are not using a linked account
if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
if (remoteAuthType == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
SkinManager.handleBedrockSkin(playerEntity, clientData);
}
// We'll send the skin upload a bit after the handshake packet (aka this packet),
// because otherwise the global server returns the data too fast.
getAuthData().upload(connector);
}
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
@ -683,7 +761,6 @@ public class GeyserSession implements CommandSender {
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.formCache = null;
closed = true;
@ -723,6 +800,18 @@ public class GeyserSession implements CommandSender {
this.sneaking = sneaking;
collisionManager.updatePlayerBoundingBox();
collisionManager.updateScaffoldingFlags();
if (mouseoverEntity != null) {
// Horses, etc can change their property depending on if you're sneaking
InteractiveTagManager.updateTag(this, mouseoverEntity);
}
}
/**
* Will be overwritten for GeyserConnect.
*/
protected void disableSrvResolving() {
this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false);
}
@Override
@ -823,10 +912,58 @@ public class GeyserSession implements CommandSender {
startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
startGamePacket.setVanillaVersion("*");
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
startGamePacket.setInventoriesServerAuthoritative(true);
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped
SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings();
settings.setMovementMode(AuthoritativeMovementMode.CLIENT);
settings.setRewindHistorySize(0);
settings.setServerAuthoritativeBlockBreaking(false);
startGamePacket.setPlayerMovementSettings(settings);
upstream.sendPacket(startGamePacket);
}
/**
* Adds a new inventory task.
* Inventory tasks are executed one at a time, in order.
*
* @param task the task to run
*/
public void addInventoryTask(Runnable task) {
synchronized (inventoryLock) {
inventoryFuture = inventoryFuture.thenRun(task).exceptionally(throwable -> {
GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
return null;
});
}
}
/**
* Adds a new inventory task with a delay.
* The delay is achieved by scheduling with the Geyser general thread pool.
* Inventory tasks are executed one at a time, in order.
*
* @param task the delayed task to run
* @param delayMillis delay in milliseconds
*/
public void addInventoryTask(Runnable task, long delayMillis) {
synchronized (inventoryLock) {
Executor delayedExecutor = command -> GeyserConnector.getInstance().getGeneralThreadPool().schedule(command, delayMillis, TimeUnit.MILLISECONDS);
inventoryFuture = inventoryFuture.thenRunAsync(task, delayedExecutor).exceptionally(throwable -> {
GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
return null;
});
}
}
/**
* @return the next Bedrock item network ID to use for a new item
*/
public int getNextItemNetId() {
return itemNetId.getAndIncrement();
}
public void addTeleport(TeleportCache teleportCache) {
teleportMap.put(teleportCache.getTeleportConfirmId(), teleportCache);
@ -931,7 +1068,7 @@ public class GeyserSession implements CommandSender {
* @param packet the java edition packet from MCProtocolLib
*/
public void sendDownstreamPacket(Packet packet) {
if (downstream != null && downstream.getSession() != null && protocol.getSubProtocol().equals(SubProtocol.GAME)) {
if (downstream != null && downstream.getSession() != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) {
downstream.getSession().send(packet);
} else {
connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server");

Datei anzeigen

@ -61,6 +61,6 @@ public class UpstreamSession {
}
public InetSocketAddress getAddress() {
return session.getAddress();
return session.getRealAddress();
}
}

Datei anzeigen

@ -25,16 +25,26 @@
package org.geysermc.connector.network.session.auth;
import lombok.AllArgsConstructor;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.connector.GeyserConnector;
import java.util.UUID;
@Getter
@AllArgsConstructor
@RequiredArgsConstructor
public class AuthData {
@Getter private final String name;
@Getter private final UUID UUID;
@Getter private final String xboxUUID;
private String name;
private UUID UUID;
private String xboxUUID;
private final JsonNode certChainData;
private final String clientData;
public void upload(GeyserConnector connector) {
// we can't upload the skin in LoginEncryptionUtil since the global server would return
// the skin too fast, that's why we upload it after we know for sure that the target server
// is ready to handle the result of the global server
connector.getSkinUploader().uploadSkin(certChainData, clientData);
}
}

Datei anzeigen

@ -34,10 +34,8 @@ import lombok.Getter;
import org.geysermc.connector.skin.SkinProvider;
import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile;
import java.awt.image.BufferedImage;
import java.util.Base64;
import java.util.UUID;
@ -114,26 +112,8 @@ public final class BedrockClientData {
private String skinColor;
@JsonProperty(value = "ThirdPartyNameOnly")
private boolean thirdPartyNameOnly;
private static RawSkin getLegacyImage(byte[] imageData, boolean alex) {
if (imageData == null) {
return null;
}
// width * height * 4 (rgba)
switch (imageData.length) {
case 8192:
return new RawSkin(64, 32, imageData, alex);
case 16384:
return new RawSkin(64, 64, imageData, alex);
case 32768:
return new RawSkin(64, 128, imageData, alex);
case 65536:
return new RawSkin(128, 128, imageData, alex);
default:
throw new IllegalArgumentException("Unknown legacy skin size");
}
}
@JsonProperty(value = "PlayFabId")
private String playFabId;
public void setJsonData(JsonNode data) {
if (this.jsonData == null && data != null) {
@ -141,51 +121,6 @@ public final class BedrockClientData {
}
}
/**
* Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java<br>
* Internally only used for Skins, but can be used for Capes too
*/
public RawSkin getImage(String name) {
if (jsonData == null || !jsonData.has(name + "Data")) {
return null;
}
boolean alex = false;
if (name.equals("Skin")) {
alex = isAlex();
}
byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText());
if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) {
return new RawSkin(
jsonData.get(name + "ImageWidth").asInt(),
jsonData.get(name + "ImageHeight").asInt(),
image, alex
);
}
return getLegacyImage(image, alex);
}
public RawSkin getAndTransformImage(String name) {
RawSkin skin = getImage(name);
if (skin != null && (skin.width > 64 || skin.height > 64)) {
BufferedImage scaledImage =
SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height);
int max = Math.max(skin.width, skin.height);
while (max > 64) {
max /= 2;
scaledImage = SkinProvider.scale(scaledImage);
}
byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage);
skin.width = scaledImage.getWidth();
skin.height = scaledImage.getHeight();
skin.data = skinData;
}
return skin;
}
public boolean isAlex() {
try {
byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8));

Datei anzeigen

@ -25,9 +25,9 @@
package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
import lombok.Setter;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
@ -63,8 +63,8 @@ public class BookEditCache {
return;
}
// Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions
ItemStack itemStack = session.getInventory().getItemInHand();
if (itemStack == null || itemStack.getId() != ItemRegistry.WRITABLE_BOOK.getJavaId()) {
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
if (itemStack == null || itemStack.getJavaId() != ItemRegistry.WRITABLE_BOOK.getJavaId()) {
packet = null;
return;
}

Datei anzeigen

@ -29,23 +29,24 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import org.geysermc.connector.bootstrap.GeyserBootstrap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.MathUtils;
public class ChunkCache {
private static final int MINIMUM_WORLD_HEIGHT = 0;
private final boolean cache;
private final Long2ObjectMap<Column> chunks = new Long2ObjectOpenHashMap<>();
private final Long2ObjectMap<Column> chunks;
public ChunkCache(GeyserSession session) {
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
this.cache = session.getConnector().getConfig().isCacheChunks();
} else {
if (session.getConnector().getWorldManager().hasOwnChunkCache()) {
this.cache = false; // To prevent Spigot from initializing
} else {
this.cache = session.getConnector().getConfig().isCacheChunks();
}
chunks = cache ? new Long2ObjectOpenHashMap<>() : null;
}
public Column addToCache(Column chunk) {
@ -86,6 +87,11 @@ public class ChunkCache {
return;
}
if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) {
// Y likely goes above or below the height limit of this world
return;
}
Chunk chunk = column.getChunks()[y >> 4];
if (chunk != null) {
chunk.set(x & 0xF, y & 0xF, z & 0xF, block);
@ -102,6 +108,11 @@ public class ChunkCache {
return BlockTranslator.JAVA_AIR_ID;
}
if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) {
// Y likely goes above or below the height limit of this world
return BlockTranslator.JAVA_AIR_ID;
}
Chunk chunk = column.getChunks()[y >> 4];
if (chunk != null) {
return chunk.get(x & 0xF, y & 0xF, z & 0xF);

Datei anzeigen

@ -38,7 +38,7 @@ import java.io.InputStream;
*/
public class EntityIdentifierRegistry {
public static NbtMap ENTITY_IDENTIFIERS;
public static final NbtMap ENTITY_IDENTIFIERS;
private EntityIdentifierRegistry() {
}

Datei anzeigen

@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdate
import com.github.steveice10.packetlib.packet.Packet;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.geysermc.common.PlatformType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.FileUtils;
@ -89,12 +90,15 @@ public class PacketTranslatorRegistry<T> {
public <P extends T> boolean translate(Class<? extends P> clazz, P packet, GeyserSession session) {
if (!session.getUpstream().isClosed() && !session.isClosed()) {
try {
if (translators.containsKey(clazz)) {
((PacketTranslator<P>) translators.get(clazz)).translate(packet, session);
PacketTranslator<P> translator = (PacketTranslator<P>) translators.get(clazz);
if (translator != null) {
translator.translate(packet, session);
return true;
} else {
if (!IGNORED_PACKETS.contains(clazz))
if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) {
// Other debug logs already take care of Bedrock packets for us if on standalone
GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet));
}
}
} catch (Throwable ex) {
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex);

Datei anzeigen

@ -26,17 +26,16 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.protocol.bedrock.packet.BookEditPacket;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import java.util.Collections;
import java.util.LinkedList;
@ -47,58 +46,55 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
@Override
public void translate(BookEditPacket packet, GeyserSession session) {
ItemStack itemStack = session.getInventory().getItemInHand();
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
if (itemStack != null) {
CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag("");
ItemStack bookItem = new ItemStack(itemStack.getId(), itemStack.getAmount(), tag);
ItemStack bookItem = new ItemStack(itemStack.getJavaId(), itemStack.getAmount(), tag);
List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
int page = packet.getPageNumber();
// Creative edits the NBT for us
if (session.getGameMode() != GameMode.CREATIVE) {
switch (packet.getAction()) {
case ADD_PAGE: {
switch (packet.getAction()) {
case ADD_PAGE: {
// Add empty pages in between
for (int i = pages.size(); i < page; i++) {
pages.add(i, new StringTag("", ""));
}
pages.add(page, new StringTag("", packet.getText()));
break;
}
// Called whenever a page is modified
case REPLACE_PAGE: {
if (page < pages.size()) {
pages.set(page, new StringTag("", packet.getText()));
} else {
// Add empty pages in between
for (int i = pages.size(); i < page; i++) {
pages.add(i, new StringTag("", ""));
}
pages.add(page, new StringTag("", packet.getText()));
break;
}
// Called whenever a page is modified
case REPLACE_PAGE: {
if (page < pages.size()) {
pages.set(page, new StringTag("", packet.getText()));
} else {
// Add empty pages in between
for (int i = pages.size(); i < page; i++) {
pages.add(i, new StringTag("", ""));
}
pages.add(page, new StringTag("", packet.getText()));
}
break;
}
case DELETE_PAGE: {
if (page < pages.size()) {
pages.remove(page);
}
break;
}
case SWAP_PAGES: {
int page2 = packet.getSecondaryPageNumber();
if (page < pages.size() && page2 < pages.size()) {
Collections.swap(pages, page, page2);
}
break;
}
case SIGN_BOOK: {
tag.put(new StringTag("author", packet.getAuthor()));
tag.put(new StringTag("title", packet.getTitle()));
break;
}
default:
return;
break;
}
case DELETE_PAGE: {
if (page < pages.size()) {
pages.remove(page);
}
break;
}
case SWAP_PAGES: {
int page2 = packet.getSecondaryPageNumber();
if (page < pages.size() && page2 < pages.size()) {
Collections.swap(pages, page, page2);
}
break;
}
case SIGN_BOOK: {
tag.put(new StringTag("author", packet.getAuthor()));
tag.put(new StringTag("title", packet.getTitle()));
break;
}
default:
return;
}
// Remove empty pages at the end
while (pages.size() > 0) {
@ -110,10 +106,10 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
}
}
tag.put(new ListTag("pages", pages));
session.getInventory().setItem(36 + session.getInventory().getHeldItemSlot(), bookItem);
InventoryTranslator.INVENTORY_TRANSLATORS.get(null).updateInventory(session, session.getInventory());
session.getPlayerInventory().setItem(36 + session.getPlayerInventory().getHeldItemSlot(), GeyserItemStack.from(bookItem), session);
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
session.getBookEditCache().setPacket(new ClientEditBookPacket(bookItem, packet.getAction() == BookEditPacket.Action.SIGN_BOOK, session.getInventory().getHeldItemSlot()));
session.getBookEditCache().setPacket(new ClientEditBookPacket(bookItem, packet.getAction() == BookEditPacket.Action.SIGN_BOOK, session.getPlayerInventory().getHeldItemSlot()));
// There won't be any more book updates after this, so we can try sending the edit packet immediately
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
session.getBookEditCache().checkForSend();

Datei anzeigen

@ -28,6 +28,7 @@ package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.MerchantContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -38,24 +39,29 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
@Override
public void translate(ContainerClosePacket packet, GeyserSession session) {
session.setLastWindowCloseTime(0);
byte windowId = packet.getId();
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (windowId == -1) { //player inventory or crafting table
if (openInventory != null) {
windowId = (byte) openInventory.getId();
} else {
windowId = 0;
session.addInventoryTask(() -> {
byte windowId = packet.getId();
//Client wants close confirmation
session.sendUpstreamPacket(packet);
session.setClosingInventory(false);
if (windowId == -1 && session.getOpenInventory() instanceof MerchantContainer) {
// 1.16.200 - window ID is always -1 sent from Bedrock
windowId = (byte) session.getOpenInventory().getId();
}
}
if (windowId == 0 || (openInventory != null && openInventory.getId() == windowId)) {
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.getDownstream().getSession().send(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId);
}
//Client wants close confirmation
session.sendUpstreamPacket(packet);
Inventory openInventory = session.getOpenInventory();
if (openInventory != null) {
if (windowId == openInventory.getId()) {
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId, false);
} else if (openInventory.isPending()) {
InventoryUtils.displayInventory(session, openInventory);
openInventory.setPending(false);
}
}
});
}
}

Datei anzeigen

@ -25,7 +25,10 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket;
import com.nukkitx.protocol.bedrock.packet.FilterTextPacket;
import org.geysermc.connector.inventory.AnvilContainer;
import org.geysermc.connector.inventory.CartographyContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -39,7 +42,17 @@ public class BedrockFilterTextTranslator extends PacketTranslator<FilterTextPack
@Override
public void translate(FilterTextPacket packet, GeyserSession session) {
if (session.getOpenInventory() instanceof CartographyContainer) {
// We don't want to be able to rename in the cartography table
return;
}
packet.setFromServer(true);
session.sendUpstreamPacket(packet);
if (session.getOpenInventory() instanceof AnvilContainer) {
// Java Edition sends a packet every time an item is renamed even slightly in GUI. Fortunately, this works out for us now
ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(packet.getText());
session.sendDownstreamPacket(renameItemPacket);
}
}
}

Datei anzeigen

@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
@ -43,23 +42,22 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import com.nukkitx.protocol.bedrock.packet.*;
import org.geysermc.connector.entity.CommandBlockMinecartEntity;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.concurrent.TimeUnit;
@ -82,15 +80,35 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
switch (packet.getTransactionType()) {
case NORMAL:
Inventory inventory = session.getInventoryCache().getOpenInventory();
if (inventory == null) inventory = session.getInventory();
InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()).translateActions(session, inventory, packet.getActions());
if (packet.getActions().size() == 2) {
InventoryActionData worldAction = packet.getActions().get(0);
InventoryActionData containerAction = packet.getActions().get(1);
if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
&& worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
session.addInventoryTask(() -> {
if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot() ||
session.getPlayerInventory().getItemInHand().isEmpty()) {
return;
}
boolean dropAll = worldAction.getToItem().getCount() > 1;
ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket(
dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
BlockUtils.POSITION_ZERO,
BlockFace.DOWN
);
session.sendDownstreamPacket(dropAllPacket);
if (dropAll) {
session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY);
} else {
session.getPlayerInventory().getItemInHand().sub(1);
}
});
}
}
break;
case INVENTORY_MISMATCH:
Inventory inv = session.getInventoryCache().getOpenInventory();
if (inv == null) inv = session.getInventory();
InventoryTranslator.INVENTORY_TRANSLATORS.get(inv.getWindowType()).updateInventory(session, inv);
InventoryUtils.updateCursor(session);
break;
case ITEM_USE:
switch (packet.getActionType()) {
@ -98,8 +116,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// Check to make sure the client isn't spamming interaction
// Based on Nukkit 1.0, with changes to ensure holding down still works
boolean hasAlreadyClicked = System.currentTimeMillis() - session.getLastInteractionTime() < 110.0 &&
packet.getBlockPosition().distanceSquared(session.getLastInteractionPosition()) < 0.00001;
session.setLastInteractionPosition(packet.getBlockPosition());
packet.getBlockPosition().distanceSquared(session.getLastInteractionBlockPosition()) < 0.00001;
session.setLastInteractionBlockPosition(packet.getBlockPosition());
session.setLastInteractionPlayerPosition(session.getPlayerEntity().getPosition());
if (hasAlreadyClicked) {
break;
} else {
@ -108,7 +127,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}
// Bedrock sends block interact code for a Java entity so we send entity code back to Java
if (BlockTranslator.isItemFrame(packet.getBlockRuntimeId()) &&
if (session.getBlockTranslator().isItemFrame(packet.getBlockRuntimeId()) &&
session.getEntityCache().getEntityByJavaId(ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition())) != null) {
Vector3f vector = packet.getClickPosition();
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()),
@ -192,7 +211,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
if (packet.getActions().isEmpty()) {
if (session.getOpPermissionLevel() >= 2 && session.getGameMode() == GameMode.CREATIVE) {
// Otherwise insufficient permissions
int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId());
int blockState = session.getBlockTranslator().getJavaBlockState(packet.getBlockRuntimeId());
String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, "");
// In the future this can be used for structure blocks too, however not all elements
// are available in each GUI
@ -215,9 +234,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.setInteracting(true);
break;
case 1:
ItemStack shieldSlot = session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36);
// Handled in Entity.java
if (shieldSlot != null && shieldSlot.getId() == ItemRegistry.SHIELD.getJavaId()) {
if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
break;
}
@ -253,7 +271,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
LevelEventPacket blockBreakPacket = new LevelEventPacket();
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
blockBreakPacket.setData(session.getBlockTranslator().getBedrockBlockId(blockState));
session.sendUpstreamPacket(blockBreakPacket);
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
@ -274,7 +292,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
case ITEM_RELEASE:
if (packet.getActionType() == 0) {
// Followed to the Minecraft Protocol specification outlined at wiki.vg
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0,0,0),
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, BlockUtils.POSITION_ZERO,
BlockFace.DOWN);
session.sendDownstreamPacket(releaseItemPacket);
}
@ -308,10 +326,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendDownstreamPacket(interactAtPacket);
EntitySoundInteractionHandler.handleEntityInteraction(session, vector, entity);
if (entity instanceof AbstractMerchantEntity) {
session.setLastInteractedVillagerEid(packet.getRuntimeEntityId());
}
break;
case 1: //Attack
if (entity.getEntityType() == EntityType.ENDER_DRAGON) {
@ -342,14 +356,14 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(blockPos);
updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(javaBlockState));
updateBlockPacket.setRuntimeId(session.getBlockTranslator().getBedrockBlockId(javaBlockState));
updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(updateBlockPacket);
UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket();
updateWaterPacket.setDataLayer(1);
updateWaterPacket.setBlockPosition(blockPos);
updateWaterPacket.setRuntimeId(BlockTranslator.isWaterlogged(javaBlockState) ? BlockTranslator.BEDROCK_WATER_ID : BlockTranslator.BEDROCK_AIR_ID);
updateWaterPacket.setRuntimeId(BlockTranslator.isWaterlogged(javaBlockState) ? session.getBlockTranslator().getBedrockWaterId() : session.getBlockTranslator().getBedrockAirId());
updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(updateWaterPacket);

Datei anzeigen

@ -28,20 +28,23 @@ package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.packet.ItemFrameDropItemPacket;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
/**
* Pre-1.16.210: used for both survival and creative item frame item removal
*
* 1.16.210: only used in creative.
*/
@Translator(packet = ItemFrameDropItemPacket.class)
public class BedrockItemFrameDropItemTranslator extends PacketTranslator<ItemFrameDropItemPacket> {
@Override
public void translate(ItemFrameDropItemPacket packet, GeyserSession session) {
Vector3i position = Vector3i.from(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, position),
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()),
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamPacket(interactPacket);
}

Datei anzeigen

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2021 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.connector.network.translators.bedrock;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
/**
* The packet sent for server-authoritative-style inventory transactions.
*/
@Translator(packet = ItemStackRequestPacket.class)
public class BedrockItemStackRequestTranslator extends PacketTranslator<ItemStackRequestPacket> {
@Override
public void translate(ItemStackRequestPacket packet, GeyserSession session) {
Inventory inventory = session.getOpenInventory();
if (inventory == null)
return;
InventoryTranslator translator = session.getInventoryTranslator();
session.addInventoryTask(() -> translator.translateRequests(session, inventory, packet.getRequests()));
}
}

Datei anzeigen

@ -0,0 +1,98 @@
/*
* Copyright (c) 2019-2021 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.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
import com.nukkitx.protocol.bedrock.packet.LecternUpdatePacket;
import org.geysermc.connector.inventory.LecternContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.InventoryUtils;
/**
* Used to translate moving pages, or closing the inventory
*/
@Translator(packet = LecternUpdatePacket.class)
public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpdatePacket> {
@Override
public void translate(LecternUpdatePacket packet, GeyserSession session) {
if (packet.isDroppingBook()) {
// Bedrock drops the book outside of the GUI. Java drops it in the GUI
// So, we enter the GUI and then drop it! :)
session.setDroppingLecternBook(true);
// Emulate an interact packet
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
BlockFace.values()[0],
Hand.MAIN_HAND,
0, 0, 0, // Java doesn't care about these when dealing with a lectern
false);
session.sendDownstreamPacket(blockPacket);
} else {
// Bedrock wants to either move a page or exit
if (!(session.getOpenInventory() instanceof LecternContainer)) {
session.getConnector().getLogger().debug("Expected lectern but it wasn't open!");
return;
}
LecternContainer lecternContainer = (LecternContainer) session.getOpenInventory();
if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) {
// The same page means Bedrock is closing the window
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId());
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, lecternContainer.getId(), false);
} else {
// Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages)
// Each "page" on Java is just one page (think a spiral notebook folded back to only show one page)
int newJavaPage = (packet.getPage() * 2);
int currentJavaPage = (lecternContainer.getCurrentBedrockPage() * 2);
// Send as many click button packets as we need to
// Java has the option to specify exact page numbers by adding 100 to the number, but buttonId variable
// is a byte when transmitted over the network and therefore this stops us at 128
if (newJavaPage > currentJavaPage) {
for (int i = currentJavaPage; i < newJavaPage; i++) {
ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 2);
session.sendDownstreamPacket(clickButtonPacket);
}
} else {
for (int i = currentJavaPage; i > newJavaPage; i--) {
ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 1);
session.sendDownstreamPacket(clickButtonPacket);
}
}
}
}
}
}

Datei anzeigen

@ -25,14 +25,19 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerChangeHeldItemPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.packet.MobEquipmentPacket;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.CooldownUtils;
import org.geysermc.connector.utils.InteractiveTagManager;
import java.util.concurrent.TimeUnit;
@Translator(packet = MobEquipmentPacket.class)
public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipmentPacket> {
@ -40,7 +45,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
@Override
public void translate(MobEquipmentPacket packet, GeyserSession session) {
if (!session.isSpawned() || packet.getHotbarSlot() > 8 ||
packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
// For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention
return;
}
@ -48,12 +53,25 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
// Send book update before switching hotbar slot
session.getBookEditCache().checkForSend();
session.getInventory().setHeldItemSlot(packet.getHotbarSlot());
session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot());
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
session.sendDownstreamPacket(changeHeldItemPacket);
if (session.isSneaking() && session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
// Activate shield since we are already sneaking
// (No need to send a release item packet - Java doesn't do this when swapping items)
// Required to do it a tick later or else it doesn't register
session.getConnector().getGeneralThreadPool().schedule(() -> session.sendDownstreamPacket(new ClientPlayerUseItemPacket(Hand.MAIN_HAND)),
50, TimeUnit.MILLISECONDS);
}
// Java sends a cooldown indicator whenever you switch an item
CooldownUtils.sendCooldown(session);
// Update the interactive tag, if an entity is present
if (session.getMouseoverEntity() != null) {
InteractiveTagManager.updateTag(session, session.getMouseoverEntity());
}
}
}

Datei anzeigen

@ -40,7 +40,7 @@ import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* Used to send the keep alive packet back to the server
* Used to send the forwarded keep alive packet back to the server
*/
@Translator(packet = NetworkStackLatencyPacket.class)
public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<NetworkStackLatencyPacket> {
@ -60,8 +60,10 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
// negative timestamps are used as hack to fix the url image loading bug
if (packet.getTimestamp() > 0) {
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
session.sendDownstreamPacket(keepAlivePacket);
if (session.getConnector().getConfig().isForwardPlayerPing()) {
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(pingId);
session.sendDownstreamPacket(keepAlivePacket);
}
return;
}

Datei anzeigen

@ -33,6 +33,8 @@ import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.SettingsUtils;
import org.geysermc.cumulus.CustomForm;
import java.util.concurrent.TimeUnit;
@Translator(packet = ServerSettingsRequestPacket.class)
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
@Override
@ -40,9 +42,12 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator<Ser
CustomForm window = SettingsUtils.buildForm(session);
int windowId = session.getFormCache().addForm(window);
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
serverSettingsResponsePacket.setFormData(window.getJsonData());
serverSettingsResponsePacket.setFormId(windowId);
session.sendUpstreamPacket(serverSettingsResponsePacket);
// Fixes https://bugs.mojang.com/browse/MCPE-94012 because of the delay
session.getConnector().getGeneralThreadPool().schedule(() -> {
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
serverSettingsResponsePacket.setFormData(window.getJsonData());
serverSettingsResponsePacket.setFormId(windowId);
session.sendUpstreamPacket(serverSettingsResponsePacket);
}, 1, TimeUnit.SECONDS);
}
}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen