Merge remote-tracking branch 'origin/master' into feature/cumulus-1.1

# Conflicts:
#	ap/pom.xml
#	api/base/pom.xml
#	api/geyser/pom.xml
#	api/pom.xml
#	bootstrap/bungeecord/pom.xml
#	bootstrap/pom.xml
#	bootstrap/spigot/pom.xml
#	bootstrap/sponge/pom.xml
#	bootstrap/standalone/pom.xml
#	bootstrap/velocity/pom.xml
#	common/pom.xml
#	core/pom.xml
#	core/src/main/java/org/geysermc/geyser/session/cache/AdvancementsCache.java
#	core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCustomPayloadTranslator.java
#	core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
#	pom.xml
This commit is contained in:
Tim203 2022-05-28 17:09:20 +02:00
commit d4ecd2bd72
No known key found for this signature in database
GPG Key ID: 064EE9F5BF7C3EE8
246 changed files with 7897 additions and 4674 deletions

View File

@ -55,9 +55,9 @@ body:
required: true
- type: input
attributes:
label: "Minecraft: Bedrock Edition Version"
description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition."
placeholder: "For example: 1.16.201"
label: "Minecraft: Bedrock Edition Device/Version"
description: "What version of Minecraft: Bedrock Edition are you using, and what device(s) does the bug occur on? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition."
placeholder: "For example: 1.16.201, Nintendo Switch"
- type: textarea
attributes:
label: Additional Context

36
.github/workflows/sonarcloud.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: SonarCloud
on:
push:
branches:
- master
jobs:
build:
name: SonarCloud
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
submodules: true
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: 17
- name: Cache SonarCloud packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Maven packages
uses: actions/cache@v1
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=GeyserMC_Geyser

6
.gitignore vendored
View File

@ -239,8 +239,10 @@ nbdist/
run/
config.yml
logs/
public-key.pem
key.pem
locales/
/cache/
/packs/
/dump.json
/dump.json
/saved-refresh-tokens.json
/languages/

View File

@ -17,16 +17,16 @@ 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!
### Currently supporting Minecraft Bedrock 1.17.30 - 1.17.41 + 1.18.0 - 1.18.2 and Minecraft Java 1.18/1.18.1.
### Currently supporting Minecraft Bedrock 1.18.0 - 1.18.31 and Minecraft Java 1.18.2.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set up Geyser.
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
[![YouTube Video](https://img.youtube.com/vi/U7dZZ8w7Gi4/0.jpg)](https://www.youtube.com/watch?v=U7dZZ8w7Gi4)
## Links:
- Website: https://geysermc.org
- Docs: https://github.com/GeyserMC/Geyser/wiki
- Docs: https://wiki.geysermc.org/geyser/
- Download: https://ci.geysermc.org
- Discord: https://discord.gg/geysermc
- Donate: https://opencollective.com/geysermc
@ -39,7 +39,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki/Setup) for how to set
- Structure block UI
## What can't be fixed
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://github.com/GeyserMC/Geyser/wiki/Current-Limitations) page.
There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://wiki.geysermc.org/geyser/current-limitations/) page.
## Compiling
1. Clone the repo to your computer

View File

@ -6,9 +6,9 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>ap</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</project>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>api-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -26,6 +26,7 @@
package org.geysermc.api.session;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.common.value.qual.IntRange;
import java.util.UUID;
@ -55,5 +56,13 @@ public interface Connection {
*/
String xuid();
/**
* Transfer the connection to a server. A Bedrock player can successfully transfer to the same server they are
* currently playing on.
*
* @param address The address of the server
* @param port The port of the server
* @return true if the transfer was a success
*/
boolean transfer(@NonNull String address, @IntRange(from = 0, to = 65535) int port);
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>api-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -26,7 +26,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>base-api</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>api-parent</artifactId>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-bungeecord</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- Used for better working with internals without reflection -->
@ -49,7 +49,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-parent</artifactId>
<packaging>pom</packaging>
@ -34,7 +34,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>ap</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-spigot</artifactId>
@ -19,13 +19,18 @@
<id>viaversion-repo</id>
<url>https://repo.viaversion.com</url>
</repository>
<repository>
<!-- For Commodore -->
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -34,6 +39,12 @@
<version>1.18.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-mojangapi</artifactId>
<version>1.18.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.viaversion</groupId>
<artifactId>viaversion</artifactId>
@ -43,7 +54,13 @@
<dependency>
<groupId>org.geysermc.geyser.adapters</groupId>
<artifactId>spigot-all</artifactId>
<version>1.3-SNAPSHOT</version>
<version>1.4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.13</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@ -70,7 +87,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@ -95,6 +112,10 @@
<pattern>org.objectweb.asm</pattern>
<shadedPattern>org.geysermc.geyser.platform.spigot.shaded.asm</shadedPattern>
</relocation>
<relocation>
<pattern>me.lucko.commodore</pattern>
<shadedPattern>org.geysermc.geyser.platform.spigot.shaded.commodore</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
@ -118,6 +139,7 @@
<exclude>io.netty:netty-codec-dns:*</exclude>
<exclude>io.netty:netty-resolver-dns:*</exclude>
<exclude>io.netty:netty-resolver-dns-native-macos:*</exclude>
<exclude>com.mojang:*</exclude> <!-- Commodore includes Brigadier -->
</excludes>
</artifactSet>
</configuration>

View File

@ -29,40 +29,51 @@ import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.common.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.command.CommandManager;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.adapters.spigot.SpigotAdapters;
import org.geysermc.geyser.platform.spigot.command.GeyserBrigadierSupport;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandExecutor;
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSender;
import org.geysermc.geyser.platform.spigot.world.GeyserPistonListener;
import org.geysermc.geyser.platform.spigot.world.GeyserSpigot1_11CraftingListener;
import org.geysermc.geyser.platform.spigot.world.GeyserSpigotBlockPlaceListener;
import org.geysermc.geyser.platform.spigot.world.manager.*;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
/**
* Determines if the plugin has been ran once before, including before /geyser reload.
*/
private static boolean INITIALIZED = false;
private GeyserSpigotCommandManager geyserCommandManager;
private GeyserSpigotConfiguration geyserConfig;
private GeyserSpigotInjector geyserInjector;
@ -230,20 +241,42 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
}
geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass());
}
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
PluginCommand pluginCommand = this.getCommand("geyser");
pluginCommand.setExecutor(new GeyserSpigotCommandExecutor(geyser));
if (isPre1_12) {
// Register events needed to send all recipes to the client
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(geyser), this);
if (!INITIALIZED) {
// Register permissions so they appear in, for example, LuckPerms' UI
// Re-registering permissions throws an error
for (Map.Entry<String, GeyserCommand> entry : geyserCommandManager.getCommands().entrySet()) {
GeyserCommand command = entry.getValue();
if (command.getAliases().contains(entry.getKey())) {
// Don't register aliases
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.getPermission(),
GeyserLocale.getLocaleStringLog(command.getDescription()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));
}
// Events cannot be unregistered - re-registering results in duplicate firings
GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(geyser, this.geyserWorldManager);
Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this);
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
}
this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser));
boolean brigadierSupported = CommodoreProvider.isSupported();
geyserLogger.debug("Brigadier supported? " + brigadierSupported);
if (brigadierSupported) {
GeyserBrigadierSupport.loadBrigadier(this, pluginCommand);
}
// Check to ensure the current setup can support the protocol version Geyser uses
GeyserSpigotVersionChecker.checkForSupportedProtocol(geyserLogger, isViaVersion);
INITIALIZED = true;
}
@Override

View File

@ -0,0 +1,61 @@
/*
* 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.platform.spigot.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.geysermc.geyser.platform.spigot.GeyserSpigotPlugin;
/**
* Needs to be a separate class so pre-1.13 loads correctly.
*/
public final class GeyserBrigadierSupport {
public static void loadBrigadier(GeyserSpigotPlugin plugin, PluginCommand pluginCommand) {
// Enable command completions if supported
// This is beneficial because this is sent over the network and Bedrock can see it
Commodore commodore = CommodoreProvider.getCommodore(plugin);
LiteralArgumentBuilder<?> builder = LiteralArgumentBuilder.literal("geyser");
for (String command : plugin.getGeyserCommandManager().getCommands().keySet()) {
builder.then(LiteralArgumentBuilder.literal(command));
}
commodore.register(pluginCommand, builder);
try {
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPaperCommandListener(), plugin);
plugin.getGeyserLogger().debug("Successfully registered AsyncPlayerSendCommandsEvent listener.");
} catch (ClassNotFoundException e) {
plugin.getGeyserLogger().debug("Not registering AsyncPlayerSendCommandsEvent listener.");
}
}
private GeyserBrigadierSupport() {
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.platform.spigot.command;
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
import com.mojang.brigadier.tree.CommandNode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Map;
public final class GeyserPaperCommandListener implements Listener {
@EventHandler
@SuppressWarnings("deprecation") // Used to indicate an unstable event
public void onCommandSend(AsyncPlayerSendCommandsEvent<?> event) {
// Documentation says to check (event.isAsynchronous() || !event.hasFiredAsync()), but as of Paper 1.18.2
// event.hasFiredAsync is never true
if (event.isAsynchronous()) {
CommandNode<?> geyserBrigadier = event.getCommandNode().getChild("geyser");
if (geyserBrigadier != null) {
Player player = event.getPlayer();
boolean isJavaPlayer = isProbablyJavaPlayer(player);
Map<String, GeyserCommand> commands = GeyserImpl.getInstance().getCommandManager().getCommands();
Iterator<? extends CommandNode<?>> it = geyserBrigadier.getChildren().iterator();
while (it.hasNext()) {
CommandNode<?> subnode = it.next();
GeyserCommand command = commands.get(subnode.getName());
if (command != null) {
if ((command.isBedrockOnly() && isJavaPlayer) || !player.hasPermission(command.getPermission())) {
// Remove this from the node as we don't have permission to use it
it.remove();
}
}
}
}
}
}
/**
* This early on, there is a rare chance that Geyser has yet to process the connection. We'll try to minimize that
* chance, though.
*/
private boolean isProbablyJavaPlayer(Player player) {
if (GeyserImpl.getInstance().connectionByUuid(player.getUniqueId()) != null) {
// For sure this is a Bedrock player
return false;
}
if (GeyserImpl.getInstance().getConfig().isUseDirectConnection()) {
InetSocketAddress address = player.getAddress();
if (address != null) {
return address.getPort() != 0;
}
}
return true;
}
}

View File

@ -26,6 +26,7 @@
package org.geysermc.geyser.platform.spigot.command;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.geysermc.geyser.GeyserImpl;
@ -35,16 +36,24 @@ import java.lang.reflect.Field;
public class GeyserSpigotCommandManager extends CommandManager {
private static CommandMap COMMAND_MAP;
private static final CommandMap COMMAND_MAP;
static {
CommandMap commandMap = null;
try {
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
cmdMapField.setAccessible(true);
COMMAND_MAP = (CommandMap) cmdMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
// Paper-only
Server.class.getMethod("getCommandMap");
commandMap = Bukkit.getServer().getCommandMap();
} catch (NoSuchMethodException e) {
try {
Field cmdMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
cmdMapField.setAccessible(true);
commandMap = (CommandMap) cmdMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
}
COMMAND_MAP = commandMap;
}
public GeyserSpigotCommandManager(GeyserImpl geyser) {

View File

@ -1,203 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.spigot.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.data.MappingData;
import com.viaversion.viaversion.api.protocol.ProtocolPathEntry;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2;
import com.viaversion.viaversion.util.Pair;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.MinecraftProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.*;
/**
* Used to send all available recipes from the server to the client, as a valid recipe book packet won't be sent by the server.
* Requires ViaVersion.
*/
public class GeyserSpigot1_11CraftingListener implements Listener {
private final GeyserImpl geyser;
/**
* Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 item into 1.13.
*/
private final MappingData mappingData1_12to1_13;
/**
* The list of all protocols from the client's version to 1.13.
*/
private final List<ProtocolPathEntry> protocolList;
public GeyserSpigot1_11CraftingListener(GeyserImpl geyser) {
this.geyser = geyser;
this.mappingData1_12to1_13 = Via.getManager().getProtocolManager().getProtocol(Protocol1_13To1_12_2.class).getMappingData();
this.protocolList = Via.getManager().getProtocolManager().getProtocolPath(MinecraftProtocol.getJavaProtocolVersion(),
ProtocolVersion.v1_13.getVersion());
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
GeyserSession session = null;
for (GeyserSession otherSession : geyser.getSessionManager().getSessions().values()) {
if (otherSession.name().equals(event.getPlayer().getName())) {
session = otherSession;
break;
}
}
if (session == null) {
return;
}
sendServerRecipes(session);
}
public void sendServerRecipes(GeyserSession session) {
int netId = InventoryUtils.LAST_RECIPE_NET_ID;
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
craftingDataPacket.setCleanRecipes(true);
Iterator<Recipe> recipeIterator = Bukkit.getServer().recipeIterator();
while (recipeIterator.hasNext()) {
Recipe recipe = recipeIterator.next();
Pair<ItemStack, ItemData> outputs = translateToBedrock(session, recipe.getResult());
ItemStack javaOutput = outputs.getKey();
ItemData output = outputs.getValue();
if (output == null || output.getId() == 0) continue; // If items make air we don't want that
boolean isNotAllAir = false; // Check for all-air recipes
if (recipe instanceof ShapedRecipe shapedRecipe) {
int size = shapedRecipe.getShape().length * shapedRecipe.getShape()[0].length();
Ingredient[] ingredients = new Ingredient[size];
ItemData[] input = new ItemData[size];
for (int i = 0; i < input.length; i++) {
// Index is converting char to integer, adding i then converting back to char based on ASCII code
Pair<ItemStack, ItemData> result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i)));
ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()});
input[i] = result.getValue();
isNotAllAir |= input[i].getId() != 0;
}
if (!isNotAllAir) continue;
UUID uuid = UUID.randomUUID();
// Add recipe to our internal cache
ShapedRecipeData data = new ShapedRecipeData(shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length,
"", ingredients, javaOutput);
session.getCraftingRecipes().put(netId,
new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data));
// Add recipe for Bedrock
craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(),
shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input),
Collections.singletonList(output), uuid, "crafting_table", 0, netId++));
} else if (recipe instanceof ShapelessRecipe shapelessRecipe) {
Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()];
ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()];
for (int i = 0; i < input.length; i++) {
Pair<ItemStack, ItemData> result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i));
ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()});
input[i] = result.getValue();
isNotAllAir |= input[i].getId() != 0;
}
if (!isNotAllAir) continue;
UUID uuid = UUID.randomUUID();
// Add recipe to our internal cache
ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput);
session.getCraftingRecipes().put(netId,
new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data));
// Add recipe for Bedrock
craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++));
}
}
session.sendUpstreamPacket(craftingDataPacket);
}
@SuppressWarnings("deprecation")
private Pair<ItemStack, ItemData> translateToBedrock(GeyserSession session, org.bukkit.inventory.ItemStack itemStack) {
if (itemStack != null && itemStack.getData() != null) {
if (itemStack.getType().getId() == 0) {
return new Pair<>(null, ItemData.AIR);
}
int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF);
if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12
legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF);
}
// old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on
int itemId;
if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) {
itemId = mappingData1_12to1_13.getNewItemId(legacyId);
} else if (mappingData1_12to1_13.getItemMappings().containsKey((itemStack.getType().getId() << 4) | (0))) {
itemId = mappingData1_12to1_13.getNewItemId((itemStack.getType().getId() << 4) | (0));
} else {
// No ID found, just send back air
return new Pair<>(null, ItemData.AIR);
}
for (int i = protocolList.size() - 1; i >= 0; i--) {
MappingData mappingData = protocolList.get(i).getProtocol().getMappingData();
if (mappingData != null) {
itemId = mappingData.getNewItemId(itemId);
}
}
ItemStack mcItemStack = new ItemStack(itemId, itemStack.getAmount());
ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack);
return new Pair<>(mcItemStack, finalData);
}
// Empty slot, most likely
return new Pair<>(null, ItemData.AIR);
}
}

View File

@ -9,34 +9,3 @@ commands:
geyser:
description: The main command for Geyser.
usage: /geyser <subcommand>
permissions:
geyser.command.help:
description: Shows help for all registered commands.
default: true
geyser.command.offhand:
description: Puts an items in your offhand.
default: true
geyser.command.advancements:
description: Shows the advancements of the player on the server.
default: true
geyser.command.tooltips:
description: Toggles showing advanced tooltips on your items.
default: true
geyser.command.statistics:
description: Shows the statistics of the player on the server.
default: true
geyser.command.settings:
description: Modify user settings
default: true
geyser.command.list:
description: List all players connected through Geyser.
default: op
geyser.command.dump:
description: Dumps Geyser debug information for bug reports.
default: op
geyser.command.reload:
description: Reloads the Geyser configurations. Kicks all players when used!
default: false
geyser.command.version:
description: Shows the current Geyser version and checks for updates.
default: op

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-sponge</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -48,7 +48,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-standalone</artifactId>
@ -18,7 +18,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -47,17 +47,17 @@
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal</artifactId>
<version>3.20.0</version>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal-jna</artifactId>
<version>3.20.0</version>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-reader</artifactId>
<version>3.20.0</version>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@ -93,7 +93,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0</version>
<dependencies>
<dependency>
<groupId>com.github.edwgiz</groupId>
@ -132,7 +132,6 @@
implementation="com.github.edwgiz.mavenShadePlugin.log4j2CacheTransformer.PluginsCacheFileTransformer">
</transformer>
</transformers>
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
</configuration>
</plugin>
</plugins>

View File

@ -275,6 +275,12 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
return Paths.get(System.getProperty("user.dir"));
}
@Override
public Path getSavedUserLoginsFolder() {
// Return the location of the config
return new File(configFilename).getAbsoluteFile().getParentFile().toPath();
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new GeyserStandaloneDumpInfo(this);

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>bootstrap-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>bootstrap-velocity</artifactId>
@ -14,7 +14,7 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -48,7 +48,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>common</artifactId>
@ -20,12 +20,12 @@
<dependency>
<groupId>org.geysermc.cumulus</groupId>
<artifactId>cumulus</artifactId>
<version>1.1-SNAPSHOT</version>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
<version>2.8.9</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,45 @@
/*
* 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.floodgate.pluginmessage;
import com.google.common.base.Charsets;
public final class PluginMessageChannels {
public static final String SKIN = "floodgate:skin";
public static final String FORM = "floodgate:form";
public static final String TRANSFER = "floodgate:transfer";
private static final byte[] FLOODGATE_REGISTER_DATA = String.join("\0", SKIN, FORM, TRANSFER).getBytes(Charsets.UTF_8);
/**
* Get the prebuilt register data as a byte array
*
* @return the register data of the Floodgate channels
*/
public static byte[] getFloodgateRegisterData() {
return FLOODGATE_REGISTER_DATA;
}
}

View File

@ -6,14 +6,14 @@
<parent>
<groupId>org.geysermc</groupId>
<artifactId>geyser-parent</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
</parent>
<artifactId>core</artifactId>
<properties>
<adventure.version>4.9.3</adventure.version>
<fastutil.version>8.5.2</fastutil.version>
<jackson.version>2.12.4</jackson.version>
<jackson.version>2.13.2</jackson.version>
<netty.version>4.1.66.Final</netty.version>
</properties>
@ -21,19 +21,19 @@
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>ap</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>geyser-api</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>common</artifactId>
<version>2.0.1-cumulus-SNAPSHOT</version>
<version>2.0.3-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- Jackson JSON and YAML serialization -->
@ -52,7 +52,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<version>${jackson.version}.1</version> <!-- Extra .1 as databind is a slightly different version -->
<scope>compile</scope>
</dependency>
<dependency>
@ -120,8 +120,8 @@
</dependency>
<dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<artifactId>bedrock-v475</artifactId>
<version>c22aa595</version>
<artifactId>bedrock-v503</artifactId>
<version>297567d</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -147,23 +147,23 @@
</exclusions>
</dependency>
<dependency>
<groupId>com.github.RednedEpic</groupId>
<groupId>com.github.GeyserMC</groupId>
<artifactId>MCAuthLib</artifactId>
<version>6c99331</version>
<version>d9d773e</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.GeyserMC</groupId>
<artifactId>MCProtocolLib</artifactId>
<version>6a23a780</version>
<version>0771504</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>com.github.steveice10</groupId>
<groupId>com.github.GeyserMC</groupId>
<artifactId>packetlib</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.steveice10</groupId>
<groupId>com.github.GeyserMC</groupId>
<artifactId>mcauthlib</artifactId>
</exclusion>
</exclusions>

View File

@ -100,7 +100,7 @@ public class GeyserSession {
}
public void login() {
this.handle.login();
throw new UnsupportedOperationException();
}
public void authenticate(String username) {
@ -120,7 +120,7 @@ public class GeyserSession {
}
public void close() {
this.handle.close();
throw new UnsupportedOperationException();
}
public void executeInEventLoop(Runnable runnable) {

View File

@ -37,6 +37,8 @@ public final class Constants {
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
static {
URI wsUri = null;
try {

View File

@ -97,6 +97,13 @@ public interface GeyserBootstrap {
*/
Path getConfigFolder();
/**
* @return the folder where user tokens are saved. This should always point to the location of the config.
*/
default Path getSavedUserLoginsFolder() {
return getConfigFolder();
}
/**
* Information used for the bootstrap section of the debug dump
*

View File

@ -26,6 +26,7 @@
package org.geysermc.geyser;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.steveice10.packetlib.tcp.TcpSession;
@ -37,6 +38,7 @@ import io.netty.channel.kqueue.KQueue;
import io.netty.util.NettyRuntime;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.internal.SystemPropertyUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -59,9 +61,11 @@ import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
import org.geysermc.geyser.session.SessionManager;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.skin.FloodgateSkinUploader;
import org.geysermc.geyser.skin.SkinProvider;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.text.MinecraftLocale;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
@ -70,6 +74,9 @@ import org.geysermc.geyser.util.*;
import javax.naming.directory.Attribute;
import javax.naming.directory.InitialDirContext;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@ -77,6 +84,7 @@ import java.net.UnknownHostException;
import java.security.Key;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
@ -124,6 +132,10 @@ public class GeyserImpl implements GeyserApi {
private Metrics metrics;
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication;
@Getter(AccessLevel.NONE)
private Map<String, String> savedRefreshTokens;
private static GeyserImpl instance;
private GeyserImpl(PlatformType platformType, GeyserBootstrap bootstrap) {
@ -195,6 +207,8 @@ public class GeyserImpl implements GeyserApi {
ScoreboardUpdater.init();
SkinProvider.registerCacheImageTask(this);
ResourcePack.loadPacks();
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
@ -265,6 +279,8 @@ public class GeyserImpl implements GeyserApi {
logger.debug("Not getting git properties for the news handler as we are in a development environment.");
}
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout());
this.newsHandler = new NewsHandler(branch, buildNumber);
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
@ -317,7 +333,7 @@ public class GeyserImpl implements GeyserApi {
metrics = new Metrics(this, "GeyserMC", config.getMetrics().getUniqueId(), false, java.util.logging.Logger.getLogger(""));
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size));
// Prevent unwanted words best we can
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase()));
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().getAuthType().toString().toLowerCase(Locale.ROOT)));
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION));
@ -401,6 +417,47 @@ public class GeyserImpl implements GeyserApi {
metrics = null;
}
if (config.getRemote().getAuthType() == AuthType.ONLINE) {
if (config.getUserAuths() != null && !config.getUserAuths().isEmpty()) {
getLogger().warning("The 'userAuths' config section is now deprecated, and will be removed in the near future! " +
"Please migrate to the new 'saved-user-logins' config option: " +
"https://wiki.geysermc.org/geyser/understanding-the-config/");
}
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
savedRefreshTokens = new ConcurrentHashMap<>();
File tokensFile = bootstrap.getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
if (tokensFile.exists()) {
TypeReference<Map<String, String>> type = new TypeReference<>() { };
Map<String, String> refreshTokenFile = null;
try {
refreshTokenFile = JSON_MAPPER.readValue(tokensFile, type);
} catch (IOException e) {
logger.error("Cannot load saved user tokens!", e);
}
if (refreshTokenFile != null) {
List<String> validUsers = config.getSavedUserLogins();
boolean doWrite = false;
for (Map.Entry<String, String> entry : refreshTokenFile.entrySet()) {
String user = entry.getKey();
if (!validUsers.contains(user)) {
// Perform a write to this file to purge the now-unused name
doWrite = true;
continue;
}
savedRefreshTokens.put(user, entry.getValue());
}
if (doWrite) {
scheduleRefreshTokensWrite();
}
}
}
} else {
savedRefreshTokens = null;
}
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);
}
@ -508,6 +565,39 @@ public class GeyserImpl implements GeyserApi {
return bootstrap.getWorldManager();
}
@Nullable
public String refreshTokenFor(@NonNull String bedrockName) {
return savedRefreshTokens.get(bedrockName);
}
public void saveRefreshToken(@NonNull String bedrockName, @NonNull String refreshToken) {
if (!getConfig().getSavedUserLogins().contains(bedrockName)) {
// Do not save this login
return;
}
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
// refreshes the token for us
if (!Objects.equals(refreshToken, savedRefreshTokens.put(bedrockName, refreshToken))) {
scheduleRefreshTokensWrite();
}
}
private void scheduleRefreshTokensWrite() {
scheduledThread.execute(() -> {
// Ensure all writes are handled on the same thread
File savedTokens = getBootstrap().getSavedUserLoginsFolder().resolve(Constants.SAVED_REFRESH_TOKEN_FILE).toFile();
TypeReference<Map<String, String>> type = new TypeReference<>() { };
try (FileWriter writer = new FileWriter(savedTokens)) {
JSON_MAPPER.writerFor(type)
.withDefaultPrettyPrinter()
.writeValue(writer, savedRefreshTokens);
} catch (IOException e) {
getLogger().error("Unable to write saved refresh tokens!", e);
}
});
}
public static GeyserImpl getInstance() {
return instance;
}

View File

@ -25,6 +25,8 @@
package org.geysermc.geyser;
import javax.annotation.Nullable;
public interface GeyserLogger {
/**
@ -78,6 +80,15 @@ public interface GeyserLogger {
*/
void debug(String message);
/**
* Logs an object to console if debug mode is enabled
*
* @param object the object to log
*/
default void debug(@Nullable Object object) {
debug(String.valueOf(object));
}
/**
* Sets if the logger should print debug messages
*

View File

@ -86,4 +86,13 @@ public abstract class GeyserCommand {
public boolean isBedrockOnly() {
return false;
}
/**
* Used for permission defaults on server implementations.
*
* @return if this command is designated to be used only by server operators.
*/
public boolean isSuggestedOpOnly() {
return false;
}
}

View File

@ -145,4 +145,9 @@ public class DumpCommand extends GeyserCommand {
public List<String> getSubCommands() {
return Arrays.asList("offline", "full", "logs");
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -51,4 +51,9 @@ public class ListCommand extends GeyserCommand {
sender.sendMessage(message);
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -54,4 +54,9 @@ public class ReloadCommand extends GeyserCommand {
geyser.getSessionManager().disconnectAll("geyser.commands.reload.kick");
geyser.reload();
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -54,4 +54,9 @@ public class StopCommand extends GeyserCommand {
geyser.getBootstrap().onDisable();
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -100,4 +100,9 @@ public class VersionCommand extends GeyserCommand {
}
}
}
@Override
public boolean isSuggestedOpOnly() {
return true;
}
}

View File

@ -44,6 +44,9 @@ public interface GeyserConfiguration {
IRemoteConfiguration getRemote();
List<String> getSavedUserLogins();
@Deprecated
Map<String, ? extends IUserAuthenticationInfo> getUserAuths();
boolean isCommandSuggestions();
@ -78,6 +81,8 @@ public interface GeyserConfiguration {
boolean isDisableBedrockScaffolding();
boolean isAlwaysQuickChangeArmor();
EmoteOffhandWorkaroundOption getEmoteOffhandWorkaround();
String getDefaultLocale();
@ -96,8 +101,14 @@ public interface GeyserConfiguration {
boolean isAllowCustomSkulls();
int getMaxVisibleCustomSkulls();
int getCustomSkullRenderDistance();
IMetricsInfo getMetrics();
int getPendingAuthenticationTimeout();
interface IBedrockConfiguration {
String getAddress();

View File

@ -35,9 +35,9 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.session.auth.AuthType;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.network.CIDRMatcher;
import org.geysermc.geyser.text.GeyserLocale;
import java.io.IOException;
@ -62,6 +62,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
private BedrockConfiguration bedrock = new BedrockConfiguration();
private RemoteConfiguration remote = new RemoteConfiguration();
@JsonProperty("saved-user-logins")
private List<String> savedUserLogins = Collections.emptyList();
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile = "key.pem";
@ -108,6 +111,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("disable-bedrock-scaffolding")
private boolean disableBedrockScaffolding = false;
@JsonProperty("always-quick-change-armor")
private boolean alwaysQuickChangeArmor = false;
@JsonDeserialize(using = EmoteOffhandWorkaroundOption.Deserializer.class)
@JsonProperty("emote-offhand-workaround")
private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED;
@ -124,6 +130,12 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("allow-custom-skulls")
private boolean allowCustomSkulls = true;
@JsonProperty("max-visible-custom-skulls")
private int maxVisibleCustomSkulls = 128;
@JsonProperty("custom-skull-render-distance")
private int customSkullRenderDistance = 32;
@JsonProperty("add-non-bedrock-items")
private boolean addNonBedrockItems = true;
@ -138,6 +150,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
private MetricsInfo metrics = new MetricsInfo();
@JsonProperty("pending-authentication-timeout")
private int pendingAuthenticationTimeout = 120;
@Getter
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BedrockConfiguration implements IBedrockConfiguration {
@ -231,8 +246,21 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
public static class MetricsInfo implements IMetricsInfo {
private boolean enabled = true;
@JsonDeserialize(using = MetricsIdDeserializer.class)
@JsonProperty("uuid")
private String uniqueId = UUID.randomUUID().toString();
private static class MetricsIdDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String uuid = p.getValueAsString();
if ("generateduuid".equals(uuid)) {
// Compensate for configs not copied from the jar
return UUID.randomUUID().toString();
}
return uuid;
}
}
}
@JsonProperty("scoreboard-packet-threshold")

View File

@ -54,10 +54,7 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.*;
import java.util.stream.Collectors;
@Getter
@ -67,6 +64,8 @@ public class DumpInfo {
private final DumpInfo.VersionInfo versionInfo;
private final int cpuCount;
private final Locale systemLocale;
private final String systemEncoding;
private Properties gitInfo;
private final GeyserConfiguration config;
private final Floodgate floodgate;
@ -81,6 +80,8 @@ public class DumpInfo {
this.versionInfo = new VersionInfo();
this.cpuCount = Runtime.getRuntime().availableProcessors();
this.systemLocale = Locale.getDefault();
this.systemEncoding = System.getProperty("file.encoding");
try (InputStream stream = GeyserImpl.getInstance().getBootstrap().getResource("git.properties")) {
this.gitInfo = new Properties();

View File

@ -65,9 +65,9 @@ public final class EntityDefinitions {
public static final EntityDefinition<ChickenEntity> CHICKEN;
public static final EntityDefinition<AbstractFishEntity> COD;
public static final EntityDefinition<CommandBlockMinecartEntity> COMMAND_BLOCK_MINECART;
public static final EntityDefinition<AnimalEntity> COW;
public static final EntityDefinition<CowEntity> COW;
public static final EntityDefinition<CreeperEntity> CREEPER;
public static final EntityDefinition<WaterEntity> DOLPHIN;
public static final EntityDefinition<DolphinEntity> DOLPHIN;
public static final EntityDefinition<ChestedHorseEntity> DONKEY;
public static final EntityDefinition<FireballEntity> DRAGON_FIREBALL;
public static final EntityDefinition<ZombieEntity> DROWNED;
@ -132,7 +132,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<ThrowableEntity> SHULKER_BULLET;
public static final EntityDefinition<MonsterEntity> SILVERFISH;
public static final EntityDefinition<SkeletonEntity> SKELETON;
public static final EntityDefinition<AbstractHorseEntity> SKELETON_HORSE;
public static final EntityDefinition<SkeletonHorseEntity> SKELETON_HORSE;
public static final EntityDefinition<SlimeEntity> SLIME;
public static final EntityDefinition<FireballEntity> SMALL_FIREBALL;
public static final EntityDefinition<ThrowableItemEntity> SNOWBALL;
@ -160,7 +160,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<WolfEntity> WOLF;
public static final EntityDefinition<ZoglinEntity> ZOGLIN;
public static final EntityDefinition<ZombieEntity> ZOMBIE;
public static final EntityDefinition<AbstractHorseEntity> ZOMBIE_HORSE;
public static final EntityDefinition<ZombieHorseEntity> ZOMBIE_HORSE;
public static final EntityDefinition<ZombieVillagerEntity> ZOMBIE_VILLAGER;
public static final EntityDefinition<ZombifiedPiglinEntity> ZOMBIFIED_PIGLIN;
@ -459,7 +459,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited)
.build();
DOLPHIN = EntityDefinition.inherited(WaterEntity::new, mobEntityBase)
DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, mobEntityBase)
.type(EntityType.DOLPHIN)
.height(0.6f).width(0.9f)
//TODO check
@ -723,7 +723,7 @@ public final class EntityDefinitions {
.type(EntityType.CHICKEN)
.height(0.7f).width(0.4f)
.build();
COW = EntityDefinition.inherited(AnimalEntity::new, ageableEntityBase)
COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase)
.type(EntityType.COW)
.height(1.4f).width(0.9f)
.build();
@ -745,14 +745,14 @@ public final class EntityDefinitions {
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer)
.build();
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) // TODO remove class
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase)
.type(EntityType.MOOSHROOM)
.height(1.4f).width(0.9f)
.addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0))
.addTranslator(MetadataType.STRING, MooshroomEntity::setVariant)
.build();
OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase)
.type(EntityType.OCELOT)
.height(0.35f).width(0.3f)
.height(0.7f).width(0.6f)
.addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase)
@ -783,7 +783,7 @@ public final class EntityDefinitions {
.build();
SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase)
.type(EntityType.SHEEP)
.heightAndWidth(0.9f)
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
.build();
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
@ -832,11 +832,11 @@ public final class EntityDefinitions {
.height(1.6f).width(1.3965f)
.addTranslator(MetadataType.INT, HorseEntity::setHorseVariant)
.build();
SKELETON_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase)
SKELETON_HORSE = EntityDefinition.inherited(SkeletonHorseEntity::new, abstractHorseEntityBase)
.type(EntityType.SKELETON_HORSE)
.height(1.6f).width(1.3965f)
.build();
ZOMBIE_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase)
ZOMBIE_HORSE = EntityDefinition.inherited(ZombieHorseEntity::new, abstractHorseEntityBase)
.type(EntityType.ZOMBIE_HORSE)
.height(1.6f).width(1.3965f)
.build();

View File

@ -34,7 +34,7 @@ import java.util.Map;
/**
* A write-only wrapper for temporarily storing entity metadata that will be sent to Bedrock.
*/
public class GeyserDirtyMetadata {
public final class GeyserDirtyMetadata {
private final Map<EntityData, Object> metadata = new Object2ObjectLinkedOpenHashMap<>();
public void put(EntityData entityData, Object value) {

View File

@ -1,293 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.entity;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import lombok.Getter;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.living.MobEntity;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity;
import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import java.util.EnumSet;
import java.util.Set;
public class InteractiveTagManager {
/**
* All entity types that can be leashed on Java Edition
*/
private static final Set<EntityType> LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.AXOLOTL, EntityType.BEE, EntityType.CAT, EntityType.CHICKEN,
EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.GOAT, EntityType.GLOW_SQUID, EntityType.HOGLIN,
EntityType.HORSE, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA,
EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG,
EntityType.POLAR_BEAR, EntityType.RABBIT, EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.SQUID, EntityType.STRIDER,
EntityType.WOLF, EntityType.ZOGLIN);
private static final Set<EntityType> SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE,
EntityType.ZOMBIE_HORSE, EntityType.MULE);
/**
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
*
* @param session the Bedrock client session
* @param interactEntity the entity that the client is currently facing.
*/
public static void updateTag(GeyserSession session, Entity interactEntity) {
ItemMapping mapping = session.getPlayerInventory().getItemInHand().getMapping(session);
String javaIdentifierStripped = mapping.getJavaIdentifier().replace("minecraft:", "");
EntityType entityType = interactEntity.getDefinition().entityType();
if (entityType == null) {
// Likely a technical entity; we don't need to worry about this
return;
}
InteractiveTag interactiveTag = InteractiveTag.NONE;
if (interactEntity instanceof MobEntity mobEntity && mobEntity.getLeashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
// Unleash the entity
interactiveTag = InteractiveTag.REMOVE_LEASH;
} else if (javaIdentifierStripped.equals("saddle") && !interactEntity.getFlag(EntityFlag.SADDLED) &&
((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(entityType) && interactEntity.getFlag(EntityFlag.TAMED) && !session.isSneaking()) ||
entityType == EntityType.PIG || entityType == EntityType.STRIDER)) {
// Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed)
interactiveTag = InteractiveTag.SADDLE;
} else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null &&
session.getPlayerInventory().getItemInHand().getNbt().contains("display")) {
// Holding a named name tag
interactiveTag = InteractiveTag.NAME;
} else if (interactEntity instanceof MobEntity mobEntity &&javaIdentifierStripped.equals("lead")
&& LEASHABLE_MOB_TYPES.contains(entityType) && mobEntity.getLeashHolderBedrockId() == -1L) {
// Holding a leash and the mob is leashable for sure
// (Plugins can change this behavior so that's something to look into in the far far future)
interactiveTag = InteractiveTag.LEASH;
} else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped, mapping)) {
// This animal can be fed
interactiveTag = InteractiveTag.FEED;
} else {
switch (entityType) {
case BOAT:
if (interactEntity.getPassengers().size() < 2) {
interactiveTag = InteractiveTag.BOARD_BOAT;
}
break;
case CAT:
if (interactEntity.getFlag(EntityFlag.TAMED) &&
((CatEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
break;
}
break;
case MOOSHROOM:
// Shear the mooshroom
if (javaIdentifierStripped.equals("shears")) {
interactiveTag = InteractiveTag.MOOSHROOM_SHEAR;
break;
}
// Bowls are acceptable here
else if (javaIdentifierStripped.equals("bowl")) {
interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW;
break;
}
// Fall down to COW as this works on mooshrooms
case COW:
if (javaIdentifierStripped.equals("bucket")) {
// Milk the cow
interactiveTag = InteractiveTag.MILK;
}
break;
case CREEPER:
if (javaIdentifierStripped.equals("flint_and_steel")) {
// Today I learned that you can ignite a creeper with flint and steel! Huh.
interactiveTag = InteractiveTag.IGNITE_CREEPER;
}
break;
case DONKEY:
case LLAMA:
case MULE:
if (interactEntity.getFlag(EntityFlag.TAMED) && !interactEntity.getFlag(EntityFlag.CHESTED)
&& javaIdentifierStripped.equals("chest")) {
// Can attach a chest
interactiveTag = InteractiveTag.ATTACH_CHEST;
break;
}
// Intentional fall-through
case HORSE:
case SKELETON_HORSE:
case TRADER_LLAMA:
case ZOMBIE_HORSE:
boolean tamed = interactEntity.getFlag(EntityFlag.TAMED);
if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || interactEntity.getFlag(EntityFlag.CHESTED))) {
interactiveTag = InteractiveTag.OPEN_CONTAINER;
break;
}
if (!interactEntity.getFlag(EntityFlag.BABY)) {
// Can't ride a baby
if (tamed) {
interactiveTag = InteractiveTag.RIDE_HORSE;
} else if (mapping.getJavaId() == 0) {
// Can't hide an untamed entity without having your hand empty
interactiveTag = InteractiveTag.MOUNT;
}
}
break;
case MINECART:
if (interactEntity.getPassengers().isEmpty()) {
interactiveTag = InteractiveTag.RIDE_MINECART;
}
break;
case CHEST_MINECART:
case COMMAND_BLOCK_MINECART:
case HOPPER_MINECART:
interactiveTag = InteractiveTag.OPEN_CONTAINER;
break;
case PIG:
if (interactEntity.getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.MOUNT;
}
break;
case PIGLIN:
if (!interactEntity.getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) {
interactiveTag = InteractiveTag.BARTER;
}
break;
case SHEEP:
if (!interactEntity.getFlag(EntityFlag.SHEARED)) {
if (javaIdentifierStripped.equals("shears")) {
// Shear the sheep
interactiveTag = InteractiveTag.SHEAR;
} else if (javaIdentifierStripped.contains("_dye")) {
// Dye the sheep
interactiveTag = InteractiveTag.DYE;
}
}
break;
case STRIDER:
if (interactEntity.getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.RIDE_STRIDER;
}
break;
case VILLAGER:
VillagerEntity villager = (VillagerEntity) interactEntity;
if (villager.isCanTradeWith() && !villager.isBaby()) { // Not a nitwit, has a profession and is not a baby
interactiveTag = InteractiveTag.TRADE;
}
break;
case WANDERING_TRADER:
interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably.
break;
case WOLF:
if (javaIdentifierStripped.equals("bone") && !interactEntity.getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame
interactiveTag = InteractiveTag.TAME;
} else if (interactEntity.getFlag(EntityFlag.TAMED) &&
((WolfEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
break;
case ZOMBIE_VILLAGER:
// We can't guarantee the existence of the weakness effect so we just always show it.
if (javaIdentifierStripped.equals("golden_apple")) {
interactiveTag = InteractiveTag.CURE;
}
break;
default:
break;
}
}
session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue());
session.getPlayerEntity().updateBedrockMetadata();
}
/**
* All interactive tags in enum form. For potential API usage.
*/
public enum InteractiveTag {
NONE((Void) null),
IGNITE_CREEPER("creeper"),
EDIT,
LEAVE_BOAT("exit.boat"),
FEED,
FISH("fishing"),
MILK,
MOOSHROOM_SHEAR("mooshear"),
MOOSHROOM_MILK_STEW("moostew"),
BOARD_BOAT("ride.boat"),
RIDE_MINECART("ride.minecart"),
RIDE_HORSE("ride.horse"),
RIDE_STRIDER("ride.strider"),
SHEAR,
SIT,
STAND,
TALK,
TAME,
DYE,
CURE,
OPEN_CONTAINER("opencontainer"),
CREATE_MAP("createMap"),
TAKE_PICTURE("takepicture"),
SADDLE,
MOUNT,
BOOST,
WRITE,
LEASH,
REMOVE_LEASH("unleash"),
NAME,
ATTACH_CHEST("attachchest"),
TRADE,
POSE_ARMOR_STAND("armorstand.pose"),
EQUIP_ARMOR_STAND("armorstand.equip"),
READ,
WAKE_VILLAGER("wakevillager"),
BARTER;
/**
* The full string that should be passed on to the client.
*/
@Getter
private final String value;
InteractiveTag(Void isNone) {
this.value = "";
}
InteractiveTag(String value) {
this.value = "action.interact." + value;
}
InteractiveTag() {
this.value = "action.interact." + name().toLowerCase();
}
}
}

View File

@ -70,8 +70,8 @@ public class AbstractArrowEntity extends Entity {
super.setMotion(motion);
double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ());
this.yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()));
this.pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed));
this.headYaw = yaw;
setYaw((float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())));
setPitch((float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)));
setHeadYaw(getYaw());
}
}

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
@ -35,6 +36,8 @@ import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -78,8 +81,8 @@ public class BoatEntity extends Entity {
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
// We don't include the rotation (y) as it causes the boat to appear sideways
setPosition(position.add(0d, this.definition.offset(), 0d));
this.yaw = yaw + 90;
this.headYaw = yaw + 90;
setYaw(yaw + 90);
setHeadYaw(yaw + 90);
setOnGround(isOnGround);
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
@ -158,6 +161,27 @@ public class BoatEntity extends Entity {
}
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (session.isSneaking()) {
return InteractiveTag.NONE;
} else if (passengers.size() < 2) {
return InteractiveTag.BOARD_BOAT;
} else {
return InteractiveTag.NONE;
}
}
@Override
public InteractionResult interact(Hand hand) {
if (session.isSneaking()) {
return InteractionResult.PASS;
} else {
// TODO: the client also checks for "out of control" ticks
return InteractionResult.SUCCESS;
}
}
private void updateLeftPaddle(GeyserSession session, Entity rower) {
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;

View File

@ -25,10 +25,16 @@
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
@ -55,4 +61,30 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getCommandBlockRuntimeId());
dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6);
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (session.canUseCommandBlocks()) {
return InteractiveTag.OPEN_CONTAINER;
} else {
return InteractiveTag.NONE;
}
}
@Override
public InteractionResult interact(Hand hand) {
if (session.canUseCommandBlocks()) {
// Client-side GUI required
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(Vector3i.ZERO);
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.COMMAND_BLOCK);
openPacket.setUniqueEntityId(geyserId);
session.sendUpstreamPacket(openPacket);
return InteractionResult.SUCCESS;
} else {
return InteractionResult.PASS;
}
}
}

View File

@ -30,15 +30,14 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
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.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@ -48,6 +47,8 @@ import org.geysermc.geyser.entity.GeyserDirtyMetadata;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.MathUtils;
import java.util.Collections;
@ -203,7 +204,7 @@ public class Entity {
}
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, boolean isOnGround) {
moveRelative(relX, relY, relZ, yaw, pitch, this.headYaw, isOnGround);
moveRelative(relX, relY, relZ, yaw, pitch, getHeadYaw(), isOnGround);
}
public void moveRelative(double relX, double relY, double relZ, float yaw, float pitch, float headYaw, boolean isOnGround) {
@ -224,7 +225,7 @@ public class Entity {
}
public void moveAbsolute(Vector3f position, float yaw, float pitch, boolean isOnGround, boolean teleported) {
moveAbsolute(position, yaw, pitch, this.headYaw, isOnGround, teleported);
moveAbsolute(position, yaw, pitch, getHeadYaw(), isOnGround, teleported);
}
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
@ -253,7 +254,8 @@ public class Entity {
* @param isOnGround Whether the entity is currently on the ground.
*/
public void teleport(Vector3f position, float yaw, float pitch, boolean isOnGround) {
moveAbsolute(position, yaw, pitch, isOnGround, false);
// teleport will always set the headYaw to yaw
moveAbsolute(position, yaw, pitch, yaw, isOnGround, false);
}
/**
@ -261,7 +263,7 @@ public class Entity {
* @param headYaw The new head rotation of the entity.
*/
public void updateHeadLookRotation(float headYaw) {
moveRelative(0, 0, 0, headYaw, pitch, this.headYaw, onGround);
moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround());
}
/**
@ -274,7 +276,7 @@ public class Entity {
* @param isOnGround Whether the entity is currently on the ground.
*/
public void updatePositionAndRotation(double moveX, double moveY, double moveZ, float yaw, float pitch, boolean isOnGround) {
moveRelative(moveX, moveY, moveZ, this.yaw, pitch, yaw, isOnGround);
moveRelative(moveX, moveY, moveZ, yaw, pitch, getHeadYaw(), isOnGround);
}
/**
@ -435,12 +437,12 @@ public class Entity {
}
/**
* x = Pitch, y = HeadYaw, z = Yaw
* x = Pitch, y = Yaw, z = HeadYaw
*
* @return the bedrock rotation
*/
public Vector3f getBedrockRotation() {
return Vector3f.from(pitch, headYaw, yaw);
return Vector3f.from(getPitch(), getYaw(), getHeadYaw());
}
/**
@ -467,12 +469,68 @@ public class Entity {
}
}
public boolean isAlive() {
return this.valid;
}
/**
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
*/
public final void updateInteractiveTag() {
InteractiveTag tag = InteractiveTag.NONE;
for (Hand hand: EntityUtils.HANDS) {
tag = testInteraction(hand);
if (tag != InteractiveTag.NONE) {
break;
}
}
session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, tag.getValue());
session.getPlayerEntity().updateBedrockMetadata();
}
/**
* Test interacting with the given hand to see if we should send a tag to the Bedrock client.
* Should usually mirror {@link #interact(Hand)} without any side effects.
*/
protected InteractiveTag testInteraction(Hand hand) {
return InteractiveTag.NONE;
}
/**
* Simulates interacting with an entity. The code here should mirror Java Edition code to the best of its ability,
* to ensure packet parity as well as functionality parity (such as sound effect responses).
*/
public InteractionResult interact(Hand hand) {
return InteractionResult.PASS;
}
/**
* Simulates interacting with this entity at a specific click point. As of Java Edition 1.18.1, this is only used for armor stands.
*/
public InteractionResult interactAt(Hand hand) {
return InteractionResult.PASS;
}
/**
* Send an entity event of the specified type to the Bedrock player from this entity.
*/
public final void playEntityEvent(EntityEventType type) {
playEntityEvent(type, 0);
}
/**
* Send an entity event of the specified type with the specified data to the Bedrock player from this entity.
*/
public final void playEntityEvent(EntityEventType type, int data) {
EntityEventPacket packet = new EntityEventPacket();
packet.setRuntimeEntityId(geyserId);
packet.setType(type);
packet.setData(data);
session.sendUpstreamPacket(packet);
}
@SuppressWarnings("unchecked")
public <I extends Entity> I as(Class<I> entityClass) {
return entityClass.isInstance(this) ? (I) this : null;
}
public <I extends Entity> boolean is(Class<I> entityClass) {
return entityClass.isInstance(this);
}
}

View File

@ -72,6 +72,6 @@ public class FireballEntity extends ThrowableEntity {
@Override
public void tick() {
moveAbsoluteImmediate(tickMovement(position), yaw, pitch, headYaw, false, false);
moveAbsoluteImmediate(tickMovement(position), getYaw(), getPitch(), getHeadYaw(), false, false);
}
}

View File

@ -152,7 +152,7 @@ public class FishingHookEntity extends ThrowableEntity {
float gravity = getGravity();
motion = motion.down(gravity);
moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false);
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
float drag = getDrag();
motion = motion.mul(drag);
@ -160,7 +160,7 @@ public class FishingHookEntity extends ThrowableEntity {
@Override
protected float getGravity() {
if (!isInWater() && !onGround) {
if (!isInWater() && !isOnGround()) {
return 0.03f;
}
return 0;

View File

@ -26,11 +26,13 @@
package org.geysermc.geyser.entity.type;
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.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.util.InteractionResult;
import java.util.UUID;
@ -42,6 +44,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
}
public void setHasFuel(BooleanEntityMetadata entityMetadata) {
// Note: Java ticks this entity and gives it particles if it has fuel
hasFuel = entityMetadata.getPrimitiveValue();
updateDefaultBlockMetadata();
}
@ -51,4 +54,10 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID));
dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6);
}
@Override
public InteractionResult interact(Hand hand) {
// Always works since you can "push" it this way
return InteractionResult.SUCCESS;
}
}

View File

@ -76,10 +76,10 @@ public class ItemEntity extends ThrowableEntity {
if (isInWater()) {
return;
}
if (!onGround || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
if (!isOnGround() || (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) > 0.00001) {
float gravity = getGravity();
motion = motion.down(gravity);
moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false);
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
float drag = getDrag();
motion = motion.mul(drag, 0.98f, drag);
}
@ -124,7 +124,7 @@ public class ItemEntity extends ThrowableEntity {
@Override
protected float getGravity() {
if (getFlag(EntityFlag.HAS_GRAVITY) && !onGround && !isInWater()) {
if (getFlag(EntityFlag.HAS_GRAVITY) && !isOnGround() && !isInWater()) {
// Gravity can change if the item is in water/lava, but
// the server calculates the motion & position for us
return 0.04f;
@ -134,7 +134,7 @@ public class ItemEntity extends ThrowableEntity {
@Override
protected float getDrag() {
if (onGround) {
if (isOnGround()) {
Vector3i groundBlockPos = position.toInt().down(1);
int blockState = session.getGeyser().getWorldManager().getBlockAt(session, groundBlockPos);
return BlockStateValues.getSlipperiness(blockState) * 0.98f;

View File

@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
@ -37,12 +38,13 @@ import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket;
import com.nukkitx.protocol.bedrock.v465.Bedrock_v465;
import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.UUID;
@ -85,10 +87,8 @@ public class ItemFrameEntity extends Entity {
.putInt("version", session.getBlockMappings().getBlockStateVersion());
NbtMapBuilder statesBuilder = NbtMap.builder()
.putInt("facing_direction", direction.ordinal())
.putByte("item_frame_map_bit", (byte) 0);
if (session.getUpstream().getProtocolVersion() >= Bedrock_v465.V465_CODEC.getProtocolVersion()) {
statesBuilder.putByte("item_frame_photo_bit", (byte) 0);
}
.putByte("item_frame_map_bit", (byte) 0)
.putByte("item_frame_photo_bit", (byte) 0);
blockBuilder.put("states", statesBuilder.build());
bedrockRuntimeId = session.getBlockMappings().getItemFrame(blockBuilder.build());
@ -208,6 +208,11 @@ public class ItemFrameEntity extends Entity {
changed = false;
}
@Override
public InteractionResult interact(Hand hand) {
return InventoryUtils.isEmpty(heldItem) && session.getPlayerInventory().getItemInHand(hand).isEmpty() ? InteractionResult.PASS : InteractionResult.SUCCESS;
}
/**
* Finds the Java entity ID of an item frame from its Bedrock position.
* @param position position of item frame in Bedrock.

View File

@ -25,9 +25,11 @@
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import java.util.UUID;
@ -38,4 +40,9 @@ public class LeashKnotEntity extends Entity {
super(session, entityId, geyserId, uuid, definition, position.add(0.5f, 0.25f, 0.5f), motion, yaw, pitch, headYaw);
}
@Override
public InteractionResult interact(Hand hand) {
// Un-leashing the knot
return InteractionResult.SUCCESS;
}
}

View File

@ -33,6 +33,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.AttributeData;
@ -48,10 +51,12 @@ import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.InteractionResult;
import java.util.ArrayList;
import java.util.Collections;
@ -94,13 +99,15 @@ public class LivingEntity extends Entity {
public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) {
byte xd = entityMetadata.getPrimitiveValue();
// Blocking gets triggered when using a bow, but if we set USING_ITEM for all items, it may look like
// you're "mining" with ex. a shield.
boolean isUsingItem = (xd & 0x01) == 0x01;
boolean isUsingOffhand = (xd & 0x02) == 0x02;
ItemMapping shield = session.getItemMappings().getStoredItems().shield();
boolean isUsingShield = (getHand().getId() == shield.getBedrockId() ||
getHand().equals(ItemData.AIR) && getOffHand().getId() == shield.getBedrockId());
setFlag(EntityFlag.USING_ITEM, (xd & 0x01) == 0x01 && !isUsingShield);
setFlag(EntityFlag.BLOCKING, (xd & 0x01) == 0x01);
boolean isUsingShield = hasShield(isUsingOffhand, shield);
setFlag(EntityFlag.USING_ITEM, isUsingItem && !isUsingShield);
// Override the blocking
setFlag(EntityFlag.BLOCKING, isUsingItem && isUsingShield);
// Riptide spin attack
setFlag(EntityFlag.DAMAGE_NEARBY_MOBS, (xd & 0x04) == 0x04);
@ -137,6 +144,14 @@ public class LivingEntity extends Entity {
}
}
protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) {
if (offhand) {
return offHand.getId() == shieldMapping.getBedrockId();
} else {
return hand.getId() == shieldMapping.getBedrockId();
}
}
@Override
protected boolean isShaking() {
return isMaxFrozenState;
@ -169,6 +184,36 @@ public class LivingEntity extends Entity {
return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, (float) Math.ceil(this.health), this.maxHealth);
}
@Override
public boolean isAlive() {
return this.valid && health > 0f;
}
@Override
public InteractionResult interact(Hand hand) {
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
if (itemStack.getJavaId() == session.getItemMappings().getStoredItems().nameTag()) {
InteractionResult result = checkInteractWithNameTag(itemStack);
if (result.consumesAction()) {
return result;
}
}
return super.interact(hand);
}
/**
* Checks to see if a nametag interaction would go through.
*/
protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) {
CompoundTag nbt = itemStack.getNbt();
if (nbt != null && nbt.get("display") instanceof CompoundTag displayTag && displayTag.get("Name") instanceof StringTag) {
// The mob shall be named
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
public void updateArmor(GeyserSession session) {
if (!valid) return;
@ -255,7 +300,9 @@ public class LivingEntity extends Entity {
if (javaAttribute.getType() instanceof AttributeType.Builtin type) {
switch (type) {
case GENERIC_MAX_HEALTH -> {
this.maxHealth = (float) AttributeUtils.calculateValue(javaAttribute);
// Since 1.18.0, setting the max health to 0 or below causes the entity to die on Bedrock but not on Java
// See https://github.com/GeyserMC/Geyser/issues/2971
this.maxHealth = Math.max((float) AttributeUtils.calculateValue(javaAttribute), 1f);
newAttributes.add(createHealthAttribute());
}
case GENERIC_ATTACK_DAMAGE -> newAttributes.add(calculateAttribute(javaAttribute, GeyserAttributeType.ATTACK_DAMAGE));

View File

@ -27,10 +27,14 @@ package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
@ -62,6 +66,41 @@ public class MinecartEntity extends Entity {
@Override
public Vector3f getBedrockRotation() {
// Note: minecart rotation on rails does not care about the actual rotation value
return Vector3f.from(0, yaw, 0);
return Vector3f.from(0, getYaw(), 0);
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {
return InteractiveTag.OPEN_CONTAINER;
} else {
if (session.isSneaking()) {
return InteractiveTag.NONE;
} else if (!passengers.isEmpty()) {
// Can't enter if someone is inside
return InteractiveTag.NONE;
} else {
// Attempt to enter
return InteractiveTag.RIDE_MINECART;
}
}
}
@Override
public InteractionResult interact(Hand hand) {
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {
// Opening the UI of this minecart
return InteractionResult.SUCCESS;
} else {
if (session.isSneaking()) {
return InteractionResult.PASS;
} else if (!passengers.isEmpty()) {
// Can't enter if someone is inside
return InteractionResult.PASS;
} else {
// Attempt to enter
return InteractionResult.SUCCESS;
}
}
}
}

View File

@ -56,7 +56,7 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
@Override
public void tick() {
moveAbsoluteImmediate(position.add(motion), yaw, pitch, headYaw, onGround, false);
moveAbsoluteImmediate(position.add(motion), getYaw(), getPitch(), getHeadYaw(), isOnGround(), false);
float drag = getDrag();
float gravity = getGravity();
motion = motion.mul(drag).down(gravity);
@ -89,20 +89,20 @@ public class ThrowableEntity extends Entity implements Tickable {
}
setPosition(position);
if (this.yaw != yaw) {
if (getYaw() != yaw) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityDeltaPacket.setYaw(yaw);
this.yaw = yaw;
setYaw(yaw);
}
if (this.pitch != pitch) {
if (getPitch() != pitch) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
moveEntityDeltaPacket.setPitch(pitch);
this.pitch = pitch;
setPitch(pitch);
}
if (this.headYaw != headYaw) {
if (getHeadYaw() != headYaw) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
moveEntityDeltaPacket.setHeadYaw(headYaw);
this.headYaw = headYaw;
setHeadYaw(headYaw);
}
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {

View File

@ -28,8 +28,12 @@ package org.geysermc.geyser.entity.type.living;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AbstractFishEntity extends WaterEntity {
@ -42,4 +46,14 @@ public class AbstractFishEntity extends WaterEntity {
setFlag(EntityFlag.CAN_CLIMB, false);
setFlag(EntityFlag.HAS_GRAVITY, false);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (EntityUtils.attemptToBucket(session, itemInHand)) {
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -36,4 +36,9 @@ public class AmbientEntity extends MobEntity {
public AmbientEntity(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);
}
@Override
protected boolean canBeLeashed() {
return false;
}
}

View File

@ -28,6 +28,8 @@ 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.Rotation;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
@ -39,6 +41,8 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.MathUtils;
import java.util.Optional;
import java.util.UUID;
@ -84,8 +88,6 @@ public class ArmorStandEntity extends LivingEntity {
@Override
public void spawnEntity() {
this.pitch = yaw;
this.headYaw = yaw;
super.spawnEntity();
}
@ -136,7 +138,7 @@ public class ArmorStandEntity extends LivingEntity {
}
isSmall = newIsSmall;
if (!isMarker) {
if (!isMarker && !isInvisible) { // Addition for isInvisible check caused by https://github.com/GeyserMC/Geyser/issues/2780
toggleSmallStatus();
}
}
@ -202,9 +204,9 @@ public class ArmorStandEntity extends LivingEntity {
// Indicate that rotation should be checked
setFlag(EntityFlag.BRIBED, true);
int rotationX = getRotation(rotation.getPitch());
int rotationY = getRotation(rotation.getYaw());
int rotationZ = getRotation(rotation.getRoll());
int rotationX = MathUtils.wrapDegreesToInt(rotation.getPitch());
int rotationY = MathUtils.wrapDegreesToInt(rotation.getYaw());
int rotationZ = MathUtils.wrapDegreesToInt(rotation.getRoll());
// The top bit acts like binary and determines if each rotation goes above 100
// We don't do this for the negative values out of concerns of the number being too big
int topBit = (Math.abs(rotationX) >= 100 ? 4 : 0) + (Math.abs(rotationY) >= 100 ? 2 : 0) + (Math.abs(rotationZ) >= 100 ? 1 : 0);
@ -237,6 +239,16 @@ public class ArmorStandEntity extends LivingEntity {
}
}
@Override
public InteractionResult interactAt(Hand hand) {
if (!isMarker && session.getPlayerInventory().getItemInHand(hand).getJavaId() != session.getItemMappings().getStoredItems().nameTag()) {
// Java Edition returns SUCCESS if in spectator mode, but this is overrided with an earlier check on the client
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
}
}
@Override
public void setHelmet(ItemData helmet) {
super.setHelmet(helmet);
@ -306,7 +318,7 @@ public class ArmorStandEntity extends LivingEntity {
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
// the metadata as it will hold the name tag.
secondEntity = new ArmorStandEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(), null,
EntityDefinitions.ARMOR_STAND, position, motion, yaw, pitch, headYaw);
EntityDefinitions.ARMOR_STAND, position, motion, getYaw(), getPitch(), getHeadYaw());
secondEntity.primaryEntity = false;
if (!this.positionRequiresOffset) {
// Ensure the offset is applied for the 0 scale
@ -362,17 +374,6 @@ public class ArmorStandEntity extends LivingEntity {
}
}
private int getRotation(float rotation) {
rotation = rotation % 360f;
if (rotation < -180f) {
rotation += 360f;
} else if (rotation >= 180f) {
// 181 -> -179
rotation = -(180 - (rotation - 180));
}
return (int) rotation;
}
/**
* If this armor stand is not a marker, set its bounding box size and scale.
*/
@ -426,9 +427,14 @@ public class ArmorStandEntity extends LivingEntity {
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
moveEntityPacket.setPosition(position);
moveEntityPacket.setRotation(Vector3f.from(yaw, yaw, yaw));
moveEntityPacket.setOnGround(onGround);
moveEntityPacket.setRotation(getBedrockRotation());
moveEntityPacket.setOnGround(isOnGround());
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}
@Override
public Vector3f getBedrockRotation() {
return Vector3f.from(getYaw(), getYaw(), getYaw());
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.entity.type.living;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class DolphinEntity extends WaterEntity {
public DolphinEntity(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);
}
@Override
protected boolean canBeLeashed() {
return true;
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
return InteractiveTag.FEED;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
// Feed
return InteractionResult.SUCCESS;
}
return super.mobInteract(itemInHand);
}
}

View File

@ -29,8 +29,11 @@ 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.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class IronGolemEntity extends GolemEntity {
@ -42,4 +45,18 @@ public class IronGolemEntity extends GolemEntity {
// Required, or else the overlay is black
dirtyMetadata.put(EntityData.COLOR_2, (byte) 0);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().ironIngot()) {
if (health < maxHealth) {
// Healing the iron golem
return InteractionResult.SUCCESS;
} else {
return InteractionResult.PASS;
}
}
return super.mobInteract(itemInHand);
}
}

View File

@ -26,14 +26,21 @@
package org.geysermc.geyser.entity.type.living;
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.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class MobEntity extends LivingEntity {
@ -62,4 +69,95 @@ public class MobEntity extends LivingEntity {
this.leashHolderBedrockId = bedrockId;
dirtyMetadata.put(EntityData.LEASH_HOLDER_EID, bedrockId);
}
@Override
protected final InteractiveTag testInteraction(Hand hand) {
if (!isAlive()) {
// dead lol
return InteractiveTag.NONE;
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
return InteractiveTag.REMOVE_LEASH;
} else {
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
if (itemStack.getJavaId() == storedItems.lead() && canBeLeashed()) {
// We shall leash
return InteractiveTag.LEASH;
} else if (itemStack.getJavaId() == storedItems.nameTag()) {
InteractionResult result = checkInteractWithNameTag(itemStack);
if (result.consumesAction()) {
return InteractiveTag.NAME;
}
}
InteractiveTag tag = testMobInteraction(itemStack);
return tag != InteractiveTag.NONE ? tag : super.testInteraction(hand);
}
}
@Override
public final InteractionResult interact(Hand hand) {
if (!isAlive()) {
// dead lol
return InteractionResult.PASS;
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
// TODO looks like the client assumes it will go through and removes the attachment itself?
return InteractionResult.SUCCESS;
} else {
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand);
InteractionResult result = checkPriorityInteractions(itemInHand);
if (result.consumesAction()) {
return result;
} else {
InteractionResult mobResult = mobInteract(itemInHand);
return mobResult.consumesAction() ? mobResult : super.interact(hand);
}
}
}
private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) {
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
if (itemInHand.getJavaId() == storedItems.lead() && canBeLeashed()) {
// We shall leash
return InteractionResult.SUCCESS;
} else if (itemInHand.getJavaId() == storedItems.nameTag()) {
InteractionResult result = checkInteractWithNameTag(itemInHand);
if (result.consumesAction()) {
return result;
}
} else {
ItemMapping mapping = itemInHand.getMapping(session);
if (mapping.getJavaIdentifier().endsWith("_spawn_egg")) {
// Using the spawn egg on this entity to create a child
return InteractionResult.CONSUME;
}
}
return InteractionResult.PASS;
}
@Nonnull
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return InteractiveTag.NONE;
}
@Nonnull
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return InteractionResult.PASS;
}
protected boolean canBeLeashed() {
return isNotLeashed() && !isEnemy();
}
protected final boolean isNotLeashed() {
return leashHolderBedrockId == -1L;
}
/**
* Returns if the entity is hostile. Used to determine if it can be leashed.
*/
protected boolean isEnemy() {
return false;
}
}

View File

@ -42,4 +42,9 @@ public class SlimeEntity extends MobEntity {
public void setScale(IntEntityMetadata entityMetadata) {
dirtyMetadata.put(EntityData.SCALE, 0.10f + entityMetadata.getPrimitiveValue());
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -29,8 +29,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEnti
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SnowGolemEntity extends GolemEntity {
@ -44,4 +48,24 @@ public class SnowGolemEntity extends GolemEntity {
// Handle the visibility of the pumpkin
setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) {
// Shearing the snow golem
return InteractiveTag.SHEAR;
}
return InteractiveTag.NONE;
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) {
// Shearing the snow golem
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
}

View File

@ -117,7 +117,12 @@ public class SquidEntity extends WaterEntity implements Tickable {
@Override
public Vector3f getBedrockRotation() {
return Vector3f.from(pitch, yaw, yaw);
return Vector3f.from(getPitch(), getYaw(), getYaw());
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
private void checkInWater() {

View File

@ -36,4 +36,9 @@ public class WaterEntity extends CreatureEntity {
public WaterEntity(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);
}
@Override
protected boolean canBeLeashed() {
return false;
}
}

View File

@ -26,11 +26,17 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AnimalEntity extends AgeableEntity {
@ -39,6 +45,12 @@ public class AnimalEntity extends AgeableEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
public final boolean canEat(GeyserItemStack itemStack) {
ItemMapping mapping = itemStack.getMapping(session);
String handIdentifier = mapping.getJavaIdentifier();
return canEat(handIdentifier.replace("minecraft:", ""), mapping);
}
/**
* @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example,
* <code>wheat</code>.
@ -48,4 +60,28 @@ public class AnimalEntity extends AgeableEntity {
// This is what it defaults to. OK.
return javaIdentifierStripped.equals("wheat");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (canEat(itemInHand)) {
return InteractiveTag.FEED;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (canEat(itemInHand)) {
// FEED
if (getFlag(EntityFlag.BABY)) {
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
return InteractionResult.SUCCESS;
} else {
return InteractionResult.CONSUME;
}
}
return super.mobInteract(itemInHand);
}
}

View File

@ -31,9 +31,13 @@ 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.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AxolotlEntity extends AnimalEntity {
@ -56,11 +60,26 @@ public class AxolotlEntity extends AnimalEntity {
@Override
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("tropical_fish_bucket");
return session.getTagCache().isAxolotlTemptItem(mapping);
}
@Override
protected int getMaxAir() {
return 6000;
}
@Override
protected boolean canBeLeashed() {
return true;
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (EntityUtils.attemptToBucket(session, itemInHand)) {
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CowEntity extends AnimalEntity {
public CowEntity(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);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
return super.testMobInteraction(itemInHand);
}
return InteractiveTag.MILK;
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
return super.mobInteract(itemInHand);
}
session.playSoundEvent(SoundEvent.MILK, position);
return InteractionResult.SUCCESS;
}
}

View File

@ -28,17 +28,20 @@ package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import lombok.Getter;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class GoatEntity extends AnimalEntity {
private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f;
private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f;
@Getter
private boolean isScreamer;
public GoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
@ -59,4 +62,15 @@ public class GoatEntity extends AnimalEntity {
super.setDimensions(pose);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.BABY) && itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
session.playSoundEvent(isScreamer ? SoundEvent.MILK_SCREAMER : SoundEvent.MILK, position);
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -56,4 +56,14 @@ public class HoglinEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("crimson_fungus");
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -25,15 +25,62 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class MooshroomEntity extends AnimalEntity {
private boolean isBrown = false;
public MooshroomEntity(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);
}
public void setVariant(ObjectEntityMetadata<String> entityMetadata) {
isBrown = entityMetadata.getValue().equals("brown");
dirtyMetadata.put(EntityData.VARIANT, isBrown ? 1 : 0);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
if (!isBaby()) {
if (itemInHand.getJavaId() == storedItems.bowl()) {
// Stew
return InteractiveTag.MOOSHROOM_MILK_STEW;
} else if (isAlive() && itemInHand.getJavaId() == storedItems.shears()) {
// Shear items
return InteractiveTag.MOOSHROOM_SHEAR;
}
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
boolean isBaby = isBaby();
if (!isBaby && itemInHand.getJavaId() == storedItems.bowl()) {
// Stew
return InteractionResult.SUCCESS;
} else if (!isBaby && isAlive() && itemInHand.getJavaId() == storedItems.shears()) {
// Shear items
return InteractionResult.SUCCESS;
} else if (isBrown && session.getTagCache().isSmallFlower(itemInHand) && itemInHand.getMapping(session).isHasSuspiciousStewEffect()) {
// ?
return InteractionResult.SUCCESS;
}
return super.mobInteract(itemInHand);
}
}

View File

@ -26,10 +26,15 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class OcelotEntity extends AnimalEntity {
@ -42,4 +47,26 @@ public class OcelotEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) {
// Attempt to feed
return InteractiveTag.FEED;
} else {
return super.testMobInteraction(itemInHand);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) {
// Attempt to feed
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -33,14 +33,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;
public class PandaEntity extends AnimalEntity {
private int mainGene;
private int hiddenGene;
private Gene mainGene = Gene.NORMAL;
private Gene hiddenGene = Gene.NORMAL;
public PandaEntity(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);
@ -61,12 +66,12 @@ public class PandaEntity extends AnimalEntity {
}
public void setMainGene(ByteEntityMetadata entityMetadata) {
mainGene = entityMetadata.getPrimitiveValue();
mainGene = Gene.fromId(entityMetadata.getPrimitiveValue());
updateAppearance();
}
public void setHiddenGene(ByteEntityMetadata entityMetadata) {
hiddenGene = entityMetadata.getPrimitiveValue();
hiddenGene = Gene.fromId(entityMetadata.getPrimitiveValue());
updateAppearance();
}
@ -86,23 +91,81 @@ public class PandaEntity extends AnimalEntity {
return javaIdentifierStripped.equals("bamboo");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (mainGene == Gene.WORRIED && session.isThunder()) {
return InteractiveTag.NONE;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (mainGene == Gene.WORRIED && session.isThunder()) {
// Huh!
return InteractionResult.PASS;
} else if (getFlag(EntityFlag.LAYING_DOWN)) {
// Stop the panda from laying down
// TODO laying up is client-side?
return InteractionResult.SUCCESS;
} else if (canEat(itemInHand)) {
if (getFlag(EntityFlag.BABY)) {
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
}
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
@Override
protected boolean canBeLeashed() {
return false;
}
/**
* Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up
* when both main and hidden genes match
*/
private void updateAppearance() {
if (mainGene == 4 || mainGene == 5) {
// Main gene is a recessive trait
if (mainGene.isRecessive) {
if (mainGene == hiddenGene) {
// Main and hidden genes match; this is what the panda looks like.
dirtyMetadata.put(EntityData.VARIANT, mainGene);
dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal());
} else {
// Genes have no effect on appearance
dirtyMetadata.put(EntityData.VARIANT, 0);
dirtyMetadata.put(EntityData.VARIANT, Gene.NORMAL.ordinal());
}
} else {
// No need to worry about hidden gene
dirtyMetadata.put(EntityData.VARIANT, mainGene);
dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal());
}
}
enum Gene {
NORMAL(false),
LAZY(false),
WORRIED(false),
PLAYFUL(false),
BROWN(true),
WEAK(true),
AGGRESSIVE(false);
private static final Gene[] VALUES = values();
private final boolean isRecessive;
Gene(boolean isRecessive) {
this.isRecessive = isRecessive;
}
@Nullable
private static Gene fromId(int id) {
if (id < 0 || id >= VALUES.length) {
return NORMAL;
}
return VALUES[id];
}
}
}

View File

@ -26,10 +26,16 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class PigEntity extends AnimalEntity {
@ -42,4 +48,37 @@ public class PigEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount
return InteractiveTag.MOUNT;
} else {
InteractiveTag superTag = super.testMobInteraction(itemInHand);
if (superTag != InteractiveTag.NONE) {
return superTag;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction()
? InteractiveTag.SADDLE : InteractiveTag.NONE;
}
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount
return InteractionResult.SUCCESS;
} else {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand);
}
}
}
}

View File

@ -30,19 +30,69 @@ 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.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.ItemUtils;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SheepEntity extends AnimalEntity {
private int color;
public SheepEntity(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);
}
public void setSheepFlags(ByteEntityMetadata entityMetadata) {
byte xd = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue();
byte xd = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10);
dirtyMetadata.put(EntityData.COLOR, xd);
color = xd & 15;
dirtyMetadata.put(EntityData.COLOR, (byte) color);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) {
return InteractiveTag.SHEAR;
} else {
InteractiveTag tag = super.testMobInteraction(itemInHand);
if (tag != InteractiveTag.NONE) {
return tag;
} else {
int color = ItemUtils.dyeColorFor(itemInHand.getJavaId());
if (canDye(color)) {
return InteractiveTag.DYE;
}
return InteractiveTag.NONE;
}
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) {
return InteractionResult.CONSUME;
} else {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
int color = ItemUtils.dyeColorFor(itemInHand.getJavaId());
if (canDye(color)) {
// Dyeing the sheep
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
}
}
private boolean canDye(int color) {
return color != -1 && color != this.color && !getFlag(EntityFlag.SHEARED);
}
}

View File

@ -30,9 +30,14 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class StriderEntity extends AnimalEntity {
@ -90,4 +95,37 @@ public class StriderEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("warped_fungus");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount Strider
return InteractiveTag.RIDE_STRIDER;
} else {
InteractiveTag tag = super.testMobInteraction(itemInHand);
if (tag != InteractiveTag.NONE) {
return tag;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction()
? InteractiveTag.SADDLE : InteractiveTag.NONE;
}
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount Strider
return InteractionResult.SUCCESS;
} else {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand);
}
}
}
}

View File

@ -52,4 +52,9 @@ public class TurtleEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("seagrass");
}
@Override
protected boolean canBeLeashed() {
return false;
}
}

View File

@ -37,9 +37,13 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.Set;
import java.util.UUID;
@ -122,4 +126,164 @@ public class AbstractHorseEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return testHorseInteraction(itemInHand);
}
@Nonnull
protected final InteractiveTag testHorseInteraction(@Nonnull GeyserItemStack itemInHand) {
boolean isBaby = isBaby();
if (!isBaby) {
if (getFlag(EntityFlag.TAMED) && session.isSneaking()) {
return InteractiveTag.OPEN_CONTAINER;
}
if (!passengers.isEmpty()) {
return super.testMobInteraction(itemInHand);
}
}
if (!itemInHand.isEmpty()) {
if (canEat(itemInHand)) {
return InteractiveTag.FEED;
}
if (testSaddle(itemInHand)) {
return InteractiveTag.SADDLE;
}
if (!getFlag(EntityFlag.TAMED)) {
// Horse will become mad
return InteractiveTag.NONE;
}
if (testForChest(itemInHand)) {
return InteractiveTag.ATTACH_CHEST;
}
if (additionalTestForInventoryOpen(itemInHand) || !isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) {
// Will open the inventory to be saddled
return InteractiveTag.OPEN_CONTAINER;
}
}
if (isBaby) {
return super.testMobInteraction(itemInHand);
} else {
return InteractiveTag.MOUNT;
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return mobHorseInteract(itemInHand);
}
@Nonnull
protected final InteractionResult mobHorseInteract(@Nonnull GeyserItemStack itemInHand) {
boolean isBaby = isBaby();
if (!isBaby) {
if (getFlag(EntityFlag.TAMED) && session.isSneaking()) {
// Will open the inventory
return InteractionResult.SUCCESS;
}
if (!passengers.isEmpty()) {
return super.mobInteract(itemInHand);
}
}
if (!itemInHand.isEmpty()) {
if (canEat(itemInHand)) {
if (isBaby) {
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
}
return InteractionResult.CONSUME;
}
if (testSaddle(itemInHand)) {
return InteractionResult.SUCCESS;
}
if (!getFlag(EntityFlag.TAMED)) {
// Horse will become mad
return InteractionResult.SUCCESS;
}
if (testForChest(itemInHand)) {
// TODO looks like chest is also handled client side
return InteractionResult.SUCCESS;
}
// Note: yes, this code triggers for llamas too. lol (as of Java Edition 1.18.1)
if (additionalTestForInventoryOpen(itemInHand) || (!isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle())) {
// Will open the inventory to be saddled
return InteractionResult.SUCCESS;
}
}
if (isBaby) {
return super.mobInteract(itemInHand);
} else {
// Attempt to mount
// TODO client-set flags sitting standing?
return InteractionResult.SUCCESS;
}
}
protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) {
return isAlive() && !getFlag(EntityFlag.BABY) && getFlag(EntityFlag.TAMED);
}
protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) {
return false;
}
protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) {
return itemInHand.getMapping(session).getJavaIdentifier().endsWith("_horse_armor");
}
/* Just a place to stuff common code for the undead variants without having duplicate code */
protected final InteractiveTag testUndeadHorseInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TAMED)) {
return InteractiveTag.NONE;
} else if (isBaby()) {
return testHorseInteraction(itemInHand);
} else if (session.isSneaking()) {
return InteractiveTag.OPEN_CONTAINER;
} else if (!passengers.isEmpty()) {
return testHorseInteraction(itemInHand);
} else {
if (session.getItemMappings().getStoredItems().saddle() == itemInHand.getJavaId()) {
return InteractiveTag.OPEN_CONTAINER;
}
if (testSaddle(itemInHand)) {
return InteractiveTag.SADDLE;
}
return InteractiveTag.RIDE_HORSE;
}
}
protected final InteractionResult undeadHorseInteract(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TAMED)) {
return InteractionResult.PASS;
} else if (isBaby()) {
return mobHorseInteract(itemInHand);
} else if (session.isSneaking()) {
// Opens inventory
return InteractionResult.SUCCESS;
} else if (!passengers.isEmpty()) {
return mobHorseInteract(itemInHand);
} else {
// The client tests for saddle but it doesn't matter for us at this point.
return InteractionResult.SUCCESS;
}
}
}

View File

@ -26,9 +26,12 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ChestedHorseEntity extends AbstractHorseEntity {
@ -41,4 +44,21 @@ public class ChestedHorseEntity extends AbstractHorseEntity {
protected int getContainerBaseSize() {
return 16;
}
@Override
protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) {
// Not checked here
return false;
}
@Override
protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) {
return itemInHand.getJavaId() == session.getItemMappings().getStoredItems().chest() && !getFlag(EntityFlag.CHESTED);
}
@Override
protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) {
// Armor won't work on these
return false;
}
}

View File

@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;

View File

@ -0,0 +1,54 @@
/*
* 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.entity.type.living.animal.horse;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SkeletonHorseEntity extends AbstractHorseEntity {
public SkeletonHorseEntity(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);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return testUndeadHorseInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return undeadHorseInteract(itemInHand);
}
}

View File

@ -23,29 +23,32 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.translator.protocol.java;
package org.geysermc.geyser.entity.type.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.UnlockRecipesAction;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundRecipePacket;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.Arrays;
import javax.annotation.Nonnull;
import java.util.UUID;
/**
* Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage)
*/
@Translator(packet = ClientboundRecipePacket.class)
public class JavaRecipeTranslator extends PacketTranslator<ClientboundRecipePacket> {
public class ZombieHorseEntity extends AbstractHorseEntity {
public ZombieHorseEntity(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);
}
@Nonnull
@Override
public void translate(GeyserSession session, ClientboundRecipePacket packet) {
if (packet.getAction() == UnlockRecipesAction.REMOVE) {
session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes()));
} else {
session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes()));
}
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return testUndeadHorseInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return undeadHorseInteract(itemInHand);
}
}

View File

@ -32,9 +32,13 @@ 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.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CatEntity extends TameableEntity {
@ -98,4 +102,28 @@ public class CatEntity extends TameableEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
boolean tamed = getFlag(EntityFlag.TAMED);
if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Toggle sitting
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
} else {
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractiveTag.NONE : InteractiveTag.FEED;
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
boolean tamed = getFlag(EntityFlag.TAMED);
if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
return InteractionResult.SUCCESS;
} else {
// Attempt to feed
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractionResult.PASS : InteractionResult.SUCCESS;
}
}
}

View File

@ -26,10 +26,15 @@
package org.geysermc.geyser.entity.type.living.animal.tameable;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ParrotEntity extends TameableEntity {
@ -40,6 +45,46 @@ public class ParrotEntity extends TameableEntity {
@Override
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie");
return false;
}
private boolean isTameFood(String javaIdentifierStripped) {
return javaIdentifierStripped.contains("seeds");
}
private boolean isPoisonousFood(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("cookie");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", "");
boolean tame = getFlag(EntityFlag.TAMED);
if (!tame && isTameFood(javaIdentifierStripped)) {
return InteractiveTag.FEED;
} else if (isPoisonousFood(javaIdentifierStripped)) {
return InteractiveTag.FEED;
} else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Sitting/standing
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", "");
boolean tame = getFlag(EntityFlag.TAMED);
if (!tame && isTameFood(javaIdentifierStripped)) {
return InteractionResult.SUCCESS;
} else if (isPoisonousFood(javaIdentifierStripped)) {
return InteractionResult.SUCCESS;
} else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Sitting/standing
return InteractionResult.SUCCESS;
}
return super.mobInteract(itemInHand);
}
}

View File

@ -61,17 +61,30 @@ public class TameableEntity extends AnimalEntity {
// Note: Must be set for wolf collar color to work
if (entityMetadata.getValue().isPresent()) {
// Owner UUID of entity
Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get());
UUID uuid = entityMetadata.getValue().get();
Entity entity;
if (uuid.equals(session.getPlayerEntity().getUuid())) {
entity = session.getPlayerEntity();
} else {
entity = session.getEntityCache().getPlayerEntity(uuid);
}
// Used as both a check since the player isn't in the entity cache and a normal fallback
if (entity == null) {
entity = session.getPlayerEntity();
// Set to tame, but indicate that we are not the player that owns this
ownerBedrockId = Long.MAX_VALUE;
} else {
// Translate to entity ID
ownerBedrockId = entity.getGeyserId();
}
// Translate to entity ID
ownerBedrockId = entity.getGeyserId();
} else {
// Reset
ownerBedrockId = 0L;
}
dirtyMetadata.put(EntityData.OWNER_EID, ownerBedrockId);
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
}

View File

@ -32,9 +32,14 @@ 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.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.ItemUtils;
import javax.annotation.Nonnull;
import java.util.Set;
import java.util.UUID;
@ -90,4 +95,45 @@ public class WolfEntity extends TameableEntity {
// Cannot be a baby to eat these foods
return WOLF_FOODS.contains(javaIdentifierStripped) && !isBaby();
}
@Override
protected boolean canBeLeashed() {
return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed();
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (getFlag(EntityFlag.ANGRY)) {
return InteractiveTag.NONE;
}
if (itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame
return InteractiveTag.TAME;
} else {
int color = ItemUtils.dyeColorFor(itemInHand.getJavaId());
if (color != -1) {
// If this fails, as of Java Edition 1.18.1, you cannot toggle sit/stand
if (color != this.collarColor) {
return InteractiveTag.DYE;
}
} else if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (ownerBedrockId == session.getPlayerEntity().getGeyserId() || getFlag(EntityFlag.TAMED)
|| itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.ANGRY)) {
// Sitting toggle or feeding; not angry
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
}
}
}

View File

@ -26,10 +26,16 @@
package org.geysermc.geyser.entity.type.living.merchant;
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.EntityDefinitions;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AbstractMerchantEntity extends AgeableEntity {
@ -37,4 +43,37 @@ public class AbstractMerchantEntity extends AgeableEntity {
public AbstractMerchantEntity(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);
}
@Override
protected boolean canBeLeashed() {
return false;
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
if (!javaIdentifier.equals("minecraft:villager_spawn_egg")
&& (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING) && ((VillagerEntity) this).isCanTradeWith())) {
// An additional check we know cannot work
if (!isBaby()) {
return InteractiveTag.TRADE;
}
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
if (!javaIdentifier.equals("minecraft:villager_spawn_egg")
&& (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING))
&& (definition != EntityDefinitions.WANDERING_TRADER || !getFlag(EntityFlag.BABY))) {
// Trading time
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -33,52 +33,49 @@ import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Optional;
import java.util.UUID;
public class VillagerEntity extends AbstractMerchantEntity {
/**
* A map of Java profession IDs to Bedrock IDs
*/
public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap();
private static final int[] VILLAGER_PROFESSIONS = new int[15];
/**
* A map of all Java region IDs (plains, savanna...) to Bedrock
*/
public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap();
private static final int[] VILLAGER_REGIONS = new int[7];
static {
// Java villager profession IDs -> Bedrock
VILLAGER_PROFESSIONS.put(0, 0);
VILLAGER_PROFESSIONS.put(1, 8);
VILLAGER_PROFESSIONS.put(2, 11);
VILLAGER_PROFESSIONS.put(3, 6);
VILLAGER_PROFESSIONS.put(4, 7);
VILLAGER_PROFESSIONS.put(5, 1);
VILLAGER_PROFESSIONS.put(6, 2);
VILLAGER_PROFESSIONS.put(7, 4);
VILLAGER_PROFESSIONS.put(8, 12);
VILLAGER_PROFESSIONS.put(9, 5);
VILLAGER_PROFESSIONS.put(10, 13);
VILLAGER_PROFESSIONS.put(11, 14);
VILLAGER_PROFESSIONS.put(12, 3);
VILLAGER_PROFESSIONS.put(13, 10);
VILLAGER_PROFESSIONS.put(14, 9);
VILLAGER_PROFESSIONS[0] = 0;
VILLAGER_PROFESSIONS[1] = 8;
VILLAGER_PROFESSIONS[2] = 11;
VILLAGER_PROFESSIONS[3] = 6;
VILLAGER_PROFESSIONS[4] = 7;
VILLAGER_PROFESSIONS[5] = 1;
VILLAGER_PROFESSIONS[6] = 2;
VILLAGER_PROFESSIONS[7] = 4;
VILLAGER_PROFESSIONS[8] = 12;
VILLAGER_PROFESSIONS[9] = 5;
VILLAGER_PROFESSIONS[10] = 13;
VILLAGER_PROFESSIONS[11] = 14;
VILLAGER_PROFESSIONS[12] = 3;
VILLAGER_PROFESSIONS[13] = 10;
VILLAGER_PROFESSIONS[14] = 9;
VILLAGER_REGIONS.put(0, 1);
VILLAGER_REGIONS.put(1, 2);
VILLAGER_REGIONS.put(2, 0);
VILLAGER_REGIONS.put(3, 3);
VILLAGER_REGIONS.put(4, 4);
VILLAGER_REGIONS.put(5, 5);
VILLAGER_REGIONS.put(6, 6);
VILLAGER_REGIONS[0] = 1;
VILLAGER_REGIONS[1] = 2;
VILLAGER_REGIONS[2] = 0;
VILLAGER_REGIONS[3] = 3;
VILLAGER_REGIONS[4] = 4;
VILLAGER_REGIONS[5] = 5;
VILLAGER_REGIONS[6] = 6;
}
private Vector3i bedPosition;
@ -95,12 +92,12 @@ public class VillagerEntity extends AbstractMerchantEntity {
public void setVillagerData(EntityMetadata<VillagerData, ?> entityMetadata) {
VillagerData villagerData = entityMetadata.getValue();
// Profession
int profession = VILLAGER_PROFESSIONS.get(villagerData.getProfession());
int profession = getBedrockProfession(villagerData.getProfession());
canTradeWith = profession != 14 && profession != 0; // Not a notwit and not professionless
dirtyMetadata.put(EntityData.VARIANT, profession);
//metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason?
// Region
dirtyMetadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType()));
dirtyMetadata.put(EntityData.MARK_VARIANT, getBedrockRegion(villagerData.getType()));
// Trade tier - different indexing in Bedrock
dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
}
@ -158,4 +155,12 @@ public class VillagerEntity extends AbstractMerchantEntity {
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}
public static int getBedrockProfession(int javaProfession) {
return javaProfession >= 0 && javaProfession < VILLAGER_PROFESSIONS.length ? VILLAGER_PROFESSIONS[javaProfession] : 0;
}
public static int getBedrockRegion(int javaRegion) {
return javaRegion >= 0 && javaRegion < VILLAGER_REGIONS.length ? VILLAGER_REGIONS[javaRegion] : 0;
}
}

View File

@ -28,10 +28,15 @@ package org.geysermc.geyser.entity.type.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CreeperEntity extends MonsterEntity {
@ -55,4 +60,26 @@ public class CreeperEntity extends MonsterEntity {
ignitedByFlintAndSteel = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) {
return InteractiveTag.IGNITE_CREEPER;
} else {
return super.testMobInteraction(itemInHand);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) {
// Ignite creeper
session.playSoundEvent(SoundEvent.IGNITE, position);
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -39,6 +39,7 @@ import org.geysermc.geyser.entity.type.living.MobEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.DimensionUtils;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@ -130,7 +131,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
for (int i = 0; i < segmentHistory.length; i++) {
segmentHistory[i] = new Segment();
segmentHistory[i].yaw = headYaw;
segmentHistory[i].yaw = getHeadYaw();
segmentHistory[i].y = position.getY();
}
}
@ -150,6 +151,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
return super.despawnEntity();
}
@Override
protected boolean isEnemy() {
return true;
}
@Override
public void tick() {
effectTick();
@ -163,7 +169,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
* Updates the positions of the Ender Dragon's multiple bounding boxes
*/
private void updateBoundingBoxes() {
Vector3f facingDir = Vector3f.createDirectionDeg(0, headYaw);
Vector3f facingDir = Vector3f.createDirectionDeg(0, getHeadYaw());
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()));
@ -182,7 +188,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
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 - headYaw).mul(4.5f).up(2f);
Vector3f wingPos = Vector3f.createDirectionDeg(0, 90f - getHeadYaw()).mul(4.5f).up(2f);
rightWing.setPosition(wingPos);
leftWing.setPosition(wingPos.mul(-1, 1, -1)); // Mirror horizontally
@ -191,7 +197,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
float distance = (i + 1) * 2f;
// Curls the tail when the dragon turns
Segment targetSegment = getSegment(12 + 2 * i);
float angle = headYaw + targetSegment.yaw - baseSegment.yaw;
float angle = getHeadYaw() + 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));
@ -257,6 +263,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
spawnParticleEffectPacket.setMolangVariablesJson(Optional.empty());
session.sendUpstreamPacket(spawnParticleEffectPacket);
}
}
@ -288,10 +295,6 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
session.sendUpstreamPacket(playSoundPacket);
}
private boolean isAlive() {
return health > 0;
}
private boolean isHovering() {
return phase == 10;
}
@ -305,7 +308,7 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
*/
private void pushSegment() {
latestSegment = (latestSegment + 1) % segmentHistory.length;
segmentHistory[latestSegment].yaw = headYaw;
segmentHistory[latestSegment].yaw = getHeadYaw();
segmentHistory[latestSegment].y = position.getY();
}

