Compare commits

..

51 Commits

Author SHA1 Message Date
Camotoy c8016647f2
Clean up and add mobile button for horse opening 2021-01-05 19:08:54 -05:00
Camotoy a88678a5c1
Add manual recipes as Recipe classes for future usage 2021-01-04 22:15:55 -05:00
Camotoy 1d47dc3f18
Merge branch 'server-inventory' of https://github.com/GeyserMC/Geyser into server-inventory 2021-01-04 20:30:19 -05:00
AJ Ferguson ff69752d2c Remove unnecessary is_block null check 2021-01-04 15:16:21 -09:00
AJ Ferguson 57e176efd6 Optimize stackSize
Co-Authored-By: Camotoy <20743703+Camotoy@users.noreply.github.com>
2021-01-04 15:12:26 -09:00
AJ Ferguson a160e3694b Add stackSize to ItemEntry 2021-01-04 14:47:48 -09:00
Camotoy 50f295b4cd
Remove unneeded GeyserItemStack constructor and add comments 2021-01-04 18:19:43 -05:00
AJ Ferguson 8928d554a1 WIP autocrafting using java recipe book
work in progress. many edge cases are currently unhandled. will not work at all pre 1.12. (support is planned)
2021-01-03 17:54:26 -09:00
AJ Ferguson 528a9a4431 Crafting table slot mappings 2021-01-01 18:37:33 -09:00
Camotoy 7a82852134
Merge branch 'server-inventory' of https://github.com/GeyserMC/Geyser into server-inventory 2021-01-01 14:22:44 -05:00
Camotoy 2a5c134ea7
Update server inventory copyrights to 2021 2021-01-01 14:22:26 -05:00
Camotoy 8317961340
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2021-01-01 14:20:52 -05:00
AJ Ferguson b7b3278d8b item painting. cursor net id 2020-12-31 17:39:54 -09:00
Camotoy c4fc604e0c
Optimize stonecutter button code 2020-12-29 20:31:48 -05:00
Camotoy 3c1a40c56a
Better net ID handling 2020-12-29 19:59:22 -05:00
Camotoy f4b1d470c3
Merge branch 'server-inventory' of https://github.com/AJ-Ferguson/Geyser into server-inventory 2020-12-29 19:02:11 -05:00
Camotoy ac4f6eceac
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-29 19:01:59 -05:00
Camotoy a5c020e7ee
More comments 2020-12-28 13:16:17 -05:00
Camotoy 3ba396e625
Fix some temporary inventory blocks not disappearing 2020-12-28 00:47:10 -05:00
Camotoy 2265de3ae9
lecterns 2020-12-28 00:29:27 -05:00
Camotoy 790c695b27
Add TODO 2020-12-27 11:08:48 -05:00
Camotoy 956d264c3e
Anvil renaming works; other things 2020-12-26 22:55:14 -05:00
AJ Ferguson 60da3b9432 Temp slot 2020-12-26 18:44:48 -09:00
Camotoy 4b461e5e0a
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-26 19:52:16 -05:00
Camotoy 7c4e95625a
Cleanup and delete FilterTextPacket - PRing to the main branch 2020-12-26 19:16:35 -05:00
AJ Ferguson 6df89ed679
Merge pull request #1 from Camotoy/server-inventory
Progress
2020-12-26 14:07:50 -09:00
Camotoy 078af59249
Streamline Item Net ID getting; cartography table finished 2020-12-26 16:41:50 -05:00
Camotoy 06f346b30b
Fix offset issue 2020-12-26 13:24:50 -05:00
Camotoy c1f5380ed1
Add horse inventory support 2020-12-26 12:20:59 -05:00
Camotoy c7fade295e
Add swap support for creative mode; start on cartography table 2020-12-24 20:43:24 -05:00
Camotoy 3d0b0a1076
Stonecutter fixed; Loom improved 2020-12-24 18:29:25 -05:00
Camotoy 617a1216d5
Initial work on stonecutters 2020-12-24 11:23:47 -05:00
Camotoy 6ae81cce52
Full banner loom support 2020-12-23 22:53:54 -05:00
Camotoy 94febd6a2f
Merge pull request #15 from D3ATHBRINGER13/server-inventory
Initial loom functionality
2020-12-23 21:02:02 -05:00
D3ATHBRINGER13 4d80edf6d9 Initial loom funtionality 2020-12-24 00:48:31 +00:00
Camotoy 0f735a8330
Block entity cleanup 2020-12-23 12:37:55 -05:00
Camotoy e4ecd1a092
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-23 12:32:39 -05:00
Camotoy ff4f712eda
Implement beacon 2020-12-23 12:30:36 -05:00
Camotoy f4f804e1ca
Enchantment table works; anvil is almost there 2020-12-23 01:21:00 -05:00
Camotoy 009905184e
Add grindstone and smithing table 2020-12-21 22:44:01 -05:00
Camotoy c6b4d163a1
Improve creative support 2020-12-21 21:09:14 -05:00
DoctorMacc f47cf32d90
Make my life tolerable 2020-12-19 19:19:44 -05:00
DoctorMacc 1705f1034c
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-18 18:37:54 -05:00
DoctorMacc aa4a1058e3
Brewing stand support; other attempts 2020-12-17 21:47:18 -05:00
DoctorMacc 33a86485dc
Implement dropper/dispenser, hopper, shulker 2020-12-17 11:46:11 -05:00
DoctorMacc 929b0ba80c
Refactors, and add furnace 2020-12-16 23:52:45 -05:00
DoctorMacc 988e697a70
Crafting table works 2020-12-15 23:33:17 -05:00
DoctorMacc 3b3e72d5c3
Initial crafting table support 2020-12-15 23:01:27 -05:00
DoctorMacc f167ed2583
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory 2020-12-09 11:57:46 -05:00
DoctorMacc 049242db4b
Update to 1.16.200 2020-12-09 01:12:02 -05:00
AJ Ferguson 7f4b588cdf server inventory. WORK IN PROGRESS
wip commit of implementing server authoritative inventories. there is a lot of experimental and debug code. this is NOT ready for testing or review.
2020-10-16 15:25:05 -08:00
170 changed files with 5364 additions and 3522 deletions

View File

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

30
Jenkinsfile vendored
View File

@ -26,27 +26,7 @@ pipeline {
}
steps {
rtMavenDeployer(
id: "maven-deployer",
serverId: "opencollab-artifactory",
releaseRepo: "maven-releases",
snapshotRepo: "maven-snapshots"
)
rtMavenResolver(
id: "maven-resolver",
serverId: "opencollab-artifactory",
releaseRepo: "release",
snapshotRepo: "snapshot"
)
rtMavenRun(
pom: 'pom.xml',
goals: 'javadoc:jar source:jar install -DskipTests',
deployerId: "maven-deployer",
resolverId: "maven-resolver"
)
rtPublishBuildInfo(
serverId: "opencollab-artifactory"
)
sh 'mvn javadoc:jar source:jar deploy -DskipTests'
}
}
}
@ -89,13 +69,5 @@ pipeline {
discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.opencollab.dev/job/GeyserMC/job/Geyser)", footer: 'Open Collaboration Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK
}
}
success {
script {
if (env.BRANCH_NAME == 'master') {
build propagate: false, wait: false, job: 'GeyserMC/Geyser-Fabric/java-1.16'
build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master'
}
}
}
}
}

View File

@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -18,7 +18,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have now joined us here!
### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4.
### Currently supporting Minecraft Bedrock v1.16.100/v1.16.101/v1.16.200 and Minecraft Java v1.16.4.
## Setting Up
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.

View File

@ -86,8 +86,8 @@
<shadedPattern>org.geysermc.platform.bungeecord.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.kyori</shadedPattern>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.bungeecord.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>

View File

@ -97,8 +97,8 @@
<shadedPattern>org.geysermc.platform.spigot.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.kyori</shadedPattern>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.spigot.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>

View File

@ -157,14 +157,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
if (isViaVersion && isViaVersionNeeded()) {
if (isLegacy) {
// Pre-1.13
this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager();
this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this);
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes);
}
} else {
// No ViaVersion
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes);
this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, use3dBiomes);
}
geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion);
} catch (Exception e) {
@ -180,13 +180,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
// No NMS adapter
if (isLegacy && isViaVersion) {
// Use ViaVersion for converting pre-1.13 block states
this.geyserWorldManager = new GeyserSpigot1_12WorldManager();
this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this);
} else if (isLegacy) {
// Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air
this.geyserWorldManager = new GeyserSpigotFallbackWorldManager();
this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this);
} else {
// Post-1.13
this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes);
this.geyserWorldManager = new GeyserSpigotWorldManager(this, use3dBiomes);
}
geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass());
}

View File

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

View File

@ -30,6 +30,7 @@ import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import us.myles.ViaVersion.api.Pair;
@ -61,8 +62,8 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager {
*/
private final List<Pair<Integer, Protocol>> protocolList;
public GeyserSpigot1_12WorldManager() {
super(false);
public GeyserSpigot1_12WorldManager(Plugin plugin) {
super(plugin, false);
this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData();
this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION,
ProtocolVersion.v1_13.getVersion());

View File

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

View File

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

View File

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

View File

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

View File

@ -86,8 +86,8 @@
<shadedPattern>org.geysermc.platform.sponge.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.kyori</shadedPattern>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.sponge.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>

View File

@ -82,8 +82,8 @@
<shadedPattern>org.geysermc.platform.velocity.shaded.dom4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.kyori</shadedPattern>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>org.geysermc.platform.velocity.shaded.adventure</shadedPattern>
</relocation>
</relocations>
</configuration>

View File

@ -30,9 +30,9 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.CloudburstMC.Protocol</groupId>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v422</artifactId>
<version>d41b84e86c</version>
<version>2.6.1-SNAPSHOT</version>
<scope>compile</scope>
<exclusions>
<exclusion>
@ -132,10 +132,6 @@
<groupId>com.github.steveice10</groupId>
<artifactId>packetlib</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.steveice10</groupId>
<artifactId>mcauthlib</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -202,11 +198,6 @@
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.GeyserMC</groupId>
<artifactId>MCAuthLib</artifactId>
<version>0e48a094f2</version>
</dependency>
</dependencies>
<build>

View File

@ -86,11 +86,6 @@ public class GeyserConnector {
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
public static final String VERSION = "DEV"; // A fallback for running in IDEs
/**
* Oauth client ID for Microsoft authentication
*/
public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88";
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
private final List<GeyserSession> players = new ArrayList<>();
@ -106,8 +101,8 @@ public class GeyserConnector {
private final ScheduledExecutorService generalThreadPool;
private BedrockServer bedrockServer;
private final PlatformType platformType;
private final GeyserBootstrap bootstrap;
private PlatformType platformType;
private GeyserBootstrap bootstrap;
private Metrics metrics;

View File

@ -52,7 +52,6 @@ public abstract class CommandManager {
registerCommand(new VersionCommand(connector, "version", "geyser.commands.version.desc", "geyser.command.version"));
registerCommand(new SettingsCommand(connector, "settings", "geyser.commands.settings.desc", "geyser.command.settings"));
registerCommand(new StatisticsCommand(connector, "statistics", "geyser.commands.statistics.desc", "geyser.command.statistics"));
registerCommand(new AdvancementsCommand(connector, "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
}
public void registerCommand(GeyserCommand command) {

View File

@ -1,74 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.command.defaults;
import org.geysermc.common.window.SimpleFormWindow;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.command.GeyserCommand;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.AdvancementsCache;
public class AdvancementsCommand extends GeyserCommand {
private final GeyserConnector connector;
public AdvancementsCommand(GeyserConnector connector, String name, String description, String permission) {
super(name, description, permission);
this.connector = connector;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (sender.isConsole()) {
return;
}
// Make sure the sender is a Bedrock edition client
GeyserSession session = null;
if (sender instanceof GeyserSession) {
session = (GeyserSession) sender;
} else {
// Needed for Spigot - sender is not an instance of GeyserSession
for (GeyserSession otherSession : connector.getPlayers()) {
if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) {
session = otherSession;
break;
}
}
}
if (session == null) return;
SimpleFormWindow window = session.getAdvancementsCache().buildMenuForm();
session.sendForm(window, AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID);
}
@Override
public boolean isExecutableOnConsole() {
return false;
}
}

View File

@ -118,8 +118,6 @@ public interface GeyserConfiguration {
String getAuthType();
boolean isPasswordAuthentication();
boolean isUseProxyProtocol();
}
@ -127,12 +125,6 @@ public interface GeyserConfiguration {
String getEmail();
String getPassword();
/**
* Will be removed after Microsoft accounts are fully migrated
*/
@Deprecated
boolean isMicrosoftAccount();
}
interface IMetricsInfo {

View File

@ -149,24 +149,17 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
@JsonProperty("auth-type")
private String authType = "online";
@JsonProperty("allow-password-authentication")
private boolean passwordAuthentication = true;
@JsonProperty("use-proxy-protocol")
private boolean useProxyProtocol = false;
}
@Getter
@JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load
public static class UserAuthenticationInfo implements IUserAuthenticationInfo {
@AsteriskSerializer.Asterisk()
private String email;
@AsteriskSerializer.Asterisk()
private String password;
@JsonProperty("microsoft-account")
private boolean microsoftAccount = false;
}
@Getter

View File

@ -50,6 +50,7 @@ import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.ArmorStandEntity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.utils.AttributeUtils;
@ -284,11 +285,12 @@ public class Entity {
// Shield code
if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) {
if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) ||
(session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD.getJavaId())) {
PlayerInventory playerInv = session.getPlayerInventory();
if ((playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) ||
(playerInv.getOffhand().getJavaId() == ItemRegistry.SHIELD.getJavaId())) {
ClientPlayerUseItemPacket useItemPacket;
metadata.getFlags().setFlag(EntityFlag.BLOCKING, true);
if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) {
if (playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
}
// Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent

View File

@ -38,11 +38,11 @@ public class ItemedFireballEntity extends ThrowableEntity {
}
@Override
public void tick(GeyserSession session) {
protected void updatePosition(GeyserSession session) {
position = position.add(motion);
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
// TODO: movement is incredibly stiff.
// TODO: Only use this laggy movement for fireballs that be reflected
// TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not,
// TODO: only use this laggy movement for fireballs that be reflected
moveAbsoluteImmediate(session, position, rotation, false, true);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);

View File

@ -33,35 +33,50 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Used as a class for any object-like entity that moves as a projectile
*/
public class ThrowableEntity extends Entity implements Tickable {
public class ThrowableEntity extends Entity {
private Vector3f lastPosition;
/**
* Updates the position for the Bedrock client.
*
* Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions
*/
protected ScheduledFuture<?> positionUpdater;
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.lastPosition = position;
}
/**
* Updates the position for the Bedrock client.
*
* Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions
*/
@Override
public void tick(GeyserSession session) {
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
float drag = getDrag(session);
float gravity = getGravity();
motion = motion.mul(drag).down(gravity);
public void spawnEntity(GeyserSession session) {
super.spawnEntity(session);
positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
if (session.isClosed()) {
positionUpdater.cancel(true);
return;
}
updatePosition(session);
}, 0, 50, TimeUnit.MILLISECONDS);
}
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
}
protected void updatePosition(GeyserSession session) {
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
float drag = getDrag(session);
float gravity = getGravity();
motion = motion.mul(drag).down(gravity);
}
/**
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
*
@ -125,6 +140,7 @@ public class ThrowableEntity extends Entity implements Tickable {
@Override
public boolean despawnEntity(GeyserSession session) {
positionUpdater.cancel(true);
if (entityType == EntityType.THROWN_ENDERPEARL) {
LevelEventPacket particlePacket = new LevelEventPacket();
particlePacket.setType(LevelEventType.PARTICLE_TELEPORT);

View File

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

View File

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

View File

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

View File

@ -28,28 +28,19 @@ package org.geysermc.connector.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import lombok.Data;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.InsentientEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.utils.DimensionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class EnderDragonEntity extends InsentientEntity implements Tickable {
public class EnderDragonEntity extends InsentientEntity {
/**
* The Ender Dragon has multiple hit boxes, which
* are each its own invisible entity
@ -70,19 +61,9 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
private final Segment[] segmentHistory = new Segment[19];
private int latestSegment = -1;
private int phase;
/**
* The number of ticks since the beginning of the phase
*/
private int phaseTicks;
private boolean hovering;
private int ticksTillNextGrowl = 100;
/**
* Used to determine when the wing flap sound should be played
*/
private float wingPosition;
private float lastWingPosition;
private ScheduledFuture<?> partPositionUpdater;
public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
@ -92,67 +73,49 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 15) { // Phase
phase = (int) entityMetadata.getValue();
phaseTicks = 0;
metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting());
}
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 8) { // Health
// Update the health attribute, so that the death animation gets played
// Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1
float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH));
if (phase == 9 && health <= 0) { // Dying phase
// Phase
if (entityMetadata.getId() == 15) {
int value = (int) entityMetadata.getValue();
if (value == 5) {
// Performing breath attack
EntityEventPacket entityEventPacket = new EntityEventPacket();
entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH);
entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
entityEventPacket.setRuntimeEntityId(geyserId);
entityEventPacket.setData(0);
session.sendUpstreamPacket(entityEventPacket);
}
attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200));
updateBedrockAttributes(session);
metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7);
hovering = value == 10;
}
}
/**
* Send an updated list of attributes to the Bedrock client.
* This is overwritten to allow the health attribute to differ from
* the health specified in the metadata.
*
* @param session GeyserSession
*/
@Override
public void updateBedrockAttributes(GeyserSession session) {
if (!valid) return;
List<AttributeData> attributes = new ArrayList<>();
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> entry : this.attributes.entrySet()) {
if (!entry.getValue().getType().isBedrockAttribute())
continue;
attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue()));
}
UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
updateAttributesPacket.setRuntimeEntityId(geyserId);
updateAttributesPacket.setAttributes(attributes);
session.sendUpstreamPacket(updateAttributesPacket);
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void spawnEntity(GeyserSession session) {
super.spawnEntity(session);
AddEntityPacket addEntityPacket = new AddEntityPacket();
addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase());
addEntityPacket.setRuntimeEntityId(geyserId);
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation());
addEntityPacket.setEntityType(entityType.getType());
addEntityPacket.getMetadata().putAll(metadata);
AtomicLong nextEntityId = session.getEntityCache().getNextEntityId();
head = new EnderDragonPartEntity(entityId + 1, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 1, 1);
neck = new EnderDragonPartEntity(entityId + 2, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 3, 3);
body = new EnderDragonPartEntity(entityId + 3, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 5, 3);
leftWing = new EnderDragonPartEntity(entityId + 4, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
rightWing = new EnderDragonPartEntity(entityId + 5, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
// Otherwise dragon is always 'dying'
addEntityPacket.getAttributes().add(new AttributeData("minecraft:health", 0.0f, 200f, 200f, 200f));
valid = true;
session.sendUpstreamPacket(addEntityPacket);
head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1);
neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3);
body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3);
leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
tail = new EnderDragonPartEntity[3];
for (int i = 0; i < 3; i++) {
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 2, 2);
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2);
}
allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
@ -166,25 +129,25 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
segmentHistory[i].yaw = rotation.getZ();
segmentHistory[i].y = position.getY();
}
partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
pushSegment();
updateBoundingBoxes(session);
}, 0, 50, TimeUnit.MILLISECONDS);
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
}
@Override
public boolean despawnEntity(GeyserSession session) {
partPositionUpdater.cancel(true);
for (EnderDragonPartEntity part : allParts) {
part.despawnEntity(session);
}
return super.despawnEntity(session);
}
@Override
public void tick(GeyserSession session) {
effectTick(session);
if (!metadata.getFlags().getFlag(EntityFlag.NO_AI) && isAlive()) {
pushSegment();
updateBoundingBoxes(session);
}
}
/**
* Updates the positions of the Ender Dragon's multiple bounding boxes
*
@ -200,7 +163,7 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
// Lowers the head when the dragon sits/hovers
float headDuck;
if (isHovering() || isSitting()) {
if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) {
headDuck = -1f;
} else {
headDuck = baseSegment.y - getSegment(0).y;
@ -230,105 +193,6 @@ public class EnderDragonEntity extends InsentientEntity implements Tickable {
}
}
/**
* Handles the particles and sounds of the Ender Dragon
* @param session GeyserSession.
*/
private void effectTick(GeyserSession session) {
Random random = ThreadLocalRandom.current();
if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) {
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
playSoundPacket.setSound("mob.enderdragon.flap");
playSoundPacket.setPosition(position);
playSoundPacket.setVolume(5f);
playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
session.sendUpstreamPacket(playSoundPacket);
}
if (!isSitting() && !isHovering() && ticksTillNextGrowl-- == 0) {
playGrowlSound(session);
ticksTillNextGrowl = 200 + random.nextInt(200);
}
lastWingPosition = wingPosition;
}
if (isAlive()) {
if (metadata.getFlags().getFlag(EntityFlag.NO_AI)) {
wingPosition = 0.5f;
} else if (isHovering() || isSitting()) {
wingPosition += 0.1f;
} else {
double speed = motion.length();
wingPosition += 0.2f / (speed * 10f + 1) * Math.pow(2, motion.getY());
}
phaseTicks++;
if (phase == 3) { // Landing Phase
float headHeight = head.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT);
Vector3f headCenter = head.getPosition().up(headHeight * 0.5f);
for (int i = 0; i < 8; i++) {
Vector3f particlePos = headCenter.add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f);
// This is missing velocity information
LevelEventPacket particlePacket = new LevelEventPacket();
particlePacket.setType(LevelEventType.PARTICLE_DRAGONS_BREATH);
particlePacket.setPosition(particlePos);
session.sendUpstreamPacket(particlePacket);
}
} else if (phase == 5) { // Sitting Flaming Phase
if (phaseTicks % 2 == 0 && phaseTicks < 10) {
// Performing breath attack
// Entity event DRAGON_FLAMING seems to create particles from the origin of the dragon,
// so we need to manually spawn particles
for (int i = 0; i < 8; i++) {
SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket();
spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
session.sendUpstreamPacket(spawnParticleEffectPacket);
}
}
} else if (phase == 7) { // Sitting Attacking Phase
playGrowlSound(session);
} else if (phase == 9) { // Dying Phase
// Send explosion particles as the dragon move towards the end portal
if (phaseTicks % 10 == 0) {
float xOffset = 8f * (random.nextFloat() - 0.5f);
float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f;
float zOffset = 8f * (random.nextFloat() - 0.5f);
Vector3f particlePos = position.add(xOffset, yOffset, zOffset);
LevelEventPacket particlePacket = new LevelEventPacket();
particlePacket.setType(LevelEventType.PARTICLE_EXPLOSION);
particlePacket.setPosition(particlePos);
session.sendUpstreamPacket(particlePacket);
}
}
}
}
private void playGrowlSound(GeyserSession session) {
Random random = ThreadLocalRandom.current();
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
playSoundPacket.setSound("mob.enderdragon.growl");
playSoundPacket.setPosition(position);
playSoundPacket.setVolume(2.5f);
playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
session.sendUpstreamPacket(playSoundPacket);
}
private boolean isAlive() {
return metadata.getFloat(EntityData.HEALTH) > 0;
}
private boolean isHovering() {
return phase == 10;
}
private boolean isSitting() {
return phase == 5 || phase == 6 || phase == 7;
}
/**
* Store the current yaw and y into the circular buffer
*/

View File

@ -32,12 +32,11 @@ import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.type.EntityType;
public class EnderDragonPartEntity extends Entity {
public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, float width, float height) {
super(entityId, geyserId, entityType, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) {
super(entityId, geyserId, entityType, position, motion, rotation);
metadata.put(EntityData.BOUNDING_BOX_WIDTH, width);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height);
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
}
}

View File

@ -27,10 +27,8 @@ package org.geysermc.connector.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
@ -47,21 +45,11 @@ public class EndermanEntity extends MonsterEntity {
if (entityMetadata.getId() == 15) {
metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue()));
}
// "Is screaming" - controls sound
// 'Angry' - mouth open
if (entityMetadata.getId() == 16) {
if ((boolean) entityMetadata.getValue()) {
LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet();
packet.setSound(SoundEvent.STARE);
packet.setPosition(this.position);
packet.setExtraData(-1);
packet.setIdentifier("minecraft:enderman");
session.sendUpstreamPacket(packet);
}
}
// "Is staring/provoked" - controls visuals
if (entityMetadata.getId() == 17) {
metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue());
}
// TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge
super.updateBedrockMetadata(entityMetadata, session);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
/**
* Used to determine if rename packets should be sent.
*/
public class AnvilContainer extends Container {
public AnvilContainer(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size, playerInventory);
}
}

View File

@ -23,16 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.action;
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam;
import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@AllArgsConstructor
enum Click {
LEFT(ClickItemParam.LEFT_CLICK),
RIGHT(ClickItemParam.RIGHT_CLICK);
@Getter
@Setter
public class BeaconContainer extends Container {
private int primaryId;
private int secondaryId;
public final WindowActionParam actionParam;
public BeaconContainer(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size, playerInventory);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
public class CartographyContainer extends Container {
public CartographyContainer(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size, playerInventory);
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
import lombok.Getter;
import lombok.NonNull;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
/**
* Combination of {@link Inventory} and {@link PlayerInventory}
*/
@Getter
public class Container extends Inventory {
private final PlayerInventory playerInventory;
private final int containerSize;
public Container(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size);
this.playerInventory = playerInventory;
this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE;
}
@Override
public GeyserItemStack getItem(int slot) {
if (slot < this.size) {
return super.getItem(slot);
} else {
return playerInventory.getItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET);
}
}
@Override
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
if (slot < this.size) {
super.setItem(slot, newItem, session);
} else {
playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session);
}
}
@Override
public int getSize() {
return this.containerSize;
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
import lombok.Getter;
public class EnchantingContainer extends Container {
/**
* A cache of what Bedrock sees
*/
@Getter
private final EnchantOptionData[] enchantOptions;
/**
* A mutable cache of what the server sends us
*/
@Getter
private final GeyserEnchantOption[] geyserEnchantOptions;
public EnchantingContainer(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size, playerInventory);
enchantOptions = new EnchantOptionData[3];
geyserEnchantOptions = new GeyserEnchantOption[3];
for (int i = 0; i < geyserEnchantOptions.length; i++) {
geyserEnchantOptions[i] = new GeyserEnchantOption(i);
}
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantData;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A mutable "wrapper" around {@link EnchantOptionData}
*/
@Setter
public class GeyserEnchantOption {
private static final List<EnchantData> EMPTY = Collections.emptyList();
/**
* This: https://cdn.discordapp.com/attachments/613168850925649981/791030657169227816/unknown.png
* is controlled by the server.
* So, of course, we have to throw in some easter eggs. ;)
*/
private static final List<String> ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better",
"explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa",
"tim two zero three", "fast walk nether", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned",
"more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft",
"strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this",
"stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out",
"xp heals tools", "dragon proxy waz here");
@Getter
private final int javaIndex;
private int xpCost = 0;
private int javaEnchantIndex = -1;
private int bedrockEnchantIndex = -1;
private int enchantLevel = -1;
public GeyserEnchantOption(int javaIndex) {
this.javaIndex = javaIndex;
}
public EnchantOptionData build(GeyserSession session) {
if (enchantLevel == -1) {
// Should not be sent to the client, as it is supposed to be empty
return null;
}
return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY,
Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY,
javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), session.getNextItemNetId());
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import lombok.Data;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
@Data
public class GeyserItemStack {
public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null);
private final int javaId;
private int amount;
private CompoundTag nbt;
private int netId;
public GeyserItemStack(int javaId) {
this(javaId, 1);
}
public GeyserItemStack(int javaId, int amount) {
this(javaId, amount, null);
}
public GeyserItemStack(int javaId, int amount, CompoundTag nbt) {
this(javaId, amount, nbt, 1);
}
public GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) {
this.javaId = javaId;
this.amount = amount;
this.nbt = nbt;
this.netId = netId;
}
public int getJavaId() {
return isEmpty() ? 0 : javaId;
}
public int getAmount() {
return isEmpty() ? 0 : amount;
}
public CompoundTag getNbt() {
return isEmpty() ? null : nbt;
}
public void setNetId(int netId) {
this.netId = netId;
}
public int getNetId() {
return isEmpty() ? 0 : netId;
}
public void add(int add) {
amount += add;
}
public void sub(int sub) {
amount -= sub;
}
public static GeyserItemStack from(ItemStack itemStack) {
return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt());
}
public ItemStack getItemStack() {
return isEmpty() ? null : new ItemStack(javaId, amount, nbt);
}
public ItemData getItemData(GeyserSession session) {
ItemData itemData = ItemTranslator.translateToBedrock(session, getItemStack());
itemData.setNetId(getNetId());
return itemData;
}
public ItemEntry getItemEntry() {
return ItemRegistry.ITEM_ENTRIES.get(getJavaId());
}
public boolean isEmpty() {
return amount <= 0 || javaId == 0;
}
public GeyserItemStack copy() {
return copy(amount);
}
public GeyserItemStack copy(int newAmount) {
return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, nbt == null ? null : nbt.clone(), netId);
}
}

View File

@ -25,26 +25,19 @@
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.math.vector.Vector3i;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Arrays;
public class Inventory {
@Getter
protected int id;
@Getter
@Setter
protected boolean open;
@Getter
protected WindowType windowType;
@Getter
protected final int size;
@ -52,9 +45,11 @@ public class Inventory {
@Setter
protected String title;
@Setter
protected ItemStack[] items;
protected GeyserItemStack[] items;
/**
* The location of the inventory block. Will either be a fake block above the player's head, or the actual block location
*/
@Getter
@Setter
protected Vector3i holderPosition = Vector3i.ZERO;
@ -64,27 +59,41 @@ public class Inventory {
protected long holderId = -1;
@Getter
protected AtomicInteger transactionId = new AtomicInteger(1);
protected short transactionId = 0;
public Inventory(int id, WindowType windowType, int size) {
this("Inventory", id, windowType, size);
protected Inventory(int id, int size) {
this("Inventory", id, size);
}
public Inventory(String title, int id, WindowType windowType, int size) {
protected Inventory(String title, int id, int size) {
this.title = title;
this.id = id;
this.windowType = windowType;
this.size = size;
this.items = new ItemStack[size];
this.items = new GeyserItemStack[size];
Arrays.fill(items, GeyserItemStack.EMPTY);
}
public ItemStack getItem(int slot) {
public GeyserItemStack getItem(int slot) {
return items[slot];
}
public void setItem(int slot, ItemStack item) {
if (item != null && (item.getId() == 0 || item.getAmount() < 1))
item = null;
items[slot] = item;
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
GeyserItemStack oldItem = items[slot];
updateItemNetId(oldItem, newItem, session);
items[slot] = newItem;
}
protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) {
if (!newItem.isEmpty()) {
if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) {
newItem.setNetId(oldItem.getNetId());
} else {
newItem.setNetId(session.getNextItemNetId());
}
}
}
public short getNextTransactionId() {
return ++transactionId;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import lombok.Getter;
import lombok.Setter;
public class LecternContainer extends Container {
@Getter @Setter
private int currentBedrockPage = 0;
@Getter @Setter
private NbtMap blockEntityTag;
@Getter @Setter
private Vector3i position;
public LecternContainer(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size, playerInventory);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.inventory;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.entity.Entity;
@Getter
@Setter
public class MerchantContainer extends Container {
private Entity villager;
private VillagerTrade[] villagerTrades;
public MerchantContainer(String title, int id, int size, PlayerInventory playerInventory) {
super(title, id, size, playerInventory);
}
}

View File

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

View File

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

View File

@ -30,16 +30,15 @@ import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.socket.DatagramPacket;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.ping.GeyserPingInfo;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.LanguageUtils;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
@ -95,20 +94,6 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
pong.setMaximumPlayerCount(config.getMaxPlayers());
}
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
// Remove the sub-MOTD first since that only appears locally
pong.setSubMotd("");
if (motdArray.length > 338) {
// If the top MOTD is still too long, we chop it down
byte[] newMotdArray = new byte[339];
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
}
}
//Bedrock will not even attempt a connection if the client thinks the server is full
//so we have to fake it not being full
if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) {

View File

@ -33,9 +33,14 @@ import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.AdvancementsCache;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.utils.*;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LoginEncryptionUtils;
import org.geysermc.connector.utils.MathUtils;
import org.geysermc.connector.utils.ResourcePack;
import org.geysermc.connector.utils.ResourcePackManifest;
import org.geysermc.connector.utils.SettingsUtils;
import org.geysermc.connector.utils.StatisticsUtils;
import java.io.FileInputStream;
import java.io.InputStream;
@ -139,19 +144,12 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(ModalFormResponsePacket packet) {
switch (packet.getFormId()) {
case AdvancementsCache.ADVANCEMENT_INFO_FORM_ID:
return session.getAdvancementsCache().handleInfoForm(packet.getFormData());
case AdvancementsCache.ADVANCEMENTS_LIST_FORM_ID:
return session.getAdvancementsCache().handleListForm(packet.getFormData());
case AdvancementsCache.ADVANCEMENTS_MENU_FORM_ID:
return session.getAdvancementsCache().handleMenuForm(packet.getFormData());
case SettingsUtils.SETTINGS_FORM_ID:
return SettingsUtils.handleSettingsForm(session, packet.getFormData());
case StatisticsUtils.STATISTICS_LIST_FORM_ID:
return StatisticsUtils.handleListForm(session, packet.getFormData());
case StatisticsUtils.STATISTICS_MENU_FORM_ID:
return StatisticsUtils.handleMenuForm(session, packet.getFormData());
if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) {
return SettingsUtils.handleSettingsForm(session, packet.getFormData());
} else if (packet.getFormId() == StatisticsUtils.STATISTICS_MENU_FORM_ID) {
return StatisticsUtils.handleMenuForm(session, packet.getFormData());
} else if (packet.getFormId() == StatisticsUtils.STATISTICS_LIST_FORM_ID) {
return StatisticsUtils.handleListForm(session, packet.getFormData());
}
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
@ -163,7 +161,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
if (info != null) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
session.setMicrosoftAccount(info.isMicrosoftAccount());
session.authenticate(info.getEmail(), info.getPassword());
// TODO send a message to bedrock user telling them they are connected (if nothing like a motd

View File

@ -26,22 +26,19 @@
package org.geysermc.connector.network.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;
import com.github.steveice10.mc.auth.service.MojangAuthenticationService;
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.Client;
@ -57,24 +54,27 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.FormWindow;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.player.SessionPlayerEntity;
import org.geysermc.connector.entity.player.SkullPlayerEntity;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.remote.RemoteServer;
import org.geysermc.connector.network.session.auth.AuthData;
@ -85,7 +85,7 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.skin.SkinManager;
import org.geysermc.connector.utils.*;
@ -98,9 +98,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
@Getter
public class GeyserSession implements CommandSender {
@ -114,23 +113,33 @@ public class GeyserSession implements CommandSender {
@Setter
private BedrockClientData clientData;
@Deprecated
@Setter
private boolean microsoftAccount;
private final SessionPlayerEntity playerEntity;
private PlayerInventory inventory;
private AdvancementsCache advancementsCache;
private BookEditCache bookEditCache;
private ChunkCache chunkCache;
private EntityCache entityCache;
private EntityEffectCache effectCache;
private InventoryCache inventoryCache;
private WorldCache worldCache;
private WindowCache windowCache;
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
private final PlayerInventory playerInventory;
@Setter
private Inventory openInventory;
@Setter
private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
/**
* Use {@link #getNextItemNetId()} instead for consistency
*/
@Getter(AccessLevel.NONE)
private final AtomicInteger itemNetId = new AtomicInteger(1);
@Getter(AccessLevel.NONE)
private final Object inventoryLock = new Object();
@Getter(AccessLevel.NONE)
private CompletableFuture<Void> inventoryFuture;
/**
* Stores session collision
*/
@ -145,6 +154,16 @@ public class GeyserSession implements CommandSender {
*/
private final Object2LongMap<Vector3i> itemFrameCache = new Object2LongOpenHashMap<>();
/**
* Stores a list of all lectern locations and their block entity tags.
* See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)}
* for more information.
*/
private final List<Vector3i> lecternCache = new ArrayList<>();
@Setter
private boolean droppingLecternBook;
@Setter
private Vector2i lastChunkPosition = null;
private int renderDistance;
@ -159,11 +178,7 @@ public class GeyserSession implements CommandSender {
@Setter
private GameMode gameMode = GameMode.SURVIVAL;
/**
* Keeps track of the world name for respawning.
*/
@Setter
private String worldName = null;
private final AtomicInteger pendingDimSwitches = new AtomicInteger(0);
private boolean sneaking;
@ -200,11 +215,11 @@ public class GeyserSession implements CommandSender {
@Setter
private Vector3i lastInteractionPosition = Vector3i.ZERO;
@Setter
private Entity ridingVehicleEntity;
private boolean manyDimPackets = false;
private ServerRespawnPacket lastDimPacket = null;
@Setter
private int craftSlot = 0;
private Entity ridingVehicleEntity;
@Setter
private long lastWindowCloseTime = 0;
@ -214,10 +229,16 @@ public class GeyserSession implements CommandSender {
@Setter
private long lastInteractedVillagerEid;
@Setter
private Int2ObjectMap<Recipe> craftingRecipes;
private final Set<String> unlockedRecipes;
/**
* Stores the enchantment information the client has received if they are in an enchantment table GUI
* Saves a list of all stonecutter recipes, for use in a stonecutter inventory.
* The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier
*/
private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3];
@Setter
private Int2ObjectMap<IntList> stonecutterRecipes;
/**
* The current attack speed of the player. Used for sending proper cooldown timings.
@ -258,14 +279,15 @@ public class GeyserSession implements CommandSender {
private ScheduledFuture<?> bucketScheduledFuture;
/**
* Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
* Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
*/
@Setter
private long lastMovementTimestamp = System.currentTimeMillis();
private ScheduledFuture<?> movementSendIfIdle;
/**
* Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless.
*/
@Setter
private boolean daylightCycle = true;
private boolean reducedDebugInfo = false;
@ -340,36 +362,33 @@ public class GeyserSession implements CommandSender {
private List<UUID> selectedEmotes = new ArrayList<>();
private final Set<UUID> emotes = new HashSet<>();
/**
* The thread that will run every 50 milliseconds - one Minecraft tick.
*/
private ScheduledFuture<?> tickThread = null;
private MinecraftProtocol protocol;
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
this.connector = connector;
this.upstream = new UpstreamSession(bedrockServerSession);
this.advancementsCache = new AdvancementsCache(this);
this.bookEditCache = new BookEditCache(this);
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.effectCache = new EntityEffectCache();
this.inventoryCache = new InventoryCache(this);
this.worldCache = new WorldCache(this);
this.windowCache = new WindowCache(this);
this.collisionManager = new CollisionManager(this);
this.playerEntity = new SessionPlayerEntity(this);
this.inventory = new PlayerInventory();
this.worldCache = new WorldCache(this);
this.windowCache = new WindowCache(this);
this.playerInventory = new PlayerInventory();
this.openInventory = null;
this.inventoryFuture = CompletableFuture.completedFuture(null);
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.unlockedRecipes = new ObjectOpenHashSet<>();
this.spawned = false;
this.loggedIn = false;
this.inventoryCache.getInventories().put(0, inventory);
connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes()));
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
@ -452,22 +471,146 @@ public class GeyserSession implements CommandSender {
new Thread(() -> {
try {
if (password != null && !password.isEmpty()) {
AuthenticationService authenticationService;
if (microsoftAccount) {
authenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
} else {
authenticationService = new MojangAuthenticationService();
}
authenticationService.setUsername(username);
authenticationService.setPassword(password);
authenticationService.login();
protocol = new MinecraftProtocol(authenticationService);
protocol = new MinecraftProtocol(username, password);
} else {
protocol = new MinecraftProtocol(username);
}
connectDownstream();
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
final PublicKey publicKey;
if (floodgate) {
PublicKey key = null;
try {
key = EncryptionUtil.getKeyFromFile(
connector.getConfig().getFloodgateKeyPath(),
PublicKey.class
);
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
}
publicKey = key;
} else publicKey = null;
if (publicKey != null) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
}
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
}
// Let Geyser handle sending the keep alive
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
downstream.getSession().addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket && floodgate) {
String encrypted = "";
try {
encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
clientData.getGameVersion(),
authData.getName(),
authData.getXboxUUID(),
clientData.getDeviceOS().ordinal(),
clientData.getLanguageCode(),
clientData.getCurrentInputMode().ordinal(),
upstream.getSession().getAddress().getAddress().getHostAddress()
));
} catch (Exception e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
}
HandshakePacket handshakePacket = event.getPacket();
event.setPacket(new HandshakePacket(
handshakePacket.getProtocolVersion(),
handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
handshakePacket.getPort(),
handshakePacket.getIntent()
));
}
}
@Override
public void connected(ConnectedEvent event) {
loggingIn = false;
loggedIn = true;
if (protocol.getProfile() == null) {
// Java account is offline
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
return;
}
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
playerEntity.setUuid(protocol.getProfile().getId());
playerEntity.setUsername(protocol.getProfile().getName());
String locale = clientData.getLanguageCode();
// Let the user know there locale may take some time to download
// as it has to be extracted from a JAR
if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
// This should probably be left hardcoded as it will only show for en_us clients
sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
}
// Download and load the language for the player
LocaleUtils.downloadAndLoadLocale(locale);
}
@Override
public void disconnected(DisconnectedEvent event) {
loggingIn = false;
loggedIn = false;
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
if (event.getCause() != null) {
event.getCause().printStackTrace();
}
upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
}
@Override
public void packetReceived(PacketReceivedEvent event) {
if (!closed) {
//handle consecutive respawn packets
if (event.getPacket().getClass().equals(ServerRespawnPacket.class)) {
manyDimPackets = lastDimPacket != null;
lastDimPacket = event.getPacket();
return;
} else if (lastDimPacket != null) {
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(lastDimPacket.getClass(), lastDimPacket, GeyserSession.this);
lastDimPacket = null;
}
// Required, or else Floodgate players break with Bukkit chunk caching
if (event.getPacket() instanceof LoginSuccessPacket) {
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
playerEntity.setUsername(profile.getName());
playerEntity.setUuid(profile.getId());
// Check if they are not using a linked account
if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
SkinManager.handleBedrockSkin(playerEntity, clientData);
}
}
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
}
}
@Override
public void packetError(PacketErrorEvent event) {
connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
if (connector.getConfig().isDebugMode())
event.getCause().printStackTrace();
event.setSuppress(true);
}
});
downstream.getSession().connect();
connector.addPlayer(this);
} catch (InvalidCredentialsException | IllegalArgumentException e) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
@ -477,199 +620,6 @@ public class GeyserSession implements CommandSender {
}).start();
}
/**
* Present a form window to the user asking to log in with another web browser
*/
public void authenticateWithMicrosoftCode() {
if (loggedIn) {
connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().getName()));
return;
}
loggingIn = true;
// new thread so clients don't timeout
new Thread(() -> {
try {
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
LoginEncryptionUtils.showMicrosoftCodeWindow(this, response);
// This just looks cool
SetTimePacket packet = new SetTimePacket();
packet.setTime(16000);
sendUpstreamPacket(packet);
// Wait for the code to validate
attemptCodeAuthentication(msaAuthenticationService);
} catch (InvalidCredentialsException | IllegalArgumentException e) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", getAuthData().getName()));
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
} catch (RequestException ex) {
ex.printStackTrace();
}
}).start();
}
/**
* Poll every second to see if the user has successfully signed in
*/
private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) {
if (loggedIn || closed) {
return;
}
try {
msaAuthenticationService.login();
protocol = new MinecraftProtocol(msaAuthenticationService);
connectDownstream();
} catch (RequestException e) {
if (!(e instanceof AuthPendingException)) {
e.printStackTrace();
} else {
// Wait one second before trying again
connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
}
}
}
/**
* After getting whatever credentials needed, we attempt to join the Java server.
*/
private void connectDownstream() {
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
final PublicKey publicKey;
if (floodgate) {
PublicKey key = null;
try {
key = EncryptionUtil.getKeyFromFile(
connector.getConfig().getFloodgateKeyPath(),
PublicKey.class
);
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
}
publicKey = key;
} else publicKey = null;
if (publicKey != null) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
}
// Start ticking
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
}
// Let Geyser handle sending the keep alive
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
downstream.getSession().addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket && floodgate) {
String encrypted = "";
try {
encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
clientData.getGameVersion(),
authData.getName(),
authData.getXboxUUID(),
clientData.getDeviceOS().ordinal(),
clientData.getLanguageCode(),
clientData.getCurrentInputMode().ordinal(),
upstream.getSession().getAddress().getAddress().getHostAddress()
));
} catch (Exception e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
}
HandshakePacket handshakePacket = event.getPacket();
event.setPacket(new HandshakePacket(
handshakePacket.getProtocolVersion(),
handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
handshakePacket.getPort(),
handshakePacket.getIntent()
));
}
}
@Override
public void connected(ConnectedEvent event) {
loggingIn = false;
loggedIn = true;
if (protocol.getProfile() == null) {
// Java account is offline
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
return;
}
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
playerEntity.setUuid(protocol.getProfile().getId());
playerEntity.setUsername(protocol.getProfile().getName());
String locale = clientData.getLanguageCode();
// Let the user know there locale may take some time to download
// as it has to be extracted from a JAR
if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
// This should probably be left hardcoded as it will only show for en_us clients
sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
}
// Download and load the language for the player
LocaleUtils.downloadAndLoadLocale(locale);
}
@Override
public void disconnected(DisconnectedEvent event) {
loggingIn = false;
loggedIn = false;
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
if (event.getCause() != null) {
event.getCause().printStackTrace();
}
upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
}
@Override
public void packetReceived(PacketReceivedEvent event) {
if (!closed) {
// Required, or else Floodgate players break with Bukkit chunk caching
if (event.getPacket() instanceof LoginSuccessPacket) {
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
playerEntity.setUsername(profile.getName());
playerEntity.setUuid(profile.getId());
// Check if they are not using a linked account
if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
SkinManager.handleBedrockSkin(playerEntity, clientData);
}
}
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
}
}
@Override
public void packetError(PacketErrorEvent event) {
connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
if (connector.getConfig().isDebugMode())
event.getCause().printStackTrace();
event.setSuppress(true);
}
});
if (!daylightCycle) {
setDaylightCycle(true);
}
downstream.getSession().connect();
connector.addPlayer(this);
}
public void disconnect(String reason) {
if (!closed) {
loggedIn = false;
@ -682,17 +632,10 @@ public class GeyserSession implements CommandSender {
}
}
if (tickThread != null) {
tickThread.cancel(true);
}
this.advancementsCache = null;
this.bookEditCache = null;
this.chunkCache = null;
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.windowCache = null;
closed = true;
@ -702,28 +645,6 @@ public class GeyserSession implements CommandSender {
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode()));
}
/**
* Called every 50 milliseconds - one Minecraft tick.
*/
public void tick() {
// Check to see if the player's position needs updating - a position update should be sent once every 3 seconds
if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) {
// Recalculate in case something else changed position
Vector3d position = collisionManager.adjustBedrockPosition(playerEntity.getPosition(), playerEntity.isOnGround());
// A null return value cancels the packet
if (position != null) {
ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(playerEntity.isOnGround(),
position.getX(), position.getY(), position.getZ());
sendDownstreamPacket(packet);
}
lastMovementTimestamp = System.currentTimeMillis();
}
for (Tickable entity : entityCache.getTickableEntities()) {
entity.tick(this);
}
}
public void setAuthenticationData(AuthData authData) {
this.authData = authData;
}
@ -827,15 +748,57 @@ public class GeyserSession implements CommandSender {
startGamePacket.setLevelName(serverName);
startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000");
// startGamePacket.setCurrentTick(0);
startGamePacket.setEnchantmentSeed(0);
startGamePacket.setMultiplayerCorrelationId("");
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
startGamePacket.setVanillaVersion("*");
startGamePacket.setInventoriesServerAuthoritative(true);
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
upstream.sendPacket(startGamePacket);
}
/**
* Adds a new inventory task.
* Inventory tasks are executed one at a time, in order.
*
* @param task the task to run
*/
public void addInventoryTask(Runnable task) {
synchronized (inventoryLock) {
System.out.println("new task " + task.toString());
inventoryFuture = inventoryFuture.thenRun(task).exceptionally(throwable -> {
GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
return null;
});
}
}
/**
* Adds a new inventory task with a delay.
* The delay is achieved by scheduling with the Geyser general thread pool.
* Inventory tasks are executed one at a time, in order.
*
* @param task the delayed task to run
* @param delayMillis delay in milliseconds
*/
public void addInventoryTask(Runnable task, long delayMillis) {
synchronized (inventoryLock) {
System.out.println("new delayed task " + task.toString());
Executor delayedExecutor = command -> GeyserConnector.getInstance().getGeneralThreadPool().schedule(command, delayMillis, TimeUnit.MILLISECONDS);
inventoryFuture = inventoryFuture.thenRunAsync(task, delayedExecutor).exceptionally(throwable -> {
GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause());
return null;
});
}
}
/**
* @return the next Bedrock item network ID to use for a new item
*/
public int getNextItemNetId() {
return itemNetId.getAndIncrement();
}
public void addTeleport(TeleportCache teleportCache) {
teleportMap.put(teleportCache.getTeleportConfirmId(), teleportCache);
@ -958,18 +921,6 @@ public class GeyserSession implements CommandSender {
reducedDebugInfo = value;
}
/**
* Changes the daylight cycle gamerule on the client
* This is used in the login screen along-side normal usage
*
* @param doCycle If the cycle should continue
*/
public void setDaylightCycle(boolean doCycle) {
sendGameRule("dodaylightcycle", doCycle);
// Save the value so we don't have to constantly send a daylight cycle gamerule update
this.daylightCycle = doCycle;
}
/**
* Send a gamerule value to the client
*

View File

@ -1,321 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.common.window.SimpleFormWindow;
import org.geysermc.common.window.button.FormButton;
import org.geysermc.common.window.response.SimpleFormResponse;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.GeyserAdvancement;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AdvancementsCache {
// Different form IDs
public static final int ADVANCEMENTS_MENU_FORM_ID = 1341;
public static final int ADVANCEMENTS_LIST_FORM_ID = 1342;
public static final int ADVANCEMENT_INFO_FORM_ID = 1343;
/**
* Stores the player's advancement progress
*/
@Getter
private final Map<String, Map<String, Long>> storedAdvancementProgress = new HashMap<>();
/**
* Stores advancements for the player.
*/
@Getter
private final Map<String, GeyserAdvancement> storedAdvancements = new HashMap<>();
/**
* Stores player's chosen advancement's ID and title for use in form creators.
*/
@Setter
private String currentAdvancementCategoryId = null;
private final GeyserSession session;
public AdvancementsCache(GeyserSession session) {
this.session = session;
}
/**
* Build a form with all advancement categories
*
* @return The built advancement category menu
*/
public SimpleFormWindow buildMenuForm() {
// Cache the language for cleaner access
String language = session.getClientData().getLanguageCode();
// Created menu window for advancement categories
SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), "");
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement
window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language)));
}
}
if (window.getButtons().isEmpty()) {
window.setContent(LocaleUtils.getLocaleString("advancements.empty", language));
}
return window;
}
/**
* Builds the list of advancements
*
* @return The built list form
*/
public SimpleFormWindow buildListForm() {
// Cache the language for easier access
String language = session.getLocale();
String id = currentAdvancementCategoryId;
GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId);
// Create the window
SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language),
MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
if (id != null) {
for (Map.Entry<String, GeyserAdvancement> advancementEntry : storedAdvancements.entrySet()) {
GeyserAdvancement advancement = advancementEntry.getValue();
if (advancement != null) {
if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) {
boolean earned = isEarned(advancement);
if (earned || !advancement.getDisplayData().isShowToast()) {
window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
} else {
window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
}
}
}
}
}
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
return window;
}
/**
* Builds the advancement display info based on the chosen category
*
* @param advancement The advancement used to create the info display
* @return The information for the chosen advancement
*/
public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) {
// Cache language for easier access
String language = session.getLocale();
String earned = isEarned(advancement) ? "yes" : "no";
String description = getColorFromAdvancementFrameType(advancement) + MessageTranslator.convertMessage(advancement.getDisplayData().getDescription(), language);
String earnedString = LanguageUtils.getPlayerLocaleString("geyser.advancements.earned", language, LocaleUtils.getLocaleString("gui." + earned, language));
/*
Layout will look like:
(Form title) Stone Age
(Description) Mine stone with your new pickaxe
Earned: Yes
Parent Advancement: Minecraft // If relevant
*/
String content = description + "\n\n§f" +
earnedString + "\n";
if (!currentAdvancementCategoryId.equals(advancement.getParentId())) {
// Only display the parent if it is not the category
content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language));
}
SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content);
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
return window;
}
/**
* Determine if this advancement has been earned.
*
* @param advancement the advancement to determine
* @return true if the advancement has been earned.
*/
public boolean isEarned(GeyserAdvancement advancement) {
boolean earned = false;
if (advancement.getRequirements().size() == 0) {
// Minecraft handles this case, so we better as well
return false;
}
Map<String, Long> progress = storedAdvancementProgress.get(advancement.getId());
if (progress != null) {
// Each advancement's requirement must be fulfilled
// For example, [[zombie, blaze, skeleton]] means that one of those three categories must be achieved
// But [[zombie], [blaze], [skeleton]] means that all three requirements must be completed
for (List<String> requirements : advancement.getRequirements()) {
boolean requirementsDone = false;
for (String requirement : requirements) {
Long obtained = progress.get(requirement);
// -1 means that this particular component required for completing the advancement
// has yet to be fulfilled
if (obtained != null && !obtained.equals(-1L)) {
requirementsDone = true;
break;
}
}
if (!requirementsDone) {
return false;
}
}
earned = true;
}
return earned;
}
/**
* Handle the menu form response
*
* @param response The response string to parse
* @return True if the form was parsed correctly, false if not
*/
public boolean handleMenuForm(String response) {
SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID);
menuForm.setResponse(response);
SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse();
String id = "";
if (formResponse != null && formResponse.getClickedButton() != null) {
int advancementIndex = 0;
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
if (advancement.getValue().getParentId() == null) { // Root advancement
if (advancementIndex == formResponse.getClickedButtonId()) {
id = advancement.getKey();
break;
} else {
advancementIndex++;
}
}
}
}
if (!id.equals("")) {
if (id.equals(currentAdvancementCategoryId)) {
// The server thinks we are already on this tab
session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
} else {
// Send a packet indicating that we intend to open this particular advancement window
ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id);
session.sendDownstreamPacket(packet);
// Wait for a response there
}
}
return true;
}
/**
* Handle the list form response (Advancement category choice)
*
* @param response The response string to parse
* @return True if the form was parsed correctly, false if not
*/
public boolean handleListForm(String response) {
SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID);
listForm.setResponse(response);
SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
GeyserAdvancement advancement = null;
int advancementIndex = 0;
// Loop around to find the advancement that the client pressed
for (GeyserAdvancement advancementEntry : storedAdvancements.values()) {
if (advancementEntry.getParentId() != null &&
currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) {
if (advancementIndex == formResponse.getClickedButtonId()) {
advancement = advancementEntry;
break;
} else {
advancementIndex++;
}
}
}
if (advancement != null) {
session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID);
} else {
session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID);
// Indicate that we have closed the current advancement tab
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
}
} else {
// Indicate that we have closed the current advancement tab
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
}
return true;
}
/**
* Handle the info form response
*
* @param response The response string to parse
* @return True if the form was parsed correctly, false if not
*/
public boolean handleInfoForm(String response) {
SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID);
listForm.setResponse(response);
SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
}
return true;
}
public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) {
String base = "\u00a7";
if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) {
return base + "5";
}
return base + "a"; // Used for types TASK and GOAL
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.session.cache;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
/**
* Manages updating the current writable book.
*
* Java sends book updates less frequently than Bedrock, and this can cause issues with servers that rate limit
* book packets. Because of this, we need to ensure packets are only send every second or so at maximum.
*/
public class BookEditCache {
private final GeyserSession session;
@Setter
private ClientEditBookPacket packet;
/**
* Stores the last time a book update packet was sent to the server.
*/
private long lastBookUpdate;
public BookEditCache(GeyserSession session) {
this.session = session;
}
/**
* Check to see if there is a book edit update to send, and if so, send it.
*/
public void checkForSend() {
if (packet == null) {
// No new packet has to be sent
return;
}
// Prevent kicks due to rate limiting - specifically on Spigot servers
if ((System.currentTimeMillis() - lastBookUpdate) < 1000) {
return;
}
// Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions
ItemStack itemStack = session.getInventory().getItemInHand();
if (itemStack == null || itemStack.getId() != ItemRegistry.WRITABLE_BOOK.getJavaId()) {
packet = null;
return;
}
session.getDownstream().getSession().send(packet);
packet = null;
lastBookUpdate = System.currentTimeMillis();
}
}

View File

@ -28,7 +28,6 @@ package org.geysermc.connector.network.session.cache;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import org.geysermc.connector.entity.Tickable;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
@ -41,21 +40,17 @@ import java.util.concurrent.atomic.AtomicLong;
* for that player (e.g. seeing vanished players from /vanish)
*/
public class EntityCache {
private final GeyserSession session;
private GeyserSession session;
@Getter
private Long2ObjectMap<Entity> entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
/**
* A list of all entities that must be ticked.
*/
private final List<Tickable> tickableEntities = Collections.synchronizedList(new ArrayList<>());
private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
private Map<UUID, PlayerEntity> playerEntities = Collections.synchronizedMap(new HashMap<>());
private Map<UUID, BossBar> bossBars = Collections.synchronizedMap(new HashMap<>());
private final Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
private Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
@Getter
private final AtomicLong nextEntityId = new AtomicLong(2L);
private AtomicLong nextEntityId = new AtomicLong(2L);
public EntityCache(GeyserSession session) {
this.session = session;
@ -64,11 +59,6 @@ public class EntityCache {
public void spawnEntity(Entity entity) {
if (cacheEntity(entity)) {
entity.spawnEntity(session);
if (entity instanceof Tickable) {
// Start ticking it
tickableEntities.add((Tickable) entity);
}
}
}
@ -86,10 +76,6 @@ public class EntityCache {
if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) {
long geyserId = entityIdTranslations.remove(entity.getEntityId());
entities.remove(geyserId);
if (entity instanceof Tickable) {
tickableEntities.remove(entity);
}
return true;
}
return false;
@ -166,8 +152,4 @@ public class EntityCache {
public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) {
cachedPlayerEntityLinks.put(playerId, linkedEntityId);
}
public List<Tickable> getTickableEntities() {
return tickableEntities;
}
}

View File

@ -37,10 +37,10 @@ import org.geysermc.connector.network.session.GeyserSession;
public class WindowCache {
private final GeyserSession session;
private GeyserSession session;
@Getter
private final Int2ObjectMap<FormWindow> windows = new Int2ObjectOpenHashMap<>();
private Int2ObjectMap<FormWindow> windows = new Int2ObjectOpenHashMap<>();
public WindowCache(GeyserSession session) {
this.session = session;

View File

@ -1,123 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import com.nukkitx.protocol.bedrock.packet.BookEditPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@Translator(packet = BookEditPacket.class)
public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket> {
@Override
public void translate(BookEditPacket packet, GeyserSession session) {
ItemStack itemStack = session.getInventory().getItemInHand();
if (itemStack != null) {
CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag("");
ItemStack bookItem = new ItemStack(itemStack.getId(), itemStack.getAmount(), tag);
List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
int page = packet.getPageNumber();
// Creative edits the NBT for us
if (session.getGameMode() != GameMode.CREATIVE) {
switch (packet.getAction()) {
case ADD_PAGE: {
// Add empty pages in between
for (int i = pages.size(); i < page; i++) {
pages.add(i, new StringTag("", ""));
}
pages.add(page, new StringTag("", packet.getText()));
break;
}
// Called whenever a page is modified
case REPLACE_PAGE: {
if (page < pages.size()) {
pages.set(page, new StringTag("", packet.getText()));
} else {
// Add empty pages in between
for (int i = pages.size(); i < page; i++) {
pages.add(i, new StringTag("", ""));
}
pages.add(page, new StringTag("", packet.getText()));
}
break;
}
case DELETE_PAGE: {
if (page < pages.size()) {
pages.remove(page);
}
break;
}
case SWAP_PAGES: {
int page2 = packet.getSecondaryPageNumber();
if (page < pages.size() && page2 < pages.size()) {
Collections.swap(pages, page, page2);
}
break;
}
case SIGN_BOOK: {
tag.put(new StringTag("author", packet.getAuthor()));
tag.put(new StringTag("title", packet.getTitle()));
break;
}
default:
return;
}
}
// Remove empty pages at the end
while (pages.size() > 0) {
StringTag currentPage = (StringTag) pages.get(pages.size() - 1);
if (currentPage.getValue() == null || currentPage.getValue().isEmpty()) {
pages.remove(pages.size() - 1);
} else {
break;
}
}
tag.put(new ListTag("pages", pages));
session.getInventory().setItem(36 + session.getInventory().getHeldItemSlot(), bookItem);
InventoryTranslator.INVENTORY_TRANSLATORS.get(null).updateInventory(session, session.getInventory());
session.getBookEditCache().setPacket(new ClientEditBookPacket(bookItem, packet.getAction() == BookEditPacket.Action.SIGN_BOOK, session.getInventory().getHeldItemSlot()));
// There won't be any more book updates after this, so we can try sending the edit packet immediately
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
session.getBookEditCache().checkForSend();
}
}
}
}

View File

@ -38,24 +38,24 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
@Override
public void translate(ContainerClosePacket packet, GeyserSession session) {
session.setLastWindowCloseTime(0);
byte windowId = packet.getId();
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (windowId == -1) { //player inventory or crafting table
if (openInventory != null) {
windowId = (byte) openInventory.getId();
} else {
windowId = 0;
session.addInventoryTask(() -> {
session.setLastWindowCloseTime(0);
byte windowId = packet.getId();
if (windowId == -1 && session.getOpenInventory() != null) {
windowId = (byte) session.getOpenInventory().getId();
}
}
if (windowId == 0 || (openInventory != null && openInventory.getId() == windowId)) {
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.getDownstream().getSession().send(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId);
}
Inventory openInventory = session.getOpenInventory();
if (openInventory != null && windowId == openInventory.getId()) {
System.out.println(packet);
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId);
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, windowId);
}
//Client wants close confirmation
session.sendUpstreamPacket(packet);
//Client wants close confirmation
session.sendUpstreamPacket(packet);
});
}
}

View File

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

View File

@ -25,7 +25,6 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
@ -39,58 +38,68 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye
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.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import org.geysermc.connector.entity.CommandBlockMinecartEntity;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.ItemFrameEntity;
import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.concurrent.TimeUnit;
/**
* BedrockInventoryTransactionTranslator handles most interactions between the client and the world,
* or the client and their inventory.
*/
@Translator(packet = InventoryTransactionPacket.class)
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f;
private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49;
private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36;
private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f;
@Override
public void translate(InventoryTransactionPacket packet, GeyserSession session) {
// Send book updates before opening inventories
session.getBookEditCache().checkForSend();
switch (packet.getTransactionType()) {
case NORMAL:
Inventory inventory = session.getInventoryCache().getOpenInventory();
if (inventory == null) inventory = session.getInventory();
InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()).translateActions(session, inventory, packet.getActions());
System.out.println(packet);
if (packet.getActions().size() == 2) {
InventoryActionData worldAction = packet.getActions().get(0);
InventoryActionData containerAction = packet.getActions().get(1);
if (worldAction.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
&& worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
session.addInventoryTask(() -> {
if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot())
return;
if (session.getPlayerInventory().getItemInHand().isEmpty())
return;
boolean dropAll = worldAction.getToItem().getCount() > 1;
ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket(
dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
new Position(0, 0, 0),
BlockFace.DOWN
);
session.sendDownstreamPacket(dropAllPacket);
if (dropAll) {
session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY);
} else {
session.getPlayerInventory().getItemInHand().sub(1);
}
});
}
}
break;
case INVENTORY_MISMATCH:
Inventory inv = session.getInventoryCache().getOpenInventory();
if (inv == null) inv = session.getInventory();
InventoryTranslator.INVENTORY_TRANSLATORS.get(inv.getWindowType()).updateInventory(session, inv);
InventoryUtils.updateCursor(session);
break;
case ITEM_USE:
switch (packet.getActionType()) {
@ -120,46 +129,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
break;
}
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
/*
Checks to ensure that the range will be accepted by the server.
"Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess),
but how much a server will accept from the client maximum
*/
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch
Vector3f playerPosition = session.getPlayerEntity().getPosition();
EntityFlags flags = session.getPlayerEntity().getMetadata().getFlags();
// Adjust position for current eye height
if (flags.getFlag(EntityFlag.SNEAKING)) {
playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 1.27f), 0);
} else if (flags.getFlag(EntityFlag.SWIMMING) || flags.getFlag(EntityFlag.GLIDING) || flags.getFlag(EntityFlag.DAMAGE_NEARBY_MOBS)) {
// Swimming, gliding, or using the trident spin attack
playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 0.4f), 0);
} else if (flags.getFlag(EntityFlag.SLEEPING)) {
playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 0.2f), 0);
} // else, we don't have to modify the position
float diffX = playerPosition.getX() - packet.getBlockPosition().getX();
float diffY = playerPosition.getY() - packet.getBlockPosition().getY();
float diffZ = playerPosition.getZ() - packet.getBlockPosition().getZ();
if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) >
(session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
restoreCorrectBlock(session, blockPos, packet);
return;
}
// Vanilla check
if (!(session.getPlayerEntity().getPosition().sub(0, EntityType.PLAYER.getOffset(), 0)
.distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
restoreCorrectBlock(session, blockPos, packet);
return;
}
/*
Block place checks end - client is good to go
*/
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
BlockFace.values()[packet.getBlockFace()],
@ -207,6 +176,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}
}
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
ItemEntry handItem = ItemRegistry.getItem(packet.getItemInHand());
if (handItem.isBlock()) {
session.setLastBlockPlacePosition(blockPos);
@ -215,9 +185,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.setInteracting(true);
break;
case 1:
ItemStack shieldSlot = session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36);
// Handled in Entity.java
if (shieldSlot != null && shieldSlot.getId() == ItemRegistry.SHIELD.getJavaId()) {
if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
break;
}
@ -231,32 +200,19 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendDownstreamPacket(useItemPacket);
break;
case 2:
int blockState = session.getGameMode() == GameMode.CREATIVE ?
session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState);
if (session.getGameMode() == GameMode.CREATIVE || (session.getConnector().getConfig().isCacheChunks() && blockHardness == 0)) {
session.setLastBlockPlacedId(null);
session.setLastBlockPlacePosition(null);
session.setLastBlockPlacedId(null);
session.setLastBlockPlacePosition(null);
// Same deal with vanilla block placing as above.
// This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player.
playerPosition = session.getPlayerEntity().getPosition();
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat();
diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f);
diffY = (playerPosition.getY() - EntityType.PLAYER.getOffset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f;
diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f);
float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ;
if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
return;
LevelEventPacket blockBreakPacket = new LevelEventPacket();
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
session.sendUpstreamPacket(blockBreakPacket);
}
LevelEventPacket blockBreakPacket = new LevelEventPacket();
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
session.sendUpstreamPacket(blockBreakPacket);
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition());
if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) {
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking());
@ -330,34 +286,4 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
break;
}
}
/**
* Restore the correct block state from the server without updating the chunk cache.
*
* @param session the session of the Bedrock client
* @param blockPos the block position to restore
*/
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
int javaBlockState = session.getConnector().getWorldManager().getBlockAt(session, blockPos);
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
updateBlockPacket.setDataLayer(0);
updateBlockPacket.setBlockPosition(blockPos);
updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(javaBlockState));
updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(updateBlockPacket);
UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket();
updateWaterPacket.setDataLayer(1);
updateWaterPacket.setBlockPosition(blockPos);
updateWaterPacket.setRuntimeId(BlockTranslator.isWaterlogged(javaBlockState) ? BlockTranslator.BEDROCK_WATER_ID : BlockTranslator.BEDROCK_AIR_ID);
updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(updateWaterPacket);
// Reset the item in hand to prevent "missing" blocks
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.INVENTORY);
slotPacket.setSlot(packet.getHotbarSlot());
slotPacket.setItem(packet.getItemInHand());
session.sendUpstreamPacket(slotPacket);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
@Translator(packet = ItemStackRequestPacket.class)
public class BedrockItemStackRequestTranslator extends PacketTranslator<ItemStackRequestPacket> {
@Override
public void translate(ItemStackRequestPacket packet, GeyserSession session) {
session.getConnector().getLogger().info(packet.toString());
Inventory inventory = session.getOpenInventory();
if (inventory == null)
return;
InventoryTranslator translator = session.getInventoryTranslator();
session.addInventoryTask(() -> translator.translateRequests(session, inventory, packet.getRequests()));
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.LecternUpdatePacket;
import org.geysermc.connector.inventory.LecternContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.InventoryUtils;
@Translator(packet = LecternUpdatePacket.class)
public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpdatePacket> {
@Override
public void translate(LecternUpdatePacket packet, GeyserSession session) {
session.getConnector().getLogger().error(packet.toString());
if (packet.isDroppingBook()) {
// Bedrock drops the book outside of the GUI. Java drops it in the GUI
// So, we enter the GUI and then drop it! :)
session.setDroppingLecternBook(true);
Vector3f diff = session.getPlayerEntity().getPosition().sub(packet.getBlockPosition().toFloat());
System.out.println(diff);
// Emulate an interact packet
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
BlockFace.values()[0],
Hand.MAIN_HAND,
packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ(), //TODO
false);
session.sendDownstreamPacket(blockPacket);
} else {
// Bedrock wants to either move a page or exit
LecternContainer lecternContainer = (LecternContainer) session.getOpenInventory();
if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) {
// The same page means Bedrock is closing the window
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId());
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, lecternContainer.getId());
} else {
// Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages)
// Each "page" on Java is just one page (think a spiral notebook folded back to only show one page)
int newJavaPage = (packet.getPage() * 2);
int currentJavaPage = (lecternContainer.getCurrentBedrockPage() * 2);
// Send as many click button packets as we need to
// Java has the option to specify exact page numbers by adding 100 to the number, but buttonId variable
// is a byte and therefore this stops us at 128
if (newJavaPage > currentJavaPage) {
for (int i = currentJavaPage; i < newJavaPage; i++) {
ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 2);
System.out.println(clickButtonPacket);
session.sendDownstreamPacket(clickButtonPacket);
}
} else {
for (int i = currentJavaPage; i > newJavaPage; i--) {
ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 1);
System.out.println(clickButtonPacket);
session.sendDownstreamPacket(clickButtonPacket);
}
}
}
}
}
}

View File

@ -40,15 +40,12 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
@Override
public void translate(MobEquipmentPacket packet, GeyserSession session) {
if (!session.isSpawned() || packet.getHotbarSlot() > 8 ||
packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) {
// For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention
return;
}
// Send book update before switching hotbar slot
session.getBookEditCache().checkForSend();
session.getInventory().setHeldItemSlot(packet.getHotbarSlot());
session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot());
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
session.sendDownstreamPacket(changeHeldItemPacket);

View File

@ -26,12 +26,13 @@
package org.geysermc.connector.network.translators.bedrock.entity;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSelectTradePacket;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.MerchantContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -47,20 +48,25 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
session.sendUpstreamPacket(packet);
return;
case COMPLETE_TRADE:
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
session.sendDownstreamPacket(selectTradePacket);
session.addInventoryTask(() -> {
ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData());
session.sendDownstreamPacket(selectTradePacket);
});
Entity villager = session.getPlayerEntity();
Inventory openInventory = session.getInventoryCache().getOpenInventory();
if (openInventory != null && openInventory.getWindowType() == WindowType.MERCHANT) {
VillagerTrade[] trades = session.getVillagerTrades();
if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) {
VillagerTrade trade = session.getVillagerTrades()[packet.getData()];
openInventory.setItem(2, trade.getOutput());
villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP));
villager.updateBedrockMetadata(session);
session.addInventoryTask(() -> {
Entity villager = session.getPlayerEntity();
Inventory openInventory = session.getOpenInventory();
if (openInventory instanceof MerchantContainer) {
MerchantContainer merchantInventory = (MerchantContainer) openInventory;
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);
villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP));
villager.updateBedrockMetadata(session);
}
}
}
}, 100);
return;
}
session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString());

View File

@ -25,16 +25,13 @@
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
@ -43,12 +40,10 @@ import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.BlockUtils;
@ -63,11 +58,6 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
if (entity == null)
return;
// Send book update before any player action
if (packet.getAction() != PlayerActionPacket.Action.RESPAWN) {
session.getBookEditCache().checkForSend();
}
Vector3i vector = packet.getBlockPosition();
Position position = new Position(vector.getX(), vector.getY(), vector.getZ());
@ -136,27 +126,6 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
break;
case START_BREAK:
if (session.getConnector().getConfig().isCacheChunks()) {
// Start the block breaking animation
if (session.getGameMode() != GameMode.CREATIVE) {
int blockState = session.getConnector().getWorldManager().getBlockAt(session, vector);
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState);
LevelEventPacket startBreak = new LevelEventPacket();
startBreak.setType(LevelEventType.BLOCK_START_BREAK);
startBreak.setPosition(vector.toFloat());
PlayerInventory inventory = session.getInventory();
ItemStack item = inventory.getItemInHand();
ItemEntry itemEntry = null;
CompoundTag nbtData = new CompoundTag("");
if (item != null) {
itemEntry = ItemRegistry.getItem(item);
nbtData = item.getNbt();
}
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, blockState, itemEntry, nbtData, session) * 20);
startBreak.setData((int) (65535 / breakTime));
session.setBreakingBlock(blockState);
session.sendUpstreamPacket(startBreak);
}
// Account for fire - the client likes to hit the block behind.
Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace());
int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos);
@ -165,44 +134,37 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(),
fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]);
session.sendDownstreamPacket(startBreakingPacket);
if (session.getGameMode() == GameMode.CREATIVE) {
break;
}
break;
}
}
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, position, BlockFace.values()[packet.getFace()]);
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
session.sendDownstreamPacket(startBreakingPacket);
break;
case CONTINUE_BREAK:
if (session.getGameMode() == GameMode.CREATIVE) {
break;
}
LevelEventPacket continueBreakPacket = new LevelEventPacket();
continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK);
continueBreakPacket.setData((BlockTranslator.getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24));
continueBreakPacket.setPosition(vector.toFloat());
continueBreakPacket.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock()));
continueBreakPacket.setPosition(packet.getBlockPosition().toFloat());
session.sendUpstreamPacket(continueBreakPacket);
break;
case ABORT_BREAK:
ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, position, BlockFace.DOWN);
ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, new Position(packet.getBlockPosition().getX(),
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.DOWN);
session.sendDownstreamPacket(abortBreakingPacket);
LevelEventPacket stopBreak = new LevelEventPacket();
stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK);
stopBreak.setPosition(vector.toFloat());
stopBreak.setData(0);
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
session.sendUpstreamPacket(stopBreak);
break;
case STOP_BREAK:
// Handled in BedrockInventoryTransactionTranslator
break;
case DIMENSION_CHANGE_SUCCESS:
//sometimes the client doesn't feel like loading
PlayStatusPacket spawnPacket = new PlayStatusPacket();
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendUpstreamPacket(spawnPacket);
entity.updateBedrockAttributes(session);
session.getEntityCache().updateBossBars();
if (session.getPendingDimSwitches().decrementAndGet() == 0) {
//sometimes the client doesn't feel like loading
PlayStatusPacket spawnPacket = new PlayStatusPacket();
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
session.sendUpstreamPacket(spawnPacket);
entity.updateBedrockAttributes(session);
session.getEntityCache().updateBossBars();
}
break;
case JUMP:
session.setJumping(true);

View File

@ -38,7 +38,10 @@ import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InteractPacket;
import lombok.Getter;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity;
import org.geysermc.connector.entity.living.animal.horse.HorseEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -98,7 +101,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
switch (packet.getAction()) {
case INTERACT:
if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD.getJavaId()) {
if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) {
break;
}
ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(),
@ -122,7 +125,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
if (interactEntity == null)
return;
EntityDataMap entityMetadata = interactEntity.getMetadata();
ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand());
ItemEntry itemEntry = session.getPlayerInventory().getItemInHand() == GeyserItemStack.EMPTY ? ItemEntry.AIR : ItemRegistry.getItem(session.getPlayerInventory().getItemInHand().getItemStack());
String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", "");
// TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen
@ -136,8 +139,8 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == 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.getInventory().getItemInHand().getNbt() != null &&
session.getInventory().getItemInHand().getNbt().contains("display")) {
} 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 (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) &&
@ -210,6 +213,11 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
case SKELETON_HORSE:
case TRADER_LLAMA:
case ZOMBIE_HORSE:
boolean tamed = entityMetadata.getFlags().getFlag(EntityFlag.TAMED);
if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || entityMetadata.getFlags().getFlag(EntityFlag.CHESTED))) {
interactiveTag = InteractiveTag.OPEN_CONTAINER;
break;
}
// have another switch statement as, while these share mount attributes they don't share food
switch (interactEntity.getEntityType()) {
case LLAMA:
@ -228,9 +236,9 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
}
if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) {
// Can't ride a baby
if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) {
if (tamed) {
interactiveTag = InteractiveTag.RIDE_HORSE;
} else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) {
} else if (itemEntry.equals(ItemEntry.AIR)) {
// Can't hide an untamed entity without having your hand empty
interactiveTag = InteractiveTag.MOUNT;
}
@ -349,20 +357,30 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
} else {
if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) {
// No interactive tag should be sent
session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG);
session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, "");
session.getPlayerEntity().updateBedrockMetadata(session);
}
}
break;
case OPEN_INVENTORY:
if (!session.getInventory().isOpen()) {
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
containerOpenPacket.setId((byte) 0);
containerOpenPacket.setType(ContainerType.INVENTORY);
containerOpenPacket.setUniqueEntityId(-1);
containerOpenPacket.setBlockPosition(entity.getPosition().toInt());
session.sendUpstreamPacket(containerOpenPacket);
session.getInventory().setOpen(true);
if (session.getOpenInventory() == null) {
Entity ridingEntity = session.getRidingVehicleEntity();
if (ridingEntity instanceof AbstractHorseEntity) {
if (ridingEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) {
// We should request to open the horse inventory instead
ClientPlayerStatePacket openHorseWindowPacket = new ClientPlayerStatePacket((int)session.getPlayerEntity().getEntityId(), PlayerState.OPEN_HORSE_INVENTORY);
session.sendDownstreamPacket(openHorseWindowPacket);
}
} else {
session.setOpenInventory(session.getPlayerInventory());
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
containerOpenPacket.setId((byte) 0);
containerOpenPacket.setType(ContainerType.INVENTORY);
containerOpenPacket.setUniqueEntityId(-1);
containerOpenPacket.setBlockPosition(entity.getPosition().toInt());
session.sendUpstreamPacket(containerOpenPacket);
}
}
break;
}

View File

@ -33,12 +33,16 @@ import com.nukkitx.math.vector.Vector3d;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import java.util.concurrent.TimeUnit;
@Translator(packet = MovePlayerPacket.class)
public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPacket> {
@ -46,7 +50,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
@Override
public void translate(MovePlayerPacket packet, GeyserSession session) {
PlayerEntity entity = session.getPlayerEntity();
if (!session.isSpawned()) return;
if (!session.isSpawned() || session.getPendingDimSwitches().get() > 0) return;
if (!session.getUpstream().isInitialized()) {
MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket();
@ -59,10 +63,9 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
return;
}
session.setLastMovementTimestamp(System.currentTimeMillis());
// Send book update before the player moves
session.getBookEditCache().checkForSend();
if (session.getMovementSendIfIdle() != null) {
session.getMovementSendIfIdle().cancel(true);
}
if (session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0))) {
// head yaw, pitch, head yaw
@ -83,7 +86,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
session.sendDownstreamPacket(playerRotationPacket);
} else {
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround());
Vector3d position = adjustBedrockPosition(session, packet.getPosition(), packet.isOnGround());
if (position != null) { // A null return value cancels the packet
if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) {
Packet movePacket;
@ -125,7 +128,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
} else {
// Not a valid move
session.getConnector().getLogger().debug("Recalculating position...");
session.getCollisionManager().recalculatePosition();
recalculatePosition(session);
}
}
}
@ -138,9 +141,13 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
if (entity.getRightParrot() != null) {
entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false);
}
// Schedule a position send loop if the player is idle
session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session),
3, TimeUnit.SECONDS));
}
private boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
if (mode != MovePlayerPacket.Mode.NORMAL)
return true;
@ -164,5 +171,81 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
return true;
}
/**
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
* the two versions.
*
* @param session the current GeyserSession
* @param bedrockPosition the current Bedrock position of the client
* @param onGround whether the Bedrock player is on the ground
* @return the position to send to the Java server, or null to cancel sending the packet
*/
private Vector3d adjustBedrockPosition(GeyserSession session, Vector3f bedrockPosition, boolean onGround) {
// We need to parse the float as a string since casting a float to a double causes us to
// lose precision and thus, causes players to get stuck when walking near walls
double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset();
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
if (session.getConnector().getConfig().isCacheChunks()) {
// With chunk caching, we can do some proper collision checks
CollisionManager collisionManager = session.getCollisionManager();
collisionManager.updatePlayerBoundingBox(position);
// Correct player position
if (!collisionManager.correctPlayerPosition()) {
// Cancel the movement if it needs to be cancelled
recalculatePosition(session);
return null;
}
position = Vector3d.from(collisionManager.getPlayerBoundingBox().getMiddleX(),
collisionManager.getPlayerBoundingBox().getMiddleY() - (collisionManager.getPlayerBoundingBox().getSizeY() / 2),
collisionManager.getPlayerBoundingBox().getMiddleZ());
} else {
// When chunk caching is off, we have to rely on this
// It rounds the Y position up to the nearest 0.5
// This snaps players to snap to the top of stairs and slabs like on Java Edition
// However, it causes issues such as the player floating on carpets
if (onGround) javaY = Math.ceil(javaY * 2) / 2;
position = position.up(javaY - position.getY());
}
return position;
}
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
public void recalculatePosition(GeyserSession session) {
PlayerEntity entity = session.getPlayerEntity();
// Gravity might need to be reset...
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().putAll(entity.getMetadata());
session.sendUpstreamPacket(entityDataPacket);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
session.sendUpstreamPacket(movePlayerPacket);
}
private void sendPositionIfIdle(GeyserSession session) {
if (session.isClosed()) return;
PlayerEntity entity = session.getPlayerEntity();
// Recalculate in case something else changed position
Vector3d position = adjustBedrockPosition(session, entity.getPosition(), entity.isOnGround());
// A null return value cancels the packet
if (position != null) {
ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(session.getPlayerEntity().isOnGround(),
position.getX(), position.getY(), position.getZ());
session.sendDownstreamPacket(packet);
}
session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session),
3, TimeUnit.SECONDS));
}
}

View File

@ -30,12 +30,8 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
@ -109,7 +105,6 @@ public class CollisionManager {
// According to the Minecraft Wiki, when sneaking:
// - In Bedrock Edition, the height becomes 1.65 blocks, allowing movement through spaces as small as 1.75 (2 - 14) blocks high.
// - In Java Edition, the height becomes 1.5 blocks.
// TODO: Have this depend on the player's literal bounding box variable
if (session.isSneaking()) {
playerBoundingBox.setSizeY(1.5);
} else {
@ -118,65 +113,6 @@ public class CollisionManager {
}
}
/**
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
* the two versions.
*
* @param bedrockPosition the current Bedrock position of the client
* @param onGround whether the Bedrock player is on the ground
* @return the position to send to the Java server, or null to cancel sending the packet
*/
public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround) {
// We need to parse the float as a string since casting a float to a double causes us to
// lose precision and thus, causes players to get stuck when walking near walls
double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset();
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
if (session.getConnector().getConfig().isCacheChunks()) {
// With chunk caching, we can do some proper collision checks
updatePlayerBoundingBox(position);
// Correct player position
if (!correctPlayerPosition()) {
// Cancel the movement if it needs to be cancelled
recalculatePosition();
return null;
}
position = Vector3d.from(playerBoundingBox.getMiddleX(),
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
playerBoundingBox.getMiddleZ());
} else {
// When chunk caching is off, we have to rely on this
// It rounds the Y position up to the nearest 0.5
// This snaps players to snap to the top of stairs and slabs like on Java Edition
// However, it causes issues such as the player floating on carpets
if (onGround) javaY = Math.ceil(javaY * 2) / 2;
position = position.up(javaY - position.getY());
}
return position;
}
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
public void recalculatePosition() {
PlayerEntity entity = session.getPlayerEntity();
// Gravity might need to be reset...
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().putAll(entity.getMetadata());
session.sendUpstreamPacket(entityDataPacket);
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
movePlayerPacket.setPosition(entity.getPosition());
movePlayerPacket.setRotation(entity.getBedrockRotation());
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
session.sendUpstreamPacket(movePlayerPacket);
}
public List<Vector3i> getPlayerCollidableBlocks() {
List<Vector3i> blocks = new ArrayList<>();

View File

@ -1,167 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.google.gson.JsonSyntaxException;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.protocol.bedrock.data.inventory.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
import java.util.List;
import java.util.stream.Collectors;
public class AnvilInventoryTranslator extends BlockInventoryTranslator {
public AnvilInventoryTranslator() {
super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, new CursorInventoryUpdater());
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
if (action.getSource().getContainerId() == ContainerId.UI) {
switch (action.getSlot()) {
case 1:
return 0;
case 2:
return 1;
case 50:
return 2;
}
}
if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) {
return 2;
}
return super.bedrockSlotToJava(action);
}
@Override
public int javaSlotToBedrock(int slot) {
switch (slot) {
case 0:
return 1;
case 1:
return 2;
case 2:
return 50;
}
return super.javaSlotToBedrock(slot);
}
@Override
public SlotType getSlotType(int javaSlot) {
if (javaSlot == 2)
return SlotType.OUTPUT;
return SlotType.NORMAL;
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
InventoryActionData anvilResult = null;
InventoryActionData anvilInput = null;
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == ContainerId.ANVIL_MATERIAL) {
//useless packet
return;
} else if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) {
anvilResult = action;
} else if (bedrockSlotToJava(action) == 0) {
anvilInput = action;
}
}
ItemData itemName = null;
if (anvilResult != null) {
itemName = anvilResult.getFromItem();
} else if (anvilInput != null) {
itemName = anvilInput.getToItem();
}
if (itemName != null) {
String rename;
NbtMap tag = itemName.getTag();
if (tag != null && tag.containsKey("display")) {
String name = tag.getCompound("display").getString("Name");
try {
Component component = GsonComponentSerializer.gson().deserialize(name);
rename = LegacyComponentSerializer.legacySection().serialize(component);
} catch (JsonSyntaxException e) {
rename = name;
}
} else {
rename = "";
}
ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename);
session.sendDownstreamPacket(renameItemPacket);
}
if (anvilResult != null) {
//Strip unnecessary actions
List<InventoryActionData> strippedActions = actions.stream()
.filter(action -> action.getSource().getContainerId() == ContainerId.ANVIL_RESULT
|| (action.getSource().getType() == InventorySource.Type.CONTAINER
&& !(action.getSource().getContainerId() == ContainerId.UI && action.getSlot() != 0)))
.collect(Collectors.toList());
super.translateActions(session, inventory, strippedActions);
return;
}
super.translateActions(session, inventory, actions);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
if (slot == 0) {
ItemStack item = inventory.getItem(slot);
if (item != null) {
String rename;
CompoundTag tag = item.getNbt();
if (tag != null) {
CompoundTag displayTag = tag.get("display");
if (displayTag != null && displayTag.contains("Name")) {
String itemName = displayTag.get("Name").getValue().toString();
try {
Component component = GsonComponentSerializer.gson().deserialize(itemName);
rename = LegacyComponentSerializer.legacySection().serialize(component);
} catch (JsonSyntaxException e) {
rename = itemName;
}
} else {
rename = "";
}
} else {
rename = "";
}
ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename);
session.sendDownstreamPacket(renameItemPacket);
}
}
super.updateSlot(session, inventory, slot);
}
}

View File

@ -23,13 +23,13 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity;
package org.geysermc.connector.network.translators.inventory;
import org.geysermc.connector.network.session.GeyserSession;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import lombok.Value;
/**
* Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds.
*/
public interface Tickable {
void tick(GeyserSession session);
@Value
public class BedrockContainerSlot {
ContainerSlotType container;
int slot;
}

View File

@ -1,264 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.geysermc.connector.common.ChatColor;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LocaleUtils;
import java.util.*;
/**
* A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete.
* The enchantment table on Bedrock without server authoritative inventories doesn't tell us which button is pressed
* when selecting an enchantment.
*/
public class EnchantmentInventoryTranslator extends BlockInventoryTranslator {
private static final int DYE_ID = ItemRegistry.getItemEntry("minecraft:lapis_lazuli").getBedrockId();
private static final int ENCHANTED_BOOK_ID = ItemRegistry.getItemEntry("minecraft:enchanted_book").getBedrockId();
public EnchantmentInventoryTranslator(InventoryUpdater updater) {
super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater);
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == inventory.getId()) {
// This is the hopper UI
switch (action.getSlot()) {
case 1:
// Don't allow the slot to be put through if the item isn't lapis
if ((action.getToItem().getId() != DYE_ID) && action.getToItem() != ItemData.AIR) {
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
break;
case 2:
case 3:
case 4:
// The books here act as buttons
ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), action.getSlot() - 2);
session.sendDownstreamPacket(packet);
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
default:
break;
}
}
}
super.translateActions(session, inventory, actions);
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
super.updateInventory(session, inventory);
List<ItemData> items = new ArrayList<>(5);
items.add(ItemTranslator.translateToBedrock(session, inventory.getItem(0)));
items.add(ItemTranslator.translateToBedrock(session, inventory.getItem(1)));
for (int i = 0; i < 3; i++) {
items.add(session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook());
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(inventory.getId());
contentPacket.setContents(items);
session.sendUpstreamPacket(contentPacket);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
int bookSlotToUpdate;
switch (key) {
case 0:
case 1:
case 2:
// Experience required
bookSlotToUpdate = key;
session.getEnchantmentSlotData()[bookSlotToUpdate].setExperienceRequired(value);
break;
case 4:
case 5:
case 6:
// Enchantment name
bookSlotToUpdate = key - 4;
if (value != -1) {
session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(EnchantmentTableEnchantments.values()[value - 1]);
} else {
// -1 means no enchantment specified
session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(null);
}
break;
case 7:
case 8:
case 9:
// Enchantment level
bookSlotToUpdate = key - 7;
session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentLevel(value);
break;
default:
return;
}
updateEnchantmentBook(session, inventory, bookSlotToUpdate);
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
super.openInventory(session, inventory);
for (int i = 0; i < session.getEnchantmentSlotData().length; i++) {
session.getEnchantmentSlotData()[i] = new EnchantmentSlotData();
}
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
super.closeInventory(session, inventory);
Arrays.fill(session.getEnchantmentSlotData(), null);
}
private ItemData createEnchantmentBook() {
NbtMapBuilder root = NbtMap.builder();
NbtMapBuilder display = NbtMap.builder();
display.putString("Name", ChatColor.RESET + "No Enchantment");
root.put("display", display.build());
return ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build());
}
private void updateEnchantmentBook(GeyserSession session, Inventory inventory, int slot) {
NbtMapBuilder root = NbtMap.builder();
NbtMapBuilder display = NbtMap.builder();
EnchantmentSlotData data = session.getEnchantmentSlotData()[slot];
if (data.getEnchantmentType() != null) {
display.putString("Name", ChatColor.ITALIC + data.getEnchantmentType().toString(session) +
(data.getEnchantmentLevel() != -1 ? " " + toRomanNumeral(session, data.getEnchantmentLevel()) : "") + "?");
} else {
display.putString("Name", ChatColor.RESET + "No Enchantment");
}
display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.DARK_GRAY + data.getExperienceRequired() + "xp"));
root.put("display", display.build());
ItemData book = ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build());
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(inventory.getId());
slotPacket.setSlot(slot + 2);
slotPacket.setItem(book);
session.sendUpstreamPacket(slotPacket);
data.setItem(book);
}
private String toRomanNumeral(GeyserSession session, int level) {
return LocaleUtils.getLocaleString("enchantment.level." + level,
session.getLocale());
}
/**
* Stores the data of each slot in an enchantment table
*/
@NoArgsConstructor
@Getter
@Setter
@ToString
public static class EnchantmentSlotData {
private EnchantmentTableEnchantments enchantmentType = null;
private int enchantmentLevel = 0;
private int experienceRequired = 0;
private ItemData item;
}
/**
* Classifies enchantments by Java order
*/
public enum EnchantmentTableEnchantments {
PROTECTION,
FIRE_PROTECTION,
FEATHER_FALLING,
BLAST_PROTECTION,
PROJECTILE_PROTECTION,
RESPIRATION,
AQUA_AFFINITY,
THORNS,
DEPTH_STRIDER,
FROST_WALKER,
BINDING_CURSE,
SHARPNESS,
SMITE,
BANE_OF_ARTHROPODS,
KNOCKBACK,
FIRE_ASPECT,
LOOTING,
SWEEPING,
EFFICIENCY,
SILK_TOUCH,
UNBREAKING,
FORTUNE,
POWER,
PUNCH,
FLAME,
INFINITY,
LUCK_OF_THE_SEA,
LURE,
LOYALTY,
IMPALING,
RIPTIDE,
CHANNELING,
MENDING,
VANISHING_CURSE, // After this is not documented
MULTISHOT,
PIERCING,
QUICK_CHARGE,
SOUL_SPEED;
public String toString(GeyserSession session) {
return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(),
session.getLocale());
}
}
}

View File

@ -25,52 +25,91 @@
package org.geysermc.connector.network.translators.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.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.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientPrepareCraftingGridPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.AllArgsConstructor;
import org.geysermc.connector.inventory.CartographyContainer;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.network.translators.inventory.click.Click;
import org.geysermc.connector.network.translators.inventory.click.ClickPlan;
import org.geysermc.connector.network.translators.inventory.translators.*;
import org.geysermc.connector.network.translators.inventory.translators.chest.DoubleChestInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.translators.chest.SingleChestInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.translators.furnace.BlastFurnaceInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.translators.furnace.FurnaceInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.translators.furnace.SmokerInventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
@AllArgsConstructor
public abstract class InventoryTranslator {
public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
public static final Map<WindowType, InventoryTranslator> INVENTORY_TRANSLATORS = new HashMap<WindowType, InventoryTranslator>() {
{
put(null, new PlayerInventoryTranslator()); //player inventory
/* Player Inventory */
put(null, PLAYER_INVENTORY_TRANSLATOR);
/* Chest UIs */
put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9));
put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18));
put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27));
put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36));
put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45));
put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54));
put(WindowType.BREWING_STAND, new BrewingInventoryTranslator());
/* Furnaces */
put(WindowType.FURNACE, new FurnaceInventoryTranslator());
put(WindowType.BLAST_FURNACE, new BlastFurnaceInventoryTranslator());
put(WindowType.SMOKER, new SmokerInventoryTranslator());
/* Specific Inventories */
put(WindowType.ANVIL, new AnvilInventoryTranslator());
put(WindowType.BEACON, new BeaconInventoryTranslator());
put(WindowType.BREWING_STAND, new BrewingInventoryTranslator());
put(WindowType.CARTOGRAPHY, new CartographyInventoryTranslator());
put(WindowType.CRAFTING, new CraftingInventoryTranslator());
//put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); //FIXME
put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator());
put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());
put(WindowType.LOOM, new LoomInventoryTranslator());
put(WindowType.MERCHANT, new MerchantInventoryTranslator());
//put(WindowType.SMITHING, new SmithingInventoryTranslator()); //TODO for server authoritative inventories
put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator());
put(WindowType.SMITHING, new SmithingInventoryTranslator());
put(WindowType.STONECUTTER, new StonecutterInventoryTranslator());
InventoryTranslator furnace = new FurnaceInventoryTranslator();
put(WindowType.FURNACE, furnace);
put(WindowType.BLAST_FURNACE, furnace);
put(WindowType.SMOKER, furnace);
/* Generics */
put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER));
put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER));
InventoryUpdater containerUpdater = new ContainerInventoryUpdater();
put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator(containerUpdater)); //TODO
put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater));
put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater));
put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater));
//put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO
/* Lectern */
put(WindowType.LECTERN, new LecternInventoryTranslator());
}
};
public static final int PLAYER_INVENTORY_SIZE = 36;
public static final int PLAYER_INVENTORY_OFFSET = 9;
private static final int MAX_ITEM_STACK_SIZE = 64;
public final int size;
public abstract void prepareInventory(GeyserSession session, Inventory inventory);
@ -79,8 +118,790 @@ public abstract class InventoryTranslator {
public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value);
public abstract void updateInventory(GeyserSession session, Inventory inventory);
public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot);
public abstract int bedrockSlotToJava(InventoryActionData action);
public abstract int javaSlotToBedrock(int slot);
public abstract int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData);
public abstract int javaSlotToBedrock(int javaSlot); //TODO
public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot); //TODO
public abstract SlotType getSlotType(int javaSlot);
public abstract void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions);
public abstract Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory);
/**
* Should be overwritten in cases where specific inventories should reject an item being in a specific spot.
* For examples, looms use this to reject items that are dyes in Bedrock but not in Java.
*
* The source/destination slot will be -1 if the cursor is the slot
*
* @return true if this transfer should be rejected
*/
public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) {
return false;
}
/**
* Should be overrided if this request matches a certain criteria and shouldn't be treated normally.
* E.G. anvil renaming or enchanting
*/
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return false;
}
/**
* If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called
*/
public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
return null;
}
public void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequestPacket.Request> requests) {
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
for (ItemStackRequestPacket.Request request : requests) {
if (request.getActions().length > 0) {
StackRequestActionData firstAction = request.getActions()[0];
if (shouldHandleRequestFirst(firstAction, inventory)) {
// Some special request that shouldn't be processed normally
responsePacket.getEntries().add(translateSpecialRequest(session, inventory, request));
} else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) {
responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request));
} else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) {
// This is also used for pulling items out of creative
responsePacket.getEntries().add(translateCreativeRequest(session, inventory, request));
} else {
responsePacket.getEntries().add(translateRequest(session, inventory, request));
}
} else {
responsePacket.getEntries().add(rejectRequest(request));
}
}
session.sendUpstreamPacket(responsePacket);
System.out.println(responsePacket);
}
public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
System.out.println(request);
ClickPlan plan = new ClickPlan(session, this, inventory);
IntSet affectedSlots = new IntOpenHashSet();
for (StackRequestActionData action : request.getActions()) {
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
switch (action.getType()) {
case TAKE:
case PLACE: {
TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) {
if (session.getGameMode().equals(GameMode.CREATIVE) && transferAction.getSource().getContainer() == ContainerSlotType.CRAFTING_INPUT &&
transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) {
return rejectRequest(request, false);
}
session.getConnector().getLogger().error("DEBUG: About to reject request.");
session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource()));
session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination()));
return rejectRequest(request);
}
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
if (shouldRejectItemPlace(session, inventory, isCursor(transferAction.getSource()) ? -1 : sourceSlot,
isCursor(transferAction.getDestination()) ? -1 : destSlot)) {
// This item would not be here in Java
return rejectRequest(request, false);
}
if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //???
return rejectRequest(request);
} else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use this stuff all the time in creative?
// Creative acts a little differently because it just edits slots
boolean sourceIsCursor = isCursor(transferAction.getSource());
boolean destIsCursor = isCursor(transferAction.getDestination());
GeyserItemStack sourceItem = sourceIsCursor ? session.getPlayerInventory().getCursor() :
inventory.getItem(sourceSlot);
GeyserItemStack newItem = sourceItem.copy();
if (sourceIsCursor) {
GeyserItemStack destItem = inventory.getItem(destSlot);
if (destItem.getJavaId() == sourceItem.getJavaId()) {
// Combining items
int itemsLeftOver = destItem.getAmount() + transferAction.getCount();
if (itemsLeftOver > MAX_ITEM_STACK_SIZE) {
// Items will remain in cursor because destination slot gets set to 64
destItem.setAmount(MAX_ITEM_STACK_SIZE);
sourceItem.setAmount(itemsLeftOver - MAX_ITEM_STACK_SIZE);
} else {
// Cursor will be emptied
destItem.setAmount(itemsLeftOver);
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
}
ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
destSlot,
destItem.getItemStack()
);
session.sendDownstreamPacket(creativeActionPacket);
affectedSlots.add(destSlot);
break;
}
} else {
// Delete the source since we're moving it
inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session);
ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
sourceSlot,
new ItemStack(0)
);
session.sendDownstreamPacket(creativeActionPacket);
affectedSlots.add(sourceSlot);
}
// Update the item count with however much the client took
newItem.setAmount(transferAction.getCount());
// Remove that amount from the existing item
sourceItem.setAmount(sourceItem.getAmount() - transferAction.getCount());
if (sourceItem.isEmpty()) {
// Item is basically deleted
if (sourceIsCursor) {
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
} else {
inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session);
}
}
if (destIsCursor) {
session.getPlayerInventory().setCursor(newItem, session);
} else {
inventory.setItem(destSlot, newItem, session);
}
GeyserItemStack itemToUpdate = destIsCursor ? sourceItem : newItem;
// The Java server doesn't care about what's in the mouse in creative mode, so we just need to track
// which inventory slot the client modified
ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
destIsCursor ? sourceSlot : destSlot,
itemToUpdate.isEmpty() ? new ItemStack(0) : itemToUpdate.getItemStack()
);
session.sendDownstreamPacket(creativeActionPacket);
System.out.println(creativeActionPacket);
if (!sourceIsCursor) { // Cursor is always added for us as an affected slot
affectedSlots.add(sourceSlot);
}
if (!destIsCursor) {
affectedSlots.add(destSlot);
}
} else if (isCursor(transferAction.getSource())) { //releasing cursor
int sourceAmount = cursor.getAmount();
if (transferAction.getCount() == sourceAmount) { //release all
plan.add(Click.LEFT, destSlot);
} else { //release some
for (int i = 0; i < transferAction.getCount(); i++) {
plan.add(Click.RIGHT, destSlot);
}
}
} else if (isCursor(transferAction.getDestination())) { //picking up into cursor
GeyserItemStack sourceItem = plan.getItem(sourceSlot);
int sourceAmount = sourceItem.getAmount();
if (cursor.isEmpty()) { //picking up into empty cursor
if (transferAction.getCount() == sourceAmount) { //pickup all
plan.add(Click.LEFT, sourceSlot);
} else if (transferAction.getCount() == sourceAmount - (sourceAmount / 2)) { //larger half; simple right click
plan.add(Click.RIGHT, sourceSlot);
} else { //pickup some; not a simple right click
plan.add(Click.LEFT, sourceSlot); //first pickup all
for (int i = 0; i < sourceAmount - transferAction.getCount(); i++) {
plan.add(Click.RIGHT, sourceSlot); //release extra items back into source slot
}
}
} else { //pickup into non-empty cursor
if (!InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //doesn't make sense, reject
return rejectRequest(request);
}
if (transferAction.getCount() != sourceAmount) {
int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot);
if (tempSlot == -1) {
return rejectRequest(request);
}
plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
plan.add(Click.LEFT, sourceSlot); //pickup source items into cursor
for (int i = 0; i < transferAction.getCount(); i++) {
plan.add(Click.RIGHT, tempSlot); //partially transfer source items into temp slot (original cursor)
}
plan.add(Click.LEFT, sourceSlot); //return remaining source items
plan.add(Click.LEFT, tempSlot); //retrieve original cursor items from temp slot
} else {
if (getSlotType(sourceSlot).equals(SlotType.NORMAL)) {
plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot
}
plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source
}
}
} else { //transfer from one slot to another
int tempSlot = -1;
if (!cursor.isEmpty()) {
tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot);
if (tempSlot == -1) {
return rejectRequest(request);
}
plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
}
int sourceAmount = plan.getItem(sourceSlot).getAmount();
if (transferAction.getCount() == sourceAmount) { //transfer all
plan.add(Click.LEFT, sourceSlot); //pickup source
plan.add(Click.LEFT, destSlot); //let go of all items and done
} else { //transfer some
//try to transfer items with least clicks possible
int halfSource = sourceAmount - (sourceAmount / 2); //larger half
int holding;
if (plan.getCursor().isEmpty() && transferAction.getCount() <= halfSource) { //faster to take only half. CURSOR MUST BE EMPTY
plan.add(Click.RIGHT, sourceSlot);
holding = halfSource;
} else { //need all
plan.add(Click.LEFT, sourceSlot);
holding = sourceAmount;
}
if (transferAction.getCount() > holding / 2) { //faster to release extra items onto source or dest slot?
for (int i = 0; i < holding - transferAction.getCount(); i++) {
plan.add(Click.RIGHT, sourceSlot); //prepare cursor
}
plan.add(Click.LEFT, destSlot); //release cursor onto dest slot
} else {
for (int i = 0; i < transferAction.getCount(); i++) {
plan.add(Click.RIGHT, destSlot); //right click until transfer goal is met
}
plan.add(Click.LEFT, sourceSlot); //return extra items to source slot
}
}
if (tempSlot != -1) {
plan.add(Click.LEFT, tempSlot); //retrieve original cursor
}
}
break;
}
case SWAP: { //TODO
SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action;
if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination())))
return rejectRequest(request);
if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) {
int destSlot = bedrockSlotToJava(swapAction.getDestination());
GeyserItemStack oldSourceItem;
GeyserItemStack oldDestinationItem = inventory.getItem(destSlot);
if (isCursor(swapAction.getSource())) {
oldSourceItem = session.getPlayerInventory().getCursor();
session.getPlayerInventory().setCursor(oldDestinationItem, session);
} else {
int sourceSlot = bedrockSlotToJava(swapAction.getSource());
oldSourceItem = inventory.getItem(sourceSlot);
ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
sourceSlot,
oldDestinationItem.isEmpty() ? new ItemStack(0) : oldDestinationItem.getItemStack() // isEmpty check... just in case
);
System.out.println(creativeActionPacket);
session.sendDownstreamPacket(creativeActionPacket);
inventory.setItem(sourceSlot, oldDestinationItem, session);
}
if (isCursor(swapAction.getDestination())) {
session.getPlayerInventory().setCursor(oldSourceItem, session);
} else {
ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
destSlot,
oldSourceItem.isEmpty() ? new ItemStack(0) : oldSourceItem.getItemStack()
);
System.out.println(creativeActionPacket);
session.sendDownstreamPacket(creativeActionPacket);
inventory.setItem(destSlot, oldSourceItem, session);
}
} else if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //???
return rejectRequest(request);
} else if (isCursor(swapAction.getSource())) { //swap cursor
int destSlot = bedrockSlotToJava(swapAction.getDestination());
if (InventoryUtils.canStack(cursor, plan.getItem(destSlot))) { //TODO: cannot simply swap if cursor stacks with slot (temp slot)
return rejectRequest(request);
}
plan.add(Click.LEFT, destSlot);
} else if (isCursor(swapAction.getDestination())) { //swap cursor
int sourceSlot = bedrockSlotToJava(swapAction.getSource());
if (InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //TODO
return rejectRequest(request);
}
plan.add(Click.LEFT, sourceSlot);
} else {
int sourceSlot = bedrockSlotToJava(swapAction.getSource());
int destSlot = bedrockSlotToJava(swapAction.getDestination());
if (!cursor.isEmpty()) { //TODO: (temp slot)
return rejectRequest(request);
}
if (sourceSlot == destSlot) { //doesn't make sense
return rejectRequest(request);
}
if (InventoryUtils.canStack(plan.getItem(sourceSlot), plan.getItem(destSlot))) { //TODO: (temp slot)
return rejectRequest(request);
}
plan.add(Click.LEFT, sourceSlot); //pickup source into cursor
plan.add(Click.LEFT, destSlot); //swap cursor with dest slot
plan.add(Click.LEFT, sourceSlot); //release cursor onto source
}
break;
}
case DROP: {
DropStackRequestActionData dropAction = (DropStackRequestActionData) action;
if (!checkNetId(session, inventory, dropAction.getSource()))
return rejectRequest(request);
if (isCursor(dropAction.getSource())) { //clicking outside of window
if (session.getGameMode() == GameMode.CREATIVE && inventory instanceof PlayerInventory) {
GeyserItemStack cursorItem = session.getPlayerInventory().getCursor();
GeyserItemStack droppingItem = cursorItem.copy();
// Subtract the cursor item by however much is being dropped
cursorItem.setAmount(cursorItem.getAmount() - dropAction.getCount());
if (cursorItem.isEmpty()) {
// Cursor item no longer exists
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
}
droppingItem.setAmount(dropAction.getCount());
ClientCreativeInventoryActionPacket packet = new ClientCreativeInventoryActionPacket(
Click.OUTSIDE_SLOT,
droppingItem.getItemStack()
);
System.out.println(packet.toString());
session.sendDownstreamPacket(packet);
} else {
int sourceAmount = plan.getCursor().getAmount();
if (dropAction.getCount() == sourceAmount) { //drop all
plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT);
} else { //drop some
for (int i = 0; i < dropAction.getCount(); i++) {
plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met
}
}
}
} else { //dropping from inventory
int sourceSlot = bedrockSlotToJava(dropAction.getSource());
int sourceAmount = plan.getItem(sourceSlot).getAmount();
if (dropAction.getCount() == sourceAmount && sourceAmount > 1) { //dropping all? (prefer DROP_ONE if only one)
plan.add(Click.DROP_ALL, sourceSlot);
} else { //drop some
for (int i = 0; i < dropAction.getCount(); i++) {
plan.add(Click.DROP_ONE, sourceSlot); //drop one until goal is met
}
}
}
break;
}
case CRAFT_CREATIVE: {
CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action;
System.out.println(creativeAction.getCreativeItemNetworkId());
break;
}
case DESTROY: {
// Only called when a creative client wants to destroy an item... I think - Camotoy
//TODO there is a Count here we don't use
DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action;
if (!session.getGameMode().equals(GameMode.CREATIVE)) {
// If this happens, let's throw an error and figure out why.
return rejectRequest(request);
}
if (!isCursor(destroyAction.getSource())) {
// Item exists; let's remove it from the inventory
int javaSlot = bedrockSlotToJava(destroyAction.getSource());
ClientCreativeInventoryActionPacket destroyItemPacket = new ClientCreativeInventoryActionPacket(
javaSlot,
new ItemStack(0)
);
session.sendDownstreamPacket(destroyItemPacket);
System.out.println(destroyItemPacket);
inventory.setItem(javaSlot, GeyserItemStack.EMPTY, session);
affectedSlots.add(javaSlot);
} else {
// Just sync up the item on our end, since the server doesn't care what's in our cursor
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
}
break;
}
// The following three tend to be called for UI inventories
case CONSUME: {
if (inventory instanceof CartographyContainer) {
// TODO add this for more inventories? Only seems to glitch out the cartography table, though.
ConsumeStackRequestActionData consumeData = (ConsumeStackRequestActionData) action;
int sourceSlot = bedrockSlotToJava(consumeData.getSource());
if (sourceSlot == 0 && inventory.getItem(1).isEmpty()) {
// Java doesn't allow an item to be renamed; this is why CARTOGRAPHY_ADDITIONAL could remain empty for Bedrock
// We check this during slot 0 since setting the inventory slots here messes up shouldRejectItemPlace
return rejectRequest(request, false);
}
GeyserItemStack item = inventory.getItem(sourceSlot);
item.setAmount(item.getAmount() - consumeData.getCount());
if (item.isEmpty()) {
inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session);
}
affectedSlots.add(sourceSlot);
}
break;
}
case CRAFT_NON_IMPLEMENTED_DEPRECATED: {
break;
}
case CRAFT_RESULTS_DEPRECATED: {
break;
}
case CRAFT_RECIPE_OPTIONAL: {
// Anvils and cartography tables will handle this
break;
}
default:
return rejectRequest(request);
}
}
plan.execute(false);
affectedSlots.addAll(plan.getAffectedSlots());
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
}
public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
System.out.println(request);
int recipeId = 0;
int resultSize = 0;
int timesCrafted = 0;
boolean autoCraft = false;
CraftState craftState = CraftState.START;
int leftover = 0;
ClickPlan plan = new ClickPlan(session, this, inventory);
for (StackRequestActionData action : request.getActions()) {
switch (action.getType()) {
case CRAFT_RECIPE: {
CraftRecipeStackRequestActionData craftAction = (CraftRecipeStackRequestActionData) action;
if (craftState != CraftState.START) {
return rejectRequest(request);
}
craftState = CraftState.RECIPE_ID;
recipeId = craftAction.getRecipeNetworkId();
autoCraft = false;
break;
}
case CRAFT_RECIPE_AUTO: {
AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action;
if (craftState != CraftState.START) {
return rejectRequest(request);
}
craftState = CraftState.RECIPE_ID;
recipeId = autoCraftAction.getRecipeNetworkId();
if (!plan.getCursor().isEmpty()) {
return rejectRequest(request);
}
//reject if crafting grid is not clear
int gridSize = inventory.getId() == 0 ? 4 : 9;
for (int i = 1; i <= gridSize; i++) {
if (!inventory.getItem(i).isEmpty()) {
return rejectRequest(request);
}
}
autoCraft = true;
break;
}
case CRAFT_RESULTS_DEPRECATED: {
CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action;
if (craftState != CraftState.RECIPE_ID) {
return rejectRequest(request);
}
craftState = CraftState.DEPRECATED;
if (deprecatedCraftAction.getResultItems().length != 1) {
return rejectRequest(request);
}
resultSize = deprecatedCraftAction.getResultItems()[0].getCount();
timesCrafted = deprecatedCraftAction.getTimesCrafted();
if (resultSize <= 0 || timesCrafted <= 0) {
return rejectRequest(request);
}
break;
}
case CONSUME: {
ConsumeStackRequestActionData consumeAction = (ConsumeStackRequestActionData) action;
if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) {
return rejectRequest(request);
}
craftState = CraftState.INGREDIENTS;
break;
}
case TAKE:
case PLACE: {
TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) {
return rejectRequest(request);
}
craftState = CraftState.TRANSFER;
if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) {
return rejectRequest(request);
}
if (transferAction.getCount() <= 0) {
return rejectRequest(request);
}
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
if (autoCraft) {
Recipe recipe = session.getCraftingRecipes().get(recipeId);
//cannot use java recipe book if recipe is locked
if (recipe == null || !session.getUnlockedRecipes().contains(recipe.getIdentifier())) {
return rejectRequest(request);
}
boolean cursorDest = isCursor(transferAction.getDestination());
boolean makeAll = timesCrafted > 1;
if (cursorDest) {
makeAll = false;
}
ClientPrepareCraftingGridPacket prepareCraftingPacket = new ClientPrepareCraftingGridPacket(inventory.getId(), recipe.getIdentifier(), makeAll);
session.sendDownstreamPacket(prepareCraftingPacket);
ItemStack output = null;
switch (recipe.getType()) {
case CRAFTING_SHAPED:
output = ((ShapedRecipeData)recipe.getData()).getResult();
break;
case CRAFTING_SHAPELESS:
output = ((ShapelessRecipeData)recipe.getData()).getResult();
break;
}
inventory.setItem(0, GeyserItemStack.from(output), session);
plan.add(cursorDest ? Click.LEFT : Click.LEFT_SHIFT, 0);
plan.execute(true);
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
}
if (isCursor(transferAction.getDestination())) {
plan.add(Click.LEFT, sourceSlot);
craftState = CraftState.DONE;
} else {
if (leftover != 0) {
if (transferAction.getCount() > leftover) {
return rejectRequest(request);
}
if (transferAction.getCount() == leftover) {
plan.add(Click.LEFT, destSlot);
} else {
for (int i = 0; i < transferAction.getCount(); i++) {
plan.add(Click.RIGHT, destSlot);
}
}
leftover -= transferAction.getCount();
break;
}
int remainder = transferAction.getCount() % resultSize;
int timesToCraft = transferAction.getCount() / resultSize;
for (int i = 0; i < timesToCraft; i++) {
plan.add(Click.LEFT, sourceSlot);
plan.add(Click.LEFT, destSlot);
}
if (remainder > 0) {
plan.add(Click.LEFT, 0);
for (int i = 0; i < remainder; i++) {
plan.add(Click.RIGHT, destSlot);
}
leftover = resultSize - remainder;
}
}
break;
}
default:
return rejectRequest(request);
}
}
plan.execute(false);
Set<Integer> affectedSlots = plan.getAffectedSlots();
affectedSlots.addAll(Arrays.asList(1, 2, 3, 4)); //TODO: crafting grid
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
}
public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
int creativeId = 0;
CraftState craftState = CraftState.START;
for (StackRequestActionData action : request.getActions()) {
switch (action.getType()) {
case CRAFT_CREATIVE: {
CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action;
if (craftState != CraftState.START) {
return rejectRequest(request);
}
craftState = CraftState.RECIPE_ID;
creativeId = creativeAction.getCreativeItemNetworkId();
break;
}
case CRAFT_RESULTS_DEPRECATED: {
CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action;
if (craftState != CraftState.RECIPE_ID) {
return rejectRequest(request);
}
craftState = CraftState.DEPRECATED;
break;
}
case TAKE:
case PLACE: {
TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action;
if (craftState != CraftState.DEPRECATED) {
return rejectRequest(request);
}
craftState = CraftState.TRANSFER;
if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) {
return rejectRequest(request);
}
// Reference the creative items list we send to the client to know what it's asking of us
ItemData creativeItem = ItemRegistry.CREATIVE_ITEMS[creativeId - 1];
// Get the correct count
creativeItem = ItemData.of(creativeItem.getId(), creativeItem.getDamage(), transferAction.getCount(), creativeItem.getTag());
ItemStack javaCreativeItem = ItemTranslator.translateToJava(creativeItem);
if (isCursor(transferAction.getDestination())) {
session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem), session);
return acceptRequest(request, Collections.singletonList(
new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR,
Collections.singletonList(makeItemEntry(0, session.getPlayerInventory().getCursor())))));
} else {
int javaSlot = bedrockSlotToJava(transferAction.getDestination());
GeyserItemStack existingItem = inventory.getItem(javaSlot);
if (existingItem.getJavaId() == javaCreativeItem.getId()) {
// Adding more to an existing item
existingItem.setAmount(existingItem.getAmount() + transferAction.getCount());
javaCreativeItem = existingItem.getItemStack();
} else {
inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem), session);
}
ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket(
javaSlot,
javaCreativeItem
);
session.sendDownstreamPacket(creativeActionPacket);
System.out.println(creativeActionPacket);
Set<Integer> affectedSlots = Collections.singleton(javaSlot);
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
}
}
default:
return rejectRequest(request);
}
}
return rejectRequest(request);
}
public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequestPacket.Request request, List<ItemStackResponsePacket.ContainerEntry> containerEntries) {
return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries);
}
public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request) {
return rejectRequest(request, true);
}
public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request, boolean throwError) {
if (throwError) {
// Currently for debugging, but might be worth it to keep in the future if something goes terribly wrong.
new Throwable("DEBUGGING: ItemStackRequest rejected").printStackTrace();
}
return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.ERROR, request.getRequestId(), Collections.emptyList());
}
public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getStackNetworkId() < 0)
return true;
// if (slotInfoData.getContainer() == ContainerSlotType.CURSOR) //TODO: temporary
// return true;
GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData));
return currentItem.getNetId() == slotInfoData.getStackNetworkId();
}
/**
* Try to find a slot that can temporarily store the given item.
* Only looks in the main inventory and hotbar (excluding offhand).
* Only slots that are empty or contain a different type of item are valid.
*
* @return java id for the temporary slot, or -1 if no viable slot was found
*/
//TODO: compatibility for simulated inventory (ClickPlan)
private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) {
int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable temp slot
HashSet<GeyserItemStack> itemBlacklist = new HashSet<>(slotBlacklist.length + 1);
itemBlacklist.add(item);
IntSet potentialSlots = new IntOpenHashSet(36);
for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) {
potentialSlots.add(i);
}
for (int i : slotBlacklist) {
potentialSlots.remove(i);
GeyserItemStack blacklistedItem = inventory.getItem(i);
if (!blacklistedItem.isEmpty()) {
itemBlacklist.add(blacklistedItem);
}
}
for (int i : potentialSlots) {
GeyserItemStack testItem = inventory.getItem(i);
if ((emptyOnly && !testItem.isEmpty())) {
continue;
}
boolean viable = true;
for (GeyserItemStack blacklistedItem : itemBlacklist) {
if (InventoryUtils.canStack(testItem, blacklistedItem)) {
viable = false;
break;
}
}
if (!viable) {
continue;
}
System.out.println("TEMP SLOT CHOSEN: " + i + " => " + inventory.getItem(i));
return i;
}
//could not find a viable temp slot
return -1;
}
public List<ItemStackResponsePacket.ContainerEntry> makeContainerEntries(GeyserSession session, Inventory inventory, Set<Integer> affectedSlots) {
Map<ContainerSlotType, List<ItemStackResponsePacket.ItemEntry>> containerMap = new HashMap<>();
for (int slot : affectedSlots) {
BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot);
List<ItemStackResponsePacket.ItemEntry> list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>());
list.add(makeItemEntry(bedrockSlot.getSlot(), inventory.getItem(slot)));
}
List<ItemStackResponsePacket.ContainerEntry> containerEntries = new ArrayList<>();
for (Map.Entry<ContainerSlotType, List<ItemStackResponsePacket.ItemEntry>> entry : containerMap.entrySet()) {
containerEntries.add(new ItemStackResponsePacket.ContainerEntry(entry.getKey(), entry.getValue()));
}
ItemStackResponsePacket.ItemEntry cursorEntry = makeItemEntry(0, session.getPlayerInventory().getCursor());
containerEntries.add(new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry)));
return containerEntries;
}
public static ItemStackResponsePacket.ItemEntry makeItemEntry(int bedrockSlot, GeyserItemStack itemStack) {
ItemStackResponsePacket.ItemEntry itemEntry;
if (!itemStack.isEmpty()) {
itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), "");
} else {
itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, "");
}
return itemEntry;
}
private static boolean isCursor(StackRequestSlotInfoData slotInfoData) {
return slotInfoData.getContainer() == ContainerSlotType.CURSOR;
}
private enum CraftState {
START,
RECIPE_ID,
DEPRECATED,
INGREDIENTS,
TRANSFER,
DONE
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.action;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.window.WindowAction;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
class ClickPlan {
private final List<ClickAction> plan = new ArrayList<>();
public void add(Click click, int slot) {
plan.add(new ClickAction(click, slot));
}
public void execute(GeyserSession session, InventoryTranslator translator, Inventory inventory, boolean refresh) {
PlayerInventory playerInventory = session.getInventory();
ListIterator<ClickAction> planIter = plan.listIterator();
while (planIter.hasNext()) {
final ClickAction action = planIter.next();
final ItemStack cursorItem = playerInventory.getCursor();
final ItemStack clickedItem = inventory.getItem(action.slot);
final short actionId = (short) inventory.getTransactionId().getAndIncrement();
//TODO: stop relying on refreshing the inventory for crafting to work properly
if (translator.getSlotType(action.slot) != SlotType.NORMAL)
refresh = true;
ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(inventory.getId(),
actionId, action.slot, !planIter.hasNext() && refresh ? InventoryUtils.REFRESH_ITEM : clickedItem,
WindowAction.CLICK_ITEM, action.click.actionParam);
if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
if (cursorItem == null && clickedItem != null) {
playerInventory.setCursor(clickedItem);
} else if (InventoryUtils.canStack(cursorItem, clickedItem)) {
playerInventory.setCursor(new ItemStack(cursorItem.getId(),
cursorItem.getAmount() + clickedItem.getAmount(), cursorItem.getNbt()));
}
} else {
switch (action.click) {
case LEFT:
if (!InventoryUtils.canStack(cursorItem, clickedItem)) {
playerInventory.setCursor(clickedItem);
inventory.setItem(action.slot, cursorItem);
} else {
playerInventory.setCursor(null);
inventory.setItem(action.slot, new ItemStack(clickedItem.getId(),
clickedItem.getAmount() + cursorItem.getAmount(), clickedItem.getNbt()));
}
break;
case RIGHT:
if (cursorItem == null && clickedItem != null) {
ItemStack halfItem = new ItemStack(clickedItem.getId(),
clickedItem.getAmount() / 2, clickedItem.getNbt());
inventory.setItem(action.slot, halfItem);
playerInventory.setCursor(new ItemStack(clickedItem.getId(),
clickedItem.getAmount() - halfItem.getAmount(), clickedItem.getNbt()));
} else if (cursorItem != null && clickedItem == null) {
playerInventory.setCursor(new ItemStack(cursorItem.getId(),
cursorItem.getAmount() - 1, cursorItem.getNbt()));
inventory.setItem(action.slot, new ItemStack(cursorItem.getId(),
1, cursorItem.getNbt()));
} else if (InventoryUtils.canStack(cursorItem, clickedItem)) {
playerInventory.setCursor(new ItemStack(cursorItem.getId(),
cursorItem.getAmount() - 1, cursorItem.getNbt()));
inventory.setItem(action.slot, new ItemStack(clickedItem.getId(),
clickedItem.getAmount() + 1, clickedItem.getNbt()));
}
break;
}
}
session.sendDownstreamPacket(clickPacket);
session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true));
}
/*if (refresh) {
translator.updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
}*/
}
private static class ClickAction {
final Click click;
final int slot;
ClickAction(Click click, int slot) {
this.click = click;
this.slot = slot;
}
}
}

