Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-03 14:50:19 +01:00
Merge branch 'floodgate-2.0' of https://github.com/Tim203/Geyser into floodgate-2.0
Dieser Commit ist enthalten in:
Commit
b36c3f2aa1
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -7,34 +7,51 @@ 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 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. -->
|
||||
<!--- 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. -->
|
||||
|
||||
**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. -->
|
||||
|
||||
**Server Version**
|
||||
<!--- Give us the exact output from `/version`. Saying "latest" does not help us at all. -->
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**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. -->
|
||||
**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.
|
||||
|
||||
**Minecraft: Bedrock Edition Version**
|
||||
<!-- The version of your Minecraft: Bedrock Edition client you tested with. -->
|
||||
|
||||
The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...).
|
||||
|
||||
**Additional Context**
|
||||
<!--- 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` --->
|
||||
|
||||
Add any other context about the problem here.
|
||||
|
@ -11,29 +11,36 @@ Thank for for considering a contribution! Generally, Geyser welcomes PRs from ev
|
||||
We have some general style guides that should be applied throughout the code:
|
||||
|
||||
```java
|
||||
public class LongClassName {
|
||||
private static final int AIR_ITEM = 0; // Static item names should be capitalized
|
||||
|
||||
private static final AIR_ITEM = 0; // Static item names should be capitalized
|
||||
public Int2IntMap items = new Int2IntOpenHashMap(); // Use the interface as the class type but initialize with the implementation.
|
||||
|
||||
public Int2IntMap items = new Int2IntOpenHashMap(); // Use the interface as the class type but initialize with the implementation.
|
||||
public int nameWithMultipleWords = 0;
|
||||
|
||||
public int nameWithMultipleWords = 0;
|
||||
/**
|
||||
* Javadoc comment to explain what a function does.
|
||||
*/
|
||||
@RandomAnnotation(stuff = true, moreStuff = "might exist")
|
||||
public void applyStuff() {
|
||||
Variable variable = new Variable();
|
||||
Variable otherVariable = new Variable();
|
||||
|
||||
/**
|
||||
* Javadoc comment to explain what a function does.
|
||||
*/
|
||||
public void applyStuff() {
|
||||
if (condition) {
|
||||
// Do stuff.
|
||||
} else if (anotherCondition) {
|
||||
// Do something else.
|
||||
}
|
||||
if (condition) {
|
||||
// Do stuff.
|
||||
} else if (anotherCondition) {
|
||||
// Do something else.
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
break:
|
||||
}
|
||||
switch (value) {
|
||||
case 0:
|
||||
stuff();
|
||||
break;
|
||||
case 1:
|
||||
differentStuff();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
31
Jenkinsfile
vendored
31
Jenkinsfile
vendored
@ -26,7 +26,27 @@ pipeline {
|
||||
}
|
||||
|
||||
steps {
|
||||
sh 'mvn javadoc:jar source:jar deploy -DskipTests'
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,5 +89,14 @@ 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', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
||||
build propagate: false, wait: false, job: 'GeyserMC/GeyserAndroid/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
||||
build propagate: false, wait: false, job: 'GeyserMC/GeyserConnect/master', parameters: [booleanParam(name: 'SKIP_DISCORD', value: true)]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
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
|
||||
|
@ -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.201 and Minecraft Java v1.16.4 - v1.16.5.
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser.
|
||||
@ -47,6 +47,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
|
||||
- Horse Inventory
|
||||
- Loom
|
||||
- Smithing Table
|
||||
- Grindstone
|
||||
|
||||
## What can't be fixed
|
||||
The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now.
|
||||
@ -54,6 +55,7 @@ The following things can't be fixed because of Bedrock limitations. They might b
|
||||
- Custom heads in inventories
|
||||
- Clickable links in chat
|
||||
- Glowing effect
|
||||
- Custom armor stand poses
|
||||
|
||||
## Compiling
|
||||
1. Clone the repo to your computer
|
||||
|
@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.15-SNAPSHOT</version>
|
||||
<version>1.16-R0.4-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@ -86,8 +86,8 @@
|
||||
<shadedPattern>org.geysermc.platform.bungeecord.shaded.dom4j</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.kyori.adventure</pattern>
|
||||
<shadedPattern>org.geysermc.platform.bungeecord.shaded.adventure</shadedPattern>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>org.geysermc.platform.bungeecord.shaded.kyori</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
|
@ -30,7 +30,9 @@ import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandExecutor;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -38,29 +40,42 @@ import java.util.Arrays;
|
||||
|
||||
public class GeyserBungeeCommandExecutor extends Command implements TabExecutor {
|
||||
|
||||
private final CommandExecutor commandExecutor;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public GeyserBungeeCommandExecutor(GeyserConnector connector) {
|
||||
super("geyser");
|
||||
|
||||
this.commandExecutor = new CommandExecutor(connector);
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length > 0) {
|
||||
if (getCommand(args[0]) != null) {
|
||||
if (!sender.hasPermission(getCommand(args[0]).getPermission())) {
|
||||
BungeeCommandSender commandSender = new BungeeCommandSender(sender);
|
||||
GeyserCommand command = this.commandExecutor.getCommand(args[0]);
|
||||
if (command != null) {
|
||||
BungeeCommandSender commandSender = new BungeeCommandSender(sender);
|
||||
if (!sender.hasPermission(command.getPermission())) {
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());
|
||||
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
return;
|
||||
}
|
||||
getCommand(args[0]).execute(new BungeeCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
GeyserSession session = null;
|
||||
if (command.isBedrockOnly()) {
|
||||
session = this.commandExecutor.getGeyserSession(commandSender);
|
||||
if (session == null) {
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale());
|
||||
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
command.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new BungeeCommandSender(sender), new String[0]);
|
||||
this.commandExecutor.getCommand("help").execute(null, new BungeeCommandSender(sender), new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,8 +86,4 @@ public class GeyserBungeeCommandExecutor extends Command implements TabExecutor
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private GeyserCommand getCommand(String label) {
|
||||
return connector.getCommandManager().getCommands().get(label);
|
||||
}
|
||||
}
|
||||
|
@ -97,8 +97,8 @@
|
||||
<shadedPattern>org.geysermc.platform.spigot.shaded.dom4j</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.kyori.adventure</pattern>
|
||||
<shadedPattern>org.geysermc.platform.spigot.shaded.adventure</shadedPattern>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>org.geysermc.platform.spigot.shaded.kyori</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
|
@ -25,40 +25,51 @@
|
||||
|
||||
package org.geysermc.platform.spigot.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabExecutor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandExecutor;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpigotCommandExecutor implements TabExecutor {
|
||||
public class GeyserSpigotCommandExecutor extends CommandExecutor implements TabExecutor {
|
||||
|
||||
private final GeyserConnector connector;
|
||||
public GeyserSpigotCommandExecutor(GeyserConnector connector) {
|
||||
super(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length > 0) {
|
||||
if (getCommand(args[0]) != null) {
|
||||
if (!sender.hasPermission(getCommand(args[0]).getPermission())) {
|
||||
SpigotCommandSender commandSender = new SpigotCommandSender(sender);
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());;
|
||||
GeyserCommand geyserCommand = getCommand(args[0]);
|
||||
if (geyserCommand != null) {
|
||||
SpigotCommandSender commandSender = new SpigotCommandSender(sender);
|
||||
if (!sender.hasPermission(geyserCommand.getPermission())) {
|
||||
String message = LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", commandSender.getLocale());
|
||||
|
||||
commandSender.sendMessage(ChatColor.RED + message);
|
||||
return true;
|
||||
}
|
||||
getCommand(args[0]).execute(new SpigotCommandSender(sender), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
GeyserSession session = null;
|
||||
if (geyserCommand.isBedrockOnly()) {
|
||||
session = getGeyserSession(commandSender);
|
||||
if (session == null) {
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", commandSender.getLocale()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
geyserCommand.execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new SpigotCommandSender(sender), new String[0]);
|
||||
getCommand("help").execute(null, new SpigotCommandSender(sender), new String[0]);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
@ -71,8 +82,4 @@ public class GeyserSpigotCommandExecutor implements TabExecutor {
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private GeyserCommand getCommand(String label) {
|
||||
return connector.getCommandManager().getCommands().get(label);
|
||||
}
|
||||
}
|
||||
|
@ -86,8 +86,8 @@
|
||||
<shadedPattern>org.geysermc.platform.sponge.shaded.dom4j</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.kyori.adventure</pattern>
|
||||
<shadedPattern>org.geysermc.platform.sponge.shaded.adventure</shadedPattern>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>org.geysermc.platform.sponge.shaded.kyori</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
|
@ -25,10 +25,12 @@
|
||||
|
||||
package org.geysermc.platform.sponge.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.command.CommandExecutor;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
import org.spongepowered.api.command.CommandCallable;
|
||||
import org.spongepowered.api.command.CommandException;
|
||||
@ -44,25 +46,36 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpongeCommandExecutor implements CommandCallable {
|
||||
public class GeyserSpongeCommandExecutor extends CommandExecutor implements CommandCallable {
|
||||
|
||||
private GeyserConnector connector;
|
||||
public GeyserSpongeCommandExecutor(GeyserConnector connector) {
|
||||
super(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult process(CommandSource source, String arguments) throws CommandException {
|
||||
public CommandResult process(CommandSource source, String arguments) {
|
||||
String[] args = arguments.split(" ");
|
||||
if (args.length > 0) {
|
||||
if (getCommand(args[0]) != null) {
|
||||
if (!source.hasPermission(getCommand(args[0]).getPermission())) {
|
||||
GeyserCommand command = getCommand(args[0]);
|
||||
if (command != null) {
|
||||
CommandSender commandSender = new SpongeCommandSender(source);
|
||||
if (!source.hasPermission(command.getPermission())) {
|
||||
// Not ideal to use log here but we dont get a session
|
||||
source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.permission_fail")));
|
||||
return CommandResult.success();
|
||||
}
|
||||
getCommand(args[0]).execute(new SpongeCommandSender(source), args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
GeyserSession session = null;
|
||||
if (command.isBedrockOnly()) {
|
||||
session = getGeyserSession(commandSender);
|
||||
if (session == null) {
|
||||
source.sendMessage(Text.of(ChatColor.RED + LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")));
|
||||
return CommandResult.success();
|
||||
}
|
||||
}
|
||||
getCommand(args[0]).execute(session, commandSender, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new SpongeCommandSender(source), new String[0]);
|
||||
getCommand("help").execute(null, new SpongeCommandSender(source), new String[0]);
|
||||
}
|
||||
return CommandResult.success();
|
||||
}
|
||||
@ -94,8 +107,4 @@ public class GeyserSpongeCommandExecutor implements CommandCallable {
|
||||
public Text getUsage(CommandSource source) {
|
||||
return Text.of("/geyser help");
|
||||
}
|
||||
|
||||
private GeyserCommand getCommand(String label) {
|
||||
return connector.getCommandManager().getCommands().get(label);
|
||||
}
|
||||
}
|
||||
|
@ -271,17 +271,17 @@ public class GeyserStandaloneGUI {
|
||||
JMenuItem commandButton = hasSubCommands ? new JMenu(command.getValue().getName()) : new JMenuItem(command.getValue().getName());
|
||||
commandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription());
|
||||
if (!hasSubCommands) {
|
||||
commandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ }));
|
||||
commandButton.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{ }));
|
||||
} else {
|
||||
// Add a submenu that's the same name as the menu can't be pressed
|
||||
JMenuItem otherCommandButton = new JMenuItem(command.getValue().getName());
|
||||
otherCommandButton.getAccessibleContext().setAccessibleDescription(command.getValue().getDescription());
|
||||
otherCommandButton.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{ }));
|
||||
otherCommandButton.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{ }));
|
||||
commandButton.add(otherCommandButton);
|
||||
// Add a menu option for all possible subcommands
|
||||
for (String subCommandName : command.getValue().getSubCommands()) {
|
||||
JMenuItem item = new JMenuItem(subCommandName);
|
||||
item.addActionListener(e -> command.getValue().execute(geyserStandaloneLogger, new String[]{subCommandName}));
|
||||
item.addActionListener(e -> command.getValue().execute(null, geyserStandaloneLogger, new String[]{subCommandName}));
|
||||
commandButton.add(item);
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +82,8 @@
|
||||
<shadedPattern>org.geysermc.platform.velocity.shaded.dom4j</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.kyori.adventure</pattern>
|
||||
<shadedPattern>org.geysermc.platform.velocity.shaded.adventure</shadedPattern>
|
||||
<pattern>net.kyori.adventure.text.serializer.gson.legacyimpl</pattern>
|
||||
<shadedPattern>org.geysermc.platform.velocity.shaded.kyori.legacyimpl</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
@ -105,6 +105,13 @@
|
||||
<exclude>io.netty:netty-codec:*</exclude>
|
||||
<exclude>org.slf4j:*</exclude>
|
||||
<exclude>org.ow2.asm:*</exclude>
|
||||
<!-- Exclude all Kyori dependencies except the legacy NBT serializer -->
|
||||
<exclude>net.kyori:adventure-api:*</exclude>
|
||||
<exclude>net.kyori:examination-api:*</exclude>
|
||||
<exclude>net.kyori:examination-string:*</exclude>
|
||||
<exclude>net.kyori:adventure-text-serializer-gson:*</exclude>
|
||||
<exclude>net.kyori:adventure-text-serializer-legacy:*</exclude>
|
||||
<exclude>net.kyori:adventure-nbt:*</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
|
@ -31,11 +31,10 @@ import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.geysermc.connector.common.ping.GeyserPingInfo;
|
||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -50,13 +49,13 @@ public class GeyserVelocityPingPassthrough implements IGeyserPingPassthrough {
|
||||
ProxyPingEvent event;
|
||||
try {
|
||||
event = server.getEventManager().fire(new ProxyPingEvent(new GeyserInboundConnection(inetSocketAddress), ServerPing.builder()
|
||||
.description(server.getConfiguration().getMotdComponent()).onlinePlayers(server.getPlayerCount())
|
||||
.description(server.getConfiguration().getMotd()).onlinePlayers(server.getPlayerCount())
|
||||
.maximumPlayers(server.getConfiguration().getShowMaxPlayers()).build())).get();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
|
||||
LegacyComponentSerializer.legacy().serialize(event.getPing().getDescription(), '§'),
|
||||
LegacyComponentSerializer.legacy('§').serialize(event.getPing().getDescriptionComponent()),
|
||||
new GeyserPingInfo.Players(
|
||||
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getMax(),
|
||||
event.getPing().getPlayers().orElseThrow(IllegalStateException::new).getOnline()
|
||||
|
@ -25,37 +25,47 @@
|
||||
|
||||
package org.geysermc.platform.velocity.command;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandExecutor;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserVelocityCommandExecutor implements SimpleCommand {
|
||||
public class GeyserVelocityCommandExecutor extends CommandExecutor implements SimpleCommand {
|
||||
|
||||
private final GeyserConnector connector;
|
||||
public GeyserVelocityCommandExecutor(GeyserConnector connector) {
|
||||
super(connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Invocation invocation) {
|
||||
if (invocation.arguments().length > 0) {
|
||||
if (getCommand(invocation.arguments()[0]) != null) {
|
||||
GeyserCommand command = getCommand(invocation.arguments()[0]);
|
||||
if (command != null) {
|
||||
CommandSender sender = new VelocityCommandSender(invocation.source());
|
||||
if (!invocation.source().hasPermission(getCommand(invocation.arguments()[0]).getPermission())) {
|
||||
CommandSender sender = new VelocityCommandSender(invocation.source());
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.getLocale()));
|
||||
return;
|
||||
}
|
||||
getCommand(invocation.arguments()[0]).execute(new VelocityCommandSender(invocation.source()), invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]);
|
||||
GeyserSession session = null;
|
||||
if (command.isBedrockOnly()) {
|
||||
session = getGeyserSession(sender);
|
||||
if (session == null) {
|
||||
sender.sendMessage(ChatColor.RED + LanguageUtils.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.getLocale()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
command.execute(session, sender, invocation.arguments().length > 1 ? Arrays.copyOfRange(invocation.arguments(), 1, invocation.arguments().length) : new String[0]);
|
||||
}
|
||||
} else {
|
||||
getCommand("help").execute(new VelocityCommandSender(invocation.source()), new String[0]);
|
||||
getCommand("help").execute(null, new VelocityCommandSender(invocation.source()), new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,8 +76,4 @@ public class GeyserVelocityCommandExecutor implements SimpleCommand {
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private GeyserCommand getCommand(String label) {
|
||||
return connector.getCommandManager().getCommands().get(label);
|
||||
}
|
||||
}
|
||||
|
@ -131,6 +131,10 @@
|
||||
<groupId>com.github.steveice10</groupId>
|
||||
<artifactId>packetlib</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.github.steveice10</groupId>
|
||||
<artifactId>mcauthlib</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -197,6 +201,11 @@
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.GeyserMC</groupId>
|
||||
<artifactId>MCAuthLib</artifactId>
|
||||
<version>0e48a094f2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -90,6 +90,12 @@ public class GeyserConnector {
|
||||
public static final String NAME = "Geyser";
|
||||
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
|
||||
public static final String MINECRAFT_VERSION = "1.16.4 - 1.16.5";
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
@ -108,8 +114,8 @@ public class GeyserConnector {
|
||||
private final ScheduledExecutorService generalThreadPool;
|
||||
|
||||
private BedrockServer bedrockServer;
|
||||
private PlatformType platformType;
|
||||
private GeyserBootstrap bootstrap;
|
||||
private final PlatformType platformType;
|
||||
private final GeyserBootstrap bootstrap;
|
||||
|
||||
private Metrics metrics;
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
/**
|
||||
* Represents helper functions for listening to {@code /geyser} commands.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class CommandExecutor {
|
||||
|
||||
protected final GeyserConnector connector;
|
||||
|
||||
public GeyserCommand getCommand(String label) {
|
||||
return connector.getCommandManager().getCommands().get(label);
|
||||
}
|
||||
|
||||
public GeyserSession getGeyserSession(CommandSender sender) {
|
||||
if (sender.isConsole()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (GeyserSession session : connector.getPlayers()) {
|
||||
if (sender.getName().equals(session.getPlayerEntity().getUsername())) {
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ import lombok.Getter;
|
||||
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.defaults.*;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.*;
|
||||
@ -52,6 +53,7 @@ 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( "advancements", "geyser.commands.advancements.desc", "geyser.command.advancements"));
|
||||
}
|
||||
|
||||
public void registerCommand(GeyserCommand command) {
|
||||
@ -88,7 +90,15 @@ public abstract class CommandManager {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd.execute(sender, args);
|
||||
if (sender instanceof GeyserSession) {
|
||||
cmd.execute((GeyserSession) sender, sender, args);
|
||||
} else {
|
||||
if (!cmd.isBedrockOnly()) {
|
||||
cmd.execute(null, sender, args);
|
||||
} else {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.bootstrap.command.bedrock_only"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,9 @@ package org.geysermc.connector.command;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -47,7 +49,7 @@ public abstract class GeyserCommand {
|
||||
@Setter
|
||||
private List<String> aliases = new ArrayList<>();
|
||||
|
||||
public abstract void execute(CommandSender sender, String[] args);
|
||||
public abstract void execute(@Nullable GeyserSession session, CommandSender sender, String[] args);
|
||||
|
||||
/**
|
||||
* If false, hides the command from being shown on the Geyser Standalone GUI.
|
||||
@ -75,4 +77,13 @@ public abstract class GeyserCommand {
|
||||
public boolean hasSubCommands() {
|
||||
return !getSubCommands().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to send a deny message to Java players if this command can only be used by Bedrock players.
|
||||
*
|
||||
* @return true if this command can only be used by Bedrock players.
|
||||
*/
|
||||
public boolean isBedrockOnly() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
public class AdvancementsCommand extends GeyserCommand {
|
||||
public AdvancementsCommand(String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
if (session != null) {
|
||||
session.getAdvancementsCache().buildAndShowMenuForm();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.common.serializer.AsteriskSerializer;
|
||||
import org.geysermc.connector.dump.DumpInfo;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
import org.geysermc.connector.utils.WebUtils;
|
||||
|
||||
@ -54,7 +55,7 @@ public class DumpCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
boolean showSensitive = false;
|
||||
boolean offlineDump = false;
|
||||
if (args.length >= 1) {
|
||||
|
@ -29,6 +29,7 @@ import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -48,7 +49,7 @@ public class HelpCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
int page = 1;
|
||||
int maxPage = 1;
|
||||
String header = LanguageUtils.getPlayerLocaleString("geyser.commands.help.header", sender.getLocale(), page, maxPage);
|
||||
|
@ -44,7 +44,7 @@ public class ListCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
String message = "";
|
||||
message = LanguageUtils.getPlayerLocaleString("geyser.commands.list.message", sender.getLocale(),
|
||||
connector.getPlayers().size(),
|
||||
|
@ -45,32 +45,23 @@ public class OffhandCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.isConsole()) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the sender is a Bedrock edition client
|
||||
if (sender instanceof GeyserSession) {
|
||||
GeyserSession session = (GeyserSession) sender;
|
||||
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0),
|
||||
BlockFace.DOWN);
|
||||
session.sendDownstreamPacket(releaseItemPacket);
|
||||
return;
|
||||
}
|
||||
// Needed for Spigot - sender is not an instance of GeyserSession
|
||||
for (GeyserSession session : connector.getPlayers()) {
|
||||
if (sender.getName().equals(session.getPlayerEntity().getUsername())) {
|
||||
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0),
|
||||
BlockFace.DOWN);
|
||||
session.sendDownstreamPacket(releaseItemPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.SWAP_HANDS, new Position(0,0,0),
|
||||
BlockFace.DOWN);
|
||||
session.sendDownstreamPacket(releaseItemPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
public class ReloadCommand extends GeyserCommand {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public ReloadCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
@ -42,7 +42,7 @@ public class ReloadCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) {
|
||||
return;
|
||||
}
|
||||
@ -51,8 +51,8 @@ public class ReloadCommand extends GeyserCommand {
|
||||
|
||||
sender.sendMessage(message);
|
||||
|
||||
for (GeyserSession session : connector.getPlayers()) {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale()));
|
||||
for (GeyserSession otherSession : connector.getPlayers()) {
|
||||
otherSession.disconnect(LanguageUtils.getPlayerLocaleString("geyser.commands.reload.kick", session.getLocale()));
|
||||
}
|
||||
connector.reload();
|
||||
}
|
||||
|
@ -32,30 +32,12 @@ import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.SettingsUtils;
|
||||
|
||||
public class SettingsCommand extends GeyserCommand {
|
||||
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public SettingsCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
if (session != null) {
|
||||
session.sendForm(SettingsUtils.buildForm(session));
|
||||
}
|
||||
@ -65,4 +47,9 @@ public class SettingsCommand extends GeyserCommand {
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -34,34 +34,14 @@ import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
public class StatisticsCommand extends GeyserCommand {
|
||||
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public StatisticsCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender.isConsole()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the sender is a Bedrock edition client
|
||||
GeyserSession session = null;
|
||||
if (sender instanceof GeyserSession) {
|
||||
session = (GeyserSession) sender;
|
||||
} else {
|
||||
// Needed for Spigot - sender is not an instance of GeyserSession
|
||||
for (GeyserSession otherSession : connector.getPlayers()) {
|
||||
if (sender.getName().equals(otherSession.getPlayerEntity().getUsername())) {
|
||||
session = otherSession;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
if (session == null) return;
|
||||
|
||||
session.setWaitingForStatistics(true);
|
||||
ClientRequestPacket clientRequestPacket = new ClientRequestPacket(ClientRequest.STATS);
|
||||
session.sendDownstreamPacket(clientRequestPacket);
|
||||
@ -71,4 +51,9 @@ public class StatisticsCommand extends GeyserCommand {
|
||||
public boolean isExecutableOnConsole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBedrockOnly() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -29,12 +29,13 @@ import org.geysermc.common.PlatformType;
|
||||
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 java.util.Collections;
|
||||
|
||||
public class StopCommand extends GeyserCommand {
|
||||
|
||||
private GeyserConnector connector;
|
||||
private final GeyserConnector connector;
|
||||
|
||||
public StopCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
@ -44,7 +45,7 @@ public class StopCommand extends GeyserCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
if (!sender.isConsole() && connector.getPlatformType() == PlatformType.STANDALONE) {
|
||||
return;
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.command.GeyserCommand;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.network.BedrockProtocol;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.FileUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
import org.geysermc.connector.utils.WebUtils;
|
||||
@ -44,15 +45,12 @@ import java.util.Properties;
|
||||
|
||||
public class VersionCommand extends GeyserCommand {
|
||||
|
||||
public GeyserConnector connector;
|
||||
|
||||
public VersionCommand(GeyserConnector connector, String name, String description, String permission) {
|
||||
super(name, description, permission);
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
public void execute(GeyserSession session, CommandSender sender, String[] args) {
|
||||
String bedrockVersions;
|
||||
List<BedrockPacketCodec> supportedCodecs = BedrockProtocol.SUPPORTED_BEDROCK_CODECS;
|
||||
if (supportedCodecs.size() > 1) {
|
||||
@ -61,7 +59,7 @@ public class VersionCommand extends GeyserCommand {
|
||||
bedrockVersions = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion();
|
||||
}
|
||||
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, MinecraftConstants.GAME_VERSION, bedrockVersions));
|
||||
sender.sendMessage(LanguageUtils.getPlayerLocaleString("geyser.commands.version.version", sender.getLocale(), GeyserConnector.NAME, GeyserConnector.VERSION, GeyserConnector.MINECRAFT_VERSION, bedrockVersions));
|
||||
|
||||
// Disable update checking in dev mode
|
||||
//noinspection ConstantConditions - changes in production
|
||||
|
@ -118,6 +118,8 @@ public interface GeyserConfiguration {
|
||||
|
||||
String getAuthType();
|
||||
|
||||
boolean isPasswordAuthentication();
|
||||
|
||||
boolean isUseProxyProtocol();
|
||||
}
|
||||
|
||||
@ -125,6 +127,12 @@ public interface GeyserConfiguration {
|
||||
String getEmail();
|
||||
|
||||
String getPassword();
|
||||
|
||||
/**
|
||||
* Will be removed after Microsoft accounts are fully migrated
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isMicrosoftAccount();
|
||||
}
|
||||
|
||||
interface IMetricsInfo {
|
||||
|
@ -149,17 +149,24 @@ 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
|
||||
|
@ -38,11 +38,11 @@ public class ItemedFireballEntity extends ThrowableEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updatePosition(GeyserSession session) {
|
||||
public void tick(GeyserSession session) {
|
||||
position = position.add(motion);
|
||||
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
|
||||
// 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
|
||||
// TODO: movement is incredibly stiff.
|
||||
// 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);
|
||||
|
@ -33,50 +33,35 @@ 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 {
|
||||
public class ThrowableEntity extends Entity implements Tickable {
|
||||
|
||||
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 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) {
|
||||
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);
|
||||
}
|
||||
|
||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
|
||||
*
|
||||
@ -140,7 +125,6 @@ public class ThrowableEntity extends Entity {
|
||||
|
||||
@Override
|
||||
public boolean despawnEntity(GeyserSession session) {
|
||||
positionUpdater.cancel(true);
|
||||
if (entityType == EntityType.THROWN_ENDERPEARL) {
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_TELEPORT);
|
||||
|
35
connector/src/main/java/org/geysermc/connector/entity/Tickable.java
Normale Datei
35
connector/src/main/java/org/geysermc/connector/entity/Tickable.java
Normale Datei
@ -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.entity;
|
||||
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
/**
|
||||
* Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds.
|
||||
*/
|
||||
public interface Tickable {
|
||||
void tick(GeyserSession session);
|
||||
}
|
@ -28,19 +28,28 @@ 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.AddEntityPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
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.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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;
|
||||
|
||||
public class EnderDragonEntity extends InsentientEntity {
|
||||
public class EnderDragonEntity extends InsentientEntity implements Tickable {
|
||||
/**
|
||||
* The Ender Dragon has multiple hit boxes, which
|
||||
* are each its own invisible entity
|
||||
@ -61,9 +70,19 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
private final Segment[] segmentHistory = new Segment[19];
|
||||
private int latestSegment = -1;
|
||||
|
||||
private boolean hovering;
|
||||
private int phase;
|
||||
/**
|
||||
* The number of ticks since the beginning of the phase
|
||||
*/
|
||||
private int phaseTicks;
|
||||
|
||||
private ScheduledFuture<?> partPositionUpdater;
|
||||
private int ticksTillNextGrowl = 100;
|
||||
|
||||
/**
|
||||
* Used to determine when the wing flap sound should be played
|
||||
*/
|
||||
private float wingPosition;
|
||||
private float lastWingPosition;
|
||||
|
||||
public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
@ -73,49 +92,67 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||
// Phase
|
||||
if (entityMetadata.getId() == 15) {
|
||||
int value = (int) entityMetadata.getValue();
|
||||
if (value == 5) {
|
||||
// Performing breath attack
|
||||
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
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
|
||||
entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH);
|
||||
entityEventPacket.setRuntimeEntityId(geyserId);
|
||||
entityEventPacket.setData(0);
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
}
|
||||
metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7);
|
||||
hovering = value == 10;
|
||||
attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200));
|
||||
updateBedrockAttributes(session);
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(GeyserSession 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);
|
||||
super.spawnEntity(session);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
tail = new EnderDragonPartEntity[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2);
|
||||
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 2, 2);
|
||||
}
|
||||
|
||||
allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
|
||||
@ -129,25 +166,25 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
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
|
||||
*
|
||||
@ -163,7 +200,7 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
|
||||
// Lowers the head when the dragon sits/hovers
|
||||
float headDuck;
|
||||
if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) {
|
||||
if (isHovering() || isSitting()) {
|
||||
headDuck = -1f;
|
||||
} else {
|
||||
headDuck = baseSegment.y - getSegment(0).y;
|
||||
@ -193,6 +230,105 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -32,11 +32,12 @@ 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, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, float width, float height) {
|
||||
super(entityId, geyserId, entityType, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,10 @@ public class PlayerEntity extends LivingEntity {
|
||||
|
||||
long linkedEntityId = session.getEntityCache().getCachedPlayerEntityLink(entityId);
|
||||
if (linkedEntityId != -1) {
|
||||
addPlayerPacket.getEntityLinks().add(new EntityLinkData(session.getEntityCache().getEntityByJavaId(linkedEntityId).getGeyserId(), geyserId, EntityLinkData.Type.RIDER, false));
|
||||
Entity linkedEntity = session.getEntityCache().getEntityByJavaId(linkedEntityId);
|
||||
if (linkedEntity != null) {
|
||||
addPlayerPacket.getEntityLinks().add(new EntityLinkData(linkedEntity.getGeyserId(), geyserId, EntityLinkData.Type.RIDER, false, false));
|
||||
}
|
||||
}
|
||||
|
||||
valid = true;
|
||||
|
@ -40,7 +40,9 @@ public class BedrockProtocol {
|
||||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
*/
|
||||
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC;
|
||||
public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC.toBuilder()
|
||||
.minecraftVersion("1.16.201")
|
||||
.build();
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
*/
|
||||
@ -50,7 +52,9 @@ public class BedrockProtocol {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder()
|
||||
.minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
.minecraftVersion("1.16.200/1.16.201")
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,15 +30,16 @@ 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.common.ping.GeyserPingInfo;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.common.ping.GeyserPingInfo;
|
||||
import org.geysermc.connector.configuration.GeyserConfiguration;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.connector.network.translators.chat.MessageTranslator;
|
||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||
|
||||
@ -94,6 +95,20 @@ 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()) {
|
||||
|
@ -33,6 +33,7 @@ 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.LanguageUtils;
|
||||
import org.geysermc.connector.utils.LoginEncryptionUtils;
|
||||
@ -152,6 +153,7 @@ 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
|
||||
|
@ -26,8 +26,12 @@
|
||||
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;
|
||||
@ -35,6 +39,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.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.login.server.LoginSuccessPacket;
|
||||
@ -62,6 +67,7 @@ import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
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;
|
||||
@ -91,6 +97,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter
|
||||
public class GeyserSession implements CommandSender {
|
||||
@ -104,9 +111,15 @@ 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;
|
||||
@ -242,15 +255,14 @@ public class GeyserSession implements CommandSender {
|
||||
private ScheduledFuture<?> bucketScheduledFuture;
|
||||
|
||||
/**
|
||||
* Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
|
||||
* Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
|
||||
*/
|
||||
@Setter
|
||||
private ScheduledFuture<?> movementSendIfIdle;
|
||||
private long lastMovementTimestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@ -322,12 +334,19 @@ 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();
|
||||
@ -345,7 +364,9 @@ public class GeyserSession implements CommandSender {
|
||||
|
||||
this.inventoryCache.getInventories().put(0, inventory);
|
||||
|
||||
connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes()));
|
||||
// Make a copy to prevent ConcurrentModificationException
|
||||
final List<GeyserSession> tmpPlayers = new ArrayList<>(connector.getPlayers());
|
||||
tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
|
||||
|
||||
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason));
|
||||
@ -427,131 +448,22 @@ public class GeyserSession implements CommandSender {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (password != null && !password.isEmpty()) {
|
||||
protocol = new MinecraftProtocol(username, password);
|
||||
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);
|
||||
} else {
|
||||
protocol = new MinecraftProtocol(username);
|
||||
}
|
||||
|
||||
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
|
||||
|
||||
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) {
|
||||
byte[] encryptedData;
|
||||
|
||||
try {
|
||||
FloodgateCipher cipher = connector.getCipher();
|
||||
encryptedData = cipher.encryptFromString(BedrockData.of(
|
||||
clientData.getGameVersion(),
|
||||
authData.getName(),
|
||||
authData.getXboxUUID(),
|
||||
clientData.getDeviceOs().ordinal(),
|
||||
clientData.getLanguageCode(),
|
||||
clientData.getUiProfile().ordinal(),
|
||||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getSession().getAddress().getAddress().getHostAddress()
|
||||
).toString());
|
||||
} catch (Exception e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] rawSkin = clientData.getAndTransformImage("Skin").encode();
|
||||
byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1];
|
||||
System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length);
|
||||
finalData[encryptedData.length] = 0x21; // splitter
|
||||
System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length);
|
||||
|
||||
String finalDataString = new String(finalData, StandardCharsets.UTF_8);
|
||||
|
||||
HandshakePacket handshakePacket = event.getPacket();
|
||||
event.setPacket(new HandshakePacket(
|
||||
handshakePacket.getProtocolVersion(),
|
||||
handshakePacket.getHostname() + '\0' + finalDataString,
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
downstream.getSession().connect();
|
||||
connector.addPlayer(this);
|
||||
connectDownstream();
|
||||
} catch (InvalidCredentialsException | IllegalArgumentException e) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
||||
@ -561,6 +473,194 @@ 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.buildAndShowMicrosoftCodeWindow(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;
|
||||
|
||||
// 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) {
|
||||
byte[] encryptedData;
|
||||
|
||||
try {
|
||||
FloodgateCipher cipher = connector.getCipher();
|
||||
encryptedData = cipher.encryptFromString(BedrockData.of(
|
||||
clientData.getGameVersion(),
|
||||
authData.getName(),
|
||||
authData.getXboxUUID(),
|
||||
clientData.getDeviceOs().ordinal(),
|
||||
clientData.getLanguageCode(),
|
||||
clientData.getUiProfile().ordinal(),
|
||||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getSession().getAddress().getAddress().getHostAddress()
|
||||
).toString());
|
||||
} catch (Exception e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] rawSkin = clientData.getAndTransformImage("Skin").encode();
|
||||
byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1];
|
||||
System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length);
|
||||
finalData[encryptedData.length] = 0x21; // splitter
|
||||
System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length);
|
||||
|
||||
String finalDataString = new String(finalData, StandardCharsets.UTF_8);
|
||||
|
||||
HandshakePacket handshakePacket = event.getPacket();
|
||||
event.setPacket(new HandshakePacket(
|
||||
handshakePacket.getProtocolVersion(),
|
||||
handshakePacket.getHostname() + '\0' + finalDataString,
|
||||
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.equalsIgnoreCase("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;
|
||||
@ -573,6 +673,12 @@ 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;
|
||||
@ -587,6 +693,28 @@ 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;
|
||||
}
|
||||
@ -821,6 +949,18 @@ 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
|
||||
*
|
||||
|
276
connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
vendored
Normale Datei
276
connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
vendored
Normale Datei
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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 lombok.experimental.Accessors;
|
||||
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 org.geysermc.cumulus.SimpleForm;
|
||||
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AdvancementsCache {
|
||||
/**
|
||||
* 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 @Accessors(chain = true)
|
||||
private String currentAdvancementCategoryId = null;
|
||||
|
||||
private final GeyserSession session;
|
||||
|
||||
public AdvancementsCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and send a form with all advancement categories
|
||||
*/
|
||||
public void buildAndShowMenuForm() {
|
||||
SimpleForm.Builder builder =
|
||||
SimpleForm.builder()
|
||||
.translator(LocaleUtils::getLocaleString, session.getLocale())
|
||||
.title("gui.advancements");
|
||||
|
||||
boolean hasAdvancements = false;
|
||||
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
|
||||
if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement
|
||||
hasAdvancements = true;
|
||||
builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAdvancements) {
|
||||
builder.content("advancements.empty");
|
||||
}
|
||||
|
||||
builder.responseHandler((form, responseData) -> {
|
||||
SimpleFormResponse response = form.parseResponse(responseData);
|
||||
if (!response.isCorrect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String id = "";
|
||||
|
||||
int advancementIndex = 0;
|
||||
for (Map.Entry<String, GeyserAdvancement> advancement : storedAdvancements.entrySet()) {
|
||||
if (advancement.getValue().getParentId() == null) { // Root advancement
|
||||
if (advancementIndex == response.getClickedButtonId()) {
|
||||
id = advancement.getKey();
|
||||
break;
|
||||
} else {
|
||||
advancementIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!id.equals("")) {
|
||||
if (id.equals(currentAdvancementCategoryId)) {
|
||||
// The server thinks we are already on this tab
|
||||
buildAndShowListForm();
|
||||
} 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
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
session.sendForm(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and send the list of advancements
|
||||
*/
|
||||
public void buildAndShowListForm() {
|
||||
GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId);
|
||||
String language = session.getLocale();
|
||||
|
||||
SimpleForm.Builder builder =
|
||||
SimpleForm.builder()
|
||||
.title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language))
|
||||
.content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
|
||||
|
||||
if (currentAdvancementCategoryId != null) {
|
||||
for (GeyserAdvancement advancement : storedAdvancements.values()) {
|
||||
if (advancement != null) {
|
||||
if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) {
|
||||
boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast();
|
||||
builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language));
|
||||
|
||||
builder.responseHandler((form, responseData) -> {
|
||||
SimpleFormResponse response = form.parseResponse(responseData);
|
||||
if (!response.isCorrect()) {
|
||||
// Indicate that we have closed the current advancement tab
|
||||
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
|
||||
return;
|
||||
}
|
||||
|
||||
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 == response.getClickedButtonId()) {
|
||||
advancement = advancementEntry;
|
||||
break;
|
||||
} else {
|
||||
advancementIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (advancement != null) {
|
||||
buildAndShowInfoForm(advancement);
|
||||
} else {
|
||||
buildAndShowMenuForm();
|
||||
// Indicate that we have closed the current advancement tab
|
||||
session.sendDownstreamPacket(new ClientAdvancementTabPacket());
|
||||
}
|
||||
});
|
||||
|
||||
session.sendForm(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the advancement display info based on the chosen category
|
||||
*
|
||||
* @param advancement The advancement used to create the info display
|
||||
*/
|
||||
public void buildAndShowInfoForm(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));
|
||||
}
|
||||
|
||||
session.sendForm(
|
||||
SimpleForm.builder()
|
||||
.title(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()))
|
||||
.content(content)
|
||||
.button(LanguageUtils.getPlayerLocaleString("gui.back", language))
|
||||
.responseHandler((form, responseData) -> {
|
||||
SimpleFormResponse response = form.parseResponse(responseData);
|
||||
if (response.isCorrect()) {
|
||||
buildAndShowListForm();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
75
connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
vendored
Normale Datei
75
connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
vendored
Normale Datei
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ 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;
|
||||
@ -40,17 +41,21 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* for that player (e.g. seeing vanished players from /vanish)
|
||||
*/
|
||||
public class EntityCache {
|
||||
private GeyserSession session;
|
||||
private final 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 Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
|
||||
private final Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
|
||||
|
||||
@Getter
|
||||
private AtomicLong nextEntityId = new AtomicLong(2L);
|
||||
private final AtomicLong nextEntityId = new AtomicLong(2L);
|
||||
|
||||
public EntityCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
@ -59,6 +64,11 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,6 +86,10 @@ 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;
|
||||
@ -114,8 +128,8 @@ public class EntityCache {
|
||||
return playerEntities.get(uuid);
|
||||
}
|
||||
|
||||
public void removePlayerEntity(UUID uuid) {
|
||||
playerEntities.remove(uuid);
|
||||
public PlayerEntity removePlayerEntity(UUID uuid) {
|
||||
return playerEntities.remove(uuid);
|
||||
}
|
||||
|
||||
public void addBossBar(UUID uuid, BossBar bossBar) {
|
||||
@ -152,4 +166,8 @@ public class EntityCache {
|
||||
public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) {
|
||||
cachedPlayerEntityLinks.put(playerId, linkedEntityId);
|
||||
}
|
||||
|
||||
public List<Tickable> getTickableEntities() {
|
||||
return tickableEntities;
|
||||
}
|
||||
}
|
||||
|
0
connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java
vendored
Normale Datei
0
connector/src/main/java/org/geysermc/connector/network/session/cache/WindowCache.java
vendored
Normale Datei
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
|
||||
public void translate(CommandRequestPacket packet, GeyserSession session) {
|
||||
String command = packet.getCommand().replace("/", "");
|
||||
CommandManager commandManager = GeyserConnector.getInstance().getCommandManager();
|
||||
if (session.getConnector().getPlatformType() == PlatformType.STANDALONE && command.startsWith("geyser ") && commandManager.getCommands().containsKey(command.split(" ")[1])) {
|
||||
if (session.getConnector().getPlatformType() == PlatformType.STANDALONE && command.trim().startsWith("geyser ") && commandManager.getCommands().containsKey(command.split(" ")[1])) {
|
||||
commandManager.runCommand(session, command);
|
||||
} else {
|
||||
String message = packet.getCommand().trim();
|
||||
|
@ -39,12 +39,11 @@ 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.ContainerOpenPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import org.geysermc.connector.entity.CommandBlockMinecartEntity;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||
@ -64,11 +63,23 @@ 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();
|
||||
@ -109,6 +120,46 @@ 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()],
|
||||
@ -156,7 +207,6 @@ 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);
|
||||
@ -181,19 +231,32 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
session.sendDownstreamPacket(useItemPacket);
|
||||
break;
|
||||
case 2:
|
||||
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);
|
||||
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
||||
session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
|
||||
|
||||
LevelEventPacket blockBreakPacket = new LevelEventPacket();
|
||||
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
|
||||
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
||||
blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
|
||||
session.sendUpstreamPacket(blockBreakPacket);
|
||||
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);
|
||||
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());
|
||||
@ -267,4 +330,34 @@ 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);
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,9 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
|
||||
return;
|
||||
}
|
||||
|
||||
// Send book update before switching hotbar slot
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
session.getInventory().setHeldItemSlot(packet.getHotbarSlot());
|
||||
|
||||
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
|
||||
|
@ -25,13 +25,16 @@
|
||||
|
||||
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;
|
||||
@ -40,9 +43,12 @@ 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.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.utils.BlockUtils;
|
||||
|
||||
@ -57,6 +63,11 @@ 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());
|
||||
|
||||
@ -125,6 +136,27 @@ 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);
|
||||
@ -133,24 +165,33 @@ 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);
|
||||
break;
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, position, 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()));
|
||||
continueBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
||||
continueBreakPacket.setData((BlockTranslator.getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24));
|
||||
continueBreakPacket.setPosition(vector.toFloat());
|
||||
session.sendUpstreamPacket(continueBreakPacket);
|
||||
break;
|
||||
case ABORT_BREAK:
|
||||
ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.DOWN);
|
||||
ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, position, 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
|
||||
|
@ -33,16 +33,12 @@ 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> {
|
||||
@ -63,9 +59,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.getMovementSendIfIdle() != null) {
|
||||
session.getMovementSendIfIdle().cancel(true);
|
||||
}
|
||||
session.setLastMovementTimestamp(System.currentTimeMillis());
|
||||
|
||||
// Send book update before the player moves
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
if (session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0))) {
|
||||
// head yaw, pitch, head yaw
|
||||
@ -86,7 +83,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
|
||||
session.sendDownstreamPacket(playerRotationPacket);
|
||||
} else {
|
||||
Vector3d position = adjustBedrockPosition(session, packet.getPosition(), packet.isOnGround());
|
||||
Vector3d position = session.getCollisionManager().adjustBedrockPosition(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;
|
||||
@ -128,7 +125,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
} else {
|
||||
// Not a valid move
|
||||
session.getConnector().getLogger().debug("Recalculating position...");
|
||||
recalculatePosition(session);
|
||||
session.getCollisionManager().recalculatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,13 +138,9 @@ 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));
|
||||
}
|
||||
|
||||
public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
|
||||
private boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
|
||||
if (mode != MovePlayerPacket.Mode.NORMAL)
|
||||
return true;
|
||||
|
||||
@ -171,81 +164,5 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,12 @@ 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;
|
||||
|
||||
@ -105,6 +109,7 @@ 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 - 1⁄4) 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 {
|
||||
@ -113,6 +118,65 @@ 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<>();
|
||||
|
||||
|
@ -95,6 +95,10 @@ public class ItemRegistry {
|
||||
* Wheat item entry, used in AbstractHorseEntity.java
|
||||
*/
|
||||
public static ItemEntry WHEAT;
|
||||
/**
|
||||
* Writable book item entry, used in BedrockBookEditTranslator.java
|
||||
*/
|
||||
public static ItemEntry WRITABLE_BOOK;
|
||||
|
||||
public static int BARRIER_INDEX = 0;
|
||||
|
||||
@ -190,6 +194,9 @@ public class ItemRegistry {
|
||||
case "minecraft:wheat":
|
||||
WHEAT = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
case "minecraft:writable_book":
|
||||
WRITABLE_BOOK = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -78,9 +78,8 @@ public class BookPagesTranslator extends NbtItemStackTranslator {
|
||||
CompoundTag pageTag = (CompoundTag) tag;
|
||||
|
||||
StringTag textTag = pageTag.get("text");
|
||||
pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue())));
|
||||
pages.add(new StringTag("", textTag.getValue()));
|
||||
}
|
||||
|
||||
itemTag.remove("pages");
|
||||
itemTag.put(new ListTag("pages", pages));
|
||||
}
|
||||
|
@ -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.java;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementTabPacket;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
|
||||
/**
|
||||
* Indicates that the client should open a particular advancement tab
|
||||
*/
|
||||
@Translator(packet = ServerAdvancementTabPacket.class)
|
||||
public class JavaAdvancementsTabTranslator extends PacketTranslator<ServerAdvancementTabPacket> {
|
||||
@Override
|
||||
public void translate(ServerAdvancementTabPacket packet, GeyserSession session) {
|
||||
session.getAdvancementsCache()
|
||||
.setCurrentAdvancementCategoryId(packet.getTabId())
|
||||
.buildAndShowListForm();
|
||||
}
|
||||
}
|
@ -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.java;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerAdvancementsPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetTitlePacket;
|
||||
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.chat.MessageTranslator;
|
||||
import org.geysermc.connector.network.session.cache.AdvancementsCache;
|
||||
import org.geysermc.connector.utils.GeyserAdvancement;
|
||||
import org.geysermc.connector.utils.LocaleUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Translator(packet = ServerAdvancementsPacket.class)
|
||||
public class JavaAdvancementsTranslator extends PacketTranslator<ServerAdvancementsPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerAdvancementsPacket packet, GeyserSession session) {
|
||||
AdvancementsCache advancementsCache = session.getAdvancementsCache();
|
||||
if (packet.isReset()) {
|
||||
advancementsCache.getStoredAdvancements().clear();
|
||||
advancementsCache.getStoredAdvancementProgress().clear();
|
||||
}
|
||||
|
||||
// Removes removed advancements from player's stored advancements
|
||||
for (String removedAdvancement : packet.getRemovedAdvancements()) {
|
||||
advancementsCache.getStoredAdvancements().remove(removedAdvancement);
|
||||
}
|
||||
|
||||
advancementsCache.getStoredAdvancementProgress().putAll(packet.getProgress());
|
||||
|
||||
sendToolbarAdvancementUpdates(session, packet);
|
||||
|
||||
// Adds advancements to the player's stored advancements when advancements are sent
|
||||
for (Advancement advancement : packet.getAdvancements()) {
|
||||
if (advancement.getDisplayData() != null && !advancement.getDisplayData().isHidden()) {
|
||||
GeyserAdvancement geyserAdvancement = GeyserAdvancement.from(advancement);
|
||||
advancementsCache.getStoredAdvancements().put(advancement.getId(), geyserAdvancement);
|
||||
} else {
|
||||
advancementsCache.getStoredAdvancements().remove(advancement.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all advancements progress updates
|
||||
*/
|
||||
public void sendToolbarAdvancementUpdates(GeyserSession session, ServerAdvancementsPacket packet) {
|
||||
if (packet.isReset()) {
|
||||
// Advancements are being cleared, so they can't be granted
|
||||
return;
|
||||
}
|
||||
for (Map.Entry<String, Map<String, Long>> progress : packet.getProgress().entrySet()) {
|
||||
GeyserAdvancement advancement = session.getAdvancementsCache().getStoredAdvancements().get(progress.getKey());
|
||||
if (advancement != null && advancement.getDisplayData() != null) {
|
||||
if (session.getAdvancementsCache().isEarned(advancement)) {
|
||||
// Java uses some pink color for toast challenge completes
|
||||
String color = advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE ?
|
||||
"§d" : "§a";
|
||||
String advancementName = MessageTranslator.convertMessage(advancement.getDisplayData().getTitle(), session.getLocale());
|
||||
|
||||
// Send an action bar message stating they earned an achievement
|
||||
// Sent for instances where broadcasting advancements through chat are disabled
|
||||
SetTitlePacket titlePacket = new SetTitlePacket();
|
||||
titlePacket.setText(color + "[" + LocaleUtils.getLocaleString("advancements.toast." +
|
||||
advancement.getDisplayData().getFrameType().toString().toLowerCase(), session.getLocale()) + "]§f " + advancementName);
|
||||
titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
|
||||
titlePacket.setFadeOutTime(3);
|
||||
titlePacket.setFadeInTime(3);
|
||||
titlePacket.setStayTime(3);
|
||||
session.sendUpstreamPacket(titlePacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,8 +31,8 @@ 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.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
@ -183,6 +183,19 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case LIVING_EQUIPMENT_BREAK_HEAD:
|
||||
case LIVING_EQUIPMENT_BREAK_CHEST:
|
||||
case LIVING_EQUIPMENT_BREAK_LEGS:
|
||||
case LIVING_EQUIPMENT_BREAK_FEET:
|
||||
case LIVING_EQUIPMENT_BREAK_MAIN_HAND:
|
||||
case LIVING_EQUIPMENT_BREAK_OFF_HAND:
|
||||
LevelSoundEvent2Packet equipmentBreakPacket = new LevelSoundEvent2Packet();
|
||||
equipmentBreakPacket.setSound(SoundEvent.BREAK);
|
||||
equipmentBreakPacket.setPosition(entity.getPosition());
|
||||
equipmentBreakPacket.setExtraData(-1);
|
||||
equipmentBreakPacket.setIdentifier("");
|
||||
session.sendUpstreamPacket(equipmentBreakPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
|
@ -27,19 +27,20 @@ package org.geysermc.connector.network.translators.java.entity.player;
|
||||
|
||||
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.entity.player.PlayerAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
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.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.item.ItemEntry;
|
||||
import org.geysermc.connector.utils.BlockUtils;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.utils.ChunkUtils;
|
||||
|
||||
@Translator(packet = ServerPlayerActionAckPacket.class)
|
||||
@ -47,54 +48,54 @@ public class JavaPlayerActionAckTranslator extends PacketTranslator<ServerPlayer
|
||||
|
||||
@Override
|
||||
public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) {
|
||||
LevelEventPacket levelEvent = new LevelEventPacket();
|
||||
switch (packet.getAction()) {
|
||||
case FINISH_DIGGING:
|
||||
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(session.getBreakingBlock());
|
||||
if (session.getGameMode() != GameMode.CREATIVE && blockHardness != 0) {
|
||||
levelEvent.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
|
||||
levelEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
|
||||
levelEvent.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock()));
|
||||
ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition());
|
||||
if (packet.getAction() == PlayerAction.START_DIGGING && !packet.isSuccessful()) {
|
||||
LevelEventPacket stopBreak = new LevelEventPacket();
|
||||
stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
stopBreak.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
|
||||
stopBreak.setData(0);
|
||||
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
|
||||
session.sendUpstreamPacket(stopBreak);
|
||||
}
|
||||
if (!session.getConnector().getConfig().isCacheChunks()) {
|
||||
LevelEventPacket levelEvent = new LevelEventPacket();
|
||||
switch (packet.getAction()) {
|
||||
case START_DIGGING:
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState());
|
||||
levelEvent.setType(LevelEventType.BLOCK_START_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
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, packet.getNewState(), itemEntry, nbtData, session) * 20);
|
||||
levelEvent.setData((int) (65535 / breakTime));
|
||||
session.setBreakingBlock(packet.getNewState());
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
session.setBreakingBlock(0);
|
||||
}
|
||||
ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition());
|
||||
break;
|
||||
case START_DIGGING:
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState());
|
||||
levelEvent.setType(LevelEventType.BLOCK_START_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
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, packet.getNewState(), itemEntry, nbtData, session) * 20);
|
||||
levelEvent.setData((int) (65535 / breakTime));
|
||||
session.setBreakingBlock(packet.getNewState());
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
break;
|
||||
case CANCEL_DIGGING:
|
||||
levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
levelEvent.setData(0);
|
||||
session.setBreakingBlock(0);
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
break;
|
||||
case CANCEL_DIGGING:
|
||||
levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
levelEvent.setData(0);
|
||||
session.setBreakingBlock(0);
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,11 @@
|
||||
|
||||
package org.geysermc.connector.network.translators.java.entity.player;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.PlayerListEntry;
|
||||
import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
@ -32,12 +37,6 @@ import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.skin.SkinManager;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.PlayerListEntry;
|
||||
import com.github.steveice10.mc.protocol.data.game.PlayerListEntryAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPlayerListEntryPacket;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
||||
|
||||
@Translator(packet = ServerPlayerListEntryPacket.class)
|
||||
public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayerListEntryPacket> {
|
||||
@Override
|
||||
@ -57,9 +56,6 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
|
||||
if (self) {
|
||||
// Entity is ourself
|
||||
playerEntity = session.getPlayerEntity();
|
||||
//TODO: playerEntity.setProfile(entry.getProfile()); seems to help with online mode skins but needs more testing to ensure Floodgate skins aren't overwritten
|
||||
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
|
||||
GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data"));
|
||||
} else {
|
||||
playerEntity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
|
||||
}
|
||||
@ -74,27 +70,35 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
|
||||
Vector3f.ZERO,
|
||||
Vector3f.ZERO
|
||||
);
|
||||
|
||||
session.getEntityCache().addPlayerEntity(playerEntity);
|
||||
} else {
|
||||
playerEntity.setProfile(entry.getProfile());
|
||||
}
|
||||
|
||||
session.getEntityCache().addPlayerEntity(playerEntity);
|
||||
|
||||
playerEntity.setProfile(entry.getProfile());
|
||||
playerEntity.setPlayerList(true);
|
||||
playerEntity.setValid(true);
|
||||
|
||||
PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity);
|
||||
// We'll send our own PlayerListEntry in requestAndHandleSkinAndCape
|
||||
// But we need to send other player's entries so they show up in the player list
|
||||
// without processing their skin information - that'll be processed when they spawn in
|
||||
if (self) {
|
||||
SkinManager.requestAndHandleSkinAndCape(playerEntity, session, skinAndCape ->
|
||||
GeyserConnector.getInstance().getLogger().debug("Loaded Local Bedrock Java Skin Data for " + session.getClientData().getUsername()));
|
||||
} else {
|
||||
playerEntity.setValid(true);
|
||||
PlayerListPacket.Entry playerListEntry = SkinManager.buildCachedEntry(session, playerEntity);
|
||||
|
||||
translate.getEntries().add(playerListEntry);
|
||||
translate.getEntries().add(playerListEntry);
|
||||
}
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
PlayerEntity entity = session.getEntityCache().getPlayerEntity(entry.getProfile().getId());
|
||||
// As the player entity is no longer present, we can remove the entry
|
||||
PlayerEntity entity = session.getEntityCache().removePlayerEntity(entry.getProfile().getId());
|
||||
if (entity != null) {
|
||||
// Just remove the entity's player list status
|
||||
// Don't despawn the entity - the Java server will also take care of that.
|
||||
entity.setPlayerList(false);
|
||||
}
|
||||
// As the player entity is no longer present, we can remove the entry
|
||||
session.getEntityCache().removePlayerEntity(entry.getProfile().getId());
|
||||
if (entity == session.getPlayerEntity()) {
|
||||
// If removing ourself we use our AuthData UUID
|
||||
translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().getUUID()));
|
||||
@ -105,7 +109,7 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized()) {
|
||||
if (!translate.getEntries().isEmpty() && (packet.getAction() == PlayerListEntryAction.REMOVE_PLAYER || session.getUpstream().isInitialized())) {
|
||||
session.sendUpstreamPacket(translate);
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
|
||||
|
||||
ChunkUtils.updateChunkPosition(session, pos.toInt());
|
||||
|
||||
session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
|
||||
session.getConnector().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
package org.geysermc.connector.network.translators.java.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
@ -37,17 +38,17 @@ import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHan
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.utils.ChunkUtils;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket;
|
||||
|
||||
@Translator(packet = ServerBlockChangePacket.class)
|
||||
public class JavaBlockChangeTranslator extends PacketTranslator<ServerBlockChangePacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerBlockChangePacket packet, GeyserSession session) {
|
||||
Position pos = packet.getRecord().getPosition();
|
||||
boolean updatePlacement = !(session.getConnector().getConfig().isCacheChunks() && session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()) == packet.getRecord().getBlock());
|
||||
ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), packet.getRecord().getPosition());
|
||||
if (updatePlacement && session.getConnector().getPlatformType() != PlatformType.SPIGOT) {
|
||||
boolean updatePlacement = session.getConnector().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event
|
||||
!(session.getConnector().getConfig().isCacheChunks() &&
|
||||
session.getConnector().getWorldManager().getBlockAt(session, pos) == packet.getRecord().getBlock());
|
||||
ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), pos);
|
||||
if (updatePlacement) {
|
||||
this.checkPlace(session, packet);
|
||||
}
|
||||
this.checkInteract(session, packet);
|
||||
|
@ -46,17 +46,10 @@ public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimeP
|
||||
session.sendUpstreamPacket(setTimePacket);
|
||||
if (!session.isDaylightCycle() && time >= 0) {
|
||||
// Client thinks there is no daylight cycle but there is
|
||||
setDoDaylightCycleGamerule(session, true);
|
||||
session.setDaylightCycle(true);
|
||||
} else if (session.isDaylightCycle() && time < 0) {
|
||||
// Client thinks there is daylight cycle but there isn't
|
||||
setDoDaylightCycleGamerule(session, false);
|
||||
session.setDaylightCycle(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
|
||||
session.sendGameRule("dodaylightcycle", doCycle);
|
||||
// Save the value so we don't have to constantly send a daylight cycle gamerule update
|
||||
session.setDaylightCycle(doCycle);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -87,7 +87,9 @@ public class BlockTranslator {
|
||||
*/
|
||||
public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID;
|
||||
|
||||
// For block breaking animation math
|
||||
/**
|
||||
* A list of all Java runtime wool IDs, for use with block breaking math and shears
|
||||
*/
|
||||
public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet();
|
||||
public static final int JAVA_RUNTIME_COBWEB_ID;
|
||||
|
||||
|
@ -33,7 +33,6 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.common.AuthType;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||
@ -47,6 +46,9 @@ import java.util.function.Consumer;
|
||||
|
||||
public class SkinManager {
|
||||
|
||||
/**
|
||||
* Builds a Bedrock player list entry from our existing, cached Bedrock skin information
|
||||
*/
|
||||
public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) {
|
||||
GameProfileData data = GameProfileData.from(playerEntity.getProfile());
|
||||
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl());
|
||||
@ -70,27 +72,31 @@ public class SkinManager {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* With all the information needed, build a Bedrock player entry with translated skin information.
|
||||
*/
|
||||
public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId,
|
||||
String skinId, byte[] skinData,
|
||||
String capeId, byte[] capeData,
|
||||
SkinProvider.SkinGeometry geometry) {
|
||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
||||
skinId, geometry.getGeometryName(), ImageData.of(skinData), Collections.emptyList(),
|
||||
ImageData.of(capeData), geometry.getGeometryData(), "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
|
||||
ImageData.of(capeData), geometry.getGeometryData(), "", true, false,
|
||||
!capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
|
||||
);
|
||||
|
||||
// This attempts to find the xuid of the player so profile images show up for xbox accounts
|
||||
// This attempts to find the XUID of the player so profile images show up for Xbox accounts
|
||||
String xuid = "";
|
||||
GeyserSession player = GeyserConnector.getInstance().getPlayerByUuid(uuid);
|
||||
GeyserSession playerSession = GeyserConnector.getInstance().getPlayerByUuid(uuid);
|
||||
|
||||
if (player != null) {
|
||||
xuid = player.getAuthData().getXboxUUID();
|
||||
if (playerSession != null) {
|
||||
xuid = playerSession.getAuthData().getXboxUUID();
|
||||
}
|
||||
|
||||
PlayerListPacket.Entry entry;
|
||||
|
||||
// If we are building a PlayerListEntry for our own session we use our AuthData UUID instead of the Java UUID
|
||||
// as bedrock expects to get back its own provided uuid
|
||||
// as Bedrock expects to get back its own provided UUID
|
||||
if (session.getPlayerEntity().getUuid().equals(uuid)) {
|
||||
entry = new PlayerListPacket.Entry(session.getAuthData().getUUID());
|
||||
} else {
|
||||
@ -134,12 +140,13 @@ public class SkinManager {
|
||||
geometry, entity.getUuid()
|
||||
), geometry, 3);
|
||||
|
||||
boolean isDeadmau5 = "deadmau5".equals(entity.getUsername());
|
||||
// Not a bedrock player check for ears
|
||||
if (geometry.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_EARS) {
|
||||
if (geometry.isFailed() && (SkinProvider.ALLOW_THIRD_PARTY_EARS || isDeadmau5)) {
|
||||
boolean isEars;
|
||||
|
||||
// Its deadmau5, gotta support his skin :)
|
||||
if (entity.getUuid().toString().equals("1e18d5ff-643d-45c8-b509-43b8461d8614")) {
|
||||
if (isDeadmau5) {
|
||||
isEars = true;
|
||||
} else {
|
||||
// Get the ears texture for the player
|
||||
@ -185,7 +192,6 @@ public class SkinManager {
|
||||
playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
|
||||
playerRemovePacket.getEntries().add(updatedEntry);
|
||||
session.sendUpstreamPacket(playerRemovePacket);
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -238,20 +244,20 @@ public class SkinManager {
|
||||
* @return The built GameProfileData
|
||||
*/
|
||||
public static GameProfileData from(GameProfile profile) {
|
||||
// Fallback to the offline mode of working it out
|
||||
boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1);
|
||||
|
||||
try {
|
||||
GameProfile.Property skinProperty = profile.getProperty("textures");
|
||||
|
||||
// TODO: Remove try/catch here
|
||||
if (skinProperty == null) {
|
||||
// Likely offline mode
|
||||
return loadBedrockOrOfflineSkin(profile);
|
||||
}
|
||||
JsonNode skinObject = GeyserConnector.JSON_MAPPER.readTree(new String(Base64.getDecoder().decode(skinProperty.getValue()), StandardCharsets.UTF_8));
|
||||
JsonNode textures = skinObject.get("textures");
|
||||
|
||||
JsonNode skinTexture = textures.get("SKIN");
|
||||
String skinUrl = skinTexture.get("url").asText().replace("http://", "https://");
|
||||
|
||||
isAlex = skinTexture.has("metadata");
|
||||
boolean isAlex = skinTexture.has("metadata");
|
||||
|
||||
String capeUrl = null;
|
||||
if (textures.has("CAPE")) {
|
||||
@ -261,20 +267,30 @@ public class SkinManager {
|
||||
|
||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||
} catch (Exception exception) {
|
||||
if (GeyserConnector.getInstance().getAuthType() != AuthType.OFFLINE) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage());
|
||||
}
|
||||
// return default skin with default cape when texture data is invalid
|
||||
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
||||
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
|
||||
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
|
||||
|
||||
if (session != null) {
|
||||
skinUrl = session.getClientData().getSkinId();
|
||||
}
|
||||
}
|
||||
return new GameProfileData(skinUrl, SkinProvider.EMPTY_CAPE.getTextureUrl(), isAlex);
|
||||
GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage());
|
||||
return loadBedrockOrOfflineSkin(profile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return default skin with default cape when texture data is invalid, or the Bedrock player's skin if this
|
||||
* is a Bedrock player.
|
||||
*/
|
||||
private static GameProfileData loadBedrockOrOfflineSkin(GameProfile profile) {
|
||||
// Fallback to the offline mode of working it out
|
||||
boolean isAlex = (Math.abs(profile.getId().hashCode() % 2) == 1);
|
||||
|
||||
String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl();
|
||||
String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl();
|
||||
if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) {
|
||||
GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId());
|
||||
|
||||
if (session != null) {
|
||||
skinUrl = session.getClientData().getSkinId();
|
||||
capeUrl = session.getClientData().getCapeId();
|
||||
}
|
||||
}
|
||||
return new GameProfileData(skinUrl, capeUrl, isAlex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ public class BlockUtils {
|
||||
if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0;
|
||||
if (toolType.equals("")) return 1.0;
|
||||
switch (toolTier) {
|
||||
// https://minecraft.gamepedia.com/Breaking#Speed
|
||||
case "wooden":
|
||||
return 2.0;
|
||||
case "stone":
|
||||
@ -58,6 +59,8 @@ public class BlockUtils {
|
||||
return 6.0;
|
||||
case "diamond":
|
||||
return 8.0;
|
||||
case "netherite":
|
||||
return 9.0;
|
||||
case "golden":
|
||||
return 12.0;
|
||||
default:
|
||||
|
@ -58,10 +58,6 @@ public class DimensionUtils {
|
||||
int bedrockDimension = javaToBedrock(javaDimension);
|
||||
Entity player = session.getPlayerEntity();
|
||||
|
||||
if (session.getMovementSendIfIdle() != null) {
|
||||
session.getMovementSendIfIdle().cancel(true);
|
||||
}
|
||||
|
||||
session.getEntityCache().removeAllEntities();
|
||||
session.getItemFrameCache().clear();
|
||||
session.getSkullCache().clear();
|
||||
|
@ -217,8 +217,8 @@ public class FileUtils {
|
||||
* @return The byte array of the file
|
||||
*/
|
||||
public static byte[] readAllBytes(File file) {
|
||||
try {
|
||||
return readAllBytes(new FileInputStream(file));
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
return readAllBytes(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read " + file);
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.advancement.Advancement;
|
||||
import lombok.NonNull;
|
||||
import org.geysermc.connector.network.session.cache.AdvancementsCache;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A wrapper around MCProtocolLib's {@link Advancement} class so we can control the parent of an advancement
|
||||
*/
|
||||
public class GeyserAdvancement {
|
||||
private final Advancement advancement;
|
||||
private String rootId = null;
|
||||
|
||||
public static GeyserAdvancement from(Advancement advancement) {
|
||||
return new GeyserAdvancement(advancement);
|
||||
}
|
||||
|
||||
private GeyserAdvancement(Advancement advancement) {
|
||||
this.advancement = advancement;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getId() {
|
||||
return this.advancement.getId();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<String> getCriteria() {
|
||||
return this.advancement.getCriteria();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<List<String>> getRequirements() {
|
||||
return this.advancement.getRequirements();
|
||||
}
|
||||
|
||||
public String getParentId() {
|
||||
return this.advancement.getParentId();
|
||||
}
|
||||
|
||||
public Advancement.DisplayData getDisplayData() {
|
||||
return this.advancement.getDisplayData();
|
||||
}
|
||||
|
||||
public String getRootId(AdvancementsCache advancementsCache) {
|
||||
if (rootId == null) {
|
||||
if (this.advancement.getParentId() == null) {
|
||||
// We are the root ID
|
||||
this.rootId = this.advancement.getId();
|
||||
} else {
|
||||
// Go through our cache, and descend until we find the root ID
|
||||
GeyserAdvancement advancement = advancementsCache.getStoredAdvancements().get(this.advancement.getParentId());
|
||||
if (advancement.getParentId() == null) {
|
||||
this.rootId = advancement.getId();
|
||||
} else {
|
||||
this.rootId = advancement.getRootId(advancementsCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rootId;
|
||||
}
|
||||
}
|
@ -192,7 +192,11 @@ public class LanguageUtils {
|
||||
if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) {
|
||||
result = false;
|
||||
if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { // Could be too early for these to be initialized
|
||||
GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language
|
||||
if (locale.equals("en_US")) {
|
||||
GeyserConnector.getInstance().getLogger().error("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)");
|
||||
} else {
|
||||
GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!LOCALE_MAPPINGS.containsKey(locale)) {
|
||||
|
@ -142,8 +142,9 @@ public class LocaleUtils {
|
||||
try {
|
||||
File hashFile = GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toFile();
|
||||
if (hashFile.exists()) {
|
||||
BufferedReader br = new BufferedReader(new FileReader(hashFile));
|
||||
curHash = br.readLine().trim();
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(hashFile))) {
|
||||
curHash = br.readLine().trim();
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) { }
|
||||
targetHash = clientJarInfo.getSha1();
|
||||
@ -208,6 +209,12 @@ public class LocaleUtils {
|
||||
|
||||
// Insert the locale into the mappings
|
||||
LOCALE_MAPPINGS.put(locale.toLowerCase(), langMap);
|
||||
|
||||
try {
|
||||
localeStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.file", locale, e.getMessage()));
|
||||
}
|
||||
} else {
|
||||
GeyserConnector.getInstance().getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.locale.fail.missing", locale));
|
||||
}
|
||||
|
@ -29,18 +29,22 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.nukkitx.network.util.Preconditions;
|
||||
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.configuration.GeyserConfiguration;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.session.auth.AuthData;
|
||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||
import org.geysermc.cumulus.CustomForm;
|
||||
import org.geysermc.cumulus.ModalForm;
|
||||
import org.geysermc.cumulus.SimpleForm;
|
||||
import org.geysermc.cumulus.response.CustomFormResponse;
|
||||
import org.geysermc.cumulus.response.ModalFormResponse;
|
||||
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
@ -154,12 +158,19 @@ public class LoginEncryptionUtils {
|
||||
}
|
||||
|
||||
public static void buildAndShowLoginWindow(GeyserSession session) {
|
||||
// Set DoDaylightCycle to false so the time doesn't accelerate while we're here
|
||||
session.setDaylightCycle(false);
|
||||
|
||||
GeyserConfiguration config = session.getConnector().getConfig();
|
||||
boolean isPasswordAuthEnabled = config.getRemote().isPasswordAuthentication();
|
||||
|
||||
session.sendForm(
|
||||
SimpleForm.builder()
|
||||
.translator(LanguageUtils::getPlayerLocaleString, session.getLocale())
|
||||
.title("geyser.auth.login.form.notice.title")
|
||||
.content("geyser.auth.login.form.notice.desc")
|
||||
.button("geyser.auth.login.form.notice.btn_login") // id = 0
|
||||
.optionalButton("geyser.auth.login.form.notice.btn_login.mojang", isPasswordAuthEnabled)
|
||||
.button("geyser.auth.login.form.notice.btn_login.microsoft")
|
||||
.button("geyser.auth.login.form.notice.btn_disconnect")
|
||||
.responseHandler((form, responseData) -> {
|
||||
SimpleFormResponse response = form.parseResponse(responseData);
|
||||
@ -168,13 +179,26 @@ public class LoginEncryptionUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getClickedButtonId() == 0) {
|
||||
if (isPasswordAuthEnabled && response.getClickedButtonId() == 0) {
|
||||
session.setMicrosoftAccount(false);
|
||||
buildAndShowLoginDetailsWindow(session);
|
||||
return;
|
||||
}
|
||||
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getClientData().getLanguageCode()));
|
||||
}));
|
||||
if (isPasswordAuthEnabled && response.getClickedButtonId() == 1) {
|
||||
session.setMicrosoftAccount(true);
|
||||
buildAndShowMicrosoftAuthenticationWindow(session);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getClickedButtonId() == 0) {
|
||||
// Just show the OAuth code
|
||||
session.authenticateWithMicrosoftCode();
|
||||
return;
|
||||
}
|
||||
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||
}));
|
||||
}
|
||||
|
||||
public static void buildAndShowLoginDetailsWindow(GeyserSession session) {
|
||||
@ -195,4 +219,56 @@ public class LoginEncryptionUtils {
|
||||
session.authenticate(response.next(), response.next());
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Promts the user between either OAuth code login or manual password authentication
|
||||
*/
|
||||
public static void buildAndShowMicrosoftAuthenticationWindow(GeyserSession session) {
|
||||
session.sendForm(
|
||||
SimpleForm.builder()
|
||||
.translator(LanguageUtils::getPlayerLocaleString, session.getLocale())
|
||||
.title("geyser.auth.login.form.notice.btn_login.microsoft")
|
||||
.button("geyser.auth.login.method.browser")
|
||||
.button("geyser.auth.login.method.password")
|
||||
.button("geyser.auth.login.form.notice.btn_disconnect")
|
||||
.responseHandler((form, responseData) -> {
|
||||
SimpleFormResponse response = form.parseResponse(responseData);
|
||||
if (!response.isCorrect()) {
|
||||
buildAndShowLoginWindow(session);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getClickedButtonId() == 0) {
|
||||
session.authenticateWithMicrosoftCode();
|
||||
} else if (response.getClickedButtonId() == 1) {
|
||||
buildAndShowLoginDetailsWindow(session);
|
||||
} else {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the code that a user must input into their browser
|
||||
*/
|
||||
public static void buildAndShowMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse msCode) {
|
||||
session.sendForm(
|
||||
ModalForm.builder()
|
||||
.title("%xbox.signin")
|
||||
.content("%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" + msCode.user_code)
|
||||
.button1("%gui.done")
|
||||
.button2("%menu.disconnect")
|
||||
.responseHandler((form, responseData) -> {
|
||||
ModalFormResponse response = form.parseResponse(responseData);
|
||||
if (!response.isCorrect()) {
|
||||
buildAndShowMicrosoftAuthenticationWindow(session);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getClickedButtonId() == 1) {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.cumulus.SimpleForm;
|
||||
import org.geysermc.cumulus.response.SimpleFormResponse;
|
||||
import org.geysermc.cumulus.util.FormImage;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
@ -54,15 +55,15 @@ public class StatisticsUtils {
|
||||
SimpleForm.builder()
|
||||
.translator(StatisticsUtils::translate, language)
|
||||
.title("gui.stats")
|
||||
.button("stat.generalButton")
|
||||
.button("stat.itemsButton - stat_type.minecraft.mined")
|
||||
.button("stat.itemsButton - stat_type.minecraft.broken")
|
||||
.button("stat.itemsButton - stat_type.minecraft.crafted")
|
||||
.button("stat.itemsButton - stat_type.minecraft.used")
|
||||
.button("stat.itemsButton - stat_type.minecraft.picked_up")
|
||||
.button("stat.itemsButton - stat_type.minecraft.dropped")
|
||||
.button("stat.mobsButton - geyser.statistics.killed")
|
||||
.button("stat.mobsButton - geyser.statistics.killed_by")
|
||||
.button("stat.generalButton", FormImage.Type.PATH, "textures/ui/World")
|
||||
.button("stat.itemsButton - stat_type.minecraft.mined", FormImage.Type.PATH, "textures/items/iron_pickaxe")
|
||||
.button("stat.itemsButton - stat_type.minecraft.broken", FormImage.Type.PATH, "textures/item/record_11")
|
||||
.button("stat.itemsButton - stat_type.minecraft.crafted", FormImage.Type.PATH, "textures/blocks/crafting_table_side")
|
||||
.button("stat.itemsButton - stat_type.minecraft.used", FormImage.Type.PATH, "textures/ui/Wrenches1")
|
||||
.button("stat.itemsButton - stat_type.minecraft.picked_up", FormImage.Type.PATH, "textures/blocks/chest_front")
|
||||
.button("stat.itemsButton - stat_type.minecraft.dropped", FormImage.Type.PATH, "textures/ui/trash_default")
|
||||
.button("stat.mobsButton - geyser.statistics.killed", FormImage.Type.PATH, "textures/items/diamon_sword")
|
||||
.button("stat.mobsButton - geyser.statistics.killed_by", FormImage.Type.PATH, "textures/ui/wither_heart_flash")
|
||||
.responseHandler((form, responseData) -> {
|
||||
SimpleFormResponse response = form.parseResponse(responseData);
|
||||
if (!response.isCorrect()) {
|
||||
@ -178,7 +179,7 @@ public class StatisticsUtils {
|
||||
|
||||
session.sendForm(
|
||||
builder.content(content.toString())
|
||||
.button("gui.back")
|
||||
.button("gui.back", FormImage.Type.PATH, "textures/gui/newgui/undo")
|
||||
.responseHandler((form1, responseData1) -> {
|
||||
SimpleFormResponse response1 = form.parseResponse(responseData1);
|
||||
if (response1.isCorrect()) {
|
||||
|
@ -32,10 +32,14 @@ remote:
|
||||
port: 25565
|
||||
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
|
||||
auth-type: online
|
||||
# Allow for password-based authentication methods through Geyser. Only useful in online mode.
|
||||
# If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop.
|
||||
allow-password-authentication: true
|
||||
# Whether to enable PROXY protocol or not while connecting to the server.
|
||||
# This is useful only when:
|
||||
# 1) Your server supports PROXY protocol (it probably doesn't)
|
||||
# 2) You run Velocity or BungeeCord with respective option enabled.
|
||||
# 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
|
||||
# IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!
|
||||
use-proxy-protocol: false
|
||||
|
||||
# Floodgate uses encryption to ensure use from authorised sources.
|
||||
@ -51,10 +55,12 @@ floodgate-key-file: key.pem
|
||||
# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username
|
||||
# email: javaccountemail@example.com # Your Minecraft: Java Edition email
|
||||
# password: javaccountpassword123 # Your Minecraft: Java Edition password
|
||||
# microsoft-account: true # Whether the account is a Mojang or Microsoft account.
|
||||
#
|
||||
# bluerkelp2:
|
||||
# email: not_really_my_email_address_mr_minecrafter53267@gmail.com
|
||||
# password: "this isn't really my password"
|
||||
# microsoft-account: false
|
||||
|
||||
# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
|
||||
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
|
||||
@ -132,8 +138,7 @@ above-bedrock-nether-building: false
|
||||
force-resource-packs: true
|
||||
|
||||
# Allows Xbox achievements to be unlocked.
|
||||
# This disables certain commands so the Bedrock client can't to "cheat" to get them.
|
||||
# Commands such as /gamemode and /give will not work from Bedrock with this enabled
|
||||
# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
|
||||
xbox-achievements-enabled: false
|
||||
|
||||
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 1a00766840baf1f512d98f5a75c177c8bcfba6f3
|
||||
Subproject commit bffb5617c1ecdacc10031c6ec36988a5f04cb5c6
|
@ -1 +1 @@
|
||||
Subproject commit 143285afb4bdf4d5ef40ef7a7959477dabf4d34c
|
||||
Subproject commit 2e52b01cc541c8925346f93be8940087d9af1661
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
* 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
|
||||
|
13
pom.xml
13
pom.xml
@ -71,19 +71,6 @@
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>releases</id>
|
||||
<name>opencollab-releases</name>
|
||||
<url>https://repo.opencollab.dev/maven-releases</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>snapshots</id>
|
||||
<name>opencollab-snapshots</name>
|
||||
<url>https://repo.opencollab.dev/maven-snapshots</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren