Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-26 00:00:41 +01:00
Merge remote-tracking branch 'origin/master' into jwt-changes
# Conflicts: # core/src/main/java/org/geysermc/geyser/registry/populator/BlockRegistryPopulator.java # core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java # core/src/main/java/org/geysermc/geyser/util/LoginEncryptionUtils.java
Dieser Commit ist enthalten in:
Commit
99671960d0
2
.github/workflows/pullrequest.yml
vendored
2
.github/workflows/pullrequest.yml
vendored
@ -31,7 +31,7 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Use author's API repo if it exists
|
||||
if: steps.find_forks.outputs.target_branch_found == 'true'
|
||||
if: ${{ steps.find_forks.outputs.target_branch_found == 'true' }}
|
||||
env:
|
||||
API_FORK_URL: ${{ steps.find_forks.outputs.user_fork_url }}
|
||||
API_FORK_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
|
@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
|
||||
|
||||
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
|
||||
|
||||
### Currently supporting Minecraft Bedrock 1.19.40 - 1.19.81 and Minecraft Java 1.19.4.
|
||||
### Currently supporting Minecraft Bedrock 1.19.80 - 1.20 and Minecraft Java 1.20.
|
||||
|
||||
## Setting Up
|
||||
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
|
||||
|
@ -35,7 +35,9 @@ import org.geysermc.geyser.api.event.EventRegistrar;
|
||||
import org.geysermc.geyser.api.extension.ExtensionManager;
|
||||
import org.geysermc.geyser.api.network.BedrockListener;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -107,6 +109,30 @@ public interface GeyserApi extends GeyserApiBase {
|
||||
@NonNull
|
||||
BedrockListener bedrockListener();
|
||||
|
||||
/**
|
||||
* Gets the {@link Path} to the Geyser config directory.
|
||||
*
|
||||
* @return the path to the Geyser config directory
|
||||
*/
|
||||
@NonNull
|
||||
Path configDirectory();
|
||||
|
||||
/**
|
||||
* Gets the {@link Path} to the Geyser packs directory.
|
||||
*
|
||||
* @return the path to the Geyser packs directory
|
||||
*/
|
||||
@NonNull
|
||||
Path packDirectory();
|
||||
|
||||
/**
|
||||
* Gets {@link PlatformType} the extension is running on
|
||||
*
|
||||
* @return type of platform
|
||||
*/
|
||||
@NonNull
|
||||
PlatformType platformType();
|
||||
|
||||
/**
|
||||
* Gets the current {@link GeyserApiBase} instance.
|
||||
*
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.event.bedrock;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
|
||||
|
||||
/**
|
||||
* Called when Geyser session connected to a Java remote server and is in a play-ready state.
|
||||
*/
|
||||
public final class SessionJoinEvent extends ConnectionEvent {
|
||||
public SessionJoinEvent(@NonNull GeyserConnection connection) {
|
||||
super(connection);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.event.bedrock;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Called when Geyser initializes a session for a new Bedrock client and is in the process of sending resource packs.
|
||||
*/
|
||||
public abstract class SessionLoadResourcePacksEvent extends ConnectionEvent {
|
||||
public SessionLoadResourcePacksEvent(@NonNull GeyserConnection connection) {
|
||||
super(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an unmodifiable list of {@link ResourcePack}s that will be sent to the client.
|
||||
*
|
||||
* @return an unmodifiable list of resource packs that will be sent to the client.
|
||||
*/
|
||||
public abstract @NonNull List<ResourcePack> resourcePacks();
|
||||
|
||||
/**
|
||||
* Registers a {@link ResourcePack} to be sent to the client.
|
||||
*
|
||||
* @param resourcePack a resource pack that will be sent to the client.
|
||||
* @return true if the resource pack was added successfully,
|
||||
* or false if already present
|
||||
*/
|
||||
public abstract boolean register(@NonNull ResourcePack resourcePack);
|
||||
|
||||
/**
|
||||
* Unregisters a resource pack from being sent to the client.
|
||||
*
|
||||
* @param uuid the UUID of the resource pack
|
||||
* @return true whether the resource pack was removed from the list of resource packs.
|
||||
*/
|
||||
public abstract boolean unregister(@NonNull UUID uuid);
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.event.bedrock;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.event.Cancellable;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.event.connection.ConnectionEvent;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
|
||||
/**
|
||||
* Called when a session has logged in, and is about to connect to a remote java server.
|
||||
* This event is cancellable, and can be used to prevent the player from connecting to the remote server.
|
||||
*/
|
||||
public final class SessionLoginEvent extends ConnectionEvent implements Cancellable {
|
||||
private RemoteServer remoteServer;
|
||||
private boolean cancelled;
|
||||
private String disconnectReason;
|
||||
|
||||
public SessionLoginEvent(@NonNull GeyserConnection connection, @NonNull RemoteServer remoteServer) {
|
||||
super(connection);
|
||||
this.remoteServer = remoteServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the event is cancelled.
|
||||
*
|
||||
* @return The cancel status of the event.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the login event, and disconnects the player.
|
||||
* If cancelled, the player disconnects without connecting to the remote server.
|
||||
* This method will use a default disconnect reason. To specify one, use {@link #setCancelled(boolean, String)}.
|
||||
*
|
||||
* @param cancelled If the login event should be cancelled.
|
||||
*/
|
||||
@Override
|
||||
public void setCancelled(boolean cancelled) {
|
||||
this.cancelled = cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the login event, and disconnects the player with the specified reason.
|
||||
* If cancelled, the player disconnects without connecting to the remote server.
|
||||
*
|
||||
* @param cancelled If the login event should be cancelled.
|
||||
* @param disconnectReason The reason for the cancellation.
|
||||
*/
|
||||
public void setCancelled(boolean cancelled, @NonNull String disconnectReason) {
|
||||
this.cancelled = cancelled;
|
||||
this.disconnectReason = disconnectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason for the cancellation, or null if there is no reason given.
|
||||
*
|
||||
* @return The reason for the cancellation.
|
||||
*/
|
||||
public @Nullable String disconnectReason() {
|
||||
return this.disconnectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link RemoteServer} the section will attempt to connect to.
|
||||
*
|
||||
* @return the {@link RemoteServer} the section will attempt to connect to.
|
||||
*/
|
||||
public @NonNull RemoteServer remoteServer() {
|
||||
return this.remoteServer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link RemoteServer} to connect the session to.
|
||||
*
|
||||
* @param remoteServer Sets the {@link RemoteServer} to connect to.
|
||||
*/
|
||||
public void remoteServer(@NonNull RemoteServer remoteServer) {
|
||||
this.remoteServer = remoteServer;
|
||||
}
|
||||
}
|
82
api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java
Normale Datei
82
api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java
Normale Datei
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.pack;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Represents a pack codec that can be used
|
||||
* to provide resource packs to clients.
|
||||
*/
|
||||
public abstract class PackCodec {
|
||||
|
||||
/**
|
||||
* Gets the sha256 hash of the resource pack.
|
||||
*
|
||||
* @return the hash of the resource pack
|
||||
*/
|
||||
public abstract byte @NonNull [] sha256();
|
||||
|
||||
/**
|
||||
* Gets the resource pack size.
|
||||
*
|
||||
* @return the resource pack file size
|
||||
*/
|
||||
public abstract long size();
|
||||
|
||||
/**
|
||||
* Serializes the given resource pack into a byte buffer.
|
||||
*
|
||||
* @param resourcePack the resource pack to serialize
|
||||
* @return the serialized resource pack
|
||||
*/
|
||||
@NonNull
|
||||
public abstract SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a new resource pack from this codec.
|
||||
*
|
||||
* @return the new resource pack
|
||||
*/
|
||||
@NonNull
|
||||
protected abstract ResourcePack create();
|
||||
|
||||
/**
|
||||
* Creates a new pack provider from the given path.
|
||||
*
|
||||
* @param path the path to create the pack provider from
|
||||
* @return the new pack provider
|
||||
*/
|
||||
@NonNull
|
||||
public static PackCodec path(@NonNull Path path) {
|
||||
return GeyserApi.api().provider(PathPackCodec.class, path);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2023 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
|
||||
@ -23,21 +23,23 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.common;
|
||||
package org.geysermc.geyser.api.pack;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PlatformType {
|
||||
ANDROID("Android"),
|
||||
BUNGEECORD("BungeeCord"),
|
||||
FABRIC("Fabric"),
|
||||
SPIGOT("Spigot"),
|
||||
SPONGE("Sponge"),
|
||||
STANDALONE("Standalone"),
|
||||
VELOCITY("Velocity");
|
||||
import java.nio.file.Path;
|
||||
|
||||
private final String platformName;
|
||||
/**
|
||||
* Represents a pack codec that creates a resource
|
||||
* pack from a path on the filesystem.
|
||||
*/
|
||||
public abstract class PathPackCodec extends PackCodec {
|
||||
|
||||
/**
|
||||
* Gets the path of the resource pack.
|
||||
*
|
||||
* @return the path of the resource pack
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Path path();
|
||||
}
|
72
api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java
Normale Datei
72
api/src/main/java/org/geysermc/geyser/api/pack/ResourcePack.java
Normale Datei
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.pack;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Represents a resource pack sent to Bedrock clients
|
||||
* <p>
|
||||
* This representation of a resource pack only contains what
|
||||
* Geyser requires to send it to the client.
|
||||
*/
|
||||
public interface ResourcePack {
|
||||
|
||||
/**
|
||||
* The {@link PackCodec codec} for this pack.
|
||||
*
|
||||
* @return the codec for this pack
|
||||
*/
|
||||
@NonNull
|
||||
PackCodec codec();
|
||||
|
||||
/**
|
||||
* Gets the resource pack manifest.
|
||||
*
|
||||
* @return the resource pack manifest
|
||||
*/
|
||||
@NonNull
|
||||
ResourcePackManifest manifest();
|
||||
|
||||
/**
|
||||
* Gets the content key of the resource pack. Lack of a content key is represented by an empty String.
|
||||
*
|
||||
* @return the content key of the resource pack
|
||||
*/
|
||||
@NonNull
|
||||
String contentKey();
|
||||
|
||||
/**
|
||||
* Creates a resource pack with the given {@link PackCodec}.
|
||||
*
|
||||
* @param codec the pack codec
|
||||
* @return the resource pack
|
||||
*/
|
||||
@NonNull
|
||||
static ResourcePack create(@NonNull PackCodec codec) {
|
||||
return codec.create();
|
||||
}
|
||||
}
|
209
api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java
Normale Datei
209
api/src/main/java/org/geysermc/geyser/api/pack/ResourcePackManifest.java
Normale Datei
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.pack;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a resource pack manifest.
|
||||
*/
|
||||
public interface ResourcePackManifest {
|
||||
|
||||
/**
|
||||
* Gets the format version of the resource pack.
|
||||
*
|
||||
* @return the format version
|
||||
*/
|
||||
int formatVersion();
|
||||
|
||||
/**
|
||||
* Gets the header of the resource pack.
|
||||
*
|
||||
* @return the header
|
||||
*/
|
||||
@NonNull
|
||||
Header header();
|
||||
|
||||
/**
|
||||
* Gets the modules of the resource pack.
|
||||
*
|
||||
* @return the modules
|
||||
*/
|
||||
@NonNull
|
||||
Collection<? extends Module> modules();
|
||||
|
||||
/**
|
||||
* Gets the dependencies of the resource pack.
|
||||
*
|
||||
* @return the dependencies
|
||||
*/
|
||||
@NonNull
|
||||
Collection<? extends Dependency> dependencies();
|
||||
|
||||
/**
|
||||
* Represents the header of a resource pack.
|
||||
*/
|
||||
interface Header {
|
||||
|
||||
/**
|
||||
* Gets the UUID of the resource pack.
|
||||
*
|
||||
* @return the UUID
|
||||
*/
|
||||
@NonNull
|
||||
UUID uuid();
|
||||
|
||||
/**
|
||||
* Gets the version of the resource pack.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
@NonNull
|
||||
Version version();
|
||||
|
||||
/**
|
||||
* Gets the name of the resource pack.
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
@NonNull
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Gets the description of the resource pack.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
@NonNull
|
||||
String description();
|
||||
|
||||
/**
|
||||
* Gets the minimum supported Minecraft version of the resource pack.
|
||||
*
|
||||
* @return the minimum supported Minecraft version
|
||||
*/
|
||||
@NonNull
|
||||
Version minimumSupportedMinecraftVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a module of a resource pack.
|
||||
*/
|
||||
interface Module {
|
||||
|
||||
/**
|
||||
* Gets the UUID of the module.
|
||||
*
|
||||
* @return the UUID
|
||||
*/
|
||||
@NonNull
|
||||
UUID uuid();
|
||||
|
||||
/**
|
||||
* Gets the version of the module.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
@NonNull
|
||||
Version version();
|
||||
|
||||
/**
|
||||
* Gets the type of the module.
|
||||
*
|
||||
* @return the type
|
||||
*/
|
||||
@NonNull
|
||||
String type();
|
||||
|
||||
/**
|
||||
* Gets the description of the module.
|
||||
*
|
||||
* @return the description
|
||||
*/
|
||||
@NonNull
|
||||
String description();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a dependency of a resource pack.
|
||||
*/
|
||||
interface Dependency {
|
||||
|
||||
/**
|
||||
* Gets the UUID of the dependency.
|
||||
*
|
||||
* @return the uuid
|
||||
*/
|
||||
@NonNull
|
||||
UUID uuid();
|
||||
|
||||
/**
|
||||
* Gets the version of the dependency.
|
||||
*
|
||||
* @return the version
|
||||
*/
|
||||
@NonNull
|
||||
Version version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a version of a resource pack.
|
||||
*/
|
||||
interface Version {
|
||||
|
||||
/**
|
||||
* Gets the major version.
|
||||
*
|
||||
* @return the major version
|
||||
*/
|
||||
int major();
|
||||
|
||||
/**
|
||||
* Gets the minor version.
|
||||
*
|
||||
* @return the minor version
|
||||
*/
|
||||
int minor();
|
||||
|
||||
/**
|
||||
* Gets the patch version.
|
||||
*
|
||||
* @return the patch version
|
||||
*/
|
||||
int patch();
|
||||
|
||||
/**
|
||||
* Gets the version formatted as a String.
|
||||
*
|
||||
* @return the version string
|
||||
*/
|
||||
@NonNull String toString();
|
||||
}
|
||||
}
|
||||
|
39
api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java
Normale Datei
39
api/src/main/java/org/geysermc/geyser/api/util/PlatformType.java
Normale Datei
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.api.util;
|
||||
|
||||
/**
|
||||
* Represents the platform Geyser is running on.
|
||||
*/
|
||||
public record PlatformType(String platformName) {
|
||||
public static final PlatformType ANDROID = new PlatformType("Android");
|
||||
public static final PlatformType BUNGEECORD = new PlatformType("BungeeCord");
|
||||
public static final PlatformType FABRIC = new PlatformType("Fabric");
|
||||
public static final PlatformType SPIGOT = new PlatformType("Spigot");
|
||||
public static final PlatformType SPONGE = new PlatformType("Sponge");
|
||||
public static final PlatformType STANDALONE = new PlatformType("Standalone");
|
||||
public static final PlatformType VELOCITY = new PlatformType("Velocity");
|
||||
}
|
@ -8,6 +8,7 @@ platformRelocate("net.md_5.bungee.jni")
|
||||
platformRelocate("com.fasterxml.jackson")
|
||||
platformRelocate("io.netty.channel.kqueue") // This is not used because relocating breaks natives, but we must include it or else we get ClassDefNotFound
|
||||
platformRelocate("net.kyori")
|
||||
platformRelocate("org.yaml") // Broken as of 1.20
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.bungeecord.proxy)
|
||||
@ -21,7 +22,6 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
exclude(dependency("org.yaml:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-epoll:.*"))
|
||||
exclude(dependency("io.netty:netty-transport-native-unix-common:.*"))
|
||||
exclude(dependency("io.netty:netty-handler:.*"))
|
||||
@ -32,4 +32,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
exclude(dependency("io.netty:netty-codec:.*"))
|
||||
exclude(dependency("io.netty:netty-resolver-dns:.*"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import net.md_5.bungee.api.config.ListenerInfo;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import net.fabricmc.loom.task.RemapJarTask
|
||||
|
||||
plugins {
|
||||
id("fabric-loom") version "1.0-SNAPSHOT"
|
||||
id("com.modrinth.minotaur") version "2.+"
|
||||
@ -89,8 +91,16 @@ tasks {
|
||||
dependsOn(shadowJar)
|
||||
inputFile.set(shadowJar.get().archiveFile)
|
||||
archiveBaseName.set("Geyser-Fabric")
|
||||
archiveClassifier.set("")
|
||||
archiveVersion.set("")
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
|
||||
register("remapModrinthJar", RemapJarTask::class) {
|
||||
dependsOn(shadowJar)
|
||||
inputFile.set(shadowJar.get().archiveFile)
|
||||
archiveBaseName.set("geyser-fabric")
|
||||
archiveVersion.set(project.version.toString() + "+build." + System.getenv("GITHUB_RUN_NUMBER"))
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,12 +113,13 @@ modrinth {
|
||||
|
||||
syncBodyFrom.set(rootProject.file("README.md").readText())
|
||||
|
||||
uploadFile.set(tasks.getByPath("remapJar"))
|
||||
gameVersions.addAll("1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4")
|
||||
uploadFile.set(tasks.getByPath("remapModrinthJar"))
|
||||
gameVersions.addAll("1.20")
|
||||
|
||||
loaders.add("fabric")
|
||||
failSilently.set(true)
|
||||
|
||||
dependencies {
|
||||
required.project("fabric-api")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
@ -217,10 +217,12 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
|
||||
return this.server.getServerVersion();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions") // IDEA thinks that ip cannot be null
|
||||
@NotNull
|
||||
@Override
|
||||
public String getServerBindAddress() {
|
||||
return this.server.getLocalIp();
|
||||
String ip = this.server.getLocalIp();
|
||||
return ip != null ? ip : ""; // See issue #3812
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -72,7 +72,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
|
||||
return;
|
||||
}
|
||||
|
||||
LevelChunk chunk = player.getLevel().getChunk(x, z);
|
||||
LevelChunk chunk = player.level().getChunk(x, z);
|
||||
final int chunkBlockX = x << 4;
|
||||
final int chunkBlockZ = z << 4;
|
||||
for (int i = 0; i < blockEntityInfos.size(); i++) {
|
||||
@ -92,7 +92,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockEntity blockEntity = player.level.getBlockEntity(new BlockPos(x, y, z));
|
||||
BlockEntity blockEntity = player.level().getBlockEntity(new BlockPos(x, y, z));
|
||||
sendLecternData(session, blockEntity, false);
|
||||
});
|
||||
}
|
||||
@ -166,7 +166,7 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
|
||||
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
// Don't create a new block entity if invalid
|
||||
BlockEntity blockEntity = player.level.getChunkAt(pos).getBlockEntity(pos);
|
||||
BlockEntity blockEntity = player.level().getChunkAt(pos).getBlockEntity(pos);
|
||||
if (blockEntity instanceof BannerBlockEntity banner) {
|
||||
// Potentially exposes other NBT data? But we need to get the NBT data for the banner patterns *and*
|
||||
// the banner might have a custom name, both of which a Java client knows and caches
|
||||
|
@ -23,9 +23,9 @@
|
||||
"geyser-fabric.mixins.json"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.8",
|
||||
"fabricloader": ">=0.14.21",
|
||||
"fabric": "*",
|
||||
"minecraft": ">=1.19",
|
||||
"minecraft": ">=1.20",
|
||||
"fabric-permissions-api-v0": "*"
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ platformRelocate("com.fasterxml.jackson")
|
||||
platformRelocate("net.kyori", "net.kyori.adventure.text.logger.slf4j.ComponentLogger")
|
||||
platformRelocate("org.objectweb.asm")
|
||||
platformRelocate("me.lucko.commodore")
|
||||
platformRelocate("org.yaml") // Broken as of 1.20
|
||||
|
||||
// These dependencies are already present on the platform
|
||||
provided(libs.viaversion)
|
||||
@ -42,7 +43,6 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
|
||||
dependencies {
|
||||
exclude(dependency("com.google.*:.*"))
|
||||
exclude(dependency("org.yaml:.*"))
|
||||
|
||||
// We cannot shade Netty, or else native libraries will not load
|
||||
// Needed because older Spigot builds do not provide the haproxy module
|
||||
@ -64,4 +64,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
|
||||
// Commodore includes Brigadier
|
||||
exclude(dependency("com.mojang:.*"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
@ -27,7 +27,7 @@ package org.geysermc.geyser.platform.sponge;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
|
@ -7,10 +7,8 @@ dependencies {
|
||||
api(projects.core)
|
||||
|
||||
implementation(libs.terminalconsoleappender) {
|
||||
exclude("org.apache.logging.log4j", "log4j-core")
|
||||
exclude("org.jline", "jline-reader")
|
||||
exclude("org.jline", "jline-terminal")
|
||||
exclude("org.jline", "jline-terminal-jna")
|
||||
exclude("org.apache.logging.log4j")
|
||||
exclude("org.jline")
|
||||
}
|
||||
|
||||
implementation(libs.bundles.jline)
|
||||
|
@ -38,7 +38,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.Appender;
|
||||
import org.apache.logging.log4j.core.Logger;
|
||||
import org.apache.logging.log4j.core.appender.ConsoleAppender;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
@ -181,14 +181,14 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
this.geyserLogger = new GeyserStandaloneLogger();
|
||||
|
||||
if (useGui && gui == null) {
|
||||
gui = new GeyserStandaloneGUI();
|
||||
gui = new GeyserStandaloneGUI(geyserLogger);
|
||||
gui.redirectSystemStreams();
|
||||
gui.startUpdateThread();
|
||||
}
|
||||
|
||||
geyserLogger = new GeyserStandaloneLogger();
|
||||
|
||||
LoopbackUtil.checkAndApplyLoopback(geyserLogger);
|
||||
|
||||
try {
|
||||
@ -224,7 +224,7 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
|
||||
geyserCommandManager.init();
|
||||
|
||||
if (gui != null) {
|
||||
gui.setupInterface(geyserLogger, geyserCommandManager);
|
||||
gui.enableCommands(geyser.getScheduledThread(), geyserCommandManager);
|
||||
}
|
||||
|
||||
geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
|
||||
|
@ -92,6 +92,7 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
|
||||
Configurator.setLevel(log.getName(), debug ? Level.DEBUG : Level.INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return log.isDebugEnabled();
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public class ColorPane extends JTextPane {
|
||||
while (stillSearching) {
|
||||
mIndex = addString.indexOf("m", aPos); // find the end of the escape sequence
|
||||
if (mIndex < 0) { // the buffer ends halfway through the ansi string!
|
||||
remaining = addString.substring(aPos, addString.length());
|
||||
remaining = addString.substring(aPos);
|
||||
stillSearching = false;
|
||||
continue;
|
||||
} else {
|
||||
@ -99,7 +99,7 @@ public class ColorPane extends JTextPane {
|
||||
aIndex = addString.indexOf("\u001B", aPos);
|
||||
|
||||
if (aIndex == -1) { // if that was the last sequence of the input, send remaining text
|
||||
tmpString = addString.substring(aPos, addString.length());
|
||||
tmpString = addString.substring(aPos);
|
||||
append(colorCurrent, tmpString);
|
||||
stillSearching = false;
|
||||
continue; // jump out of loop early, as the whole string has been sent now
|
||||
|
@ -26,10 +26,8 @@
|
||||
package org.geysermc.geyser.platform.standalone.gui;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.platform.standalone.GeyserStandaloneLogger;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
|
||||
@ -45,28 +43,37 @@ import java.io.PrintStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class GeyserStandaloneGUI {
|
||||
|
||||
private static final DefaultTableModel playerTableModel = new DefaultTableModel();
|
||||
private static final List<Integer> ramValues = new ArrayList<>();
|
||||
|
||||
private static final ColorPane consolePane = new ColorPane();
|
||||
private static final GraphPanel ramGraph = new GraphPanel();
|
||||
private static final JTable playerTable = new JTable(playerTableModel);
|
||||
private static final int originalFontSize = consolePane.getFont().getSize();
|
||||
|
||||
private static final long MEGABYTE = 1024L * 1024L;
|
||||
|
||||
private final JMenu commandsMenu;
|
||||
private final JMenu optionsMenu;
|
||||
private final GeyserLogger logger;
|
||||
|
||||
private final ColorPane consolePane = new ColorPane();
|
||||
private final int originalFontSize = consolePane.getFont().getSize();
|
||||
private final JTextField commandInput = new JTextField();
|
||||
private final CommandListener commandListener = new CommandListener();
|
||||
|
||||
private final GraphPanel ramGraph = new GraphPanel();
|
||||
private final List<Integer> ramValues = new ArrayList<>();
|
||||
|
||||
private final DefaultTableModel playerTableModel = new DefaultTableModel();
|
||||
private final JTable playerTable = new JTable(playerTableModel);
|
||||
|
||||
/**
|
||||
* Create and show the Geyser-Standalone GUI
|
||||
*
|
||||
* @param logger the logger for determining debug mode, and executing commands from the console
|
||||
*/
|
||||
public GeyserStandaloneGUI(GeyserLogger logger) {
|
||||
this.logger = logger;
|
||||
|
||||
public GeyserStandaloneGUI() {
|
||||
// Create the frame and setup basic settings
|
||||
JFrame frame = new JFrame(GeyserLocale.getLocaleStringLog("geyser.gui.title"));
|
||||
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
@ -81,8 +88,7 @@ public class GeyserStandaloneGUI {
|
||||
// Show a confirm dialog on close
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent we)
|
||||
{
|
||||
public void windowClosing(WindowEvent we) {
|
||||
String[] buttons = {GeyserLocale.getLocaleStringLog("geyser.gui.exit.confirm"), GeyserLocale.getLocaleStringLog("geyser.gui.exit.deny")};
|
||||
int result = JOptionPane.showOptionDialog(frame, GeyserLocale.getLocaleStringLog("geyser.gui.exit.message"), frame.getTitle(), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttons, buttons[1]);
|
||||
if (result == JOptionPane.YES_OPTION) {
|
||||
@ -100,91 +106,41 @@ public class GeyserStandaloneGUI {
|
||||
frame.setIconImage(icon.getImage());
|
||||
}
|
||||
|
||||
// File, View, Options, etc
|
||||
setupMenuBar(frame);
|
||||
|
||||
// Setup the split pane and event listeners
|
||||
JSplitPane splitPane = new JSplitPane();
|
||||
splitPane.setDividerLocation(600);
|
||||
splitPane.addPropertyChangeListener("dividerLocation", e -> splitPaneLimit((JSplitPane)e.getSource()));
|
||||
splitPane.addPropertyChangeListener("dividerLocation", e -> splitPaneLimit((JSplitPane) e.getSource()));
|
||||
splitPane.addComponentListener(new ComponentAdapter() {
|
||||
public void componentResized(ComponentEvent e) {
|
||||
splitPaneLimit((JSplitPane)e.getSource());
|
||||
splitPaneLimit((JSplitPane) e.getSource());
|
||||
}
|
||||
});
|
||||
|
||||
cp.add(splitPane, BorderLayout.CENTER);
|
||||
|
||||
// Set the background and disable input for the text pane
|
||||
// Holds console and command input components
|
||||
JPanel leftPane = new JPanel(new BorderLayout());
|
||||
splitPane.setLeftComponent(leftPane);
|
||||
|
||||
// Set the background and disable editing of the console
|
||||
consolePane.setBackground(Color.BLACK);
|
||||
consolePane.setEditable(false);
|
||||
|
||||
// Wrap the text pane in a scroll pane and add it to the form
|
||||
JScrollPane consoleScrollPane = new JScrollPane(consolePane);
|
||||
//cp.add(consoleScrollPane, BorderLayout.CENTER);
|
||||
splitPane.setLeftComponent(consoleScrollPane);
|
||||
leftPane.add(consoleScrollPane, BorderLayout.CENTER);
|
||||
|
||||
// Create a new menu bar for the top of the frame
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
// Create 'File'
|
||||
JMenu fileMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file"));
|
||||
fileMenu.setMnemonic(KeyEvent.VK_F);
|
||||
menuBar.add(fileMenu);
|
||||
|
||||
// 'Open Geyser folder' button
|
||||
JMenuItem openButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O);
|
||||
openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
|
||||
openButton.addActionListener(e -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(new File("./"));
|
||||
} catch (IOException ignored) { }
|
||||
});
|
||||
fileMenu.add(openButton);
|
||||
|
||||
fileMenu.addSeparator();
|
||||
|
||||
// 'Exit' button
|
||||
JMenuItem exitButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X);
|
||||
exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_MASK));
|
||||
exitButton.addActionListener(e -> System.exit(0));
|
||||
fileMenu.add(exitButton);
|
||||
|
||||
// Create 'Commands'
|
||||
commandsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.commands"));
|
||||
commandsMenu.setMnemonic(KeyEvent.VK_C);
|
||||
menuBar.add(commandsMenu);
|
||||
|
||||
// Create 'View'
|
||||
JMenu viewMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view"));
|
||||
viewMenu.setMnemonic(KeyEvent.VK_V);
|
||||
menuBar.add(viewMenu);
|
||||
|
||||
// 'Zoom in' button
|
||||
JMenuItem zoomInButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_in"));
|
||||
zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK));
|
||||
zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1)));
|
||||
viewMenu.add(zoomInButton);
|
||||
|
||||
// 'Zoom in' button
|
||||
JMenuItem zoomOutButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_out"));
|
||||
zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
|
||||
zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1)));
|
||||
viewMenu.add(zoomOutButton);
|
||||
|
||||
// 'Reset Zoom' button
|
||||
JMenuItem resetZoomButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.reset_zoom"));
|
||||
resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize)));
|
||||
viewMenu.add(resetZoomButton);
|
||||
|
||||
// create 'Options'
|
||||
optionsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options"));
|
||||
viewMenu.setMnemonic(KeyEvent.VK_O);
|
||||
menuBar.add(optionsMenu);
|
||||
|
||||
// Set the frames menu bar
|
||||
frame.setJMenuBar(menuBar);
|
||||
// a bit taller than the default layout - width is ignored fortunately
|
||||
commandInput.setPreferredSize(new Dimension(100, 25));
|
||||
commandInput.setEnabled(false); // disabled until command handler is initialized
|
||||
commandInput.addActionListener(commandListener);
|
||||
leftPane.add(commandInput, BorderLayout.SOUTH);
|
||||
|
||||
JPanel rightPane = new JPanel();
|
||||
rightPane.setLayout(new CardLayout(5, 5));
|
||||
//cp.add(rightPane, BorderLayout.EAST);
|
||||
splitPane.setRightComponent(rightPane);
|
||||
|
||||
JPanel rightContentPane = new JPanel();
|
||||
@ -209,12 +165,75 @@ public class GeyserStandaloneGUI {
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
private void setupMenuBar(JFrame frame) {
|
||||
// Create a new menu bar for the top of the frame
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
// Create 'File'
|
||||
JMenu fileMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file"));
|
||||
fileMenu.setMnemonic(KeyEvent.VK_F);
|
||||
menuBar.add(fileMenu);
|
||||
|
||||
// 'Open Geyser folder' button
|
||||
JMenuItem openButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.open_folder"), KeyEvent.VK_O);
|
||||
openButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK));
|
||||
openButton.addActionListener(e -> {
|
||||
try {
|
||||
Desktop.getDesktop().open(new File("./"));
|
||||
} catch (IOException ignored) { }
|
||||
});
|
||||
fileMenu.add(openButton);
|
||||
|
||||
fileMenu.addSeparator();
|
||||
|
||||
// 'Exit' button
|
||||
JMenuItem exitButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.file.exit"), KeyEvent.VK_X);
|
||||
exitButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK));
|
||||
exitButton.addActionListener(e -> System.exit(0));
|
||||
fileMenu.add(exitButton);
|
||||
|
||||
// Create 'View'
|
||||
JMenu viewMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view"));
|
||||
viewMenu.setMnemonic(KeyEvent.VK_V);
|
||||
menuBar.add(viewMenu);
|
||||
|
||||
// 'Zoom in' button
|
||||
JMenuItem zoomInButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_in"));
|
||||
zoomInButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.CTRL_DOWN_MASK));
|
||||
zoomInButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() + 1)));
|
||||
viewMenu.add(zoomInButton);
|
||||
|
||||
// 'Zoom in' button
|
||||
JMenuItem zoomOutButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.zoom_out"));
|
||||
zoomOutButton.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
|
||||
zoomOutButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), consolePane.getFont().getSize() - 1)));
|
||||
viewMenu.add(zoomOutButton);
|
||||
|
||||
// 'Reset Zoom' button
|
||||
JMenuItem resetZoomButton = new JMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.view.reset_zoom"));
|
||||
resetZoomButton.addActionListener(e -> consolePane.setFont(new Font(consolePane.getFont().getName(), consolePane.getFont().getStyle(), originalFontSize)));
|
||||
viewMenu.add(resetZoomButton);
|
||||
|
||||
// create 'Options'
|
||||
JMenu optionsMenu = new JMenu(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options"));
|
||||
viewMenu.setMnemonic(KeyEvent.VK_O);
|
||||
menuBar.add(optionsMenu);
|
||||
|
||||
JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode"));
|
||||
debugMode.setSelected(logger.isDebug());
|
||||
debugMode.addActionListener(e -> logger.setDebug(debugMode.getState()));
|
||||
optionsMenu.add(debugMode);
|
||||
|
||||
// Set the frames menu bar
|
||||
frame.setJMenuBar(menuBar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue up an update to the text pane so we don't block the main thread
|
||||
*
|
||||
* @param text The text to append
|
||||
*/
|
||||
private void updateTextPane(final String text) {
|
||||
private void appendConsole(final String text) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
consolePane.appendANSI(text);
|
||||
Document doc = consolePane.getDocument();
|
||||
@ -230,12 +249,12 @@ public class GeyserStandaloneGUI {
|
||||
OutputStream out = new OutputStream() {
|
||||
@Override
|
||||
public void write(final int b) {
|
||||
updateTextPane(String.valueOf((char) b));
|
||||
appendConsole(String.valueOf((char) b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
updateTextPane(new String(b, off, len));
|
||||
appendConsole(new String(b, off, len));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -251,50 +270,17 @@ public class GeyserStandaloneGUI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all the Geyser commands to the commands menu, and setup the debug mode toggle
|
||||
* Enable the command input box.
|
||||
*
|
||||
* @param geyserStandaloneLogger The current logger
|
||||
* @param geyserCommandManager The commands manager
|
||||
* @param executor the executor for running commands off the GUI thread
|
||||
* @param commandManager the command manager to delegate commands to
|
||||
*/
|
||||
public void setupInterface(GeyserStandaloneLogger geyserStandaloneLogger, GeyserCommandManager geyserCommandManager) {
|
||||
commandsMenu.removeAll();
|
||||
optionsMenu.removeAll();
|
||||
|
||||
for (Map.Entry<String, Command> entry : geyserCommandManager.getCommands().entrySet()) {
|
||||
// Remove the offhand command and any alias commands to prevent duplicates in the list
|
||||
if (!entry.getValue().isExecutableOnConsole() || entry.getValue().aliases().contains(entry.getKey())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
GeyserCommand command = (GeyserCommand) entry.getValue();
|
||||
// Create the button that runs the command
|
||||
boolean hasSubCommands = !entry.getValue().subCommands().isEmpty();
|
||||
// Add an extra menu if there are more commands that can be run
|
||||
JMenuItem commandButton = hasSubCommands ? new JMenu(entry.getValue().name()) : new JMenuItem(entry.getValue().name());
|
||||
commandButton.getAccessibleContext().setAccessibleDescription(entry.getValue().description());
|
||||
if (!hasSubCommands) {
|
||||
commandButton.addActionListener(e -> command.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(entry.getValue().name());
|
||||
otherCommandButton.getAccessibleContext().setAccessibleDescription(entry.getValue().description());
|
||||
otherCommandButton.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{ }));
|
||||
commandButton.add(otherCommandButton);
|
||||
// Add a menu option for all possible subcommands
|
||||
for (String subCommandName : entry.getValue().subCommands()) {
|
||||
JMenuItem item = new JMenuItem(subCommandName);
|
||||
item.addActionListener(e -> command.execute(null, geyserStandaloneLogger, new String[]{subCommandName}));
|
||||
commandButton.add(item);
|
||||
}
|
||||
}
|
||||
commandsMenu.add(commandButton);
|
||||
}
|
||||
|
||||
// 'Debug Mode' toggle
|
||||
JCheckBoxMenuItem debugMode = new JCheckBoxMenuItem(GeyserLocale.getLocaleStringLog("geyser.gui.menu.options.toggle_debug_mode"));
|
||||
debugMode.setSelected(geyserStandaloneLogger.isDebug());
|
||||
debugMode.addActionListener(e -> geyserStandaloneLogger.setDebug(!geyserStandaloneLogger.isDebug()));
|
||||
optionsMenu.add(debugMode);
|
||||
public void enableCommands(ScheduledExecutorService executor, GeyserCommandManager commandManager) {
|
||||
// we don't want to block the GUI thread with the command execution
|
||||
// todo: once cloud is used, an AsynchronousCommandExecutionCoordinator can be used to avoid this scheduler
|
||||
commandListener.handler = cmd -> executor.schedule(() -> commandManager.runCommand(logger, cmd), 0, TimeUnit.SECONDS);
|
||||
commandInput.setEnabled(true);
|
||||
commandInput.requestFocusInWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -322,14 +308,14 @@ public class GeyserStandaloneGUI {
|
||||
// Update ram graph
|
||||
final long freeMemory = Runtime.getRuntime().freeMemory();
|
||||
final long totalMemory = Runtime.getRuntime().totalMemory();
|
||||
final int freePercent = (int)(freeMemory * 100.0 / totalMemory + 0.5);
|
||||
final int freePercent = (int) (freeMemory * 100.0 / totalMemory + 0.5);
|
||||
ramValues.add(100 - freePercent);
|
||||
|
||||
ramGraph.setXLabel(GeyserLocale.getLocaleStringLog("geyser.gui.graph.usage", String.format("%,d", (totalMemory - freeMemory) / MEGABYTE), freePercent));
|
||||
|
||||
// Trim the list
|
||||
int k = ramValues.size();
|
||||
if ( k > 10 )
|
||||
if (k > 10)
|
||||
ramValues.subList(0, k - 10).clear();
|
||||
|
||||
// Update the graph
|
||||
@ -354,4 +340,17 @@ public class GeyserStandaloneGUI {
|
||||
splitPane.setDividerLocation(frame.getWidth() - 200);
|
||||
}
|
||||
}
|
||||
|
||||
private class CommandListener implements ActionListener {
|
||||
|
||||
private Consumer<String> handler;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
String command = commandInput.getText();
|
||||
appendConsole(command + "\n"); // show what was run in the console
|
||||
handler.accept(command); // run the command
|
||||
commandInput.setText(""); // clear the input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<TerminalConsole name="TerminalConsole">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
|
||||
</TerminalConsole>
|
||||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red dark, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %style{%highlight{%level}{FATAL=red, ERROR=red, WARN=yellow bright, INFO=cyan bright, DEBUG=green, TRACE=white}}] %minecraftFormatting{%msg}%n"/>
|
||||
</Console>
|
||||
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss.SSS} %t/%level] %minecraftFormatting{%msg}{strip}%n"/>
|
||||
|
@ -36,7 +36,7 @@ import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.util.Codec;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserBootstrap;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
|
@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "org.geysermc.geyser"
|
||||
version = "2.1.0-SNAPSHOT"
|
||||
version = "2.1.1-SNAPSHOT"
|
||||
description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers."
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
|
@ -30,7 +30,7 @@ dependencies {
|
||||
}
|
||||
|
||||
implementation(libs.raknet) {
|
||||
exclude("io.netty", "*");
|
||||
exclude("io.netty", "*")
|
||||
}
|
||||
|
||||
implementation(libs.netty.resolver.dns)
|
||||
|
@ -26,7 +26,7 @@
|
||||
package org.geysermc.connector;
|
||||
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
|
@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.api.Geyser;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.erosion.packet.Packets;
|
||||
@ -69,9 +69,9 @@ import org.geysermc.geyser.event.GeyserEventBus;
|
||||
import org.geysermc.geyser.extension.GeyserExtensionManager;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.network.netty.GeyserServer;
|
||||
import org.geysermc.geyser.pack.ResourcePack;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||
import org.geysermc.geyser.scoreboard.ScoreboardUpdater;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.PendingMicrosoftAuthentication;
|
||||
@ -90,6 +90,7 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.*;
|
||||
@ -217,25 +218,9 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
GeyserConfiguration config = bootstrap.getGeyserConfig();
|
||||
|
||||
boolean isGui = false;
|
||||
// This will check if we are in standalone and get the 'useGui' variable from there
|
||||
if (platformType == PlatformType.STANDALONE) {
|
||||
try {
|
||||
Class<?> cls = Class.forName("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap");
|
||||
isGui = (boolean) cls.getMethod("isUseGui").invoke(cls.cast(bootstrap));
|
||||
} catch (Exception e) {
|
||||
logger.debug("Failed detecting if standalone is using a GUI; if this is a GeyserConnect instance this can be safely ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;
|
||||
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime)) + " ";
|
||||
if (isGui) {
|
||||
message += GeyserLocale.getLocaleStringLog("geyser.core.finish.gui");
|
||||
} else {
|
||||
message += GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
|
||||
}
|
||||
|
||||
String message = GeyserLocale.getLocaleStringLog("geyser.core.finish.done", new DecimalFormat("#.###").format(completeTime));
|
||||
message += " " + GeyserLocale.getLocaleStringLog("geyser.core.finish.console");
|
||||
logger.info(message);
|
||||
|
||||
if (platformType == PlatformType.STANDALONE) {
|
||||
@ -258,7 +243,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
|
||||
SkinProvider.registerCacheImageTask(this);
|
||||
|
||||
ResourcePack.loadPacks();
|
||||
Registries.RESOURCE_PACKS.load();
|
||||
|
||||
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
|
||||
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
|
||||
@ -410,7 +395,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("players", sessionManager::size));
|
||||
// Prevent unwanted words best we can
|
||||
metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> config.getRemote().authType().toString().toLowerCase(Locale.ROOT)));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::getPlatformName));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("platform", platformType::platformName));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("defaultLocale", GeyserLocale::getDefaultLocale));
|
||||
metrics.addCustomChart(new Metrics.SimplePie("version", () -> GeyserImpl.VERSION));
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("playerPlatform", () -> {
|
||||
@ -446,7 +431,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
if (minecraftVersion != null) {
|
||||
Map<String, Map<String, Integer>> versionMap = new HashMap<>();
|
||||
Map<String, Integer> platformMap = new HashMap<>();
|
||||
platformMap.put(platformType.getPlatformName(), 1);
|
||||
platformMap.put(platformType.platformName(), 1);
|
||||
versionMap.put(minecraftVersion, platformMap);
|
||||
|
||||
metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> {
|
||||
@ -622,7 +607,7 @@ public class GeyserImpl implements GeyserApi {
|
||||
this.erosionUnixListener.close();
|
||||
}
|
||||
|
||||
ResourcePack.PACKS.clear();
|
||||
Registries.RESOURCE_PACKS.get().clear();
|
||||
|
||||
this.eventBus.fire(new GeyserShutdownEvent(this.extensionManager, this.eventBus));
|
||||
this.extensionManager.disableExtensions();
|
||||
@ -681,6 +666,24 @@ public class GeyserImpl implements GeyserApi {
|
||||
return getConfig().getBedrock();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Path configDirectory() {
|
||||
return bootstrap.getConfigFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Path packDirectory() {
|
||||
return bootstrap.getConfigFolder().resolve("packs");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public PlatformType platformType() {
|
||||
return platformType;
|
||||
}
|
||||
|
||||
public int buildNumber() {
|
||||
if (!this.isProductionEnvironment()) {
|
||||
return 0;
|
||||
|
@ -29,7 +29,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.api.command.CommandExecutor;
|
||||
|
@ -26,7 +26,7 @@
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
@ -30,7 +30,7 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.command.Command;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
@ -26,7 +26,7 @@
|
||||
package org.geysermc.geyser.command.defaults;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.command.GeyserCommand;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
|
@ -27,7 +27,7 @@ package org.geysermc.geyser.dump;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.text.AsteriskSerializer;
|
||||
|
||||
|
@ -104,6 +104,7 @@ public final class EntityDefinitions {
|
||||
public static final EntityDefinition<HorseEntity> HORSE;
|
||||
public static final EntityDefinition<ZombieEntity> HUSK;
|
||||
public static final EntityDefinition<SpellcasterIllagerEntity> ILLUSIONER; // Not present on Bedrock
|
||||
public static final EntityDefinition<InteractionEntity> INTERACTION;
|
||||
public static final EntityDefinition<IronGolemEntity> IRON_GOLEM;
|
||||
public static final EntityDefinition<ItemEntity> ITEM;
|
||||
public static final EntityDefinition<ItemFrameEntity> ITEM_FRAME;
|
||||
@ -133,6 +134,7 @@ public final class EntityDefinitions {
|
||||
public static final EntityDefinition<AbstractFishEntity> SALMON;
|
||||
public static final EntityDefinition<SheepEntity> SHEEP;
|
||||
public static final EntityDefinition<ShulkerEntity> SHULKER;
|
||||
public static final EntityDefinition<SnifferEntity> SNIFFER;
|
||||
public static final EntityDefinition<ThrowableEntity> SHULKER_BULLET;
|
||||
public static final EntityDefinition<MonsterEntity> SILVERFISH;
|
||||
public static final EntityDefinition<SkeletonEntity> SKELETON;
|
||||
@ -235,7 +237,7 @@ public final class EntityDefinitions {
|
||||
.type(EntityType.EXPERIENCE_ORB)
|
||||
.identifier("minecraft:xp_orb")
|
||||
.build();
|
||||
EVOKER_FANGS = EntityDefinition.builder(EvokerFangsEntity::new) // No entity metadata to listen to as of 1.18.1
|
||||
EVOKER_FANGS = EntityDefinition.inherited(EvokerFangsEntity::new, entityBase)
|
||||
.type(EntityType.EVOKER_FANGS)
|
||||
.height(0.8f).width(0.5f)
|
||||
.identifier("minecraft:evocation_fang")
|
||||
@ -318,6 +320,15 @@ public final class EntityDefinitions {
|
||||
.addTranslator(MetadataType.CHAT, TextDisplayEntity::setText)
|
||||
.build();
|
||||
|
||||
INTERACTION = EntityDefinition.inherited(InteractionEntity::new, entityBase)
|
||||
.type(EntityType.INTERACTION)
|
||||
.heightAndWidth(1.0f) // default size until server specifies otherwise
|
||||
.identifier("minecraft:armor_stand")
|
||||
.addTranslator(MetadataType.FLOAT, InteractionEntity::setWidth)
|
||||
.addTranslator(MetadataType.FLOAT, InteractionEntity::setHeight)
|
||||
.addTranslator(MetadataType.BOOLEAN, InteractionEntity::setResponse)
|
||||
.build();
|
||||
|
||||
EntityDefinition<FireballEntity> fireballBase = EntityDefinition.inherited(FireballEntity::new, entityBase)
|
||||
.addTranslator(null) // Item
|
||||
.build();
|
||||
@ -842,6 +853,12 @@ public final class EntityDefinitions {
|
||||
.height(1.3f).width(0.9f)
|
||||
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
|
||||
.build();
|
||||
SNIFFER = EntityDefinition.inherited(SnifferEntity::new, ageableEntityBase)
|
||||
.type(EntityType.SNIFFER)
|
||||
.height(1.75f).width(1.9f)
|
||||
.addTranslator(MetadataType.SNIFFER_STATE, SnifferEntity::setSnifferState)
|
||||
.addTranslator(null) // Integer, drop seed at tick
|
||||
.build();
|
||||
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
|
||||
.type(EntityType.STRIDER)
|
||||
.height(1.7f).width(0.9f)
|
||||
@ -884,7 +901,6 @@ public final class EntityDefinitions {
|
||||
.build();
|
||||
CAMEL = EntityDefinition.inherited(CamelEntity::new, abstractHorseEntityBase)
|
||||
.type(EntityType.CAMEL)
|
||||
.identifier("minecraft:llama") // todo 1.20
|
||||
.height(2.375f).width(1.7f)
|
||||
.addTranslator(MetadataType.BOOLEAN, CamelEntity::setDashing)
|
||||
.addTranslator(null) // Last pose change tick
|
||||
|
@ -125,8 +125,8 @@ public class BoatEntity extends Entity {
|
||||
public void setVariant(IntEntityMetadata entityMetadata) {
|
||||
variant = entityMetadata.getPrimitiveValue();
|
||||
dirtyMetadata.put(EntityDataTypes.VARIANT, switch (variant) {
|
||||
case 6, 7 -> variant - 1; // Dark oak and mangrove
|
||||
case 5, 8 -> 0; // TODO temp until 1.20. Cherry and bamboo
|
||||
case 6, 7, 8 -> variant - 1; // dark_oak, mangrove, bamboo
|
||||
case 5 -> 8; // cherry
|
||||
default -> variant;
|
||||
});
|
||||
}
|
||||
|
@ -493,9 +493,10 @@ public class Entity implements GeyserEntity {
|
||||
* Update the mount offsets of each passenger on this vehicle
|
||||
*/
|
||||
protected void updatePassengerOffsets() {
|
||||
for (Entity passenger : passengers) {
|
||||
for (int i = 0; i < passengers.size(); i++) {
|
||||
Entity passenger = passengers.get(i);
|
||||
if (passenger != null) {
|
||||
boolean rider = passengers.get(0) == this;
|
||||
boolean rider = i == 0;
|
||||
EntityUtils.updateMountOffset(passenger, this, rider, true, passengers.size() > 1);
|
||||
passenger.updateBedrockMetadata();
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class InteractionEntity extends Entity {
|
||||
|
||||
/**
|
||||
* true - java client hears swing sound when attacking, and arm swings when right-clicking
|
||||
* false - java client hears no swing sound when attacking, and arm does not swing when right-clicking
|
||||
*/
|
||||
private boolean response = false;
|
||||
|
||||
public InteractionEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
|
||||
// hide the armor stand but keep the hitbox active
|
||||
setFlag(EntityFlag.INVISIBLE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult interact(Hand hand) {
|
||||
// these InteractionResults do mirror the java client
|
||||
// but the bedrock client won't arm swing itself because of our armor stand workaround
|
||||
if (response) {
|
||||
AnimatePacket animatePacket = new AnimatePacket();
|
||||
animatePacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
animatePacket.setAction(AnimatePacket.Action.SWING_ARM);
|
||||
session.sendUpstreamPacket(animatePacket);
|
||||
|
||||
session.sendDownstreamPacket(new ServerboundSwingPacket(hand));
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
|
||||
public void setWidth(FloatEntityMetadata width) {
|
||||
setBoundingBoxWidth(width.getPrimitiveValue());
|
||||
}
|
||||
|
||||
public void setHeight(FloatEntityMetadata height) {
|
||||
setBoundingBoxHeight(height.getPrimitiveValue());
|
||||
}
|
||||
|
||||
public void setResponse(BooleanEntityMetadata response) {
|
||||
this.response = response.getPrimitiveValue();
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.entity.type.living.animal;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.SnifferState;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Tickable;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SnifferEntity extends AnimalEntity implements Tickable {
|
||||
private static final float DIGGING_HEIGHT = EntityDefinitions.SNIFFER.height() - 0.4f;
|
||||
private static final int DIG_END = 120;
|
||||
private static final int DIG_START = DIG_END - 34;
|
||||
|
||||
private Pose pose = Pose.STANDING; // Needed to call setDimensions for DIGGING state
|
||||
private int digTicks;
|
||||
|
||||
public SnifferEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
this.pose = pose;
|
||||
super.setPose(pose);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDimensions(Pose pose) {
|
||||
if (getFlag(EntityFlag.DIGGING)) {
|
||||
setBoundingBoxHeight(DIGGING_HEIGHT);
|
||||
setBoundingBoxWidth(definition.width());
|
||||
} else {
|
||||
super.setDimensions(pose);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEat(Item item) {
|
||||
return session.getTagCache().isSnifferFood(item);
|
||||
}
|
||||
|
||||
public void setSnifferState(ObjectEntityMetadata<SnifferState> entityMetadata) {
|
||||
SnifferState snifferState = entityMetadata.getValue();
|
||||
|
||||
// SnifferState.SCENTING and SnifferState.IDLING not used in bedrock
|
||||
// The bedrock client does the scenting animation and sound on its own
|
||||
setFlag(EntityFlag.FEELING_HAPPY, snifferState == SnifferState.FEELING_HAPPY);
|
||||
setFlag(EntityFlag.SCENTING, snifferState == SnifferState.SNIFFING); // SnifferState.SNIFFING -> EntityFlag.SCENTING
|
||||
setFlag(EntityFlag.SEARCHING, snifferState == SnifferState.SEARCHING);
|
||||
setFlag(EntityFlag.DIGGING, snifferState == SnifferState.DIGGING);
|
||||
setFlag(EntityFlag.RISING, snifferState == SnifferState.RISING);
|
||||
|
||||
setDimensions(pose);
|
||||
|
||||
if (getFlag(EntityFlag.DIGGING)) {
|
||||
digTicks = DIG_END;
|
||||
} else {
|
||||
// Handles situations where the DIGGING state is exited earlier than expected,
|
||||
// such as hitting the sniffer or joining the game while it is digging
|
||||
digTicks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
// The java client renders digging particles on its own, but bedrock does not
|
||||
if (digTicks > 0 && --digTicks < DIG_START && digTicks % 5 == 0) {
|
||||
Vector3f rot = Vector3f.createDirectionDeg(0, -getYaw()).mul(2.25f);
|
||||
Vector3f pos = getPosition().add(rot).up(0.2f).floor(); // Handle non-full blocks
|
||||
int blockId = session.getBlockMappings().getBedrockBlockId(session.getGeyser().getWorldManager().getBlockAt(session, pos.toInt().down()));
|
||||
|
||||
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
||||
levelEventPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK_NO_SOUND);
|
||||
levelEventPacket.setPosition(pos);
|
||||
levelEventPacket.setData(blockId);
|
||||
session.sendUpstreamPacket(levelEventPacket);
|
||||
|
||||
if (digTicks % 10 == 0) {
|
||||
LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket();
|
||||
levelSoundEventPacket.setSound(SoundEvent.HIT);
|
||||
levelSoundEventPacket.setPosition(pos);
|
||||
levelSoundEventPacket.setExtraData(blockId);
|
||||
levelSoundEventPacket.setIdentifier(":");
|
||||
session.sendUpstreamPacket(levelSoundEventPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,8 +27,13 @@ package org.geysermc.geyser.entity.type.living.animal.horse;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
@ -38,16 +43,50 @@ import java.util.UUID;
|
||||
|
||||
public class CamelEntity extends AbstractHorseEntity {
|
||||
|
||||
private static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
|
||||
public static final float SITTING_HEIGHT_DIFFERENCE = 1.43F;
|
||||
|
||||
public CamelEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
|
||||
|
||||
dirtyMetadata.put(EntityDataTypes.CONTAINER_TYPE, (byte) ContainerType.HORSE.getId());
|
||||
|
||||
// Always tamed, but not indicated in horse flags
|
||||
setFlag(EntityFlag.TAMED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
this.dirtyMetadata.put(EntityDataTypes.VARIANT, 2); // Closest llama colour to camel
|
||||
public void setHorseFlags(ByteEntityMetadata entityMetadata) {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
boolean saddled = (xd & 0x04) == 0x04;
|
||||
setFlag(EntityFlag.SADDLED, saddled);
|
||||
setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10);
|
||||
setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20);
|
||||
|
||||
// HorseFlags
|
||||
// Bred 0x10
|
||||
// Eating 0x20
|
||||
// Open mouth 0x80
|
||||
int horseFlags = 0x0;
|
||||
horseFlags = (xd & 0x40) == 0x40 ? horseFlags | 0x80 : horseFlags;
|
||||
|
||||
// Only set eating when we don't have mouth open so a player interaction doesn't trigger the eating animation
|
||||
horseFlags = (xd & 0x10) == 0x10 && (xd & 0x40) != 0x40 ? horseFlags | 0x20 : horseFlags;
|
||||
|
||||
// Set the flags into the horse flags
|
||||
dirtyMetadata.put(EntityDataTypes.HORSE_FLAGS, horseFlags);
|
||||
|
||||
// Send the eating particles
|
||||
// We use the wheat metadata as static particles since Java
|
||||
// doesn't send over what item was used to feed the horse
|
||||
if ((xd & 0x40) == 0x40) {
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setRuntimeEntityId(geyserId);
|
||||
entityEventPacket.setType(EntityEventType.EATING_ITEM);
|
||||
entityEventPacket.setData(session.getItemMappings().getStoredItems().wheat().getBedrockDefinition().getRuntimeId() << 16);
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
}
|
||||
|
||||
// Shows the dash meter
|
||||
setFlag(EntityFlag.CAN_DASH, saddled);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -55,10 +94,16 @@ public class CamelEntity extends AbstractHorseEntity {
|
||||
return item == Items.CACTUS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPose(Pose pose) {
|
||||
setFlag(EntityFlag.SITTING, pose == Pose.SITTING);
|
||||
super.setPose(pose);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDimensions(Pose pose) {
|
||||
if (pose == Pose.SITTING) {
|
||||
setBoundingBoxWidth(definition.height() - SITTING_HEIGHT_DIFFERENCE);
|
||||
setBoundingBoxHeight(definition.height() - SITTING_HEIGHT_DIFFERENCE);
|
||||
setBoundingBoxWidth(definition.width());
|
||||
} else {
|
||||
super.setDimensions(pose);
|
||||
|
@ -67,10 +67,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
*/
|
||||
@Getter
|
||||
private boolean isRidingInFront;
|
||||
/**
|
||||
* Used for villager inventory emulation.
|
||||
*/
|
||||
private int fakeTradeXp;
|
||||
|
||||
public SessionPlayerEntity(GeyserSession session) {
|
||||
super(session, -1, 1, null, Vector3f.ZERO, Vector3f.ZERO, 0, 0, 0, null, null);
|
||||
@ -175,11 +171,6 @@ public class SessionPlayerEntity extends PlayerEntity {
|
||||
this.isRidingInFront = position != null && position.getX() > 0;
|
||||
}
|
||||
|
||||
public void addFakeTradeExperience(int tradeXp) {
|
||||
fakeTradeXp += tradeXp;
|
||||
dirtyMetadata.put(EntityDataTypes.TRADE_EXPERIENCE, fakeTradeXp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeData createHealthAttribute() {
|
||||
// Max health must be divisible by two in bedrock
|
||||
|
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.event.type;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SessionLoadResourcePacksEventImpl extends SessionLoadResourcePacksEvent {
|
||||
|
||||
private final Map<String, ResourcePack> packs;
|
||||
|
||||
public SessionLoadResourcePacksEventImpl(GeyserSession session, Map<String, ResourcePack> packMap) {
|
||||
super(session);
|
||||
this.packs = packMap;
|
||||
}
|
||||
|
||||
public @NonNull Map<String, ResourcePack> getPacks() {
|
||||
return packs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<ResourcePack> resourcePacks() {
|
||||
return List.copyOf(packs.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean register(@NonNull ResourcePack resourcePack) {
|
||||
String packID = resourcePack.manifest().header().uuid().toString();
|
||||
if (packs.containsValue(resourcePack) || packs.containsKey(packID)) {
|
||||
return false;
|
||||
}
|
||||
packs.put(resourcePack.manifest().header().uuid().toString(), resourcePack);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unregister(@NonNull UUID uuid) {
|
||||
return packs.remove(uuid.toString()) != null;
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.extension.event;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.event.Event;
|
||||
import org.geysermc.event.FireResult;
|
||||
import org.geysermc.event.PostOrder;
|
||||
import org.geysermc.event.subscribe.Subscriber;
|
||||
import org.geysermc.geyser.api.event.EventBus;
|
||||
@ -47,10 +48,15 @@ public record GeyserExtensionEventBus(EventBus<EventRegistrar> eventBus, Extensi
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fire(@NonNull Event event) {
|
||||
public FireResult fire(@NonNull Event event) {
|
||||
return eventBus.fire(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FireResult fireSilently(@NonNull Event event) {
|
||||
return eventBus.fireSilently(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull <T extends Event> Set<? extends EventSubscriber<EventRegistrar, T>> subscribers(@NonNull Class<T> eventClass) {
|
||||
return eventBus.subscribers(eventClass);
|
||||
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.inventory.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
@ -40,6 +41,8 @@ public class MerchantContainer extends Container {
|
||||
private VillagerTrade[] villagerTrades;
|
||||
@Getter @Setter
|
||||
private ClientboundMerchantOffersPacket pendingOffersPacket;
|
||||
@Getter @Setter
|
||||
private int tradeExperience;
|
||||
|
||||
public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
super(title, id, size, containerType, playerInventory);
|
||||
@ -49,9 +52,10 @@ public class MerchantContainer extends Container {
|
||||
if (villagerTrades != null && slot >= 0 && slot < villagerTrades.length) {
|
||||
VillagerTrade trade = villagerTrades[slot];
|
||||
setItem(2, GeyserItemStack.from(trade.getOutput()), session);
|
||||
// TODO this logic doesn't add up
|
||||
session.getPlayerEntity().addFakeTradeExperience(trade.getXp());
|
||||
session.getPlayerEntity().updateBedrockMetadata();
|
||||
|
||||
tradeExperience += trade.getXp();
|
||||
villager.getDirtyMetadata().put(EntityDataTypes.TRADE_EXPERIENCE, tradeExperience);
|
||||
villager.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ public class StoredItemMappings {
|
||||
private final ItemMapping egg;
|
||||
private final ItemMapping shield;
|
||||
private final ItemMapping wheat;
|
||||
private final ItemMapping writableBook;
|
||||
|
||||
public StoredItemMappings(Map<Item, ItemMapping> itemMappings) {
|
||||
this.bamboo = load(itemMappings, Items.BAMBOO);
|
||||
@ -64,6 +65,7 @@ public class StoredItemMappings {
|
||||
this.egg = load(itemMappings, Items.EGG);
|
||||
this.shield = load(itemMappings, Items.SHIELD);
|
||||
this.wheat = load(itemMappings, Items.WHEAT);
|
||||
this.writableBook = load(itemMappings, Items.WRITABLE_BOOK);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.inventory.recipe;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimMaterial;
|
||||
import org.cloudburstmc.protocol.bedrock.data.TrimPattern;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Hardcoded recipe information about armor trims until further improvements can be made. This information was scraped
|
||||
* from BDS 1.19.81 with a world with the next_major_update and sniffer features enabled, using ProxyPass.
|
||||
*/
|
||||
public class TrimRecipe {
|
||||
|
||||
// For TrimDataPacket, which BDS sends just before the CraftingDataPacket
|
||||
public static final List<TrimPattern> PATTERNS;
|
||||
public static final List<TrimMaterial> MATERIALS;
|
||||
|
||||
// For CraftingDataPacket
|
||||
public static final String ID = "minecraft:smithing_armor_trim";
|
||||
public static final ItemDescriptorWithCount BASE = tagDescriptor("minecraft:trimmable_armors");
|
||||
public static final ItemDescriptorWithCount ADDITION = tagDescriptor("minecraft:trim_materials");
|
||||
public static final ItemDescriptorWithCount TEMPLATE = tagDescriptor("minecraft:trim_templates");
|
||||
|
||||
static {
|
||||
List<TrimPattern> patterns = new ArrayList<>(16);
|
||||
patterns.add(new TrimPattern("minecraft:ward_armor_trim_smithing_template", "ward"));
|
||||
patterns.add(new TrimPattern("minecraft:sentry_armor_trim_smithing_template", "sentry"));
|
||||
patterns.add(new TrimPattern("minecraft:snout_armor_trim_smithing_template", "snout"));
|
||||
patterns.add(new TrimPattern("minecraft:dune_armor_trim_smithing_template", "dune"));
|
||||
patterns.add(new TrimPattern("minecraft:spire_armor_trim_smithing_template", "spire"));
|
||||
patterns.add(new TrimPattern("minecraft:tide_armor_trim_smithing_template", "tide"));
|
||||
patterns.add(new TrimPattern("minecraft:wild_armor_trim_smithing_template", "wild"));
|
||||
patterns.add(new TrimPattern("minecraft:rib_armor_trim_smithing_template", "rib"));
|
||||
patterns.add(new TrimPattern("minecraft:coast_armor_trim_smithing_template", "coast"));
|
||||
patterns.add(new TrimPattern("minecraft:shaper_armor_trim_smithing_template", "shaper"));
|
||||
patterns.add(new TrimPattern("minecraft:eye_armor_trim_smithing_template", "eye"));
|
||||
patterns.add(new TrimPattern("minecraft:vex_armor_trim_smithing_template", "vex"));
|
||||
patterns.add(new TrimPattern("minecraft:silence_armor_trim_smithing_template", "silence"));
|
||||
patterns.add(new TrimPattern("minecraft:wayfinder_armor_trim_smithing_template", "wayfinder"));
|
||||
patterns.add(new TrimPattern("minecraft:raiser_armor_trim_smithing_template", "raiser"));
|
||||
patterns.add(new TrimPattern("minecraft:host_armor_trim_smithing_template", "host"));
|
||||
PATTERNS = Collections.unmodifiableList(patterns);
|
||||
|
||||
List<TrimMaterial> materials = new ArrayList<>(10);
|
||||
materials.add(new TrimMaterial("quartz", "§h", "minecraft:quartz"));
|
||||
materials.add(new TrimMaterial("iron", "§i", "minecraft:iron_ingot"));
|
||||
materials.add(new TrimMaterial("netherite", "§j", "minecraft:netherite_ingot"));
|
||||
materials.add(new TrimMaterial("redstone", "§m", "minecraft:redstone"));
|
||||
materials.add(new TrimMaterial("copper", "§n", "minecraft:copper_ingot"));
|
||||
materials.add(new TrimMaterial("gold", "§p", "minecraft:gold_ingot"));
|
||||
materials.add(new TrimMaterial("emerald", "§q", "minecraft:emerald"));
|
||||
materials.add(new TrimMaterial("diamond", "§s", "minecraft:diamond"));
|
||||
materials.add(new TrimMaterial("lapis", "§t", "minecraft:lapis_lazuli"));
|
||||
materials.add(new TrimMaterial("amethyst", "§u", "minecraft:amethyst_shard"));
|
||||
MATERIALS = Collections.unmodifiableList(materials);
|
||||
}
|
||||
|
||||
private TrimRecipe() {
|
||||
//no-op
|
||||
}
|
||||
|
||||
private static ItemDescriptorWithCount tagDescriptor(String tag) {
|
||||
return new ItemDescriptorWithCount(new ItemTagDescriptor(tag), 1);
|
||||
}
|
||||
}
|
@ -84,6 +84,7 @@ public final class Items {
|
||||
public static final Item BEDROCK = register(new BlockItem("bedrock", builder()));
|
||||
public static final Item SAND = register(new BlockItem("sand", builder()));
|
||||
public static final Item SUSPICIOUS_SAND = register(new BlockItem("suspicious_sand", builder()));
|
||||
public static final Item SUSPICIOUS_GRAVEL = register(new BlockItem("suspicious_gravel", builder()));
|
||||
public static final Item RED_SAND = register(new BlockItem("red_sand", builder()));
|
||||
public static final Item GRAVEL = register(new BlockItem("gravel", builder()));
|
||||
public static final Item COAL_ORE = register(new BlockItem("coal_ore", builder()));
|
||||
@ -247,6 +248,7 @@ public final class Items {
|
||||
public static final Item LILY_OF_THE_VALLEY = register(new FlowerItem("lily_of_the_valley", builder()));
|
||||
public static final Item WITHER_ROSE = register(new FlowerItem("wither_rose", builder()));
|
||||
public static final Item TORCHFLOWER = register(new FlowerItem("torchflower", builder()));
|
||||
public static final Item PITCHER_PLANT = register(new BlockItem("pitcher_plant", builder()));
|
||||
public static final Item SPORE_BLOSSOM = register(new BlockItem("spore_blossom", builder()));
|
||||
public static final Item BROWN_MUSHROOM = register(new BlockItem("brown_mushroom", builder()));
|
||||
public static final Item RED_MUSHROOM = register(new BlockItem("red_mushroom", builder()));
|
||||
@ -302,7 +304,7 @@ public final class Items {
|
||||
public static final Item BRICKS = register(new BlockItem("bricks", builder()));
|
||||
public static final Item BOOKSHELF = register(new BlockItem("bookshelf", builder()));
|
||||
public static final Item CHISELED_BOOKSHELF = register(new BlockItem("chiseled_bookshelf", builder()));
|
||||
public static final Item DECORATED_POT = register(new BlockItem("decorated_pot", builder().stackSize(1)));
|
||||
public static final Item DECORATED_POT = register(new DecoratedPotItem("decorated_pot", builder().stackSize(1)));
|
||||
public static final Item MOSSY_COBBLESTONE = register(new BlockItem("mossy_cobblestone", builder()));
|
||||
public static final Item OBSIDIAN = register(new BlockItem("obsidian", builder()));
|
||||
public static final Item TORCH = register(new BlockItem("torch", builder()));
|
||||
@ -313,7 +315,7 @@ public final class Items {
|
||||
public static final Item PURPUR_PILLAR = register(new BlockItem("purpur_pillar", builder()));
|
||||
public static final Item PURPUR_STAIRS = register(new BlockItem("purpur_stairs", builder()));
|
||||
public static final Item SPAWNER = register(new BlockItem("spawner", builder()));
|
||||
public static final Item CHEST = register(new BlockItem("chest", builder()));
|
||||
public static final Item CHEST = register(new ChestItem("chest", builder()));
|
||||
public static final Item CRAFTING_TABLE = register(new BlockItem("crafting_table", builder()));
|
||||
public static final Item FARMLAND = register(new BlockItem("farmland", builder()));
|
||||
public static final Item FURNACE = register(new BlockItem("furnace", builder()));
|
||||
@ -395,7 +397,7 @@ public final class Items {
|
||||
public static final Item END_STONE_BRICKS = register(new BlockItem("end_stone_bricks", builder()));
|
||||
public static final Item DRAGON_EGG = register(new BlockItem("dragon_egg", builder()));
|
||||
public static final Item SANDSTONE_STAIRS = register(new BlockItem("sandstone_stairs", builder()));
|
||||
public static final Item ENDER_CHEST = register(new BlockItem("ender_chest", builder()));
|
||||
public static final Item ENDER_CHEST = register(new ChestItem("ender_chest", builder()));
|
||||
public static final Item EMERALD_BLOCK = register(new BlockItem("emerald_block", builder()));
|
||||
public static final Item OAK_STAIRS = register(new BlockItem("oak_stairs", builder()));
|
||||
public static final Item SPRUCE_STAIRS = register(new BlockItem("spruce_stairs", builder()));
|
||||
@ -602,6 +604,7 @@ public final class Items {
|
||||
public static final Item RED_CONCRETE_POWDER = register(new BlockItem("red_concrete_powder", builder()));
|
||||
public static final Item BLACK_CONCRETE_POWDER = register(new BlockItem("black_concrete_powder", builder()));
|
||||
public static final Item TURTLE_EGG = register(new BlockItem("turtle_egg", builder()));
|
||||
public static final Item SNIFFER_EGG = register(new BlockItem("sniffer_egg", builder()));
|
||||
public static final Item DEAD_TUBE_CORAL_BLOCK = register(new BlockItem("dead_tube_coral_block", builder()));
|
||||
public static final Item DEAD_BRAIN_CORAL_BLOCK = register(new BlockItem("dead_brain_coral_block", builder()));
|
||||
public static final Item DEAD_BUBBLE_CORAL_BLOCK = register(new BlockItem("dead_bubble_coral_block", builder()));
|
||||
@ -689,8 +692,9 @@ public final class Items {
|
||||
public static final Item LIGHTNING_ROD = register(new BlockItem("lightning_rod", builder()));
|
||||
public static final Item DAYLIGHT_DETECTOR = register(new BlockItem("daylight_detector", builder()));
|
||||
public static final Item SCULK_SENSOR = register(new BlockItem("sculk_sensor", builder()));
|
||||
public static final Item CALIBRATED_SCULK_SENSOR = register(new BlockItem("calibrated_sculk_sensor", builder()));
|
||||
public static final Item TRIPWIRE_HOOK = register(new BlockItem("tripwire_hook", builder()));
|
||||
public static final Item TRAPPED_CHEST = register(new BlockItem("trapped_chest", builder()));
|
||||
public static final Item TRAPPED_CHEST = register(new ChestItem("trapped_chest", builder()));
|
||||
public static final Item TNT = register(new BlockItem("tnt", builder()));
|
||||
public static final Item REDSTONE_LAMP = register(new BlockItem("redstone_lamp", builder()));
|
||||
public static final Item NOTE_BLOCK = register(new BlockItem("note_block", builder()));
|
||||
@ -1080,8 +1084,8 @@ public final class Items {
|
||||
public static final Item ZOMBIFIED_PIGLIN_SPAWN_EGG = register(new SpawnEggItem("zombified_piglin_spawn_egg", builder()));
|
||||
public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder()));
|
||||
public static final Item FIRE_CHARGE = register(new Item("fire_charge", builder()));
|
||||
public static final Item WRITABLE_BOOK = register(new ReadableBookItem("writable_book", builder().stackSize(1)));
|
||||
public static final Item WRITTEN_BOOK = register(new ReadableBookItem("written_book", builder().stackSize(16)));
|
||||
public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1)));
|
||||
public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16)));
|
||||
public static final Item ITEM_FRAME = register(new Item("item_frame", builder()));
|
||||
public static final Item GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder()));
|
||||
public static final Item FLOWER_POT = register(new BlockItem("flower_pot", builder()));
|
||||
@ -1141,6 +1145,7 @@ public final class Items {
|
||||
public static final Item CHORUS_FRUIT = register(new Item("chorus_fruit", builder()));
|
||||
public static final Item POPPED_CHORUS_FRUIT = register(new Item("popped_chorus_fruit", builder()));
|
||||
public static final Item TORCHFLOWER_SEEDS = register(new BlockItem("torchflower_seeds", builder()));
|
||||
public static final Item PITCHER_POD = register(new BlockItem("pitcher_pod", builder()));
|
||||
public static final Item BEETROOT = register(new Item("beetroot", builder()));
|
||||
public static final Item BEETROOT_SEEDS = register(new BlockItem("beetroot_seeds", builder()));
|
||||
public static final Item BEETROOT_SOUP = register(new Item("beetroot_soup", builder().stackSize(1)));
|
||||
@ -1168,6 +1173,7 @@ public final class Items {
|
||||
public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1)));
|
||||
public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1)));
|
||||
public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder()));
|
||||
@ -1186,7 +1192,7 @@ public final class Items {
|
||||
public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1)));
|
||||
public static final Item GOAT_HORN = register(new GoatHornItem("goat_horn", builder().stackSize(1)));
|
||||
public static final Item COMPOSTER = register(new BlockItem("composter", builder()));
|
||||
public static final Item BARREL = register(new BlockItem("barrel", builder()));
|
||||
public static final Item BARREL = register(new ChestItem("barrel", builder()));
|
||||
public static final Item SMOKER = register(new BlockItem("smoker", builder()));
|
||||
public static final Item BLAST_FURNACE = register(new BlockItem("blast_furnace", builder()));
|
||||
public static final Item CARTOGRAPHY_TABLE = register(new BlockItem("cartography_table", builder()));
|
||||
@ -1262,10 +1268,31 @@ public final class Items {
|
||||
public static final Item SNOUT_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("snout_armor_trim_smithing_template", builder()));
|
||||
public static final Item RIB_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("rib_armor_trim_smithing_template", builder()));
|
||||
public static final Item SPIRE_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("spire_armor_trim_smithing_template", builder()));
|
||||
public static final Item POTTERY_SHARD_ARCHER = register(new Item("pottery_shard_archer", builder()));
|
||||
public static final Item POTTERY_SHARD_PRIZE = register(new Item("pottery_shard_prize", builder()));
|
||||
public static final Item POTTERY_SHARD_ARMS_UP = register(new Item("pottery_shard_arms_up", builder()));
|
||||
public static final Item POTTERY_SHARD_SKULL = register(new Item("pottery_shard_skull", builder()));
|
||||
public static final Item WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("wayfinder_armor_trim_smithing_template", builder()));
|
||||
public static final Item SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("shaper_armor_trim_smithing_template", builder()));
|
||||
public static final Item SILENCE_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("silence_armor_trim_smithing_template", builder()));
|
||||
public static final Item RAISER_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("raiser_armor_trim_smithing_template", builder()));
|
||||
public static final Item HOST_ARMOR_TRIM_SMITHING_TEMPLATE = register(new Item("host_armor_trim_smithing_template", builder()));
|
||||
public static final Item ANGLER_POTTERY_SHERD = register(new Item("angler_pottery_sherd", builder()));
|
||||
public static final Item ARCHER_POTTERY_SHERD = register(new Item("archer_pottery_sherd", builder()));
|
||||
public static final Item ARMS_UP_POTTERY_SHERD = register(new Item("arms_up_pottery_sherd", builder()));
|
||||
public static final Item BLADE_POTTERY_SHERD = register(new Item("blade_pottery_sherd", builder()));
|
||||
public static final Item BREWER_POTTERY_SHERD = register(new Item("brewer_pottery_sherd", builder()));
|
||||
public static final Item BURN_POTTERY_SHERD = register(new Item("burn_pottery_sherd", builder()));
|
||||
public static final Item DANGER_POTTERY_SHERD = register(new Item("danger_pottery_sherd", builder()));
|
||||
public static final Item EXPLORER_POTTERY_SHERD = register(new Item("explorer_pottery_sherd", builder()));
|
||||
public static final Item FRIEND_POTTERY_SHERD = register(new Item("friend_pottery_sherd", builder()));
|
||||
public static final Item HEART_POTTERY_SHERD = register(new Item("heart_pottery_sherd", builder()));
|
||||
public static final Item HEARTBREAK_POTTERY_SHERD = register(new Item("heartbreak_pottery_sherd", builder()));
|
||||
public static final Item HOWL_POTTERY_SHERD = register(new Item("howl_pottery_sherd", builder()));
|
||||
public static final Item MINER_POTTERY_SHERD = register(new Item("miner_pottery_sherd", builder()));
|
||||
public static final Item MOURNER_POTTERY_SHERD = register(new Item("mourner_pottery_sherd", builder()));
|
||||
public static final Item PLENTY_POTTERY_SHERD = register(new Item("plenty_pottery_sherd", builder()));
|
||||
public static final Item PRIZE_POTTERY_SHERD = register(new Item("prize_pottery_sherd", builder()));
|
||||
public static final Item SHEAF_POTTERY_SHERD = register(new Item("sheaf_pottery_sherd", builder()));
|
||||
public static final Item SHELTER_POTTERY_SHERD = register(new Item("shelter_pottery_sherd", builder()));
|
||||
public static final Item SKULL_POTTERY_SHERD = register(new Item("skull_pottery_sherd", builder()));
|
||||
public static final Item SNORT_POTTERY_SHERD = register(new Item("snort_pottery_sherd", builder()));
|
||||
|
||||
private static <T extends Item> T register(T item) {
|
||||
return register(item, Registries.JAVA_ITEMS.get().size());
|
||||
|
@ -25,7 +25,12 @@
|
||||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.ArmorMaterial;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class ArmorItem extends Item {
|
||||
private final ArmorMaterial material;
|
||||
@ -35,8 +40,42 @@ public class ArmorItem extends Item {
|
||||
this.material = material;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (tag.get("Trim") instanceof CompoundTag trim) {
|
||||
StringTag material = trim.remove("material");
|
||||
StringTag pattern = trim.remove("pattern");
|
||||
// bedrock has an uppercase first letter key, and the value is not namespaced
|
||||
trim.put(new StringTag("Material", stripNamespace(material.getValue())));
|
||||
trim.put(new StringTag("Pattern", stripNamespace(pattern.getValue())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToJava(@NonNull CompoundTag tag, @NonNull ItemMapping mapping) {
|
||||
super.translateNbtToJava(tag, mapping);
|
||||
|
||||
if (tag.get("Trim") instanceof CompoundTag trim) {
|
||||
StringTag material = trim.remove("Material");
|
||||
StringTag pattern = trim.remove("Pattern");
|
||||
// java has a lowercase key, and namespaced value
|
||||
trim.put(new StringTag("material", "minecraft:" + material.getValue()));
|
||||
trim.put(new StringTag("pattern", "minecraft:" + pattern.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidRepairItem(Item other) {
|
||||
return material.getRepairIngredient() == other;
|
||||
}
|
||||
|
||||
private static String stripNamespace(String identifier) {
|
||||
int i = identifier.indexOf(':');
|
||||
if (i >= 0) {
|
||||
return identifier.substring(i + 1);
|
||||
}
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
* Copyright (c) 2019-2023 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
|
||||
@ -23,42 +23,31 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.inventory.item;
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import org.geysermc.geyser.item.type.Item;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public abstract class NbtItemStackTranslator {
|
||||
|
||||
/**
|
||||
* Translate the item NBT to Bedrock
|
||||
* @param session the client's current session
|
||||
* @param itemTag the item's CompoundTag (cloned from Geyser's cached copy)
|
||||
* @param mapping Geyser's item mapping
|
||||
*/
|
||||
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemMapping mapping) {
|
||||
public class ChestItem extends BlockItem {
|
||||
|
||||
public ChestItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the item NBT to Java.
|
||||
* @param itemTag the item's CompoundTag
|
||||
* @param mapping Geyser's item mapping
|
||||
*/
|
||||
public void translateToJava(CompoundTag itemTag, ItemMapping mapping) {
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
// Strip the BlockEntityTag from the chests contents
|
||||
// sent to the client. The client does not parse this
|
||||
// or use it for anything, as this tag is fully
|
||||
// server-side, so we remove it to reduce bandwidth and
|
||||
// solve potential issues with very large tags.
|
||||
|
||||
// There was a problem in the past where this would strip
|
||||
// NBT data in creative mode, however with the new server
|
||||
// authoritative inventories, this is no longer a concern.
|
||||
tag.remove("BlockEntityTag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this nbt translator takes in this item.
|
||||
*
|
||||
* @param item Geyser's item mapping
|
||||
* @return if the item should be processed under this class
|
||||
*/
|
||||
public boolean acceptItem(Item item) {
|
||||
return true;
|
||||
} // TODO
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class DecoratedPotItem extends BlockItem {
|
||||
|
||||
public DecoratedPotItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (tag.remove("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
|
||||
if (blockEntityTag.remove("sherds") instanceof ListTag sherds) {
|
||||
// bedrock wants it on the root level
|
||||
tag.put(sherds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -144,7 +144,7 @@ public class Item {
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes NBT from Java Edition and converts any value that Bedrock parses differently. <br>
|
||||
* Takes NBT from Bedrock Edition and converts any value that Java parses differently. <br>
|
||||
* Do note that this method is, these days, only called in three places (as of 2023/~1.19):
|
||||
* <ul>
|
||||
* <li>Extra recipe loading</li>
|
||||
|
@ -25,13 +25,40 @@
|
||||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.item.components.ToolTier;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
public class ShieldItem extends Item {
|
||||
public ShieldItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (tag.remove("BlockEntityTag") instanceof CompoundTag blockEntityTag) {
|
||||
if (blockEntityTag.get("Patterns") instanceof ListTag patterns) {
|
||||
for (Tag pattern : patterns) {
|
||||
if (((CompoundTag) pattern).get("Color") instanceof IntTag color) {
|
||||
color.setValue(15 - color.getValue());
|
||||
}
|
||||
}
|
||||
// Bedrock looks for patterns at the root
|
||||
tag.put(patterns);
|
||||
}
|
||||
if (blockEntityTag.get("Base") instanceof IntTag base) {
|
||||
base.setValue(15 - base.getValue());
|
||||
tag.put(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidRepairItem(Item other) {
|
||||
// Java Edition 1.19.3 checks the tag, but TODO check to see if we want it or are simulating what Bedrock is doing
|
||||
|
@ -77,9 +77,17 @@ public class ShulkerBoxItem extends BlockItem {
|
||||
itemsList.add(boxItemTag);
|
||||
}
|
||||
tag.put(itemsList);
|
||||
// Don't actually bother with removing the block entity tag. Too risky to translate
|
||||
// if the user is on creative and messing with a shulker box
|
||||
//itemTag.remove("BlockEntityTag");
|
||||
|
||||
// Strip the BlockEntityTag from the chests contents
|
||||
// sent to the client. The client does not parse this
|
||||
// or use it for anything, as this tag is fully
|
||||
// server-side, so we remove it to reduce bandwidth and
|
||||
// solve potential issues with very large tags.
|
||||
|
||||
// There was a problem in the past where this would strip
|
||||
// NBT data in creative mode, however with the new server
|
||||
// authoritative inventories, this is no longer a concern.
|
||||
tag.remove("BlockEntityTag");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,11 +37,8 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Encapsulates written books and writable books. Customly named class to share common code.
|
||||
*/
|
||||
public class ReadableBookItem extends Item {
|
||||
public ReadableBookItem(String javaIdentifier, Builder builder) {
|
||||
public class WritableBookItem extends Item {
|
||||
public WritableBookItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.item.type;
|
||||
|
||||
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 net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WrittenBookItem extends WritableBookItem {
|
||||
public static final int MAXIMUM_PAGE_EDIT_LENGTH = 1024;
|
||||
public static final int MAXIMUM_PAGE_LENGTH = 32768;
|
||||
public static final int MAXIMUM_PAGE_COUNT = 100; // Java edition limit. Bedrock edition has a limit of 50 pages.
|
||||
public static final int MAXIMUM_TITLE_LENGTH = 16;
|
||||
|
||||
public WrittenBookItem(String javaIdentifier, Builder builder) {
|
||||
super(javaIdentifier, builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateNbtToBedrock(@NonNull GeyserSession session, @NonNull CompoundTag tag) {
|
||||
boolean isValid = isValidWrittenBook(tag);
|
||||
if (!isValid) {
|
||||
tag.remove("pages");
|
||||
}
|
||||
|
||||
super.translateNbtToBedrock(session, tag);
|
||||
|
||||
if (!isValid) {
|
||||
CompoundTag invalidTagPage = new CompoundTag("");
|
||||
invalidTagPage.put(new StringTag("photoname", ""));
|
||||
invalidTagPage.put(new StringTag(
|
||||
"text",
|
||||
MessageTranslator.convertMessage(
|
||||
Component.translatable("book.invalid.tag", NamedTextColor.DARK_RED),
|
||||
session.locale()
|
||||
)
|
||||
));
|
||||
tag.put(new ListTag("pages", List.of(invalidTagPage)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidWrittenBook(CompoundTag tag) {
|
||||
if (!(tag.get("title") instanceof StringTag title)) {
|
||||
return false;
|
||||
}
|
||||
if (title.getValue().length() > (MAXIMUM_TITLE_LENGTH * 2)) {
|
||||
// Java rejects books with titles more than 2x the maximum length allowed in the input box
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tag.get("author") instanceof StringTag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(tag.get("pages") instanceof ListTag pages)) {
|
||||
return false;
|
||||
}
|
||||
for (Tag pageTag : pages) {
|
||||
if (pageTag instanceof StringTag page) {
|
||||
if (page.getValue().length() > MAXIMUM_PAGE_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ public final class BlockStateValues {
|
||||
private static final IntSet ALL_CAULDRONS = new IntOpenHashSet();
|
||||
private static final Int2IntMap BANNER_COLORS = new FixedInt2IntMap();
|
||||
private static final Int2ByteMap BED_COLORS = new FixedInt2ByteMap();
|
||||
private static final Int2IntMap BRUSH_PROGRESS = new Int2IntOpenHashMap();
|
||||
private static final Int2ByteMap COMMAND_BLOCK_VALUES = new Int2ByteOpenHashMap();
|
||||
private static final Int2ObjectMap<DoubleChestValue> DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
private static final Int2ObjectMap<String> FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>();
|
||||
@ -98,6 +99,15 @@ public final class BlockStateValues {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode bedrockStates = blockData.get("bedrock_states");
|
||||
if (bedrockStates != null) {
|
||||
JsonNode brushedProgress = bedrockStates.get("brushed_progress");
|
||||
if (brushedProgress != null) {
|
||||
BRUSH_PROGRESS.put(javaBlockState, brushedProgress.intValue());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (javaId.contains("command_block")) {
|
||||
COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
|
||||
return;
|
||||
@ -225,6 +235,17 @@ public final class BlockStateValues {
|
||||
return BED_COLORS.getOrDefault(state, (byte) -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brush progress of suspicious sand/gravel is not sent by the java server when it updates the block entity.
|
||||
* Although brush progress is part of the bedrock block state, it must be included in the block entity update.
|
||||
*
|
||||
* @param state BlockState of the block
|
||||
* @return brush progress or 0 if the lookup failed
|
||||
*/
|
||||
public static int getBrushProgress(int state) {
|
||||
return BRUSH_PROGRESS.getOrDefault(state, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-water cauldrons (since Bedrock 1.18.30) must have a block entity packet sent on chunk load to fix rendering issues.
|
||||
*
|
||||
|
@ -28,12 +28,8 @@ package org.geysermc.geyser.network;
|
||||
import com.github.steveice10.mc.protocol.codec.MinecraftCodec;
|
||||
import com.github.steveice10.mc.protocol.codec.PacketCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v557.Bedrock_v557;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v568.Bedrock_v568;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
|
||||
@ -49,9 +45,8 @@ public final class GameProtocol {
|
||||
* Default Bedrock codec that should act as a fallback. Should represent the latest available
|
||||
* release of the game that Geyser supports.
|
||||
*/
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v582.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.81")
|
||||
.build();
|
||||
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v589.CODEC;
|
||||
|
||||
/**
|
||||
* A list of all supported Bedrock versions that can join Geyser
|
||||
*/
|
||||
@ -64,20 +59,10 @@ public final class GameProtocol {
|
||||
private static final PacketCodec DEFAULT_JAVA_CODEC = MinecraftCodec.CODEC;
|
||||
|
||||
static {
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v557.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.40/1.19.41")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v560.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.50/1.19.51")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v567.CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v568.CODEC);
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v575.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.70/1.19.71/1.19.73")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
|
||||
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v582.CODEC.toBuilder()
|
||||
.minecraftVersion("1.19.80/1.19.81")
|
||||
.build());
|
||||
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,16 +81,8 @@ public final class GameProtocol {
|
||||
|
||||
/* Bedrock convenience methods to gatekeep features and easily remove the check on version removal */
|
||||
|
||||
public static boolean supports1_19_50(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() >= Bedrock_v560.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
public static boolean supports1_19_60(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() >= Bedrock_v567.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
public static boolean supports1_19_80(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() >= Bedrock_v582.CODEC.getProtocolVersion();
|
||||
public static boolean isPre1_20(GeyserSession session) {
|
||||
return session.getUpstream().getProtocolVersion() < Bedrock_v589.CODEC.getProtocolVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,11 +47,6 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
|
||||
this.geyser = geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void postInitChannel(Channel channel) throws Exception {
|
||||
super.postInitChannel(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
|
||||
try {
|
||||
|
@ -28,8 +28,6 @@ package org.geysermc.geyser.network;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v568.Bedrock_v568;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
|
||||
@ -51,9 +49,12 @@ import org.cloudburstmc.protocol.common.PacketSignal;
|
||||
import org.geysermc.geyser.Constants;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.pack.ResourcePack;
|
||||
import org.geysermc.geyser.pack.ResourcePackManifest;
|
||||
import org.geysermc.geyser.event.type.SessionLoadResourcePacksEventImpl;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
@ -63,15 +64,20 @@ import org.geysermc.geyser.util.LoginEncryptionUtils;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
import org.geysermc.geyser.util.VersionCheckUtils;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
private Deque<String> packsToSent = new ArrayDeque<>();
|
||||
private boolean networkSettingsRequested = false;
|
||||
private final Deque<String> packsToSent = new ArrayDeque<>();
|
||||
|
||||
private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;
|
||||
|
||||
public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
|
||||
super(geyser, session);
|
||||
@ -87,8 +93,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
return translateAndDefault(packet);
|
||||
}
|
||||
|
||||
private boolean newProtocol = false; // TEMPORARY
|
||||
|
||||
private boolean setCorrectCodec(int protocolVersion) {
|
||||
BedrockCodec packetCodec = GameProtocol.getBedrockCodec(protocolVersion);
|
||||
if (packetCodec == null) {
|
||||
@ -127,9 +131,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
@Override
|
||||
public PacketSignal handle(RequestNetworkSettingsPacket packet) {
|
||||
if (setCorrectCodec(packet.getProtocolVersion())) {
|
||||
newProtocol = true;
|
||||
} else {
|
||||
if (!setCorrectCodec(packet.getProtocolVersion())) {
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
@ -143,6 +145,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
session.getUpstream().getSession().setCompression(algorithm);
|
||||
session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
|
||||
networkSettingsRequested = true;
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
@ -154,12 +157,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
// session.getUpstream().getSession().getCodec() == null
|
||||
|
||||
if (!newProtocol) {
|
||||
if (!setCorrectCodec(loginPacket.getProtocolVersion())) { // REMOVE WHEN ONLY 1.19.30 IS SUPPORTED OR 1.20
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
if (!networkSettingsRequested) {
|
||||
session.disconnect(GeyserLocale.getLocaleStringLog("geyser.network.outdated.client", GameProtocol.getAllSupportedBedrockVersions()));
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
// Set the block translation based off of version
|
||||
@ -173,23 +173,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
return PacketSignal.HANDLED;
|
||||
}
|
||||
|
||||
// Hack for... whatever this is
|
||||
if (loginPacket.getProtocolVersion() == Bedrock_v567.CODEC.getProtocolVersion() && !session.getClientData().getGameVersion().equals("1.19.60")) {
|
||||
session.getUpstream().getSession().setCodec(Bedrock_v568.CODEC);
|
||||
}
|
||||
|
||||
PlayStatusPacket playStatus = new PlayStatusPacket();
|
||||
playStatus.setStatus(PlayStatusPacket.Status.LOGIN_SUCCESS);
|
||||
session.sendUpstreamPacket(playStatus);
|
||||
|
||||
geyser.getSessionManager().addPendingSession(session);
|
||||
|
||||
this.resourcePackLoadEvent = new SessionLoadResourcePacksEventImpl(session, new HashMap<>(Registries.RESOURCE_PACKS.get()));
|
||||
this.geyser.eventBus().fire(this.resourcePackLoadEvent);
|
||||
|
||||
ResourcePacksInfoPacket resourcePacksInfo = new ResourcePacksInfoPacket();
|
||||
for(ResourcePack resourcePack : ResourcePack.PACKS.values()) {
|
||||
ResourcePackManifest.Header header = resourcePack.getManifest().getHeader();
|
||||
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
|
||||
PackCodec codec = pack.codec();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
resourcePacksInfo.getResourcePackInfos().add(new ResourcePacksInfoPacket.Entry(
|
||||
header.getUuid().toString(), header.getVersionString(), resourcePack.getFile().length(),
|
||||
resourcePack.getContentKey(), "", header.getUuid().toString(), false, false));
|
||||
header.uuid().toString(), header.version().toString(), codec.size(), pack.contentKey(),
|
||||
"", header.uuid().toString(), false, false));
|
||||
}
|
||||
resourcePacksInfo.setForcedToAccept(GeyserImpl.getInstance().getConfig().isForceResourcePacks());
|
||||
session.sendUpstreamPacket(resourcePacksInfo);
|
||||
@ -222,9 +221,9 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
stackPacket.setForcedToAccept(false); // Leaving this as false allows the player to choose to download or not
|
||||
stackPacket.setGameVersion(session.getClientData().getGameVersion());
|
||||
|
||||
for (ResourcePack pack : ResourcePack.PACKS.values()) {
|
||||
ResourcePackManifest.Header header = pack.getManifest().getHeader();
|
||||
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.getUuid().toString(), header.getVersionString(), ""));
|
||||
for (ResourcePack pack : this.resourcePackLoadEvent.resourcePacks()) {
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
stackPacket.getResourcePacks().add(new ResourcePackStackPacket.Entry(header.uuid().toString(), header.version().toString(), ""));
|
||||
}
|
||||
|
||||
if (GeyserImpl.getInstance().getConfig().isAddNonBedrockItems()) {
|
||||
@ -232,6 +231,11 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
stackPacket.getExperiments().add(new ExperimentData("data_driven_items", true));
|
||||
}
|
||||
|
||||
if (GameProtocol.isPre1_20(session)) {
|
||||
stackPacket.getExperiments().add(new ExperimentData("next_major_update", true));
|
||||
stackPacket.getExperiments().add(new ExperimentData("sniffer", true));
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(stackPacket);
|
||||
break;
|
||||
|
||||
@ -298,21 +302,22 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
@Override
|
||||
public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
|
||||
ResourcePackChunkDataPacket data = new ResourcePackChunkDataPacket();
|
||||
ResourcePack pack = ResourcePack.PACKS.get(packet.getPackId().toString());
|
||||
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packet.getPackId().toString());
|
||||
PackCodec codec = pack.codec();
|
||||
|
||||
data.setChunkIndex(packet.getChunkIndex());
|
||||
data.setProgress(packet.getChunkIndex() * ResourcePack.CHUNK_SIZE);
|
||||
data.setProgress((long) packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setPackVersion(packet.getPackVersion());
|
||||
data.setPackId(packet.getPackId());
|
||||
|
||||
int offset = packet.getChunkIndex() * ResourcePack.CHUNK_SIZE;
|
||||
long remainingSize = pack.getFile().length() - offset;
|
||||
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, ResourcePack.CHUNK_SIZE)];
|
||||
int offset = packet.getChunkIndex() * GeyserResourcePack.CHUNK_SIZE;
|
||||
long remainingSize = codec.size() - offset;
|
||||
byte[] packData = new byte[(int) MathUtils.constrain(remainingSize, 0, GeyserResourcePack.CHUNK_SIZE)];
|
||||
|
||||
try (InputStream inputStream = new FileInputStream(pack.getFile())) {
|
||||
inputStream.skip(offset);
|
||||
inputStream.read(packData, 0, packData.length);
|
||||
} catch (Exception e) {
|
||||
try (SeekableByteChannel channel = codec.serialize(pack)) {
|
||||
channel.position(offset);
|
||||
channel.read(ByteBuffer.wrap(packData, 0, packData.length));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@ -321,7 +326,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
session.sendUpstreamPacket(data);
|
||||
|
||||
// Check if it is the last chunk and send next pack in queue when available.
|
||||
if (remainingSize <= ResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
||||
if (remainingSize <= GeyserResourcePack.CHUNK_SIZE && !packsToSent.isEmpty()) {
|
||||
sendPackDataInfo(packsToSent.pop());
|
||||
}
|
||||
|
||||
@ -331,15 +336,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
private void sendPackDataInfo(String id) {
|
||||
ResourcePackDataInfoPacket data = new ResourcePackDataInfoPacket();
|
||||
String[] packID = id.split("_");
|
||||
ResourcePack pack = ResourcePack.PACKS.get(packID[0]);
|
||||
ResourcePackManifest.Header header = pack.getManifest().getHeader();
|
||||
ResourcePack pack = this.resourcePackLoadEvent.getPacks().get(packID[0]);
|
||||
PackCodec codec = pack.codec();
|
||||
ResourcePackManifest.Header header = pack.manifest().header();
|
||||
|
||||
data.setPackId(header.getUuid());
|
||||
int chunkCount = (int) Math.ceil((int) pack.getFile().length() / (double) ResourcePack.CHUNK_SIZE);
|
||||
data.setPackId(header.uuid());
|
||||
int chunkCount = (int) Math.ceil(codec.size() / (double) GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setChunkCount(chunkCount);
|
||||
data.setCompressedPackSize(pack.getFile().length());
|
||||
data.setMaxChunkSize(ResourcePack.CHUNK_SIZE);
|
||||
data.setHash(pack.getSha256());
|
||||
data.setCompressedPackSize(codec.size());
|
||||
data.setMaxChunkSize(GeyserResourcePack.CHUNK_SIZE);
|
||||
data.setHash(codec.sha256());
|
||||
data.setPackVersion(packID[1]);
|
||||
data.setPremium(false);
|
||||
data.setType(ResourcePackType.RESOURCES);
|
||||
|
@ -187,7 +187,16 @@ public final class GeyserServer {
|
||||
|
||||
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
|
||||
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
|
||||
String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
|
||||
String ip;
|
||||
if (geyser.getConfig().isLogPlayerIpAddresses()) {
|
||||
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
|
||||
ip = this.proxiedAddresses.getOrDefault(inetSocketAddress, inetSocketAddress).toString();
|
||||
} else {
|
||||
ip = inetSocketAddress.toString();
|
||||
}
|
||||
} else {
|
||||
ip = "<IP address withheld>";
|
||||
}
|
||||
geyser.getLogger().debug(GeyserLocale.getLocaleStringLog("geyser.network.pinged", ip));
|
||||
}
|
||||
|
||||
|
@ -73,10 +73,10 @@ public class ProxyServerHandler extends SimpleChannelInboundHandler<DatagramPack
|
||||
presentAddress = new InetSocketAddress(decoded.sourceAddress(), decoded.sourcePort());
|
||||
log.debug("Got PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||
GeyserImpl.getInstance().getGeyserServer().getProxiedAddresses().put(packet.sender(), presentAddress);
|
||||
return;
|
||||
} else {
|
||||
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||
}
|
||||
|
||||
log.trace("Reusing PROXY header: (from {}) {}", packet.sender(), presentAddress);
|
||||
ctx.fireChannelRead(packet.retain());
|
||||
}
|
||||
}
|
38
core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java
Normale Datei
38
core/src/main/java/org/geysermc/geyser/pack/GeyserResourcePack.java
Normale Datei
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import org.geysermc.geyser.api.pack.PackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
|
||||
public record GeyserResourcePack(PackCodec codec, ResourcePackManifest manifest, String contentKey) implements ResourcePack {
|
||||
|
||||
/**
|
||||
* The size of each chunk to use when sending the resource packs to clients in bytes
|
||||
*/
|
||||
public static final int CHUNK_SIZE = 102400;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import org.geysermc.geyser.api.pack.ResourcePackManifest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public record GeyserResourcePackManifest(@JsonProperty("format_version") int formatVersion, Header header, Collection<Module> modules, Collection<Dependency> dependencies) implements ResourcePackManifest {
|
||||
|
||||
public record Header(UUID uuid, Version version, String name, String description, @JsonProperty("min_engine_version") Version minimumSupportedMinecraftVersion) implements ResourcePackManifest.Header { }
|
||||
|
||||
public record Module(UUID uuid, Version version, String type, String description) implements ResourcePackManifest.Module { }
|
||||
|
||||
public record Dependency(UUID uuid, Version version) implements ResourcePackManifest.Dependency { }
|
||||
|
||||
@JsonDeserialize(using = Version.VersionDeserializer.class)
|
||||
public record Version(int major, int minor, int patch) implements ResourcePackManifest.Version {
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return major + "." + minor + "." + patch;
|
||||
}
|
||||
|
||||
public static class VersionDeserializer extends JsonDeserializer<Version> {
|
||||
@Override
|
||||
public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
int[] version = ctxt.readValue(p, int[].class);
|
||||
return new Version(version[0], version[1], version[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* This represents a resource pack and all the data relevant to it
|
||||
*/
|
||||
public class ResourcePack {
|
||||
/**
|
||||
* The list of loaded resource packs
|
||||
*/
|
||||
public static final Map<String, ResourcePack> PACKS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The size of each chunk to use when sending the resource packs to clients in bytes
|
||||
*/
|
||||
public static final int CHUNK_SIZE = 102400;
|
||||
|
||||
private byte[] sha256;
|
||||
private File file;
|
||||
private ResourcePackManifest manifest;
|
||||
private ResourcePackManifest.Version version;
|
||||
|
||||
@Getter
|
||||
private String contentKey;
|
||||
|
||||
/**
|
||||
* Loop through the packs directory and locate valid resource pack files
|
||||
*/
|
||||
public static void loadPacks() {
|
||||
Path directory = GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("packs");
|
||||
|
||||
if (!Files.exists(directory)) {
|
||||
try {
|
||||
Files.createDirectory(directory);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
||||
}
|
||||
|
||||
// As we just created the directory it will be empty
|
||||
return;
|
||||
}
|
||||
|
||||
List<Path> resourcePacks;
|
||||
try {
|
||||
resourcePacks = Files.walk(directory).collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
for (Path path : event.resourcePacks()) {
|
||||
File file = path.toFile();
|
||||
|
||||
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
|
||||
ResourcePack pack = new ResourcePack();
|
||||
|
||||
pack.sha256 = FileUtils.calculateSHA256(file);
|
||||
|
||||
try (ZipFile zip = new ZipFile(file);
|
||||
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||
stream.forEach((x) -> {
|
||||
String name = x.getName();
|
||||
if (name.length() >= 80) {
|
||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + file.getName()
|
||||
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
||||
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
||||
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
||||
}
|
||||
if (name.contains("manifest.json")) {
|
||||
try {
|
||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
||||
// Sometimes a pack_manifest file is present and not in a valid format,
|
||||
// but a manifest file is, so we null check through that one
|
||||
if (manifest.getHeader().getUuid() != null) {
|
||||
pack.file = file;
|
||||
pack.manifest = manifest;
|
||||
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
|
||||
|
||||
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
||||
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
||||
File keyFile = new File(file.getParentFile(), file.getName() + ".key");
|
||||
pack.contentKey = keyFile.exists() ? Files.readString(keyFile.toPath(), StandardCharsets.UTF_8) : "";
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", file.getName()));
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getSha256() {
|
||||
return sha256;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public ResourcePackManifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public ResourcePackManifest.Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* author: NukkitX
|
||||
* Nukkit Project
|
||||
*/
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class ResourcePackManifest {
|
||||
@JsonProperty("format_version")
|
||||
private Integer formatVersion;
|
||||
private Header header;
|
||||
private Collection<Module> modules;
|
||||
protected Collection<Dependency> dependencies;
|
||||
|
||||
public Collection<Module> getModules() {
|
||||
return Collections.unmodifiableCollection(modules);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Header {
|
||||
private String description;
|
||||
private String name;
|
||||
private UUID uuid;
|
||||
private int[] version;
|
||||
@JsonProperty("min_engine_version")
|
||||
private int[] minimumSupportedMinecraftVersion;
|
||||
|
||||
public String getVersionString() {
|
||||
return version[0] + "." + version[1] + "." + version[2];
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Module {
|
||||
private String description;
|
||||
private String name;
|
||||
private UUID uuid;
|
||||
private int[] version;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public static class Dependency {
|
||||
private UUID uuid;
|
||||
private int[] version;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class Version {
|
||||
private final int major;
|
||||
private final int minor;
|
||||
private final int patch;
|
||||
|
||||
public static Version fromString(String ver) {
|
||||
String[] split = ver.replace(']', ' ')
|
||||
.replace('[', ' ')
|
||||
.replaceAll(" ", "").split(",");
|
||||
|
||||
return new Version(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
|
||||
}
|
||||
|
||||
public static Version fromArray(int[] ver) {
|
||||
return new Version(ver[0], ver[1], ver[2]);
|
||||
}
|
||||
|
||||
private Version(int major, int minor, int patch) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return major + "." + minor + "." + patch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.pack.path;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.registry.loader.ResourcePackLoader;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class GeyserPathPackCodec extends PathPackCodec {
|
||||
private final Path path;
|
||||
private FileTime lastModified;
|
||||
|
||||
private byte[] sha256;
|
||||
private long size = -1;
|
||||
|
||||
@Override
|
||||
public @NonNull Path path() {
|
||||
this.checkLastModified();
|
||||
return this.path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NonNull [] sha256() {
|
||||
this.checkLastModified();
|
||||
if (this.sha256 != null) {
|
||||
return this.sha256;
|
||||
}
|
||||
|
||||
return this.sha256 = FileUtils.calculateSHA256(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
this.checkLastModified();
|
||||
if (this.size != -1) {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.size = Files.size(this.path);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not get file size of path " + this.path, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SeekableByteChannel serialize(@NonNull ResourcePack resourcePack) throws IOException {
|
||||
return FileChannel.open(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull ResourcePack create() {
|
||||
return ResourcePackLoader.readPack(this.path);
|
||||
}
|
||||
|
||||
private void checkLastModified() {
|
||||
try {
|
||||
FileTime lastModified = Files.getLastModifiedTime(this.path);
|
||||
if (this.lastModified == null) {
|
||||
this.lastModified = lastModified;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastModified.toInstant().isAfter(this.lastModified.toInstant())) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Detected a change in the resource pack " + path + ". This is likely to cause undefined behavior for new clients joining. It is suggested you restart Geyser.");
|
||||
this.lastModified = lastModified;
|
||||
this.sha256 = null;
|
||||
this.size = -1;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
184
core/src/main/java/org/geysermc/geyser/registry/DeferredRegistry.java
Normale Datei
184
core/src/main/java/org/geysermc/geyser/registry/DeferredRegistry.java
Normale Datei
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoader;
|
||||
import org.geysermc.geyser.registry.loader.RegistryLoaders;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A deferred registry is a registry that is not loaded until it is needed.
|
||||
* This is useful for registries that are not needed until after other parts
|
||||
* of the lifecycle have been completed.
|
||||
* <p>
|
||||
* This class is slightly different from other registries in that it acts as
|
||||
* a wrapper around another registry. This is to allow for any kind of registry
|
||||
* type to be deferred.
|
||||
*
|
||||
* @param <M> the value being held by the registry
|
||||
*/
|
||||
public final class DeferredRegistry<M> implements IRegistry<M> {
|
||||
private final Registry<M> backingRegistry;
|
||||
private final Supplier<M> loader;
|
||||
|
||||
private boolean loaded;
|
||||
|
||||
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
|
||||
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.load(null);
|
||||
}
|
||||
|
||||
private <I> DeferredRegistry(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
this.backingRegistry = registryLoader.apply(RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.get().load(null);
|
||||
}
|
||||
|
||||
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
|
||||
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.load(input);
|
||||
}
|
||||
|
||||
private <I> DeferredRegistry(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
this.backingRegistry = registryInitializer.initialize(input, RegistryLoaders.uninitialized());
|
||||
this.loader = () -> deferredLoader.get().load(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying value held by this registry.
|
||||
*
|
||||
* @return the underlying value held by this registry
|
||||
* @throws IllegalStateException if this deferred registry has not been loaded yet
|
||||
*/
|
||||
@Override
|
||||
public M get() {
|
||||
if (!this.loaded) {
|
||||
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||
}
|
||||
|
||||
return this.backingRegistry.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(M mappings) {
|
||||
this.backingRegistry.set(mappings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers what is specified in the given {@link Consumer} into the underlying value.
|
||||
*
|
||||
* @param consumer the consumer
|
||||
* @throws IllegalStateException if this deferred registry has not been loaded yet
|
||||
*/
|
||||
@Override
|
||||
public void register(Consumer<M> consumer) {
|
||||
if (!this.loaded) {
|
||||
throw new IllegalStateException("Registry has not been loaded yet!");
|
||||
}
|
||||
|
||||
this.backingRegistry.register(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the registry.
|
||||
*/
|
||||
public void load() {
|
||||
this.backingRegistry.set(this.loader.get());
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryLoader the registry loader
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, RegistryLoader<I, M> deferredLoader) {
|
||||
return new DeferredRegistry<>(registryLoader, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryLoader the registry loader
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(Function<RegistryLoader<I, M>, Registry<M>> registryLoader, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
return new DeferredRegistry<>(registryLoader, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryInitializer the registry initializer
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, RegistryLoader<I, M> deferredLoader) {
|
||||
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deferred registry.
|
||||
*
|
||||
* @param registryInitializer the registry initializer
|
||||
* @param deferredLoader the deferred loader
|
||||
* @param <I> the input type
|
||||
* @param <M> the registry type
|
||||
* @return the new deferred registry
|
||||
*/
|
||||
public static <I, M> DeferredRegistry<M> create(I input, RegistryInitializer<M> registryInitializer, Supplier<RegistryLoader<I, M>> deferredLoader) {
|
||||
return new DeferredRegistry<>(input, registryInitializer, deferredLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* A registry initializer.
|
||||
*
|
||||
* @param <M> the registry type
|
||||
*/
|
||||
interface RegistryInitializer<M> {
|
||||
|
||||
/**
|
||||
* Initializes the registry.
|
||||
*
|
||||
* @param input the input
|
||||
* @param registryLoader the registry loader
|
||||
* @param <I> the input type
|
||||
* @return the initialized registry
|
||||
*/
|
||||
<I> Registry<M> initialize(I input, RegistryLoader<I, M> registryLoader);
|
||||
}
|
||||
}
|
60
core/src/main/java/org/geysermc/geyser/registry/IRegistry.java
Normale Datei
60
core/src/main/java/org/geysermc/geyser/registry/IRegistry.java
Normale Datei
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Represents a registry.
|
||||
*
|
||||
* @param <M> the value being held by the registry
|
||||
*/
|
||||
interface IRegistry<M> {
|
||||
|
||||
/**
|
||||
* Gets the underlying value held by this registry.
|
||||
*
|
||||
* @return the underlying value held by this registry.
|
||||
*/
|
||||
M get();
|
||||
|
||||
/**
|
||||
* Sets the underlying value held by this registry.
|
||||
* Clears any existing data associated with the previous
|
||||
* value.
|
||||
*
|
||||
* @param mappings the underlying value held by this registry
|
||||
*/
|
||||
void set(M mappings);
|
||||
|
||||
/**
|
||||
* Registers what is specified in the given
|
||||
* {@link Consumer} into the underlying value.
|
||||
*
|
||||
* @param consumer the consumer
|
||||
*/
|
||||
void register(Consumer<M> consumer);
|
||||
}
|
@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.registry;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDelimiterPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket;
|
||||
import io.netty.channel.EventLoop;
|
||||
@ -44,6 +45,7 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
|
||||
static {
|
||||
IGNORED_PACKETS.add(ClientboundLightUpdatePacket.class); // Light is handled on Bedrock for us
|
||||
IGNORED_PACKETS.add(ClientboundTabListPacket.class); // Cant be implemented in Bedrock
|
||||
IGNORED_PACKETS.add(ClientboundDelimiterPacket.class); // Not implemented, spams logs
|
||||
}
|
||||
|
||||
protected PacketTranslatorRegistry() {
|
||||
|
@ -41,6 +41,8 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.PotionMixData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.RecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.inventory.item.Enchantment.JavaEnchantment;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
@ -158,6 +160,11 @@ public final class Registries {
|
||||
*/
|
||||
public static final IntMappedRegistry<org.cloudburstmc.protocol.bedrock.data.SoundEvent> RECORDS = IntMappedRegistry.create(RegistryLoaders.empty(Int2ObjectOpenHashMap::new));
|
||||
|
||||
/**
|
||||
* A mapped registry holding {@link ResourcePack}'s with the pack uuid as keys.
|
||||
*/
|
||||
public static final DeferredRegistry<Map<String, ResourcePack>> RESOURCE_PACKS = DeferredRegistry.create(GeyserImpl.getInstance().packDirectory(), SimpleMappedRegistry::create, RegistryLoaders.RESOURCE_PACKS);
|
||||
|
||||
/**
|
||||
* A mapped registry holding sound identifiers to their corresponding {@link SoundMapping}.
|
||||
*/
|
||||
|
@ -64,7 +64,7 @@ import java.util.function.Consumer;
|
||||
*
|
||||
* @param <M> the value being held by the registry
|
||||
*/
|
||||
public abstract class Registry<M> {
|
||||
public abstract class Registry<M> implements IRegistry<M> {
|
||||
protected M mappings;
|
||||
|
||||
/**
|
||||
@ -85,6 +85,7 @@ public abstract class Registry<M> {
|
||||
*
|
||||
* @return the underlying value held by this registry.
|
||||
*/
|
||||
@Override
|
||||
public M get() {
|
||||
return this.mappings;
|
||||
}
|
||||
@ -96,6 +97,7 @@ public abstract class Registry<M> {
|
||||
*
|
||||
* @param mappings the underlying value held by this registry
|
||||
*/
|
||||
@Override
|
||||
public void set(M mappings) {
|
||||
this.mappings = mappings;
|
||||
}
|
||||
@ -106,6 +108,7 @@ public abstract class Registry<M> {
|
||||
*
|
||||
* @param consumer the consumer
|
||||
*/
|
||||
@Override
|
||||
public void register(Consumer<M> consumer) {
|
||||
consumer.accept(this.mappings);
|
||||
}
|
||||
|
@ -31,13 +31,16 @@ import org.geysermc.geyser.api.extension.Extension;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemData;
|
||||
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
|
||||
import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.api.pack.PathPackCodec;
|
||||
import org.geysermc.geyser.command.GeyserCommandManager;
|
||||
import org.geysermc.geyser.event.GeyserEventRegistrar;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemData;
|
||||
import org.geysermc.geyser.item.GeyserCustomItemOptions;
|
||||
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.registry.provider.ProviderSupplier;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -47,11 +50,15 @@ public class ProviderRegistryLoader implements RegistryLoader<Map<Class<?>, Prov
|
||||
|
||||
@Override
|
||||
public Map<Class<?>, ProviderSupplier> load(Map<Class<?>, ProviderSupplier> providers) {
|
||||
// misc
|
||||
providers.put(Command.Builder.class, args -> new GeyserCommandManager.CommandBuilder<>((Extension) args[0]));
|
||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||
providers.put(PathPackCodec.class, args -> new GeyserPathPackCodec((Path) args[0]));
|
||||
|
||||
// items
|
||||
providers.put(CustomItemData.Builder.class, args -> new GeyserCustomItemData.CustomItemDataBuilder());
|
||||
providers.put(CustomItemOptions.Builder.class, args -> new GeyserCustomItemOptions.CustomItemOptionsBuilder());
|
||||
providers.put(NonVanillaCustomItemData.Builder.class, args -> new GeyserNonVanillaCustomItemData.NonVanillaCustomItemDataBuilder());
|
||||
providers.put(EventRegistrar.class, args -> new GeyserEventRegistrar(args[0]));
|
||||
|
||||
return providers;
|
||||
}
|
||||
|
@ -36,7 +36,12 @@ public final class RegistryLoaders {
|
||||
/**
|
||||
* The {@link RegistryLoader} responsible for loading NBT.
|
||||
*/
|
||||
public static NbtRegistryLoader NBT = new NbtRegistryLoader();
|
||||
public static final NbtRegistryLoader NBT = new NbtRegistryLoader();
|
||||
|
||||
/**
|
||||
* The {@link RegistryLoader} responsible for loading resource packs.
|
||||
*/
|
||||
public static final ResourcePackLoader RESOURCE_PACKS = new ResourcePackLoader();
|
||||
|
||||
/**
|
||||
* Wraps the surrounding {@link Supplier} in a {@link RegistryLoader} which does
|
||||
@ -51,10 +56,14 @@ public final class RegistryLoaders {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RegistryLoader} which has not taken
|
||||
* in any input value.
|
||||
*
|
||||
* @param <I> the input
|
||||
* @param <V> the value
|
||||
* @return a RegistryLoader that is yet to contain a value.
|
||||
*/
|
||||
public static <V> RegistryLoader<Object, V> uninitialized() {
|
||||
public static <I, V> RegistryLoader<I, V> uninitialized() {
|
||||
return input -> null;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.registry.loader;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.event.lifecycle.GeyserLoadResourcePacksEvent;
|
||||
import org.geysermc.geyser.api.pack.ResourcePack;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePack;
|
||||
import org.geysermc.geyser.pack.GeyserResourcePackManifest;
|
||||
import org.geysermc.geyser.pack.path.GeyserPathPackCodec;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.util.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* Loads {@link ResourcePack}s within a {@link Path} directory, firing the {@link GeyserLoadResourcePacksEvent}.
|
||||
*/
|
||||
public class ResourcePackLoader implements RegistryLoader<Path, Map<String, ResourcePack>> {
|
||||
|
||||
static final PathMatcher PACK_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.{zip,mcpack}");
|
||||
|
||||
private static final boolean SHOW_RESOURCE_PACK_LENGTH_WARNING = Boolean.parseBoolean(System.getProperty("Geyser.ShowResourcePackLengthWarning", "true"));
|
||||
|
||||
/**
|
||||
* Loop through the packs directory and locate valid resource pack files
|
||||
*/
|
||||
@Override
|
||||
public Map<String, ResourcePack> load(Path directory) {
|
||||
Map<String, ResourcePack> packMap = new HashMap<>();
|
||||
|
||||
if (!Files.exists(directory)) {
|
||||
try {
|
||||
Files.createDirectory(directory);
|
||||
} catch (IOException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not create packs directory", e);
|
||||
}
|
||||
}
|
||||
|
||||
List<Path> resourcePacks;
|
||||
try (Stream<Path> stream = Files.walk(directory)) {
|
||||
resourcePacks = stream.filter(PACK_MATCHER::matches)
|
||||
.collect(Collectors.toCollection(ArrayList::new)); // toList() does not guarantee mutability
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not list packs directory", e);
|
||||
|
||||
// Ensure the event is fired even if there was an issue reading
|
||||
// from our own resource pack directory. External projects may have
|
||||
// resource packs located at different locations.
|
||||
resourcePacks = new ArrayList<>();
|
||||
}
|
||||
|
||||
GeyserLoadResourcePacksEvent event = new GeyserLoadResourcePacksEvent(resourcePacks);
|
||||
GeyserImpl.getInstance().eventBus().fire(event);
|
||||
|
||||
for (Path path : event.resourcePacks()) {
|
||||
try {
|
||||
GeyserResourcePack pack = readPack(path);
|
||||
packMap.put(pack.manifest().header().uuid().toString(), pack);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return packMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a resource pack at the given file. Also searches for a file in the same directory, with the same name
|
||||
* but suffixed by ".key", containing the content key. If such file does not exist, no content key is stored.
|
||||
*
|
||||
* @param path the file to read from, in ZIP format
|
||||
* @return a {@link ResourcePack} representation
|
||||
* @throws IllegalArgumentException if the pack manifest was invalid or there was any processing exception
|
||||
*/
|
||||
public static GeyserResourcePack readPack(Path path) throws IllegalArgumentException {
|
||||
if (!path.getFileName().toString().endsWith(".mcpack") && !path.getFileName().toString().endsWith(".zip")) {
|
||||
throw new IllegalArgumentException("Resource pack " + path.getFileName() + " must be a .zip or .mcpack file!");
|
||||
}
|
||||
|
||||
AtomicReference<GeyserResourcePackManifest> manifestReference = new AtomicReference<>();
|
||||
|
||||
try (ZipFile zip = new ZipFile(path.toFile());
|
||||
Stream<? extends ZipEntry> stream = zip.stream()) {
|
||||
stream.forEach(x -> {
|
||||
String name = x.getName();
|
||||
if (SHOW_RESOURCE_PACK_LENGTH_WARNING && name.length() >= 80) {
|
||||
GeyserImpl.getInstance().getLogger().warning("The resource pack " + path.getFileName()
|
||||
+ " has a file in it that meets or exceeds 80 characters in its path (" + name
|
||||
+ ", " + name.length() + " characters long). This will cause problems on some Bedrock platforms." +
|
||||
" Please rename it to be shorter, or reduce the amount of folders needed to get to the file.");
|
||||
}
|
||||
if (name.contains("manifest.json")) {
|
||||
try {
|
||||
GeyserResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), GeyserResourcePackManifest.class);
|
||||
if (manifest.header().uuid() != null) {
|
||||
manifestReference.set(manifest);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
GeyserResourcePackManifest manifest = manifestReference.get();
|
||||
if (manifest == null) {
|
||||
throw new IllegalArgumentException(path.getFileName() + " does not contain a valid pack_manifest.json or manifest.json");
|
||||
}
|
||||
|
||||
// Check if a file exists with the same name as the resource pack suffixed by .key,
|
||||
// and set this as content key. (e.g. test.zip, key file would be test.zip.key)
|
||||
Path keyFile = path.resolveSibling(path.getFileName().toString() + ".key");
|
||||
String contentKey = Files.exists(keyFile) ? Files.readString(path, StandardCharsets.UTF_8) : "";
|
||||
|
||||
return new GeyserResourcePack(new GeyserPathPackCodec(path), manifest, contentKey);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(GeyserLocale.getLocaleStringLog("geyser.resource_pack.broken", path.getFileName()), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -32,11 +32,8 @@ import com.google.common.collect.Interner;
|
||||
import com.google.common.collect.Interners;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.cloudburstmc.nbt.*;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
@ -70,41 +67,54 @@ public final class BlockRegistryPopulator {
|
||||
}
|
||||
|
||||
private static void registerBedrockBlocks() {
|
||||
BiFunction<String, NbtMapBuilder, String> woolMapper = (bedrockIdentifier, statesBuilder) -> {
|
||||
if (bedrockIdentifier.equals("minecraft:wool")) {
|
||||
String color = (String) statesBuilder.remove("color");
|
||||
if ("silver".equals(color)) {
|
||||
color = "light_gray";
|
||||
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
|
||||
|
||||
// We are using mappings that directly support 1.20, so this maps it back to 1.19.80
|
||||
BiFunction<String, NbtMapBuilder, String> legacyMapper = (bedrockIdentifier, statesBuilder) -> {
|
||||
if (bedrockIdentifier.endsWith("pumpkin")) {
|
||||
String direction = statesBuilder.remove("minecraft:cardinal_direction").toString();
|
||||
statesBuilder.putInt("direction", switch (direction) {
|
||||
case "north" -> 2;
|
||||
case "east" -> 3;
|
||||
case "west" -> 1;
|
||||
default -> 0; // south
|
||||
});
|
||||
} else if (bedrockIdentifier.endsWith("carpet") && !bedrockIdentifier.startsWith("minecraft:moss")) {
|
||||
String color = bedrockIdentifier.replace("minecraft:", "").replace("_carpet", "");
|
||||
if (color.equals("light_gray")) {
|
||||
color = "silver";
|
||||
}
|
||||
return "minecraft:" + color + "_wool";
|
||||
statesBuilder.putString("color", color);
|
||||
return "minecraft:carpet";
|
||||
} else if (bedrockIdentifier.equals("minecraft:sniffer_egg")) {
|
||||
statesBuilder.remove("cracked_state");
|
||||
return "minecraft:dragon_egg";
|
||||
} else if (bedrockIdentifier.endsWith("coral")) {
|
||||
statesBuilder.putString("coral_color", "blue"); // all blue
|
||||
statesBuilder.putBoolean("dead_bit", bedrockIdentifier.startsWith("minecraft:dead"));
|
||||
return "minecraft:coral";
|
||||
} else if (bedrockIdentifier.endsWith("sculk_sensor")) {
|
||||
int phase = (int) statesBuilder.remove("sculk_sensor_phase");
|
||||
statesBuilder.putBoolean("powered_bit", phase != 0);
|
||||
} else if (bedrockIdentifier.endsWith("pitcher_plant")) {
|
||||
statesBuilder.putString("double_plant_type", "sunflower");
|
||||
return "minecraft:double_plant";
|
||||
} else if (bedrockIdentifier.endsWith("pitcher_crop")) {
|
||||
statesBuilder.remove("growth");
|
||||
if (((byte) statesBuilder.remove("upper_block_bit")) == 1){
|
||||
statesBuilder.putString("flower_type", "orchid");
|
||||
return "minecraft:red_flower"; // top
|
||||
}
|
||||
statesBuilder.putBoolean("update_bit", false);
|
||||
return "minecraft:flower_pot"; // bottom
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
BiFunction<String, NbtMapBuilder, String> emptyMapper = (bedrockIdentifier, statesBuilder) -> null;
|
||||
|
||||
ImmutableMap<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>> blockMappers = ImmutableMap.<ObjectIntPair<String>, BiFunction<String, NbtMapBuilder, String>>builder()
|
||||
.put(ObjectIntPair.of("1_19_20", Bedrock_v544.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_19_50", Bedrock_v560.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_19_60", Bedrock_v567.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.put(ObjectIntPair.of("1_19_70", Bedrock_v575.CODEC.getProtocolVersion()), woolMapper)
|
||||
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), (bedrockIdentifier, statesBuilder) -> {
|
||||
String identifier = woolMapper.apply(bedrockIdentifier, statesBuilder);
|
||||
if (identifier != null) {
|
||||
return identifier;
|
||||
}
|
||||
switch (bedrockIdentifier) {
|
||||
case "minecraft:log", "minecraft:log2" -> {
|
||||
String woodType = (String) statesBuilder.remove(bedrockIdentifier.equals("minecraft:log") ? "old_log_type" : "new_log_type");
|
||||
return "minecraft:" + woodType + "_log";
|
||||
}
|
||||
case "minecraft:fence" -> {
|
||||
String woodType = (String) statesBuilder.remove("wood_type");
|
||||
return "minecraft:" + woodType + "_fence";
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.put(ObjectIntPair.of("1_19_80", Bedrock_v582.CODEC.getProtocolVersion()), legacyMapper)
|
||||
.put(ObjectIntPair.of("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()), emptyMapper)
|
||||
.build();
|
||||
|
||||
// We can keep this strong as nothing should be garbage collected
|
||||
@ -167,8 +177,8 @@ public final class BlockRegistryPopulator {
|
||||
|
||||
GeyserBedrockBlock bedrockDefinition = blockStateOrderedMap.get(buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
if (bedrockDefinition == null) {
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition! Built NBT tag: \n" +
|
||||
buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
throw new RuntimeException("Unable to find " + javaId + " Bedrock BlockDefinition on version "
|
||||
+ palette.getKey().key() + "! Built NBT tag: \n" + buildBedrockState(entry.getValue(), stateVersion, stateMapper));
|
||||
}
|
||||
|
||||
switch (javaId) {
|
||||
|
@ -54,18 +54,18 @@ public class CreativeItemRegistryPopulator {
|
||||
(identifier, data) -> identifier.equals("minecraft:bordure_indented_banner_pattern") || identifier.equals("minecraft:field_masoned_banner_pattern")
|
||||
);
|
||||
|
||||
static void populate(Map.Entry<String, ItemRegistryPopulator.PaletteVersion> version, Map<String, ItemDefinition> definitions, Consumer<ItemData.Builder> itemConsumer) {
|
||||
static void populate(ItemRegistryPopulator.PaletteVersion palette, Map<String, ItemDefinition> definitions, Consumer<ItemData.Builder> itemConsumer) {
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
// Load creative items
|
||||
JsonNode creativeItemEntries;
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", version.getKey()))) {
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/creative_items.%s.json", palette.version()))) {
|
||||
creativeItemEntries = GeyserImpl.JSON_MAPPER.readTree(stream).get("items");
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load creative items", e);
|
||||
}
|
||||
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(version.getValue().protocolVersion());
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
|
||||
for (JsonNode itemNode : creativeItemEntries) {
|
||||
ItemData.Builder itemBuilder = createItemData(itemNode, blockMappings, definitions);
|
||||
if (itemBuilder == null) {
|
||||
|
@ -34,14 +34,12 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.objects.*;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v544.Bedrock_v544;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v560.Bedrock_v560;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v567.Bedrock_v567;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v575.Bedrock_v575;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v582.Bedrock_v582;
|
||||
import org.cloudburstmc.protocol.bedrock.codec.v589.Bedrock_v589;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.SimpleItemDefinition;
|
||||
@ -70,22 +68,40 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
*/
|
||||
public class ItemRegistryPopulator {
|
||||
|
||||
record PaletteVersion(int protocolVersion, Map<Item, String> additionalTranslatedItems) {
|
||||
record PaletteVersion(String version, int protocolVersion, Map<Item, String> javaOnlyItems, Remapper remapper) {
|
||||
|
||||
public PaletteVersion(String version, int protocolVersion) {
|
||||
this(version, protocolVersion, Collections.emptyMap(), (item, mapping) -> mapping);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface Remapper {
|
||||
@NonNull
|
||||
GeyserMappingItem remap(Item item, GeyserMappingItem mapping);
|
||||
}
|
||||
|
||||
public static void populate() {
|
||||
Map<Item, String> manualFallback = new HashMap<>();
|
||||
manualFallback.put(Items.ENDER_DRAGON_SPAWN_EGG, "minecraft:enderman_spawn_egg");
|
||||
manualFallback.put(Items.WITHER_SPAWN_EGG, "minecraft:wither_skeleton_spawn_egg");
|
||||
manualFallback.put(Items.SNOW_GOLEM_SPAWN_EGG, "minecraft:polar_bear_spawn_egg");
|
||||
manualFallback.put(Items.IRON_GOLEM_SPAWN_EGG, "minecraft:villager_spawn_egg");
|
||||
Map<Item, String> legacyJavaOnly = new HashMap<>();
|
||||
legacyJavaOnly.put(Items.MUSIC_DISC_RELIC, "minecraft:music_disc_wait");
|
||||
legacyJavaOnly.put(Items.PITCHER_PLANT, "minecraft:chorus_flower");
|
||||
legacyJavaOnly.put(Items.PITCHER_POD, "minecraft:beetroot");
|
||||
legacyJavaOnly.put(Items.SNIFFER_EGG, "minecraft:sniffer_spawn_egg"); // the BlockItem of the sniffer egg block
|
||||
|
||||
Map<String, PaletteVersion> paletteVersions = new Object2ObjectOpenHashMap<>();
|
||||
paletteVersions.put("1_19_20", new PaletteVersion(Bedrock_v544.CODEC.getProtocolVersion(), manualFallback));
|
||||
paletteVersions.put("1_19_50", new PaletteVersion(Bedrock_v560.CODEC.getProtocolVersion(), manualFallback));
|
||||
paletteVersions.put("1_19_60", new PaletteVersion(Bedrock_v567.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
paletteVersions.put("1_19_70", new PaletteVersion(Bedrock_v575.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
paletteVersions.put("1_19_80", new PaletteVersion(Bedrock_v582.CODEC.getProtocolVersion(), Collections.emptyMap()));
|
||||
List<PaletteVersion> paletteVersions = new ArrayList<>(2);
|
||||
paletteVersions.add(new PaletteVersion("1_19_80", Bedrock_v582.CODEC.getProtocolVersion(), legacyJavaOnly, (item, mapping) -> {
|
||||
String id = item.javaIdentifier();
|
||||
if (id.endsWith("pottery_sherd")) {
|
||||
return mapping.withBedrockIdentifier(id.replace("sherd", "shard"));
|
||||
} else if (id.endsWith("carpet") && !id.startsWith("minecraft:moss")) {
|
||||
return mapping.withBedrockIdentifier("minecraft:carpet");
|
||||
} else if (id.endsWith("coral")) {
|
||||
return mapping.withBedrockIdentifier("minecraft:coral");
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}));
|
||||
paletteVersions.add(new PaletteVersion("1_20_0", Bedrock_v589.CODEC.getProtocolVersion()));
|
||||
|
||||
GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap();
|
||||
|
||||
@ -115,11 +131,11 @@ public class ItemRegistryPopulator {
|
||||
boolean firstMappingsPass = true;
|
||||
|
||||
/* Load item palette */
|
||||
for (Map.Entry<String, PaletteVersion> palette : paletteVersions.entrySet()) {
|
||||
for (PaletteVersion palette : paletteVersions) {
|
||||
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
|
||||
|
||||
List<PaletteItem> itemEntries;
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.getKey()))) {
|
||||
try (InputStream stream = bootstrap.getResource(String.format("bedrock/runtime_item_states.%s.json", palette.version()))) {
|
||||
itemEntries = GeyserImpl.JSON_MAPPER.readValue(stream, paletteEntriesType);
|
||||
} catch (Exception e) {
|
||||
throw new AssertionError("Unable to load Bedrock runtime item IDs", e);
|
||||
@ -177,22 +193,16 @@ public class ItemRegistryPopulator {
|
||||
}
|
||||
});
|
||||
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.getValue().protocolVersion());
|
||||
BlockMappings blockMappings = BlockRegistries.BLOCKS.forVersion(palette.protocolVersion());
|
||||
|
||||
Set<Item> javaOnlyItems = new ObjectOpenHashSet<>();
|
||||
Collections.addAll(javaOnlyItems, Items.SPECTRAL_ARROW, Items.DEBUG_STICK,
|
||||
Items.KNOWLEDGE_BOOK, Items.TIPPED_ARROW, Items.BUNDLE);
|
||||
// these spawn eggs exist in 1.19.60+;
|
||||
if (palette.getValue().protocolVersion() < Bedrock_v567.CODEC.getProtocolVersion()) {
|
||||
Collections.addAll(javaOnlyItems, Items.IRON_GOLEM_SPAWN_EGG, Items.SNOW_GOLEM_SPAWN_EGG,
|
||||
Items.WITHER_SPAWN_EGG, Items.ENDER_DRAGON_SPAWN_EGG);
|
||||
}
|
||||
javaOnlyItems.add(Items.DECORATED_POT);
|
||||
if (!customItemsAllowed) {
|
||||
javaOnlyItems.add(Items.FURNACE_MINECART);
|
||||
}
|
||||
// Java-only items for this version
|
||||
javaOnlyItems.addAll(palette.getValue().additionalTranslatedItems().keySet());
|
||||
javaOnlyItems.addAll(palette.javaOnlyItems().keySet());
|
||||
|
||||
Int2ObjectMap<String> customIdMappings = new Int2ObjectOpenHashMap<>();
|
||||
Set<String> registeredItemNames = new ObjectOpenHashSet<>(); // This is used to check for duplicate item names
|
||||
@ -203,12 +213,12 @@ public class ItemRegistryPopulator {
|
||||
throw new RuntimeException("Extra item in mappings? " + entry.getKey());
|
||||
}
|
||||
GeyserMappingItem mappingItem;
|
||||
String replacementItem = palette.getValue().additionalTranslatedItems().get(javaItem);
|
||||
String replacementItem = palette.javaOnlyItems().get(javaItem);
|
||||
if (replacementItem != null) {
|
||||
mappingItem = items.get(replacementItem);
|
||||
mappingItem = items.get(replacementItem); // java only item, a java id fallback has been provided
|
||||
} else {
|
||||
// This items has a mapping specifically for this version of the game
|
||||
mappingItem = entry.getValue();
|
||||
// check if any mapping changes need to be made on this version
|
||||
mappingItem = palette.remapper().remap(javaItem, entry.getValue());
|
||||
}
|
||||
|
||||
if (customItemsAllowed && javaItem == Items.FURNACE_MINECART) {
|
||||
@ -217,26 +227,10 @@ public class ItemRegistryPopulator {
|
||||
continue;
|
||||
}
|
||||
|
||||
String bedrockIdentifier;
|
||||
// 1.19.70+
|
||||
if (palette.getValue().protocolVersion() >= Bedrock_v575.CODEC.getProtocolVersion() && mappingItem.getBedrockIdentifier().equals("minecraft:wool")) {
|
||||
bedrockIdentifier = javaItem.javaIdentifier();
|
||||
} else {
|
||||
bedrockIdentifier = mappingItem.getBedrockIdentifier();
|
||||
}
|
||||
|
||||
//1.19.80+
|
||||
if (palette.getValue().protocolVersion >= Bedrock_v582.CODEC.getProtocolVersion()) {
|
||||
if (mappingItem.getBedrockIdentifier().equals("minecraft:log") ||
|
||||
mappingItem.getBedrockIdentifier().equals("minecraft:log2") ||
|
||||
mappingItem.getBedrockIdentifier().equals("minecraft:fence")) {
|
||||
bedrockIdentifier = javaItem.javaIdentifier();
|
||||
}
|
||||
}
|
||||
|
||||
String bedrockIdentifier = mappingItem.getBedrockIdentifier();
|
||||
ItemDefinition definition = definitions.get(bedrockIdentifier);
|
||||
if (definition == null) {
|
||||
throw new RuntimeException("Missing Bedrock ItemDefinition in mappings: " + bedrockIdentifier);
|
||||
throw new RuntimeException("Missing Bedrock ItemDefinition in version " + palette.version() + " for mapping: " + mappingItem);
|
||||
}
|
||||
|
||||
BlockDefinition bedrockBlock = null;
|
||||
@ -430,7 +424,7 @@ public class ItemRegistryPopulator {
|
||||
} else if (javaItem.javaIdentifier().startsWith("minecraft:music_disc_")) {
|
||||
// The Java record level event uses the item ID as the "key" to play the record
|
||||
Registries.RECORDS.register(javaItem.javaId(), SoundEvent.valueOf("RECORD_" +
|
||||
javaItem.javaIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH)));
|
||||
mapping.getBedrockIdentifier().replace("minecraft:music_disc_", "").toUpperCase(Locale.ENGLISH)));
|
||||
}
|
||||
|
||||
mappings.add(mapping);
|
||||
@ -521,7 +515,7 @@ public class ItemRegistryPopulator {
|
||||
.customIdMappings(customIdMappings)
|
||||
.build();
|
||||
|
||||
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);
|
||||
Registries.ITEMS.register(palette.protocolVersion(), itemMappings);
|
||||
|
||||
firstMappingsPass = false;
|
||||
}
|
||||
|
@ -26,12 +26,22 @@
|
||||
package org.geysermc.geyser.registry.type;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.With;
|
||||
|
||||
/**
|
||||
* Represents Geyser's own serialized item information before being processed per-version
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@With
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class GeyserMappingItem {
|
||||
@JsonProperty("bedrock_identifier") String bedrockIdentifier;
|
||||
@JsonProperty("bedrock_data") int bedrockData;
|
||||
|
@ -56,7 +56,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.Server
|
||||
import com.github.steveice10.mc.protocol.packet.login.serverbound.ServerboundCustomQueryPacket;
|
||||
import com.github.steveice10.packetlib.BuiltinFlags;
|
||||
import com.github.steveice10.packetlib.Session;
|
||||
import com.github.steveice10.packetlib.event.session.*;
|
||||
import com.github.steveice10.packetlib.event.session.ConnectedEvent;
|
||||
import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
|
||||
import com.github.steveice10.packetlib.event.session.PacketErrorEvent;
|
||||
import com.github.steveice10.packetlib.event.session.PacketSendingEvent;
|
||||
import com.github.steveice10.packetlib.event.session.SessionAdapter;
|
||||
import com.github.steveice10.packetlib.packet.Packet;
|
||||
import com.github.steveice10.packetlib.tcp.TcpClientSession;
|
||||
import com.github.steveice10.packetlib.tcp.TcpSession;
|
||||
@ -81,6 +85,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.checkerframework.common.value.qual.IntRange;
|
||||
import org.cloudburstmc.math.vector.*;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockDisconnectReasons;
|
||||
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
|
||||
import org.cloudburstmc.protocol.bedrock.data.*;
|
||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData;
|
||||
@ -93,7 +98,7 @@ import org.cloudburstmc.protocol.common.util.OptionalBoolean;
|
||||
import org.geysermc.api.util.BedrockPlatform;
|
||||
import org.geysermc.api.util.InputMode;
|
||||
import org.geysermc.api.util.UiProfile;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.cumulus.form.Form;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
@ -103,6 +108,7 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.api.connection.GeyserConnection;
|
||||
import org.geysermc.geyser.api.entity.type.GeyserEntity;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.api.event.bedrock.SessionLoginEvent;
|
||||
import org.geysermc.geyser.api.network.AuthType;
|
||||
import org.geysermc.geyser.api.network.RemoteServer;
|
||||
import org.geysermc.geyser.command.GeyserCommandSource;
|
||||
@ -123,6 +129,7 @@ import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.network.netty.LocalSession;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.BlockMappings;
|
||||
@ -877,6 +884,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
* After getting whatever credentials needed, we attempt to join the Java server.
|
||||
*/
|
||||
private void connectDownstream() {
|
||||
SessionLoginEvent loginEvent = new SessionLoginEvent(this, remoteServer);
|
||||
GeyserImpl.getInstance().eventBus().fire(loginEvent);
|
||||
if (loginEvent.isCancelled()) {
|
||||
String disconnectReason = loginEvent.disconnectReason() == null ?
|
||||
BedrockDisconnectReasons.DISCONNECTED : loginEvent.disconnectReason();
|
||||
disconnect(disconnectReason);
|
||||
return;
|
||||
}
|
||||
|
||||
this.remoteServer = loginEvent.remoteServer();
|
||||
boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE;
|
||||
|
||||
// Start ticking
|
||||
@ -1539,6 +1556,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
startGamePacket.setRewindHistorySize(0);
|
||||
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
||||
|
||||
if (GameProtocol.isPre1_20(this)) {
|
||||
startGamePacket.getExperiments().add(new ExperimentData("next_major_update", true));
|
||||
startGamePacket.getExperiments().add(new ExperimentData("sniffer", true));
|
||||
}
|
||||
|
||||
upstream.sendPacket(startGamePacket);
|
||||
}
|
||||
|
||||
@ -1942,8 +1964,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
||||
}
|
||||
|
||||
EmotePacket packet = new EmotePacket();
|
||||
packet.setEmoteId(emoteId);
|
||||
packet.setRuntimeEntityId(entity.getGeyserId());
|
||||
packet.setXuid("");
|
||||
packet.setPlatformId(""); // BDS sends empty
|
||||
packet.setEmoteId(emoteId);
|
||||
sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ public class BookEditCache {
|
||||
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
|
||||
// Don't send the update if the player is not holding a book, shouldn't happen if we catch all interactions
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
|
||||
if (itemStack == null || itemStack.asItem() != Items.WRITABLE_BOOK) {
|
||||
packet = null;
|
||||
|
@ -64,6 +64,7 @@ public class TagCache {
|
||||
private IntList foxFood;
|
||||
private IntList piglinLoved;
|
||||
private IntList smallFlowers;
|
||||
private IntList snifferFood;
|
||||
|
||||
public TagCache() {
|
||||
// Ensure all lists are non-null
|
||||
@ -101,6 +102,7 @@ public class TagCache {
|
||||
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
|
||||
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
|
||||
this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers"));
|
||||
this.snifferFood = load(itemTags.get("minecraft:sniffer_food"));
|
||||
|
||||
// Hack btw
|
||||
boolean emulatePost1_13Logic = itemTags.get("minecraft:signs").length > 1;
|
||||
@ -137,6 +139,7 @@ public class TagCache {
|
||||
this.foxFood = IntLists.emptyList();
|
||||
this.piglinLoved = IntLists.emptyList();
|
||||
this.smallFlowers = IntLists.emptyList();
|
||||
this.snifferFood = IntLists.emptyList();
|
||||
}
|
||||
|
||||
public boolean isAxolotlTemptItem(Item item) {
|
||||
@ -167,6 +170,10 @@ public class TagCache {
|
||||
return smallFlowers.contains(itemStack.getJavaId());
|
||||
}
|
||||
|
||||
public boolean isSnifferFood(Item item) {
|
||||
return snifferFood.contains(item.javaId());
|
||||
}
|
||||
|
||||
public boolean isAxeEffective(BlockMapping blockMapping) {
|
||||
return axeEffective.contains(blockMapping.getJavaBlockId());
|
||||
}
|
||||
|
@ -61,6 +61,10 @@ public final class WorldCache {
|
||||
private int currentSequence;
|
||||
private final Object2IntMap<Vector3i> unverifiedPredictions = new Object2IntOpenHashMap<>(1);
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean editingSignOnFront;
|
||||
|
||||
public WorldCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
this.scoreboard = new Scoreboard(session);
|
||||
|
@ -30,10 +30,9 @@ import org.geysermc.geyser.util.AssetUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@ -80,14 +79,14 @@ public final class ProvidedSkins {
|
||||
.resolve(slim ? "slim" : "wide");
|
||||
String assetName = asset.substring(asset.lastIndexOf('/') + 1);
|
||||
|
||||
File location = folder.resolve(assetName).toFile();
|
||||
AssetUtils.addTask(!location.exists(), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
|
||||
Path location = folder.resolve(assetName);
|
||||
AssetUtils.addTask(!Files.exists(location), new AssetUtils.ClientJarTask("assets/minecraft/" + asset,
|
||||
(stream) -> AssetUtils.saveFile(location, stream),
|
||||
() -> {
|
||||
try {
|
||||
// TODO lazy initialize?
|
||||
BufferedImage image;
|
||||
try (InputStream stream = new FileInputStream(location)) {
|
||||
try (InputStream stream = Files.newInputStream(location)) {
|
||||
image = ImageIO.read(stream);
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import org.geysermc.geyser.util.WebUtils;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
@ -57,8 +58,8 @@ public class MinecraftLocale {
|
||||
}
|
||||
|
||||
public static void ensureEN_US() {
|
||||
File localeFile = getFile("en_us");
|
||||
AssetUtils.addTask(!localeFile.exists(), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
|
||||
Path localeFile = getPath("en_us");
|
||||
AssetUtils.addTask(!Files.exists(localeFile), new AssetUtils.ClientJarTask("assets/minecraft/lang/en_us.json",
|
||||
(stream) -> AssetUtils.saveFile(localeFile, stream),
|
||||
() -> {
|
||||
if ("en_us".equals(GeyserLocale.getDefaultLocale())) {
|
||||
@ -106,10 +107,10 @@ public class MinecraftLocale {
|
||||
if (locale.equals("en_us")) {
|
||||
return;
|
||||
}
|
||||
File localeFile = getFile(locale);
|
||||
Path localeFile = getPath(locale);
|
||||
|
||||
// Check if we have already downloaded the locale file
|
||||
if (localeFile.exists()) {
|
||||
if (Files.exists(localeFile)) {
|
||||
String curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
|
||||
String targetHash = AssetUtils.getAsset("minecraft/lang/" + locale + ".json").getHash();
|
||||
|
||||
@ -130,8 +131,8 @@ public class MinecraftLocale {
|
||||
}
|
||||
}
|
||||
|
||||
private static File getFile(String locale) {
|
||||
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json").toFile();
|
||||
private static Path getPath(String locale) {
|
||||
return GeyserImpl.getInstance().getBootstrap().getConfigFolder().resolve("locales/" + locale + ".json");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,7 +94,7 @@ public abstract class InventoryTranslator {
|
||||
put(ContainerType.LOOM, new LoomInventoryTranslator());
|
||||
put(ContainerType.MERCHANT, new MerchantInventoryTranslator());
|
||||
put(ContainerType.SHULKER_BOX, new ShulkerInventoryTranslator());
|
||||
put(ContainerType.LEGACY_SMITHING, new SmithingInventoryTranslator());
|
||||
put(ContainerType.SMITHING, new SmithingInventoryTranslator());
|
||||
put(ContainerType.STONECUTTER, new StonecutterInventoryTranslator());
|
||||
|
||||
/* Lectern */
|
||||
@ -525,10 +525,28 @@ public abstract class InventoryTranslator {
|
||||
|
||||
int remainder = transferAction.getCount() % resultSize;
|
||||
int timesToCraft = transferAction.getCount() / resultSize;
|
||||
for (int i = 0; i < timesToCraft; i++) {
|
||||
plan.add(Click.LEFT, sourceSlot);
|
||||
plan.add(Click.LEFT, destSlot);
|
||||
|
||||
if (plan.getCursor().isEmpty()) {
|
||||
// No carried items - move to destination
|
||||
for (int i = 0; i < timesToCraft; i++) {
|
||||
plan.add(Click.LEFT, sourceSlot);
|
||||
plan.add(Click.LEFT, destSlot);
|
||||
}
|
||||
} else {
|
||||
GeyserItemStack cursor = session.getPlayerInventory().getCursor();
|
||||
int tempSlot = findTempSlot(inventory, cursor, true, sourceSlot, destSlot);
|
||||
if (tempSlot == -1) {
|
||||
return rejectRequest(request);
|
||||
}
|
||||
|
||||
plan.add(Click.LEFT, tempSlot); //place cursor into temp slot
|
||||
for (int i = 0; i < timesToCraft; i++) {
|
||||
plan.add(Click.LEFT, sourceSlot); //pick up source item
|
||||
plan.add(Click.LEFT, destSlot); //place source item into dest slot
|
||||
}
|
||||
plan.add(Click.LEFT, tempSlot); //pick up original item
|
||||
}
|
||||
|
||||
if (remainder > 0) {
|
||||
plan.add(Click.LEFT, 0);
|
||||
for (int i = 0; i < remainder; i++) {
|
||||
|
@ -33,15 +33,16 @@ import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
|
||||
|
||||
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||
public SmithingInventoryTranslator() {
|
||||
super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
|
||||
super(4, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData) {
|
||||
return switch (slotInfoData.getContainer()) {
|
||||
case SMITHING_TABLE_INPUT -> 0;
|
||||
case SMITHING_TABLE_MATERIAL -> 1;
|
||||
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 2;
|
||||
case SMITHING_TABLE_TEMPLATE -> 0;
|
||||
case SMITHING_TABLE_INPUT -> 1;
|
||||
case SMITHING_TABLE_MATERIAL -> 2;
|
||||
case SMITHING_TABLE_RESULT, CREATED_OUTPUT -> 3;
|
||||
default -> super.bedrockSlotToJava(slotInfoData);
|
||||
};
|
||||
}
|
||||
@ -49,9 +50,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
|
||||
@Override
|
||||
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
|
||||
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
|
||||
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
|
||||
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
|
||||
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
|
||||
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
|
||||
case 3 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
|
||||
default -> super.javaSlotToBedrockContainer(slot);
|
||||
};
|
||||
}
|
||||
@ -59,9 +61,10 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
|
||||
@Override
|
||||
public int javaSlotToBedrock(int slot) {
|
||||
return switch (slot) {
|
||||
case 0 -> 51;
|
||||
case 1 -> 52;
|
||||
case 2 -> 50;
|
||||
case 0 -> 53;
|
||||
case 1 -> 51;
|
||||
case 2 -> 52;
|
||||
case 3 -> 50;
|
||||
default -> super.javaSlotToBedrock(slot);
|
||||
};
|
||||
}
|
||||
|
@ -62,19 +62,20 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
|
||||
}
|
||||
|
||||
StonecutterContainer container = (StonecutterContainer) inventory;
|
||||
ItemStack javaOutput = craftingData.output();
|
||||
int button = craftingData.buttonId();
|
||||
|
||||
// If we've already pressed the button with this item, no need to press it again!
|
||||
if (container.getStonecutterButton() != button) {
|
||||
ItemStack javaOutput = craftingData.output();
|
||||
|
||||
// Getting the index of the item in the Java stonecutter list
|
||||
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), button);
|
||||
session.sendDownstreamPacket(packet);
|
||||
container.setStonecutterButton(button);
|
||||
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
|
||||
// We don't know there is an output here, so we tell ourselves that there is
|
||||
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
|
||||
}
|
||||
}
|
||||
|
||||
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
|
||||
// We don't know there is an output here, so we tell ourselves that there is
|
||||
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
|
||||
}
|
||||
|
||||
return translateRequest(session, inventory, request);
|
||||
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.BRUSHABLE_BLOCK)
|
||||
public class BrushableBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState {
|
||||
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
if (!(tag.remove("item") instanceof CompoundTag itemTag)) {
|
||||
return;
|
||||
}
|
||||
Tag hitDirection = tag.get("hit_direction");
|
||||
if (hitDirection == null) {
|
||||
// java server sends no direction when the item recedes back into the block (if player stops brushing)
|
||||
return;
|
||||
}
|
||||
|
||||
String id = ((StringTag) itemTag.get("id")).getValue();
|
||||
if (Items.AIR.javaIdentifier().equals(id)) {
|
||||
return; // server sends air when the block contains nothing
|
||||
}
|
||||
|
||||
ItemMapping mapping = Registries.ITEMS.forVersion(GameProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getMapping(id);
|
||||
if (mapping == null) {
|
||||
return;
|
||||
}
|
||||
NbtMapBuilder itemBuilder = NbtMap.builder()
|
||||
.putString("Name", mapping.getBedrockIdentifier())
|
||||
.putByte("Count", (byte) itemTag.get("Count").getValue());
|
||||
|
||||
builder.putCompound("item", itemBuilder.build());
|
||||
// controls which side the item protrudes from
|
||||
builder.putByte("brush_direction", ((Number) hitDirection.getValue()).byteValue());
|
||||
// controls how much the item protrudes
|
||||
builder.putInt("brush_count", BlockStateValues.getBrushProgress(blockState));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.DECORATED_POT)
|
||||
public class DecoratedPotBlockEntityTranslator extends BlockEntityTranslator {
|
||||
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
// exact same format
|
||||
if (tag.get("sherds") instanceof ListTag sherds) {
|
||||
List<String> translated = new ArrayList<>(4);
|
||||
for (Tag sherd : sherds) {
|
||||
translated.add(((StringTag) sherd).getValue());
|
||||
}
|
||||
builder.putList("sherds", NbtType.STRING, translated);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import org.geysermc.geyser.util.SignUtils;
|
||||
|
||||
@BlockEntity(type = BlockEntityType.HANGING_SIGN)
|
||||
public class HangingSignBlockEntityTranslator extends SignBlockEntityTranslator {
|
||||
|
||||
@Override
|
||||
public int signWidthMax() {
|
||||
return SignUtils.HANGING_SIGN_WIDTH_MAX; // Smaller than that for BlockEntityType.SIGN
|
||||
}
|
||||
}
|
@ -36,13 +36,12 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.level.block.BlockStateValues;
|
||||
import org.geysermc.geyser.level.physics.Axis;
|
||||
import org.geysermc.geyser.level.physics.BoundingBox;
|
||||
import org.geysermc.geyser.level.physics.CollisionManager;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.registry.Registries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
@ -622,10 +621,6 @@ public class PistonBlockEntity {
|
||||
Vector3i movement = getMovement();
|
||||
attachedBlocks.forEach((blockPos, javaId) -> {
|
||||
blockPos = blockPos.add(movement);
|
||||
if (!GameProtocol.supports1_19_50(session)) {
|
||||
// Send a final block entity packet to detach blocks for clients older than 1.19.50
|
||||
BlockEntityUtils.updateBlockEntity(session, buildMovingBlockTag(blockPos, javaId, Direction.DOWN.getUnitVector()), blockPos);
|
||||
}
|
||||
// Don't place blocks that collide with the player
|
||||
if (!SOLID_BOUNDING_BOX.checkIntersection(blockPos.toDouble(), session.getCollisionManager().getPlayerBoundingBox())) {
|
||||
ChunkUtils.updateBlock(session, javaId, blockPos);
|
||||
|
@ -27,12 +27,15 @@ package org.geysermc.geyser.translator.level.block.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityType;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.SignUtils;
|
||||
|
||||
@BlockEntity(type = {BlockEntityType.SIGN, BlockEntityType.HANGING_SIGN})
|
||||
@BlockEntity(type = BlockEntityType.SIGN)
|
||||
public class SignBlockEntityTranslator extends BlockEntityTranslator {
|
||||
/**
|
||||
* Maps a color stored in a sign's Color tag to its ARGB value.
|
||||
@ -64,54 +67,80 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator {
|
||||
return dyeColor | (255 << 24);
|
||||
}
|
||||
|
||||
public int signWidthMax() {
|
||||
return SignUtils.SIGN_WIDTH_MAX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) {
|
||||
StringBuilder signText = new StringBuilder();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int currentLine = i + 1;
|
||||
String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), "");
|
||||
signLine = MessageTranslator.convertMessageLenient(signLine);
|
||||
builder.putCompound("FrontText", translateSide(tag.get("front_text")));
|
||||
builder.putCompound("BackText", translateSide(tag.get("back_text")));
|
||||
var waxed = tag.get("is_waxed");
|
||||
builder.putBoolean("IsWaxed", waxed != null && waxed.getValue() instanceof Number number && number.byteValue() != 0);
|
||||
}
|
||||
|
||||
// Check the character width on the sign to ensure there is no overflow that is usually hidden
|
||||
// to Java Edition clients but will appear to Bedrock clients
|
||||
int signWidth = 0;
|
||||
StringBuilder finalSignLine = new StringBuilder();
|
||||
boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width
|
||||
for (char c : signLine.toCharArray()) {
|
||||
if (c == '\u00a7') {
|
||||
// Don't count this character
|
||||
previousCharacterWasFormatting = true;
|
||||
} else if (previousCharacterWasFormatting) {
|
||||
// Don't count this character either
|
||||
previousCharacterWasFormatting = false;
|
||||
} else {
|
||||
signWidth += SignUtils.getCharacterWidth(c);
|
||||
private NbtMap translateSide(Tag tag) {
|
||||
if (!(tag instanceof CompoundTag signData)) {
|
||||
return NbtMap.EMPTY;
|
||||
}
|
||||
NbtMapBuilder builder = NbtMap.builder();
|
||||
|
||||
StringBuilder signText = new StringBuilder();
|
||||
Tag messages = signData.get("messages");
|
||||
if (messages instanceof ListTag listTag) {
|
||||
var it = listTag.iterator();
|
||||
while (it.hasNext()) {
|
||||
String signLine = (String) it.next().getValue();
|
||||
signLine = MessageTranslator.convertMessageLenient(signLine);
|
||||
|
||||
// Check the character width on the sign to ensure there is no overflow that is usually hidden
|
||||
// to Java Edition clients but will appear to Bedrock clients
|
||||
int signWidth = 0;
|
||||
StringBuilder finalSignLine = new StringBuilder();
|
||||
boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width
|
||||
for (char c : signLine.toCharArray()) {
|
||||
if (c == ChatColor.ESCAPE) {
|
||||
// Don't count this character
|
||||
previousCharacterWasFormatting = true;
|
||||
} else if (previousCharacterWasFormatting) {
|
||||
// Don't count this character either
|
||||
previousCharacterWasFormatting = false;
|
||||
} else {
|
||||
signWidth += SignUtils.getCharacterWidth(c);
|
||||
}
|
||||
|
||||
if (signWidth <= signWidthMax()) {
|
||||
finalSignLine.append(c);
|
||||
} else {
|
||||
// Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// todo 1.20: update for hanging signs (smaller width). Currently OK because bedrock sees hanging signs as normal signs
|
||||
if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) {
|
||||
finalSignLine.append(c);
|
||||
} else {
|
||||
// Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to
|
||||
break;
|
||||
signText.append(finalSignLine);
|
||||
if (it.hasNext()) {
|
||||
signText.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signText.append(finalSignLine);
|
||||
signText.append("\n");
|
||||
// Trim extra newlines - this makes editing difficult if preserved because the cursor starts at the bottom,
|
||||
// Which can easily go over the screen
|
||||
while (!signText.isEmpty() && signText.charAt(signText.length() - 1) == '\n') {
|
||||
signText.deleteCharAt(signText.length() - 1);
|
||||
}
|
||||
|
||||
builder.putString("Text", signText.toString());
|
||||
|
||||
// Java Edition 1.14 added the ability to change the text color of the whole sign using dye
|
||||
Tag color = tag.get("Color");
|
||||
Tag color = signData.get("color");
|
||||
if (color != null) {
|
||||
builder.putInt("SignTextColor", getBedrockSignColor(color.getValue().toString()));
|
||||
}
|
||||
|
||||
// Glowing text
|
||||
boolean isGlowing = getOrDefault(tag.getValue().get("GlowingText"), (byte) 0) != (byte) 0;
|
||||
boolean isGlowing = getOrDefault(signData.get("has_glowing_text"), (byte) 0) != (byte) 0;
|
||||
builder.putBoolean("IgnoreLighting", isGlowing);
|
||||
builder.putBoolean("TextIgnoreLegacyBugResolved", isGlowing); // ??? required
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import com.github.steveice10.mc.protocol.packet.ingame.serverbound.level.Serverb
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
|
||||
import org.geysermc.geyser.network.GameProtocol;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
@ -44,16 +43,12 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
||||
public void translate(GeyserSession session, BlockEntityDataPacket packet) {
|
||||
NbtMap tag = packet.getData();
|
||||
String id = tag.getString("id");
|
||||
if (id.equals("Sign")) {
|
||||
String text;
|
||||
if (GameProtocol.supports1_19_80(session)) {
|
||||
// The other side is called... you guessed it... BackText
|
||||
text = tag.getCompound("FrontText")
|
||||
.getString("Text");
|
||||
} else {
|
||||
text = tag.getString("Text");
|
||||
}
|
||||
text = MessageTranslator.convertToPlainText(text);
|
||||
if (id.endsWith("Sign")) {
|
||||
// Hanging signs are narrower
|
||||
int widthMax = SignUtils.getSignWidthMax(id.startsWith("Hanging"));
|
||||
|
||||
String text = MessageTranslator.convertToPlainText(
|
||||
tag.getCompound(session.getWorldCache().isEditingSignOnFront() ? "FrontText" : "BackText").getString("Text"));
|
||||
// Note: as of 1.18.30, only one packet is sent from Bedrock when the sign is finished.
|
||||
// Previous versions did not have this behavior.
|
||||
StringBuilder newMessage = new StringBuilder();
|
||||
@ -68,13 +63,10 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
||||
for (char character : text.toCharArray()) {
|
||||
widthCount += SignUtils.getCharacterWidth(character);
|
||||
|
||||
// todo 1.20: update for hanging signs (smaller width). Currently bedrock thinks hanging signs are normal,
|
||||
// so it thinks hanging signs have more width than they actually do. Seems like JE just truncates it.
|
||||
|
||||
// If we get a return in Bedrock, or go over the character width max, that signals to use the next line.
|
||||
if (character == '\n' || widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX) {
|
||||
if (character == '\n' || widthCount > widthMax) {
|
||||
// We need to apply some more logic if we went over the character width max
|
||||
boolean wentOverMax = widthCount > SignUtils.JAVA_CHARACTER_WIDTH_MAX && character != '\n';
|
||||
boolean wentOverMax = widthCount > widthMax && character != '\n';
|
||||
widthCount = 0;
|
||||
// Saves if we're moving a word to the next line
|
||||
String word = null;
|
||||
@ -115,7 +107,7 @@ public class BedrockBlockEntityDataTranslator extends PacketTranslator<BlockEnti
|
||||
// Put the final line on since it isn't done in the for loop
|
||||
if (iterator < lines.length) lines[iterator] = newMessage.toString();
|
||||
Vector3i pos = Vector3i.from(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
|
||||
ServerboundSignUpdatePacket signUpdatePacket = new ServerboundSignUpdatePacket(pos, lines);
|
||||
ServerboundSignUpdatePacket signUpdatePacket = new ServerboundSignUpdatePacket(pos, lines, session.getWorldCache().isEditingSignOnFront());
|
||||
session.sendDownstreamPacket(signUpdatePacket);
|
||||
|
||||
} else if (id.equals("JigsawBlock")) {
|
||||
|
@ -33,12 +33,12 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BookEditPacket;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.type.WrittenBookItem;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
@ -46,12 +46,10 @@ import java.util.List;
|
||||
|
||||
@Translator(packet = BookEditPacket.class)
|
||||
public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket> {
|
||||
private static final int MAXIMUM_PAGE_LENGTH = 8192 * 4;
|
||||
private static final int MAXIMUM_TITLE_LENGTH = 128 * 4;
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, BookEditPacket packet) {
|
||||
if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().getBytes(StandardCharsets.UTF_8).length > MAXIMUM_PAGE_LENGTH) {
|
||||
if (packet.getText() != null && !packet.getText().isEmpty() && packet.getText().length() > WrittenBookItem.MAXIMUM_PAGE_EDIT_LENGTH) {
|
||||
session.getGeyser().getLogger().warning("Page length greater than server allowed!");
|
||||
return;
|
||||
}
|
||||
@ -63,6 +61,10 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
||||
List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
|
||||
|
||||
int page = packet.getPageNumber();
|
||||
if (page < 0 || WrittenBookItem.MAXIMUM_PAGE_COUNT <= page) {
|
||||
session.getGeyser().getLogger().warning("Edited page is out of acceptable bounds!");
|
||||
return;
|
||||
}
|
||||
switch (packet.getAction()) {
|
||||
case ADD_PAGE: {
|
||||
// Add empty pages in between
|
||||
@ -129,7 +131,7 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
||||
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
|
||||
// Add title to packet so the server knows we're signing
|
||||
title = MessageTranslator.convertToPlainText(packet.getTitle());
|
||||
if (title.getBytes(StandardCharsets.UTF_8).length > MAXIMUM_TITLE_LENGTH) {
|
||||
if (title.length() > WrittenBookItem.MAXIMUM_TITLE_LENGTH) {
|
||||
session.getGeyser().getLogger().warning("Book title larger than server allows!");
|
||||
return;
|
||||
}
|
||||
|
@ -26,10 +26,9 @@
|
||||
package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.packet.CommandRequestPacket;
|
||||
import org.geysermc.common.PlatformType;
|
||||
import org.geysermc.geyser.api.util.PlatformType;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
@ -46,7 +45,9 @@ public class BedrockCommandRequestTranslator extends PacketTranslator<CommandReq
|
||||
return;
|
||||
}
|
||||
|
||||
session.sendCommand(command.substring(1));
|
||||
// running commands via Bedrock's command select menu adds a trailing whitespace which Java doesn't like
|
||||
// https://github.com/GeyserMC/Geyser/issues/3877
|
||||
session.sendCommand(command.substring(1).stripTrailing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren