Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-03 14:50:19 +01:00
Merge branch 'feature/extensions' into api-version-check
Dieser Commit ist enthalten in:
Commit
22b16e3587
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -102,7 +102,7 @@ pipeline {
|
|||||||
success {
|
success {
|
||||||
script {
|
script {
|
||||||
if (env.BRANCH_NAME == 'master') {
|
if (env.BRANCH_NAME == 'master') {
|
||||||
build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.18', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
||||||
build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
|||||||
|
|
||||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||||
|
|
||||||
### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.10 and Minecraft Java 1.19.0.
|
### Currently supporting Minecraft Bedrock 1.19.0 - 1.19.10/1.19.11 and Minecraft Java 1.19.0.
|
||||||
|
|
||||||
## Setting Up
|
## Setting Up
|
||||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||||
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.platform.bungeecord;
|
|||||||
|
|
||||||
import net.md_5.bungee.api.config.ListenerInfo;
|
import net.md_5.bungee.api.config.ListenerInfo;
|
||||||
import net.md_5.bungee.api.plugin.Plugin;
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.geysermc.common.PlatformType;
|
import org.geysermc.common.PlatformType;
|
||||||
import org.geysermc.geyser.GeyserBootstrap;
|
import org.geysermc.geyser.GeyserBootstrap;
|
||||||
@ -65,6 +66,20 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
|
|||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
GeyserLocale.init(this);
|
GeyserLocale.init(this);
|
||||||
|
|
||||||
|
// Copied from ViaVersion.
|
||||||
|
// https://github.com/ViaVersion/ViaVersion/blob/b8072aad86695cc8ec6f5e4103e43baf3abf6cc5/bungee/src/main/java/us/myles/ViaVersion/BungeePlugin.java#L43
|
||||||
|
try {
|
||||||
|
ProtocolConstants.class.getField("MINECRAFT_1_19_1");
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
getLogger().warning(" / \\");
|
||||||
|
getLogger().warning(" / \\");
|
||||||
|
getLogger().warning(" / | \\");
|
||||||
|
getLogger().warning(" / | \\ " + GeyserLocale.getLocaleStringLog("geyser.bootstrap.unsupported_proxy", getProxy().getName()));
|
||||||
|
getLogger().warning(" / \\ " + GeyserLocale.getLocaleStringLog("geyser.may_not_work_as_intended_all_caps"));
|
||||||
|
getLogger().warning(" / o \\");
|
||||||
|
getLogger().warning("/_____________\\");
|
||||||
|
}
|
||||||
|
|
||||||
if (!getDataFolder().exists())
|
if (!getDataFolder().exists())
|
||||||
getDataFolder().mkdir();
|
getDataFolder().mkdir();
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
|||||||
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
this.geyserSpigotPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
geyserLogger.info("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
|
geyserLogger.debug("Spigot ping passthrough type: " + (this.geyserSpigotPingPassthrough == null ? null : this.geyserSpigotPingPassthrough.getClass()));
|
||||||
|
|
||||||
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
|
this.geyserCommandManager = new GeyserSpigotCommandManager(geyser);
|
||||||
this.geyserCommandManager.init();
|
this.geyserCommandManager.init();
|
||||||
|
@ -51,6 +51,7 @@ import org.geysermc.geyser.platform.standalone.command.GeyserStandaloneCommandMa
|
|||||||
import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI;
|
import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.util.FileUtils;
|
import org.geysermc.geyser.util.FileUtils;
|
||||||
|
import org.geysermc.geyser.util.LoopbackUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -188,7 +189,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
|||||||
|
|
||||||
geyserLogger = new GeyserStandaloneLogger();
|
geyserLogger = new GeyserStandaloneLogger();
|
||||||
|
|
||||||
LoopbackUtil.checkLoopback(geyserLogger);
|
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
|
File configFile = FileUtils.fileOrCopiedFromResource(new File(configFilename), "config.yml",
|
||||||
|
@ -35,6 +35,7 @@ import com.velocitypowered.api.network.ListenerType;
|
|||||||
import com.velocitypowered.api.plugin.Plugin;
|
import com.velocitypowered.api.plugin.Plugin;
|
||||||
import com.velocitypowered.api.proxy.ProxyServer;
|
import com.velocitypowered.api.proxy.ProxyServer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import net.kyori.adventure.util.Codec;
|
||||||
import org.geysermc.common.PlatformType;
|
import org.geysermc.common.PlatformType;
|
||||||
import org.geysermc.geyser.GeyserBootstrap;
|
import org.geysermc.geyser.GeyserBootstrap;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
@ -84,6 +85,15 @@ public class GeyserVelocityPlugin implements GeyserBootstrap {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
|
try {
|
||||||
|
Codec.class.getMethod("codec", Codec.Decoder.class, Codec.Encoder.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// velocitypowered.com has a build that is very outdated
|
||||||
|
logger.error("Please download Velocity from https://papermc.io/downloads#Velocity - the 'stable' Velocity version " +
|
||||||
|
"that has likely been downloaded is very outdated and does not support 1.19.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
GeyserLocale.init(this);
|
GeyserLocale.init(this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -35,7 +35,7 @@ object Versions {
|
|||||||
// See comment in settings.gradle.kts
|
// See comment in settings.gradle.kts
|
||||||
const val raknetVersion = "1.6.28-SNAPSHOT"
|
const val raknetVersion = "1.6.28-SNAPSHOT"
|
||||||
const val mcauthlibVersion = "d9d773e"
|
const val mcauthlibVersion = "d9d773e"
|
||||||
const val mcprotocollibversion = "54fc9f0"
|
const val mcprotocollibversion = "9f78bd5"
|
||||||
const val packetlibVersion = "3.0"
|
const val packetlibVersion = "3.0"
|
||||||
const val adventureVersion = "4.9.3"
|
const val adventureVersion = "4.9.3"
|
||||||
const val eventVersion = "3.0.0"
|
const val eventVersion = "3.0.0"
|
||||||
|
@ -81,8 +81,6 @@ import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
|
|||||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
import org.geysermc.geyser.util.*;
|
import org.geysermc.geyser.util.*;
|
||||||
|
|
||||||
import javax.naming.directory.Attribute;
|
|
||||||
import javax.naming.directory.InitialDirContext;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -250,24 +248,13 @@ public class GeyserImpl implements GeyserApi {
|
|||||||
String remoteAddress = config.getRemote().address();
|
String remoteAddress = config.getRemote().address();
|
||||||
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
|
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
|
||||||
if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) {
|
if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) {
|
||||||
int remotePort;
|
String[] record = WebUtils.findSrvRecord(this, remoteAddress);
|
||||||
try {
|
if (record != null) {
|
||||||
// Searches for a server address and a port from a SRV record of the specified host name
|
int remotePort = Integer.parseInt(record[2]);
|
||||||
InitialDirContext ctx = new InitialDirContext();
|
|
||||||
Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV");
|
|
||||||
// size > 0 = SRV entry found
|
|
||||||
if (attr != null && attr.size() > 0) {
|
|
||||||
String[] record = ((String) attr.get(0)).split(" ");
|
|
||||||
// Overwrites the existing address and port with that from the SRV record.
|
|
||||||
config.getRemote().setAddress(remoteAddress = record[3]);
|
config.getRemote().setAddress(remoteAddress = record[3]);
|
||||||
config.getRemote().setPort(remotePort = Integer.parseInt(record[2]));
|
config.getRemote().setPort(remotePort);
|
||||||
logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\"");
|
logger.debug("Found SRV record \"" + remoteAddress + ":" + remotePort + "\"");
|
||||||
}
|
}
|
||||||
} catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes
|
|
||||||
logger.debug("Exception while trying to find an SRV record for the remote host.");
|
|
||||||
if (config.isDebugMode())
|
|
||||||
ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
|
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
|
||||||
|
@ -66,6 +66,7 @@ public abstract class GeyserCommandManager extends CommandManager {
|
|||||||
registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
registerBuiltInCommand(new StatisticsCommand(geyser, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
|
||||||
registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
registerBuiltInCommand(new AdvancementsCommand("advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
||||||
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
|
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
|
||||||
|
registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
|
||||||
if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) {
|
if (GeyserImpl.getInstance().getPlatformType() == PlatformType.STANDALONE) {
|
||||||
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.geyser.command.defaults;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import org.geysermc.common.PlatformType;
|
||||||
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
import org.geysermc.geyser.command.GeyserCommand;
|
||||||
|
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||||
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
import org.geysermc.geyser.util.LoopbackUtil;
|
||||||
|
import org.geysermc.geyser.util.WebUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class ConnectionTestCommand extends GeyserCommand {
|
||||||
|
private final GeyserImpl geyser;
|
||||||
|
|
||||||
|
public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) {
|
||||||
|
super(name, description, permission);
|
||||||
|
this.geyser = geyser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(@Nullable GeyserSession session, GeyserCommandSource sender, String[] args) {
|
||||||
|
// Only allow the console to create dumps on Geyser Standalone
|
||||||
|
if (!sender.isConsole() && geyser.getPlatformType() == PlatformType.STANDALONE) {
|
||||||
|
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0) {
|
||||||
|
sender.sendMessage("Provide the Bedrock server IP you are trying to connect with. Example: `test.geysermc.org:19132`");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still allow people to not supply a port and fallback to 19132
|
||||||
|
String[] fullAddress = args[0].split(":", 2);
|
||||||
|
int port;
|
||||||
|
if (fullAddress.length == 2) {
|
||||||
|
port = Integer.parseInt(fullAddress[1]);
|
||||||
|
} else {
|
||||||
|
port = 19132;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue: do the ports not line up?
|
||||||
|
if (port != geyser.getConfig().getBedrock().port()) {
|
||||||
|
sender.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration ("
|
||||||
|
+ geyser.getConfig().getBedrock().port() + "). You can change it under `bedrock` `port`.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue: is the `bedrock` `address` in the config different?
|
||||||
|
if (!geyser.getConfig().getBedrock().address().equals("0.0.0.0")) {
|
||||||
|
sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue: did someone turn on enable-proxy-protocol and they didn't mean it?
|
||||||
|
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||||
|
sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
|
||||||
|
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
// Issue: SRV record?
|
||||||
|
String ip = fullAddress[0];
|
||||||
|
String[] record = WebUtils.findSrvRecord(geyser, ip);
|
||||||
|
if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) {
|
||||||
|
sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
|
||||||
|
+ ". If that fails, re-run this command with that address and port.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue: does Loopback need applying?
|
||||||
|
if (LoopbackUtil.needsLoopback(GeyserImpl.getInstance().getLogger())) {
|
||||||
|
sender.sendMessage("Loopback is not applied on this computer! You will have issues connecting from the same computer. " +
|
||||||
|
"See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// mcsrvstatus will likely be replaced in the future with our own service where we can also test
|
||||||
|
// around the OVH workaround without worrying about caching
|
||||||
|
JsonNode output = WebUtils.getJson("https://api.mcsrvstat.us/bedrock/2/" + args[0]);
|
||||||
|
|
||||||
|
long cacheTime = output.get("debug").get("cachetime").asLong();
|
||||||
|
String when;
|
||||||
|
if (cacheTime == 0) {
|
||||||
|
when = "now";
|
||||||
|
} else {
|
||||||
|
when = ((System.currentTimeMillis() / 1000L) - cacheTime) + " seconds ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.get("online").asBoolean()) {
|
||||||
|
sender.sendMessage("Your server is likely online as of " + when + "!");
|
||||||
|
sendLinks(sender);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage("Your server is likely unreachable from outside the network as of " + when + ".");
|
||||||
|
sendLinks(sender);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sender.sendMessage("Error while trying to check your connection!");
|
||||||
|
geyser.getLogger().error("Error while trying to check your connection!", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendLinks(GeyserCommandSource sender) {
|
||||||
|
sender.sendMessage("If you still have issues, check to see if your hosting provider has a specific setup: " +
|
||||||
|
"https://wiki.geysermc.org/geyser/supported-hosting-providers/" + ", see this page: "
|
||||||
|
+ "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on our Discord: " + "https://discord.gg/geysermc");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuggestedOpOnly() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -453,6 +453,8 @@ public final class EntityDefinitions {
|
|||||||
ALLAY = EntityDefinition.inherited(AllayEntity::new, mobEntityBase)
|
ALLAY = EntityDefinition.inherited(AllayEntity::new, mobEntityBase)
|
||||||
.type(EntityType.ALLAY)
|
.type(EntityType.ALLAY)
|
||||||
.height(0.6f).width(0.35f)
|
.height(0.6f).width(0.35f)
|
||||||
|
.addTranslator(MetadataType.BOOLEAN, AllayEntity::setDancing)
|
||||||
|
.addTranslator(MetadataType.BOOLEAN, AllayEntity::setCanDuplicate)
|
||||||
.build();
|
.build();
|
||||||
BAT = EntityDefinition.inherited(BatEntity::new, mobEntityBase)
|
BAT = EntityDefinition.inherited(BatEntity::new, mobEntityBase)
|
||||||
.type(EntityType.BAT)
|
.type(EntityType.BAT)
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.entity.type.living;
|
package org.geysermc.geyser.entity.type.living;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||||
import com.nukkitx.math.vector.Vector3f;
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
|
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
@ -37,14 +39,27 @@ import javax.annotation.Nonnull;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class AllayEntity extends MobEntity {
|
public class AllayEntity extends MobEntity {
|
||||||
|
private boolean canDuplicate;
|
||||||
|
|
||||||
public AllayEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
public AllayEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDancing(BooleanEntityMetadata entityMetadata) {
|
||||||
|
setFlag(EntityFlag.DANCING, entityMetadata.getPrimitiveValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCanDuplicate(BooleanEntityMetadata entityMetadata) {
|
||||||
|
this.canDuplicate = entityMetadata.getPrimitiveValue();
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
protected InteractiveTag testMobInteraction(@Nonnull Hand hand, @Nonnull GeyserItemStack itemInHand) {
|
protected InteractiveTag testMobInteraction(@Nonnull Hand hand, @Nonnull GeyserItemStack itemInHand) {
|
||||||
if (!this.hand.isValid() && !itemInHand.isEmpty()) {
|
if (this.canDuplicate && getFlag(EntityFlag.DANCING) && isDuplicationItem(itemInHand)) {
|
||||||
|
// Maybe better as another tag?
|
||||||
|
return InteractiveTag.GIVE_ITEM_TO_ALLAY;
|
||||||
|
} else if (!this.hand.isValid() && !itemInHand.isEmpty()) {
|
||||||
return InteractiveTag.GIVE_ITEM_TO_ALLAY;
|
return InteractiveTag.GIVE_ITEM_TO_ALLAY;
|
||||||
} else if (this.hand.isValid() && hand == Hand.MAIN_HAND && itemInHand.isEmpty()) {
|
} else if (this.hand.isValid() && hand == Hand.MAIN_HAND && itemInHand.isEmpty()) {
|
||||||
// Seems like there isn't a good tag for this yet
|
// Seems like there isn't a good tag for this yet
|
||||||
@ -57,7 +72,10 @@ public class AllayEntity extends MobEntity {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
protected InteractionResult mobInteract(@Nonnull Hand hand, @Nonnull GeyserItemStack itemInHand) {
|
protected InteractionResult mobInteract(@Nonnull Hand hand, @Nonnull GeyserItemStack itemInHand) {
|
||||||
if (!this.hand.isValid() && !itemInHand.isEmpty()) {
|
if (this.canDuplicate && getFlag(EntityFlag.DANCING) && isDuplicationItem(itemInHand)) {
|
||||||
|
//TOCHECK sound
|
||||||
|
return InteractionResult.SUCCESS;
|
||||||
|
} else if (!this.hand.isValid() && !itemInHand.isEmpty()) {
|
||||||
//TODO play sound?
|
//TODO play sound?
|
||||||
return InteractionResult.SUCCESS;
|
return InteractionResult.SUCCESS;
|
||||||
} else if (this.hand.isValid() && hand == Hand.MAIN_HAND && itemInHand.isEmpty()) {
|
} else if (this.hand.isValid() && hand == Hand.MAIN_HAND && itemInHand.isEmpty()) {
|
||||||
@ -67,4 +85,8 @@ public class AllayEntity extends MobEntity {
|
|||||||
return super.mobInteract(hand, itemInHand);
|
return super.mobInteract(hand, itemInHand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isDuplicationItem(GeyserItemStack itemStack) {
|
||||||
|
return itemStack.getJavaId() == session.getItemMappings().getStoredItems().amethystShard();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,13 @@
|
|||||||
package org.geysermc.geyser.entity.type.living;
|
package org.geysermc.geyser.entity.type.living;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||||
import com.nukkitx.math.vector.Vector3f;
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
||||||
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.geysermc.geyser.entity.EntityDefinition;
|
import org.geysermc.geyser.entity.EntityDefinition;
|
||||||
@ -54,6 +54,8 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
@Getter
|
@Getter
|
||||||
private boolean isSmall = false;
|
private boolean isSmall = false;
|
||||||
|
|
||||||
|
private boolean isNameTagVisible = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock.
|
* On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock.
|
||||||
* By having a second entity, we can allow an invisible entity with the name tag.
|
* By having a second entity, we can allow an invisible entity with the name tag.
|
||||||
@ -75,7 +77,6 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
* - No armor, no name: false
|
* - No armor, no name: false
|
||||||
* - No armor, yes name: true
|
* - No armor, yes name: true
|
||||||
*/
|
*/
|
||||||
@Getter
|
|
||||||
private boolean positionRequiresOffset = false;
|
private boolean positionRequiresOffset = false;
|
||||||
/**
|
/**
|
||||||
* Whether we should update the position of this armor stand after metadata updates.
|
* Whether we should update the position of this armor stand after metadata updates.
|
||||||
@ -88,7 +89,11 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void spawnEntity() {
|
public void spawnEntity() {
|
||||||
|
Vector3f javaPosition = position;
|
||||||
|
// Apply the offset if we're the second entity
|
||||||
|
position = position.up(getYOffset());
|
||||||
super.spawnEntity();
|
super.spawnEntity();
|
||||||
|
position = javaPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -101,22 +106,18 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
|
||||||
if (secondEntity != null) {
|
moveAbsolute(position.add(relX, relY, relZ), yaw, pitch, headYaw, onGround, false);
|
||||||
secondEntity.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
|
|
||||||
}
|
|
||||||
super.moveRelative(relX, relY, relZ, yaw, pitch, headYaw, isOnGround);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||||
if (secondEntity != null) {
|
if (secondEntity != null) {
|
||||||
secondEntity.moveAbsolute(applyOffsetToPosition(position), yaw, pitch, headYaw, isOnGround, teleported);
|
secondEntity.moveAbsolute(position, yaw, pitch, headYaw, isOnGround, teleported);
|
||||||
} else if (positionRequiresOffset) {
|
|
||||||
// Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands
|
|
||||||
position = applyOffsetToPosition(position);
|
|
||||||
}
|
}
|
||||||
|
// Fake the height to be above where it is so the nametag appears in the right location
|
||||||
super.moveAbsolute(position, yaw, yaw, yaw, isOnGround, teleported);
|
float yOffset = getYOffset();
|
||||||
|
super.moveAbsolute(yOffset != 0 ? position.up(yOffset) : position , yaw, yaw, yaw, isOnGround, teleported);
|
||||||
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -127,20 +128,14 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
|
|
||||||
public void setArmorStandFlags(ByteEntityMetadata entityMetadata) {
|
public void setArmorStandFlags(ByteEntityMetadata entityMetadata) {
|
||||||
byte xd = entityMetadata.getPrimitiveValue();
|
byte xd = entityMetadata.getPrimitiveValue();
|
||||||
|
boolean offsetChanged = false;
|
||||||
// isSmall
|
// isSmall
|
||||||
boolean newIsSmall = (xd & 0x01) == 0x01;
|
boolean newIsSmall = (xd & 0x01) == 0x01;
|
||||||
if (newIsSmall != isSmall) {
|
if (newIsSmall != isSmall) {
|
||||||
if (positionRequiresOffset) {
|
|
||||||
// Fix new inconsistency with offset
|
|
||||||
this.position = fixOffsetForSize(position, newIsSmall);
|
|
||||||
positionUpdateRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSmall = newIsSmall;
|
isSmall = newIsSmall;
|
||||||
if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780
|
offsetChanged = true;
|
||||||
toggleSmallStatus();
|
// Update the passenger offset as the armor stand's height has changed
|
||||||
}
|
updatePassengerOffsets();
|
||||||
}
|
}
|
||||||
|
|
||||||
// setMarker
|
// setMarker
|
||||||
@ -150,12 +145,21 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
if (isMarker) {
|
if (isMarker) {
|
||||||
setBoundingBoxWidth(0.0f);
|
setBoundingBoxWidth(0.0f);
|
||||||
setBoundingBoxHeight(0.0f);
|
setBoundingBoxHeight(0.0f);
|
||||||
dirtyMetadata.put(EntityData.SCALE, 0f);
|
|
||||||
} else {
|
} else {
|
||||||
toggleSmallStatus();
|
setBoundingBoxWidth(definition.width());
|
||||||
|
setBoundingBoxHeight(definition.height());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMountOffset();
|
updateMountOffset();
|
||||||
|
offsetChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offsetChanged) {
|
||||||
|
if (positionRequiresOffset) {
|
||||||
|
positionUpdateRequired = true;
|
||||||
|
} else if (secondEntity != null) {
|
||||||
|
secondEntity.positionUpdateRequired = true;
|
||||||
|
}
|
||||||
updateSecondEntityStatus(false);
|
updateSecondEntityStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +230,7 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
super.updateBedrockMetadata();
|
super.updateBedrockMetadata();
|
||||||
if (positionUpdateRequired) {
|
if (positionUpdateRequired) {
|
||||||
positionUpdateRequired = false;
|
positionUpdateRequired = false;
|
||||||
updatePosition();
|
moveAbsolute(position, yaw, pitch, headYaw, onGround, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,6 +289,13 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
updateSecondEntityStatus(true);
|
updateSecondEntityStatus(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) {
|
||||||
|
super.setDisplayNameVisible(entityMetadata);
|
||||||
|
isNameTagVisible = entityMetadata.getPrimitiveValue();
|
||||||
|
updateSecondEntityStatus(false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if we need to load or unload the second entity.
|
* Determine if we need to load or unload the second entity.
|
||||||
*
|
*
|
||||||
@ -293,40 +304,44 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
private void updateSecondEntityStatus(boolean sendMetadata) {
|
private void updateSecondEntityStatus(boolean sendMetadata) {
|
||||||
// A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows.
|
// A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows.
|
||||||
if (!primaryEntity) return;
|
if (!primaryEntity) return;
|
||||||
if (!isInvisible || isMarker) {
|
if (!isInvisible) {
|
||||||
// It is either impossible to show armor, or the armor stand isn't invisible. We good.
|
// The armor stand isn't invisible. We good.
|
||||||
setFlag(EntityFlag.INVISIBLE, false);
|
setFlag(EntityFlag.INVISIBLE, false);
|
||||||
|
dirtyMetadata.put(EntityData.SCALE, getScale());
|
||||||
updateOffsetRequirement(false);
|
updateOffsetRequirement(false);
|
||||||
if (positionUpdateRequired) {
|
|
||||||
positionUpdateRequired = false;
|
|
||||||
updatePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secondEntity != null) {
|
if (secondEntity != null) {
|
||||||
secondEntity.despawnEntity();
|
secondEntity.despawnEntity();
|
||||||
secondEntity = null;
|
secondEntity = null;
|
||||||
}
|
}
|
||||||
|
if (sendMetadata) {
|
||||||
|
this.updateBedrockMetadata();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean isNametagEmpty = nametag.isEmpty();
|
boolean isNametagEmpty = nametag.isEmpty();
|
||||||
if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR)
|
if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR)
|
||||||
|| !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) {
|
|| !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) {
|
||||||
// If the second entity exists, no need to recreate it.
|
// Reset scale of the proper armor stand
|
||||||
// We can't stuff this check above or else it'll fall into another else case and delete the second entity
|
this.dirtyMetadata.put(EntityData.SCALE, getScale());
|
||||||
if (secondEntity != null) return;
|
// Set the proper armor stand to invisible to show armor
|
||||||
|
setFlag(EntityFlag.INVISIBLE, true);
|
||||||
|
// Update the position of the armor stand
|
||||||
|
updateOffsetRequirement(false);
|
||||||
|
|
||||||
|
if (secondEntity == null) {
|
||||||
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
|
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
|
||||||
// the metadata as it will hold the name tag.
|
// the metadata as it will hold the name tag.
|
||||||
secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null,
|
secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null,
|
||||||
EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw());
|
EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw());
|
||||||
secondEntity.primaryEntity = false;
|
secondEntity.primaryEntity = false;
|
||||||
if (!this.positionRequiresOffset) {
|
|
||||||
// Ensure the offset is applied for the 0 scale
|
|
||||||
secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position);
|
|
||||||
}
|
}
|
||||||
// Copy metadata
|
// Copy metadata
|
||||||
secondEntity.isSmall = isSmall;
|
secondEntity.isSmall = isSmall;
|
||||||
//secondEntity.getDirtyMetadata().putAll(dirtyMetadata); //TODO check
|
secondEntity.isMarker = isMarker;
|
||||||
|
secondEntity.positionRequiresOffset = true; // Offset should always be applied
|
||||||
|
secondEntity.getDirtyMetadata().put(EntityData.NAMETAG, nametag);
|
||||||
|
secondEntity.getDirtyMetadata().put(EntityData.NAMETAG_ALWAYS_SHOW, isNameTagVisible ? (byte) 1 : (byte) 0);
|
||||||
secondEntity.flags.merge(this.flags);
|
secondEntity.flags.merge(this.flags);
|
||||||
// Guarantee this copy is NOT invisible
|
// Guarantee this copy is NOT invisible
|
||||||
secondEntity.setFlag(EntityFlag.INVISIBLE, false);
|
secondEntity.setFlag(EntityFlag.INVISIBLE, false);
|
||||||
@ -335,18 +350,13 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
// No bounding box as we don't want to interact with this entity
|
// No bounding box as we don't want to interact with this entity
|
||||||
secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
|
secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
|
||||||
secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
|
secondEntity.getDirtyMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
|
||||||
|
if (!secondEntity.valid) { // Spawn the entity once
|
||||||
secondEntity.spawnEntity();
|
secondEntity.spawnEntity();
|
||||||
|
}
|
||||||
// Reset scale of the proper armor stand
|
|
||||||
this.dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
|
|
||||||
// Set the proper armor stand to invisible to show armor
|
|
||||||
setFlag(EntityFlag.INVISIBLE, true);
|
|
||||||
// Update the position of the armor stand
|
|
||||||
updateOffsetRequirement(false);
|
|
||||||
} else if (isNametagEmpty) {
|
} else if (isNametagEmpty) {
|
||||||
// We can just make an invisible entity
|
// We can just make an invisible entity
|
||||||
// Reset scale of the proper armor stand
|
// Reset scale of the proper armor stand
|
||||||
dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
|
dirtyMetadata.put(EntityData.SCALE, getScale());
|
||||||
// Set the proper armor stand to invisible to show armor
|
// Set the proper armor stand to invisible to show armor
|
||||||
setFlag(EntityFlag.INVISIBLE, true);
|
setFlag(EntityFlag.INVISIBLE, true);
|
||||||
// Update offset
|
// Update offset
|
||||||
@ -362,7 +372,7 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
setFlag(EntityFlag.INVISIBLE, false);
|
setFlag(EntityFlag.INVISIBLE, false);
|
||||||
dirtyMetadata.put(EntityData.SCALE, 0.0f);
|
dirtyMetadata.put(EntityData.SCALE, 0.0f);
|
||||||
// As the above is applied, we need an offset
|
// As the above is applied, we need an offset
|
||||||
updateOffsetRequirement(true);
|
updateOffsetRequirement(!isMarker);
|
||||||
|
|
||||||
if (secondEntity != null) {
|
if (secondEntity != null) {
|
||||||
secondEntity.despawnEntity();
|
secondEntity.despawnEntity();
|
||||||
@ -374,35 +384,34 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* If this armor stand is not a marker, set its bounding box size and scale.
|
public float getBoundingBoxWidth() {
|
||||||
*/
|
// For consistency with getBoundingBoxHeight()
|
||||||
private void toggleSmallStatus() {
|
return super.getBoundingBoxWidth() * getScale();
|
||||||
setBoundingBoxWidth(isSmall ? 0.25f : definition.width());
|
}
|
||||||
setBoundingBoxHeight(isSmall ? 0.9875f : definition.height());
|
|
||||||
dirtyMetadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
|
@Override
|
||||||
|
public float getBoundingBoxHeight() {
|
||||||
|
// This is required so that EntityUtils#updateMountOffset() calculates the correct offset for small
|
||||||
|
// armor stands. The bounding box height is not changed as the SCALE entity data handles that for us.
|
||||||
|
return super.getBoundingBoxHeight() * getScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the selected position with the position offset applied.
|
* @return the y offset required to position the name tag correctly
|
||||||
*/
|
*/
|
||||||
private Vector3f applyOffsetToPosition(Vector3f position) {
|
public float getYOffset() {
|
||||||
return position.add(0d, definition.height() * (isSmall ? 0.55d : 1d), 0d);
|
if (!positionRequiresOffset || isMarker || secondEntity != null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return definition.height() * getScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return an adjusted offset for the new small status.
|
* @return the scale according to Java
|
||||||
*/
|
*/
|
||||||
private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) {
|
private float getScale() {
|
||||||
position = removeOffsetFromPosition(position);
|
return isSmall ? 0.5f : 1f;
|
||||||
return position.add(0d, definition.height() * (isNowSmall ? 0.55d : 1d), 0d);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the selected position with the position offset removed.
|
|
||||||
*/
|
|
||||||
private Vector3f removeOffsetFromPosition(Vector3f position) {
|
|
||||||
return position.sub(0d, definition.height() * (isSmall ? 0.55d : 1d), 0d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -411,28 +420,10 @@ public class ArmorStandEntity extends LivingEntity {
|
|||||||
private void updateOffsetRequirement(boolean newValue) {
|
private void updateOffsetRequirement(boolean newValue) {
|
||||||
if (newValue != positionRequiresOffset) {
|
if (newValue != positionRequiresOffset) {
|
||||||
this.positionRequiresOffset = newValue;
|
this.positionRequiresOffset = newValue;
|
||||||
if (positionRequiresOffset) {
|
this.positionUpdateRequired = true;
|
||||||
this.position = applyOffsetToPosition(position);
|
// Update the passenger offset as the armor stand's y offset has changed
|
||||||
// Update the passenger offset as armorstand is moving up by roughly 2 blocks
|
|
||||||
updatePassengerOffsets();
|
updatePassengerOffsets();
|
||||||
} else {
|
|
||||||
this.position = removeOffsetFromPosition(position);
|
|
||||||
}
|
}
|
||||||
positionUpdateRequired = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates position without calling movement code.
|
|
||||||
*/
|
|
||||||
private void updatePosition() {
|
|
||||||
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
|
|
||||||
moveEntityPacket.setRuntimeEntityId(geyserId);
|
|
||||||
moveEntityPacket.setPosition(position);
|
|
||||||
moveEntityPacket.setRotation(getBedrockRotation());
|
|
||||||
moveEntityPacket.setOnGround(isOnGround());
|
|
||||||
moveEntityPacket.setTeleported(false);
|
|
||||||
session.sendUpstreamPacket(moveEntityPacket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,6 +38,7 @@ import java.util.Map;
|
|||||||
@Getter
|
@Getter
|
||||||
@Accessors(fluent = true)
|
@Accessors(fluent = true)
|
||||||
public class StoredItemMappings {
|
public class StoredItemMappings {
|
||||||
|
private final int amethystShard;
|
||||||
private final ItemMapping bamboo;
|
private final ItemMapping bamboo;
|
||||||
private final ItemMapping banner;
|
private final ItemMapping banner;
|
||||||
private final ItemMapping barrier;
|
private final ItemMapping barrier;
|
||||||
@ -71,6 +72,7 @@ public class StoredItemMappings {
|
|||||||
private final ItemMapping writableBook;
|
private final ItemMapping writableBook;
|
||||||
|
|
||||||
public StoredItemMappings(Map<String, ItemMapping> itemMappings) {
|
public StoredItemMappings(Map<String, ItemMapping> itemMappings) {
|
||||||
|
this.amethystShard = load(itemMappings, "amethyst_shard").getJavaId();
|
||||||
this.bamboo = load(itemMappings, "bamboo");
|
this.bamboo = load(itemMappings, "bamboo");
|
||||||
this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID
|
this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID
|
||||||
this.barrier = load(itemMappings, "barrier");
|
this.barrier = load(itemMappings, "barrier");
|
||||||
|
@ -61,7 +61,9 @@ public final class GameProtocol {
|
|||||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v527.V527_CODEC.toBuilder()
|
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v527.V527_CODEC.toBuilder()
|
||||||
.minecraftVersion("1.19.0/1.19.2")
|
.minecraftVersion("1.19.0/1.19.2")
|
||||||
.build());
|
.build());
|
||||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||||
|
.minecraftVersion("1.19.10/1.19.11")
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,6 +39,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
|
|||||||
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
||||||
import it.unimi.dsi.fastutil.ints.*;
|
import it.unimi.dsi.fastutil.ints.*;
|
||||||
import com.nukkitx.protocol.bedrock.v527.Bedrock_v527;
|
import com.nukkitx.protocol.bedrock.v527.Bedrock_v527;
|
||||||
|
import com.nukkitx.protocol.bedrock.v534.Bedrock_v534;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||||
import it.unimi.dsi.fastutil.ints.IntList;
|
import it.unimi.dsi.fastutil.ints.IntList;
|
||||||
@ -74,7 +75,9 @@ public class ItemRegistryPopulator {
|
|||||||
|
|
||||||
public static void populate() {
|
public static void populate() {
|
||||||
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
||||||
paletteVersions.put("1_19_0", new PaletteVersion(Bedrock_v527.V527_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
paletteVersions.put("1_19_0", new PaletteVersion(Bedrock_v527.V527_CODEC.getProtocolVersion(),
|
||||||
|
Collections.singletonMap("minecraft:trader_llama_spawn_egg", "minecraft:llama_spawn_egg")));
|
||||||
|
paletteVersions.put("1_19_10", new PaletteVersion(Bedrock_v534.V534_CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||||
|
|
||||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||||
|
|
||||||
@ -280,8 +283,7 @@ public class ItemRegistryPopulator {
|
|||||||
|
|
||||||
Set<String> javaOnlyItems = new ObjectOpenHashSet<>();
|
Set<String> javaOnlyItems = new ObjectOpenHashSet<>();
|
||||||
Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick",
|
Collections.addAll(javaOnlyItems, "minecraft:spectral_arrow", "minecraft:debug_stick",
|
||||||
"minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:trader_llama_spawn_egg",
|
"minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:bundle");
|
||||||
"minecraft:bundle");
|
|
||||||
if (!customItemsAllowed) {
|
if (!customItemsAllowed) {
|
||||||
javaOnlyItems.add("minecraft:furnace_minecart");
|
javaOnlyItems.add("minecraft:furnace_minecart");
|
||||||
}
|
}
|
||||||
|
@ -126,9 +126,9 @@ import org.geysermc.geyser.session.auth.AuthData;
|
|||||||
import org.geysermc.geyser.session.auth.BedrockClientData;
|
import org.geysermc.geyser.session.auth.BedrockClientData;
|
||||||
import org.geysermc.geyser.session.cache.*;
|
import org.geysermc.geyser.session.cache.*;
|
||||||
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
import org.geysermc.geyser.skin.FloodgateSkinUploader;
|
||||||
import org.geysermc.geyser.text.ChatTypeEntry;
|
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
import org.geysermc.geyser.text.MinecraftLocale;
|
import org.geysermc.geyser.text.MinecraftLocale;
|
||||||
|
import org.geysermc.geyser.text.TextDecoration;
|
||||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
import org.geysermc.geyser.util.ChunkUtils;
|
import org.geysermc.geyser.util.ChunkUtils;
|
||||||
@ -339,7 +339,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
*/
|
*/
|
||||||
private final Map<String, JavaDimension> dimensions = new Object2ObjectOpenHashMap<>(3);
|
private final Map<String, JavaDimension> dimensions = new Object2ObjectOpenHashMap<>(3);
|
||||||
|
|
||||||
private final Int2ObjectMap<ChatTypeEntry> chatTypes = new Int2ObjectOpenHashMap<>(8);
|
private final Int2ObjectMap<TextDecoration> chatTypes = new Int2ObjectOpenHashMap<>(7);
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private int breakingBlock;
|
private int breakingBlock;
|
||||||
@ -562,8 +562,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
this.playerEntity = new SessionPlayerEntity(this);
|
this.playerEntity = new SessionPlayerEntity(this);
|
||||||
collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition());
|
collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition());
|
||||||
|
|
||||||
ChatTypeEntry.applyDefaults(chatTypes);
|
|
||||||
|
|
||||||
this.playerInventory = new PlayerInventory();
|
this.playerInventory = new PlayerInventory();
|
||||||
this.openInventory = null;
|
this.openInventory = null;
|
||||||
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
|
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
|
||||||
@ -1384,14 +1382,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
* Sends a chat message to the Java server.
|
* Sends a chat message to the Java server.
|
||||||
*/
|
*/
|
||||||
public void sendChat(String message) {
|
public void sendChat(String message) {
|
||||||
sendDownstreamPacket(new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0L, ByteArrays.EMPTY_ARRAY, false));
|
sendDownstreamPacket(new ServerboundChatPacket(message, Instant.now().toEpochMilli(), 0L, ByteArrays.EMPTY_ARRAY, false, Collections.emptyList(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a command to the Java server.
|
* Sends a command to the Java server.
|
||||||
*/
|
*/
|
||||||
public void sendCommand(String command) {
|
public void sendCommand(String command) {
|
||||||
sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyMap(), false));
|
sendDownstreamPacket(new ServerboundChatCommandPacket(command, Instant.now().toEpochMilli(), 0L, Collections.emptyList(), false, Collections.emptyList(), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServerRenderDistance(int renderDistance) {
|
public void setServerRenderDistance(int renderDistance) {
|
||||||
@ -1674,6 +1672,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
abilities.add(Ability.INSTABUILD);
|
abilities.add(Ability.INSTABUILD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (commandPermission == CommandPermission.OPERATOR) {
|
||||||
|
// Fixes a bug? since 1.19.11 where the player can change their gamemode in Bedrock settings and
|
||||||
|
// a packet is not sent to the server.
|
||||||
|
// https://github.com/GeyserMC/Geyser/issues/3191
|
||||||
|
abilities.add(Ability.OPERATOR_COMMANDS);
|
||||||
|
}
|
||||||
|
|
||||||
if (flying || spectator) {
|
if (flying || spectator) {
|
||||||
if (spectator && !flying) {
|
if (spectator && !flying) {
|
||||||
// We're "flying locked" in this gamemode
|
// We're "flying locked" in this gamemode
|
||||||
@ -1690,7 +1695,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
|
|
||||||
abilityLayer.setLayerType(AbilityLayer.Type.BASE);
|
abilityLayer.setLayerType(AbilityLayer.Type.BASE);
|
||||||
abilityLayer.setFlySpeed(flySpeed);
|
abilityLayer.setFlySpeed(flySpeed);
|
||||||
abilityLayer.setWalkSpeed(walkSpeed);
|
// https://github.com/GeyserMC/Geyser/issues/3139 as of 1.19.10
|
||||||
|
abilityLayer.setWalkSpeed(walkSpeed == 0f ? 0.01f : walkSpeed);
|
||||||
Collections.addAll(abilityLayer.getAbilitiesSet(), USED_ABILITIES);
|
Collections.addAll(abilityLayer.getAbilitiesSet(), USED_ABILITIES);
|
||||||
|
|
||||||
updateAbilitiesPacket.getAbilityLayers().add(abilityLayer);
|
updateAbilitiesPacket.getAbilityLayers().add(abilityLayer);
|
||||||
|
@ -34,9 +34,7 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
public record ChatTypeEntry(@Nonnull TextPacket.Type bedrockChatType, @Nullable TextDecoration textDecoration) {
|
public record ChatTypeEntry(@Nonnull TextPacket.Type bedrockChatType, @Nullable TextDecoration textDecoration) {
|
||||||
private static final ChatTypeEntry CHAT = new ChatTypeEntry(TextPacket.Type.CHAT, null);
|
private static final ChatTypeEntry CHAT = new ChatTypeEntry(TextPacket.Type.CHAT, null);
|
||||||
private static final ChatTypeEntry SYSTEM = new ChatTypeEntry(TextPacket.Type.CHAT, null);
|
private static final ChatTypeEntry RAW = new ChatTypeEntry(TextPacket.Type.RAW, null);
|
||||||
private static final ChatTypeEntry TIP = new ChatTypeEntry(TextPacket.Type.CHAT, null);
|
|
||||||
private static final ChatTypeEntry RAW = new ChatTypeEntry(TextPacket.Type.CHAT, null);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply defaults to a map so it isn't empty in the event a chat message is sent before the login packet.
|
* Apply defaults to a map so it isn't empty in the event a chat message is sent before the login packet.
|
||||||
@ -46,12 +44,11 @@ public record ChatTypeEntry(@Nonnull TextPacket.Type bedrockChatType, @Nullable
|
|||||||
// But, the only way this happens is if a chat message is sent to us before the login packet, which is rare.
|
// But, the only way this happens is if a chat message is sent to us before the login packet, which is rare.
|
||||||
// So we'll just make sure chat ends up in the right place.
|
// So we'll just make sure chat ends up in the right place.
|
||||||
chatTypes.put(BuiltinChatType.CHAT.ordinal(), CHAT);
|
chatTypes.put(BuiltinChatType.CHAT.ordinal(), CHAT);
|
||||||
chatTypes.put(BuiltinChatType.SYSTEM.ordinal(), SYSTEM);
|
|
||||||
chatTypes.put(BuiltinChatType.GAME_INFO.ordinal(), TIP);
|
|
||||||
chatTypes.put(BuiltinChatType.SAY_COMMAND.ordinal(), RAW);
|
chatTypes.put(BuiltinChatType.SAY_COMMAND.ordinal(), RAW);
|
||||||
chatTypes.put(BuiltinChatType.MSG_COMMAND.ordinal(), RAW);
|
chatTypes.put(BuiltinChatType.MSG_COMMAND_INCOMING.ordinal(), RAW);
|
||||||
chatTypes.put(BuiltinChatType.TEAM_MSG_COMMAND.ordinal(), RAW);
|
chatTypes.put(BuiltinChatType.MSG_COMMAND_OUTGOING.ordinal(), RAW);
|
||||||
|
chatTypes.put(BuiltinChatType.TEAM_MSG_COMMAND_INCOMING.ordinal(), RAW);
|
||||||
|
chatTypes.put(BuiltinChatType.TEAM_MSG_COMMAND_OUTGOING.ordinal(), RAW);
|
||||||
chatTypes.put(BuiltinChatType.EMOTE_COMMAND.ordinal(), RAW);
|
chatTypes.put(BuiltinChatType.EMOTE_COMMAND.ordinal(), RAW);
|
||||||
chatTypes.put(BuiltinChatType.TELLRAW_COMMAND.ordinal(), RAW);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ public final class TextDecoration {
|
|||||||
|
|
||||||
CompoundTag styleTag = tag.get("style");
|
CompoundTag styleTag = tag.get("style");
|
||||||
Style.Builder builder = Style.style();
|
Style.Builder builder = Style.style();
|
||||||
|
if (styleTag != null) {
|
||||||
StringTag color = styleTag.get("color");
|
StringTag color = styleTag.get("color");
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
builder.color(NamedTextColor.NAMES.value(color.getValue()));
|
builder.color(NamedTextColor.NAMES.value(color.getValue()));
|
||||||
@ -55,6 +56,7 @@ public final class TextDecoration {
|
|||||||
if (italic != null && ((Number) italic.getValue()).byteValue() == (byte) 1) {
|
if (italic != null && ((Number) italic.getValue()).byteValue() == (byte) 1) {
|
||||||
builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC);
|
builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
style = builder.build();
|
style = builder.build();
|
||||||
|
|
||||||
this.parameters = EnumSet.noneOf(Parameter.class);
|
this.parameters = EnumSet.noneOf(Parameter.class);
|
||||||
@ -88,6 +90,6 @@ public final class TextDecoration {
|
|||||||
public enum Parameter {
|
public enum Parameter {
|
||||||
CONTENT,
|
CONTENT,
|
||||||
SENDER,
|
SENDER,
|
||||||
TEAM_NAME
|
TARGET
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
CommandNode node = nodes[nodeIndex];
|
CommandNode node = nodes[nodeIndex];
|
||||||
|
|
||||||
// Make sure we don't have duplicated commands (happens if there is more than 1 root node)
|
// Make sure we don't have duplicated commands (happens if there is more than 1 root node)
|
||||||
if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) continue;
|
if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase(Locale.ROOT))) continue;
|
||||||
|
|
||||||
// Get and update the commandArgs list with the found arguments
|
// Get and update the commandArgs list with the found arguments
|
||||||
if (node.getChildIndices().length >= 1) {
|
if (node.getChildIndices().length >= 1) {
|
||||||
@ -325,7 +325,7 @@ public class JavaCommandsTranslator extends PacketTranslator<ClientboundCommands
|
|||||||
CommandParam type = null;
|
CommandParam type = null;
|
||||||
boolean optional = this.paramNode.isExecutable();
|
boolean optional = this.paramNode.isExecutable();
|
||||||
if (mappedType instanceof String[]) {
|
if (mappedType instanceof String[]) {
|
||||||
enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(), (String[]) mappedType, false);
|
enumData = new CommandEnumData(paramNode.getParser().name().toLowerCase(Locale.ROOT), (String[]) mappedType, false);
|
||||||
} else {
|
} else {
|
||||||
type = (CommandParam) mappedType;
|
type = (CommandParam) mappedType;
|
||||||
// Bedrock throws a fit if an optional message comes after a string or target
|
// Bedrock throws a fit if an optional message comes after a string or target
|
||||||
|
@ -25,25 +25,21 @@
|
|||||||
|
|
||||||
package org.geysermc.geyser.translator.protocol.java;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.BuiltinChatType;
|
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundLoginPacket;
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.ServerboundCustomPayloadPacket;
|
||||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
|
||||||
import com.nukkitx.protocol.bedrock.data.GameRuleData;
|
import com.nukkitx.protocol.bedrock.data.GameRuleData;
|
||||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||||
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
|
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
|
import com.nukkitx.protocol.bedrock.packet.GameRulesChangedPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
import com.nukkitx.protocol.bedrock.packet.SetPlayerGameTypePacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.TextPacket;
|
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
import org.geysermc.floodgate.pluginmessage.PluginMessageChannels;
|
||||||
import org.geysermc.geyser.api.network.AuthType;
|
import org.geysermc.geyser.api.network.AuthType;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
import org.geysermc.geyser.level.JavaDimension;
|
import org.geysermc.geyser.level.JavaDimension;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.ChatTypeEntry;
|
|
||||||
import org.geysermc.geyser.text.TextDecoration;
|
import org.geysermc.geyser.text.TextDecoration;
|
||||||
import org.geysermc.geyser.translator.level.BiomeTranslator;
|
import org.geysermc.geyser.translator.level.BiomeTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
@ -68,7 +64,7 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||||||
|
|
||||||
JavaDimension.load(packet.getRegistry(), dimensions);
|
JavaDimension.load(packet.getRegistry(), dimensions);
|
||||||
|
|
||||||
Int2ObjectMap<ChatTypeEntry> chatTypes = session.getChatTypes();
|
Int2ObjectMap<TextDecoration> chatTypes = session.getChatTypes();
|
||||||
chatTypes.clear();
|
chatTypes.clear();
|
||||||
for (CompoundTag tag : JavaCodecEntry.iterateAsTag(packet.getRegistry().get("minecraft:chat_type"))) {
|
for (CompoundTag tag : JavaCodecEntry.iterateAsTag(packet.getRegistry().get("minecraft:chat_type"))) {
|
||||||
// The ID is NOT ALWAYS THE SAME! ViaVersion as of 1.19 adds two registry entries that do NOT match vanilla.
|
// The ID is NOT ALWAYS THE SAME! ViaVersion as of 1.19 adds two registry entries that do NOT match vanilla.
|
||||||
@ -77,21 +73,9 @@ public class JavaLoginTranslator extends PacketTranslator<ClientboundLoginPacket
|
|||||||
CompoundTag chat = element.get("chat");
|
CompoundTag chat = element.get("chat");
|
||||||
TextDecoration textDecoration = null;
|
TextDecoration textDecoration = null;
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
CompoundTag decorationTag = chat.get("decoration");
|
textDecoration = new TextDecoration(chat);
|
||||||
if (decorationTag != null) {
|
|
||||||
textDecoration = new TextDecoration(decorationTag);
|
|
||||||
}
|
}
|
||||||
}
|
chatTypes.put(id, textDecoration);
|
||||||
BuiltinChatType type = BuiltinChatType.from(((StringTag) tag.get("name")).getValue());
|
|
||||||
// TODO new types?
|
|
||||||
// The built-in type can be null if custom plugins/mods add in new types
|
|
||||||
TextPacket.Type bedrockType = type != null ? switch (type) {
|
|
||||||
case CHAT -> TextPacket.Type.CHAT;
|
|
||||||
case SYSTEM -> TextPacket.Type.SYSTEM;
|
|
||||||
case GAME_INFO -> TextPacket.Type.TIP;
|
|
||||||
default -> TextPacket.Type.RAW;
|
|
||||||
} : TextPacket.Type.RAW;
|
|
||||||
chatTypes.put(id, new ChatTypeEntry(bedrockType, textDecoration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the player is already initialized and a join game packet is sent, they
|
// If the player is already initialized and a join game packet is sent, they
|
||||||
|
@ -30,7 +30,6 @@ import com.nukkitx.protocol.bedrock.packet.TextPacket;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.TranslatableComponent;
|
import net.kyori.adventure.text.TranslatableComponent;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.ChatTypeEntry;
|
|
||||||
import org.geysermc.geyser.text.TextDecoration;
|
import org.geysermc.geyser.text.TextDecoration;
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
@ -45,18 +44,16 @@ public class JavaPlayerChatTranslator extends PacketTranslator<ClientboundPlayer
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundPlayerChatPacket packet) {
|
public void translate(GeyserSession session, ClientboundPlayerChatPacket packet) {
|
||||||
ChatTypeEntry entry = session.getChatTypes().get(packet.getTypeId());
|
|
||||||
|
|
||||||
TextPacket textPacket = new TextPacket();
|
TextPacket textPacket = new TextPacket();
|
||||||
textPacket.setPlatformChatId("");
|
textPacket.setPlatformChatId("");
|
||||||
textPacket.setSourceName("");
|
textPacket.setSourceName("");
|
||||||
textPacket.setXuid(session.getAuthData().xuid());
|
textPacket.setXuid(session.getAuthData().xuid());
|
||||||
textPacket.setType(entry.bedrockChatType());
|
textPacket.setType(TextPacket.Type.CHAT);
|
||||||
|
|
||||||
textPacket.setNeedsTranslation(false);
|
textPacket.setNeedsTranslation(false);
|
||||||
Component message = packet.getUnsignedContent() == null ? packet.getSignedContent() : packet.getUnsignedContent();
|
Component message = packet.getUnsignedContent() == null ? packet.getMessageDecorated() : packet.getUnsignedContent();
|
||||||
|
|
||||||
TextDecoration decoration = entry.textDecoration();
|
TextDecoration decoration = session.getChatTypes().get(packet.getChatType());
|
||||||
if (decoration != null) {
|
if (decoration != null) {
|
||||||
// As of 1.19 - do this to apply all the styling for signed messages
|
// As of 1.19 - do this to apply all the styling for signed messages
|
||||||
// Though, Bedrock cannot care about the signed stuff.
|
// Though, Bedrock cannot care about the signed stuff.
|
||||||
@ -65,11 +62,11 @@ public class JavaPlayerChatTranslator extends PacketTranslator<ClientboundPlayer
|
|||||||
.style(decoration.style());
|
.style(decoration.style());
|
||||||
Set<TextDecoration.Parameter> parameters = decoration.parameters();
|
Set<TextDecoration.Parameter> parameters = decoration.parameters();
|
||||||
List<Component> args = new ArrayList<>(3);
|
List<Component> args = new ArrayList<>(3);
|
||||||
if (parameters.contains(TextDecoration.Parameter.TEAM_NAME)) {
|
if (parameters.contains(TextDecoration.Parameter.TARGET)) {
|
||||||
args.add(packet.getSenderTeamName());
|
args.add(packet.getTargetName());
|
||||||
}
|
}
|
||||||
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
|
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
|
||||||
args.add(packet.getSenderName());
|
args.add(packet.getName());
|
||||||
}
|
}
|
||||||
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
|
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
|
||||||
args.add(message);
|
args.add(message);
|
||||||
|
@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java;
|
|||||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket;
|
||||||
import com.nukkitx.protocol.bedrock.packet.TextPacket;
|
import com.nukkitx.protocol.bedrock.packet.TextPacket;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.ChatTypeEntry;
|
|
||||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||||
import org.geysermc.geyser.translator.protocol.Translator;
|
import org.geysermc.geyser.translator.protocol.Translator;
|
||||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
@ -38,15 +37,11 @@ public class JavaSystemChatTranslator extends PacketTranslator<ClientboundSystem
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(GeyserSession session, ClientboundSystemChatPacket packet) {
|
public void translate(GeyserSession session, ClientboundSystemChatPacket packet) {
|
||||||
ChatTypeEntry chatTypeEntry = session.getChatTypes().get(packet.getTypeId());
|
|
||||||
// This probably isn't proper but system chat won't care about the registry in 1.19.1 anyway
|
|
||||||
TextPacket.Type chatType = chatTypeEntry == null ? TextPacket.Type.RAW : chatTypeEntry.bedrockChatType();
|
|
||||||
|
|
||||||
TextPacket textPacket = new TextPacket();
|
TextPacket textPacket = new TextPacket();
|
||||||
textPacket.setPlatformChatId("");
|
textPacket.setPlatformChatId("");
|
||||||
textPacket.setSourceName("");
|
textPacket.setSourceName("");
|
||||||
textPacket.setXuid(session.getAuthData().xuid());
|
textPacket.setXuid(session.getAuthData().xuid());
|
||||||
textPacket.setType(chatType);
|
textPacket.setType(packet.isOverlay() ? TextPacket.Type.TIP : TextPacket.Type.SYSTEM);
|
||||||
|
|
||||||
textPacket.setNeedsTranslation(false);
|
textPacket.setNeedsTranslation(false);
|
||||||
textPacket.setMessage(MessageTranslator.convertMessage(packet.getContent(), session.locale()));
|
textPacket.setMessage(MessageTranslator.convertMessage(packet.getContent(), session.locale()));
|
||||||
|
@ -104,7 +104,9 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
|
|||||||
recipe.put("sell", getItemTag(session, trade.getOutput()));
|
recipe.put("sell", getItemTag(session, trade.getOutput()));
|
||||||
|
|
||||||
// The buy count before demand and special price adjustments
|
// The buy count before demand and special price adjustments
|
||||||
recipe.putInt("buyCountA", Math.max(trade.getFirstInput().getAmount(), 0));
|
// The first input CAN be null as of Java 1.19.0/Bedrock 1.19.10
|
||||||
|
// Replicable item: https://gist.github.com/Camotoy/3f3f23d1f80981d1b4472bdb23bba698 from https://github.com/GeyserMC/Geyser/issues/3171
|
||||||
|
recipe.putInt("buyCountA", trade.getFirstInput() != null ? Math.max(trade.getFirstInput().getAmount(), 0) : 0);
|
||||||
recipe.putInt("buyCountB", trade.getSecondInput() != null ? Math.max(trade.getSecondInput().getAmount(), 0) : 0);
|
recipe.putInt("buyCountB", trade.getSecondInput() != null ? Math.max(trade.getSecondInput().getAmount(), 0) : 0);
|
||||||
|
|
||||||
recipe.putInt("demand", trade.getDemand()); // Seems to have no effect
|
recipe.putInt("demand", trade.getDemand()); // Seems to have no effect
|
||||||
|
@ -190,11 +190,8 @@ public final class EntityUtils {
|
|||||||
if (passenger.getDefinition().entityType() == EntityType.FALLING_BLOCK) {
|
if (passenger.getDefinition().entityType() == EntityType.FALLING_BLOCK) {
|
||||||
yOffset += 0.5f;
|
yOffset += 0.5f;
|
||||||
}
|
}
|
||||||
if (mount.getDefinition().entityType() == EntityType.ARMOR_STAND) {
|
if (mount instanceof ArmorStandEntity armorStand) {
|
||||||
ArmorStandEntity armorStand = (ArmorStandEntity) mount;
|
yOffset -= armorStand.getYOffset();
|
||||||
if (armorStand.isPositionRequiresOffset()) {
|
|
||||||
yOffset -= EntityDefinitions.ARMOR_STAND.height() * (armorStand.isSmall() ? 0.55d : 1d);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset);
|
Vector3f offset = Vector3f.from(xOffset, yOffset, zOffset);
|
||||||
passenger.setRiderSeatPosition(offset);
|
passenger.setRiderSeatPosition(offset);
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
* @link https://github.com/GeyserMC/Geyser
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.geysermc.geyser.platform.standalone;
|
package org.geysermc.geyser.util;
|
||||||
|
|
||||||
|
import org.geysermc.geyser.GeyserLogger;
|
||||||
import org.geysermc.geyser.text.ChatColor;
|
import org.geysermc.geyser.text.ChatColor;
|
||||||
import org.geysermc.geyser.text.GeyserLocale;
|
import org.geysermc.geyser.text.GeyserLocale;
|
||||||
|
|
||||||
@ -32,32 +33,47 @@ import java.io.InputStream;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
public class LoopbackUtil {
|
public final class LoopbackUtil {
|
||||||
private static final String checkExemption = "powershell -Command \"CheckNetIsolation LoopbackExempt -s\""; // Java's Exec feature runs as CMD, NetIsolation is only accessible from PowerShell.
|
private static final String checkExemption = "CheckNetIsolation LoopbackExempt -s";
|
||||||
private static final String loopbackCommand = "powershell -Command \"CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'\"";
|
private static final String loopbackCommand = "CheckNetIsolation LoopbackExempt -a -n='Microsoft.MinecraftUWP_8wekyb3d8bbwe'";
|
||||||
|
/**
|
||||||
|
* This string needs to be checked in the event Minecraft is not installed - no Minecraft string will be present in the checkExemption command.
|
||||||
|
*/
|
||||||
|
private static final String minecraftApplication = "S-1-15-2-1958404141-86561845-1752920682-3514627264-368642714-62675701-733520436";
|
||||||
private static final String startScript = "powershell -Command \"Start-Process 'cmd' -ArgumentList /c,%temp%/loopback_minecraft.bat -Verb runAs\"";
|
private static final String startScript = "powershell -Command \"Start-Process 'cmd' -ArgumentList /c,%temp%/loopback_minecraft.bat -Verb runAs\"";
|
||||||
|
|
||||||
public static void checkLoopback(GeyserStandaloneLogger geyserLogger) {
|
/**
|
||||||
if (System.getProperty("os.name").equalsIgnoreCase("Windows 10")) {
|
* @return true if loopback is not addressed properly.
|
||||||
|
*/
|
||||||
|
public static boolean needsLoopback(GeyserLogger logger) {
|
||||||
|
String os = System.getProperty("os.name");
|
||||||
|
if (os.equalsIgnoreCase("Windows 10") || os.equalsIgnoreCase("Windows 11")) {
|
||||||
try {
|
try {
|
||||||
Process process = Runtime.getRuntime().exec(checkExemption);
|
Process process = Runtime.getRuntime().exec(checkExemption);
|
||||||
|
process.waitFor();
|
||||||
InputStream is = process.getInputStream();
|
InputStream is = process.getInputStream();
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
while (process.isAlive()) {
|
StringBuilder sb = new StringBuilder();
|
||||||
if (is.available() != 0) {
|
while (is.available() != 0) {
|
||||||
sb.append((char) is.read());
|
sb.append((char) is.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return !sb.toString().contains(minecraftApplication);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Couldn't detect if loopback has been added on Windows!", e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String result = sb.toString();
|
public static void checkAndApplyLoopback(GeyserLogger geyserLogger) {
|
||||||
|
if (needsLoopback(geyserLogger)) {
|
||||||
if (!result.contains("minecraftuwp")) {
|
try {
|
||||||
Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes());
|
Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes());
|
||||||
Runtime.getRuntime().exec(startScript);
|
Runtime.getRuntime().exec(startScript);
|
||||||
|
|
||||||
geyserLogger.info(ChatColor.AQUA + GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.added"));
|
geyserLogger.info(ChatColor.AQUA + GeyserLocale.getLocaleStringLog("geyser.bootstrap.loopback.added"));
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
@ -66,4 +82,6 @@ public class LoopbackUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LoopbackUtil() {
|
||||||
|
}
|
||||||
}
|
}
|
@ -28,6 +28,9 @@ package org.geysermc.geyser.util;
|
|||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.naming.directory.Attribute;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -170,4 +173,23 @@ public class WebUtils {
|
|||||||
|
|
||||||
return connectionToString(con);
|
return connectionToString(con);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static String[] findSrvRecord(GeyserImpl geyser, String remoteAddress) {
|
||||||
|
try {
|
||||||
|
// Searches for a server address and a port from a SRV record of the specified host name
|
||||||
|
InitialDirContext ctx = new InitialDirContext();
|
||||||
|
Attribute attr = ctx.getAttributes("dns:///_minecraft._tcp." + remoteAddress, new String[]{"SRV"}).get("SRV");
|
||||||
|
// size > 0 = SRV entry found
|
||||||
|
if (attr != null && attr.size() > 0) {
|
||||||
|
return ((String) attr.get(0)).split(" ");
|
||||||
|
}
|
||||||
|
} catch (Exception | NoClassDefFoundError ex) { // Check for a NoClassDefFoundError to prevent Android crashes
|
||||||
|
if (geyser.getConfig().isDebugMode()) {
|
||||||
|
geyser.getLogger().debug("Exception while trying to find an SRV record for the remote host.");
|
||||||
|
ex.printStackTrace(); // Otherwise we can get a stack trace for any domain that doesn't have an SRV record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5440
core/src/main/resources/bedrock/creative_items.1_19_10.json
Normale Datei
5440
core/src/main/resources/bedrock/creative_items.1_19_10.json
Normale Datei
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
Binäre Datei nicht angezeigt.
4530
core/src/main/resources/bedrock/runtime_item_states.1_19_10.json
Normale Datei
4530
core/src/main/resources/bedrock/runtime_item_states.1_19_10.json
Normale Datei
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -71,7 +71,7 @@ floodgate-key-file: key.pem
|
|||||||
# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords,
|
# This saves a token that can be reused to authenticate the player later. This does not save emails or passwords,
|
||||||
# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files.
|
# but you should still be cautious when adding to this list and giving others access to this Geyser instance's files.
|
||||||
# Removing a name from this list will delete its cached login information on the next Geyser startup.
|
# Removing a name from this list will delete its cached login information on the next Geyser startup.
|
||||||
# The file for this is in the same folder as this config, named "saved-refresh-tokens.json".
|
# The file that tokens will be saved in is in the same folder as this config, named "saved-refresh-tokens.json".
|
||||||
saved-user-logins:
|
saved-user-logins:
|
||||||
- ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername
|
- ThisExampleUsernameShouldBeLongEnoughToNeverBeAnXboxUsername
|
||||||
- ThisOtherExampleUsernameShouldAlsoBeLongEnough
|
- ThisOtherExampleUsernameShouldAlsoBeLongEnough
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 919908f4825e9fa1bb7b5a2f5e09218f0a3f72f3
|
Subproject commit 0127891232742209b8470298dfd997249c506320
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren