Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-25 15:50:14 +01:00
Merge remote-tracking branch 'origin/feature/extensions' into feature/extensions-gradle
Dieser Commit ist enthalten in:
Commit
4455dc0ded
36
.github/workflows/sonarcloud.yml
vendored
Normale Datei
36
.github/workflows/sonarcloud.yml
vendored
Normale Datei
@ -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
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -239,8 +239,9 @@ nbdist/
|
||||
run/
|
||||
config.yml
|
||||
logs/
|
||||
public-key.pem
|
||||
key.pem
|
||||
locales/
|
||||
/cache/
|
||||
/packs/
|
||||
/dump.json
|
||||
/dump.json
|
||||
/saved-refresh-tokens.json
|
@ -17,7 +17,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
||||
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 and Minecraft Java 1.18/1.18.1.
|
||||
### Currently supporting Minecraft Bedrock 1.17.41 + 1.18.0 - 1.18.10 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.
|
||||
|
@ -32,27 +32,26 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
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.GeyserCommandManager;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
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.GeyserSpigotCommandExecutor;
|
||||
import org.geysermc.geyser.platform.spigot.command.GeyserSpigotCommandManager;
|
||||
import org.geysermc.geyser.platform.spigot.command.SpigotCommandSource;
|
||||
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;
|
||||
@ -236,11 +235,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
|
||||
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserPistonListener(geyser, this.geyserWorldManager), this);
|
||||
|
||||
if (isPre1_12) {
|
||||
// Register events needed to send all recipes to the client
|
||||
Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(geyser), this);
|
||||
}
|
||||
|
||||
this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(geyser));
|
||||
|
||||
// Check to ensure the current setup can support the protocol version Geyser uses
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -278,6 +278,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);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
@ -65,6 +67,7 @@ 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;
|
||||
@ -77,6 +80,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;
|
||||
@ -84,6 +90,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;
|
||||
@ -137,6 +144,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) {
|
||||
@ -271,6 +282,8 @@ public class GeyserImpl implements GeyserApi {
|
||||
}
|
||||
}
|
||||
|
||||
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication(config.getPendingAuthenticationTimeout());
|
||||
|
||||
this.newsHandler = new NewsHandler(BRANCH, BUILD_NUMBER);
|
||||
|
||||
CooldownUtils.setDefaultShowCooldown(config.getShowCooldown());
|
||||
@ -323,7 +336,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));
|
||||
@ -407,6 +420,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);
|
||||
|
||||
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
|
||||
@ -530,6 +584,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;
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ public interface GeyserConfiguration {
|
||||
|
||||
IRemoteConfiguration getRemote();
|
||||
|
||||
List<String> getSavedUserLogins();
|
||||
|
||||
@Deprecated
|
||||
Map<String, ? extends IUserAuthenticationInfo> getUserAuths();
|
||||
|
||||
boolean isCommandSuggestions();
|
||||
@ -100,6 +103,8 @@ public interface GeyserConfiguration {
|
||||
|
||||
IMetricsInfo getMetrics();
|
||||
|
||||
int getPendingAuthenticationTimeout();
|
||||
|
||||
interface IBedrockConfiguration {
|
||||
|
||||
String getAddress();
|
||||
|
@ -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";
|
||||
|
||||
@ -141,6 +144,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 {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -467,12 +468,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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -42,6 +43,8 @@ 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.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -205,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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -169,6 +174,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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
@ -64,4 +68,39 @@ public class MinecartEntity extends Entity {
|
||||
// Note: minecart rotation on rails does not care about the actual rotation value
|
||||
return Vector3f.from(0, yaw, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,7 @@ 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 java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@ -237,6 +240,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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,11 @@ public class SquidEntity extends WaterEntity implements Tickable {
|
||||
return Vector3f.from(pitch, yaw, yaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canBeLeashed() {
|
||||
return isNotLeashed();
|
||||
}
|
||||
|
||||
private void checkInWater() {
|
||||
if (getFlag(EntityFlag.RIDING)) {
|
||||
inWater = false;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 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
|
||||
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
|
||||
return testUndeadHorseInteraction(itemInHand);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
|
||||
return undeadHorseInteract(itemInHand);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -64,14 +64,21 @@ public class TameableEntity extends AnimalEntity {
|
||||
Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get());
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -150,6 +150,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
||||
return super.despawnEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
effectTick();
|
||||
@ -288,10 +293,6 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
|
||||
session.sendUpstreamPacket(playSoundPacket);
|
||||
}
|
||||
|
||||
private boolean isAlive() {
|
||||
return health > 0;
|
||||
}
|
||||
|
||||
private boolean isHovering() {
|
||||
return phase == 10;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -48,4 +48,9 @@ public class PhantomEntity extends FlyingEntity {
|
||||
setBoundingBoxHeight(boundsScale * definition.height());
|
||||
dirtyMetadata.put(EntityData.SCALE, modelScale);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -65,4 +65,9 @@ public class ShulkerEntity extends GolemEntity {
|
||||
dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnemy() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,15 +31,27 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class MerchantContainer extends Container {
|
||||
@Getter @Setter
|
||||
private Entity villager;
|
||||
@Setter
|
||||
private VillagerTrade[] villagerTrades;
|
||||
@Getter @Setter
|
||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||
|
||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
}
|
||||
|
||||
public void onTradeSelected(GeyserSession session, int slot) {
|
||||
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
|
||||
VillagerTrade trade = villagerTrades[slot];
|
||||
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||
// TODO this logic doesn't add up
|
||||
session.getPlayerEntity().addFakeTradeExperience(trade.getXp());
|
||||
session.getPlayerEntity().updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.inventory;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
@ -61,6 +62,10 @@ public class PlayerInventory extends Inventory {
|
||||
cursor = newCursor;
|
||||
}
|
||||
|
||||
public GeyserItemStack getItemInHand(@Nonnull Hand hand) {
|
||||
return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand();
|
||||
}
|
||||
|
||||
public GeyserItemStack getItemInHand() {
|
||||
if (36 + heldItemSlot > this.size) {
|
||||
GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");
|
||||
|
@ -41,16 +41,27 @@ public class StoredItemMappings {
|
||||
private final ItemMapping bamboo;
|
||||
private final ItemMapping banner;
|
||||
private final ItemMapping barrier;
|
||||
private final int bowl;
|
||||
private final int chest;
|
||||
private final ItemMapping compass;
|
||||
private final ItemMapping crossbow;
|
||||
private final ItemMapping enchantedBook;
|
||||
private final ItemMapping fishingRod;
|
||||
private final int flintAndSteel;
|
||||
private final int goldenApple;
|
||||
private final int goldIngot;
|
||||
private final int ironIngot;
|
||||
private final int lead;
|
||||
private final ItemMapping lodestoneCompass;
|
||||
private final ItemMapping milkBucket;
|
||||
private final int nameTag;
|
||||
private final ItemMapping powderSnowBucket;
|
||||
private final ItemMapping playerHead;
|
||||
private final ItemMapping egg;
|
||||
private final int saddle;
|
||||
private final int shears;
|
||||
private final ItemMapping shield;
|
||||
private final int waterBucket;
|
||||
private final ItemMapping wheat;
|
||||
private final ItemMapping writableBook;
|
||||
|
||||
@ -58,16 +69,27 @@ public class StoredItemMappings {
|
||||
this.bamboo = load(itemMappings, "bamboo");
|
||||
this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID
|
||||
this.barrier = load(itemMappings, "barrier");
|
||||
this.bowl = load(itemMappings, "bowl").getJavaId();
|
||||
this.chest = load(itemMappings, "chest").getJavaId();
|
||||
this.compass = load(itemMappings, "compass");
|
||||
this.crossbow = load(itemMappings, "crossbow");
|
||||
this.enchantedBook = load(itemMappings, "enchanted_book");
|
||||
this.fishingRod = load(itemMappings, "fishing_rod");
|
||||
this.flintAndSteel = load(itemMappings, "flint_and_steel").getJavaId();
|
||||
this.goldenApple = load(itemMappings, "golden_apple").getJavaId();
|
||||
this.goldIngot = load(itemMappings, "gold_ingot").getJavaId();
|
||||
this.ironIngot = load(itemMappings, "iron_ingot").getJavaId();
|
||||
this.lead = load(itemMappings, "lead").getJavaId();
|
||||
this.lodestoneCompass = load(itemMappings, "lodestone_compass");
|
||||
this.milkBucket = load(itemMappings, "milk_bucket");
|
||||
this.nameTag = load(itemMappings, "name_tag").getJavaId();
|
||||
this.powderSnowBucket = load(itemMappings, "powder_snow_bucket");
|
||||
this.playerHead = load(itemMappings, "player_head");
|
||||
this.egg = load(itemMappings, "egg");
|
||||
this.saddle = load(itemMappings, "saddle").getJavaId();
|
||||
this.shears = load(itemMappings, "shears").getJavaId();
|
||||
this.shield = load(itemMappings, "shield");
|
||||
this.waterBucket = load(itemMappings, "water_bucket").getJavaId();
|
||||
this.wheat = load(itemMappings, "wheat");
|
||||
this.writableBook = load(itemMappings, "writable_book");
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.inventory.recipe;
|
||||
|
||||
/**
|
||||
* A more compact version of {@link com.github.steveice10.mc.protocol.data.game.recipe.Recipe}.
|
||||
*/
|
||||
public interface GeyserRecipe {
|
||||
/**
|
||||
* Whether the recipe is flexible or not in which items can be placed where.
|
||||
*/
|
||||
boolean isShaped();
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.inventory.recipe;
|
||||
|
||||
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;
|
||||
|
||||
public record GeyserShapedRecipe(int width, int height, Ingredient[] ingredients, ItemStack result) implements GeyserRecipe {
|
||||
|
||||
public GeyserShapedRecipe(ShapedRecipeData data) {
|
||||
this(data.getWidth(), data.getHeight(), data.getIngredients(), data.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShaped() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.inventory.recipe;
|
||||
|
||||
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.data.ShapelessRecipeData;
|
||||
|
||||
public record GeyserShapelessRecipe(Ingredient[] ingredients, ItemStack result) implements GeyserRecipe {
|
||||
|
||||
public GeyserShapelessRecipe(ShapelessRecipeData data) {
|
||||
this(data.getIngredients(), data.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShaped() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -38,6 +38,8 @@ import org.geysermc.geyser.util.collection.FixedInt2ByteMap;
|
||||
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
|
||||
import org.geysermc.geyser.util.collection.LecternHasBookMap;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Used for block entities if the Java block state contains Bedrock block information.
|
||||
*/
|
||||
@ -47,6 +49,7 @@ public final class BlockStateValues {
|
||||
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
|
||||
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final IntSet HORIZONTAL_FACING_JIGSAWS = new IntOpenHashSet();
|
||||
private static final LecternHasBookMap LECTERN_BOOK_STATES = new LecternHasBookMap();
|
||||
private static final Int2IntMap NOTEBLOCK_PITCHES = new FixedInt2IntMap();
|
||||
private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap();
|
||||
@ -170,12 +173,22 @@ public final class BlockStateValues {
|
||||
JsonNode shulkerDirection = blockData.get("shulker_direction");
|
||||
if (shulkerDirection != null) {
|
||||
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
|
||||
return;
|
||||
}
|
||||
|
||||
if (javaId.startsWith("minecraft:water")) {
|
||||
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
|
||||
int level = Integer.parseInt(strLevel);
|
||||
WATER_LEVEL.put(javaBlockState, level);
|
||||
return;
|
||||
}
|
||||
|
||||
if (javaId.startsWith("minecraft:jigsaw[orientation=")) {
|
||||
String blockStateData = javaId.substring(javaId.indexOf("orientation=") + "orientation=".length(), javaId.lastIndexOf('_'));
|
||||
Direction direction = Direction.valueOf(blockStateData.toUpperCase(Locale.ROOT));
|
||||
if (direction.isHorizontal()) {
|
||||
HORIZONTAL_FACING_JIGSAWS.add(javaBlockState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,6 +243,13 @@ public final class BlockStateValues {
|
||||
return FLOWER_POT_VALUES;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a set of all forward-facing jigsaws, to use as a fallback if NBT is missing.
|
||||
*/
|
||||
public static IntSet getHorizontalFacingJigsaws() {
|
||||
return HORIZONTAL_FACING_JIGSAWS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lectern book state map pointing to book present state
|
||||
*/
|
||||
|
@ -32,10 +32,7 @@ import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Contains information about the supported protocols in Geyser.
|
||||
@ -60,7 +57,9 @@ public final class MinecraftProtocol {
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v471.V471_CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v475.V475_CODEC.toBuilder().minecraftVersion("1.18.0/1.18.1/1.18.2").build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
.minecraftVersion("1.18.10/1.18.12") // 1.18.11 is also supported, but was only on Switch and since that auto-updates it's not needed
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,7 +91,7 @@ public final class MinecraftProtocol {
|
||||
* @return the supported Minecraft: Java Edition version names
|
||||
*/
|
||||
public static List<String> getJavaVersions() {
|
||||
return Arrays.asList("1.18", "1.18.1");
|
||||
return Collections.singletonList(DEFAULT_JAVA_CODEC.getMinecraftVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.ResourcePackType;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||
import org.geysermc.geyser.session.auth.AuthType;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -73,11 +74,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
String supportedVersions = MinecraftProtocol.getAllSupportedBedrockVersions();
|
||||
if (loginPacket.getProtocolVersion() > MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
// Too early to determine session locale
|
||||
session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions));
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.server", supportedVersions));
|
||||
return true;
|
||||
} else if (loginPacket.getProtocolVersion() < MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()) {
|
||||
session.getGeyser().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", supportedVersions));
|
||||
return true;
|
||||
}
|
||||
@ -189,6 +188,14 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
}
|
||||
|
||||
private boolean couldLoginUserByName(String bedrockUsername) {
|
||||
if (geyser.getConfig().getSavedUserLogins().contains(bedrockUsername)) {
|
||||
String refreshToken = geyser.refreshTokenFor(bedrockUsername);
|
||||
if (refreshToken != null) {
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().name()));
|
||||
session.authenticateWithRefreshToken(refreshToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (geyser.getConfig().getUserAuths() != null) {
|
||||
GeyserConfiguration.IUserAuthenticationInfo info = geyser.getConfig().getUserAuths().get(bedrockUsername);
|
||||
|
||||
@ -199,6 +206,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(session.getAuthData().xuid());
|
||||
if (task != null) {
|
||||
if (task.getAuthentication().isDone() && session.onMicrosoftLoginComplete(task)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType;
|
||||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import com.nukkitx.nbt.NbtMap;
|
||||
@ -45,6 +44,7 @@ import net.kyori.adventure.key.Key;
|
||||
import org.geysermc.geyser.api.extension.ExtensionLoader;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.registry.loader.*;
|
||||
import org.geysermc.geyser.registry.populator.ItemRegistryPopulator;
|
||||
import org.geysermc.geyser.registry.populator.PacketRegistryPopulator;
|
||||
@ -146,9 +146,9 @@ public final class Registries {
|
||||
public static final SimpleRegistry<Set<PotionMixData>> POTION_MIXES;
|
||||
|
||||
/**
|
||||
* A versioned registry holding all the recipes, with the net ID being the key, and {@link Recipe} as the value.
|
||||
* A versioned registry holding all the recipes, with the net ID being the key, and {@link GeyserRecipe} as the value.
|
||||
*/
|
||||
public static final VersionedRegistry<Int2ObjectMap<Recipe>> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
public static final VersionedRegistry<Int2ObjectMap<GeyserRecipe>> RECIPES = VersionedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A mapped registry holding the available records, with the ID of the record being the key, and the {@link com.nukkitx.protocol.bedrock.data.SoundEvent}
|
||||
|
@ -38,10 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
|
||||
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
|
||||
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.ints.*;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
@ -49,6 +46,8 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.*;
|
||||
import org.geysermc.geyser.util.ItemUtils;
|
||||
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
@ -84,6 +83,10 @@ public class ItemRegistryPopulator {
|
||||
throw new AssertionError("Unable to load Java runtime item IDs", e);
|
||||
}
|
||||
|
||||
// We can reduce some operations as Java information is the same across all palette versions
|
||||
boolean firstMappingsPass = true;
|
||||
Int2IntMap dyeColors = new FixedInt2IntMap();
|
||||
|
||||
/* Load item palette */
|
||||
for (Map.Entry<String, PaletteVersion> palette : PALETTE_VERSIONS.entrySet()) {
|
||||
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
|
||||
@ -224,8 +227,14 @@ public class ItemRegistryPopulator {
|
||||
// This items has a mapping specifically for this version of the game
|
||||
mappingItem = entry.getValue();
|
||||
}
|
||||
|
||||
String bedrockIdentifier;
|
||||
if (javaIdentifier.equals("minecraft:music_disc_otherside") && palette.getValue().protocolVersion() <= Bedrock_v471.V471_CODEC.getProtocolVersion()) {
|
||||
mappingItem.setBedrockIdentifier("minecraft:music_disc_pigstep");
|
||||
bedrockIdentifier = "minecraft:music_disc_pigstep";
|
||||
} else if (javaIdentifier.equals("minecraft:globe_banner_pattern") && palette.getValue().protocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) {
|
||||
bedrockIdentifier = "minecraft:banner_pattern";
|
||||
} else {
|
||||
bedrockIdentifier = mappingItem.getBedrockIdentifier();
|
||||
}
|
||||
|
||||
if (usingFurnaceMinecart && javaIdentifier.equals("minecraft:furnace_minecart")) {
|
||||
@ -233,7 +242,7 @@ public class ItemRegistryPopulator {
|
||||
itemIndex++;
|
||||
continue;
|
||||
}
|
||||
String bedrockIdentifier = mappingItem.getBedrockIdentifier().intern();
|
||||
|
||||
int bedrockId = bedrockIdentifierToId.getInt(bedrockIdentifier);
|
||||
if (bedrockId == Short.MIN_VALUE) {
|
||||
throw new RuntimeException("Missing Bedrock ID in mappings: " + bedrockIdentifier);
|
||||
@ -358,12 +367,13 @@ public class ItemRegistryPopulator {
|
||||
ItemMapping.ItemMappingBuilder mappingBuilder = ItemMapping.builder()
|
||||
.javaIdentifier(javaIdentifier)
|
||||
.javaId(itemIndex)
|
||||
.bedrockIdentifier(bedrockIdentifier)
|
||||
.bedrockIdentifier(bedrockIdentifier.intern())
|
||||
.bedrockId(bedrockId)
|
||||
.bedrockData(mappingItem.getBedrockData())
|
||||
.bedrockBlockId(bedrockBlockId)
|
||||
.stackSize(stackSize)
|
||||
.maxDamage(mappingItem.getMaxDamage());
|
||||
.maxDamage(mappingItem.getMaxDamage())
|
||||
.hasSuspiciousStewEffect(mappingItem.isHasSuspiciousStewEffect());
|
||||
|
||||
if (mappingItem.getRepairMaterials() != null) {
|
||||
mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials()));
|
||||
@ -411,6 +421,10 @@ public class ItemRegistryPopulator {
|
||||
|
||||
itemNames.add(javaIdentifier);
|
||||
|
||||
if (firstMappingsPass && mappingItem.getDyeColor() != -1) {
|
||||
dyeColors.put(itemIndex, mappingItem.getDyeColor());
|
||||
}
|
||||
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
@ -506,6 +520,10 @@ public class ItemRegistryPopulator {
|
||||
.build();
|
||||
|
||||
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);
|
||||
|
||||
firstMappingsPass = false;
|
||||
}
|
||||
|
||||
ItemUtils.setDyeColors(dyeColors);
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,7 @@ package org.geysermc.geyser.registry.populator;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
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.Recipe;
|
||||
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.nbt.NbtMap;
|
||||
import com.nukkitx.nbt.NbtUtils;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
|
||||
@ -40,6 +37,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
@ -71,7 +71,7 @@ public class RecipeRegistryPopulator {
|
||||
// Make a bit of an assumption here that the last recipe net ID will be equivalent between all versions
|
||||
LAST_RECIPE_NET_ID = currentRecipeId;
|
||||
Map<RecipeType, List<CraftingData>> craftingData = new EnumMap<>(RecipeType.class);
|
||||
Int2ObjectMap<Recipe> recipes = new Int2ObjectOpenHashMap<>();
|
||||
Int2ObjectMap<GeyserRecipe> recipes = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
craftingData.put(RecipeType.CRAFTING_SPECIAL_BOOKCLONING,
|
||||
Collections.singletonList(CraftingData.fromMulti(UUID.fromString("d1ca6b84-338e-4f2f-9c6b-76cc8b4bd98d"), ++LAST_RECIPE_NET_ID)));
|
||||
@ -124,7 +124,7 @@ public class RecipeRegistryPopulator {
|
||||
* @param recipes a list of all the recipes
|
||||
* @return the {@link CraftingData} to send to the Bedrock client.
|
||||
*/
|
||||
private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap<Recipe> recipes, ItemMappings mappings) {
|
||||
private static CraftingData getCraftingDataFromJsonNode(JsonNode node, Int2ObjectMap<GeyserRecipe> recipes, ItemMappings mappings) {
|
||||
int netId = ++LAST_RECIPE_NET_ID;
|
||||
int type = node.get("bedrockRecipeType").asInt();
|
||||
JsonNode outputNode = node.get("output");
|
||||
@ -165,9 +165,8 @@ public class RecipeRegistryPopulator {
|
||||
for (ItemData input : inputs) {
|
||||
ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)}));
|
||||
}
|
||||
ShapedRecipeData data = new ShapedRecipeData(shape.get(0).length(), shape.size(), "crafting_table",
|
||||
GeyserRecipe recipe = new GeyserShapedRecipe(shape.get(0).length(), shape.size(),
|
||||
ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings));
|
||||
Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPED, "", data);
|
||||
recipes.put(netId, recipe);
|
||||
/* Convert end */
|
||||
|
||||
@ -185,9 +184,7 @@ public class RecipeRegistryPopulator {
|
||||
for (ItemData input : inputs) {
|
||||
ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input, mappings)}));
|
||||
}
|
||||
ShapelessRecipeData data = new ShapelessRecipeData("crafting_table",
|
||||
ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings));
|
||||
Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPELESS, "", data);
|
||||
GeyserRecipe recipe = new GeyserShapelessRecipe(ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output, mappings));
|
||||
recipes.put(netId, recipe);
|
||||
/* Convert end */
|
||||
|
||||
|
@ -44,4 +44,6 @@ public class GeyserMappingItem {
|
||||
@JsonProperty("tool_tier") String toolTier;
|
||||
@JsonProperty("max_damage") int maxDamage = 0;
|
||||
@JsonProperty("repair_materials") List<String> repairMaterials;
|
||||
@JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false;
|
||||
@JsonProperty("dye_color") int dyeColor = -1;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ import java.util.Set;
|
||||
public class ItemMapping {
|
||||
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
|
||||
BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(),
|
||||
64, null, null, null, 0, null);
|
||||
64, null, null, null, 0, null, false);
|
||||
|
||||
String javaIdentifier;
|
||||
String bedrockIdentifier;
|
||||
@ -63,6 +63,8 @@ public class ItemMapping {
|
||||
|
||||
Set<String> repairMaterials;
|
||||
|
||||
boolean hasSuspiciousStewEffect;
|
||||
|
||||
/**
|
||||
* Gets if this item is a block.
|
||||
*
|
||||
|
@ -26,7 +26,6 @@
|
||||
package org.geysermc.geyser.session;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
|
||||
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.auth.service.AuthenticationService;
|
||||
@ -39,7 +38,6 @@ import com.github.steveice10.mc.protocol.data.UnexpectedEncryptionException;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.HandPreference;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.ChatVisibility;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.SkinPart;
|
||||
import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic;
|
||||
@ -85,7 +83,6 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
|
||||
import org.geysermc.geyser.entity.InteractiveTagManager;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
@ -94,6 +91,7 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.network.netty.LocalSession;
|
||||
@ -114,13 +112,13 @@ import org.geysermc.geyser.util.DimensionUtils;
|
||||
import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -128,13 +126,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
@Getter
|
||||
public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
private final GeyserImpl geyser;
|
||||
private final UpstreamSession upstream;
|
||||
private final @Nonnull GeyserImpl geyser;
|
||||
private final @Nonnull UpstreamSession upstream;
|
||||
/**
|
||||
* The loop where all packets and ticking is processed to prevent concurrency issues.
|
||||
* If this is manually called, ensure that any exceptions are properly handled.
|
||||
*/
|
||||
private final EventLoop eventLoop;
|
||||
private final @Nonnull EventLoop eventLoop;
|
||||
private TcpSession downstream;
|
||||
@Setter
|
||||
private AuthData authData;
|
||||
@ -350,7 +348,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
private Entity mouseoverEntity;
|
||||
|
||||
@Setter
|
||||
private Int2ObjectMap<Recipe> craftingRecipes;
|
||||
private Int2ObjectMap<GeyserRecipe> craftingRecipes;
|
||||
private final Set<String> unlockedRecipes;
|
||||
private final AtomicInteger lastRecipeNetId;
|
||||
|
||||
@ -361,6 +359,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
@Setter
|
||||
private Int2ObjectMap<IntList> stonecutterRecipes;
|
||||
|
||||
/**
|
||||
* Whether to work around 1.13's different behavior in villager trading menus.
|
||||
*/
|
||||
@Setter
|
||||
private boolean emulatePost1_14Logic = true;
|
||||
/**
|
||||
* Starting in 1.17, Java servers expect the <code>carriedItem</code> parameter of the serverbound click container
|
||||
* packet to be the current contents of the mouse after the transaction has been done. 1.16 expects the clicked slot
|
||||
@ -444,6 +447,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
*/
|
||||
private boolean flying = false;
|
||||
|
||||
@Setter
|
||||
private boolean instabuild = false;
|
||||
|
||||
/**
|
||||
* Caches current rain status.
|
||||
*/
|
||||
@ -542,11 +548,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
}
|
||||
|
||||
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
|
||||
InetAddress address = bedrockServerSession.getRealAddress().getAddress();
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason));
|
||||
String message = switch (disconnectReason) {
|
||||
// A generic message that just means the player quit normally.
|
||||
case CLOSED_BY_REMOTE_PEER -> GeyserLocale.getLocaleStringLog("geyser.network.disconnect.closed_by_remote_peer");
|
||||
case TIMED_OUT -> GeyserLocale.getLocaleStringLog("geyser.network.disconnect.timed_out");
|
||||
default -> disconnectReason.name();
|
||||
};
|
||||
|
||||
disconnect(disconnectReason.name());
|
||||
geyser.getSessionManager().removeSession(this);
|
||||
disconnect(message);
|
||||
});
|
||||
|
||||
this.remoteAddress = geyser.getConfig().getRemote().getAddress();
|
||||
@ -632,7 +641,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
loggingIn = true;
|
||||
|
||||
// Use a future to prevent timeouts as all the authentication is handled sync
|
||||
// This will be changed with the new protocol library.
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
if (password != null && !password.isEmpty()) {
|
||||
@ -689,10 +697,58 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
});
|
||||
}
|
||||
|
||||
public void authenticateWithRefreshToken(String refreshToken) {
|
||||
if (loggedIn) {
|
||||
geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
|
||||
return;
|
||||
}
|
||||
|
||||
loggingIn = true;
|
||||
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
MsaAuthenticationService service = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
||||
service.setRefreshToken(refreshToken);
|
||||
try {
|
||||
service.login();
|
||||
} catch (RequestException e) {
|
||||
geyser.getLogger().error("Error while attempting to use refresh token for " + name() + "!", e);
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
GameProfile profile = service.getSelectedProfile();
|
||||
if (profile == null) {
|
||||
// Java account is offline
|
||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
||||
return null;
|
||||
}
|
||||
|
||||
protocol = new MinecraftProtocol(profile, service.getAccessToken());
|
||||
geyser.saveRefreshToken(name(), service.getRefreshToken());
|
||||
return Boolean.TRUE;
|
||||
}).whenComplete((successful, ex) -> {
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
if (successful == Boolean.FALSE) {
|
||||
// The player is waiting for a spawn packet, so let's spawn them in now to show them forms
|
||||
connect();
|
||||
// Will be cached for after login
|
||||
LoginEncryptionUtils.buildAndShowTokenExpiredWindow(this);
|
||||
return;
|
||||
}
|
||||
|
||||
connectDownstream();
|
||||
});
|
||||
}
|
||||
|
||||
public void authenticateWithMicrosoftCode() {
|
||||
authenticateWithMicrosoftCode(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a form window to the user asking to log in with another web browser
|
||||
*/
|
||||
public void authenticateWithMicrosoftCode() {
|
||||
public void authenticateWithMicrosoftCode(boolean offlineAccess) {
|
||||
if (loggedIn) {
|
||||
geyser.getLogger().severe(GeyserLocale.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().name()));
|
||||
return;
|
||||
@ -705,65 +761,64 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
packet.setTime(16000);
|
||||
sendUpstreamPacket(packet);
|
||||
|
||||
// new thread so clients don't timeout
|
||||
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
||||
final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask(
|
||||
getAuthData().xuid()
|
||||
);
|
||||
task.setOnline(true);
|
||||
task.resetTimer();
|
||||
|
||||
// Use a future to prevent timeouts as all the authentication is handled sync
|
||||
// This will be changed with the new protocol library.
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return msaAuthenticationService.getAuthCode();
|
||||
} catch (RequestException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}).whenComplete((response, ex) -> {
|
||||
if (ex != null) {
|
||||
ex.printStackTrace();
|
||||
disconnect(ex.toString());
|
||||
return;
|
||||
}
|
||||
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
||||
attemptCodeAuthentication(msaAuthenticationService);
|
||||
});
|
||||
if (task.getAuthentication().isDone()) {
|
||||
onMicrosoftLoginComplete(task);
|
||||
} else {
|
||||
task.getCode(offlineAccess).whenComplete((response, ex) -> {
|
||||
boolean connected = !closed;
|
||||
if (ex != null) {
|
||||
if (connected) {
|
||||
geyser.getLogger().error("Failed to get Microsoft auth code", ex);
|
||||
disconnect(ex.toString());
|
||||
}
|
||||
task.cleanup(); // error getting auth code -> clean up immediately
|
||||
} else if (connected) {
|
||||
LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
|
||||
task.getAuthentication().whenComplete((r, $) -> onMicrosoftLoginComplete(task));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll every second to see if the user has successfully signed in
|
||||
* If successful, also begins connecting to the Java server.
|
||||
*/
|
||||
private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) {
|
||||
if (loggedIn || closed) {
|
||||
return;
|
||||
public boolean onMicrosoftLoginComplete(PendingMicrosoftAuthentication.AuthenticationTask task) {
|
||||
if (closed) {
|
||||
return false;
|
||||
}
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
msaAuthenticationService.login();
|
||||
GameProfile profile = msaAuthenticationService.getSelectedProfile();
|
||||
if (profile == null) {
|
||||
// Java account is offline
|
||||
disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
||||
return null;
|
||||
}
|
||||
|
||||
return new MinecraftProtocol(profile, msaAuthenticationService.getAccessToken());
|
||||
} catch (RequestException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}).whenComplete((response, ex) -> {
|
||||
if (ex != null) {
|
||||
if (!(ex instanceof CompletionException completionException) || !(completionException.getCause() instanceof AuthPendingException)) {
|
||||
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
||||
disconnect(ex.toString());
|
||||
} else {
|
||||
// Wait one second before trying again
|
||||
geyser.getScheduledThread().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!closed) {
|
||||
this.protocol = response;
|
||||
task.cleanup(); // player is online -> remove pending authentication immediately
|
||||
Throwable ex = task.getLoginException();
|
||||
if (ex != null) {
|
||||
geyser.getLogger().error("Failed to log in with Microsoft code!", ex);
|
||||
disconnect(ex.toString());
|
||||
} else {
|
||||
MsaAuthenticationService service = task.getMsaAuthenticationService();
|
||||
GameProfile selectedProfile = service.getSelectedProfile();
|
||||
if (selectedProfile == null) {
|
||||
disconnect(GeyserLocale.getPlayerLocaleString(
|
||||
"geyser.network.remote.invalid_account",
|
||||
clientData.getLanguageCode()
|
||||
));
|
||||
} else {
|
||||
this.protocol = new MinecraftProtocol(
|
||||
selectedProfile,
|
||||
service.getAccessToken()
|
||||
);
|
||||
connectDownstream();
|
||||
|
||||
// Save our refresh token for later use
|
||||
geyser.saveRefreshToken(name(), service.getRefreshToken());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -958,11 +1013,22 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
loggedIn = false;
|
||||
if (downstream != null) {
|
||||
downstream.disconnect(reason);
|
||||
} else {
|
||||
// Downstream's disconnect will fire an event that prints a log message
|
||||
// Otherwise, we print a message here
|
||||
InetAddress address = upstream.getAddress().getAddress();
|
||||
geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.disconnect", address, reason));
|
||||
}
|
||||
if (upstream != null && !upstream.isClosed()) {
|
||||
geyser.getSessionManager().removeSession(this);
|
||||
if (!upstream.isClosed()) {
|
||||
upstream.disconnect(reason);
|
||||
}
|
||||
geyser.getSessionManager().removeSession(this);
|
||||
if (authData != null) {
|
||||
PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getTask(authData.xuid());
|
||||
if (task != null) {
|
||||
task.setOnline(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tickThread != null) {
|
||||
@ -1076,7 +1142,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
|
||||
if (mouseoverEntity != null) {
|
||||
// Horses, etc can change their property depending on if you're sneaking
|
||||
InteractiveTagManager.updateTag(this, mouseoverEntity);
|
||||
mouseoverEntity.updateInteractiveTag();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1526,4 +1592,17 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
packet.getFogStack().addAll(this.fogNameSpaces);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
public boolean canUseCommandBlocks() {
|
||||
return instabuild && opPermissionLevel >= 2;
|
||||
}
|
||||
|
||||
public void playSoundEvent(SoundEvent sound, Vector3f position) {
|
||||
LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet();
|
||||
packet.setPosition(position);
|
||||
packet.setSound(sound);
|
||||
packet.setIdentifier(":");
|
||||
packet.setExtraData(-1);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.session;
|
||||
|
||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* Pending Microsoft authentication task cache.
|
||||
* It permits user to exit the server while they authorize Geyser to access their Microsoft account.
|
||||
*/
|
||||
public class PendingMicrosoftAuthentication {
|
||||
/**
|
||||
* For GeyserConnect usage.
|
||||
*/
|
||||
private boolean storeServerInformation = false;
|
||||
private final LoadingCache<String, AuthenticationTask> authentications;
|
||||
|
||||
public PendingMicrosoftAuthentication(int timeoutSeconds) {
|
||||
this.authentications = CacheBuilder.newBuilder()
|
||||
.build(new CacheLoader<>() {
|
||||
@Override
|
||||
public AuthenticationTask load(@NonNull String userKey) {
|
||||
return storeServerInformation ? new ProxyAuthenticationTask(userKey, timeoutSeconds * 1000L)
|
||||
: new AuthenticationTask(userKey, timeoutSeconds * 1000L);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AuthenticationTask getTask(@Nonnull String userKey) {
|
||||
return authentications.getIfPresent(userKey);
|
||||
}
|
||||
|
||||
@SneakyThrows(ExecutionException.class)
|
||||
public AuthenticationTask getOrCreateTask(@Nonnull String userKey) {
|
||||
return authentications.get(userKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // GeyserConnect
|
||||
public void setStoreServerInformation() {
|
||||
storeServerInformation = true;
|
||||
}
|
||||
|
||||
public class AuthenticationTask {
|
||||
private static final Executor DELAYED_BY_ONE_SECOND = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);
|
||||
|
||||
@Getter
|
||||
private final MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserImpl.OAUTH_CLIENT_ID);
|
||||
private final String userKey;
|
||||
private final long timeoutMs;
|
||||
|
||||
private long remainingTimeMs;
|
||||
|
||||
@Setter
|
||||
private boolean online = true;
|
||||
|
||||
@Getter
|
||||
private final CompletableFuture<MsaAuthenticationService> authentication;
|
||||
|
||||
@Getter
|
||||
private volatile Throwable loginException;
|
||||
|
||||
private AuthenticationTask(String userKey, long timeoutMs) {
|
||||
this.userKey = userKey;
|
||||
this.timeoutMs = timeoutMs;
|
||||
this.remainingTimeMs = timeoutMs;
|
||||
|
||||
this.authentication = new CompletableFuture<>();
|
||||
this.authentication.whenComplete((r, ex) -> {
|
||||
this.loginException = ex;
|
||||
// avoid memory leak, in case player doesn't connect again
|
||||
CompletableFuture.delayedExecutor(timeoutMs, TimeUnit.MILLISECONDS).execute(this::cleanup);
|
||||
});
|
||||
}
|
||||
|
||||
public void resetTimer() {
|
||||
this.remainingTimeMs = this.timeoutMs;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
GeyserLogger logger = GeyserImpl.getInstance().getLogger();
|
||||
if (logger.isDebug()) {
|
||||
logger.debug("Cleaning up authentication task for " + userKey);
|
||||
}
|
||||
authentications.invalidate(userKey);
|
||||
}
|
||||
|
||||
public CompletableFuture<MsaAuthenticationService.MsCodeResponse> getCode(boolean offlineAccess) {
|
||||
// Request the code
|
||||
CompletableFuture<MsaAuthenticationService.MsCodeResponse> code = CompletableFuture.supplyAsync(() -> tryGetCode(offlineAccess));
|
||||
// Once the code is received, continuously try to request the access token, profile, etc
|
||||
code.thenRun(() -> performLoginAttempt(System.currentTimeMillis()));
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param offlineAccess whether we want a refresh token for later use.
|
||||
*/
|
||||
private MsaAuthenticationService.MsCodeResponse tryGetCode(boolean offlineAccess) throws CompletionException {
|
||||
try {
|
||||
return msaAuthenticationService.getAuthCode(offlineAccess);
|
||||
} catch (RequestException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void performLoginAttempt(long lastAttempt) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
msaAuthenticationService.login();
|
||||
} catch (AuthPendingException e) {
|
||||
long currentAttempt = System.currentTimeMillis();
|
||||
if (!online) {
|
||||
// decrement timer only when player's offline
|
||||
remainingTimeMs -= currentAttempt - lastAttempt;
|
||||
if (remainingTimeMs <= 0L) {
|
||||
// time's up
|
||||
authentication.completeExceptionally(new TaskTimeoutException());
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// try again in 1 second
|
||||
performLoginAttempt(currentAttempt);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
authentication.completeExceptionally(e);
|
||||
return;
|
||||
}
|
||||
// login successful
|
||||
authentication.complete(msaAuthenticationService);
|
||||
}, DELAYED_BY_ONE_SECOND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{userKey='" + userKey + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public final class ProxyAuthenticationTask extends AuthenticationTask {
|
||||
private String server;
|
||||
private int port;
|
||||
|
||||
private ProxyAuthenticationTask(String userKey, long timeoutMs) {
|
||||
super(userKey, timeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PendingMicrosoftAuthentication
|
||||
*/
|
||||
public static class TaskTimeoutException extends Exception {
|
||||
TaskTimeoutException() {
|
||||
super("It took too long to authorize Geyser to access your Microsoft account. " +
|
||||
"Please request new code and try again.");
|
||||
}
|
||||
}
|
||||
}
|
@ -28,15 +28,19 @@ package org.geysermc.geyser.session.cache;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.ints.IntLists;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.registry.type.BlockMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages information sent from the {@link ClientboundUpdateTagsPacket}. If that packet is not sent, all lists here
|
||||
* will remain empty, matching Java Edition behavior.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public class TagCache {
|
||||
/* Blocks */
|
||||
private IntList leaves;
|
||||
@ -52,16 +56,19 @@ public class TagCache {
|
||||
private IntList requiresDiamondTool;
|
||||
|
||||
/* Items */
|
||||
private IntList axolotlTemptItems;
|
||||
private IntList fishes;
|
||||
private IntList flowers;
|
||||
private IntList foxFood;
|
||||
private IntList piglinLoved;
|
||||
private IntList smallFlowers;
|
||||
|
||||
public TagCache() {
|
||||
// Ensure all lists are non-null
|
||||
clear();
|
||||
}
|
||||
|
||||
public void loadPacket(ClientboundUpdateTagsPacket packet) {
|
||||
public void loadPacket(GeyserSession session, ClientboundUpdateTagsPacket packet) {
|
||||
Map<String, int[]> blockTags = packet.getTags().get("minecraft:block");
|
||||
this.leaves = IntList.of(blockTags.get("minecraft:leaves"));
|
||||
this.wool = IntList.of(blockTags.get("minecraft:wool"));
|
||||
@ -76,9 +83,19 @@ public class TagCache {
|
||||
this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool"));
|
||||
|
||||
Map<String, int[]> itemTags = packet.getTags().get("minecraft:item");
|
||||
this.axolotlTemptItems = IntList.of(itemTags.get("minecraft:axolotl_tempt_items"));
|
||||
this.fishes = IntList.of(itemTags.get("minecraft:fishes"));
|
||||
this.flowers = IntList.of(itemTags.get("minecraft:flowers"));
|
||||
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
|
||||
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
|
||||
this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers"));
|
||||
|
||||
// Hack btw
|
||||
boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1;
|
||||
session.setEmulatePost1_14Logic(emulatePost1_14Logic);
|
||||
if (session.getGeyser().getLogger().isDebug()) {
|
||||
session.getGeyser().getLogger().debug("Emulating post 1.14 villager logic for " + session.name() + "? " + emulatePost1_14Logic);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
@ -94,9 +111,20 @@ public class TagCache {
|
||||
this.requiresIronTool = IntLists.emptyList();
|
||||
this.requiresDiamondTool = IntLists.emptyList();
|
||||
|
||||
this.axolotlTemptItems = IntLists.emptyList();
|
||||
this.fishes = IntLists.emptyList();
|
||||
this.flowers = IntLists.emptyList();
|
||||
this.foxFood = IntLists.emptyList();
|
||||
this.piglinLoved = IntLists.emptyList();
|
||||
this.smallFlowers = IntLists.emptyList();
|
||||
}
|
||||
|
||||
public boolean isAxolotlTemptItem(ItemMapping itemMapping) {
|
||||
return axolotlTemptItems.contains(itemMapping.getJavaId());
|
||||
}
|
||||
|
||||
public boolean isFish(GeyserItemStack itemStack) {
|
||||
return fishes.contains(itemStack.getJavaId());
|
||||
}
|
||||
|
||||
public boolean isFlower(ItemMapping mapping) {
|
||||
@ -111,6 +139,10 @@ public class TagCache {
|
||||
return piglinLoved.contains(mapping.getJavaId());
|
||||
}
|
||||
|
||||
public boolean isSmallFlower(GeyserItemStack itemStack) {
|
||||
return smallFlowers.contains(itemStack.getJavaId());
|
||||
}
|
||||
|
||||
public boolean isAxeEffective(BlockMapping blockMapping) {
|
||||
return axeEffective.contains(blockMapping.getJavaBlockId());
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector2d;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
@ -38,7 +37,6 @@ import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
|
||||
public class WorldBorder {
|
||||
private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D;
|
||||
@ -131,11 +129,14 @@ public class WorldBorder {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true as long the entity is within the world limits.
|
||||
* @return true as long as the player entity is within the world limits.
|
||||
*/
|
||||
public boolean isInsideBorderBoundaries() {
|
||||
Vector3f entityPosition = session.getPlayerEntity().getPosition();
|
||||
return entityPosition.getX() > minX && entityPosition.getX() < maxX && entityPosition.getZ() > minZ && entityPosition.getZ() < maxZ;
|
||||
return isInsideBorderBoundaries(session.getPlayerEntity().getPosition());
|
||||
}
|
||||
|
||||
public boolean isInsideBorderBoundaries(Vector3f position) {
|
||||
return position.getX() > minX && position.getX() < maxX && position.getZ() > minZ && position.getZ() < maxZ;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.text;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer;
|
||||
import net.kyori.adventure.util.Codec;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class DummyLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
|
||||
private final HoverEvent.ShowEntity dummyShowEntity;
|
||||
private final HoverEvent.ShowItem dummyShowItem;
|
||||
|
||||
public DummyLegacyHoverEventSerializer() {
|
||||
dummyShowEntity = HoverEvent.ShowEntity.of(Key.key("geysermc", "dummyshowitem"),
|
||||
UUID.nameUUIDFromBytes("entitiesareprettyneat".getBytes(StandardCharsets.UTF_8)));
|
||||
dummyShowItem = HoverEvent.ShowItem.of(Key.key("geysermc", "dummyshowentity"), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoverEvent.@NotNull ShowItem deserializeShowItem(@NotNull Component input) {
|
||||
return dummyShowItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HoverEvent.@NotNull ShowEntity deserializeShowEntity(@NotNull Component input,
|
||||
Codec.Decoder<Component, String, ? extends RuntimeException> componentDecoder) {
|
||||
return dummyShowEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component serializeShowItem(HoverEvent.@NotNull ShowItem input) {
|
||||
return Component.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input,
|
||||
Codec.Encoder<Component, String, ? extends RuntimeException> componentEncoder) {
|
||||
return Component.empty();
|
||||
}
|
||||
}
|
@ -28,9 +28,6 @@ package org.geysermc.geyser.translator.inventory;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData;
|
||||
import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
@ -45,6 +42,9 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.*;
|
||||
import org.geysermc.geyser.inventory.click.Click;
|
||||
import org.geysermc.geyser.inventory.click.ClickPlan;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator;
|
||||
@ -535,7 +535,6 @@ public abstract class InventoryTranslator {
|
||||
}
|
||||
int gridDimensions = gridSize == 4 ? 2 : 3;
|
||||
|
||||
Recipe recipe;
|
||||
Ingredient[] ingredients = new Ingredient[0];
|
||||
ItemStack output = null;
|
||||
int recipeWidth = 0;
|
||||
@ -564,7 +563,7 @@ public abstract class InventoryTranslator {
|
||||
craftState = CraftState.RECIPE_ID;
|
||||
|
||||
int recipeId = autoCraftAction.getRecipeNetworkId();
|
||||
recipe = session.getCraftingRecipes().get(recipeId);
|
||||
GeyserRecipe recipe = session.getCraftingRecipes().get(recipeId);
|
||||
if (recipe == null) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
@ -578,24 +577,21 @@ public abstract class InventoryTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
switch (recipe.getType()) {
|
||||
case CRAFTING_SHAPED -> {
|
||||
ShapedRecipeData shapedData = (ShapedRecipeData) recipe.getData();
|
||||
ingredients = shapedData.getIngredients();
|
||||
recipeWidth = shapedData.getWidth();
|
||||
output = shapedData.getResult();
|
||||
if (shapedData.getWidth() > gridDimensions || shapedData.getHeight() > gridDimensions) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
if (recipe.isShaped()) {
|
||||
GeyserShapedRecipe shapedRecipe = (GeyserShapedRecipe) recipe;
|
||||
ingredients = shapedRecipe.ingredients();
|
||||
recipeWidth = shapedRecipe.width();
|
||||
output = shapedRecipe.result();
|
||||
if (recipeWidth > gridDimensions || shapedRecipe.height() > gridDimensions) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
case CRAFTING_SHAPELESS -> {
|
||||
ShapelessRecipeData shapelessData = (ShapelessRecipeData) recipe.getData();
|
||||
ingredients = shapelessData.getIngredients();
|
||||
recipeWidth = gridDimensions;
|
||||
output = shapelessData.getResult();
|
||||
if (ingredients.length > gridSize) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
} else {
|
||||
GeyserShapelessRecipe shapelessRecipe = (GeyserShapelessRecipe) recipe;
|
||||
ingredients = shapelessRecipe.ingredients();
|
||||
recipeWidth = gridDimensions;
|
||||
output = shapelessRecipe.result();
|
||||
if (ingredients.length > gridSize) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -26,14 +26,18 @@
|
||||
package org.geysermc.geyser.translator.inventory;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.inventory.ContainerType;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.AutoCraftRecipeStackRequestActionData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData;
|
||||
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
|
||||
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
@ -44,6 +48,9 @@ import org.geysermc.geyser.inventory.BedrockContainerSlot;
|
||||
import org.geysermc.geyser.inventory.SlotType;
|
||||
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
|
||||
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
||||
private final InventoryUpdater updater;
|
||||
@ -131,11 +138,63 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) {
|
||||
return super.translateCraftingRequest(session, inventory, request);
|
||||
}
|
||||
|
||||
// Behavior as of 1.18.10.
|
||||
// We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to
|
||||
// store a map of net ID to trade index on our end.
|
||||
int tradeChoice = ((CraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1;
|
||||
return handleTrade(session, inventory, request, tradeChoice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
|
||||
// We're not crafting here
|
||||
// Called at least by consoles when pressing a trade option button
|
||||
return translateRequest(session, inventory, request);
|
||||
if (session.getUpstream().getProtocolVersion() < Bedrock_v486.V486_CODEC.getProtocolVersion()) {
|
||||
// We're not crafting here
|
||||
// Called at least by consoles when pressing a trade option button
|
||||
return translateRequest(session, inventory, request);
|
||||
}
|
||||
|
||||
// 1.18.10 update - seems impossible to call without consoles/controller input
|
||||
// We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to
|
||||
// store a map of net ID to trade index on our end.
|
||||
int tradeChoice = ((AutoCraftRecipeStackRequestActionData) request.getActions()[0]).getRecipeNetworkId() - 1;
|
||||
return handleTrade(session, inventory, request, tradeChoice);
|
||||
}
|
||||
|
||||
private ItemStackResponsePacket.Response handleTrade(GeyserSession session, Inventory inventory, ItemStackRequest request, int tradeChoice) {
|
||||
ServerboundSelectTradePacket packet = new ServerboundSelectTradePacket(tradeChoice);
|
||||
session.sendDownstreamPacket(packet);
|
||||
|
||||
if (session.isEmulatePost1_14Logic()) {
|
||||
// 1.18 Java cooperates nicer than older versions
|
||||
if (inventory instanceof MerchantContainer merchantInventory) {
|
||||
merchantInventory.onTradeSelected(session, tradeChoice);
|
||||
}
|
||||
return translateRequest(session, inventory, request);
|
||||
} else {
|
||||
// 1.18 servers works fine without a workaround, but ViaVersion needs to work around 1.13 servers,
|
||||
// so we need to work around that with the delay. Specifically they force a window refresh after a
|
||||
// trade packet has been sent.
|
||||
session.scheduleInEventLoop(() -> {
|
||||
if (inventory instanceof MerchantContainer merchantInventory) {
|
||||
merchantInventory.onTradeSelected(session, tradeChoice);
|
||||
// Ignore output since we don't want to send a delayed response packet back to the client
|
||||
translateRequest(session, inventory, request);
|
||||
|
||||
// Resync items once more
|
||||
updateInventory(session, inventory);
|
||||
InventoryUtils.updateCursor(session);
|
||||
}
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Revert this request, for now
|
||||
return rejectRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,6 +37,7 @@ import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -96,10 +97,7 @@ public class BannerTranslator extends ItemTranslator {
|
||||
public static NbtList<NbtMap> convertBannerPattern(ListTag patterns) {
|
||||
List<NbtMap> tagsList = new ArrayList<>();
|
||||
for (Tag patternTag : patterns.getValue()) {
|
||||
NbtMap newPatternTag = getBedrockBannerPattern((CompoundTag) patternTag);
|
||||
if (newPatternTag != null) {
|
||||
tagsList.add(newPatternTag);
|
||||
}
|
||||
tagsList.add(getBedrockBannerPattern((CompoundTag) patternTag));
|
||||
}
|
||||
|
||||
return new NbtList<>(NbtType.COMPOUND, tagsList);
|
||||
@ -111,17 +109,11 @@ public class BannerTranslator extends ItemTranslator {
|
||||
* @param pattern Java edition pattern nbt
|
||||
* @return The Bedrock edition format pattern nbt
|
||||
*/
|
||||
public static NbtMap getBedrockBannerPattern(CompoundTag pattern) {
|
||||
String patternName = (String) pattern.get("Pattern").getValue();
|
||||
|
||||
// Return null if its the globe pattern as it doesn't exist on bedrock
|
||||
if (patternName.equals("glb")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static NbtMap getBedrockBannerPattern(CompoundTag pattern) {
|
||||
return NbtMap.builder()
|
||||
.putInt("Color", 15 - (int) pattern.get("Color").getValue())
|
||||
.putString("Pattern", patternName)
|
||||
.putString("Pattern", (String) pattern.get("Pattern").getValue())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -28,16 +28,25 @@ package org.geysermc.geyser.translator.level.block.entity;
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.nbt.NbtMapBuilder;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.JIGSAW)
|
||||
public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator {
|
||||
public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
builder.put("joint", ((StringTag) tag.get("joint")).getValue());
|
||||
builder.put("name", ((StringTag) tag.get("name")).getValue());
|
||||
builder.put("target_pool", ((StringTag) tag.get("pool")).getValue());
|
||||
Tag jointTag = tag.get("joint");
|
||||
if (jointTag instanceof StringTag) {
|
||||
builder.put("joint", ((StringTag) jointTag).getValue());
|
||||
} else {
|
||||
// Tag is not present in at least 1.14.4 Paper
|
||||
// Minecraft 1.18.1 deliberately has a fallback here, but not for any other value
|
||||
builder.put("joint", BlockStateValues.getHorizontalFacingJigsaws().contains(blockState) ? "aligned" : "rollable");
|
||||
}
|
||||
builder.put("name", getOrDefault(tag.get("name"), ""));
|
||||
builder.put("target_pool", getOrDefault(tag.get("pool"), ""));
|
||||
builder.put("final_state", ((StringTag) tag.get("final_state")).getValue());
|
||||
builder.put("target", ((StringTag) tag.get("target")).getValue());
|
||||
builder.put("target", getOrDefault(tag.get("target"), ""));
|
||||
}
|
||||
}
|
||||
|
@ -33,23 +33,21 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.*;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.inventory.click.Click;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
@ -58,8 +56,9 @@ import org.geysermc.geyser.registry.type.ItemMappings;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
|
||||
import org.geysermc.geyser.util.BlockUtils;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.List;
|
||||
@ -91,18 +90,41 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
InventoryActionData containerAction = packet.getActions().get(1);
|
||||
if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
|
||||
&& worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
|
||||
if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot() ||
|
||||
session.getPlayerInventory().getItemInHand().isEmpty()) {
|
||||
boolean dropAll = worldAction.getToItem().getCount() > 1;
|
||||
|
||||
if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot()) {
|
||||
// Dropping an item that you don't have selected isn't supported in Java, but we can workaround it with an inventory hack
|
||||
PlayerInventory inventory = session.getPlayerInventory();
|
||||
int hotbarSlot = inventory.getOffsetForHotbar(containerAction.getSlot());
|
||||
Click clickType = dropAll ? Click.DROP_ALL : Click.DROP_ONE;
|
||||
Int2ObjectMap<ItemStack> changedItem;
|
||||
if (dropAll) {
|
||||
inventory.setItem(hotbarSlot, GeyserItemStack.EMPTY, session);
|
||||
changedItem = Int2ObjectMaps.singleton(hotbarSlot, null);
|
||||
} else {
|
||||
GeyserItemStack itemStack = inventory.getItem(hotbarSlot);
|
||||
if (itemStack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
itemStack.sub(1);
|
||||
changedItem = Int2ObjectMaps.singleton(hotbarSlot, itemStack.getItemStack());
|
||||
}
|
||||
ServerboundContainerClickPacket dropPacket = new ServerboundContainerClickPacket(
|
||||
inventory.getId(), inventory.getStateId(), hotbarSlot, clickType.actionType, clickType.action,
|
||||
inventory.getCursor().getItemStack(), changedItem);
|
||||
session.sendDownstreamPacket(dropPacket);
|
||||
return;
|
||||
}
|
||||
if (session.getPlayerInventory().getItemInHand().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean dropAll = worldAction.getToItem().getCount() > 1;
|
||||
ServerboundPlayerActionPacket dropAllPacket = new ServerboundPlayerActionPacket(
|
||||
ServerboundPlayerActionPacket dropPacket = new ServerboundPlayerActionPacket(
|
||||
dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
|
||||
BlockUtils.POSITION_ZERO,
|
||||
Direction.DOWN
|
||||
);
|
||||
session.sendDownstreamPacket(dropAllPacket);
|
||||
session.sendDownstreamPacket(dropPacket);
|
||||
|
||||
if (dropAll) {
|
||||
session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY);
|
||||
@ -151,14 +173,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
if (session.getBlockMappings().isItemFrame(packet.getBlockRuntimeId())) {
|
||||
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
|
||||
if (itemFrameEntity != null) {
|
||||
int entityId = itemFrameEntity.getEntityId();
|
||||
Vector3f vector = packet.getClickPosition();
|
||||
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(entityId,
|
||||
InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking());
|
||||
ServerboundInteractPacket interactAtPacket = new ServerboundInteractPacket(entityId,
|
||||
InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking());
|
||||
session.sendDownstreamPacket(interactPacket);
|
||||
session.sendDownstreamPacket(interactAtPacket);
|
||||
processEntityInteraction(session, packet, itemFrameEntity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -397,27 +412,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
//https://wiki.vg/Protocol#Interact_Entity
|
||||
switch (packet.getActionType()) {
|
||||
case 0: //Interact
|
||||
if (entity instanceof CommandBlockMinecartEntity) {
|
||||
// The UI is handled client-side on Java Edition
|
||||
// Ensure OP permission level and gamemode is appropriate
|
||||
if (session.getOpPermissionLevel() < 2 || session.getGameMode() != GameMode.CREATIVE) return;
|
||||
ContainerOpenPacket openPacket = new ContainerOpenPacket();
|
||||
openPacket.setBlockPosition(Vector3i.ZERO);
|
||||
openPacket.setId((byte) 1);
|
||||
openPacket.setType(ContainerType.COMMAND_BLOCK);
|
||||
openPacket.setUniqueEntityId(entity.getGeyserId());
|
||||
session.sendUpstreamPacket(openPacket);
|
||||
break;
|
||||
}
|
||||
Vector3f vector = packet.getClickPosition().sub(entity.getPosition());
|
||||
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(entity.getEntityId(),
|
||||
InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking());
|
||||
ServerboundInteractPacket interactAtPacket = new ServerboundInteractPacket(entity.getEntityId(),
|
||||
InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking());
|
||||
session.sendDownstreamPacket(interactPacket);
|
||||
session.sendDownstreamPacket(interactAtPacket);
|
||||
|
||||
EntitySoundInteractionTranslator.handleEntityInteraction(session, packet.getClickPosition(), entity);
|
||||
processEntityInteraction(session, packet, entity);
|
||||
break;
|
||||
case 1: //Attack
|
||||
if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) {
|
||||
@ -437,6 +432,46 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
}
|
||||
}
|
||||
|
||||
private void processEntityInteraction(GeyserSession session, InventoryTransactionPacket packet, Entity entity) {
|
||||
Vector3f entityPosition = entity.getPosition();
|
||||
if (!session.getWorldBorder().isInsideBorderBoundaries(entityPosition)) {
|
||||
// No transaction is able to go through (as of Java Edition 1.18.1)
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f clickPosition = packet.getClickPosition().sub(entityPosition);
|
||||
boolean isSpectator = session.getGameMode() == GameMode.SPECTATOR;
|
||||
for (Hand hand : EntityUtils.HANDS) {
|
||||
session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(),
|
||||
InteractAction.INTERACT_AT, clickPosition.getX(), clickPosition.getY(), clickPosition.getZ(),
|
||||
hand, session.isSneaking()));
|
||||
|
||||
InteractionResult result;
|
||||
if (isSpectator) {
|
||||
result = InteractionResult.PASS;
|
||||
} else {
|
||||
result = entity.interactAt(hand);
|
||||
}
|
||||
|
||||
if (!result.consumesAction()) {
|
||||
session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(),
|
||||
InteractAction.INTERACT, hand, session.isSneaking()));
|
||||
if (!isSpectator) {
|
||||
result = entity.interact(hand);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.consumesAction()) {
|
||||
if (result.shouldSwing() && hand == Hand.OFF_HAND) {
|
||||
// Currently, Bedrock will send us the arm swing packet in most cases. But it won't for offhand.
|
||||
session.sendDownstreamPacket(new ServerboundSwingPacket(hand));
|
||||
// Note here to look into sending the animation packet back to Bedrock
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the correct block state from the server without updating the chunk cache.
|
||||
*
|
||||
|
@ -34,7 +34,6 @@ 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.CooldownUtils;
|
||||
import org.geysermc.geyser.entity.InteractiveTagManager;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -70,7 +69,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
|
||||
|
||||
// Update the interactive tag, if an entity is present
|
||||
if (session.getMouseoverEntity() != null) {
|
||||
InteractiveTagManager.updateTag(session, session.getMouseoverEntity());
|
||||
session.getMouseoverEntity().updateInteractiveTag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,17 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
|
||||
|
||||
if (session.getRemoteAuthType() == AuthType.ONLINE) {
|
||||
if (!session.isLoggedIn()) {
|
||||
LoginEncryptionUtils.buildAndShowLoginWindow(session);
|
||||
if (session.getGeyser().getConfig().getSavedUserLogins().contains(session.name())) {
|
||||
if (session.getGeyser().refreshTokenFor(session.name()) == null) {
|
||||
LoginEncryptionUtils.buildAndShowConsentWindow(session);
|
||||
} else {
|
||||
// If the refresh token is not null and we're here, then the refresh token expired
|
||||
// and the expiration form has been cached
|
||||
session.getFormCache().resendAllForms();
|
||||
}
|
||||
} else {
|
||||
LoginEncryptionUtils.buildAndShowLoginWindow(session);
|
||||
}
|
||||
}
|
||||
// else we were able to log the user in
|
||||
}
|
||||
|
@ -25,11 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.bedrock.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.MerchantContainer;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -50,21 +47,14 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
|
||||
return;
|
||||
}
|
||||
case COMPLETE_TRADE -> {
|
||||
// Not sent as of 1.18.10
|
||||
ServerboundSelectTradePacket selectTradePacket = new ServerboundSelectTradePacket(packet.getData());
|
||||
session.sendDownstreamPacket(selectTradePacket);
|
||||
|
||||
session.scheduleInEventLoop(() -> {
|
||||
SessionPlayerEntity villager = session.getPlayerEntity();
|
||||
Inventory openInventory = session.getOpenInventory();
|
||||
if (openInventory instanceof MerchantContainer merchantInventory) {
|
||||
VillagerTrade[] trades = merchantInventory.getVillagerTrades();
|
||||
if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) {
|
||||
VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()];
|
||||
openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||
// TODO this logic doesn't add up
|
||||
villager.addFakeTradeExperience(trade.getXp());
|
||||
villager.updateBedrockMetadata();
|
||||
}
|
||||
merchantInventory.onTradeSelected(session, packet.getData());
|
||||
}
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
return;
|
||||
|
@ -40,7 +40,6 @@ import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.entity.InteractiveTagManager;
|
||||
|
||||
@Translator(packet = InteractPacket.class)
|
||||
public class BedrockInteractTranslator extends PacketTranslator<InteractPacket> {
|
||||
@ -84,7 +83,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
|
||||
return;
|
||||
}
|
||||
|
||||
InteractiveTagManager.updateTag(session, interactEntity);
|
||||
interactEntity.updateInteractiveTag();
|
||||
} else {
|
||||
if (session.getMouseoverEntity() != null) {
|
||||
// No interactive tag should be sent
|
||||
|
@ -42,6 +42,9 @@ import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
|
||||
import it.unimi.dsi.fastutil.ints.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapelessRecipe;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
@ -80,7 +83,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
|
||||
boolean applySmithingRecipes = session.getUpstream().getProtocolVersion() >= Bedrock_v486.V486_CODEC.getProtocolVersion();
|
||||
|
||||
Int2ObjectMap<Recipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion()));
|
||||
Int2ObjectMap<GeyserRecipe> recipeMap = new Int2ObjectOpenHashMap<>(Registries.RECIPES.forVersion(session.getUpstream().getProtocolVersion()));
|
||||
Int2ObjectMap<List<StoneCuttingRecipeData>> unsortedStonecutterData = new Int2ObjectOpenHashMap<>();
|
||||
CraftingDataPacket craftingDataPacket = new CraftingDataPacket();
|
||||
craftingDataPacket.setCleanRecipes(true);
|
||||
@ -100,7 +103,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
UUID uuid = UUID.randomUUID();
|
||||
craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(),
|
||||
Arrays.asList(inputs), Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
||||
recipeMap.put(netId++, recipe);
|
||||
recipeMap.put(netId++, new GeyserShapelessRecipe(shapelessRecipeData));
|
||||
}
|
||||
}
|
||||
case CRAFTING_SHAPED -> {
|
||||
@ -118,7 +121,7 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
||||
craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(),
|
||||
shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), Arrays.asList(inputs),
|
||||
Collections.singletonList(output), uuid, "crafting_table", 0, netId));
|
||||
recipeMap.put(netId++, recipe);
|
||||
recipeMap.put(netId++, new GeyserShapedRecipe(shapedRecipeData));
|
||||
}
|
||||
}
|
||||
case STONECUTTING -> {
|
||||
|
@ -35,6 +35,6 @@ public class JavaUpdateTagsTranslator extends PacketTranslator<ClientboundUpdate
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundUpdateTagsPacket packet) {
|
||||
session.getTagCache().loadPacket(packet);
|
||||
session.getTagCache().loadPacket(session, packet);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.entity;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.InteractiveTagManager;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
@ -60,8 +59,9 @@ public class JavaSetEntityDataTranslator extends PacketTranslator<ClientboundSet
|
||||
entity.updateBedrockMetadata();
|
||||
|
||||
// Update the interactive tag, if necessary
|
||||
if (session.getMouseoverEntity() != null && session.getMouseoverEntity().getEntityId() == entity.getEntityId()) {
|
||||
InteractiveTagManager.updateTag(session, entity);
|
||||
Entity mouseoverEntity = session.getMouseoverEntity();
|
||||
if (mouseoverEntity != null && mouseoverEntity.getEntityId() == entity.getEntityId()) {
|
||||
mouseoverEntity.updateInteractiveTag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ public class JavaPlayerAbilitiesTranslator extends PacketTranslator<ClientboundP
|
||||
public void translate(GeyserSession session, ClientboundPlayerAbilitiesPacket packet) {
|
||||
session.setCanFly(packet.isCanFly());
|
||||
session.setFlying(packet.isFlying());
|
||||
session.setInstabuild(packet.isCreative());
|
||||
session.sendAdventureSettings();
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
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.Recipe;
|
||||
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.packet.ingame.clientbound.inventory.ClientboundContainerSetSlotPacket;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.CraftingData;
|
||||
@ -38,6 +35,7 @@ import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
|
||||
@ -165,9 +163,8 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
}
|
||||
}
|
||||
|
||||
ShapedRecipeData data = new ShapedRecipeData(width, height, "", javaIngredients, item);
|
||||
// Cache this recipe so we know the client has received it
|
||||
session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data));
|
||||
session.getCraftingRecipes().put(newRecipeId, new GeyserShapedRecipe(width, height, javaIngredients, item));
|
||||
|
||||
CraftingDataPacket craftPacket = new CraftingDataPacket();
|
||||
craftPacket.getCraftingData().add(CraftingData.fromShaped(
|
||||
|
@ -29,12 +29,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.Cli
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.geyser.text.MinecraftLocale;
|
||||
|
||||
@Translator(packet = ClientboundOpenScreenPacket.class)
|
||||
public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenScreenPacket> {
|
||||
@ -57,8 +56,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
|
||||
return;
|
||||
}
|
||||
|
||||
String name = MessageTranslator.convertMessageLenient(packet.getName(), session.locale());
|
||||
name = MinecraftLocale.getLocaleString(name, session.locale());
|
||||
String name = MessageTranslator.convertMessage(packet.getTitle(), session.locale());
|
||||
|
||||
Inventory newInventory = newTranslator.createInventory(name, packet.getContainerId(), packet.getType(), session.getPlayerInventory());
|
||||
if (openInventory != null) {
|
||||
|
@ -1,93 +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.translator.sound;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Sound interaction handler for when an entity is right-clicked.
|
||||
*/
|
||||
public interface EntitySoundInteractionTranslator extends SoundInteractionTranslator<Entity> {
|
||||
|
||||
/**
|
||||
* Handles the block interaction when a player
|
||||
* right-clicks an entity.
|
||||
*
|
||||
* @param session the session interacting with the block
|
||||
* @param position the position of the block
|
||||
* @param entity the entity interacted with
|
||||
*/
|
||||
static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) {
|
||||
// If we need to get the hand identifier, only get it once and save it to a variable
|
||||
String handIdentifier = null;
|
||||
|
||||
for (Map.Entry<SoundTranslator, SoundInteractionTranslator<?>> interactionEntry : Registries.SOUND_TRANSLATORS.get().entrySet()) {
|
||||
if (!(interactionEntry.getValue() instanceof EntitySoundInteractionTranslator)) {
|
||||
continue;
|
||||
}
|
||||
if (interactionEntry.getKey().entities().length != 0) {
|
||||
boolean contains = false;
|
||||
for (String entityIdentifier : interactionEntry.getKey().entities()) {
|
||||
if (entity.getDefinition().entityType().name().toLowerCase().contains(entityIdentifier)) {
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contains) continue;
|
||||
}
|
||||
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand();
|
||||
if (interactionEntry.getKey().items().length != 0) {
|
||||
if (itemInHand.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (handIdentifier == null) {
|
||||
// Don't get the identifier unless we need it
|
||||
handIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
|
||||
}
|
||||
boolean contains = false;
|
||||
for (String itemIdentifier : interactionEntry.getKey().items()) {
|
||||
if (handIdentifier.contains(itemIdentifier)) {
|
||||
contains = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!contains) continue;
|
||||
}
|
||||
if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) {
|
||||
if (!itemInHand.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
((EntitySoundInteractionTranslator) interactionEntry.getValue()).translate(session, position, entity);
|
||||
}
|
||||
}
|
||||
}
|
@ -54,17 +54,6 @@ public @interface SoundTranslator {
|
||||
*/
|
||||
String[] items() default {};
|
||||
|
||||
/**
|
||||
* The identifier(s) that the interacted entity must have.
|
||||
* Leave empty to ignore.
|
||||
*
|
||||
* Only applies to interaction handlers that are an
|
||||
* instance of {@link EntitySoundInteractionTranslator}.
|
||||
*
|
||||
* @return the value the item in the player's hand must contain
|
||||
*/
|
||||
String[] entities() default {};
|
||||
|
||||
/**
|
||||
* Controls if the interaction should still be
|
||||
* called even if the player is sneaking while
|
||||
|
@ -1,57 +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.translator.sound.entity;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.OcelotEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
|
||||
import org.geysermc.geyser.translator.sound.SoundTranslator;
|
||||
|
||||
@SoundTranslator
|
||||
public class FeedBabySoundInteractionTranslator implements EntitySoundInteractionTranslator {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, Vector3f position, Entity entity) {
|
||||
if (entity instanceof AnimalEntity animalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) {
|
||||
String handIdentifier = session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier();
|
||||
boolean isBaby = animalEntity.isBaby();
|
||||
if (isBaby && animalEntity.canEat(handIdentifier.replace("minecraft:", ""),
|
||||
session.getPlayerInventory().getItemInHand().getMapping(session))) {
|
||||
// Play the "feed child" effect
|
||||
EntityEventPacket feedEvent = new EntityEventPacket();
|
||||
feedEvent.setRuntimeEntityId(entity.getGeyserId());
|
||||
feedEvent.setType(EntityEventType.BABY_ANIMAL_FEED);
|
||||
session.sendUpstreamPacket(feedEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren