Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-26 00:00:41 +01:00
Merge remote-tracking branch 'origin/master' into floodgate-2.0
# Conflicts: # common/src/main/java/org/geysermc/common/window/CustomFormWindow.java # common/src/main/java/org/geysermc/common/window/SimpleFormWindow.java # connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java # connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java # connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java # connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
Dieser Commit ist enthalten in:
Commit
36419e5931
1
.gitignore
vendored
1
.gitignore
vendored
@ -243,3 +243,4 @@ public-key.pem
|
||||
locales/
|
||||
/cache/
|
||||
/packs/
|
||||
/dump.json
|
35
Jenkinsfile
vendored
35
Jenkinsfile
vendored
@ -5,7 +5,7 @@ pipeline {
|
||||
jdk 'Java 8'
|
||||
}
|
||||
options {
|
||||
buildDiscarder(logRotator(artifactNumToKeepStr: '5'))
|
||||
buildDiscarder(logRotator(artifactNumToKeepStr: '20'))
|
||||
}
|
||||
stages {
|
||||
stage ('Build') {
|
||||
@ -32,9 +32,40 @@ pipeline {
|
||||
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
def changeLogSets = currentBuild.changeSets
|
||||
def message = "**Changes:**"
|
||||
|
||||
if (changeLogSets.size() == 0) {
|
||||
message += "\n*No changes.*"
|
||||
} else {
|
||||
def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "")
|
||||
def count = 0;
|
||||
def extra = 0;
|
||||
for (int i = 0; i < changeLogSets.size(); i++) {
|
||||
def entries = changeLogSets[i].items
|
||||
for (int j = 0; j < entries.length; j++) {
|
||||
if (count <= 10) {
|
||||
def entry = entries[j]
|
||||
def commitId = entry.commitId.substring(0, 6)
|
||||
message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}"
|
||||
count++
|
||||
} else {
|
||||
extra++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extra != 0) {
|
||||
message += "\n - ${extra} more commits"
|
||||
}
|
||||
}
|
||||
|
||||
env.changes = message
|
||||
}
|
||||
deleteDir()
|
||||
withCredentials([string(credentialsId: 'geyser-discord-webhook', variable: 'DISCORD_WEBHOOK')]) {
|
||||
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'NukkitX Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
|
||||
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.nukkitx.com/job/Geyser)", footer: 'Cloudburst Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
|
||||
- Download: http://ci.geysermc.org
|
||||
- Discord: http://discord.geysermc.org/
|
||||
- ~~Donate: https://patreon.com/GeyserMC~~ Currently disabled.
|
||||
- Test Server: test.geysermc.org port 25565 for Java and 19132 for Bedrock
|
||||
- Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock
|
||||
|
||||
## What's Left to be Added/Fixed
|
||||
- The Following Inventories
|
||||
|
@ -25,17 +25,20 @@
|
||||
|
||||
package org.geysermc.platform.bungeecord.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class BungeeCommandSender implements CommandSender {
|
||||
|
||||
private net.md_5.bungee.api.CommandSender handle;
|
||||
private final net.md_5.bungee.api.CommandSender handle;
|
||||
|
||||
public BungeeCommandSender(net.md_5.bungee.api.CommandSender handle) {
|
||||
this.handle = handle;
|
||||
// Ensure even Java players' languages are loaded
|
||||
LanguageUtils.loadGeyserLocale(getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -51,4 +54,14 @@ public class BungeeCommandSender implements CommandSender {
|
||||
public boolean isConsole() {
|
||||
return !(handle instanceof ProxiedPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
if (handle instanceof ProxiedPlayer) {
|
||||
ProxiedPlayer player = (ProxiedPlayer) handle;
|
||||
String locale = player.getLocale().getLanguage() + "_" + player.getLocale().getCountry();
|
||||
return LanguageUtils.formatLocale(locale);
|
||||
}
|
||||
return LanguageUtils.getDefaultLocale();
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,10 @@ package org.geysermc.platform.bungeecord.command;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -41,7 +38,7 @@ import java.util.Arrays;
|
||||
|
||||
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public GeyserBungeeCommandExecutor(GeyserConnector connector) {
|
||||
super("geyser");
|
||||
@ -54,20 +51,16 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor
|
||||
if (args.length > 0) {
|
||||
if (getCommand(args[0]) != null) {
|
||||
if (!sender.hasPermission(getCommand(args[0]).getPermission())) {
|
||||
String message = "";
|
||||
if (sender instanceof GeyserSession) {
|
||||
message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode());
|
||||
} else {
|
||||
message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail");
|
||||
}
|
||||
BungeeCommandSender commandSender = new BungeeCommandSender(sender);
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());
|
||||
|
||||
sender.sendMessage(TextComponent.fromLegacyText(ChatColor.RED + message));
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
return;
|
||||
}
|
||||
getCommand(args[0]).execute(new BungeeCommandSender(sender), args);
|
||||
getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new BungeeCommandSender(sender), args);
|
||||
getCommand("help").execute(new BungeeCommandSender(sender), new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ import org.geysermc.connector.utils.FileUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
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.GeyserSpigotBlockPlaceListener;
|
||||
import org.geysermc.platform.spigot.world.GeyserSpigotWorldManager;
|
||||
|
||||
@ -130,6 +131,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes.");
|
||||
}
|
||||
|
||||
// Set if we need to use a different method for getting a player's locale
|
||||
SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0"));
|
||||
|
||||
this.geyserWorldManager = new GeyserSpigotWorldManager(isLegacy, use3dBiomes, isViaVersion);
|
||||
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, isLegacy, isViaVersion);
|
||||
|
||||
|
@ -32,7 +32,6 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -42,28 +41,24 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpigotCommandExecutor implements TabExecutor {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length > 0) {
|
||||
if (getCommand(args[0]) != null) {
|
||||
if (!sender.hasPermission(getCommand(args[0]).getPermission())) {
|
||||
String message = "";
|
||||
if (sender instanceof GeyserSession) {
|
||||
message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", ((GeyserSession) sender).getClientData().getLanguageCode());
|
||||
} else {
|
||||
message = LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail");
|
||||
}
|
||||
SpigotCommandSender commandSender = new SpigotCommandSender(sender);
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());;
|
||||
|
||||
sender.sendMessage(ChatColor.RED + message);
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
return true;
|
||||
}
|
||||
getCommand(args[0]).execute(new SpigotCommandSender(sender), args);
|
||||
getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new SpigotCommandSender(sender), args);
|
||||
getCommand("help").execute(new SpigotCommandSender(sender), new String[0]);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
|
@ -25,15 +25,33 @@
|
||||
|
||||
package org.geysermc.platform.spigot.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class SpigotCommandSender implements CommandSender {
|
||||
|
||||
private org.bukkit.command.CommandSender handle;
|
||||
/**
|
||||
* Whether to use {@code Player.getLocale()} or {@code Player.spigot().getLocale()}, depending on version.
|
||||
* 1.12 or greater should not use the legacy method.
|
||||
*/
|
||||
private static boolean USE_LEGACY_METHOD = false;
|
||||
private static Method LOCALE_METHOD;
|
||||
|
||||
private final org.bukkit.command.CommandSender handle;
|
||||
private final String locale;
|
||||
|
||||
public SpigotCommandSender(org.bukkit.command.CommandSender handle) {
|
||||
this.handle = handle;
|
||||
this.locale = getSpigotLocale();
|
||||
// Ensure even Java players' languages are loaded
|
||||
LanguageUtils.loadGeyserLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -49,4 +67,49 @@ public class SpigotCommandSender implements CommandSender {
|
||||
public boolean isConsole() {
|
||||
return handle instanceof ConsoleCommandSender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if we are on pre-1.12, and therefore {@code player.getLocale()} doesn't exist and we have to get
|
||||
* {@code player.spigot().getLocale()}.
|
||||
*
|
||||
* @param useLegacyMethod if we are running pre-1.12 and therefore need to use reflection to get the player locale
|
||||
*/
|
||||
public static void setUseLegacyLocaleMethod(boolean useLegacyMethod) {
|
||||
USE_LEGACY_METHOD = useLegacyMethod;
|
||||
if (USE_LEGACY_METHOD) {
|
||||
try {
|
||||
//noinspection JavaReflectionMemberAccess - of course it doesn't exist; that's why we're doing it
|
||||
LOCALE_METHOD = Player.Spigot.class.getMethod("getLocale");
|
||||
} catch (NoSuchMethodException e) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Player.Spigot.getLocale() doesn't exist? Not a big deal but if you're seeing this please report it to the developers!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* So we only have to do nasty reflection stuff once per command
|
||||
*
|
||||
* @return the locale of the Spigot player
|
||||
*/
|
||||
private String getSpigotLocale() {
|
||||
if (handle instanceof Player) {
|
||||
Player player = (Player) handle;
|
||||
if (USE_LEGACY_METHOD) {
|
||||
try {
|
||||
// sigh
|
||||
// This was the only option on older Spigot instances and now it's gone
|
||||
return (String) LOCALE_METHOD.invoke(player.spigot());
|
||||
} catch (IllegalAccessException | InvocationTargetException ignored) {
|
||||
}
|
||||
} else {
|
||||
return player.getLocale();
|
||||
}
|
||||
}
|
||||
return LanguageUtils.getDefaultLocale();
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,14 @@
|
||||
package org.geysermc.platform.spigot.world;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
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.entity.Player;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
|
||||
@ -93,23 +95,32 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
if (session.getPlayerEntity() == null) {
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
if (Bukkit.getPlayer(session.getPlayerEntity().getUsername()) == null) {
|
||||
Player bukkitPlayer;
|
||||
if ((this.isLegacy && !this.isViaVersion)
|
||||
|| session.getPlayerEntity() == null
|
||||
|| (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
World world = bukkitPlayer.getWorld();
|
||||
if (isLegacy) {
|
||||
return getLegacyBlock(session, x, y, z, isViaVersion);
|
||||
return getLegacyBlock(session, x, y, z, true);
|
||||
}
|
||||
//TODO possibly: detect server version for all versions and use ViaVersion for block state mappings like below
|
||||
return BlockTranslator.getJavaIdBlockMap().getOrDefault(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z).getBlockData().getAsString(), 0);
|
||||
return BlockTranslator.getJavaIdBlockMap().getOrDefault(world.getBlockAt(x, y, z).getBlockData().getAsString(), 0);
|
||||
}
|
||||
|
||||
public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) {
|
||||
if (isViaVersion) {
|
||||
return getLegacyBlock(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(), x, y, z, true);
|
||||
} else {
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static int getLegacyBlock(GeyserSession session, int x, int y, int z, boolean isViaVersion) {
|
||||
public static int getLegacyBlock(World world, int x, int y, int z, boolean isViaVersion) {
|
||||
if (isViaVersion) {
|
||||
Block block = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getBlockAt(x, y, z);
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
// Black magic that gets the old block state ID
|
||||
int oldBlockId = (block.getType().getId() << 4) | (block.getData() & 0xF);
|
||||
// Convert block state from old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 -> 1.16.2
|
||||
@ -124,6 +135,42 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) {
|
||||
Player bukkitPlayer;
|
||||
if ((this.isLegacy && !this.isViaVersion)
|
||||
|| session.getPlayerEntity() == null
|
||||
|| (bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) {
|
||||
return;
|
||||
}
|
||||
World world = bukkitPlayer.getWorld();
|
||||
if (this.isLegacy) {
|
||||
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
|
||||
for (int blockZ = 0; blockZ < 16; blockZ++) {
|
||||
for (int blockX = 0; blockX < 16; blockX++) {
|
||||
chunk.set(blockX, blockY, blockZ, getLegacyBlock(world, (x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//TODO: see above TODO in getBlockAt
|
||||
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
|
||||
for (int blockZ = 0; blockZ < 16; blockZ++) {
|
||||
for (int blockX = 0; blockX < 16; blockX++) {
|
||||
Block block = world.getBlockAt((x << 4) + blockX, (y << 4) + blockY, (z << 4) + blockZ);
|
||||
int id = BlockTranslator.getJavaIdBlockMap().getOrDefault(block.getBlockData().getAsString(), 0);
|
||||
chunk.set(blockX, blockY, blockZ, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreBlockDataThanChunkCache() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
|
||||
|
@ -59,10 +59,10 @@ public class GeyserSpongeCommandExecutor implements CommandCallable {
|
||||
source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")));
|
||||
return CommandResult.success();
|
||||
}
|
||||
getCommand(args[0]).execute(new SpongeCommandSender(source), args);
|
||||
getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new SpongeCommandSender(source), args);
|
||||
getCommand("help").execute(new SpongeCommandSender(source), new String[0]);
|
||||
}
|
||||
return CommandResult.success();
|
||||
}
|
||||
|
@ -261,14 +261,30 @@ public class GeyserStandaloneGUI {
|
||||
|
||||
for (Map.Entry<String, GeyserCommand> command : geyserCommandManager.getCommands().entrySet()) {
|
||||
// Remove the offhand command and any alias commands to prevent duplicates in the list
|
||||
if ("offhand".equals(command.getValue().getName()) || command.getValue().getAliases().contains(command.getKey())) {
|
||||
if (!command.getValue().isExecutableOnConsole() || command.getValue().getAliases().contains(command.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the button that runs the command
|
||||
JMenuItem commandButton = new JMenuItem(command.getValue().getName());
|
||||
boolean hasSubCommands = command.getValue().hasSubCommands();
|
||||
// Add an extra menu if there are more commands that can be run
|
||||
JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName());
|
||||
commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription());
|
||||
commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ }));
|
||||
if (!hasSubCommands) {
|
||||
commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ }));
|
||||
} else {
|
||||
// Add a submenu that's the same name as the menu can't be pressed
|
||||
JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName());
|
||||
otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription());
|
||||
otherCommandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ }));
|
||||
commandButton.add(otherCommandButton);
|
||||
// Add a menu option for all possible subcommands
|
||||
for (String subCommandName : command.getValue().getSubCommands()) {
|
||||
JMenuItem item = new JMenuItem(subCommandName);
|
||||
item.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{subCommandName}));
|
||||
commandButton.add(item);
|
||||
}
|
||||
}
|
||||
commandsMenu.add(commandButton);
|
||||
}
|
||||
|
||||
@ -291,7 +307,7 @@ public class GeyserStandaloneGUI {
|
||||
playerTableModel.getDataVector().removeAllElements();
|
||||
|
||||
for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) {
|
||||
Vector row = new Vector();
|
||||
Vector<String> row = new Vector<>();
|
||||
row.add(player.getSocketAddress().getHostName());
|
||||
row.add(player.getPlayerEntity().getUsername());
|
||||
|
||||
|
@ -27,34 +27,33 @@ package org.geysermc.platform.velocity.command;
|
||||
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import net.kyori.text.TextComponent;
|
||||
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserVelocityCommandExecutor implements Command {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
@Override
|
||||
public void execute(CommandSource source, String[] args) {
|
||||
if (args.length > 0) {
|
||||
if (getCommand(args[0]) != null) {
|
||||
if (!source.hasPermission(getCommand(args[0]).getPermission())) {
|
||||
// Not ideal to use log here but we dont get a session
|
||||
source.sendMessage(TextComponent.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")));
|
||||
CommandSender sender = new VelocityCommandSender(source);
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale()));
|
||||
return;
|
||||
}
|
||||
getCommand(args[0]).execute(new VelocityCommandSender(source), args);
|
||||
getCommand(args[0]).execute(new VelocityCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new VelocityCommandSender(source), args);
|
||||
getCommand("help").execute(new VelocityCommandSender(source), new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,17 +28,21 @@ package org.geysermc.platform.velocity.command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import net.kyori.text.TextComponent;
|
||||
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class VelocityCommandSender implements CommandSender {
|
||||
|
||||
private CommandSource handle;
|
||||
private final CommandSource handle;
|
||||
|
||||
public VelocityCommandSender(CommandSource handle) {
|
||||
this.handle = handle;
|
||||
// Ensure even Java players' languages are loaded
|
||||
LanguageUtils.loadGeyserLocale(getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -59,4 +63,13 @@ public class VelocityCommandSender implements CommandSender {
|
||||
public boolean isConsole() {
|
||||
return handle instanceof ConsoleCommandSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
if (handle instanceof Player) {
|
||||
Locale locale = ((Player) handle).getPlayerSettings().getLocale();
|
||||
return LanguageUtils.formatLocale(locale.getLanguage() + "_" + locale.getCountry());
|
||||
}
|
||||
return LanguageUtils.getDefaultLocale();
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.CloudburstMC.Protocol</groupId>
|
||||
<artifactId>bedrock-v408</artifactId>
|
||||
<version>250beb2a94</version>
|
||||
<version>02f46a8700</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -109,9 +109,9 @@
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.GeyserMC</groupId>
|
||||
<groupId>com.github.steveice10</groupId>
|
||||
<artifactId>mcprotocollib</artifactId>
|
||||
<version>e4a3aa636a</version>
|
||||
<version>1b01b1ffef</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -137,21 +137,21 @@
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.kyoripowered.adventure</groupId>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>557865caef</version>
|
||||
<version>4.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.kyoripowered.adventure</groupId>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>557865caef</version>
|
||||
<version>4.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.kyoripowered.adventure</groupId>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>557865caef</version>
|
||||
<version>4.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -71,7 +71,9 @@ import java.net.UnknownHostException;
|
||||
import java.security.Key;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@ -217,8 +219,39 @@ public class GeyserConnector {
|
||||
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger(""));
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1));
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("players", players::size));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", authType.name()::toLowerCase));
|
||||
// Prevent unwanted words best we can
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> AuthType.getByName(config.getRemote().getAuthType()).toString().toLowerCase()));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", LanguageUtils::getDefaultLocale));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserConnector.VERSION));
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (GeyserSession session : players) {
|
||||
if (session == null) continue;
|
||||
if (session.getClientData() == null) continue;
|
||||
String os = session.getClientData().getDeviceOS().toString();
|
||||
if (!valueMap.containsKey(os)) {
|
||||
valueMap.put(os, 1);
|
||||
} else {
|
||||
valueMap.put(os, valueMap.get(os) + 1);
|
||||
}
|
||||
}
|
||||
return valueMap;
|
||||
}));
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("playerVersion", () -> {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (GeyserSession session : players) {
|
||||
if (session == null) continue;
|
||||
if (session.getClientData() == null) continue;
|
||||
String version = session.getClientData().getGameVersion();
|
||||
if (!valueMap.containsKey(version)) {
|
||||
valueMap.put(version, 1);
|
||||
} else {
|
||||
valueMap.put(version, valueMap.get(version) + 1);
|
||||
}
|
||||
}
|
||||
return valueMap;
|
||||
}));
|
||||
}
|
||||
|
||||
boolean isGui = false;
|
||||
@ -256,7 +289,7 @@ public class GeyserConnector {
|
||||
// Make a copy to prevent ConcurrentModificationException
|
||||
final List<GeyserSession> tmpPlayers = new ArrayList<>(players);
|
||||
for (GeyserSession playerSession : tmpPlayers) {
|
||||
playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getClientData().getLanguageCode()));
|
||||
playerSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.core.shutdown.kick.message", playerSession.getLocale()));
|
||||
}
|
||||
|
||||
CompletableFuture<Void> future = CompletableFuture.runAsync(new Runnable() {
|
||||
@ -330,15 +363,16 @@ public class GeyserConnector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the production status of the current runtime.
|
||||
* Will return true if the version number is not 'DEV'.
|
||||
* Should only happen in compiled jars.
|
||||
* Whether to use XML reflections in the jar or manually find the reflections.
|
||||
* Will return true if the version number is not 'DEV' and the platform is not Fabric.
|
||||
* On Fabric - it complains about being unable to create a default XMLReader.
|
||||
* On other platforms this should only be true in compiled jars.
|
||||
*
|
||||
* @return If we are in a production build/environment
|
||||
* @return whether to use XML reflections
|
||||
*/
|
||||
public boolean isProduction() {
|
||||
public boolean useXmlReflections() {
|
||||
//noinspection ConstantConditions
|
||||
return !"DEV".equals(GeyserConnector.VERSION);
|
||||
return !this.getPlatformType().equals(PlatformType.FABRIC) && !"DEV".equals(GeyserConnector.VERSION);
|
||||
}
|
||||
|
||||
public static GeyserConnector getInstance() {
|
||||
|
@ -40,18 +40,19 @@ public abstract class CommandManager {
|
||||
@Getter
|
||||
private final Map<String, GeyserCommand> commands = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public CommandManager(GeyserConnector connector) {
|
||||
this.connector = connector;
|
||||
|
||||
registerCommand(new HelpCommand(connector, "help", LanguageUtils.getLocaleStringLog("geyser.commands.help.desc"), "geyser.command.help"));
|
||||
registerCommand(new ListCommand(connector, "list", LanguageUtils.getLocaleStringLog("geyser.commands.list.desc"), "geyser.command.list"));
|
||||
registerCommand(new ReloadCommand(connector, "reload", LanguageUtils.getLocaleStringLog("geyser.commands.reload.desc"), "geyser.command.reload"));
|
||||
registerCommand(new StopCommand(connector, "stop", LanguageUtils.getLocaleStringLog("geyser.commands.stop.desc"), "geyser.command.stop"));
|
||||
registerCommand(new OffhandCommand(connector, "offhand", LanguageUtils.getLocaleStringLog("geyser.commands.offhand.desc"), "geyser.command.offhand"));
|
||||
registerCommand(new DumpCommand(connector, "dump", LanguageUtils.getLocaleStringLog("geyser.commands.dump.desc"), "geyser.command.dump"));
|
||||
registerCommand(new VersionCommand(connector, "version", LanguageUtils.getLocaleStringLog("geyser.commands.version.desc"), "geyser.command.version"));
|
||||
registerCommand(new HelpCommand(connector, "help", "geyser.commands.help.desc", "geyser.command.help"));
|
||||
registerCommand(new ListCommand(connector, "list", "geyser.commands.list.desc", "geyser.command.list"));
|
||||
registerCommand(new ReloadCommand(connector, "reload", "geyser.commands.reload.desc", "geyser.command.reload"));
|
||||
registerCommand(new StopCommand(connector, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||
registerCommand(new OffhandCommand(connector, "offhand", "geyser.commands.offhand.desc", "geyser.command.offhand"));
|
||||
registerCommand(new DumpCommand(connector, "dump", "geyser.commands.dump.desc", "geyser.command.dump"));
|
||||
registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version"));
|
||||
registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
||||
}
|
||||
|
||||
public void registerCommand(GeyserCommand command) {
|
||||
|
@ -25,6 +25,12 @@
|
||||
|
||||
package org.geysermc.connector.command;
|
||||
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
/**
|
||||
* Implemented on top of any class that can send a command.
|
||||
* For example, it wraps around Spigot's CommandSender class.
|
||||
*/
|
||||
public interface CommandSender {
|
||||
|
||||
String getName();
|
||||
@ -37,5 +43,17 @@ public interface CommandSender {
|
||||
|
||||
void sendMessage(String message);
|
||||
|
||||
/**
|
||||
* @return true if the specified sender is from the console.
|
||||
*/
|
||||
boolean isConsole();
|
||||
|
||||
/**
|
||||
* Returns the locale of the command sender. Defaults to the default locale at {@link LanguageUtils#getDefaultLocale()}.
|
||||
*
|
||||
* @return the locale of the command sender.
|
||||
*/
|
||||
default String getLocale() {
|
||||
return LanguageUtils.getDefaultLocale();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@ -37,6 +38,9 @@ import java.util.List;
|
||||
public abstract class GeyserCommand {
|
||||
|
||||
protected final String name;
|
||||
/**
|
||||
* The description of the command - will attempt to be translated.
|
||||
*/
|
||||
protected final String description;
|
||||
protected final String permission;
|
||||
|
||||
@ -44,4 +48,31 @@ public abstract class GeyserCommand {
|
||||
private List<String> aliases = new ArrayList<>();
|
||||
|
||||
public abstract void execute(CommandSender sender, String[] args);
|
||||
|
||||
/**
|
||||
* If false, hides the command from being shown on the Geyser Standalone GUI.
|
||||
*
|
||||
* @return true if the command can be run on the server console
|
||||
*/
|
||||
public boolean isExecutableOnConsole() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the GUI to know what subcommands can be run
|
||||
*
|
||||
* @return a list of all possible subcommands, or empty if none.
|
||||
*/
|
||||
public List<String> getSubCommands() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to {@link #getSubCommands()}{@code .isEmpty()}.
|
||||
*
|
||||
* @return true if there are subcommand present for this command.
|
||||
*/
|
||||
public boolean hasSubCommands() {
|
||||
return !getSubCommands().isEmpty();
|
||||
}
|
||||
}
|
@ -27,12 +27,10 @@ package org.geysermc.connector.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
|
||||
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.common.serializer.AsteriskSerializer;
|
||||
import org.geysermc.connector.dump.DumpInfo;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
@ -40,6 +38,8 @@ import org.geysermc.connector.utils.WebUtils;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class DumpCommand extends GeyserCommand {
|
||||
|
||||
@ -73,7 +73,7 @@ public class DumpCommand extends GeyserCommand {
|
||||
|
||||
AsteriskSerializer.showSensitive = showSensitive;
|
||||
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collecting"));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collecting", sender.getLocale()));
|
||||
String dumpData = "";
|
||||
try {
|
||||
if (offlineDump) {
|
||||
@ -82,7 +82,7 @@ public class DumpCommand extends GeyserCommand {
|
||||
dumpData = MAPPER.writeValueAsString(new DumpInfo());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error"));
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.collect_error", sender.getLocale()));
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.collect_error_short"), e);
|
||||
return;
|
||||
}
|
||||
@ -90,21 +90,21 @@ public class DumpCommand extends GeyserCommand {
|
||||
String uploadedDumpUrl = "";
|
||||
|
||||
if (offlineDump) {
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.writing"));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.writing", sender.getLocale()));
|
||||
|
||||
try {
|
||||
FileOutputStream outputStream = new FileOutputStream(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("dump.json").toFile());
|
||||
outputStream.write(dumpData.getBytes());
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error"));
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.write_error", sender.getLocale()));
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.write_error_short"), e);
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedDumpUrl = "dump.json";
|
||||
} else {
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.uploading"));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.uploading", sender.getLocale()));
|
||||
|
||||
String response;
|
||||
JsonNode responseNode;
|
||||
@ -112,22 +112,27 @@ public class DumpCommand extends GeyserCommand {
|
||||
response = WebUtils.post(DUMP_URL + "documents", dumpData);
|
||||
responseNode = MAPPER.readTree(response);
|
||||
} catch (IOException e) {
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error"));
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error", sender.getLocale()));
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short"), e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!responseNode.has("key")) {
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.dump.upload_error_short") + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.dump.upload_error_short", sender.getLocale()) + ": " + (responseNode.has("message") ? responseNode.get("message").asText() : response));
|
||||
return;
|
||||
}
|
||||
|
||||
uploadedDumpUrl = DUMP_URL + responseNode.get("key").asText();
|
||||
}
|
||||
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.dump.message") + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.dump.message", sender.getLocale()) + " " + ChatColor.DARK_AQUA + uploadedDumpUrl);
|
||||
if (!sender.isConsole()) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.commands.dump.created", sender.getName(), uploadedDumpUrl));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSubCommands() {
|
||||
return Arrays.asList("offline", "full");
|
||||
}
|
||||
}
|
||||
|
@ -25,11 +25,10 @@
|
||||
|
||||
package org.geysermc.connector.command.defaults;
|
||||
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -52,17 +51,12 @@ public class HelpCommand extends GeyserCommand {
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
String header = "";
|
||||
|
||||
if (sender instanceof GeyserSession) {
|
||||
header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", ((GeyserSession) sender).getClientData().getLanguageCode(), page, maxPage);
|
||||
} else {
|
||||
header = LanguageUtils.getLocaleStringLog("geyser.commands.help.header", page, maxPage);
|
||||
}
|
||||
String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage);
|
||||
|
||||
sender.sendMessage(header);
|
||||
Map<String, GeyserCommand> cmds = connector.getCommandManager().getCommands();
|
||||
List<String> commands = connector.getCommandManager().getCommands().keySet().stream().sorted().collect(Collectors.toList());
|
||||
commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " + cmds.get(cmd).getDescription()));
|
||||
commands.forEach(cmd -> sender.sendMessage(ChatColor.YELLOW + "/geyser " + cmd + ChatColor.WHITE + ": " +
|
||||
LanguageUtils.getPlayerLocaleString(cmds.get(cmd).getDescription(), sender.getLocale())));
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class ListCommand extends GeyserCommand {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public ListCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
@ -46,11 +46,9 @@ public class ListCommand extends GeyserCommand {
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
String message = "";
|
||||
if (sender instanceof GeyserSession) {
|
||||
message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", ((GeyserSession) sender).getClientData().getLanguageCode(), connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" ")));
|
||||
} else {
|
||||
message = LanguageUtils.getLocaleStringLog("geyser.commands.list.message", connector.getPlayers().size(), connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" ")));
|
||||
}
|
||||
message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(),
|
||||
connector.getPlayers().size(),
|
||||
connector.getPlayers().stream().map(GeyserSession::getName).collect(Collectors.joining(" ")));
|
||||
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
public class OffhandCommand extends GeyserCommand {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public OffhandCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
@ -58,7 +58,7 @@ public class OffhandCommand extends GeyserCommand {
|
||||
session.sendDownstreamPacket(releaseItemPacket);
|
||||
return;
|
||||
}
|
||||
// Needed for Bukkit - sender is not an instance of GeyserSession
|
||||
// Needed for Spigot - sender is not an instance of GeyserSession
|
||||
for (GeyserSession session : connector.getPlayers()) {
|
||||
if (sender.getName().equals(session.getPlayerEntity().getUsername())) {
|
||||
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0),
|
||||
@ -68,4 +68,9 @@ public class OffhandCommand extends GeyserCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -47,17 +47,12 @@ public class ReloadCommand extends GeyserCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
String message = "";
|
||||
if (sender instanceof GeyserSession) {
|
||||
message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", ((GeyserSession) sender).getClientData().getLanguageCode());
|
||||
} else {
|
||||
message = LanguageUtils.getLocaleStringLog("geyser.commands.reload.message");
|
||||
}
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.commands.reload.message", sender.getLocale());
|
||||
|
||||
sender.sendMessage(message);
|
||||
|
||||
for (GeyserSession session : connector.getPlayers()) {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getClientData().getLanguageCode()));
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale()));
|
||||
}
|
||||
connector.reload();
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.connector.command.defaults;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.ClientRequest;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
public class StatisticsCommand extends GeyserCommand {
|
||||
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.isConsole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the sender is a Bedrock edition client
|
||||
GeyserSession session = null;
|
||||
if (sender instanceof GeyserSession) {
|
||||
session = (GeyserSession) sender;
|
||||
} else {
|
||||
// Needed for Spigot - sender is not an instance of GeyserSession
|
||||
for (GeyserSession otherSession : connector.getPlayers()) {
|
||||
if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) {
|
||||
session = otherSession;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (session == null) return;
|
||||
session.setWaitingForStatistics(true);
|
||||
ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS);
|
||||
session.sendDownstreamPacket(clientRequestPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -61,12 +61,12 @@ public class VersionCommand extends GeyserCommand {
|
||||
bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
|
||||
}
|
||||
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.version", GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions));
|
||||
|
||||
// Disable update checking in dev mode
|
||||
//noinspection ConstantConditions - changes in production
|
||||
if (!GeyserConnector.VERSION.equals("DEV")) {
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.checking"));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.checking", sender.getLocale()));
|
||||
try {
|
||||
Properties gitProp = new Properties();
|
||||
gitProp.load(FileUtils.getResource("git.properties"));
|
||||
@ -76,16 +76,16 @@ public class VersionCommand extends GeyserCommand {
|
||||
int latestBuildNum = Integer.parseInt(buildXML.replaceAll("<(\\\\)?(/)?buildNumber>", "").trim());
|
||||
int buildNum = Integer.parseInt(gitProp.getProperty("git.build.number"));
|
||||
if (latestBuildNum == buildNum) {
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.no_updates"));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.no_updates", sender.getLocale()));
|
||||
} else {
|
||||
sender.sendMessage(LanguageUtils.getLocaleStringLog("geyser.commands.version.outdated", (latestBuildNum - buildNum), "http://ci.geysermc.org/"));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.outdated", sender.getLocale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/"));
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError("buildNumber missing");
|
||||
}
|
||||
} catch (IOException | AssertionError | NumberFormatException e) {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"), e);
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.commands.version.failed"));
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.commands.version.failed", sender.getLocale()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +34,11 @@ public enum PlatformType {
|
||||
|
||||
ANDROID("Android"),
|
||||
BUNGEECORD("BungeeCord"),
|
||||
FABRIC("Fabric"),
|
||||
SPIGOT("Spigot"),
|
||||
SPONGE("Sponge"),
|
||||
STANDALONE("Standalone"),
|
||||
VELOCITY("Velocity");
|
||||
|
||||
private String platformName;
|
||||
private final String platformName;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import org.geysermc.connector.common.serializer.AsteriskSerializer;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@ -45,8 +46,8 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@Setter
|
||||
private boolean autoconfiguredRemote = false;
|
||||
|
||||
private BedrockConfiguration bedrock;
|
||||
private RemoteConfiguration remote;
|
||||
private BedrockConfiguration bedrock = new BedrockConfiguration();
|
||||
private RemoteConfiguration remote = new RemoteConfiguration();
|
||||
|
||||
@JsonProperty("floodgate-key-file")
|
||||
private String floodgateKeyFile = "public-key.pem";
|
||||
@ -106,7 +107,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@JsonProperty("force-resource-packs")
|
||||
private boolean forceResourcePacks = true;
|
||||
|
||||
private MetricsInfo metrics;
|
||||
private MetricsInfo metrics = new MetricsInfo();
|
||||
|
||||
@Getter
|
||||
public static class BedrockConfiguration implements IBedrockConfiguration {
|
||||
@ -154,7 +155,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
private boolean enabled = true;
|
||||
|
||||
@JsonProperty("uuid")
|
||||
private String uniqueId = "generateuuid";
|
||||
private String uniqueId = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
@JsonProperty("scoreboard-packet-threshold")
|
||||
|
@ -43,6 +43,8 @@ public class AreaEffectCloudEntity extends Entity {
|
||||
|
||||
// This disabled client side shrink of the cloud
|
||||
metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, 0.0f);
|
||||
metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_RATE, -0.005f);
|
||||
metadata.put(EntityData.AREA_EFFECT_CLOUD_CHANGE_ON_PICKUP, -0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -50,11 +52,14 @@ public class AreaEffectCloudEntity extends Entity {
|
||||
if (entityMetadata.getId() == 7) {
|
||||
metadata.put(EntityData.AREA_EFFECT_CLOUD_RADIUS, entityMetadata.getValue());
|
||||
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 2.0f * (float) entityMetadata.getValue());
|
||||
} else if (entityMetadata.getId() == 8) {
|
||||
metadata.put(EntityData.EFFECT_COLOR, entityMetadata.getValue());
|
||||
} else if (entityMetadata.getId() == 10) {
|
||||
Particle particle = (Particle) entityMetadata.getValue();
|
||||
metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, EffectRegistry.getParticleString(particle.getType()));
|
||||
} else if (entityMetadata.getId() == 8) {
|
||||
metadata.put(EntityData.POTION_AUX_VALUE, entityMetadata.getValue());
|
||||
int particleId = EffectRegistry.getParticleId(particle.getType());
|
||||
if (particleId != -1) {
|
||||
metadata.put(EntityData.AREA_EFFECT_CLOUD_PARTICLE_ID, particleId);
|
||||
}
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
|
@ -321,7 +321,7 @@ public class Entity {
|
||||
Message message = (Message) entityMetadata.getValue();
|
||||
if (message != null)
|
||||
// Always translate even if it's a TextMessage since there could be translatable parameters
|
||||
metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getClientData().getLanguageCode(), true));
|
||||
metadata.put(EntityData.NAMETAG, MessageUtils.getTranslatedBedrockMessage(message, session.getLocale(), true));
|
||||
}
|
||||
break;
|
||||
case 3: // is custom name visible
|
||||
|
@ -26,11 +26,65 @@
|
||||
package org.geysermc.connector.entity;
|
||||
|
||||
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 org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ThrowableEntity extends Entity {
|
||||
|
||||
private Vector3f lastPosition;
|
||||
private ScheduledFuture<?> positionUpdater;
|
||||
|
||||
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
this.lastPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(GeyserSession session) {
|
||||
super.spawnEntity(session);
|
||||
positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
|
||||
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
|
||||
|
||||
if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) {
|
||||
float gravity = 0.03f; // Snowball, Egg, and Ender Pearl
|
||||
if (entityType == EntityType.THROWN_POTION || entityType == EntityType.LINGERING_POTION) {
|
||||
gravity = 0.05f;
|
||||
} else if (entityType == EntityType.THROWN_EXP_BOTTLE) {
|
||||
gravity = 0.07f;
|
||||
}
|
||||
motion = motion.down(gravity);
|
||||
}
|
||||
}, 0, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean despawnEntity(GeyserSession session) {
|
||||
positionUpdater.cancel(true);
|
||||
if (entityType == EntityType.THROWN_ENDERPEARL) {
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_TELEPORT);
|
||||
particlePacket.setPosition(position);
|
||||
session.sendUpstreamPacket(particlePacket);
|
||||
}
|
||||
return super.despawnEntity(session);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
||||
lastPosition = position;
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,39 @@
|
||||
|
||||
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 org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.item.TippedArrowPotion;
|
||||
|
||||
/**
|
||||
* Internally this is known as TippedArrowEntity but is used with tipped arrows and normal arrows
|
||||
*/
|
||||
public class TippedArrowEntity extends AbstractArrowEntity {
|
||||
|
||||
public TippedArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||
// Arrow potion effect color
|
||||
if (entityMetadata.getId() == 9) {
|
||||
int potionColor = (int) entityMetadata.getValue();
|
||||
// -1 means no color
|
||||
if (potionColor == -1) {
|
||||
metadata.remove(EntityData.CUSTOM_DISPLAY);
|
||||
} else {
|
||||
TippedArrowPotion potion = TippedArrowPotion.getByJavaColor(potionColor);
|
||||
if (potion != null && potion.getJavaColor() != -1) {
|
||||
metadata.put(EntityData.CUSTOM_DISPLAY, (byte) potion.getBedrockId());
|
||||
} else {
|
||||
metadata.remove(EntityData.CUSTOM_DISPLAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
public class WolfEntity extends TameableEntity {
|
||||
|
||||
private byte collarColor;
|
||||
|
||||
public WolfEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
}
|
||||
@ -57,12 +59,13 @@ public class WolfEntity extends TameableEntity {
|
||||
// Wolf collar color
|
||||
// Relies on EntityData.OWNER_EID being set in TameableEntity.java
|
||||
if (entityMetadata.getId() == 19 && !metadata.getFlags().getFlag(EntityFlag.ANGRY)) {
|
||||
metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue());
|
||||
metadata.put(EntityData.COLOR, collarColor = (byte) (int) entityMetadata.getValue());
|
||||
}
|
||||
|
||||
// Wolf anger (1.16+)
|
||||
if (entityMetadata.getId() == 20) {
|
||||
metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() != 0);
|
||||
metadata.put(EntityData.COLOR, (int) entityMetadata.getValue() != 0 ? (byte) 0 : collarColor);
|
||||
}
|
||||
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
|
@ -32,33 +32,60 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import lombok.Data;
|
||||
import org.geysermc.connector.entity.living.InsentientEntity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EnderDragonEntity extends InsentientEntity {
|
||||
/**
|
||||
* The Ender Dragon has multiple hit boxes, which
|
||||
* are each its own invisible entity
|
||||
*/
|
||||
private EnderDragonPartEntity head;
|
||||
private EnderDragonPartEntity neck;
|
||||
private EnderDragonPartEntity body;
|
||||
private EnderDragonPartEntity leftWing;
|
||||
private EnderDragonPartEntity rightWing;
|
||||
private EnderDragonPartEntity[] tail;
|
||||
|
||||
private EnderDragonPartEntity[] allParts;
|
||||
|
||||
/**
|
||||
* A circular buffer that stores a history of
|
||||
* y and yaw values.
|
||||
*/
|
||||
private final Segment[] segmentHistory = new Segment[19];
|
||||
private int latestSegment = -1;
|
||||
|
||||
private boolean hovering;
|
||||
|
||||
private ScheduledFuture<?> partPositionUpdater;
|
||||
|
||||
public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
|
||||
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||
// Phase
|
||||
if (entityMetadata.getId() == 15) {
|
||||
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
|
||||
switch ((int) entityMetadata.getValue()) {
|
||||
int value = (int) entityMetadata.getValue();
|
||||
if (value == 5) {
|
||||
// Performing breath attack
|
||||
case 5:
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
|
||||
entityEventPacket.setRuntimeEntityId(geyserId);
|
||||
entityEventPacket.setData(0);
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
case 6:
|
||||
case 7:
|
||||
metadata.getFlags().setFlag(EntityFlag.SITTING, true);
|
||||
break;
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
|
||||
entityEventPacket.setRuntimeEntityId(geyserId);
|
||||
entityEventPacket.setData(0);
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
}
|
||||
metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7);
|
||||
hovering = value == 10;
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
@ -81,6 +108,118 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
valid = true;
|
||||
session.sendUpstreamPacket(addEntityPacket);
|
||||
|
||||
head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1);
|
||||
neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3);
|
||||
body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3);
|
||||
leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
|
||||
rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
|
||||
tail = new EnderDragonPartEntity[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2);
|
||||
}
|
||||
|
||||
allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
|
||||
|
||||
for (EnderDragonPartEntity part : allParts) {
|
||||
session.getEntityCache().spawnEntity(part);
|
||||
}
|
||||
|
||||
for (int i = 0; i < segmentHistory.length; i++) {
|
||||
segmentHistory[i] = new Segment();
|
||||
segmentHistory[i].yaw = rotation.getZ();
|
||||
segmentHistory[i].y = position.getY();
|
||||
}
|
||||
|
||||
partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
|
||||
pushSegment();
|
||||
updateBoundingBoxes(session);
|
||||
}, 0, 50, TimeUnit.MILLISECONDS);
|
||||
|
||||
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean despawnEntity(GeyserSession session) {
|
||||
partPositionUpdater.cancel(true);
|
||||
|
||||
for (EnderDragonPartEntity part : allParts) {
|
||||
part.despawnEntity(session);
|
||||
}
|
||||
return super.despawnEntity(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the positions of the Ender Dragon's multiple bounding boxes
|
||||
*
|
||||
* @param session GeyserSession.
|
||||
*/
|
||||
private void updateBoundingBoxes(GeyserSession session) {
|
||||
Vector3f facingDir = Vector3f.createDirectionDeg(0, rotation.getZ());
|
||||
Segment baseSegment = getSegment(5);
|
||||
// Used to angle the head, neck, and tail when the dragon flies up/down
|
||||
float pitch = (float) Math.toRadians(10 * (baseSegment.getY() - getSegment(10).getY()));
|
||||
float pitchXZ = (float) Math.cos(pitch);
|
||||
float pitchY = (float) Math.sin(pitch);
|
||||
|
||||
// Lowers the head when the dragon sits/hovers
|
||||
float headDuck;
|
||||
if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) {
|
||||
headDuck = -1f;
|
||||
} else {
|
||||
headDuck = baseSegment.y - getSegment(0).y;
|
||||
}
|
||||
|
||||
head.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(6.5f).up(headDuck));
|
||||
neck.setPosition(facingDir.up(pitchY).mul(pitchXZ, 1, -pitchXZ).mul(5.5f).up(headDuck));
|
||||
body.setPosition(facingDir.mul(0.5f, 0f, -0.5f));
|
||||
|
||||
Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - rotation.getZ()).mul(4.5f).up(2f);
|
||||
rightWing.setPosition(wingPos);
|
||||
leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally
|
||||
|
||||
Vector3f tailBase = facingDir.mul(1.5f);
|
||||
for (int i = 0; i < tail.length; i++) {
|
||||
float distance = (i + 1) * 2f;
|
||||
// Curls the tail when the dragon turns
|
||||
Segment targetSegment = getSegment(12 + 2 * i);
|
||||
float angle = rotation.getZ() + targetSegment.yaw - baseSegment.yaw;
|
||||
|
||||
float tailYOffset = targetSegment.y - baseSegment.y - (distance + 1.5f) * pitchY + 1.5f;
|
||||
tail[i].setPosition(Vector3f.createDirectionDeg(0, angle).mul(distance).add(tailBase).mul(-pitchXZ, 1, pitchXZ).up(tailYOffset));
|
||||
}
|
||||
// Send updated positions
|
||||
for (EnderDragonPartEntity part : allParts) {
|
||||
part.moveAbsolute(session, part.getPosition().add(position), Vector3f.ZERO, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current yaw and y into the circular buffer
|
||||
*/
|
||||
private void pushSegment() {
|
||||
latestSegment = (latestSegment + 1) % segmentHistory.length;
|
||||
segmentHistory[latestSegment].yaw = rotation.getZ();
|
||||
segmentHistory[latestSegment].y = position.getY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous yaw and y
|
||||
* Used to curl the tail and pitch the head and tail up/down
|
||||
*
|
||||
* @param index Number of ticks in the past
|
||||
* @return Segment with the yaw and y
|
||||
*/
|
||||
private Segment getSegment(int index) {
|
||||
index = (latestSegment - index) % segmentHistory.length;
|
||||
if (index < 0) {
|
||||
index += segmentHistory.length;
|
||||
}
|
||||
return segmentHistory[index];
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class Segment {
|
||||
private float yaw;
|
||||
private float y;
|
||||
}
|
||||
}
|
||||
|
@ -23,31 +23,20 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.world.chunk;
|
||||
package org.geysermc.connector.entity.living.monster;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
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.Entity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
public class ChunkPosition {
|
||||
public class EnderDragonPartEntity extends Entity {
|
||||
public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
|
||||
private int x;
|
||||
private int z;
|
||||
|
||||
public Position getBlock(int x, int y, int z) {
|
||||
return new Position((this.x << 4) + x, y, (this.z << 4) + z);
|
||||
}
|
||||
|
||||
public Position getChunkBlock(int x, int y, int z) {
|
||||
int chunkX = x & 15;
|
||||
int chunkY = y & 15;
|
||||
int chunkZ = z & 15;
|
||||
return new Position(chunkX, chunkY, chunkZ);
|
||||
metadata.put(EntityData.BOUNDING_BOX_WIDTH, width);
|
||||
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height);
|
||||
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.connector.entity.living.monster.raid;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
public class PillagerEntity extends AbstractIllagerEntity {
|
||||
|
||||
public PillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||
if (entityMetadata.getId() == 16) {
|
||||
// Java Edition always has the Pillager entity as positioning the crossbow
|
||||
metadata.getFlags().setFlag(EntityFlag.USING_ITEM, true);
|
||||
metadata.getFlags().setFlag(EntityFlag.CHARGED, true);
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import org.geysermc.connector.entity.living.animal.tameable.*;
|
||||
import org.geysermc.connector.entity.living.merchant.*;
|
||||
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;
|
||||
import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity;
|
||||
import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity;
|
||||
|
||||
@ -85,12 +86,12 @@ public enum EntityType {
|
||||
ELDER_GUARDIAN(ElderGuardianEntity.class, 50, 1.9975f),
|
||||
NPC(PlayerEntity.class, 51, 1.8f, 0.6f, 0.6f, 1.62f),
|
||||
WITHER(WitherEntity.class, 52, 3.5f, 0.9f),
|
||||
ENDER_DRAGON(EnderDragonEntity.class, 53, 4f, 13f),
|
||||
ENDER_DRAGON(EnderDragonEntity.class, 53, 0f, 0f),
|
||||
SHULKER(ShulkerEntity.class, 54, 1f, 1f),
|
||||
ENDERMITE(MonsterEntity.class, 55, 0.3f, 0.4f),
|
||||
AGENT(Entity.class, 56, 0f),
|
||||
VINDICATOR(AbstractIllagerEntity.class, 57, 1.8f, 0.6f, 0.6f, 1.62f),
|
||||
PILLAGER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f),
|
||||
PILLAGER(PillagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f),
|
||||
WANDERING_TRADER(AbstractMerchantEntity.class, 118, 1.8f, 0.6f, 0.6f, 1.62f),
|
||||
PHANTOM(FlyingEntity.class, 58, 0.5f, 0.9f, 0.9f, 0.6f),
|
||||
RAVAGER(RaidParticipantEntity.class, 59, 1.9f, 1.2f),
|
||||
@ -165,7 +166,12 @@ public enum EntityType {
|
||||
/**
|
||||
* Not an entity in Bedrock, so we replace it with a Pillager
|
||||
*/
|
||||
ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager");
|
||||
ILLUSIONER(AbstractIllagerEntity.class, 114, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:pillager"),
|
||||
|
||||
/**
|
||||
* Not an entity in Bedrock, but used for the Ender Dragon's multiple hitboxes
|
||||
*/
|
||||
ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand");
|
||||
|
||||
private static final EntityType[] VALUES = values();
|
||||
|
||||
|
@ -116,7 +116,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
case HAVE_ALL_PACKS:
|
||||
ResourcePackStackPacket stackPacket = new ResourcePackStackPacket();
|
||||
stackPacket.setExperimental(false);
|
||||
stackPacket.setExperimentsPreviouslyToggled(false);
|
||||
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
|
||||
stackPacket.setGameVersion(session.getClientData().getGameVersion());
|
||||
|
||||
@ -139,7 +139,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
@Override
|
||||
public boolean handle(ModalFormResponsePacket packet) {
|
||||
session.getFormCache().handleResponse(packet);
|
||||
return true;
|
||||
return true; //todo change the Statistics Form to match the new style
|
||||
}
|
||||
|
||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||
@ -161,7 +161,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(SetLocalPlayerAsInitializedPacket packet) {
|
||||
LanguageUtils.loadGeyserLocale(session.getClientData().getLanguageCode());
|
||||
LanguageUtils.loadGeyserLocale(session.getLocale());
|
||||
|
||||
if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) {
|
||||
// TODO it is safer to key authentication on something that won't change (UUID, not username)
|
||||
@ -176,7 +176,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
@Override
|
||||
public boolean handle(MovePlayerPacket packet) {
|
||||
if (session.isLoggingIn()) {
|
||||
session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getClientData().getLanguageCode()));
|
||||
session.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.auth.login.wait", session.getLocale()));
|
||||
}
|
||||
|
||||
return translateAndDefault(packet);
|
||||
|
@ -28,9 +28,11 @@ package org.geysermc.connector.network.session;
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
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.statistic.Statistic;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
|
||||
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
|
||||
@ -54,6 +56,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.common.form.Form;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
@ -79,6 +82,7 @@ import org.geysermc.floodgate.util.BedrockData;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Getter
|
||||
@ -113,8 +117,6 @@ public class GeyserSession implements CommandSender {
|
||||
*/
|
||||
private final Object2LongMap<Vector3i> itemFrameCache = new Object2LongOpenHashMap<>();
|
||||
|
||||
private DataCache<Packet> javaPacketCache;
|
||||
|
||||
@Setter
|
||||
private Vector2i lastChunkPosition = null;
|
||||
private int renderDistance;
|
||||
@ -172,6 +174,12 @@ public class GeyserSession implements CommandSender {
|
||||
@Setter
|
||||
private long lastWindowCloseTime = 0;
|
||||
|
||||
/**
|
||||
* Saves the timestamp of the last keep alive packet
|
||||
*/
|
||||
@Setter
|
||||
private long lastKeepAliveTimestamp = 0;
|
||||
|
||||
@Setter
|
||||
private VillagerTrade[] villagerTrades;
|
||||
@Setter
|
||||
@ -184,9 +192,10 @@ public class GeyserSession implements CommandSender {
|
||||
|
||||
/**
|
||||
* The current attack speed of the player. Used for sending proper cooldown timings.
|
||||
* Setting a default fixes cooldowns not showing up on a fresh world.
|
||||
*/
|
||||
@Setter
|
||||
private double attackSpeed;
|
||||
private double attackSpeed = 4.0d;
|
||||
/**
|
||||
* The time of the last hit. Used to gauge how long the cooldown is taking.
|
||||
* This is a session variable in order to prevent more scheduled threads than necessary.
|
||||
@ -201,6 +210,13 @@ public class GeyserSession implements CommandSender {
|
||||
@Setter
|
||||
private long lastInteractionTime;
|
||||
|
||||
/**
|
||||
* Stores a future interaction to place a bucket. Will be cancelled if the client instead intended to
|
||||
* interact with a block.
|
||||
*/
|
||||
@Setter
|
||||
private ScheduledFuture<?> bucketScheduledFuture;
|
||||
|
||||
private boolean reducedDebugInfo = false;
|
||||
|
||||
/**
|
||||
@ -254,6 +270,22 @@ public class GeyserSession implements CommandSender {
|
||||
@Setter
|
||||
private String lastSignMessage;
|
||||
|
||||
/**
|
||||
* Stores a map of all statistics sent from the server.
|
||||
* The server only sends new statistics back to us, so in order to show all statistics we need to cache existing ones.
|
||||
*/
|
||||
private final Map<Statistic, Integer> statistics = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Whether we're expecting statistics to be sent back to us.
|
||||
*/
|
||||
@Setter
|
||||
private boolean waitingForStatistics = false;
|
||||
|
||||
@Setter
|
||||
private List<UUID> selectedEmotes = new ArrayList<>();
|
||||
private final Set<UUID> emotes = new HashSet<>();
|
||||
|
||||
private MinecraftProtocol protocol;
|
||||
|
||||
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
|
||||
@ -269,13 +301,13 @@ public class GeyserSession implements CommandSender {
|
||||
this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
|
||||
this.inventory = new PlayerInventory();
|
||||
|
||||
this.javaPacketCache = new DataCache<>();
|
||||
|
||||
this.spawned = false;
|
||||
this.loggedIn = false;
|
||||
|
||||
this.inventoryCache.getInventories().put(0, inventory);
|
||||
|
||||
connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes()));
|
||||
|
||||
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason));
|
||||
|
||||
@ -359,6 +391,8 @@ public class GeyserSession implements CommandSender {
|
||||
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
|
||||
|
||||
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
|
||||
// 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) {
|
||||
@ -421,7 +455,7 @@ public class GeyserSession implements CommandSender {
|
||||
// as it has to be extracted from a JAR
|
||||
if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
|
||||
// This should probably be left hardcoded as it will only show for en_us clients
|
||||
sendMessage("Downloading your locale (en_us) this may take some time");
|
||||
sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
|
||||
}
|
||||
|
||||
// Download and load the language for the player
|
||||
@ -541,6 +575,11 @@ public class GeyserSession implements CommandSender {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocale() {
|
||||
return clientData.getLanguageCode();
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
|
||||
if (renderDistance > 32) renderDistance = 32; // <3 u ViaVersion but I don't like crashing clients x)
|
||||
@ -717,6 +756,10 @@ public class GeyserSession implements CommandSender {
|
||||
// Required to make command blocks destroyable
|
||||
adventureSettingsPacket.setPlayerPermission(opPermissionLevel >= 2 ? PlayerPermission.OPERATOR : PlayerPermission.MEMBER);
|
||||
|
||||
// Update the noClip and worldImmutable values based on the current gamemode
|
||||
noClip = gameMode == GameMode.SPECTATOR;
|
||||
worldImmutable = gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR;
|
||||
|
||||
Set<AdventureSetting> flags = new HashSet<>();
|
||||
if (canFly) {
|
||||
flags.add(AdventureSetting.MAY_FLY);
|
||||
@ -739,4 +782,31 @@ public class GeyserSession implements CommandSender {
|
||||
adventureSettingsPacket.getSettings().addAll(flags);
|
||||
sendUpstreamPacket(adventureSettingsPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for updating statistic values since we only get changes from the server
|
||||
*
|
||||
* @param statistics Updated statistics values
|
||||
*/
|
||||
public void updateStatistics(@NonNull Map<Statistic, Integer> statistics) {
|
||||
this.statistics.putAll(statistics);
|
||||
}
|
||||
|
||||
public void refreshEmotes(List<UUID> emotes) {
|
||||
this.selectedEmotes = emotes;
|
||||
this.emotes.addAll(emotes);
|
||||
for (GeyserSession player : connector.getPlayers()) {
|
||||
List<UUID> pieces = new ArrayList<>();
|
||||
for (UUID piece : emotes) {
|
||||
if (!player.getEmotes().contains(piece)) {
|
||||
pieces.add(piece);
|
||||
}
|
||||
player.getEmotes().add(piece);
|
||||
}
|
||||
EmoteListPacket emoteList = new EmoteListPacket();
|
||||
emoteList.setRuntimeEntityId(player.getPlayerEntity().getGeyserId());
|
||||
emoteList.getPieceIds().addAll(pieces);
|
||||
player.sendUpstreamPacket(emoteList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class BossBar {
|
||||
BossEventPacket bossEventPacket = new BossEventPacket();
|
||||
bossEventPacket.setBossUniqueEntityId(entityId);
|
||||
bossEventPacket.setAction(BossEventPacket.Action.CREATE);
|
||||
bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode()));
|
||||
bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale()));
|
||||
bossEventPacket.setHealthPercentage(health);
|
||||
bossEventPacket.setColor(color); //ignored by client
|
||||
bossEventPacket.setOverlay(overlay);
|
||||
@ -72,7 +72,7 @@ public class BossBar {
|
||||
BossEventPacket bossEventPacket = new BossEventPacket();
|
||||
bossEventPacket.setBossUniqueEntityId(entityId);
|
||||
bossEventPacket.setAction(BossEventPacket.Action.UPDATE_NAME);
|
||||
bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getClientData().getLanguageCode()));
|
||||
bossEventPacket.setTitle(MessageUtils.getTranslatedBedrockMessage(title, session.getLocale()));
|
||||
|
||||
session.sendUpstreamPacket(bossEventPacket);
|
||||
}
|
||||
|
@ -27,22 +27,18 @@ package org.geysermc.connector.network.session.cache;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import lombok.Getter;
|
||||
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.network.translators.world.chunk.ChunkPosition;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.geysermc.connector.utils.MathUtils;
|
||||
|
||||
public class ChunkCache {
|
||||
|
||||
private final boolean cache;
|
||||
|
||||
@Getter
|
||||
private final Map<ChunkPosition, Column> chunks = new HashMap<>();
|
||||
private final Long2ObjectMap<Column> chunks = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
public ChunkCache(GeyserSession session) {
|
||||
if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) {
|
||||
@ -52,61 +48,74 @@ public class ChunkCache {
|
||||
}
|
||||
}
|
||||
|
||||
public void addToCache(Column chunk) {
|
||||
public Column addToCache(Column chunk) {
|
||||
if (!cache) {
|
||||
return;
|
||||
return chunk;
|
||||
}
|
||||
ChunkPosition position = new ChunkPosition(chunk.getX(), chunk.getZ());
|
||||
if (chunk.getBiomeData() == null && chunks.containsKey(position)) {
|
||||
Column newColumn = chunk;
|
||||
chunk = chunks.get(position);
|
||||
for (int i = 0; i < newColumn.getChunks().length; i++) {
|
||||
if (newColumn.getChunks()[i] != null) {
|
||||
chunk.getChunks()[i] = newColumn.getChunks()[i];
|
||||
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ());
|
||||
Column existingChunk;
|
||||
if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk
|
||||
&& (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away
|
||||
if (chunk.getChunks()[i] != null) {
|
||||
existingChunk.getChunks()[i] = chunk.getChunks()[i];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
chunks.put(position, chunk);
|
||||
}
|
||||
|
||||
public void updateBlock(Position position, int block) {
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
|
||||
if (!chunks.containsKey(chunkPosition))
|
||||
return;
|
||||
|
||||
Column column = chunks.get(chunkPosition);
|
||||
Chunk chunk = column.getChunks()[position.getY() >> 4];
|
||||
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
|
||||
if (chunk != null) {
|
||||
chunk.set(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), block);
|
||||
return changed ? existingChunk : null;
|
||||
} else {
|
||||
chunks.put(chunkPosition, chunk);
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
|
||||
public int getBlockAt(Position position) {
|
||||
public Column getChunk(int chunkX, int chunkZ) {
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
|
||||
return chunks.getOrDefault(chunkPosition, null);
|
||||
}
|
||||
|
||||
public void updateBlock(int x, int y, int z, int block) {
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
|
||||
Column column = this.getChunk(x >> 4, z >> 4);
|
||||
if (column == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk chunk = column.getChunks()[y >> 4];
|
||||
if (chunk != null) {
|
||||
chunk.set(x & 0xF, y & 0xF, z & 0xF, block);
|
||||
}
|
||||
}
|
||||
|
||||
public int getBlockAt(int x, int y, int z) {
|
||||
if (!cache) {
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
ChunkPosition chunkPosition = new ChunkPosition(position.getX() >> 4, position.getZ() >> 4);
|
||||
if (!chunks.containsKey(chunkPosition))
|
||||
return BlockTranslator.AIR;
|
||||
|
||||
Column column = chunks.get(chunkPosition);
|
||||
Chunk chunk = column.getChunks()[position.getY() >> 4];
|
||||
Position blockPosition = chunkPosition.getChunkBlock(position.getX(), position.getY(), position.getZ());
|
||||
Column column = this.getChunk(x >> 4, z >> 4);
|
||||
if (column == null) {
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
|
||||
Chunk chunk = column.getChunks()[y >> 4];
|
||||
if (chunk != null) {
|
||||
return chunk.get(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
|
||||
}
|
||||
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
|
||||
public void removeChunk(ChunkPosition position) {
|
||||
public void removeChunk(int chunkX, int chunkZ) {
|
||||
if (!cache) {
|
||||
return;
|
||||
}
|
||||
chunks.remove(position);
|
||||
|
||||
long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
|
||||
chunks.remove(chunkPosition);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@
|
||||
|
||||
package org.geysermc.connector.network.translators;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListDataPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateLightPacket;
|
||||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
@ -49,7 +48,7 @@ public class PacketTranslatorRegistry<T> {
|
||||
private static final ObjectArrayList<Class<?>> IGNORED_PACKETS = new ObjectArrayList<>();
|
||||
|
||||
static {
|
||||
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators");
|
||||
Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators") : new Reflections("org.geysermc.connector.network.translators");
|
||||
|
||||
for (Class<?> clazz : ref.getTypesAnnotatedWith(Translator.class)) {
|
||||
Class<?> packet = clazz.getAnnotation(Translator.class).packet();
|
||||
@ -75,7 +74,6 @@ public class PacketTranslatorRegistry<T> {
|
||||
}
|
||||
}
|
||||
|
||||
IGNORED_PACKETS.add(ServerKeepAlivePacket.class); // Handled by MCProtocolLib
|
||||
IGNORED_PACKETS.add(ServerUpdateLightPacket.class); // Light is handled on Bedrock for us
|
||||
IGNORED_PACKETS.add(ServerPlayerListDataPacket.class); // Cant be implemented in bedrock
|
||||
}
|
||||
|
@ -23,15 +23,18 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.session.cache;
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
|
||||
import lombok.Getter;
|
||||
import com.nukkitx.protocol.bedrock.packet.EmoteListPacket;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@Translator(packet = EmoteListPacket.class)
|
||||
public class BedrockEmoteListTranslator extends PacketTranslator<EmoteListPacket> {
|
||||
|
||||
public class DataCache<T> {
|
||||
|
||||
@Getter
|
||||
private Map<String, T> cachedValues = new HashMap<String, T>();
|
||||
@Override
|
||||
public void translate(EmoteListPacket packet, GeyserSession session) {
|
||||
session.refreshEmotes(packet.getPieceIds());
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ 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.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
@ -58,8 +59,11 @@ 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;
|
||||
|
||||
@Translator(packet = InventoryTransactionPacket.class)
|
||||
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
|
||||
|
||||
@ -119,18 +123,19 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
session.sendDownstreamPacket(itemPacket);
|
||||
}
|
||||
// Check actions, otherwise buckets may be activated when block inventories are accessed
|
||||
// But don't check actions if the item damage is 0
|
||||
else if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId() &&
|
||||
(packet.getItemInHand().getDamage() == 0 || !packet.getActions().isEmpty())) {
|
||||
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
|
||||
session.sendDownstreamPacket(itemPacket);
|
||||
|
||||
else if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId()) {
|
||||
// Let the server decide if the bucket item should change, not the client, and revert the changes the client made
|
||||
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||
slotPacket.setContainerId(ContainerId.INVENTORY);
|
||||
slotPacket.setSlot(packet.getHotbarSlot());
|
||||
slotPacket.setItem(packet.getItemInHand());
|
||||
session.sendUpstreamPacket(slotPacket);
|
||||
// Delay the interaction in case the client doesn't intend to actually use the bucket
|
||||
// See BedrockActionTranslator.java
|
||||
session.setBucketScheduledFuture(session.getConnector().getGeneralThreadPool().schedule(() -> {
|
||||
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
|
||||
session.sendDownstreamPacket(itemPacket);
|
||||
}, 5, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
if (packet.getActions().isEmpty()) {
|
||||
@ -151,28 +156,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
}
|
||||
}
|
||||
|
||||
Vector3i blockPos = packet.getBlockPosition();
|
||||
// TODO: Find a better way to do this?
|
||||
switch (packet.getBlockFace()) {
|
||||
case 0:
|
||||
blockPos = blockPos.sub(0, 1, 0);
|
||||
break;
|
||||
case 1:
|
||||
blockPos = blockPos.add(0, 1, 0);
|
||||
break;
|
||||
case 2:
|
||||
blockPos = blockPos.sub(0, 0, 1);
|
||||
break;
|
||||
case 3:
|
||||
blockPos = blockPos.add(0, 0, 1);
|
||||
break;
|
||||
case 4:
|
||||
blockPos = blockPos.sub(1, 0, 0);
|
||||
break;
|
||||
case 5:
|
||||
blockPos = blockPos.add(1, 0, 0);
|
||||
break;
|
||||
}
|
||||
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
|
||||
ItemEntry handItem = ItemRegistry.getItem(packet.getItemInHand());
|
||||
if (handItem.isBlock()) {
|
||||
session.setLastBlockPlacePosition(blockPos);
|
||||
@ -187,10 +171,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
break;
|
||||
}
|
||||
|
||||
// Handled in ITEM_USE
|
||||
// Handled in ITEM_USE if the item is not milk
|
||||
if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemRegistry.BUCKET.getBedrockId() &&
|
||||
// Normal bucket, water bucket, lava bucket
|
||||
(packet.getItemInHand().getDamage() == 0 || packet.getItemInHand().getDamage() == 8 || packet.getItemInHand().getDamage() == 10)) {
|
||||
packet.getItemInHand().getDamage() != 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -269,9 +252,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
}
|
||||
break;
|
||||
case 1: //Attack
|
||||
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
|
||||
InteractAction.ATTACK, session.isSneaking());
|
||||
session.sendDownstreamPacket(attackPacket);
|
||||
if (entity.getEntityType() == EntityType.ENDER_DRAGON) {
|
||||
// Redirects the attack to its body entity, this only happens when
|
||||
// attacking the underbelly of the ender dragon
|
||||
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId() + 3,
|
||||
InteractAction.ATTACK, session.isSneaking());
|
||||
session.sendDownstreamPacket(attackPacket);
|
||||
} else {
|
||||
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
|
||||
InteractAction.ATTACK, session.isSneaking());
|
||||
session.sendDownstreamPacket(attackPacket);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@ -40,7 +40,8 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
|
||||
@Override
|
||||
public void translate(MobEquipmentPacket packet, GeyserSession session) {
|
||||
if (!session.isSpawned() || packet.getHotbarSlot() > 8 ||
|
||||
packet.getContainerId() != ContainerId.INVENTORY) {
|
||||
packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().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;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.connector.network.translators.bedrock;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientKeepAlivePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
|
||||
/**
|
||||
* Used to send the keep alive packet back to the server
|
||||
*/
|
||||
@Translator(packet = NetworkStackLatencyPacket.class)
|
||||
public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<NetworkStackLatencyPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(NetworkStackLatencyPacket packet, GeyserSession session) {
|
||||
// The client sends a timestamp back but it's rounded and therefore unreliable when we need the exact number
|
||||
ClientKeepAlivePacket keepAlivePacket = new ClientKeepAlivePacket(session.getLastKeepAliveTimestamp());
|
||||
session.sendDownstreamPacket(keepAlivePacket);
|
||||
}
|
||||
}
|
@ -28,7 +28,10 @@ package org.geysermc.connector.network.translators.bedrock;
|
||||
import com.github.steveice10.mc.protocol.data.game.ClientRequest;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientRequestPacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.RespawnPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.geysermc.connector.entity.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
@ -39,12 +42,30 @@ public class BedrockRespawnTranslator extends PacketTranslator<RespawnPacket> {
|
||||
@Override
|
||||
public void translate(RespawnPacket packet, GeyserSession session) {
|
||||
if (packet.getState() == RespawnPacket.State.CLIENT_READY) {
|
||||
if (!session.isSpawned()) { // Otherwise when immediate respawn is on the client never loads
|
||||
RespawnPacket respawnPacket = new RespawnPacket();
|
||||
respawnPacket.setRuntimeEntityId(0);
|
||||
respawnPacket.setPosition(Vector3f.ZERO);
|
||||
respawnPacket.setState(RespawnPacket.State.SERVER_READY);
|
||||
session.sendUpstreamPacket(respawnPacket);
|
||||
// Previously we only sent the respawn packet before the server finished loading
|
||||
// The message included was 'Otherwise when immediate respawn is on the client never loads'
|
||||
// But I assume the new if statement below fixes that problem
|
||||
RespawnPacket respawnPacket = new RespawnPacket();
|
||||
respawnPacket.setRuntimeEntityId(0);
|
||||
respawnPacket.setPosition(Vector3f.ZERO);
|
||||
respawnPacket.setState(RespawnPacket.State.SERVER_READY);
|
||||
session.sendUpstreamPacket(respawnPacket);
|
||||
|
||||
if (session.isSpawned()) {
|
||||
// Client might be stuck; resend spawn information
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
if (entity == null) return;
|
||||
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
|
||||
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
entityDataPacket.getMetadata().putAll(entity.getMetadata());
|
||||
session.sendUpstreamPacket(entityDataPacket);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
movePlayerPacket.setPosition(entity.getPosition());
|
||||
movePlayerPacket.setRotation(entity.getBedrockRotation());
|
||||
movePlayerPacket.setMode(MovePlayerPacket.Mode.RESPAWN);
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
}
|
||||
|
||||
ClientRequestPacket javaRespawnPacket = new ClientRequestPacket(ClientRequest.RESPAWN);
|
||||
|
@ -44,6 +44,7 @@ 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.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.utils.BlockUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -67,6 +68,8 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||
eventPacket.setType(EntityEventType.RESPAWN);
|
||||
eventPacket.setData(0);
|
||||
session.sendUpstreamPacket(eventPacket);
|
||||
// Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server
|
||||
entity.updateBedrockAttributes(session);
|
||||
break;
|
||||
case START_SWIMMING:
|
||||
ClientPlayerStatePacket startSwimPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SPRINTING);
|
||||
@ -113,19 +116,24 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||
session.sendDownstreamPacket(stopSleepingPacket);
|
||||
break;
|
||||
case BLOCK_INTERACT:
|
||||
// Handled in BedrockInventoryTransactionTranslator
|
||||
// Client means to interact with a block; cancel bucket interaction, if any
|
||||
if (session.getBucketScheduledFuture() != null) {
|
||||
session.getBucketScheduledFuture().cancel(true);
|
||||
session.setBucketScheduledFuture(null);
|
||||
}
|
||||
// Otherwise handled in BedrockInventoryTransactionTranslator
|
||||
break;
|
||||
case START_BREAK:
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
if (packet.getFace() == BlockFace.UP.ordinal()) {
|
||||
int blockUp = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().add(0, 1, 0));
|
||||
String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp);
|
||||
if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) {
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY() + 1, packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
|
||||
session.sendDownstreamPacket(startBreakingPacket);
|
||||
break;
|
||||
}
|
||||
// Account for fire - the client likes to hit the block behind.
|
||||
Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace());
|
||||
int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos);
|
||||
String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp);
|
||||
if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) {
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(),
|
||||
fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]);
|
||||
session.sendDownstreamPacket(startBreakingPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
|
@ -32,10 +32,11 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import lombok.NonNull;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.utils.FileUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
@ -50,8 +51,19 @@ public class EffectRegistry {
|
||||
public static final Map<SoundEffect, Effect> SOUND_EFFECTS = new HashMap<>();
|
||||
public static final Int2ObjectMap<SoundEvent> RECORDS = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static Map<ParticleType, LevelEventType> particleTypeMap = new HashMap<>();
|
||||
private static Map<ParticleType, String> particleStringMap = new HashMap<>();
|
||||
/**
|
||||
* Java particle type to Bedrock particle ID
|
||||
* Used for area effect clouds.
|
||||
*/
|
||||
private static final Object2IntMap<ParticleType> PARTICLE_TO_ID = new Object2IntOpenHashMap<>();
|
||||
/**
|
||||
* Java particle type to Bedrock level event
|
||||
*/
|
||||
private static final Map<ParticleType, LevelEventType> PARTICLE_TO_LEVEL_EVENT = new HashMap<>();
|
||||
/**
|
||||
* Java particle type to Bedrock namespaced string ID
|
||||
*/
|
||||
private static final Map<ParticleType, String> PARTICLE_TO_STRING = new HashMap<>();
|
||||
|
||||
public static void init() {
|
||||
// no-op
|
||||
@ -68,22 +80,24 @@ public class EffectRegistry {
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<String, JsonNode>> particlesIterator = particleEntries.fields();
|
||||
while (particlesIterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = particlesIterator.next();
|
||||
try {
|
||||
particleTypeMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(entry.getValue().asText().toUpperCase()));
|
||||
} catch (IllegalArgumentException e1) {
|
||||
try {
|
||||
particleStringMap.put(ParticleType.valueOf(entry.getKey().toUpperCase()), entry.getValue().asText());
|
||||
GeyserConnector.getInstance().getLogger().debug("Force to map particle "
|
||||
+ entry.getKey()
|
||||
+ "=>"
|
||||
+ entry.getValue().asText()
|
||||
+ ", it will take effect.");
|
||||
} catch (IllegalArgumentException e2){
|
||||
GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.particle.failed_map", entry.getKey(), entry.getValue().asText()));
|
||||
try {
|
||||
while (particlesIterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = particlesIterator.next();
|
||||
JsonNode bedrockId = entry.getValue().get("bedrockId");
|
||||
JsonNode bedrockIdNumeric = entry.getValue().get("bedrockNumericId");
|
||||
JsonNode eventType = entry.getValue().get("eventType");
|
||||
if (bedrockIdNumeric != null) {
|
||||
PARTICLE_TO_ID.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockIdNumeric.asInt());
|
||||
}
|
||||
if (bedrockId != null) {
|
||||
PARTICLE_TO_STRING.put(ParticleType.valueOf(entry.getKey().toUpperCase()), bedrockId.asText());
|
||||
}
|
||||
if (eventType != null) {
|
||||
PARTICLE_TO_LEVEL_EVENT.put(ParticleType.valueOf(entry.getKey().toUpperCase()), LevelEventType.valueOf(eventType.asText().toUpperCase()));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
/* Load effects */
|
||||
@ -149,11 +163,27 @@ public class EffectRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) {
|
||||
return particleTypeMap.getOrDefault(type, null);
|
||||
/**
|
||||
* @param type the Java particle to search for
|
||||
* @return the Bedrock integer ID of the particle, or -1 if it does not exist
|
||||
*/
|
||||
public static int getParticleId(@NonNull ParticleType type) {
|
||||
return PARTICLE_TO_ID.getOrDefault(type, -1);
|
||||
}
|
||||
|
||||
public static String getParticleString(@NonNull ParticleType type){
|
||||
return particleStringMap.getOrDefault(type, null);
|
||||
/**
|
||||
* @param type the Java particle to search for
|
||||
* @return the level event equivalent Bedrock particle
|
||||
*/
|
||||
public static LevelEventType getParticleLevelEventType(@NonNull ParticleType type) {
|
||||
return PARTICLE_TO_LEVEL_EVENT.getOrDefault(type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param type the Java particle to search for
|
||||
* @return the namespaced ID equivalent for Bedrock
|
||||
*/
|
||||
public static String getParticleString(@NonNull ParticleType type) {
|
||||
return PARTICLE_TO_STRING.getOrDefault(type, null);
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator {
|
||||
|
||||
private String toRomanNumeral(GeyserSession session, int level) {
|
||||
return LocaleUtils.getLocaleString("enchantment.level." + level,
|
||||
session.getClientData().getLanguageCode());
|
||||
session.getLocale());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,7 +261,7 @@ public class EnchantmentInventoryTranslator extends BlockInventoryTranslator {
|
||||
|
||||
public String toString(GeyserSession session) {
|
||||
return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(),
|
||||
session.getClientData().getLanguageCode());
|
||||
session.getLocale());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,8 @@ import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.geysermc.connector.inventory.Inventory;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
|
||||
import org.geysermc.connector.utils.LocaleUtils;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class BlockInventoryHolder extends InventoryHolder {
|
||||
@ -60,7 +59,7 @@ public class BlockInventoryHolder extends InventoryHolder {
|
||||
.putInt("x", position.getX())
|
||||
.putInt("y", position.getY())
|
||||
.putInt("z", position.getZ())
|
||||
.putString("CustomName", LocaleUtils.getLocaleString(inventory.getTitle(), session.getClientData().getLanguageCode())).build();
|
||||
.putString("CustomName", inventory.getTitle()).build();
|
||||
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag);
|
||||
dataPacket.setBlockPosition(position);
|
||||
|
@ -63,6 +63,10 @@ public class ItemRegistry {
|
||||
* Bucket item entry, used in BedrockInventoryTransactionTranslator.java
|
||||
*/
|
||||
public static ItemEntry BUCKET;
|
||||
/**
|
||||
* Egg item entry, used in JavaEntityStatusTranslator.java
|
||||
*/
|
||||
public static ItemEntry EGG;
|
||||
/**
|
||||
* Gold item entry, used in PiglinEntity.java
|
||||
*/
|
||||
@ -141,6 +145,9 @@ public class ItemRegistry {
|
||||
case "minecraft:oak_boat":
|
||||
BOAT = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
case "minecraft:egg":
|
||||
EGG = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
case "minecraft:gold_ingot":
|
||||
GOLD = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
@ -197,7 +204,9 @@ public class ItemRegistry {
|
||||
*/
|
||||
public static ItemEntry getItem(ItemData data) {
|
||||
for (ItemEntry itemEntry : ITEM_ENTRIES.values()) {
|
||||
if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) {
|
||||
if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() ||
|
||||
// Make exceptions for potions and tipped arrows, whose damage values can vary
|
||||
(itemEntry.getJavaIdentifier().endsWith("potion") || itemEntry.getJavaIdentifier().equals("minecraft:arrow")))) {
|
||||
return itemEntry;
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public abstract class ItemTranslator {
|
||||
|
||||
static {
|
||||
/* Load item translators */
|
||||
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item");
|
||||
Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.item") : new Reflections("org.geysermc.connector.network.translators.item");
|
||||
|
||||
Map<NbtItemStackTranslator, Integer> loadedNbtItemTranslators = new HashMap<>();
|
||||
for (Class<?> clazz : ref.getTypesAnnotatedWith(ItemRemapper.class)) {
|
||||
@ -400,7 +400,7 @@ public abstract class ItemTranslator {
|
||||
// Check if its a message to translate
|
||||
if (MessageUtils.isMessage(name)) {
|
||||
// Get the translated name
|
||||
name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getClientData().getLanguageCode());
|
||||
name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(name), session.getLocale());
|
||||
|
||||
// Add the new name tag
|
||||
display.put(new StringTag("Name", name));
|
||||
|
@ -48,7 +48,7 @@ public enum Potion {
|
||||
STRONG_SWIFTNESS(16),
|
||||
LONG_SWIFTNESS(15),
|
||||
SLOWNESS(17),
|
||||
STRONG_SLOWNESS(18), //does not exist
|
||||
STRONG_SLOWNESS(42),
|
||||
LONG_SLOWNESS(18),
|
||||
WATER_BREATHING(19),
|
||||
LONG_WATER_BREATHING(20),
|
||||
|
@ -34,8 +34,7 @@ import org.geysermc.connector.utils.FileUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Manages any recipe-related storing
|
||||
@ -46,22 +45,45 @@ public class RecipeRegistry {
|
||||
* A list of all possible leather armor dyeing recipes.
|
||||
* Created manually.
|
||||
*/
|
||||
public static List<CraftingData> LEATHER_DYEING_RECIPES = new ObjectArrayList<>();
|
||||
public static final List<CraftingData> LEATHER_DYEING_RECIPES = new ObjectArrayList<>();
|
||||
/**
|
||||
* A list of all possible firework rocket recipes, including the base rocket.
|
||||
* Obtained from a ProxyPass dump of protocol v407
|
||||
*/
|
||||
public static List<CraftingData> FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>(21);
|
||||
public static final List<CraftingData> FIREWORK_ROCKET_RECIPES = new ObjectArrayList<>();
|
||||
/**
|
||||
* A list of all possible firework star recipes.
|
||||
* Obtained from a ProxyPass dump of protocol v407
|
||||
*/
|
||||
public static List<CraftingData> FIREWORK_STAR_RECIPES = new ObjectArrayList<>(40);
|
||||
public static final List<CraftingData> FIREWORK_STAR_RECIPES = new ObjectArrayList<>();
|
||||
/**
|
||||
* A list of all possible shulker box dyeing options.
|
||||
* Obtained from a ProxyPass dump of protocol v407
|
||||
*/
|
||||
public static List<CraftingData> SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>();
|
||||
public static final List<CraftingData> SHULKER_BOX_DYEING_RECIPES = new ObjectArrayList<>();
|
||||
/**
|
||||
* A list of all possible suspicious stew recipes.
|
||||
* Obtained from a ProxyPass dump of protocol v407
|
||||
*/
|
||||
public static final List<CraftingData> SUSPICIOUS_STEW_RECIPES = new ObjectArrayList<>();
|
||||
/**
|
||||
* A list of all possible tipped arrow recipes.
|
||||
* Obtained from a ProxyPass dump of protocol v407
|
||||
*/
|
||||
public static final List<CraftingData> TIPPED_ARROW_RECIPES = new ObjectArrayList<>();
|
||||
|
||||
// TODO: These are the other "multi" UUIDs that supposedly enable various recipes. Find out what each enables.
|
||||
// 442d85ed-8272-4543-a6f1-418f90ded05d 8b36268c-1829-483c-a0f1-993b7156a8f2 602234e4-cac1-4353-8bb7-b1ebff70024b 98c84b38-1085-46bd-b1ce-dd38c159e6cc
|
||||
// d81aaeaf-e172-4440-9225-868df030d27b b5c5d105-75a2-4076-af2b-923ea2bf4bf0 00000000-0000-0000-0000-000000000002 85939755-ba10-4d9d-a4cc-efb7a8e943c4
|
||||
// d392b075-4ba1-40ae-8789-af868d56f6ce aecd2294-4b94-434b-8667-4499bb2c9327
|
||||
/**
|
||||
* Recipe data that, when sent to the client, enables book cloning
|
||||
*/
|
||||
public static final CraftingData BOOK_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"));
|
||||
/**
|
||||
* Recipe data that, when sent to the client, enables tool repairing in a crafting table
|
||||
*/
|
||||
public static final CraftingData TOOL_REPAIRING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("00000000-0000-0000-0000-000000000001"));
|
||||
|
||||
static {
|
||||
// Get all recipes that are not directly sent from a Java server
|
||||
@ -88,6 +110,12 @@ public class RecipeRegistry {
|
||||
for (JsonNode entry : items.get("shulker_boxes")) {
|
||||
SHULKER_BOX_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry));
|
||||
}
|
||||
for (JsonNode entry : items.get("suspicious_stew")) {
|
||||
SUSPICIOUS_STEW_RECIPES.add(getCraftingDataFromJsonNode(entry));
|
||||
}
|
||||
for (JsonNode entry : items.get("tipped_arrows")) {
|
||||
TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,11 +125,41 @@ public class RecipeRegistry {
|
||||
*/
|
||||
private static CraftingData getCraftingDataFromJsonNode(JsonNode node) {
|
||||
ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0));
|
||||
UUID uuid = UUID.randomUUID();
|
||||
if (node.get("type").asInt() == 1) {
|
||||
// Shaped recipe
|
||||
List<String> shape = new ArrayList<>();
|
||||
// Get the shape of the recipe
|
||||
for (JsonNode chars : node.get("shape")) {
|
||||
shape.add(chars.asText());
|
||||
}
|
||||
|
||||
// In recipes.json each recipe is mapped by a letter
|
||||
Map<String, ItemData> letterToRecipe = new HashMap<>();
|
||||
Iterator<Map.Entry<String, JsonNode>> iterator = node.get("input").fields();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, JsonNode> entry = iterator.next();
|
||||
letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue()));
|
||||
}
|
||||
|
||||
ItemData[] inputs = new ItemData[shape.size() * shape.get(0).length()];
|
||||
int i = 0;
|
||||
// Create a linear array of items from the "cube" of the shape
|
||||
for (int j = 0; i < shape.size() * shape.get(0).length(); j++) {
|
||||
for (char c : shape.get(j).toCharArray()) {
|
||||
ItemData data = letterToRecipe.getOrDefault(String.valueOf(c), ItemData.AIR);
|
||||
inputs[i] = data;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(),
|
||||
inputs, new ItemData[]{output}, uuid, "crafting_table", 0);
|
||||
}
|
||||
List<ItemData> inputs = new ObjectArrayList<>();
|
||||
for (JsonNode entry : node.get("input")) {
|
||||
inputs.add(ItemRegistry.getBedrockItemFromJson(entry));
|
||||
}
|
||||
UUID uuid = UUID.randomUUID();
|
||||
if (node.get("type").asInt() == 5) {
|
||||
// Shulker box
|
||||
return CraftingData.fromShulkerBox(uuid.toString(),
|
||||
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.connector.network.translators.item;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Potion identifiers and their respective Bedrock IDs used with arrows.
|
||||
* https://minecraft.gamepedia.com/Arrow#Item_Data
|
||||
*/
|
||||
@Getter
|
||||
public enum TippedArrowPotion {
|
||||
MUNDANE(2, ArrowParticleColors.NONE), // 3 is extended?
|
||||
THICK(4, ArrowParticleColors.NONE),
|
||||
AWKWARD(5, ArrowParticleColors.NONE),
|
||||
NIGHT_VISION(6, ArrowParticleColors.NIGHT_VISION),
|
||||
LONG_NIGHT_VISION(7, ArrowParticleColors.NIGHT_VISION),
|
||||
INVISIBILITY(8, ArrowParticleColors.INVISIBILITY),
|
||||
LONG_INVISIBILITY(9, ArrowParticleColors.INVISIBILITY),
|
||||
LEAPING(10, ArrowParticleColors.LEAPING),
|
||||
LONG_LEAPING(11, ArrowParticleColors.LEAPING),
|
||||
STRONG_LEAPING(12, ArrowParticleColors.LEAPING),
|
||||
FIRE_RESISTANCE(13, ArrowParticleColors.FIRE_RESISTANCE),
|
||||
LONG_FIRE_RESISTANCE(14, ArrowParticleColors.FIRE_RESISTANCE),
|
||||
SWIFTNESS(15, ArrowParticleColors.SWIFTNESS),
|
||||
LONG_SWIFTNESS(16, ArrowParticleColors.SWIFTNESS),
|
||||
STRONG_SWIFTNESS(17, ArrowParticleColors.SWIFTNESS),
|
||||
SLOWNESS(18, ArrowParticleColors.SLOWNESS),
|
||||
LONG_SLOWNESS(19, ArrowParticleColors.SLOWNESS),
|
||||
STRONG_SLOWNESS(43, ArrowParticleColors.SLOWNESS),
|
||||
WATER_BREATHING(20, ArrowParticleColors.WATER_BREATHING),
|
||||
LONG_WATER_BREATHING(21, ArrowParticleColors.WATER_BREATHING),
|
||||
HEALING(22, ArrowParticleColors.HEALING),
|
||||
STRONG_HEALING(23, ArrowParticleColors.HEALING),
|
||||
HARMING(24, ArrowParticleColors.HARMING),
|
||||
STRONG_HARMING(25, ArrowParticleColors.HARMING),
|
||||
POISON(26, ArrowParticleColors.POISON),
|
||||
LONG_POISON(27, ArrowParticleColors.POISON),
|
||||
STRONG_POISON(28, ArrowParticleColors.POISON),
|
||||
REGENERATION(29, ArrowParticleColors.REGENERATION),
|
||||
LONG_REGENERATION(30, ArrowParticleColors.REGENERATION),
|
||||
STRONG_REGENERATION(31, ArrowParticleColors.REGENERATION),
|
||||
STRENGTH(32, ArrowParticleColors.STRENGTH),
|
||||
LONG_STRENGTH(33, ArrowParticleColors.STRENGTH),
|
||||
STRONG_STRENGTH(34, ArrowParticleColors.STRENGTH),
|
||||
WEAKNESS(35, ArrowParticleColors.WEAKNESS),
|
||||
LONG_WEAKNESS(36, ArrowParticleColors.WEAKNESS),
|
||||
LUCK(2, ArrowParticleColors.NONE), // does not exist in Bedrock
|
||||
TURTLE_MASTER(38, ArrowParticleColors.TURTLE_MASTER),
|
||||
LONG_TURTLE_MASTER(39, ArrowParticleColors.TURTLE_MASTER),
|
||||
STRONG_TURTLE_MASTER(40, ArrowParticleColors.TURTLE_MASTER),
|
||||
SLOW_FALLING(41, ArrowParticleColors.SLOW_FALLING),
|
||||
LONG_SLOW_FALLING(42, ArrowParticleColors.SLOW_FALLING);
|
||||
|
||||
private final String javaIdentifier;
|
||||
private final short bedrockId;
|
||||
/**
|
||||
* The Java color associated with this ID.
|
||||
* Used for looking up Java arrow color entity metadata as Bedrock potion IDs, which is what is used for entities in Bedrock
|
||||
*/
|
||||
private final int javaColor;
|
||||
|
||||
TippedArrowPotion(int bedrockId, ArrowParticleColors arrowParticleColor) {
|
||||
this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH);
|
||||
this.bedrockId = (short) bedrockId;
|
||||
this.javaColor = arrowParticleColor.getColor();
|
||||
}
|
||||
|
||||
public static TippedArrowPotion getByJavaIdentifier(String javaIdentifier) {
|
||||
for (TippedArrowPotion potion : TippedArrowPotion.values()) {
|
||||
if (potion.javaIdentifier.equals(javaIdentifier)) {
|
||||
return potion;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TippedArrowPotion getByBedrockId(short bedrockId) {
|
||||
for (TippedArrowPotion potion : TippedArrowPotion.values()) {
|
||||
if (potion.bedrockId == bedrockId) {
|
||||
return potion;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param color the potion color to look up
|
||||
* @return the tipped arrow potion that most closely resembles that color.
|
||||
*/
|
||||
public static TippedArrowPotion getByJavaColor(int color) {
|
||||
for (TippedArrowPotion potion : TippedArrowPotion.values()) {
|
||||
if (potion.javaColor == color) {
|
||||
return potion;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private enum ArrowParticleColors {
|
||||
NONE(-1),
|
||||
NIGHT_VISION(2039713),
|
||||
INVISIBILITY(8356754),
|
||||
LEAPING(2293580),
|
||||
FIRE_RESISTANCE(14981690),
|
||||
SWIFTNESS(8171462),
|
||||
SLOWNESS(5926017),
|
||||
TURTLE_MASTER(7691106),
|
||||
WATER_BREATHING(3035801),
|
||||
HEALING(16262179),
|
||||
HARMING(4393481),
|
||||
POISON(5149489),
|
||||
REGENERATION(13458603),
|
||||
STRENGTH(9643043),
|
||||
WEAKNESS(4738376),
|
||||
LUCK(3381504),
|
||||
SLOW_FALLING(16773073);
|
||||
|
||||
@Getter
|
||||
private final int color;
|
||||
|
||||
ArrowParticleColors(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ import java.util.stream.Collectors;
|
||||
@ItemRemapper
|
||||
public class BannerTranslator extends ItemTranslator {
|
||||
|
||||
private List<ItemEntry> appliedItems;
|
||||
private final List<ItemEntry> appliedItems;
|
||||
|
||||
public BannerTranslator() {
|
||||
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("banner")).collect(Collectors.toList());
|
||||
|
@ -40,7 +40,7 @@ import java.util.stream.Collectors;
|
||||
@ItemRemapper
|
||||
public class CompassTranslator extends ItemTranslator {
|
||||
|
||||
private List<ItemEntry> appliedItems;
|
||||
private final List<ItemEntry> appliedItems;
|
||||
|
||||
public CompassTranslator() {
|
||||
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("compass")).collect(Collectors.toList());
|
||||
|
@ -42,7 +42,7 @@ import java.util.stream.Collectors;
|
||||
@ItemRemapper
|
||||
public class PotionTranslator extends ItemTranslator {
|
||||
|
||||
private List<ItemEntry> appliedItems;
|
||||
private final List<ItemEntry> appliedItems;
|
||||
|
||||
public PotionTranslator() {
|
||||
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry -> entry.getJavaIdentifier().endsWith("potion")).collect(Collectors.toList());
|
||||
@ -57,7 +57,7 @@ public class PotionTranslator extends ItemTranslator {
|
||||
if (potion != null) {
|
||||
return ItemData.of(itemEntry.getBedrockId(), potion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt()));
|
||||
}
|
||||
GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue());
|
||||
GeyserConnector.getInstance().getLogger().debug("Unknown Java potion: " + potionTag.getValue());
|
||||
}
|
||||
return super.translateToBedrock(itemStack, itemEntry);
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.connector.network.translators.item.translators;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.translators.ItemRemapper;
|
||||
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.item.TippedArrowPotion;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ItemRemapper
|
||||
public class TippedArrowTranslator extends ItemTranslator {
|
||||
|
||||
private final List<ItemEntry> appliedItems;
|
||||
|
||||
private static final int TIPPED_ARROW_JAVA_ID = ItemRegistry.getItemEntry("minecraft:tipped_arrow").getJavaId();
|
||||
|
||||
public TippedArrowTranslator() {
|
||||
appliedItems = ItemRegistry.ITEM_ENTRIES.values().stream().filter(entry ->
|
||||
entry.getJavaIdentifier().contains("arrow") && !entry.getJavaIdentifier().contains("spectral")).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemData translateToBedrock(ItemStack itemStack, ItemEntry itemEntry) {
|
||||
if (!itemEntry.getJavaIdentifier().equals("minecraft:tipped_arrow") || itemStack.getNbt() == null) {
|
||||
// We're only concerned about minecraft:arrow when translating Bedrock -> Java
|
||||
return super.translateToBedrock(itemStack, itemEntry);
|
||||
}
|
||||
Tag potionTag = itemStack.getNbt().get("Potion");
|
||||
if (potionTag instanceof StringTag) {
|
||||
TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByJavaIdentifier(((StringTag) potionTag).getValue());
|
||||
if (tippedArrowPotion != null) {
|
||||
return ItemData.of(itemEntry.getBedrockId(), tippedArrowPotion.getBedrockId(), itemStack.getAmount(), translateNbtToBedrock(itemStack.getNbt()));
|
||||
}
|
||||
GeyserConnector.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionTag.getValue());
|
||||
}
|
||||
return super.translateToBedrock(itemStack, itemEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) {
|
||||
TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage());
|
||||
ItemStack itemStack = super.translateToJava(itemData, itemEntry);
|
||||
if (tippedArrowPotion != null) {
|
||||
itemStack = new ItemStack(TIPPED_ARROW_JAVA_ID, itemStack.getAmount(), itemStack.getNbt());
|
||||
StringTag potionTag = new StringTag("Potion", tippedArrowPotion.getJavaIdentifier());
|
||||
itemStack.getNbt().put(potionTag);
|
||||
}
|
||||
return itemStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemEntry> getAppliedItems() {
|
||||
return appliedItems;
|
||||
}
|
||||
}
|
@ -101,7 +101,7 @@ public class BasicItemTranslator extends NbtItemStackTranslator {
|
||||
if (message.startsWith("§r")) {
|
||||
message = message.replaceFirst("§r", "");
|
||||
}
|
||||
Component component = TextComponent.of(message);
|
||||
Component component = Component.text(message);
|
||||
return GsonComponentSerializer.gson().serialize(component);
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,15 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.item.translators.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
|
||||
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.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.ItemRemapper;
|
||||
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
|
||||
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.item.NbtItemStackTranslator;
|
||||
|
||||
@ItemRemapper
|
||||
public class CrossbowTranslator extends NbtItemStackTranslator {
|
||||
@ -44,12 +45,17 @@ public class CrossbowTranslator extends NbtItemStackTranslator {
|
||||
if (!chargedProjectiles.getValue().isEmpty()) {
|
||||
CompoundTag projectile = (CompoundTag) chargedProjectiles.getValue().get(0);
|
||||
|
||||
CompoundTag newProjectile = new CompoundTag("chargedItem");
|
||||
newProjectile.put(new ByteTag("Count", (byte) projectile.get("Count").getValue()));
|
||||
newProjectile.put(new StringTag("Name", (String) projectile.get("id").getValue()));
|
||||
ItemEntry entry = ItemRegistry.getItemEntry((String) projectile.get("id").getValue());
|
||||
if (entry == null) return;
|
||||
CompoundTag tag = projectile.get("tag");
|
||||
ItemStack itemStack = new ItemStack(itemEntry.getJavaId(), (byte) projectile.get("Count").getValue(), tag);
|
||||
ItemData itemData = ItemTranslator.translateToBedrock(session, itemStack);
|
||||
|
||||
// Not sure what this is for
|
||||
newProjectile.put(new ByteTag("Damage", (byte) 0));
|
||||
CompoundTag newProjectile = new CompoundTag("chargedItem");
|
||||
newProjectile.put(new ByteTag("Count", (byte) itemData.getCount()));
|
||||
newProjectile.put(new StringTag("Name", ItemRegistry.getBedrockIdentifer(entry)));
|
||||
|
||||
newProjectile.put(new ShortTag("Damage", itemData.getDamage()));
|
||||
|
||||
itemTag.put(newProjectile);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public class JavaChatTranslator extends PacketTranslator<ServerChatPacket> {
|
||||
break;
|
||||
}
|
||||
|
||||
String locale = session.getClientData().getLanguageCode();
|
||||
String locale = session.getLocale();
|
||||
|
||||
if (packet.getMessage() instanceof TranslationMessage) {
|
||||
textPacket.setType(TextPacket.Type.TRANSLATION);
|
||||
|
@ -80,8 +80,19 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// These recipes are enabled by sending a special recipe
|
||||
case CRAFTING_SPECIAL_BOOKCLONING: {
|
||||
craftingDataPacket.getCraftingData().add(RecipeRegistry.BOOK_CLONING_RECIPE_DATA);
|
||||
break;
|
||||
}
|
||||
case CRAFTING_SPECIAL_REPAIRITEM: {
|
||||
craftingDataPacket.getCraftingData().add(RecipeRegistry.TOOL_REPAIRING_RECIPE_DATA);
|
||||
break;
|
||||
}
|
||||
|
||||
// Java doesn't actually tell us the recipes so we need to calculate this ahead of time.
|
||||
case CRAFTING_SPECIAL_FIREWORK_ROCKET: {
|
||||
// Java doesn't actually tell us the recipes so we need to calculate this ahead of time.
|
||||
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.FIREWORK_ROCKET_RECIPES);
|
||||
break;
|
||||
}
|
||||
@ -93,6 +104,14 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator<ServerDeclare
|
||||
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.SHULKER_BOX_DYEING_RECIPES);
|
||||
break;
|
||||
}
|
||||
case CRAFTING_SPECIAL_SUSPICIOUSSTEW: {
|
||||
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.SUSPICIOUS_STEW_RECIPES);
|
||||
break;
|
||||
}
|
||||
case CRAFTING_SPECIAL_TIPPEDARROW: {
|
||||
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.TIPPED_ARROW_RECIPES);
|
||||
break;
|
||||
}
|
||||
case CRAFTING_SPECIAL_ARMORDYE: {
|
||||
// This one's even worse since it's not actually on Bedrock, but it still works!
|
||||
craftingDataPacket.getCraftingData().addAll(RecipeRegistry.LEATHER_DYEING_RECIPES);
|
||||
|
@ -36,6 +36,6 @@ public class JavaDisconnectPacket extends PacketTranslator<ServerDisconnectPacke
|
||||
|
||||
@Override
|
||||
public void translate(ServerDisconnectPacket packet, GeyserSession session) {
|
||||
session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode(), true));
|
||||
session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getLocale(), true));
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
|
||||
session.setRenderDistance(packet.getViewDistance());
|
||||
|
||||
// We need to send our skin parts to the server otherwise java sees us with no hat, jacket etc
|
||||
String locale = session.getClientData().getLanguageCode();
|
||||
String locale = session.getLocale();
|
||||
List<SkinPart> skinParts = Arrays.asList(SkinPart.values());
|
||||
ClientSettingsPacket clientSettingsPacket = new ClientSettingsPacket(locale, (byte) session.getRenderDistance(), ChatVisibility.FULL, true, skinParts, HandPreference.RIGHT_HAND);
|
||||
session.sendDownstreamPacket(clientSettingsPacket);
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.connector.network.translators.java;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerKeepAlivePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
|
||||
/**
|
||||
* Used to forward the keep alive packet to the client in order to get back a reliable ping.
|
||||
*/
|
||||
@Translator(packet = ServerKeepAlivePacket.class)
|
||||
public class JavaKeepAliveTranslator extends PacketTranslator<ServerKeepAlivePacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerKeepAlivePacket packet, GeyserSession session) {
|
||||
session.setLastKeepAliveTimestamp(packet.getPingId());
|
||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||
latencyPacket.setFromServer(true);
|
||||
latencyPacket.setTimestamp(packet.getPingId());
|
||||
session.sendUpstreamPacket(latencyPacket);
|
||||
}
|
||||
}
|
@ -37,6 +37,6 @@ public class JavaLoginDisconnectTranslator extends PacketTranslator<LoginDisconn
|
||||
@Override
|
||||
public void translate(LoginDisconnectPacket packet, GeyserSession session) {
|
||||
// The client doesn't manually get disconnected so we have to do it ourselves
|
||||
session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getClientData().getLanguageCode()));
|
||||
session.disconnect(MessageUtils.getTranslatedBedrockMessage(packet.getReason(), session.getLocale()));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.connector.network.translators.java;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerStatisticsPacket;
|
||||
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.StatisticsUtils;
|
||||
|
||||
@Translator(packet = ServerStatisticsPacket.class)
|
||||
public class JavaStatisticsTranslator extends PacketTranslator<ServerStatisticsPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerStatisticsPacket packet, GeyserSession session) {
|
||||
session.updateStatistics(packet.getStatistics());
|
||||
|
||||
if (session.isWaitingForStatistics()) {
|
||||
session.setWaitingForStatistics(false);
|
||||
session.sendForm(StatisticsUtils.buildMenuForm(session), StatisticsUtils.STATISTICS_MENU_FORM_ID);
|
||||
}
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ public class JavaTitleTranslator extends PacketTranslator<ServerTitlePacket> {
|
||||
@Override
|
||||
public void translate(ServerTitlePacket packet, GeyserSession session) {
|
||||
SetTitlePacket titlePacket = new SetTitlePacket();
|
||||
String locale = session.getClientData().getLanguageCode();
|
||||
String locale = session.getLocale();
|
||||
|
||||
switch (packet.getAction()) {
|
||||
case TITLE:
|
||||
|
@ -82,7 +82,6 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
|
||||
}
|
||||
|
||||
passenger.updateBedrockMetadata(session);
|
||||
this.updateOffset(passenger, entity.getEntityType(), session, rider, true, (passengers.size() > 1));
|
||||
rider = false;
|
||||
}
|
||||
|
||||
@ -90,6 +89,9 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
|
||||
|
||||
for (long passengerId : entity.getPassengers()) {
|
||||
Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId);
|
||||
if (passengerId == session.getPlayerEntity().getEntityId()) {
|
||||
passenger = session.getPlayerEntity();
|
||||
}
|
||||
if (passenger == null) {
|
||||
continue;
|
||||
}
|
||||
@ -99,66 +101,160 @@ public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEn
|
||||
session.sendUpstreamPacket(linkPacket);
|
||||
passengers.remove(passenger.getEntityId());
|
||||
|
||||
this.updateOffset(passenger, entity.getEntityType(), session, false, false, (passengers.size() > 1));
|
||||
this.updateOffset(passenger, entity, session, false, false, (packet.getPassengerIds().length > 1));
|
||||
} else {
|
||||
this.updateOffset(passenger, entity, session, (packet.getPassengerIds()[0] == passengerId), true, (packet.getPassengerIds().length > 1));
|
||||
}
|
||||
|
||||
// Force an update to the passenger metadata
|
||||
passenger.updateBedrockMetadata(session);
|
||||
}
|
||||
|
||||
if (entity.getEntityType() == EntityType.HORSE) {
|
||||
entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f));
|
||||
entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f);
|
||||
|
||||
entity.updateBedrockMetadata(session);
|
||||
switch (entity.getEntityType()) {
|
||||
case HORSE:
|
||||
case SKELETON_HORSE:
|
||||
case DONKEY:
|
||||
case MULE:
|
||||
case RAVAGER:
|
||||
entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f);
|
||||
entity.updateBedrockMetadata(session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) {
|
||||
// Without the Y offset, Bedrock players will find themselves in the floor when mounting
|
||||
float yOffset = 0;
|
||||
private float getMountedHeightOffset(Entity mount) {
|
||||
final EntityType mountType = mount.getEntityType();
|
||||
float mountedHeightOffset = mountType.getHeight() * 0.75f;
|
||||
switch (mountType) {
|
||||
case BOAT:
|
||||
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f;
|
||||
break;
|
||||
case MINECART:
|
||||
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f;
|
||||
case CHICKEN:
|
||||
case SPIDER:
|
||||
mountedHeightOffset = mountType.getHeight() * 0.5f;
|
||||
break;
|
||||
case DONKEY:
|
||||
yOffset = 2.1f;
|
||||
break;
|
||||
case HORSE:
|
||||
case SKELETON_HORSE:
|
||||
case ZOMBIE_HORSE:
|
||||
case MULE:
|
||||
yOffset = 2.3f;
|
||||
mountedHeightOffset -= 0.25f;
|
||||
break;
|
||||
case LLAMA:
|
||||
case TRADER_LLAMA:
|
||||
yOffset = 2.5f;
|
||||
mountedHeightOffset = mountType.getHeight() * 0.67f;
|
||||
break;
|
||||
case PIG:
|
||||
yOffset = 1.85001f;
|
||||
case MINECART:
|
||||
case MINECART_HOPPER:
|
||||
case MINECART_TNT:
|
||||
case MINECART_CHEST:
|
||||
case MINECART_FURNACE:
|
||||
case MINECART_SPAWNER:
|
||||
case MINECART_COMMAND_BLOCK:
|
||||
mountedHeightOffset = 0;
|
||||
break;
|
||||
case ARMOR_STAND:
|
||||
yOffset = 1.3f;
|
||||
case BOAT:
|
||||
mountedHeightOffset = -0.1f;
|
||||
break;
|
||||
case HOGLIN:
|
||||
case ZOGLIN:
|
||||
boolean isBaby = mount.getMetadata().getFlags().getFlag(EntityFlag.BABY);
|
||||
mountedHeightOffset = mountType.getHeight() - (isBaby ? 0.2f : 0.15f);
|
||||
break;
|
||||
case PIGLIN:
|
||||
mountedHeightOffset = mountType.getHeight() * 0.92f;
|
||||
break;
|
||||
case RAVAGER:
|
||||
mountedHeightOffset = 2.1f;
|
||||
break;
|
||||
case SKELETON_HORSE:
|
||||
mountedHeightOffset -= 0.1875f;
|
||||
break;
|
||||
case STRIDER:
|
||||
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 2.8200102f : 1.6f;
|
||||
mountedHeightOffset = mountType.getHeight() - 0.19f;
|
||||
break;
|
||||
}
|
||||
Vector3f offset = Vector3f.from(0f, yOffset, 0f);
|
||||
if (mountType == EntityType.STRIDER) {
|
||||
offset = offset.add(0f, 0f, -0.2f);
|
||||
}
|
||||
// Without the X offset, more than one entity on a boat is stacked on top of each other
|
||||
if (rider && moreThanOneEntity) {
|
||||
offset = offset.add(Vector3f.from(0.2, 0, 0));
|
||||
} else if (moreThanOneEntity) {
|
||||
offset = offset.add(Vector3f.from(-0.6, 0, 0));
|
||||
return mountedHeightOffset;
|
||||
}
|
||||
|
||||
private float getHeightOffset(Entity passenger) {
|
||||
boolean isBaby;
|
||||
switch (passenger.getEntityType()) {
|
||||
case SKELETON:
|
||||
case STRAY:
|
||||
case WITHER_SKELETON:
|
||||
return -0.6f;
|
||||
case ARMOR_STAND:
|
||||
// Armor stand isn't a marker
|
||||
if (passenger.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT) != 0.0f) {
|
||||
return 0.1f;
|
||||
} else {
|
||||
return 0.0f;
|
||||
}
|
||||
case ENDERMITE:
|
||||
case SILVERFISH:
|
||||
return 0.1f;
|
||||
case PIGLIN:
|
||||
case PIGLIN_BRUTE:
|
||||
case ZOMBIFIED_PIGLIN:
|
||||
isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY);
|
||||
return isBaby ? -0.05f : -0.45f;
|
||||
case ZOMBIE:
|
||||
isBaby = passenger.getMetadata().getFlags().getFlag(EntityFlag.BABY);
|
||||
return isBaby ? 0.0f : -0.45f;
|
||||
case EVOKER:
|
||||
case ILLUSIONER:
|
||||
case PILLAGER:
|
||||
case RAVAGER:
|
||||
case VINDICATOR:
|
||||
case WITCH:
|
||||
return -0.45f;
|
||||
case PLAYER:
|
||||
return -0.35f;
|
||||
}
|
||||
return 0f;
|
||||
}
|
||||
|
||||
private void updateOffset(Entity passenger, Entity mount, GeyserSession session, boolean rider, boolean riding, boolean moreThanOneEntity) {
|
||||
passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding);
|
||||
if (riding) {
|
||||
// Without the Y offset, Bedrock players will find themselves in the floor when mounting
|
||||
float mountedHeightOffset = getMountedHeightOffset(mount);
|
||||
float heightOffset = getHeightOffset(passenger);
|
||||
|
||||
float xOffset = 0;
|
||||
float yOffset = mountedHeightOffset + heightOffset;
|
||||
float zOffset = 0;
|
||||
switch (mount.getEntityType()) {
|
||||
case BOAT:
|
||||
// Without the X offset, more than one entity on a boat is stacked on top of each other
|
||||
if (rider && moreThanOneEntity) {
|
||||
xOffset = 0.2f;
|
||||
} else if (moreThanOneEntity) {
|
||||
xOffset = -0.6f;
|
||||
}
|
||||
break;
|
||||
case CHICKEN:
|
||||
zOffset = -0.1f;
|
||||
break;
|
||||
case LLAMA:
|
||||
zOffset = -0.3f;
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* Bedrock Differences
|
||||
* Zoglin & Hoglin seem to be taller in Bedrock edition
|
||||
* Horses are tinier
|
||||
* Players, Minecarts, and Boats have different origins
|
||||
*/
|
||||
if (passenger.getEntityType() == EntityType.PLAYER) {
|
||||
yOffset += EntityType.PLAYER.getOffset();
|
||||
}
|
||||
switch (mount.getEntityType()) {
|
||||
case MINECART:
|
||||
case MINECART_HOPPER:
|
||||
case MINECART_TNT:
|
||||
case MINECART_CHEST:
|
||||
case MINECART_FURNACE:
|
||||
case MINECART_SPAWNER:
|
||||
case MINECART_COMMAND_BLOCK:
|
||||
case BOAT:
|
||||
yOffset -= mount.getEntityType().getHeight() * 0.5f;
|
||||
}
|
||||
Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset);
|
||||
passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset);
|
||||
}
|
||||
passenger.updateBedrockMetadata(session);
|
||||
|
@ -25,17 +25,26 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.java.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.PlayerEntity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
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.item.ItemEntry;
|
||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||
import org.geysermc.connector.network.translators.item.ItemTranslator;
|
||||
|
||||
@Translator(packet = ServerEntityStatusPacket.class)
|
||||
public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntityStatusPacket> {
|
||||
@ -88,6 +97,22 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
|
||||
break;
|
||||
case LIVING_DEATH:
|
||||
entityEventPacket.setType(EntityEventType.DEATH);
|
||||
if (entity.getEntityType() == EntityType.THROWN_EGG) {
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_ITEM_BREAK);
|
||||
particlePacket.setData(ItemRegistry.EGG.getBedrockId() << 16);
|
||||
particlePacket.setPosition(entity.getPosition());
|
||||
for (int i = 0; i < 6; i++) {
|
||||
session.sendUpstreamPacket(particlePacket);
|
||||
}
|
||||
} else if (entity.getEntityType() == EntityType.SNOWBALL) {
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_SNOWBALL_POOF);
|
||||
particlePacket.setPosition(entity.getPosition());
|
||||
for (int i = 0; i < 8; i++) {
|
||||
session.sendUpstreamPacket(particlePacket);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WOLF_SHAKE_WATER:
|
||||
entityEventPacket.setType(EntityEventType.SHAKE_WETNESS);
|
||||
@ -96,8 +121,20 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
|
||||
entityEventPacket.setType(EntityEventType.USE_ITEM);
|
||||
break;
|
||||
case FISHING_HOOK_PULL_PLAYER:
|
||||
entityEventPacket.setType(EntityEventType.FISH_HOOK_TEASE); //TODO: CHECK
|
||||
break;
|
||||
// Player is pulled from a fishing rod
|
||||
// The physics of this are clientside on Java
|
||||
long pulledById = entity.getMetadata().getLong(EntityData.TARGET_EID);
|
||||
if (session.getPlayerEntity().getGeyserId() == pulledById) {
|
||||
Entity hookOwner = session.getEntityCache().getEntityByGeyserId(entity.getMetadata().getLong(EntityData.OWNER_EID));
|
||||
if (hookOwner != null) {
|
||||
// https://minecraft.gamepedia.com/Fishing_Rod#Hooking_mobs_and_other_entities
|
||||
SetEntityMotionPacket motionPacket = new SetEntityMotionPacket();
|
||||
motionPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
motionPacket.setMotion(hookOwner.getPosition().sub(session.getPlayerEntity().getPosition()).mul(0.1f));
|
||||
session.sendUpstreamPacket(motionPacket);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case TAMEABLE_TAMING_FAILED:
|
||||
entityEventPacket.setType(EntityEventType.TAME_FAILED);
|
||||
break;
|
||||
|
@ -57,9 +57,8 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
|
||||
if (self) {
|
||||
// Entity is ourself
|
||||
playerEntity = session.getPlayerEntity();
|
||||
SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape -> {
|
||||
GeyserConnector.getInstance().getLogger().debug("Loading Local Bedrock Java Skin Data");
|
||||
});
|
||||
SkinUtils.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
|
||||
GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data"));
|
||||
} else {
|
||||
playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
|
||||
}
|
||||
|
@ -62,8 +62,8 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
|
||||
.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
|
||||
.setColor(packet.getColor())
|
||||
.setNameTagVisibility(packet.getNameTagVisibility())
|
||||
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
|
||||
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()));
|
||||
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale()))
|
||||
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale()));
|
||||
break;
|
||||
case UPDATE:
|
||||
if (team == null) {
|
||||
@ -77,8 +77,8 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
|
||||
team.setName(MessageUtils.getBedrockMessage(packet.getDisplayName()))
|
||||
.setColor(packet.getColor())
|
||||
.setNameTagVisibility(packet.getNameTagVisibility())
|
||||
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getClientData().getLanguageCode()))
|
||||
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()))
|
||||
.setPrefix(MessageUtils.getTranslatedBedrockMessage(packet.getPrefix(), session.getLocale()))
|
||||
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getLocale()))
|
||||
.setUpdateType(UpdateType.UPDATE);
|
||||
break;
|
||||
case ADD_PLAYER:
|
||||
|
@ -25,18 +25,17 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.java.window;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.message.MessageSerializer;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
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;
|
||||
import org.geysermc.connector.utils.InventoryUtils;
|
||||
import org.geysermc.connector.utils.LocaleUtils;
|
||||
import org.geysermc.connector.utils.MessageUtils;
|
||||
|
||||
@Translator(packet = ServerOpenWindowPacket.class)
|
||||
public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowPacket> {
|
||||
@ -58,18 +57,10 @@ public class JavaOpenWindowTranslator extends PacketTranslator<ServerOpenWindowP
|
||||
return;
|
||||
}
|
||||
|
||||
String name = packet.getName();
|
||||
try {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject jsonObject = parser.parse(packet.getName()).getAsJsonObject();
|
||||
if (jsonObject.has("text")) {
|
||||
name = jsonObject.get("text").getAsString();
|
||||
} else if (jsonObject.has("translate")) {
|
||||
name = jsonObject.get("translate").getAsString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GeyserConnector.getInstance().getLogger().debug("JavaOpenWindowTranslator: " + e.toString());
|
||||
}
|
||||
String name = MessageUtils.getTranslatedBedrockMessage(MessageSerializer.fromString(packet.getName()),
|
||||
session.getLocale());
|
||||
|
||||
name = LocaleUtils.getLocaleString(name, session.getLocale());
|
||||
|
||||
Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36);
|
||||
session.getInventoryCache().cacheInventory(newInventory);
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.java.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
|
||||
import com.nukkitx.nbt.NBTOutputStream;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
@ -32,8 +33,8 @@ import com.nukkitx.nbt.NbtUtils;
|
||||
import com.nukkitx.network.VarInts;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.BiomeTranslator;
|
||||
@ -66,57 +67,69 @@ public class JavaChunkDataTranslator extends PacketTranslator<ServerChunkDataPac
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-full chunks don't have all the chunk data, and Bedrock won't accept that
|
||||
final boolean isNonFullChunk = (packet.getColumn().getBiomeData() == null);
|
||||
// Merge received column with cache on network thread
|
||||
Column mergedColumn = session.getChunkCache().addToCache(packet.getColumn());
|
||||
if (mergedColumn == null) { // There were no changes?!?
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isNonFullChunk = packet.getColumn().getBiomeData() == null;
|
||||
|
||||
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
|
||||
try {
|
||||
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, packet.getColumn(), isNonFullChunk);
|
||||
ByteBuf byteBuf = Unpooled.buffer(32);
|
||||
ChunkSection[] sections = chunkData.sections;
|
||||
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(session, mergedColumn, isNonFullChunk);
|
||||
ChunkSection[] sections = chunkData.getSections();
|
||||
|
||||
// Find highest section
|
||||
int sectionCount = sections.length - 1;
|
||||
while (sectionCount >= 0 && sections[sectionCount].isEmpty()) {
|
||||
while (sectionCount >= 0 && sections[sectionCount] == null) {
|
||||
sectionCount--;
|
||||
}
|
||||
sectionCount++;
|
||||
|
||||
// Estimate chunk size
|
||||
int size = 0;
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
ChunkSection section = chunkData.sections[i];
|
||||
section.writeToNetwork(byteBuf);
|
||||
ChunkSection section = sections[i];
|
||||
size += (section != null ? section : ChunkUtils.EMPTY_SECTION).estimateNetworkSize();
|
||||
}
|
||||
size += 256; // Biomes
|
||||
size += 1; // Border blocks
|
||||
size += 1; // Extra data length (always 0)
|
||||
size += chunkData.getBlockEntities().length * 64; // Conservative estimate of 64 bytes per tile entity
|
||||
|
||||
byte[] bedrockBiome;
|
||||
if (packet.getColumn().getBiomeData() == null) {
|
||||
bedrockBiome = BiomeTranslator.toBedrockBiome(session.getConnector().getWorldManager().getBiomeDataAt(session, packet.getColumn().getX(), packet.getColumn().getZ()));
|
||||
} else {
|
||||
bedrockBiome = BiomeTranslator.toBedrockBiome(packet.getColumn().getBiomeData());
|
||||
// Allocate output buffer
|
||||
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(size);
|
||||
byte[] payload;
|
||||
try {
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
ChunkSection section = sections[i];
|
||||
(section != null ? section : ChunkUtils.EMPTY_SECTION).writeToNetwork(byteBuf);
|
||||
}
|
||||
|
||||
byteBuf.writeBytes(BiomeTranslator.toBedrockBiome(mergedColumn.getBiomeData())); // Biomes - 256 bytes
|
||||
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
||||
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
|
||||
|
||||
// Encode tile entities into buffer
|
||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(new ByteBufOutputStream(byteBuf));
|
||||
for (NbtMap blockEntity : chunkData.getBlockEntities()) {
|
||||
nbtStream.writeTag(blockEntity);
|
||||
}
|
||||
|
||||
// Copy data into byte[], because the protocol lib really likes things that are s l o w
|
||||
byteBuf.readBytes(payload = new byte[byteBuf.readableBytes()]);
|
||||
} finally {
|
||||
byteBuf.release(); // Release buffer to allow buffer pooling to be useful
|
||||
}
|
||||
|
||||
byteBuf.writeBytes(bedrockBiome); // Biomes - 256 bytes
|
||||
byteBuf.writeByte(0); // Border blocks - Edu edition only
|
||||
VarInts.writeUnsignedInt(byteBuf, 0); // extra data length, 0 for now
|
||||
|
||||
ByteBufOutputStream stream = new ByteBufOutputStream(Unpooled.buffer());
|
||||
NBTOutputStream nbtStream = NbtUtils.createNetworkWriter(stream);
|
||||
for (NbtMap blockEntity : chunkData.getBlockEntities()) {
|
||||
nbtStream.writeTag(blockEntity);
|
||||
}
|
||||
|
||||
byteBuf.writeBytes(stream.buffer());
|
||||
|
||||
byte[] payload = new byte[byteBuf.writerIndex()];
|
||||
byteBuf.readBytes(payload);
|
||||
|
||||
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
||||
levelChunkPacket.setSubChunksLength(sectionCount);
|
||||
levelChunkPacket.setCachingEnabled(false);
|
||||
levelChunkPacket.setChunkX(packet.getColumn().getX());
|
||||
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
|
||||
levelChunkPacket.setChunkX(mergedColumn.getX());
|
||||
levelChunkPacket.setChunkZ(mergedColumn.getZ());
|
||||
levelChunkPacket.setData(payload);
|
||||
session.sendUpstreamPacket(levelChunkPacket);
|
||||
|
||||
session.getChunkCache().addToCache(packet.getColumn());
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
@ -103,8 +103,6 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
|
||||
case CHANGE_GAMEMODE:
|
||||
GameMode gameMode = (GameMode) packet.getValue();
|
||||
|
||||
session.setNoClip(gameMode == GameMode.SPECTATOR);
|
||||
session.setWorldImmutable(gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR);
|
||||
session.sendAdventureSettings();
|
||||
|
||||
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
|
||||
@ -145,7 +143,15 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
|
||||
case INVALID_BED:
|
||||
// Not sent as a proper message? Odd.
|
||||
session.sendMessage(LocaleUtils.getLocaleString("block.minecraft.spawn.not_valid",
|
||||
session.getClientData().getLanguageCode()));
|
||||
session.getLocale()));
|
||||
break;
|
||||
case ARROW_HIT_PLAYER:
|
||||
PlaySoundPacket arrowSoundPacket = new PlaySoundPacket();
|
||||
arrowSoundPacket.setSound("random.orb");
|
||||
arrowSoundPacket.setPitch(0.5f);
|
||||
arrowSoundPacket.setVolume(0.5f);
|
||||
arrowSoundPacket.setPosition(entity.getPosition());
|
||||
session.sendUpstreamPacket(arrowSoundPacket);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -78,7 +78,7 @@ public class JavaPlayEffectTranslator extends PacketTranslator<ServerPlayEffectP
|
||||
textPacket.setMessage("record.nowPlaying");
|
||||
List<String> params = new ArrayList<>();
|
||||
String recordString = "%item." + soundEvent.name().toLowerCase(Locale.ROOT) + ".desc";
|
||||
params.add(LocaleUtils.getLocaleString(recordString, session.getClientData().getLanguageCode()));
|
||||
params.add(LocaleUtils.getLocaleString(recordString, session.getLocale()));
|
||||
textPacket.setParameters(params);
|
||||
session.sendUpstreamPacket(textPacket);
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||
import com.nukkitx.protocol.bedrock.packet.UpdateTradePacket;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
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;
|
||||
@ -60,14 +62,20 @@ public class JavaTradeListTranslator extends PacketTranslator<ServerTradeListPac
|
||||
|
||||
UpdateTradePacket updateTradePacket = new UpdateTradePacket();
|
||||
updateTradePacket.setTradeTier(packet.getVillagerLevel() - 1);
|
||||
updateTradePacket.setContainerId((short) packet.getWindowId()); //TODO: CHECK THIS AND THE ONE BELOW
|
||||
updateTradePacket.setContainerId((short) packet.getWindowId());
|
||||
updateTradePacket.setContainerType(ContainerType.TRADE);
|
||||
Inventory openInv = session.getInventoryCache().getOpenInventory();
|
||||
String displayName;
|
||||
Entity realVillager = session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid());
|
||||
if (realVillager != null && realVillager.getMetadata().containsKey(EntityData.NAMETAG) && realVillager.getMetadata().getString(EntityData.NAMETAG) != null) {
|
||||
displayName = realVillager.getMetadata().getString(EntityData.NAMETAG);
|
||||
if (openInv != null && openInv.getId() == packet.getWindowId()) {
|
||||
displayName = openInv.getTitle();
|
||||
} else {
|
||||
displayName = packet.isRegularVillager() ? "Villager" : "Wandering Trader";
|
||||
Entity realVillager = session.getEntityCache().getEntityByGeyserId(session.getLastInteractedVillagerEid());
|
||||
if (realVillager != null && realVillager.getMetadata().containsKey(EntityData.NAMETAG) && realVillager.getMetadata().getString(EntityData.NAMETAG) != null) {
|
||||
displayName = realVillager.getMetadata().getString(EntityData.NAMETAG);
|
||||
} else {
|
||||
displayName = realVillager != null &&
|
||||
realVillager.getEntityType() == EntityType.WANDERING_TRADER ? "Wandering Trader" : "Villager";
|
||||
}
|
||||
}
|
||||
updateTradePacket.setDisplayName(displayName);
|
||||
updateTradePacket.setSize(0);
|
||||
@ -87,7 +95,7 @@ public class JavaTradeListTranslator extends PacketTranslator<ServerTradeListPac
|
||||
recipe.putInt("buyCountB", trade.getSecondInput() != null ? trade.getSecondInput().getAmount() : 0);
|
||||
recipe.putInt("buyCountA", trade.getFirstInput().getAmount());
|
||||
recipe.putInt("demand", trade.getDemand());
|
||||
recipe.putInt("tier", packet.getVillagerLevel() - 1);
|
||||
recipe.putInt("tier", packet.getVillagerLevel() > 0 ? packet.getVillagerLevel() - 1 : 0); // -1 crashes client
|
||||
recipe.put("buyA", getItemTag(session, trade.getFirstInput(), trade.getSpecialPrice()));
|
||||
if (trade.getSecondInput() != null) {
|
||||
recipe.put("buyB", getItemTag(session, trade.getSecondInput(), 0));
|
||||
|
@ -28,7 +28,6 @@ package org.geysermc.connector.network.translators.java.world;
|
||||
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.world.chunk.ChunkPosition;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUnloadChunkPacket;
|
||||
|
||||
@ -37,6 +36,6 @@ public class JavaUnloadChunkTranslator extends PacketTranslator<ServerUnloadChun
|
||||
|
||||
@Override
|
||||
public void translate(ServerUnloadChunkPacket packet, GeyserSession session) {
|
||||
session.getChunkCache().removeChunk(new ChunkPosition(packet.getX(), packet.getZ()));
|
||||
session.getChunkCache().removeChunk(packet.getX(), packet.getZ());
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class SoundHandlerRegistry {
|
||||
static final Map<SoundHandler, SoundInteractionHandler<?>> INTERACTION_HANDLERS = new HashMap<>();
|
||||
|
||||
static {
|
||||
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound");
|
||||
Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.sound") : new Reflections("org.geysermc.connector.network.translators.sound");
|
||||
for (Class<?> clazz : ref.getTypesAnnotatedWith(SoundHandler.class)) {
|
||||
try {
|
||||
SoundInteractionHandler<?> interactionHandler = (SoundInteractionHandler<?>) clazz.newInstance();
|
||||
|
@ -38,6 +38,7 @@ public class BucketSoundInteractionHandler implements BlockSoundInteractionHandl
|
||||
|
||||
@Override
|
||||
public void handleInteraction(GeyserSession session, Vector3f position, String identifier) {
|
||||
if (session.getBucketScheduledFuture() == null) return; // No bucket was really interacted with
|
||||
String handItemIdentifier = ItemRegistry.getItem(session.getInventory().getItemInHand()).getJavaIdentifier();
|
||||
LevelSoundEventPacket soundEventPacket = new LevelSoundEventPacket();
|
||||
soundEventPacket.setPosition(position);
|
||||
@ -68,5 +69,6 @@ public class BucketSoundInteractionHandler implements BlockSoundInteractionHandl
|
||||
soundEventPacket.setSound(soundEvent);
|
||||
session.sendUpstreamPacket(soundEventPacket);
|
||||
}
|
||||
session.setBucketScheduledFuture(null);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public class DoorSoundInteractionHandler implements BlockSoundInteractionHandler
|
||||
|
||||
@Override
|
||||
public void handleInteraction(GeyserSession session, Vector3f position, String identifier) {
|
||||
if (identifier.contains("iron")) return;
|
||||
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
||||
levelEventPacket.setType(LevelEventType.SOUND_DOOR_OPEN);
|
||||
levelEventPacket.setPosition(position);
|
||||
|
@ -25,14 +25,15 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
|
||||
import org.geysermc.connector.network.session.cache.ChunkCache;
|
||||
import org.geysermc.connector.utils.GameRule;
|
||||
|
||||
public class GeyserWorldManager extends WorldManager {
|
||||
@ -41,14 +42,50 @@ public class GeyserWorldManager extends WorldManager {
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
return session.getChunkCache().getBlockAt(new Position(x, y, z));
|
||||
ChunkCache chunkCache = session.getChunkCache();
|
||||
if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously
|
||||
return chunkCache.getBlockAt(x, y, z);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk chunk) {
|
||||
ChunkCache chunkCache = session.getChunkCache();
|
||||
Column cachedColumn;
|
||||
Chunk cachedChunk;
|
||||
if (chunkCache == null || (cachedColumn = chunkCache.getChunk(x, z)) == null || (cachedChunk = cachedColumn.getChunks()[y]) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy state IDs from cached chunk to output chunk
|
||||
for (int blockY = 0; blockY < 16; blockY++) { // Cache-friendly iteration order
|
||||
for (int blockZ = 0; blockZ < 16; blockZ++) {
|
||||
for (int blockX = 0; blockX < 16; blockX++) {
|
||||
chunk.set(blockX, blockY, blockZ, cachedChunk.get(blockX, blockY, blockZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreBlockDataThanChunkCache() {
|
||||
// This implementation can only fetch data from the session chunk cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getBiomeDataAt(GeyserSession session, int x, int z) {
|
||||
if (!session.getConnector().getConfig().isCacheChunks())
|
||||
return new int[1024];
|
||||
return session.getChunkCache().getChunks().get(new ChunkPosition(x, z)).getBiomeData();
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
ChunkCache chunkCache = session.getChunkCache();
|
||||
if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously
|
||||
Column column = chunkCache.getChunk(x, z);
|
||||
if (column != null) { // Column can be null if the server sent a partial chunk update before the first ground-up-continuous one
|
||||
return column.getBiomeData();
|
||||
}
|
||||
}
|
||||
}
|
||||
return new int[1024];
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
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.setting.Difficulty;
|
||||
@ -74,6 +75,27 @@ public abstract class WorldManager {
|
||||
*/
|
||||
public abstract int getBlockAt(GeyserSession session, int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Gets all block states in the specified chunk section.
|
||||
*
|
||||
* @param session the session
|
||||
* @param x the chunk's X coordinate
|
||||
* @param y the chunk's Y coordinate
|
||||
* @param z the chunk's Z coordinate
|
||||
* @param section the chunk section to store the block data in
|
||||
*/
|
||||
public abstract void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk section);
|
||||
|
||||
/**
|
||||
* Checks whether or not this world manager has access to more block data than the chunk cache.
|
||||
* <p>
|
||||
* Some world managers (e.g. Spigot) can provide access to block data outside of the chunk cache, and even with chunk caching disabled. This
|
||||
* method provides a means to check if this manager has this capability.
|
||||
*
|
||||
* @return whether or not this world manager has access to more block data than the chunk cache
|
||||
*/
|
||||
public abstract boolean hasMoreBlockDataThanChunkCache();
|
||||
|
||||
/**
|
||||
* Gets the biome data for the specified chunk.
|
||||
*
|
||||
|
@ -67,6 +67,11 @@ public class BlockTranslator {
|
||||
public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap();
|
||||
public static final Int2ObjectMap<String> JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* Java numeric ID to java unique identifier, used for block names in the statistics screen
|
||||
*/
|
||||
public static final Int2ObjectMap<String> JAVA_ID_TO_JAVA_IDENTIFIER_MAP = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* Runtime command block ID, used for fixing command block minecart appearances
|
||||
*/
|
||||
@ -113,7 +118,7 @@ public class BlockTranslator {
|
||||
addedStatesMap.defaultReturnValue(-1);
|
||||
List<NbtMap> paletteList = new ArrayList<>();
|
||||
|
||||
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
|
||||
Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
|
||||
ref.getTypesAnnotatedWith(BlockEntity.class);
|
||||
|
||||
int waterRuntimeId = -1;
|
||||
@ -124,6 +129,7 @@ public class BlockTranslator {
|
||||
int furnaceRuntimeId = -1;
|
||||
int furnaceLitRuntimeId = -1;
|
||||
int spawnerRuntimeId = -1;
|
||||
int uniqueJavaId = -1;
|
||||
Iterator<Map.Entry<String, JsonNode>> blocksIterator = blocks.fields();
|
||||
while (blocksIterator.hasNext()) {
|
||||
javaRuntimeId++;
|
||||
@ -166,6 +172,11 @@ public class BlockTranslator {
|
||||
|
||||
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];
|
||||
|
||||
if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) {
|
||||
uniqueJavaId++;
|
||||
JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier);
|
||||
}
|
||||
|
||||
if (!cleanJavaIdentifier.equals(bedrockIdentifier)) {
|
||||
JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier);
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public abstract class BlockEntityTranslator {
|
||||
}
|
||||
|
||||
static {
|
||||
Reflections ref = GeyserConnector.getInstance().isProduction() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
|
||||
Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") : new Reflections("org.geysermc.connector.network.translators.world.block.entity");
|
||||
for (Class<?> clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName());
|
||||
|
||||
|
@ -29,14 +29,16 @@ import com.nukkitx.network.VarInts;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion;
|
||||
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
@Getter
|
||||
public class BlockStorage {
|
||||
|
||||
private static final int SIZE = 4096;
|
||||
public static final int SIZE = 4096;
|
||||
|
||||
private final IntList palette;
|
||||
private BitArray bitArray;
|
||||
@ -46,12 +48,12 @@ public class BlockStorage {
|
||||
}
|
||||
|
||||
public BlockStorage(BitArrayVersion version) {
|
||||
this.bitArray = version.createPalette(SIZE);
|
||||
this.bitArray = version.createArray(SIZE);
|
||||
this.palette = new IntArrayList(16);
|
||||
this.palette.add(0); // Air is at the start of every palette.
|
||||
}
|
||||
|
||||
private BlockStorage(BitArray bitArray, IntArrayList palette) {
|
||||
public BlockStorage(BitArray bitArray, IntList palette) {
|
||||
this.palette = palette;
|
||||
this.bitArray = bitArray;
|
||||
}
|
||||
@ -64,16 +66,16 @@ public class BlockStorage {
|
||||
return BitArrayVersion.get(header >> 1, true);
|
||||
}
|
||||
|
||||
public synchronized int getFullBlock(int index) {
|
||||
public int getFullBlock(int index) {
|
||||
return this.palette.getInt(this.bitArray.get(index));
|
||||
}
|
||||
|
||||
public synchronized void setFullBlock(int index, int runtimeId) {
|
||||
public void setFullBlock(int index, int runtimeId) {
|
||||
int idx = this.idFor(runtimeId);
|
||||
this.bitArray.set(index, idx);
|
||||
}
|
||||
|
||||
public synchronized void writeToNetwork(ByteBuf buffer) {
|
||||
public void writeToNetwork(ByteBuf buffer) {
|
||||
buffer.writeByte(getPaletteHeader(bitArray.getVersion(), true));
|
||||
|
||||
for (int word : bitArray.getWords()) {
|
||||
@ -84,8 +86,18 @@ public class BlockStorage {
|
||||
palette.forEach((IntConsumer) id -> VarInts.writeInt(buffer, id));
|
||||
}
|
||||
|
||||
public int estimateNetworkSize() {
|
||||
int size = 1; // Palette header
|
||||
size += this.bitArray.getWords().length * 4;
|
||||
|
||||
// We assume that none of the VarInts will be larger than 3 bytes
|
||||
size += 3; // Palette size
|
||||
size += this.palette.size() * 3;
|
||||
return size;
|
||||
}
|
||||
|
||||
private void onResize(BitArrayVersion version) {
|
||||
BitArray newBitArray = version.createPalette(SIZE);
|
||||
BitArray newBitArray = version.createArray(SIZE);
|
||||
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
newBitArray.set(i, this.bitArray.get(i));
|
||||
|
@ -27,42 +27,19 @@ package org.geysermc.connector.network.translators.world.chunk;
|
||||
|
||||
import com.nukkitx.network.util.Preconditions;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Synchronized;
|
||||
|
||||
public class ChunkSection {
|
||||
|
||||
private static final int CHUNK_SECTION_VERSION = 8;
|
||||
public static final int SIZE = 4096;
|
||||
|
||||
private final BlockStorage[] storage;
|
||||
private final NibbleArray blockLight;
|
||||
private final NibbleArray skyLight;
|
||||
|
||||
public ChunkSection() {
|
||||
this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}, new NibbleArray(SIZE),
|
||||
new NibbleArray(SIZE));
|
||||
this(new BlockStorage[]{new BlockStorage(), new BlockStorage()});
|
||||
}
|
||||
|
||||
public ChunkSection(BlockStorage[] blockStorage) {
|
||||
this(blockStorage, new NibbleArray(SIZE), new NibbleArray(SIZE));
|
||||
}
|
||||
|
||||
public ChunkSection(BlockStorage[] storage, byte[] blockLight, byte[] skyLight) {
|
||||
Preconditions.checkNotNull(storage, "storage");
|
||||
Preconditions.checkArgument(storage.length > 1, "Block storage length must be at least 2");
|
||||
for (BlockStorage blockStorage : storage) {
|
||||
Preconditions.checkNotNull(blockStorage, "storage");
|
||||
}
|
||||
|
||||
public ChunkSection(BlockStorage[] storage) {
|
||||
this.storage = storage;
|
||||
this.blockLight = new NibbleArray(blockLight);
|
||||
this.skyLight = new NibbleArray(skyLight);
|
||||
}
|
||||
|
||||
private ChunkSection(BlockStorage[] storage, NibbleArray blockLight, NibbleArray skyLight) {
|
||||
this.storage = storage;
|
||||
this.blockLight = blockLight;
|
||||
this.skyLight = skyLight;
|
||||
}
|
||||
|
||||
public int getFullBlock(int x, int y, int z, int layer) {
|
||||
@ -77,30 +54,6 @@ public class ChunkSection {
|
||||
this.storage[layer].setFullBlock(blockPosition(x, y, z), fullBlock);
|
||||
}
|
||||
|
||||
@Synchronized("skyLight")
|
||||
public byte getSkyLight(int x, int y, int z) {
|
||||
checkBounds(x, y, z);
|
||||
return this.skyLight.get(blockPosition(x, y, z));
|
||||
}
|
||||
|
||||
@Synchronized("skyLight")
|
||||
public void setSkyLight(int x, int y, int z, byte val) {
|
||||
checkBounds(x, y, z);
|
||||
this.skyLight.set(blockPosition(x, y, z), val);
|
||||
}
|
||||
|
||||
@Synchronized("blockLight")
|
||||
public byte getBlockLight(int x, int y, int z) {
|
||||
checkBounds(x, y, z);
|
||||
return this.blockLight.get(blockPosition(x, y, z));
|
||||
}
|
||||
|
||||
@Synchronized("blockLight")
|
||||
public void setBlockLight(int x, int y, int z, byte val) {
|
||||
checkBounds(x, y, z);
|
||||
this.blockLight.set(blockPosition(x, y, z), val);
|
||||
}
|
||||
|
||||
public void writeToNetwork(ByteBuf buffer) {
|
||||
buffer.writeByte(CHUNK_SECTION_VERSION);
|
||||
buffer.writeByte(this.storage.length);
|
||||
@ -109,12 +62,12 @@ public class ChunkSection {
|
||||
}
|
||||
}
|
||||
|
||||
public NibbleArray getSkyLightArray() {
|
||||
return skyLight;
|
||||
}
|
||||
|
||||
public NibbleArray getBlockLightArray() {
|
||||
return blockLight;
|
||||
public int estimateNetworkSize() {
|
||||
int size = 2; // Version + storage count
|
||||
for (BlockStorage blockStorage : this.storage) {
|
||||
size += blockStorage.estimateNetworkSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
public BlockStorage[] getBlockStorageArray() {
|
||||
@ -135,7 +88,7 @@ public class ChunkSection {
|
||||
for (int i = 0; i < storage.length; i++) {
|
||||
storage[i] = this.storage[i].copy();
|
||||
}
|
||||
return new ChunkSection(storage, skyLight.copy(), blockLight.copy());
|
||||
return new ChunkSection(storage);
|
||||
}
|
||||
|
||||
public static int blockPosition(int x, int y, int z) {
|
||||
|
@ -37,6 +37,8 @@ public enum BitArrayVersion {
|
||||
V2(2, 16, V3),
|
||||
V1(1, 32, V2);
|
||||
|
||||
private static final BitArrayVersion[] VALUES = values();
|
||||
|
||||
final byte bits;
|
||||
final byte entriesPerWord;
|
||||
final int maxEntryValue;
|
||||
@ -58,8 +60,14 @@ public enum BitArrayVersion {
|
||||
throw new IllegalArgumentException("Invalid palette version: " + version);
|
||||
}
|
||||
|
||||
public BitArray createPalette(int size) {
|
||||
return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]);
|
||||
public static BitArrayVersion forBitsCeil(int bits) {
|
||||
for (int i = VALUES.length - 1; i >= 0; i--) {
|
||||
BitArrayVersion version = VALUES[i];
|
||||
if (version.bits >= bits) {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte getId() {
|
||||
@ -78,7 +86,11 @@ public enum BitArrayVersion {
|
||||
return next;
|
||||
}
|
||||
|
||||
public BitArray createPalette(int size, int[] words) {
|
||||
public BitArray createArray(int size) {
|
||||
return this.createArray(size, new int[MathUtils.ceil((float) size / entriesPerWord)]);
|
||||
}
|
||||
|
||||
public BitArray createArray(int size, int[] words) {
|
||||
if (this == V3 || this == V5 || this == V6) {
|
||||
// Padded palettes aren't able to use bitwise operations due to their padding.
|
||||
return new PaddedBitArray(this, size, words);
|
||||
|
@ -76,7 +76,8 @@ public class Objective {
|
||||
if (!scores.containsKey(id)) {
|
||||
Score score1 = new Score(this, id)
|
||||
.setScore(score)
|
||||
.setTeam(scoreboard.getTeamFor(id));
|
||||
.setTeam(scoreboard.getTeamFor(id))
|
||||
.setUpdateType(UpdateType.ADD);
|
||||
scores.put(id, score1);
|
||||
}
|
||||
}
|
||||
@ -96,15 +97,6 @@ public class Objective {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Score getScore(int line) {
|
||||
for (Score score : scores.values()) {
|
||||
if (score.getScore() == line) {
|
||||
return score;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeScore(String id) {
|
||||
if (scores.containsKey(id)) {
|
||||
scores.get(id).setUpdateType(UpdateType.REMOVE);
|
||||
|
@ -49,7 +49,6 @@ public class Score {
|
||||
this.id = objective.getScoreboard().getNextId().getAndIncrement();
|
||||
this.objective = objective;
|
||||
this.name = name;
|
||||
update();
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
|
@ -122,7 +122,7 @@ public class Scoreboard {
|
||||
|
||||
for (Objective objective : objectives.values()) {
|
||||
if (!objective.isActive()) {
|
||||
logger.debug("Ignoring non-active Scoreboard Objective '"+ objective.getObjectiveName() +'\'');
|
||||
logger.debug("Ignoring non-active Scoreboard Objective '" + objective.getObjectiveName() + '\'');
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -152,10 +152,6 @@ public class Scoreboard {
|
||||
boolean globalAdd = objective.getUpdateType() == ADD;
|
||||
boolean globalRemove = objective.getUpdateType() == REMOVE;
|
||||
|
||||
// Track if any scores changed
|
||||
// Used to delete and resend scoreboard objectives; otherwise they won't update on Bedrock
|
||||
boolean scoreChanged = false;
|
||||
|
||||
for (Score score : objective.getScores().values()) {
|
||||
Team team = score.getTeam();
|
||||
|
||||
@ -171,16 +167,21 @@ public class Scoreboard {
|
||||
teamChanged |= team.getUpdateType() == UPDATE;
|
||||
|
||||
add |= team.getUpdateType() == ADD || team.getUpdateType() == UPDATE;
|
||||
remove |= team.getUpdateType() == REMOVE;
|
||||
remove |= team.getUpdateType() != NOTHING;
|
||||
}
|
||||
|
||||
add |= score.getUpdateType() == ADD || score.getUpdateType() == UPDATE;
|
||||
remove |= score.getUpdateType() == REMOVE;
|
||||
if (score.getUpdateType() == REMOVE) {
|
||||
remove |= score.getUpdateType() == REMOVE || score.getUpdateType() == UPDATE;
|
||||
|
||||
if (score.getUpdateType() == REMOVE || globalRemove) {
|
||||
add = false;
|
||||
}
|
||||
|
||||
if (score.getUpdateType() == UPDATE || teamChanged) {
|
||||
if (score.getUpdateType() == ADD) {
|
||||
remove = false;
|
||||
}
|
||||
|
||||
if (score.getUpdateType() == ADD || score.getUpdateType() == UPDATE || teamChanged) {
|
||||
score.update();
|
||||
}
|
||||
|
||||
@ -191,12 +192,6 @@ public class Scoreboard {
|
||||
removeScores.add(score.getCachedInfo());
|
||||
}
|
||||
|
||||
if (add || remove) {
|
||||
scoreChanged = true;
|
||||
}
|
||||
// score is pending to be updated, so we use the current score as the old score
|
||||
score.setOldScore(score.getScore());
|
||||
|
||||
// score is pending to be removed, so we can remove it from the objective
|
||||
if (score.getUpdateType() == REMOVE) {
|
||||
objective.removeScore0(score.getName());
|
||||
@ -205,17 +200,17 @@ public class Scoreboard {
|
||||
score.setUpdateType(NOTHING);
|
||||
}
|
||||
|
||||
if (globalRemove || globalUpdate || scoreChanged) {
|
||||
if (globalRemove || globalUpdate) {
|
||||
RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket();
|
||||
removeObjectivePacket.setObjectiveId(objective.getObjectiveName());
|
||||
session.sendUpstreamPacket(removeObjectivePacket);
|
||||
if (objective.getUpdateType() == REMOVE) {
|
||||
if (globalRemove) {
|
||||
objectives.remove(objective.getObjectiveName()); // now we can deregister
|
||||
objective.removed();
|
||||
}
|
||||
}
|
||||
|
||||
if (globalAdd || globalUpdate || scoreChanged) {
|
||||
if ((globalAdd || globalUpdate) && !globalRemove) {
|
||||
SetDisplayObjectivePacket displayObjectivePacket = new SetDisplayObjectivePacket();
|
||||
displayObjectivePacket.setObjectiveId(objective.getObjectiveName());
|
||||
displayObjectivePacket.setDisplayName(objective.getDisplayName());
|
||||
|
@ -28,6 +28,7 @@ package org.geysermc.connector.utils;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.Effect;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.item.ItemEntry;
|
||||
@ -138,4 +139,28 @@ public class BlockUtils {
|
||||
return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround, insideWaterNotOnGround);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a position, return the position if a block were located on the specified block face.
|
||||
* @param blockPos the block position
|
||||
* @param face the face of the block - see {@link com.github.steveice10.mc.protocol.data.game.world.block.BlockFace}
|
||||
* @return the block position with the block face accounted for
|
||||
*/
|
||||
public static Vector3i getBlockPosition(Vector3i blockPos, int face) {
|
||||
switch (face) {
|
||||
case 0:
|
||||
return blockPos.sub(0, 1, 0);
|
||||
case 1:
|
||||
return blockPos.add(0, 1, 0);
|
||||
case 2:
|
||||
return blockPos.sub(0, 0, 1);
|
||||
case 3:
|
||||
return blockPos.add(0, 0, 1);
|
||||
case 4:
|
||||
return blockPos.sub(1, 0, 0);
|
||||
case 5:
|
||||
return blockPos.add(1, 0, 0);
|
||||
}
|
||||
return blockPos;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,8 +25,10 @@
|
||||
|
||||
package org.geysermc.connector.utils;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.BitStorage;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||
import com.github.steveice10.mc.protocol.data.game.chunk.palette.Palette;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
@ -36,27 +38,39 @@ import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.nbt.NBTOutputStream;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtUtils;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import lombok.Getter;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.*;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.world.chunk.ChunkPosition;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.connector.network.translators.world.block.entity.RequiresBlockState;
|
||||
import org.geysermc.connector.network.translators.world.chunk.BlockStorage;
|
||||
import org.geysermc.connector.network.translators.world.chunk.ChunkSection;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray;
|
||||
import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.geysermc.connector.network.translators.world.block.BlockTranslator.AIR;
|
||||
import static org.geysermc.connector.network.translators.world.block.BlockTranslator.BEDROCK_WATER_ID;
|
||||
import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*;
|
||||
|
||||
@UtilityClass
|
||||
public class ChunkUtils {
|
||||
|
||||
/**
|
||||
@ -67,6 +81,9 @@ public class ChunkUtils {
|
||||
private static final NbtMap EMPTY_TAG = NbtMap.builder().build();
|
||||
public static final byte[] EMPTY_LEVEL_CHUNK_DATA;
|
||||
|
||||
public static final BlockStorage EMPTY_STORAGE = new BlockStorage();
|
||||
public static final ChunkSection EMPTY_SECTION = new ChunkSection(new BlockStorage[]{ EMPTY_STORAGE });
|
||||
|
||||
static {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size
|
||||
@ -76,73 +93,144 @@ public class ChunkUtils {
|
||||
}
|
||||
|
||||
EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray();
|
||||
}catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("Unable to generate empty level chunk data");
|
||||
}
|
||||
}
|
||||
|
||||
public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) {
|
||||
ChunkData chunkData = new ChunkData();
|
||||
Chunk[] chunks = column.getChunks();
|
||||
chunkData.sections = new ChunkSection[chunks.length];
|
||||
private static int indexYZXtoXZY(int yzx) {
|
||||
return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8);
|
||||
}
|
||||
|
||||
CompoundTag[] blockEntities = column.getTileEntities();
|
||||
// Temporarily stores positions of BlockState values per chunk load
|
||||
Object2IntMap<Position> blockEntityPositions = new Object2IntOpenHashMap<>();
|
||||
public static ChunkData translateToBedrock(GeyserSession session, Column column, boolean isNonFullChunk) {
|
||||
Chunk[] javaSections = column.getChunks();
|
||||
ChunkSection[] sections = new ChunkSection[javaSections.length];
|
||||
|
||||
// Temporarily stores compound tags of Bedrock-only block entities
|
||||
ObjectArrayList<NbtMap> bedrockOnlyBlockEntities = new ObjectArrayList<>();
|
||||
List<NbtMap> bedrockOnlyBlockEntities = Collections.emptyList();
|
||||
|
||||
for (int chunkY = 0; chunkY < chunks.length; chunkY++) {
|
||||
chunkData.sections[chunkY] = new ChunkSection();
|
||||
Chunk chunk = chunks[chunkY];
|
||||
BitSet waterloggedPaletteIds = new BitSet();
|
||||
BitSet pistonOrFlowerPaletteIds = new BitSet();
|
||||
|
||||
// Chunk is null and caching chunks is off or this isn't a non-full chunk
|
||||
if (chunk == null && (!session.getConnector().getConfig().isCacheChunks() || !isNonFullChunk))
|
||||
boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasMoreBlockDataThanChunkCache();
|
||||
|
||||
// If the received packet was a full chunk update, null sections in the chunk are guaranteed to also be null in the world manager
|
||||
boolean shouldCheckWorldManagerOnMissingSections = isNonFullChunk && worldManagerHasMoreBlockDataThanCache;
|
||||
Chunk temporarySection = null;
|
||||
|
||||
for (int sectionY = 0; sectionY < javaSections.length; sectionY++) {
|
||||
Chunk javaSection = javaSections[sectionY];
|
||||
|
||||
// Section is null, the cache will not contain anything of use
|
||||
if (javaSection == null) {
|
||||
// The column parameter contains all data currently available from the cache. If the chunk is null and the world manager
|
||||
// reports the ability to access more data than the cache, attempt to fetch from the world manager instead.
|
||||
if (shouldCheckWorldManagerOnMissingSections) {
|
||||
// Ensure that temporary chunk is set
|
||||
if (temporarySection == null) {
|
||||
temporarySection = new Chunk();
|
||||
}
|
||||
|
||||
// Read block data in section
|
||||
session.getConnector().getWorldManager().getBlocksInSection(session, column.getX(), sectionY, column.getZ(), temporarySection);
|
||||
|
||||
if (temporarySection.isEmpty()) {
|
||||
// The world manager only contains air for the given section
|
||||
// We can leave temporarySection as-is to allow it to potentially be re-used for later sections
|
||||
continue;
|
||||
} else {
|
||||
javaSection = temporarySection;
|
||||
|
||||
// Section contents have been modified, we can't re-use it
|
||||
temporarySection = null;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// No need to encode an empty section...
|
||||
if (javaSection.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If chunk is empty then no need to process
|
||||
if (chunk != null && chunk.isEmpty())
|
||||
continue;
|
||||
Palette javaPalette = javaSection.getPalette();
|
||||
IntList bedrockPalette = new IntArrayList(javaPalette.size());
|
||||
waterloggedPaletteIds.clear();
|
||||
pistonOrFlowerPaletteIds.clear();
|
||||
|
||||
ChunkSection section = chunkData.sections[chunkY];
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
int blockState;
|
||||
// If a non-full chunk, then grab the block that should be here to create a 'full' chunk
|
||||
if (chunk == null) {
|
||||
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
|
||||
blockState = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ());
|
||||
} else {
|
||||
blockState = chunk.get(x, y, z);
|
||||
}
|
||||
int id = BlockTranslator.getBedrockBlockId(blockState);
|
||||
// Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go
|
||||
for (int i = 0; i < javaPalette.size(); i++) {
|
||||
int javaId = javaPalette.idToState(i);
|
||||
bedrockPalette.add(BlockTranslator.getBedrockBlockId(javaId));
|
||||
|
||||
// Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently
|
||||
if (BlockTranslator.getBlockEntityString(blockState) != null) {
|
||||
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
|
||||
blockEntityPositions.put(pos, blockState);
|
||||
}
|
||||
if (BlockTranslator.isWaterlogged(javaId)) {
|
||||
waterloggedPaletteIds.set(i);
|
||||
}
|
||||
|
||||
section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id);
|
||||
// Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock
|
||||
if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) {
|
||||
pistonOrFlowerPaletteIds.set(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if block is piston or flower - only block entities in Bedrock
|
||||
if (BlockStateValues.getFlowerPotValues().containsKey(blockState) ||
|
||||
BlockStateValues.getPistonValues().containsKey(blockState)) {
|
||||
Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z);
|
||||
bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(Vector3i.from(pos.getX(), pos.getY(), pos.getZ()), blockState));
|
||||
}
|
||||
BitStorage javaData = javaSection.getStorage();
|
||||
|
||||
if (BlockTranslator.isWaterlogged(blockState)) {
|
||||
section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), BEDROCK_WATER_ID);
|
||||
}
|
||||
// Add Bedrock-exclusive block entities
|
||||
// We only if the palette contained any blocks that are Bedrock-exclusive block entities to avoid iterating through the whole block data
|
||||
// for no reason, as most sections will not contain any pistons or flower pots
|
||||
if (!pistonOrFlowerPaletteIds.isEmpty()) {
|
||||
bedrockOnlyBlockEntities = new ArrayList<>();
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int paletteId = javaData.get(yzx);
|
||||
if (pistonOrFlowerPaletteIds.get(paletteId)) {
|
||||
bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(
|
||||
Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)),
|
||||
javaPalette.idToState(paletteId)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitArray bedrockData = BitArrayVersion.forBitsCeil(javaData.getBitsPerEntry()).createArray(BlockStorage.SIZE);
|
||||
BlockStorage layer0 = new BlockStorage(bedrockData, bedrockPalette);
|
||||
BlockStorage[] layers;
|
||||
|
||||
// Convert data array from YZX to XZY coordinate order
|
||||
if (waterloggedPaletteIds.isEmpty()) {
|
||||
// No blocks are waterlogged, simply convert coordinate order
|
||||
// This could probably be optimized further...
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
bedrockData.set(indexYZXtoXZY(yzx), javaData.get(yzx));
|
||||
}
|
||||
|
||||
layers = new BlockStorage[]{ layer0 };
|
||||
} else {
|
||||
// The section contains waterlogged blocks, we need to convert coordinate order AND generate a V1 block storage for
|
||||
// layer 1 with palette ID 1 indicating water
|
||||
int[] layer1Data = new int[BlockStorage.SIZE >> 5];
|
||||
for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) {
|
||||
int paletteId = javaData.get(yzx);
|
||||
int xzy = indexYZXtoXZY(yzx);
|
||||
bedrockData.set(xzy, paletteId);
|
||||
|
||||
if (waterloggedPaletteIds.get(paletteId)) {
|
||||
layer1Data[xzy >> 5] |= 1 << (xzy & 0x1F);
|
||||
}
|
||||
}
|
||||
|
||||
// V1 palette
|
||||
IntList layer1Palette = new IntArrayList(2);
|
||||
layer1Palette.add(0); // Air
|
||||
layer1Palette.add(BEDROCK_WATER_ID);
|
||||
|
||||
layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) };
|
||||
}
|
||||
|
||||
sections[sectionY] = new ChunkSection(layers);
|
||||
}
|
||||
|
||||
CompoundTag[] blockEntities = column.getTileEntities();
|
||||
NbtMap[] bedrockBlockEntities = new NbtMap[blockEntities.length + bedrockOnlyBlockEntities.size()];
|
||||
int i = 0;
|
||||
while (i < blockEntities.length) {
|
||||
@ -156,7 +244,7 @@ public class ChunkUtils {
|
||||
for (Tag subTag : tag) {
|
||||
if (subTag instanceof StringTag) {
|
||||
StringTag stringTag = (StringTag) subTag;
|
||||
if (stringTag.getValue().equals("")) {
|
||||
if (stringTag.getValue().isEmpty()) {
|
||||
tagName = stringTag.getName();
|
||||
break;
|
||||
}
|
||||
@ -170,17 +258,25 @@ public class ChunkUtils {
|
||||
String id = BlockEntityUtils.getBedrockBlockEntityId(tagName);
|
||||
BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id);
|
||||
Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue());
|
||||
int blockState = blockEntityPositions.getOrDefault(pos, 0);
|
||||
|
||||
// Get Java blockstate ID from block entity position
|
||||
int blockState = 0;
|
||||
Chunk section = column.getChunks()[pos.getY() >> 4];
|
||||
if (section != null) {
|
||||
blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF);
|
||||
}
|
||||
|
||||
bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Append Bedrock-exclusive block entities to output array
|
||||
for (NbtMap tag : bedrockOnlyBlockEntities) {
|
||||
bedrockBlockEntities[i] = tag;
|
||||
i++;
|
||||
}
|
||||
|
||||
chunkData.blockEntities = bedrockBlockEntities;
|
||||
return chunkData;
|
||||
return new ChunkData(sections, bedrockBlockEntities);
|
||||
}
|
||||
|
||||
public static void updateChunkPosition(GeyserSession session, Vector3i position) {
|
||||
@ -250,7 +346,7 @@ public class ChunkUtils {
|
||||
break; //No block will be a part of two classes
|
||||
}
|
||||
}
|
||||
session.getChunkCache().updateBlock(new Position(position.getX(), position.getY(), position.getZ()), blockState);
|
||||
session.getChunkCache().updateBlock(position.getX(), position.getY(), position.getZ(), blockState);
|
||||
}
|
||||
|
||||
public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) {
|
||||
@ -278,10 +374,10 @@ public class ChunkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static final class ChunkData {
|
||||
public ChunkSection[] sections;
|
||||
private final ChunkSection[] sections;
|
||||
|
||||
@Getter
|
||||
private NbtMap[] blockEntities = new NbtMap[0];
|
||||
private final NbtMap[] blockEntities;
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,8 @@ public class LanguageUtils {
|
||||
*/
|
||||
public static void loadGeyserLocale(String locale) {
|
||||
locale = formatLocale(locale);
|
||||
// Don't load the locale if it's already loaded.
|
||||
if (LOCALE_MAPPINGS.containsKey(locale)) return;
|
||||
|
||||
InputStream localeStream = GeyserConnector.class.getClassLoader().getResourceAsStream("languages/texts/" + locale + ".properties");
|
||||
|
||||
@ -131,7 +133,7 @@ public class LanguageUtils {
|
||||
* @param locale The locale to format
|
||||
* @return The formatted locale
|
||||
*/
|
||||
private static String formatLocale(String locale) {
|
||||
public static String formatLocale(String locale) {
|
||||
try {
|
||||
String[] parts = locale.toLowerCase().split("_");
|
||||
return parts[0] + "_" + parts[1].toUpperCase();
|
||||
|
@ -35,7 +35,6 @@ import org.geysermc.connector.GeyserConnector;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -182,7 +182,7 @@ public class LoginEncryptionUtils {
|
||||
}
|
||||
|
||||
public static void buildAndShowLoginDetailsWindow(GeyserSession session) {
|
||||
String userLanguage = session.getClientData().getLanguageCode();
|
||||
String userLanguage = session.getLocale();
|
||||
session.sendForm(
|
||||
CustomForm.builder()
|
||||
.translator(LanguageUtils::getPlayerLocaleString, userLanguage)
|
||||
|
@ -74,4 +74,15 @@ public class MathUtils {
|
||||
}
|
||||
return (Byte) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Packs a chunk's X and Z coordinates into a single {@code long}.
|
||||
*
|
||||
* @param x the X coordinate
|
||||
* @param z the Z coordinate
|
||||
* @return the packed coordinates
|
||||
*/
|
||||
public static long chunkPositionToLong(int x, int z) {
|
||||
return ((x & 0xFFFFFFFFL) << 32L) | (z & 0xFFFFFFFFL);
|
||||
}
|
||||
}
|
||||
|
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren