itemFrameCache = new Object2ObjectOpenHashMap<>();
/**
* Stores a list of all lectern locations and their block entity tags.
@@ -243,12 +241,6 @@ public class GeyserSession implements CommandSender {
@Setter
private boolean sprinting;
- /**
- * Not updated if cache chunks is enabled.
- */
- @Setter
- private boolean jumping;
-
/**
* Whether the player is swimming in water.
* Used to update speed when crawling.
@@ -378,9 +370,6 @@ public class GeyserSession implements CommandSender {
private boolean reducedDebugInfo = false;
- @Setter
- private CustomFormWindow settingsForm;
-
/**
* The op permission level set by the server
*/
@@ -424,7 +413,7 @@ public class GeyserSession implements CommandSender {
/**
* Stores the last text inputted into a sign.
- *
+ *
* Bedrock sends packets every time you update the sign, Java only wants the final packet.
* Until we determine that the user has finished editing, we save the sign's current status.
*/
@@ -443,9 +432,7 @@ public class GeyserSession implements CommandSender {
@Setter
private boolean waitingForStatistics = false;
- @Setter
- private List selectedEmotes = new ArrayList<>();
- private final Set emotes = new HashSet<>();
+ private final Set emotes;
/**
* The thread that will run every 50 milliseconds - one Minecraft tick.
@@ -463,8 +450,10 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.effectCache = new EntityEffectCache();
+ this.formCache = new FormCache(this);
+ this.preferencesCache = new PreferencesCache(this);
+ this.tagCache = new TagCache();
this.worldCache = new WorldCache(this);
- this.windowCache = new WindowCache(this);
this.collisionManager = new CollisionManager(this);
@@ -485,8 +474,14 @@ public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServ
.onCancelled(result -> disconnect(result.getEvent().getMessage()));
// Make a copy to prevent ConcurrentModificationException
- final List tmpPlayers = new ArrayList<>(connector.getPlayers());
- tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
+ if (connector.getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.NO_EMOTES) {
+ this.emotes = new HashSet<>();
+ // Make a copy to prevent ConcurrentModificationException
+ final List tmpPlayers = new ArrayList<>(connector.getPlayers());
+ tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes()));
+ } else {
+ this.emotes = null;
+ }
bedrockServerSession.addDisconnectHandler(disconnectReason -> {
EventManager.getInstance().triggerEvent(new SessionDisconnectEvent(this, disconnectReason));
@@ -527,12 +522,7 @@ public void connect() {
sendUpstreamPacket(entityPacket);
CreativeContentPacket creativePacket = new CreativeContentPacket();
- if (upstream.getSession().getPacketCodec().getProtocolVersion() < Bedrock_v431.V431_CODEC.getProtocolVersion()) {
- creativePacket.setContents(ItemRegistry.getPre1_16_220CreativeContents());
- } else {
- // No additional work required
- creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS);
- }
+ creativePacket.setContents(ItemRegistry.CREATIVE_ITEMS);
sendUpstreamPacket(creativePacket);
PlayStatusPacket playStatusPacket = new PlayStatusPacket();
@@ -599,9 +589,18 @@ public void authenticate(String username, String password) {
authenticationService.setPassword(password);
authenticationService.login();
- protocol = new MinecraftProtocol(authenticationService);
+ protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken());
} else {
- protocol = new MinecraftProtocol(username);
+ // always replace spaces when using Floodgate,
+ // as usernames with spaces cause issues with Bungeecord's login cycle.
+ // However, this doesn't affect the final username as Floodgate is still in charge of that.
+ // So if you have (for example) replace spaces enabled on Floodgate the spaces will re-appear.
+ String validUsername = username;
+ if (remoteAuthType == AuthType.FLOODGATE) {
+ validUsername = username.replace(' ', '_');
+ }
+
+ protocol = new MinecraftProtocol(validUsername);
}
connectDownstream();
@@ -630,7 +629,7 @@ public void authenticateWithMicrosoftCode() {
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
- LoginEncryptionUtils.showMicrosoftCodeWindow(this, response);
+ LoginEncryptionUtils.buildAndShowMicrosoftCodeWindow(this, response);
// This just looks cool
SetTimePacket packet = new SetTimePacket();
@@ -657,7 +656,7 @@ private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticatio
}
try {
msaAuthenticationService.login();
- protocol = new MinecraftProtocol(msaAuthenticationService);
+ protocol = new MinecraftProtocol(msaAuthenticationService.getSelectedProfile(), msaAuthenticationService.getAccessToken());
connectDownstream();
} catch (RequestException e) {
@@ -675,61 +674,61 @@ private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticatio
*/
private void connectDownstream() {
boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE;
- final PublicKey publicKey;
-
- if (floodgate) {
- PublicKey key = null;
- try {
- key = EncryptionUtil.getKeyFromFile(
- connector.getConfig().getFloodgateKeyPath(),
- PublicKey.class
- );
- } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
- connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
- }
- publicKey = key;
- } else publicKey = null;
-
- if (publicKey != null) {
- connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
- }
// Start ticking
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
- downstream = new Client(this.remoteAddress, this.remotePort, protocol, new TcpSessionFactory());
+ downstream = new TcpClientSession(this.remoteAddress, this.remotePort, protocol);
disableSrvResolving();
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
- downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
- downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
+ downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
+ downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
}
if (connector.getConfig().isForwardPlayerPing()) {
// Let Geyser handle sending the keep alive
- downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
+ downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
}
- downstream.getSession().addListener(new SessionAdapter() {
+ downstream.addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket) {
String addressSuffix;
if (floodgate) {
- String encrypted = "";
+ byte[] encryptedData;
+
try {
- encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
+ FloodgateSkinUploader skinUploader = connector.getSkinUploader();
+ FloodgateCipher cipher = connector.getCipher();
+
+ encryptedData = cipher.encryptFromString(BedrockData.of(
clientData.getGameVersion(),
authData.getName(),
authData.getXboxUUID(),
- clientData.getDeviceOS().ordinal(),
+ clientData.getDeviceOs().ordinal(),
clientData.getLanguageCode(),
+ clientData.getUiProfile().ordinal(),
clientData.getCurrentInputMode().ordinal(),
- upstream.getAddress().getAddress().getHostAddress()
- ));
+ upstream.getAddress().getAddress().getHostAddress(),
+ skinUploader.getId(),
+ skinUploader.getVerifyCode(),
+ connector.getTimeSyncer()
+ ).toString());
+
+ if (!connector.getTimeSyncer().hasUsefulOffset()) {
+ connector.getLogger().warning(
+ "We couldn't make sure that your system clock is accurate. " +
+ "This can cause issues with logging in."
+ );
+ }
+
} 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;
}
- addressSuffix = '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted;
+ addressSuffix = '\0' + new String(encryptedData, StandardCharsets.UTF_8);
} else {
addressSuffix = "";
}
@@ -811,6 +810,12 @@ public void packetReceived(PacketReceivedEvent event) {
if (!closed) {
handleDownstreamPacket(event.getPacket());
}
+
+ if (remoteAuthType == AuthType.FLOODGATE) {
+ // We'll send the skin upload a bit after the handshake packet (aka this packet),
+ // because otherwise the global server returns the data too fast.
+ getAuthData().upload(connector);
+ }
}
@Override
@@ -825,7 +830,7 @@ public void packetError(PacketErrorEvent event) {
if (!daylightCycle) {
setDaylightCycle(true);
}
- downstream.getSession().connect();
+ downstream.connect();
connector.addPlayer(this);
}
@@ -843,7 +848,7 @@ public void handleDownstreamPacket(Packet packet) {
}
EventResult> result = EventManager.getInstance().triggerEvent(DownstreamPacketReceiveEvent.of(this, packet));
- if (!result.isCancelled()) {
+ if (result.getEvent() != null && !result.isCancelled()) {
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(result.getEvent().getPacket().getClass(), result.getEvent().getPacket(), this);
}
}
@@ -851,8 +856,8 @@ public void handleDownstreamPacket(Packet packet) {
public void disconnect(String reason) {
if (!closed) {
loggedIn = false;
- if (downstream != null && downstream.getSession() != null) {
- downstream.getSession().disconnect(reason);
+ if (downstream != null) {
+ downstream.disconnect(reason);
}
if (upstream != null && !upstream.isClosed()) {
connector.getPlayers().remove(this);
@@ -870,7 +875,6 @@ public void disconnect(String reason) {
this.entityCache = null;
this.effectCache = null;
this.worldCache = null;
- this.windowCache = null;
closed = true;
}
@@ -882,7 +886,7 @@ public void close() {
/**
* Called every 50 milliseconds - one Minecraft tick.
*/
- public void tick() {
+ protected 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
@@ -980,7 +984,7 @@ public boolean adjustSpeed() {
* Will be overwritten for GeyserConnect.
*/
protected void disableSrvResolving() {
- this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false);
+ this.downstream.setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false);
}
@Override
@@ -1011,10 +1015,6 @@ public String getLocale() {
return clientData.getLanguageCode();
}
- public void sendForm(FormWindow window, int id) {
- windowCache.showWindow(window, id);
- }
-
public void setRenderDistance(int renderDistance) {
renderDistance = GenericMath.ceil(++renderDistance * MathUtils.SQRT_OF_TWO); //square to circle
this.renderDistance = renderDistance;
@@ -1028,8 +1028,12 @@ public InetSocketAddress getSocketAddress() {
return this.upstream.getAddress();
}
- public void sendForm(FormWindow window) {
- windowCache.showWindow(window);
+ public void sendForm(Form form) {
+ formCache.showForm(form);
+ }
+
+ public void sendForm(FormBuilder, ?> formBuilder) {
+ formCache.showForm(formBuilder.build());
}
private void startGame() {
@@ -1081,7 +1085,7 @@ private void startGame() {
startGamePacket.setItemEntries(ItemRegistry.ITEMS);
startGamePacket.setVanillaVersion("*");
startGamePacket.setInventoriesServerAuthoritative(true);
- startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped
+ startGamePacket.setServerEngine(""); // Do we want to fill this in?
SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings();
settings.setMovementMode(AuthoritativeMovementMode.CLIENT);
@@ -1263,8 +1267,8 @@ public void sendUpstreamPacketImmediately(BedrockPacket packet) {
public void sendDownstreamPacket(Packet packet) {
EventManager.getInstance().triggerEvent(DownstreamPacketSendEvent.of(this, packet))
.onNotCancelled(result -> {
- if (downstream != null && downstream.getSession() != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) {
- downstream.getSession().send(result.getEvent().getPacket());
+ if (downstream != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) {
+ downstream.send(result.getEvent().getPacket());
} else {
connector.getLogger().debug("Tried to send downstream packet " + result.getEvent().getPacket().getClass().getSimpleName() + " before connected to the server");
}
@@ -1280,7 +1284,7 @@ public void sendDownstreamPacket(Packet packet) {
public void setReducedDebugInfo(boolean value) {
reducedDebugInfo = value;
// Set the showCoordinates data. This is done because updateShowCoordinates() uses this gamerule as a variable.
- getWorldCache().updateShowCoordinates();
+ preferencesCache.updateShowCoordinates();
}
/**
@@ -1299,7 +1303,7 @@ public void setDaylightCycle(boolean doCycle) {
* Send a gamerule value to the client
*
* @param gameRule The gamerule to send
- * @param value The value of the gamerule
+ * @param value The value of the gamerule
*/
public void sendGameRule(String gameRule, Object value) {
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
@@ -1366,7 +1370,6 @@ public void updateStatistics(@NonNull Map statistics) {
}
public void refreshEmotes(List emotes) {
- this.selectedEmotes = emotes;
this.emotes.addAll(emotes);
for (GeyserSession player : connector.getPlayers()) {
List pieces = new ArrayList<>();
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java
index ba9e13548fa..46327689177 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java
@@ -25,16 +25,26 @@
package org.geysermc.connector.network.session.auth;
-import lombok.AllArgsConstructor;
+import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.geysermc.connector.GeyserConnector;
import java.util.UUID;
-@Getter
-@AllArgsConstructor
+@RequiredArgsConstructor
public class AuthData {
+ @Getter private final String name;
+ @Getter private final UUID UUID;
+ @Getter private final String xboxUUID;
- private String name;
- private UUID UUID;
- private String xboxUUID;
+ private final JsonNode certChainData;
+ private final String clientData;
+
+ public void upload(GeyserConnector connector) {
+ // we can't upload the skin in LoginEncryptionUtil since the global server would return
+ // the skin too fast, that's why we upload it after we know for sure that the target server
+ // is ready to handle the result of the global server
+ connector.getSkinUploader().uploadSkin(certChainData, clientData);
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java
index 16e06c06644..fc4d1164a63 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java
@@ -25,17 +25,18 @@
package org.geysermc.connector.network.session.auth;
-import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
-import org.geysermc.floodgate.util.DeviceOS;
+import org.geysermc.floodgate.util.DeviceOs;
+import org.geysermc.floodgate.util.InputMode;
+import org.geysermc.floodgate.util.UiProfile;
import java.util.UUID;
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
-public class BedrockClientData {
+public final class BedrockClientData {
@JsonProperty(value = "GameVersion")
private String gameVersion;
@JsonProperty(value = "ServerAddress")
@@ -77,9 +78,9 @@ public class BedrockClientData {
@JsonProperty(value = "DeviceModel")
private String deviceModel;
@JsonProperty(value = "DeviceOS")
- private DeviceOS deviceOS;
+ private DeviceOs deviceOs;
@JsonProperty(value = "UIProfile")
- private UIProfile uiProfile;
+ private UiProfile uiProfile;
@JsonProperty(value = "GuiScale")
private int guiScale;
@JsonProperty(value = "CurrentInputMode")
@@ -106,18 +107,19 @@ public class BedrockClientData {
@JsonProperty(value = "PlayFabId")
private String playFabId;
- public enum UIProfile {
- @JsonEnumDefaultValue
- CLASSIC,
- POCKET
+ public DeviceOs getDeviceOs() {
+ return deviceOs != null ? deviceOs : DeviceOs.UNKNOWN;
}
- public enum InputMode {
- @JsonEnumDefaultValue
- UNKNOWN,
- KEYBOARD_MOUSE,
- TOUCH, // I guess Touch?
- CONTROLLER,
- VR
+ public InputMode getCurrentInputMode() {
+ return currentInputMode != null ? currentInputMode : InputMode.UNKNOWN;
+ }
+
+ public InputMode getDefaultInputMode() {
+ return defaultInputMode != null ? defaultInputMode : InputMode.UNKNOWN;
+ }
+
+ public UiProfile getUiProfile() {
+ return uiProfile != null ? uiProfile : UiProfile.CLASSIC;
}
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
index 369967accdb..98ec5b26258 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/AdvancementsCache.java
@@ -29,26 +29,19 @@
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientAdvancementTabPacket;
import lombok.Getter;
import lombok.Setter;
-import org.geysermc.common.window.SimpleFormWindow;
-import org.geysermc.common.window.button.FormButton;
-import org.geysermc.common.window.response.SimpleFormResponse;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.chat.MessageTranslator;
import org.geysermc.connector.utils.GeyserAdvancement;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils;
+import 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 {
-
- // Different form IDs
- public static final int ADVANCEMENTS_MENU_FORM_ID = 1341;
- public static final int ADVANCEMENTS_LIST_FORM_ID = 1342;
- public static final int ADVANCEMENT_INFO_FORM_ID = 1343;
-
/**
* Stores the player's advancement progress
*/
@@ -74,73 +67,128 @@ public AdvancementsCache(GeyserSession session) {
}
/**
- * Build a form with all advancement categories
- *
- * @return The built advancement category menu
+ * Build and send a form with all advancement categories
*/
- public SimpleFormWindow buildMenuForm() {
- // Cache the language for cleaner access
- String language = session.getClientData().getLanguageCode();
+ public void buildAndShowMenuForm() {
+ SimpleForm.Builder builder =
+ SimpleForm.builder()
+ .translator(LocaleUtils::getLocaleString, session.getLocale())
+ .title("gui.advancements");
- // Created menu window for advancement categories
- SimpleFormWindow window = new SimpleFormWindow(LocaleUtils.getLocaleString("gui.advancements", language), "");
+ boolean hasAdvancements = false;
for (Map.Entry advancement : storedAdvancements.entrySet()) {
if (advancement.getValue().getParentId() == null) { // No parent means this is a root advancement
- window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), language)));
+ hasAdvancements = true;
+ builder.button(MessageTranslator.convertMessage(advancement.getValue().getDisplayData().getTitle(), session.getLocale()));
}
}
- if (window.getButtons().isEmpty()) {
- window.setContent(LocaleUtils.getLocaleString("advancements.empty", language));
+ if (!hasAdvancements) {
+ builder.content("advancements.empty");
}
- return window;
+ builder.responseHandler((form, responseData) -> {
+ SimpleFormResponse response = form.parseResponse(responseData);
+ if (!response.isCorrect()) {
+ return;
+ }
+
+ String id = "";
+
+ int advancementIndex = 0;
+ for (Map.Entry 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);
}
/**
- * Builds the list of advancements
- *
- * @return The built list form
+ * Build and send the list of advancements
*/
- public SimpleFormWindow buildListForm() {
- // Cache the language for easier access
- String language = session.getLocale();
- String id = currentAdvancementCategoryId;
+ public void buildAndShowListForm() {
GeyserAdvancement categoryAdvancement = storedAdvancements.get(currentAdvancementCategoryId);
+ String language = session.getLocale();
- // Create the window
- SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language),
- MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
+ SimpleForm.Builder builder =
+ SimpleForm.builder()
+ .title(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getTitle(), language))
+ .content(MessageTranslator.convertMessage(categoryAdvancement.getDisplayData().getDescription(), language));
- if (id != null) {
- for (Map.Entry advancementEntry : storedAdvancements.entrySet()) {
- GeyserAdvancement advancement = advancementEntry.getValue();
+ if (currentAdvancementCategoryId != null) {
+ for (GeyserAdvancement advancement : storedAdvancements.values()) {
if (advancement != null) {
if (advancement.getParentId() != null && currentAdvancementCategoryId.equals(advancement.getRootId(this))) {
- boolean earned = isEarned(advancement);
-
- if (earned || !advancement.getDisplayData().isShowToast()) {
- window.getButtons().add(new FormButton("§6" + MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
- } else {
- window.getButtons().add(new FormButton(MessageTranslator.convertMessage(advancementEntry.getValue().getDisplayData().getTitle()) + "\n"));
- }
+ boolean color = isEarned(advancement) || !advancement.getDisplayData().isShowToast();
+ builder.button((color ? "§6" : "") + MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()) + '\n');
}
}
}
}
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
+ builder.button(LanguageUtils.getPlayerLocaleString("gui.back", language));
- return window;
+ 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
- * @return The information for the chosen advancement
*/
- public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) {
+ public void buildAndShowInfoForm(GeyserAdvancement advancement) {
// Cache language for easier access
String language = session.getLocale();
@@ -160,16 +208,24 @@ public SimpleFormWindow buildInfoForm(GeyserAdvancement advancement) {
Parent Advancement: Minecraft // If relevant
*/
- String content = description + "\n\n§f" +
- earnedString + "\n";
+ String content = description + "\n\n§f" + earnedString + "\n";
if (!currentAdvancementCategoryId.equals(advancement.getParentId())) {
// Only display the parent if it is not the category
content += LanguageUtils.getPlayerLocaleString("geyser.advancements.parentid", language, MessageTranslator.convertMessage(storedAdvancements.get(advancement.getParentId()).getDisplayData().getTitle(), language));
}
- SimpleFormWindow window = new SimpleFormWindow(MessageTranslator.convertMessage(advancement.getDisplayData().getTitle()), content);
- window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("gui.back", language)));
- return window;
+ 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();
+ }
+ })
+ );
}
/**
@@ -209,108 +265,6 @@ public boolean isEarned(GeyserAdvancement advancement) {
return earned;
}
- /**
- * Handle the menu form response
- *
- * @param response The response string to parse
- * @return True if the form was parsed correctly, false if not
- */
- public boolean handleMenuForm(String response) {
- SimpleFormWindow menuForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_MENU_FORM_ID);
- menuForm.setResponse(response);
-
- SimpleFormResponse formResponse = (SimpleFormResponse) menuForm.getResponse();
-
- String id = "";
- if (formResponse != null && formResponse.getClickedButton() != null) {
- int advancementIndex = 0;
- for (Map.Entry advancement : storedAdvancements.entrySet()) {
- if (advancement.getValue().getParentId() == null) { // Root advancement
- if (advancementIndex == formResponse.getClickedButtonId()) {
- id = advancement.getKey();
- break;
- } else {
- advancementIndex++;
- }
- }
- }
- }
- if (!id.equals("")) {
- if (id.equals(currentAdvancementCategoryId)) {
- // The server thinks we are already on this tab
- session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
- } else {
- // Send a packet indicating that we intend to open this particular advancement window
- ClientAdvancementTabPacket packet = new ClientAdvancementTabPacket(id);
- session.sendDownstreamPacket(packet);
- // Wait for a response there
- }
- }
-
- return true;
- }
-
- /**
- * Handle the list form response (Advancement category choice)
- *
- * @param response The response string to parse
- * @return True if the form was parsed correctly, false if not
- */
- public boolean handleListForm(String response) {
- SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENTS_LIST_FORM_ID);
- listForm.setResponse(response);
-
- SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
-
- if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
- GeyserAdvancement advancement = null;
- int advancementIndex = 0;
- // Loop around to find the advancement that the client pressed
- for (GeyserAdvancement advancementEntry : storedAdvancements.values()) {
- if (advancementEntry.getParentId() != null &&
- currentAdvancementCategoryId.equals(advancementEntry.getRootId(this))) {
- if (advancementIndex == formResponse.getClickedButtonId()) {
- advancement = advancementEntry;
- break;
- } else {
- advancementIndex++;
- }
- }
- }
- if (advancement != null) {
- session.sendForm(buildInfoForm(advancement), ADVANCEMENT_INFO_FORM_ID);
- } else {
- session.sendForm(buildMenuForm(), ADVANCEMENTS_MENU_FORM_ID);
- // Indicate that we have closed the current advancement tab
- session.sendDownstreamPacket(new ClientAdvancementTabPacket());
- }
- } else {
- // Indicate that we have closed the current advancement tab
- session.sendDownstreamPacket(new ClientAdvancementTabPacket());
- }
-
- return true;
- }
-
- /**
- * Handle the info form response
- *
- * @param response The response string to parse
- * @return True if the form was parsed correctly, false if not
- */
- public boolean handleInfoForm(String response) {
- SimpleFormWindow listForm = (SimpleFormWindow) session.getWindowCache().getWindows().get(ADVANCEMENT_INFO_FORM_ID);
- listForm.setResponse(response);
-
- SimpleFormResponse formResponse = (SimpleFormResponse) listForm.getResponse();
-
- if (!listForm.isClosed() && formResponse != null && formResponse.getClickedButton() != null) {
- session.sendForm(buildListForm(), ADVANCEMENTS_LIST_FORM_ID);
- }
-
- return true;
- }
-
public String getColorFromAdvancementFrameType(GeyserAdvancement advancement) {
String base = "\u00a7";
if (advancement.getDisplayData().getFrameType() == Advancement.DisplayData.FrameType.CHALLENGE) {
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
index c82645dbfdb..cb373789577 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
@@ -68,7 +68,7 @@ public void checkForSend() {
packet = null;
return;
}
- session.getDownstream().getSession().send(packet);
+ session.sendDownstreamPacket(packet);
packet = null;
lastBookUpdate = System.currentTimeMillis();
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java
index c1ba6fff16d..28ac9459ef4 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BossBar.java
@@ -37,15 +37,14 @@
@AllArgsConstructor
public class BossBar {
+ private final GeyserSession session;
- private GeyserSession session;
-
- private long entityId;
+ private final long entityId;
private Component title;
private float health;
- private int color;
- private int overlay;
- private int darkenSky;
+ private final int color;
+ private final int overlay;
+ private final int darkenSky;
public void addBossBar() {
addBossEntity();
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java
index d182a6f12b2..393a1f1880f 100644
--- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java
@@ -29,47 +29,29 @@
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import lombok.Setter;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.MathUtils;
public class ChunkCache {
- private static final int MINIMUM_WORLD_HEIGHT = 0;
-
private final boolean cache;
-
private final Long2ObjectMap chunks;
+ @Setter
+ private int minY;
+
public ChunkCache(GeyserSession session) {
- if (session.getConnector().getWorldManager().hasOwnChunkCache()) {
- this.cache = false; // To prevent Spigot from initializing
- } else {
- this.cache = session.getConnector().getConfig().isCacheChunks();
- }
+ this.cache = !session.getConnector().getWorldManager().hasOwnChunkCache(); // To prevent Spigot from initializing
chunks = cache ? new Long2ObjectOpenHashMap<>() : null;
}
- public Column addToCache(Column chunk) {
+ public void addToCache(Column chunk) {
if (!cache) {
- return chunk;
+ return;
}
- long chunkPosition = MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ());
- Column existingChunk;
- if (chunk.getBiomeData() == null // Only consider merging columns if the new chunk isn't a full chunk
- && (existingChunk = chunks.getOrDefault(chunkPosition, null)) != null) { // Column is already present in cache, we can merge with existing
- boolean changed = false;
- for (int i = 0; i < chunk.getChunks().length; i++) { // The chunks member is final, so chunk.getChunks() will probably be inlined and then completely optimized away
- if (chunk.getChunks()[i] != null) {
- existingChunk.getChunks()[i] = chunk.getChunks()[i];
- changed = true;
- }
- }
- return changed ? existingChunk : null;
- } else {
- chunks.put(chunkPosition, chunk);
- return chunk;
- }
+ chunks.put(MathUtils.chunkPositionToLong(chunk.getX(), chunk.getZ()), chunk);
}
public Column getChunk(int chunkX, int chunkZ) {
@@ -87,15 +69,24 @@ public void updateBlock(int x, int y, int z, int block) {
return;
}
- if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) {
+ if (y < minY || (y >> 4) > column.getChunks().length - 1) {
// Y likely goes above or below the height limit of this world
return;
}
- Chunk chunk = column.getChunks()[y >> 4];
- if (chunk != null) {
- chunk.set(x & 0xF, y & 0xF, z & 0xF, block);
+ Chunk chunk = column.getChunks()[(y >> 4) - getChunkMinY()];
+ if (chunk == null) {
+ if (block != BlockTranslator.JAVA_AIR_ID) {
+ chunk = new Chunk();
+ // A previously empty chunk, which is no longer empty as a block has been added to it
+ column.getChunks()[(y >> 4) - getChunkMinY()] = chunk;
+ } else {
+ // Nothing to update
+ return;
+ }
}
+
+ chunk.set(x & 0xF, y & 0xF, z & 0xF, block);
}
public int getBlockAt(int x, int y, int z) {
@@ -108,12 +99,12 @@ public int getBlockAt(int x, int y, int z) {
return BlockTranslator.JAVA_AIR_ID;
}
- if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) {
+ if (y < minY || (y >> 4) > column.getChunks().length - 1) {
// Y likely goes above or below the height limit of this world
return BlockTranslator.JAVA_AIR_ID;
}
- Chunk chunk = column.getChunks()[y >> 4];
+ Chunk chunk = column.getChunks()[(y >> 4) - getChunkMinY()];
if (chunk != null) {
return chunk.get(x & 0xF, y & 0xF, z & 0xF);
}
@@ -129,4 +120,8 @@ public void removeChunk(int chunkX, int chunkZ) {
long chunkPosition = MathUtils.chunkPositionToLong(chunkX, chunkZ);
chunks.remove(chunkPosition);
}
+
+ public int getChunkMinY() {
+ return minY >> 4;
+ }
}
diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java
new file mode 100644
index 00000000000..954a2b033fe
--- /dev/null
+++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/FormCache.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * 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.nukkitx.protocol.bedrock.packet.ModalFormRequestPacket;
+import com.nukkitx.protocol.bedrock.packet.ModalFormResponsePacket;
+import com.nukkitx.protocol.bedrock.packet.NetworkStackLatencyPacket;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import lombok.RequiredArgsConstructor;
+import org.geysermc.connector.GeyserConnector;
+import org.geysermc.connector.network.session.GeyserSession;
+import org.geysermc.cumulus.Form;
+import org.geysermc.cumulus.SimpleForm;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@RequiredArgsConstructor
+public class FormCache {
+ private final AtomicInteger formId = new AtomicInteger(0);
+ private final Int2ObjectMap