View File

@ -44,4 +44,9 @@ public class GhastEntity extends FlyingEntity {
// If the ghast is attacking
dirtyMetadata.put(EntityData.CHARGE_AMOUNT, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -37,4 +37,9 @@ public class MonsterEntity extends CreatureEntity {
public MonsterEntity(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);
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -48,4 +48,9 @@ public class PhantomEntity extends FlyingEntity {
setBoundingBoxHeight(boundsScale * definition.height());
dirtyMetadata.put(EntityData.SCALE, modelScale);
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -30,8 +30,12 @@ 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.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class PiglinEntity extends BasePiglinEntity {
@ -64,4 +68,30 @@ public class PiglinEntity extends BasePiglinEntity {
super.updateOffHand(session);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
InteractiveTag tag = super.testMobInteraction(itemInHand);
if (tag != InteractiveTag.NONE) {
return tag;
} else {
return canGiveGoldTo(itemInHand) ? InteractiveTag.BARTER : InteractiveTag.NONE;
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
return canGiveGoldTo(itemInHand) ? InteractionResult.SUCCESS : InteractionResult.PASS;
}
}
private boolean canGiveGoldTo(@Nonnull GeyserItemStack itemInHand) {
return !getFlag(EntityFlag.BABY) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldIngot() && !getFlag(EntityFlag.ADMIRING);
}
}

View File

@ -65,4 +65,9 @@ public class ShulkerEntity extends GolemEntity {
dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15));
}
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -55,4 +55,14 @@ public class ZoglinEntity extends MonsterEntity {
float scale = getFlag(EntityFlag.BABY) ? 0.55f : 1f;
return scale * definition.height();
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -33,33 +33,56 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ZombieVillagerEntity extends ZombieEntity {
private boolean isTransforming;
public ZombieVillagerEntity(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);
}
public void setTransforming(BooleanEntityMetadata entityMetadata) {
isTransforming = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.IS_TRANSFORMING, isTransforming);
setFlag(EntityFlag.IS_TRANSFORMING, entityMetadata.getPrimitiveValue());
setFlag(EntityFlag.SHAKING, isShaking());
}
public void setZombieVillagerData(EntityMetadata<VillagerData, ?> entityMetadata) {
VillagerData villagerData = entityMetadata.getValue();
dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack
dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType()));
dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.getBedrockProfession(villagerData.getProfession())); // Actually works properly with the OptionalPack
dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.getBedrockRegion(villagerData.getType()));
// Used with the OptionalPack
dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
}
@Override
protected boolean isShaking() {
return isTransforming || super.isShaking();
return getFlag(EntityFlag.IS_TRANSFORMING) || super.isShaking();
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) {
return InteractiveTag.CURE;
} else {
return super.testMobInteraction(itemInHand);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) {
// The client doesn't know if the entity has weakness as that's not usually sent over the network
return InteractionResult.CONSUME;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
@ -38,6 +37,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.GameType;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
@ -61,15 +61,21 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Getter @Setter
public class PlayerEntity extends LivingEntity {
public static final float SNEAKING_POSE_HEIGHT = 1.5f;
private GameProfile profile;
private String username;
private boolean playerList = true; // Player is in the player list
private boolean playerList = true; // Player is in the player list
/**
* The textures property from the GameProfile.
*/
@Nullable
private String texturesProperty;
private Vector3i bedPosition;
@ -82,11 +88,12 @@ public class PlayerEntity extends LivingEntity {
*/
private ParrotEntity rightParrot;
public PlayerEntity(GeyserSession session, int entityId, long geyserId, GameProfile gameProfile, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, gameProfile.getId(), EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
public PlayerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, Vector3f position,
Vector3f motion, float yaw, float pitch, float headYaw, String username, @Nullable String texturesProperty) {
super(session, entityId, geyserId, uuid, EntityDefinitions.PLAYER, position, motion, yaw, pitch, headYaw);
profile = gameProfile;
username = gameProfile.getName();
this.username = username;
this.texturesProperty = texturesProperty;
}
@Override
@ -120,6 +127,7 @@ public class PlayerEntity extends LivingEntity {
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
addPlayerPacket.setDeviceId("");
addPlayerPacket.setPlatformChatId("");
addPlayerPacket.setGameType(GameType.SURVIVAL); //TODO
addPlayerPacket.getMetadata().putFlags(flags);
dirtyMetadata.apply(addPlayerPacket.getMetadata());
@ -205,7 +213,7 @@ public class PlayerEntity extends LivingEntity {
@Override
public void updateHeadLookRotation(float headYaw) {
moveRelative(0, 0, 0, yaw, pitch, headYaw, onGround);
moveRelative(0, 0, 0, getYaw(), getPitch(), headYaw, isOnGround());
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(geyserId);
movePlayerPacket.setPosition(position);
@ -225,9 +233,11 @@ public class PlayerEntity extends LivingEntity {
}
}
@Override
public void updateRotation(float yaw, float pitch, boolean isOnGround) {
super.updateRotation(yaw, pitch, isOnGround);
public void updateRotation(float yaw, float pitch, float headYaw, boolean isOnGround) {
// the method below is called by super.updateRotation(yaw, pitch, isOnGround).
// but we have to be able to set the headYaw, so we call the method below directly.
super.moveRelative(0, 0, 0, yaw, pitch, headYaw, isOnGround);
// Both packets need to be sent or else player head rotation isn't correctly updated
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(geyserId);
@ -244,6 +254,11 @@ public class PlayerEntity extends LivingEntity {
}
}
@Override
public void updateRotation(float yaw, float pitch, boolean isOnGround) {
updateRotation(yaw, pitch, getHeadYaw(), isOnGround);
}
@Override
public void setPosition(Vector3f position) {
super.setPosition(position.add(0, definition.offset(), 0));
@ -292,7 +307,7 @@ public class PlayerEntity extends LivingEntity {
}
// The parrot is a separate entity in Bedrock, but part of the player entity in Java //TODO is a UUID provided in NBT?
ParrotEntity parrot = new ParrotEntity(session, 0, session.getEntityCache().getNextEntityId().incrementAndGet(),
null, EntityDefinitions.PARROT, position, motion, yaw, pitch, headYaw);
null, EntityDefinitions.PARROT, position, motion, getYaw(), getPitch(), getHeadYaw());
parrot.spawnEntity();
parrot.getDirtyMetadata().put(EntityData.VARIANT, tag.get("Variant").getValue());
// Different position whether the parrot is left or right
@ -382,15 +397,26 @@ public class PlayerEntity extends LivingEntity {
@Override
protected void setDimensions(Pose pose) {
float height;
float width;
switch (pose) {
case SNEAKING -> height = SNEAKING_POSE_HEIGHT;
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> height = 0.6f;
case SNEAKING -> {
height = SNEAKING_POSE_HEIGHT;
width = definition.width();
}
case FALL_FLYING, SPIN_ATTACK, SWIMMING -> {
height = 0.6f;
width = definition.width();
}
case DYING -> {
height = 0.2f;
width = 0.2f;
}
default -> {
super.setDimensions(pose);
return;
}
}
setBoundingBoxWidth(definition.width());
setBoundingBoxWidth(width);
setBoundingBoxHeight(height);
}

View File

@ -25,7 +25,6 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.Attribute;
import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeType;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
@ -38,6 +37,7 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.AttributeUtils;
@ -70,7 +70,7 @@ public class SessionPlayerEntity extends PlayerEntity {
private int fakeTradeXp;
public SessionPlayerEntity(GeyserSession session) {
super(session, -1, 1, new GameProfile(UUID.randomUUID(), "unknown"), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0);
super(session, -1, 1, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "unknown", null);
valid = true;
}
@ -134,6 +134,10 @@ public class SessionPlayerEntity extends PlayerEntity {
return maxHealth;
}
public float getHealth() {
return this.health;
}
public void setHealth(float health) {
this.health = health;
}
@ -167,6 +171,16 @@ public class SessionPlayerEntity extends PlayerEntity {
return super.createHealthAttribute();
}
@Override
protected boolean hasShield(boolean offhand, ItemMapping shieldMapping) {
// Must be overridden to point to the player's inventory cache
if (offhand) {
return session.getPlayerInventory().getOffhand().getJavaId() == shieldMapping.getJavaId();
} else {
return session.getPlayerInventory().getItemInHand().getJavaId() == shieldMapping.getJavaId();
}
}
@Override
public void updateBedrockMetadata() {
super.updateBedrockMetadata();

View File

@ -25,32 +25,29 @@
package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.auth.data.GameProfile;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.GameType;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket;
import lombok.Getter;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.SkullSkinManager;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* A wrapper to handle skulls more effectively - skulls have to be treated as entities since there are no
* custom player skulls in Bedrock.
*/
public class SkullPlayerEntity extends PlayerEntity {
/**
* Stores the block state that the skull is associated with. Used to determine if the block in the skull's position
* has changed
*/
@Getter
private final int blockState;
public SkullPlayerEntity(GeyserSession session, long geyserId, GameProfile gameProfile, Vector3f position, float rotation, int blockState) {
super(session, 0, geyserId, gameProfile, position, Vector3f.ZERO, rotation, 0, rotation);
this.blockState = blockState;
public SkullPlayerEntity(GeyserSession session, long geyserId) {
super(session, 0, geyserId, UUID.randomUUID(), Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, "", null);
setPlayerList(false);
}
@ -83,6 +80,7 @@ public class SkullPlayerEntity extends PlayerEntity {
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
addPlayerPacket.setDeviceId("");
addPlayerPacket.setPlatformChatId("");
addPlayerPacket.setGameType(GameType.SURVIVAL);
addPlayerPacket.getMetadata().putFlags(flags);
dirtyMetadata.apply(addPlayerPacket.getMetadata());
@ -92,8 +90,57 @@ public class SkullPlayerEntity extends PlayerEntity {
session.sendUpstreamPacket(addPlayerPacket);
}
public void despawnEntity(Vector3i position) {
this.despawnEntity();
session.getSkullCache().remove(position, this);
/**
* Hide the player entity so that it can be reused for a different skull.
*/
public void free() {
setFlag(EntityFlag.INVISIBLE, true);
updateBedrockMetadata();
// Move skull entity out of the way
moveAbsolute(session.getPlayerEntity().getPosition().up(128), 0, 0, 0, false, true);
}
public void updateSkull(SkullCache.Skull skull) {
if (!skull.getTexturesProperty().equals(getTexturesProperty())) {
// Make skull invisible as we change skins
setFlag(EntityFlag.INVISIBLE, true);
updateBedrockMetadata();
setTexturesProperty(skull.getTexturesProperty());
SkullSkinManager.requestAndHandleSkin(this, session, (skin -> session.scheduleInEventLoop(() -> {
// Delay to minimize split-second "player" pop-in
setFlag(EntityFlag.INVISIBLE, false);
updateBedrockMetadata();
}, 250, TimeUnit.MILLISECONDS)));
} else {
// Just a rotation/position change
setFlag(EntityFlag.INVISIBLE, false);
updateBedrockMetadata();
}
float x = skull.getPosition().getX() + .5f;
float y = skull.getPosition().getY() - .01f;
float z = skull.getPosition().getZ() + .5f;
float rotation;
int blockState = skull.getBlockState();
byte floorRotation = BlockStateValues.getSkullRotation(blockState);
if (floorRotation == -1) {
// Wall skull
y += 0.25f;
rotation = BlockStateValues.getSkullWallDirections().get(blockState);
switch ((int) rotation) {
case 180 -> z += 0.24f; // North
case 0 -> z -= 0.24f; // South
case 90 -> x += 0.24f; // West
case 270 -> x -= 0.24f; // East
}
} else {
rotation = (180f + (floorRotation * 22.5f)) % 360;
}
moveAbsolute(Vector3f.from(x, y, z), rotation, 0, rotation, true, true);
}
}

Some files were not shown because too many files have changed in this diff Show More