View File

@ -1,338 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.action;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
import com.github.steveice10.mc.protocol.data.game.window.*;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.*;
public class InventoryActionDataTranslator {
public static void translate(InventoryTranslator translator, GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
if (actions.size() != 2)
return;
InventoryActionData worldAction = null;
InventoryActionData cursorAction = null;
InventoryActionData containerAction = null;
boolean refresh = false;
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == ContainerId.CRAFTING_USE_INGREDIENT) {
return;
} else if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION) {
worldAction = action;
} else if (action.getSource().getContainerId() == ContainerId.UI && action.getSlot() == 0) {
cursorAction = action;
ItemData translatedCursor = ItemTranslator.translateToBedrock(session, session.getInventory().getCursor());
if (!translatedCursor.equals(action.getFromItem())) {
refresh = true;
}
} else {
containerAction = action;
ItemData translatedItem = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.bedrockSlotToJava(action)));
if (!translatedItem.equals(action.getFromItem())) {
refresh = true;
}
}
}
final int craftSlot = session.getCraftSlot();
session.setCraftSlot(0);
if (worldAction != null) {
InventoryActionData sourceAction;
if (cursorAction != null) {
sourceAction = cursorAction;
} else {
sourceAction = containerAction;
}
if (sourceAction != null) {
if (worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
//quick dropping from hotbar?
if (session.getInventoryCache().getOpenInventory() == null && sourceAction.getSource().getContainerId() == ContainerId.INVENTORY) {
int heldSlot = session.getInventory().getHeldItemSlot();
if (sourceAction.getSlot() == heldSlot) {
ClientPlayerActionPacket actionPacket = new ClientPlayerActionPacket(
sourceAction.getToItem().getCount() == 0 ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM,
new Position(0, 0, 0), BlockFace.DOWN);
session.sendDownstreamPacket(actionPacket);
ItemStack item = session.getInventory().getItem(heldSlot);
if (item != null) {
session.getInventory().setItem(heldSlot, new ItemStack(item.getId(), item.getAmount() - 1, item.getNbt()));
}
return;
}
}
int dropAmount = sourceAction.getFromItem().getCount() - sourceAction.getToItem().getCount();
if (sourceAction != cursorAction) { //dropping directly from inventory
int javaSlot = translator.bedrockSlotToJava(sourceAction);
if (dropAmount == sourceAction.getFromItem().getCount()) {
ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(),
inventory.getTransactionId().getAndIncrement(),
javaSlot, null, WindowAction.DROP_ITEM,
DropItemParam.DROP_SELECTED_STACK);
session.sendDownstreamPacket(dropPacket);
} else {
for (int i = 0; i < dropAmount; i++) {
ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(),
inventory.getTransactionId().getAndIncrement(),
javaSlot, null, WindowAction.DROP_ITEM,
DropItemParam.DROP_FROM_SELECTED);
session.sendDownstreamPacket(dropPacket);
}
}
ItemStack item = inventory.getItem(javaSlot);
if (item != null) {
inventory.setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt()));
}
return;
} else { //clicking outside of inventory
ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(),
-999, null, WindowAction.CLICK_ITEM,
dropAmount > 1 ? ClickItemParam.LEFT_CLICK : ClickItemParam.RIGHT_CLICK);
session.sendDownstreamPacket(dropPacket);
ItemStack cursor = session.getInventory().getCursor();
if (cursor != null) {
session.getInventory().setCursor(new ItemStack(cursor.getId(), dropAmount > 1 ? 0 : cursor.getAmount() - 1, cursor.getNbt()));
}
return;
}
}
}
} else if (cursorAction != null && containerAction != null) {
//left/right click
ClickPlan plan = new ClickPlan();
int javaSlot = translator.bedrockSlotToJava(containerAction);
if (cursorAction.getFromItem().equals(containerAction.getToItem())
&& containerAction.getFromItem().equals(cursorAction.getToItem())
&& !InventoryUtils.canStack(cursorAction.getFromItem(), containerAction.getFromItem())) { //simple swap
plan.add(Click.LEFT, javaSlot);
} else if (cursorAction.getFromItem().getCount() > cursorAction.getToItem().getCount()) { //release
if (cursorAction.getToItem().getCount() == 0) {
plan.add(Click.LEFT, javaSlot);
} else {
int difference = cursorAction.getFromItem().getCount() - cursorAction.getToItem().getCount();
for (int i = 0; i < difference; i++) {
plan.add(Click.RIGHT, javaSlot);
}
}
} else { //pickup
if (cursorAction.getFromItem().getCount() == 0) {
if (containerAction.getToItem().getCount() == 0) { //pickup all
plan.add(Click.LEFT, javaSlot);
} else { //pickup some
if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT
|| containerAction.getToItem().getCount() == containerAction.getFromItem().getCount() / 2) { //right click
plan.add(Click.RIGHT, javaSlot);
} else {
plan.add(Click.LEFT, javaSlot);
int difference = containerAction.getFromItem().getCount() - cursorAction.getToItem().getCount();
for (int i = 0; i < difference; i++) {
plan.add(Click.RIGHT, javaSlot);
}
}
}
} else { //pickup into non-empty cursor
if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT) {
if (containerAction.getToItem().getCount() == 0) {
plan.add(Click.LEFT, javaSlot);
} else {
ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(),
inventory.getTransactionId().getAndIncrement(),
javaSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM,
ShiftClickItemParam.LEFT_CLICK);
session.sendDownstreamPacket(shiftClickPacket);
translator.updateInventory(session, inventory);
return;
}
} else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) {
plan.add(Click.LEFT, javaSlot);
} else {
int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot), false);
if (cursorSlot != -1) {
plan.add(Click.LEFT, cursorSlot);
} else {
translator.updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
plan.add(Click.LEFT, javaSlot);
int difference = cursorAction.getToItem().getCount() - cursorAction.getFromItem().getCount();
for (int i = 0; i < difference; i++) {
plan.add(Click.RIGHT, cursorSlot);
}
plan.add(Click.LEFT, javaSlot);
plan.add(Click.LEFT, cursorSlot);
}
}
}
plan.execute(session, translator, inventory, refresh);
return;
} else {
ClickPlan plan = new ClickPlan();
InventoryActionData fromAction;
InventoryActionData toAction;
if (actions.get(0).getFromItem().getCount() >= actions.get(0).getToItem().getCount()) {
fromAction = actions.get(0);
toAction = actions.get(1);
} else {
fromAction = actions.get(1);
toAction = actions.get(0);
}
int fromSlot = translator.bedrockSlotToJava(fromAction);
int toSlot = translator.bedrockSlotToJava(toAction);
if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) {
if ((craftSlot != 0 && craftSlot != -2) && (inventory.getItem(toSlot) == null
|| InventoryUtils.canStack(session.getInventory().getCursor(), inventory.getItem(toSlot)))) {
if (fromAction.getToItem().getCount() == 0) {
refresh = true;
plan.add(Click.LEFT, toSlot);
if (craftSlot != -1) {
plan.add(Click.LEFT, craftSlot);
}
} else {
int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount();
for (int i = 0; i < difference; i++) {
plan.add(Click.RIGHT, toSlot);
}
session.setCraftSlot(craftSlot);
}
plan.execute(session, translator, inventory, refresh);
return;
} else {
session.setCraftSlot(-2);
}
}
int cursorSlot = -1;
if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot
cursorSlot = findTempSlot(inventory,
session.getInventory().getCursor(),
Arrays.asList(fromSlot, toSlot),
translator.getSlotType(fromSlot) == SlotType.OUTPUT);
if (cursorSlot != -1) {
plan.add(Click.LEFT, cursorSlot);
} else {
translator.updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
}
if ((fromAction.getFromItem().equals(toAction.getToItem()) && !InventoryUtils.canStack(fromAction.getFromItem(), toAction.getFromItem()))
|| fromAction.getToItem().getId() == 0) { //slot swap
plan.add(Click.LEFT, fromSlot);
plan.add(Click.LEFT, toSlot);
if (fromAction.getToItem().getId() != 0) {
plan.add(Click.LEFT, fromSlot);
}
} else if (InventoryUtils.canStack(fromAction.getFromItem(), toAction.getToItem())) { //partial item move
if (translator.getSlotType(fromSlot) == SlotType.FURNACE_OUTPUT) {
ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(),
inventory.getTransactionId().getAndIncrement(),
fromSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM,
ShiftClickItemParam.LEFT_CLICK);
session.sendDownstreamPacket(shiftClickPacket);
translator.updateInventory(session, inventory);
return;
} else if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) {
session.setCraftSlot(cursorSlot);
plan.add(Click.LEFT, fromSlot);
int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount();
for (int i = 0; i < difference; i++) {
plan.add(Click.RIGHT, toSlot);
}
//client will send additional packets later to finish transferring crafting output
//translator will know how to handle this using the craftSlot variable
} else {
plan.add(Click.LEFT, fromSlot);
int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount();
for (int i = 0; i < difference; i++) {
plan.add(Click.RIGHT, toSlot);
}
plan.add(Click.LEFT, fromSlot);
}
}
if (cursorSlot != -1) {
plan.add(Click.LEFT, cursorSlot);
}
plan.execute(session, translator, inventory, refresh);
return;
}
translator.updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
}
private static int findTempSlot(Inventory inventory, ItemStack item, List<Integer> slotBlacklist, boolean emptyOnly) {
/*try and find a slot that can temporarily store the given item
only look in the main inventory and hotbar
only slots that are empty or contain a different type of item are valid*/
int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable slot (some servers disable it)
List<ItemStack> itemBlacklist = new ArrayList<>(slotBlacklist.size() + 1);
itemBlacklist.add(item);
for (int slot : slotBlacklist) {
ItemStack blacklistItem = inventory.getItem(slot);
if (blacklistItem != null)
itemBlacklist.add(blacklistItem);
}
for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) {
ItemStack testItem = inventory.getItem(i);
boolean acceptable = true;
if (testItem != null) {
if (emptyOnly) {
continue;
}
for (ItemStack blacklistItem : itemBlacklist) {
if (InventoryUtils.canStack(testItem, blacklistItem)) {
acceptable = false;
break;
}
}
}
if (acceptable && !slotBlacklist.contains(i))
return i;
}
//could not find a viable temp slot
return -1;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.click;
import com.github.steveice10.mc.protocol.data.game.window.*;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public enum Click {
LEFT(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK),
RIGHT(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK),
LEFT_SHIFT(WindowAction.SHIFT_CLICK_ITEM, ShiftClickItemParam.LEFT_CLICK),
DROP_ONE(WindowAction.DROP_ITEM, DropItemParam.DROP_FROM_SELECTED),
DROP_ALL(WindowAction.DROP_ITEM, DropItemParam.DROP_SELECTED_STACK),
LEFT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK),
RIGHT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK);
public static final int OUTSIDE_SLOT = -999;
public final WindowAction windowAction;
public final WindowActionParam actionParam;
}

View File

@ -0,0 +1,239 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.click;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.window.WindowAction;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Value;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.*;
public class ClickPlan {
private final List<ClickAction> plan = new ArrayList<>();
private final Int2ObjectMap<GeyserItemStack> simulatedItems;
private GeyserItemStack simulatedCursor;
private boolean simulating;
private final GeyserSession session;
private final InventoryTranslator translator;
private final Inventory inventory;
public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) {
this.session = session;
this.translator = translator;
this.inventory = inventory;
this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize());
this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
this.simulating = true;
}
private void resetSimulation() {
this.simulatedItems.clear();
this.simulatedCursor = session.getPlayerInventory().getCursor().copy();
}
public void add(Click click, int slot) {
if (!simulating)
throw new UnsupportedOperationException("ClickPlan already executed");
if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) {
slot = Click.OUTSIDE_SLOT;
}
ClickAction action = new ClickAction(click, slot);
plan.add(action);
simulateAction(action);
}
public void execute(boolean refresh) {
//update geyser inventory after simulation to avoid net id desync
resetSimulation();
ListIterator<ClickAction> planIter = plan.listIterator();
while (planIter.hasNext()) {
ClickAction action = planIter.next();
if (action.slot != Click.OUTSIDE_SLOT && translator.getSlotType(action.slot) != SlotType.NORMAL) {
refresh = true;
}
ItemStack clickedItemStack;
if (!planIter.hasNext() && refresh) {
clickedItemStack = InventoryUtils.REFRESH_ITEM;
} else if (action.click.windowAction == WindowAction.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) {
clickedItemStack = null;
} else {
clickedItemStack = getItem(action.slot).getItemStack();
}
short actionId = inventory.getNextTransactionId();
ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(
inventory.getId(),
actionId,
action.slot,
clickedItemStack,
action.click.windowAction,
action.click.actionParam
);
simulateAction(action);
session.sendDownstreamPacket(clickPacket);
if (clickedItemStack == InventoryUtils.REFRESH_ITEM) {
session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true));
}
System.out.println(clickPacket);
}
session.getPlayerInventory().setCursor(simulatedCursor, session);
for (Int2ObjectMap.Entry<GeyserItemStack> simulatedSlot : simulatedItems.int2ObjectEntrySet()) {
inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session);
}
simulating = false;
}
public GeyserItemStack getItem(int slot) {
return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy());
}
public GeyserItemStack getCursor() {
return simulatedCursor;
}
private void setItem(int slot, GeyserItemStack item) {
if (simulating) {
simulatedItems.put(slot, item);
} else {
inventory.setItem(slot, item, session);
}
}
private void setCursor(GeyserItemStack item) {
if (simulating) {
simulatedCursor = item;
} else {
session.getPlayerInventory().setCursor(item, session);
}
}
private void simulateAction(ClickAction action) {
GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor();
switch (action.click) {
case LEFT_OUTSIDE:
setCursor(GeyserItemStack.EMPTY);
return;
case RIGHT_OUTSIDE:
if (!cursor.isEmpty()) {
cursor.sub(1);
}
return;
}
GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot);
if (translator.getSlotType(action.slot) == SlotType.OUTPUT) {
switch (action.click) {
case LEFT:
case RIGHT:
if (cursor.isEmpty() && !clicked.isEmpty()) {
setCursor(clicked.copy());
} else if (InventoryUtils.canStack(cursor, clicked)) {
cursor.add(clicked.getAmount());
}
break;
}
} else {
switch (action.click) {
case LEFT:
if (!InventoryUtils.canStack(cursor, clicked)) {
setCursor(clicked);
setItem(action.slot, cursor);
} else {
setCursor(GeyserItemStack.EMPTY);
clicked.add(cursor.getAmount());
}
break;
case RIGHT:
if (cursor.isEmpty() && !clicked.isEmpty()) {
int half = clicked.getAmount() / 2; //smaller half
setCursor(clicked.copy(clicked.getAmount() - half)); //larger half
clicked.setAmount(half);
} else if (!cursor.isEmpty() && clicked.isEmpty()) {
cursor.sub(1);
setItem(action.slot, cursor.copy(1));
} else if (InventoryUtils.canStack(cursor, clicked)) {
cursor.sub(1);
clicked.add(1);
}
break;
case LEFT_SHIFT:
//TODO
break;
case DROP_ONE:
if (!clicked.isEmpty()) {
clicked.sub(1);
}
break;
case DROP_ALL:
setItem(action.slot, GeyserItemStack.EMPTY);
break;
}
}
}
/**
* @return a new set of all affected slots. This isn't a constant variable; it's newly generated each time it is run.
*/
public IntSet getAffectedSlots() {
IntSet affectedSlots = new IntOpenHashSet();
for (ClickAction action : plan) {
if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) {
affectedSlots.add(action.slot);
}
}
return affectedSlots;
}
@Value
private static class ClickAction {
Click click;
/**
* Java slot
*/
int slot;
}
}

View File

@ -38,6 +38,9 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
/**
* Manages the fake block we implement for each inventory.
*/
@AllArgsConstructor
public class BlockInventoryHolder extends InventoryHolder {
private final int blockId;
@ -45,6 +48,11 @@ public class BlockInventoryHolder extends InventoryHolder {
@Override
public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
//TODO: Improve on this (for example, multiple block states). We need this for the beacon.
if (BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionPosition())) == blockId) {
inventory.setHolderPosition(session.getLastInteractionPosition());
return;
}
Vector3i position = session.getPlayerEntity().getPosition().toInt();
position = position.add(Vector3i.UP);
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
@ -79,12 +87,16 @@ public class BlockInventoryHolder extends InventoryHolder {
@Override
public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
Vector3i holderPos = inventory.getHolderPosition();
if (holderPos.equals(session.getLastInteractionPosition())) {
return;
}
Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ());
int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ());
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
blockPacket.setDataLayer(0);
blockPacket.setBlockPosition(holderPos);
blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock));
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(blockPacket);
}
}

View File

@ -23,25 +23,33 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder;
import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class BlockInventoryTranslator extends BaseInventoryTranslator {
/**
* Provided as a base for any inventory that requires a block for opening it
*/
public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTranslator {
private final InventoryHolder holder;
private final InventoryUpdater updater;
public BlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) {
/**
* @param size the amount of slots that the inventory adds alongside the base inventory slots
* @param javaBlockIdentifier a Java block identifier that is used as a temporary block
* @param containerType the container type of this inventory
* @param updater updater
*/
public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) {
super(size);
int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier);
int blockId = BlockTranslator.getBedrockBlockId(javaBlockState);
this.holder = new BlockInventoryHolder(blockId, containerType);
this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), containerType);
this.updater = updater;
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.inventory.AnvilContainer;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
public AnvilInventoryTranslator() {
super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE);
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_MATERIAL) {
return 1;
}
if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 2;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
switch (slot) {
case 0:
return new BedrockContainerSlot(ContainerSlotType.ANVIL_INPUT, 1);
case 1:
return new BedrockContainerSlot(ContainerSlotType.ANVIL_MATERIAL, 2);
case 2:
return new BedrockContainerSlot(ContainerSlotType.ANVIL_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
switch (slot) {
case 0:
return 1;
case 1:
return 2;
case 2:
return 50;
}
return super.javaSlotToBedrock(slot);
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new AnvilContainer(name, windowId, this.size, playerInventory);
}
}

View File

@ -23,18 +23,21 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.inventory.Container;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.inventory.SlotType;
import java.util.List;
public abstract class BaseInventoryTranslator extends InventoryTranslator{
BaseInventoryTranslator(int size) {
public abstract class BaseInventoryTranslator extends InventoryTranslator {
public BaseInventoryTranslator(int size) {
super(size);
}
@ -44,15 +47,18 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
int slotnum = action.getSlot();
if (action.getSource().getContainerId() == ContainerId.INVENTORY) {
//hotbar
if (slotnum >= 9) {
return slotnum + this.size - 9;
} else {
return slotnum + this.size + 27;
}
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
int slotnum = slotInfoData.getSlot();
switch (slotInfoData.getContainer()) {
case HOTBAR_AND_INVENTORY:
case HOTBAR:
case INVENTORY:
//hotbar
if (slotnum >= 9) {
return slotnum + this.size - 9;
} else {
return slotnum + this.size + 27;
}
}
return slotnum;
}
@ -70,13 +76,26 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{
return slot;
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot >= this.size) {
final int tmp = slot - this.size;
if (tmp < 27) {
return new BedrockContainerSlot(ContainerSlotType.INVENTORY, tmp + 9);
} else {
return new BedrockContainerSlot(ContainerSlotType.HOTBAR, tmp - 27);
}
}
throw new IllegalArgumentException("Unknown bedrock slot");
}
@Override
public SlotType getSlotType(int javaSlot) {
return SlotType.NORMAL;
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
InventoryActionDataTranslator.translate(this, session, inventory, actions);
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new Container(name, windowId, this.size, playerInventory);
}
}

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSetBeaconEffectPacket;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.BeaconPaymentStackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import org.geysermc.connector.inventory.BeaconContainer;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
import java.util.Collections;
public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator {
public BeaconInventoryTranslator() {
super(1, "minecraft:beacon", ContainerType.BEACON, UIInventoryUpdater.INSTANCE);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
//FIXME?: Beacon graphics look weird after inputting an item. This might be a Bedrock bug, since it resets to nothing
// on BDS
BeaconContainer beaconContainer = (BeaconContainer) inventory;
switch (key) {
case 0:
// Power - beacon doesn't use this, and uses the block position instead
break;
case 1:
beaconContainer.setPrimaryId(value == -1 ? 0 : value);
break;
case 2:
beaconContainer.setSecondaryId(value == -1 ? 0 : value);
break;
}
// Send a block entity data packet update to the fake beacon inventory
Vector3i position = inventory.getHolderPosition();
NbtMapBuilder builder = NbtMap.builder()
.putInt("x", position.getX())
.putInt("y", position.getY())
.putInt("z", position.getZ())
.putString("CustomName", inventory.getTitle())
.putString("id", "Beacon")
.putInt("primary", beaconContainer.getPrimaryId())
.putInt("secondary", beaconContainer.getSecondaryId());
BlockEntityDataPacket packet = new BlockEntityDataPacket();
packet.setBlockPosition(position);
packet.setData(builder.build());
System.out.println(packet.toString());
session.sendUpstreamPacket(packet);
}
@Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return action.getType() == StackRequestActionType.BEACON_PAYMENT;
}
@Override
public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
// Input a beacon payment
BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0];
ClientSetBeaconEffectPacket packet = new ClientSetBeaconEffectPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect());
System.out.println(packet.toString());
session.sendDownstreamPacket(packet);
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.BEACON_PAYMENT) {
return 0;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.BEACON_PAYMENT, 27);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
if (slot == 0) {
return 27;
}
return super.javaSlotToBedrock(slot);
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new BeaconContainer(name, windowId, this.size, playerInventory);
}
}

View File

@ -23,18 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
public class BrewingInventoryTranslator extends BlockInventoryTranslator {
public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator {
public BrewingInventoryTranslator() {
super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, new ContainerInventoryUpdater());
super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, ContainerInventoryUpdater.INSTANCE);
}
@Override
@ -66,20 +68,18 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator {
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
final int slot = super.bedrockSlotToJava(action);
switch (slot) {
case 0:
return 3;
case 1:
return 0;
case 2:
return 1;
case 3:
return 2;
default:
return slot;
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
System.out.println("Brewing stand: " + slotInfoData);
if (slotInfoData.getContainer() == ContainerSlotType.BREWING_INPUT) {
// Ingredient
// TODO: This hasn't worked and then suddenly, it did.
return 3;
}
if (slotInfoData.getContainer() == ContainerSlotType.BREWING_RESULT) {
// Potions
return slotInfoData.getSlot() - 1;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
@ -96,4 +96,18 @@ public class BrewingInventoryTranslator extends BlockInventoryTranslator {
}
return super.javaSlotToBedrock(slot);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0 || slot == 1 || slot == 2) {
return new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot));
}
if (slot == 3) {
return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0);
}
if (slot == 4) {
return new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4);
}
return super.javaSlotToBedrockContainer(slot);
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.inventory.CartographyContainer;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator {
public CartographyInventoryTranslator() {
super(3, "minecraft:cartography_table", ContainerType.CARTOGRAPHY, UIInventoryUpdater.INSTANCE);
}
@Override
public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) {
if (javaDestinationSlot == 0) {
// Bedrock Edition can use paper in slot 0
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:paper");
} else if (javaDestinationSlot == 1) {
// Bedrock Edition can use a compass to create locator maps in the ADDITIONAL slot
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:compass");
}
return false;
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_ADDITIONAL) {
return 1;
}
if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 2;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
switch (slot) {
case 0:
return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_INPUT, 12);
case 1:
return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_ADDITIONAL, 13);
case 2:
return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
switch (slot) {
case 0:
return 12;
case 1:
return 13;
case 2:
return 50;
}
return super.javaSlotToBedrock(slot);
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new CartographyContainer(name, windowId, this.size, playerInventory);
}
}

View File

@ -23,47 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
import org.geysermc.connector.utils.InventoryUtils;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
import java.util.List;
public class CraftingInventoryTranslator extends BlockInventoryTranslator {
public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator {
public CraftingInventoryTranslator() {
super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, new CursorInventoryUpdater());
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
if (action.getSlot() == 50) {
// Slot 50 is used for crafting with a controller.
return 0;
}
if (action.getSource().getContainerId() == ContainerId.UI) {
int slotnum = action.getSlot();
if (slotnum >= 32 && 42 >= slotnum) {
return slotnum - 31;
}
}
return super.bedrockSlotToJava(action);
}
@Override
public int javaSlotToBedrock(int slot) {
if (slot < size) {
return slot == 0 ? 50 : slot + 31;
}
return super.javaSlotToBedrock(slot);
super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE);
}
@Override
@ -74,16 +45,34 @@ public class CraftingInventoryTranslator extends BlockInventoryTranslator {
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
if (session.getGameMode() == GameMode.CREATIVE) {
for (InventoryActionData action : actions) {
if (action.getSource().getType() == InventorySource.Type.CREATIVE) {
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
}
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot >= 1 && slot <= 9) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 31);
}
super.translateActions(session, inventory, actions);
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT) {
// Java goes from 1 - 9, left to right then up to down
// Bedrock is the same, but it starts from 32.
return slotInfoData.getSlot() - 31;
}
if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_OUTPUT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 0;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public int javaSlotToBedrock(int slot) {
if (slot < size) {
return slot == 0 ? 50 : slot + 31;
}
return super.javaSlotToBedrock(slot);
}
}

View File

@ -0,0 +1,219 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket;
import org.geysermc.connector.inventory.EnchantingContainer;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
import org.geysermc.connector.network.translators.item.Enchantment;
import java.util.Arrays;
import java.util.Collections;
public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator {
public EnchantingInventoryTranslator() {
super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, UIInventoryUpdater.INSTANCE);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
int slotToUpdate;
EnchantingContainer enchantingInventory = (EnchantingContainer) inventory;
boolean shouldUpdate = false;
switch (key) {
case 0:
case 1:
case 2:
// Experience required
slotToUpdate = key;
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setXpCost(value);
break;
case 4:
case 5:
case 6:
// Enchantment type
slotToUpdate = key - 4;
int index = value;
if (index != -1) {
Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[index].name().toLowerCase());
if (enchantment != null) {
// Convert the Java enchantment index to Bedrock's
index = enchantment.ordinal();
} else {
index = -1;
}
}
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setJavaEnchantIndex(value);
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setBedrockEnchantIndex(index);
break;
case 7:
case 8:
case 9:
// Enchantment level
slotToUpdate = key - 7;
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantLevel(value);
shouldUpdate = true; // Java sends each property as its own packet, so let's only update after all properties have been sent
break;
default:
return;
}
if (shouldUpdate) {
enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].build(session);
PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket();
packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions()));
System.out.println(packet);
session.sendUpstreamPacket(packet);
}
}
@Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return action.getType() == StackRequestActionType.CRAFT_RECIPE;
}
@Override
public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
// Client has requested an item to be enchanted
CraftRecipeStackRequestActionData craftRecipeData = (CraftRecipeStackRequestActionData) request.getActions()[0];
EnchantingContainer enchantingInventory = (EnchantingContainer) inventory;
int javaSlot = -1;
for (int i = 0; i < enchantingInventory.getEnchantOptions().length; i++) {
EnchantOptionData enchantData = enchantingInventory.getEnchantOptions()[i];
if (enchantData != null) {
if (craftRecipeData.getRecipeNetworkId() == enchantData.getEnchantNetId()) {
// Enchant net ID is how we differentiate between what item Bedrock wants
javaSlot = enchantingInventory.getGeyserEnchantOptions()[i].getJavaIndex();
break;
}
}
}
if (javaSlot == -1) {
// Slot should be determined as 0, 1, or 2
return rejectRequest(request);
}
ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), javaSlot);
System.out.println(packet);
session.sendDownstreamPacket(packet);
return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet()));
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_LAPIS) {
return 1;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_INPUT, 14);
}
if (slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_LAPIS, 15);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
if (slot == 0) {
return 14;
}
if (slot == 1) {
return 15;
}
return super.javaSlotToBedrock(slot);
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new EnchantingContainer(name, windowId, this.size, playerInventory);
}
/**
* Enchantments classified by their Java index
*/
public enum JavaEnchantment {
PROTECTION,
FIRE_PROTECTION,
FEATHER_FALLING,
BLAST_PROTECTION,
PROJECTILE_PROTECTION,
RESPIRATION,
AQUA_AFFINITY,
THORNS,
DEPTH_STRIDER,
FROST_WALKER,
BINDING_CURSE,
SOUL_SPEED,
SHARPNESS,
SMITE,
BANE_OF_ARTHROPODS,
KNOCKBACK,
FIRE_ASPECT,
LOOTING,
SWEEPING,
EFFICIENCY,
SILK_TOUCH,
UNBREAKING,
FORTUNE,
POWER,
PUNCH,
FLAME,
INFINITY,
LUCK_OF_THE_SEA,
LURE,
LOYALTY,
IMPALING,
RIPTIDE,
CHANNELING,
MULTISHOT,
QUICK_CHARGE,
PIERCING,
MENDING,
VANISHING_CURSE
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
/**
* Implemented on top of any block that does not have special properties implemented
*/
public class GenericBlockInventoryTranslator extends AbstractBlockInventoryTranslator {
public GenericBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType) {
super(size, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot);
}
return super.javaSlotToBedrockContainer(javaSlot);
}
}

View File

@ -23,34 +23,44 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
public class GrindstoneInventoryTranslator extends BlockInventoryTranslator {
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator {
public GrindstoneInventoryTranslator() {
super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, new CursorInventoryUpdater());
super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, UIInventoryUpdater.INSTANCE);
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
final int slot = super.bedrockSlotToJava(action);
if (action.getSource().getContainerId() == ContainerId.UI) {
switch (slot) {
case 16:
return 0;
case 17:
return 1;
case 50:
return 2;
default:
return slot;
}
} return slot;
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_ADDITIONAL) {
return 1;
}
if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 2;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
switch (slot) {
case 0:
return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_INPUT, 16);
case 1:
return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_ADDITIONAL, 17);
case 2:
return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
@ -65,5 +75,4 @@ public class GrindstoneInventoryTranslator extends BlockInventoryTranslator {
}
return super.javaSlotToBedrock(slot);
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtMapBuilder;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.LecternContainer;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.utils.BlockEntityUtils;
import org.geysermc.connector.utils.InventoryUtils;
public class LecternInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
public LecternInventoryTranslator() {
super(1);
this.updater = new LecternInventoryUpdater();
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
if (key == 0) { // Lectern page update
LecternContainer lecternContainer = (LecternContainer) inventory;
lecternContainer.setCurrentBedrockPage(value / 2);
lecternContainer.setBlockEntityTag(lecternContainer.getBlockEntityTag().toBuilder().putInt("page", lecternContainer.getCurrentBedrockPage()).build());
BlockEntityUtils.updateBlockEntity(session, lecternContainer.getBlockEntityTag(), lecternContainer.getPosition());
}
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
this.updater.updateSlot(this, session, inventory, slot);
if (slot == 0) {
LecternContainer lecternContainer = (LecternContainer) inventory;
if (session.isDroppingLecternBook()) {
// We have to enter the inventory GUI to eject the book
ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3);
session.sendDownstreamPacket(packet);
session.setDroppingLecternBook(false);
InventoryUtils.closeInventory(session, inventory.getId());
} else if (lecternContainer.getBlockEntityTag() == null) {
// If the method returns true, this is already handled for us
GeyserItemStack geyserItemStack = inventory.getItem(0);
CompoundTag tag = geyserItemStack.getNbt();
if (tag != null) {
// Position has to be the last interacted position... right?
Vector3i position = session.getLastInteractionPosition();
// shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet
boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position);
int pagesSize = ((ListTag) tag.get("pages")).size();
ItemData itemData = geyserItemStack.getItemData(session);
NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize);
lecternTag.putCompound("book", NbtMap.builder()
.putByte("Count", (byte) itemData.getCount())
.putShort("Damage", (short) 0)
.putString("Name", "minecraft:written_book")
.putCompound("tag", itemData.getTag())
.build());
lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage());
NbtMap blockEntityTag = lecternTag.build();
// Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild
// the block entity tag
lecternContainer.setBlockEntityTag(blockEntityTag);
lecternContainer.setPosition(position);
if (shouldRefresh) {
// Update the lectern because it's not updated client-side
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position);
session.getLecternCache().add(position);
// Close the window - we will reopen it once the client has this data synced
ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId());
session.sendDownstreamPacket(closeWindowPacket);
InventoryUtils.closeInventory(session, inventory.getId());
session.getConnector().getLogger().warning("Closing inventory");
}
}
}
}
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new LecternContainer(name, windowId, this.size, playerInventory);
}
public static NbtMapBuilder getBaseLecternTag(int x, int y, int z, int totalPages) {
NbtMapBuilder builder = NbtMap.builder()
.putInt("x", x)
.putInt("y", y)
.putInt("z", z)
.putString("id", "Lectern");
if (totalPages != 0) {
builder.putByte("hasBook", (byte) 1);
builder.putInt("totalPages", totalPages);
} else {
// Not usually needed, but helps with kicking out Bedrock players from reading the UI
builder.putByte("hasBook", (byte) 0);
}
return builder;
}
private static class LecternInventoryUpdater extends InventoryUpdater {
}
}

View File

@ -0,0 +1,220 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.nukkitx.nbt.NbtMap;
import com.nukkitx.nbt.NbtType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
import org.geysermc.connector.network.translators.item.translators.BannerTranslator;
import java.util.Collections;
import java.util.List;
public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
/**
* A map of Bedrock patterns to Java index. Used to request for a specific banner pattern.
*/
private static final Object2IntMap<String> PATTERN_TO_INDEX = new Object2IntOpenHashMap<>();
static {
// Added from left-to-right then up-to-down in the order Java presents it
int index = 1;
PATTERN_TO_INDEX.put("bl", index++);
PATTERN_TO_INDEX.put("br", index++);
PATTERN_TO_INDEX.put("tl", index++);
PATTERN_TO_INDEX.put("tr", index++);
PATTERN_TO_INDEX.put("bs", index++);
PATTERN_TO_INDEX.put("ts", index++);
PATTERN_TO_INDEX.put("ls", index++);
PATTERN_TO_INDEX.put("rs", index++);
PATTERN_TO_INDEX.put("cs", index++);
PATTERN_TO_INDEX.put("ms", index++);
PATTERN_TO_INDEX.put("drs", index++);
PATTERN_TO_INDEX.put("dls", index++);
PATTERN_TO_INDEX.put("ss", index++);
PATTERN_TO_INDEX.put("cr", index++);
PATTERN_TO_INDEX.put("sc", index++);
PATTERN_TO_INDEX.put("bt", index++);
PATTERN_TO_INDEX.put("tt", index++);
PATTERN_TO_INDEX.put("bts", index++);
PATTERN_TO_INDEX.put("tts", index++);
PATTERN_TO_INDEX.put("ld", index++);
PATTERN_TO_INDEX.put("rd", index++);
PATTERN_TO_INDEX.put("lud", index++);
PATTERN_TO_INDEX.put("rud", index++);
PATTERN_TO_INDEX.put("mc", index++);
PATTERN_TO_INDEX.put("mr", index++);
PATTERN_TO_INDEX.put("vh", index++);
PATTERN_TO_INDEX.put("hh", index++);
PATTERN_TO_INDEX.put("vhr", index++);
PATTERN_TO_INDEX.put("hhb", index++);
PATTERN_TO_INDEX.put("bo", index++);
index++; // Bordure indented, does not appear to exist in Bedrock?
PATTERN_TO_INDEX.put("gra", index++);
PATTERN_TO_INDEX.put("gru", index);
// Bricks do not appear to be a pattern on Bedrock, either
}
public LoomInventoryTranslator() {
super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE);
}
@Override
public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) {
if (javaDestinationSlot != 1) {
return false;
}
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
if (itemStack.isEmpty()) {
return false;
}
// Reject the item if Bedrock is attempting to put in a dye that is not a dye in Java Edition
return !itemStack.getItemEntry().getJavaIdentifier().endsWith("_dye");
}
@Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
// If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item
return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED && inventory.getItem(2).isEmpty();
}
@Override
public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
// TODO: I anticipate this will be changed in the future to use something non-deprecated. Keep an eye out.
StackRequestActionData data = request.getActions()[1];
if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) {
return rejectRequest(request);
}
CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data;
// Get the patterns compound tag
List<NbtMap> newBlockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND);
// Get the pattern that the Bedrock client requests - the last pattern in the Patterns list
NbtMap pattern = newBlockEntityTag.get(newBlockEntityTag.size() - 1);
// Get the Java index of this pattern
int index = PATTERN_TO_INDEX.getOrDefault(pattern.getString("Pattern"), -1);
if (index == -1) {
return rejectRequest(request);
}
// Java's formula: 4 * row + col
// And the Java loom window has a fixed row/width of four
// So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :)
ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index);
System.out.println(packet);
session.sendDownstreamPacket(packet);
GeyserItemStack inputCopy = inventory.getItem(0).copy();
inputCopy.setNetId(session.getNextItemNetId());
// Add the pattern manually, for better item synchronization
if (inputCopy.getNbt() == null) {
inputCopy.setNbt(new CompoundTag(""));
}
CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag");
CompoundTag javaBannerPattern = BannerTranslator.getJavaBannerPattern(pattern);
if (blockEntityTag != null) {
ListTag patternsList = blockEntityTag.get("Patterns");
if (patternsList != null) {
patternsList.add(javaBannerPattern);
} else {
patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern));
blockEntityTag.put(patternsList);
}
} else {
blockEntityTag = new CompoundTag("BlockEntityTag");
ListTag patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern));
blockEntityTag.put(patternsList);
inputCopy.getNbt().put(blockEntityTag);
}
// Set the new item as the output
inventory.setItem(3, inputCopy, session);
return translateRequest(session, inventory, request);
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.LOOM_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.LOOM_DYE) {
return 1;
}
if (slotInfoData.getContainer() == ContainerSlotType.LOOM_MATERIAL) {
return 2;
}
if (slotInfoData.getContainer() == ContainerSlotType.LOOM_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 3;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
switch (slot) {
case 0:
return new BedrockContainerSlot(ContainerSlotType.LOOM_INPUT, 9);
case 1:
return new BedrockContainerSlot(ContainerSlotType.LOOM_DYE, 10);
case 2:
return new BedrockContainerSlot(ContainerSlotType.LOOM_MATERIAL, 11);
case 3:
return new BedrockContainerSlot(ContainerSlotType.LOOM_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
switch (slot) {
case 0:
return 9;
case 1:
return 10;
case 2:
return 11;
case 3:
return 50;
}
return super.javaSlotToBedrock(slot);
}
}

View File

@ -0,0 +1,154 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
import com.nukkitx.protocol.bedrock.data.inventory.*;
import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.MerchantContainer;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
public MerchantInventoryTranslator() {
super(3);
this.updater = UIInventoryUpdater.INSTANCE;
}
@Override
public int javaSlotToBedrock(int slot) {
switch (slot) {
case 0:
return 4;
case 1:
return 5;
case 2:
return 50;
}
return super.javaSlotToBedrock(slot);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
switch (slot) {
case 0:
return new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT1, 4);
case 1:
return new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT2, 5);
case 2:
return new BedrockContainerSlot(ContainerSlotType.TRADE2_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
switch (slotInfoData.getContainer()) {
case TRADE2_INGREDIENT1:
return 0;
case TRADE2_INGREDIENT2:
return 1;
case TRADE2_RESULT:
case CREATIVE_OUTPUT:
return 2;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public SlotType getSlotType(int javaSlot) {
if (javaSlot == 2) {
return SlotType.OUTPUT;
}
return SlotType.NORMAL;
}
@Override
public void prepareInventory(GeyserSession session, Inventory inventory) {
MerchantContainer merchantInventory = (MerchantContainer) inventory;
if (merchantInventory.getVillager() == null) {
long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet();
Vector3f pos = session.getPlayerEntity().getPosition().sub(0, 3, 0);
EntityDataMap metadata = new EntityDataMap();
metadata.put(EntityData.SCALE, 0f);
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0f);
Entity villager = new Entity(0, geyserId, EntityType.VILLAGER, pos, Vector3f.ZERO, Vector3f.ZERO);
villager.setMetadata(metadata);
villager.spawnEntity(session);
SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
EntityLinkData.Type type = EntityLinkData.Type.PASSENGER;
linkPacket.setEntityLink(new EntityLinkData(session.getPlayerEntity().getGeyserId(), geyserId, type, true, false));
session.sendUpstreamPacket(linkPacket);
merchantInventory.setVillager(villager);
}
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
//Handled in JavaTradeListTranslator
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
MerchantContainer merchantInventory = (MerchantContainer) inventory;
if (merchantInventory.getVillager() != null) {
merchantInventory.getVillager().despawnEntity(session);
}
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new MerchantContainer(name, windowId, this.size, playerInventory);
}
}

View File

@ -23,20 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.protocol.bedrock.data.inventory.*;
import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LanguageUtils;
@ -61,11 +61,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
ItemData[] contents = new ItemData[36];
// Inventory
for (int i = 9; i < 36; i++) {
contents[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
contents[i] = inventory.getItem(i).getItemData(session);
}
// Hotbar
for (int i = 36; i < 45; i++) {
contents[i - 36] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
contents[i - 36] = inventory.getItem(i).getItemData(session);
}
inventoryContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(inventoryContentPacket);
@ -75,7 +75,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
armorContentPacket.setContainerId(ContainerId.ARMOR);
contents = new ItemData[4];
for (int i = 5; i < 9; i++) {
contents[i - 5] = ItemTranslator.translateToBedrock(session, inventory.getItem(i));
contents[i - 5] = inventory.getItem(i).getItemData(session);
}
armorContentPacket.setContents(Arrays.asList(contents));
session.sendUpstreamPacket(armorContentPacket);
@ -83,7 +83,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
// Offhand
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
offhandPacket.setContents(Collections.singletonList(ItemTranslator.translateToBedrock(session, inventory.getItem(45))));
offhandPacket.setContents(Collections.singletonList(inventory.getItem(45).getItemData(session)));
session.sendUpstreamPacket(offhandPacket);
}
@ -102,7 +102,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
if (session.getGameMode() == GameMode.CREATIVE) {
slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK);
}else{
slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i)));
slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack()));
}
session.sendUpstreamPacket(slotPacket);
@ -127,21 +127,23 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
slotPacket.setContainerId(ContainerId.UI);
slotPacket.setSlot(slot + 27);
}
slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(slot)));
slotPacket.setItem(inventory.getItem(slot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
} else if (slot == 45) {
InventoryContentPacket offhandPacket = new InventoryContentPacket();
offhandPacket.setContainerId(ContainerId.OFFHAND);
offhandPacket.setContents(Collections.singletonList(ItemTranslator.translateToBedrock(session, inventory.getItem(slot))));
offhandPacket.setContents(Collections.singletonList(inventory.getItem(slot).getItemData(session)));
session.sendUpstreamPacket(offhandPacket);
}
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
int slotnum = action.getSlot();
switch (action.getSource().getContainerId()) {
case ContainerId.INVENTORY:
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
int slotnum = slotInfoData.getSlot();
switch (slotInfoData.getContainer()) {
case HOTBAR_AND_INVENTORY:
case HOTBAR:
case INVENTORY:
// Inventory
if (slotnum >= 9 && slotnum <= 35) {
return slotnum;
@ -151,19 +153,19 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
return slotnum + 36;
}
break;
case ContainerId.ARMOR:
case ARMOR:
if (slotnum >= 0 && slotnum <= 3) {
return slotnum + 5;
}
break;
case ContainerId.OFFHAND:
case OFFHAND:
return 45;
case ContainerId.UI:
case CRAFTING_INPUT:
if (slotnum >= 28 && 31 >= slotnum) {
return slotnum - 27;
}
break;
case ContainerId.CRAFTING_RESULT:
case CREATIVE_OUTPUT:
return 0;
}
return slotnum;
@ -171,7 +173,26 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
@Override
public int javaSlotToBedrock(int slot) {
return slot;
return -1;
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot >= 36 && slot <= 44) {
return new BedrockContainerSlot(ContainerSlotType.HOTBAR, slot - 36);
} else if (slot >= 9 && slot <= 35) {
return new BedrockContainerSlot(ContainerSlotType.INVENTORY, slot);
} else if (slot >= 5 && slot <= 8) {
return new BedrockContainerSlot(ContainerSlotType.ARMOR, slot - 5);
} else if (slot == 45) {
return new BedrockContainerSlot(ContainerSlotType.OFFHAND, 1);
} else if (slot >= 1 && slot <= 4) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 27);
} else if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0);
} else {
throw new IllegalArgumentException("Unknown bedrock slot");
}
}
@Override
@ -182,52 +203,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
if (session.getGameMode() == GameMode.CREATIVE) {
//crafting grid is not visible in creative mode in java edition
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == ContainerId.UI && (action.getSlot() >= 28 && 31 >= action.getSlot())) {
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
}
public void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequestPacket.Request> requests) {
super.translateRequests(session, inventory, requests);
}
ItemStack javaItem;
for (InventoryActionData action : actions) {
switch (action.getSource().getContainerId()) {
case ContainerId.INVENTORY:
case ContainerId.ARMOR:
case ContainerId.OFFHAND:
int javaSlot = bedrockSlotToJava(action);
if (action.getToItem().getId() == 0) {
javaItem = new ItemStack(-1, 0, null);
} else {
javaItem = ItemTranslator.translateToJava(action.getToItem());
}
ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem);
session.sendDownstreamPacket(creativePacket);
inventory.setItem(javaSlot, javaItem);
break;
case ContainerId.UI:
if (action.getSlot() == 0) {
session.getInventory().setCursor(ItemTranslator.translateToJava(action.getToItem()));
}
break;
case ContainerId.NONE:
if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION
&& action.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) {
javaItem = ItemTranslator.translateToJava(action.getToItem());
ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem);
session.sendDownstreamPacket(creativeDropPacket);
}
break;
}
}
return;
}
InventoryActionDataTranslator.translate(this, session, inventory, actions);
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
throw new UnsupportedOperationException();
}
@Override

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator {
public ShulkerInventoryTranslator() {
super(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, ContainerInventoryUpdater.INSTANCE);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.SHULKER, javaSlot);
}
return super.javaSlotToBedrockContainer(javaSlot);
}
}

View File

@ -23,34 +23,44 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
public class SmithingInventoryTranslator extends BlockInventoryTranslator {
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
public SmithingInventoryTranslator() {
super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, new CursorInventoryUpdater());
super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
final int slot = super.bedrockSlotToJava(action);
if (action.getSource().getContainerId() == ContainerId.UI) {
switch (slot) {
case 51:
return 0;
case 52:
return 1;
case 50:
return 2;
default:
return slot;
}
} return slot;
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_MATERIAL) {
return 1;
}
if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 2;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
switch (slot) {
case 0:
return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case 1:
return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case 2:
return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
@ -65,5 +75,4 @@ public class SmithingInventoryTranslator extends BlockInventoryTranslator {
}
return super.javaSlotToBedrock(slot);
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData;
import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType;
import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket;
import it.unimi.dsi.fastutil.ints.IntList;
import org.geysermc.connector.inventory.GeyserItemStack;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.inventory.StonecutterContainer;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater;
import org.geysermc.connector.network.translators.item.ItemTranslator;
public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator {
public StonecutterInventoryTranslator() {
super(2, "minecraft:stonecutter[facing=north]", ContainerType.STONECUTTER, UIInventoryUpdater.INSTANCE);
}
@Override
public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) {
return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED;
}
@Override
public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) {
// TODO: Also surely to change in the future
StackRequestActionData data = request.getActions()[1];
if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) {
return rejectRequest(request);
}
CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data;
StonecutterContainer container = (StonecutterContainer) inventory;
// Get the ID of the item we are cutting
int id = inventory.getItem(0).getJavaId();
// Look up all possible options of cutting from this ID
IntList results = session.getStonecutterRecipes().get(id);
if (results == null) {
return rejectRequest(request);
}
System.out.println(id + " " + results);
ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]);
System.out.println(javaOutput);
int button = results.indexOf(javaOutput.getId());
// If we've already pressed the button with this item, no need to press it again!
if (container.getStonecutterButton() != button) {
// Getting the index of the item in the Java stonecutter list
ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), button);
System.out.println(packet.toString());
session.sendDownstreamPacket(packet);
container.setStonecutterButton(button);
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
// We don't know there is an output here, so we tell ourselves that there is
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
}
}
return translateRequest(session, inventory, request);
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_INPUT) {
return 0;
}
if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) {
return 1;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_INPUT, 3);
}
if (slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
if (slot == 0) {
return 3;
}
if (slot == 1) {
return 50;
}
return super.javaSlotToBedrock(slot);
}
@Override
public SlotType getSlotType(int javaSlot) {
if (javaSlot == 1) {
return SlotType.OUTPUT;
}
return super.getSlotType(javaSlot);
}
@Override
public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) {
return new StonecutterContainer(name, windowId, this.size, playerInventory);
}
}

View File

@ -23,16 +23,15 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators.chest;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.translators.BaseInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import org.geysermc.connector.utils.InventoryUtils;
import java.util.List;
public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
@ -53,17 +52,10 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
for (InventoryActionData action : actions) {
if (action.getSource().getContainerId() == inventory.getId()) {
if (action.getSlot() >= size) {
updateInventory(session, inventory);
InventoryUtils.updateCursor(session);
return;
}
}
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot);
}
super.translateActions(session, inventory, actions);
return super.javaSlotToBedrockContainer(javaSlot);
}
}

View File

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators.chest;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.nukkitx.math.vector.Vector3i;

View File

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators.chest;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.inventory.Inventory;

View File

@ -23,18 +23,21 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators.furnace;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import org.geysermc.connector.network.translators.inventory.SlotType;
import org.geysermc.connector.network.translators.inventory.translators.AbstractBlockInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater;
public class FurnaceInventoryTranslator extends BlockInventoryTranslator {
public FurnaceInventoryTranslator() {
super(3, "minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE, new ContainerInventoryUpdater());
public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator {
AbstractFurnaceInventoryTranslator(String javaBlockIdentifier, ContainerType containerType) {
super(3, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE);
}
@Override
@ -50,9 +53,6 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator {
break;
case 2:
dataPacket.setProperty(ContainerSetDataPacket.FURNACE_TICK_COUNT);
if (inventory.getWindowType() == WindowType.BLAST_FURNACE || inventory.getWindowType() == WindowType.SMOKER) {
value *= 2;
}
break;
default:
return;
@ -67,4 +67,15 @@ public class FurnaceInventoryTranslator extends BlockInventoryTranslator {
return SlotType.FURNACE_OUTPUT;
return SlotType.NORMAL;
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.FURNACE_FUEL, javaSlotToBedrock(slot));
}
if (slot == 2) {
return new BedrockContainerSlot(ContainerSlotType.FURNACE_OUTPUT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.furnace;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
public class BlastFurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator {
public BlastFurnaceInventoryTranslator() {
super("minecraft:blast_furnace[facing=north,lit=false]", ContainerType.BLAST_FURNACE);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.BLAST_FURNACE_INGREDIENT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.furnace;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
public class FurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator {
public FurnaceInventoryTranslator() {
super("minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.FURNACE_INGREDIENT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.furnace;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
public class SmokerInventoryTranslator extends AbstractFurnaceInventoryTranslator {
public SmokerInventoryTranslator() {
super("minecraft:smoker[facing=north,lit=false]", ContainerType.SMOKER);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.SMOKER_INGREDIENT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
}
}

View File

@ -23,68 +23,20 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory;
package org.geysermc.connector.network.translators.inventory.translators.horse;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.translators.BaseInventoryTranslator;
import org.geysermc.connector.network.translators.inventory.updater.HorseInventoryUpdater;
import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater;
import java.util.List;
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator {
private final InventoryUpdater updater;
public MerchantInventoryTranslator() {
super(3);
this.updater = new CursorInventoryUpdater();
}
@Override
public int javaSlotToBedrock(int slot) {
switch (slot) {
case 0:
return 4;
case 1:
return 5;
case 2:
return 50;
}
return super.javaSlotToBedrock(slot);
}
@Override
public int bedrockSlotToJava(InventoryActionData action) {
switch (action.getSource().getContainerId()) {
case ContainerId.UI:
switch (action.getSlot()) {
case 4:
return 0;
case 5:
return 1;
case 50:
return 2;
}
break;
case -28: // Trading 1?
return 0;
case -29: // Trading 2?
return 1;
case -30: // Trading Output?
return 2;
}
return super.bedrockSlotToJava(action);
}
@Override
public SlotType getSlotType(int javaSlot) {
if (javaSlot == 2) {
return SlotType.OUTPUT;
}
return SlotType.NORMAL;
public AbstractHorseInventoryTranslator(int size) {
super(size);
this.updater = HorseInventoryUpdater.INSTANCE;
}
@Override
@ -99,8 +51,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
session.setLastInteractedVillagerEid(-1);
session.setVillagerTrades(null);
}
@Override
@ -112,13 +63,4 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
}
@Override
public void translateActions(GeyserSession session, Inventory inventory, List<InventoryActionData> actions) {
if (actions.stream().anyMatch(a -> a.getSource().getContainerId() == -31)) {
return;
}
super.translateActions(session, inventory, actions);
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.horse;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
import java.util.Arrays;
public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInventoryTranslator {
private final int chestSize;
private final int equipSlot;
/**
* @param size the total Java size of the inventory
* @param equipSlot the Java equipment slot. Java always has two slots - one for armor and one for saddle. Chested horses
* on Bedrock only acknowledge one slot.
*/
public ChestedHorseInventoryTranslator(int size, int equipSlot) {
super(size);
this.chestSize = size - 2;
this.equipSlot = equipSlot;
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) {
return this.equipSlot;
}
if (slotInfoData.getContainer() == ContainerSlotType.CONTAINER) {
return slotInfoData.getSlot() + 1;
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == this.equipSlot) {
return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, 0);
}
if (slot <= this.size - 1) { // Accommodate for the lack of one slot (saddle or armor)
return new BedrockContainerSlot(ContainerSlotType.CONTAINER, slot - 1);
}
return super.javaSlotToBedrockContainer(slot);
}
@Override
public int javaSlotToBedrock(int slot) {
if (slot == 0 && this.equipSlot == 0) {
return 0;
}
if (slot <= this.size - 1) {
return slot - 1;
}
return super.javaSlotToBedrock(slot);
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
ItemData[] bedrockItems = new ItemData[36];
for (int i = 0; i < 36; i++) {
final int offset = i < 9 ? 27 : -9;
bedrockItems[i] = inventory.getItem(this.size + i + offset).getItemData(session);
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(ContainerId.INVENTORY);
contentPacket.setContents(Arrays.asList(bedrockItems));
session.sendUpstreamPacket(contentPacket);
ItemData[] horseItems = new ItemData[chestSize + 1];
// Manually specify the first slot - Java always has two slots (armor and saddle) and one is invisible.
// Bedrock doesn't have this invisible slot.
horseItems[0] = inventory.getItem(this.equipSlot).getItemData(session);
for (int i = 1; i < horseItems.length; i++) {
horseItems[i] = inventory.getItem(i + 1).getItemData(session);
}
InventoryContentPacket horseContentsPacket = new InventoryContentPacket();
horseContentsPacket.setContainerId(inventory.getId());
horseContentsPacket.setContents(Arrays.asList(horseItems));
System.out.println(horseContentsPacket);
session.sendUpstreamPacket(horseContentsPacket);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.horse;
public class DonkeyInventoryTranslator extends ChestedHorseInventoryTranslator {
public DonkeyInventoryTranslator(int size) {
super(size, 0);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.horse;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType;
import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData;
import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot;
public class HorseInventoryTranslator extends AbstractHorseInventoryTranslator {
public HorseInventoryTranslator(int size) {
super(size);
}
@Override
public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) {
if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) {
return slotInfoData.getSlot();
}
return super.bedrockSlotToJava(slotInfoData);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
if (slot == 0 || slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, slot);
}
return super.javaSlotToBedrockContainer(slot);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.inventory.translators.horse;
public class LlamaInventoryTranslator extends ChestedHorseInventoryTranslator {
public LlamaInventoryTranslator(int size) {
super(size, 1);
}
}

View File

@ -32,7 +32,6 @@ import lombok.AllArgsConstructor;
import org.geysermc.connector.inventory.Inventory;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.inventory.InventoryTranslator;
import org.geysermc.connector.network.translators.item.ItemTranslator;
import org.geysermc.connector.utils.InventoryUtils;
import org.geysermc.connector.utils.LanguageUtils;
@ -52,7 +51,7 @@ public class ChestInventoryUpdater extends InventoryUpdater {
List<ItemData> bedrockItems = new ArrayList<>(paddedSize);
for (int i = 0; i < paddedSize; i++) {
if (i < translator.size) {
bedrockItems.add(ItemTranslator.translateToBedrock(session, inventory.getItem(i)));
bedrockItems.add(inventory.getItem(i).getItemData(session));
} else {
bedrockItems.add(UNUSUABLE_SPACE_BLOCK);
}
@ -72,7 +71,7 @@ public class ChestInventoryUpdater extends InventoryUpdater {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(inventory.getId());
slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot));
slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot)));
slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session));
session.sendUpstreamPacket(slotPacket);
return true;
}

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