Merge remote-tracking branch 'origin/master' into feature/floodgate-merge

This commit is contained in:
Tim203 2023-09-24 10:44:56 +02:00
commit 05eb054559
No known key found for this signature in database
298 changed files with 35381 additions and 44504 deletions

View File

@ -19,61 +19,59 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository and submodules
uses: actions/checkout@v3
# See https://github.com/actions/checkout/commits
uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9
with:
submodules: recursive
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
# See https://github.com/gradle/wrapper-validation-action/commits
uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4
- uses: actions/setup-java@v3
# See https://github.com/actions/setup-java/commits
- uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758
with:
java-version: 17
distribution: temurin
- name: Build
uses: gradle/gradle-build-action@v2
# See https://github.com/gradle/gradle-build-action/commits
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
with:
arguments: build
gradle-home-cache-cleanup: true
- name: Archive artifacts (Geyser Fabric)
uses: actions/upload-artifact@v3
# See https://github.com/actions/upload-artifact/commits
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Fabric
path: bootstrap/fabric/build/libs/Geyser-Fabric.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Standalone)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Standalone
path: bootstrap/standalone/build/libs/Geyser-Standalone.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Spigot)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Spigot
path: bootstrap/spigot/build/libs/Geyser-Spigot.jar
if-no-files-found: error
- name: Archive artifacts (Geyser BungeeCord)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser BungeeCord
path: bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Sponge)
uses: actions/upload-artifact@v3
if: success()
with:
name: Geyser Sponge
path: bootstrap/sponge/build/libs/Geyser-Sponge.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Velocity)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Velocity
@ -82,7 +80,7 @@ jobs:
- name: Publish to Maven Repository
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
uses: gradle/gradle-build-action@v2
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
env:
ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }}
ORG_GRADLE_PROJECT_geysermcPassword: ${{ secrets.DEPLOY_PASS }}
@ -114,7 +112,7 @@ jobs:
rsync -P -e "ssh -o StrictHostKeyChecking=no -i id_ecdsa" metadata.json $DOWNLOADS_USERNAME@$DOWNLOADS_SERVER_IP:~/uploads/$project/$GITHUB_RUN_NUMBER/
- name: Publish to Modrinth
uses: gradle/gradle-build-action@v2
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
if: ${{ success() && github.repository == 'GeyserMC/Geyser' && github.ref_name == 'master' }}
env:
MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }}
@ -124,7 +122,8 @@ jobs:
- name: Notify Discord
if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Geyser' }}
uses: Tim203/actions-git-discord-webhook@main
# See https://github.com/Tim203/actions-git-discord-webhook/commits
uses: Tim203/actions-git-discord-webhook@70f38ded3aca51635ec978ab4e1a58cd4cd0c2ff
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}

View File

@ -17,13 +17,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up JDK 17
uses: actions/setup-java@v1
# See https://github.com/actions/setup-java/commits
uses: actions/setup-java@4075bfc1b51bf22876335ae1cd589602d60d8758
with:
java-version: 17
distribution: temurin
- name: Check if the author has forked the API repo
uses: Kas-tle/ForkFinder@v1.0.1
# See https://github.com/Kas-tle/find-forks-action/commits
uses: Kas-tle/find-forks-action@1b5447d1e3c7a8ed79583dd817cc5399686eed3a
id: find_forks
with:
owner: GeyserMC
@ -31,7 +33,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 }}
@ -41,54 +43,54 @@ jobs:
./gradlew publishToMavenLocal
- name: Checkout repository and submodules
uses: actions/checkout@v3
# See https://github.com/actions/checkout/commits
uses: actions/checkout@72f2cec99f417b1a1c5e2e88945068983b7965f9
with:
submodules: recursive
path: geyser
- name: Validate Gradle Wrapper
# See https://github.com/gradle/wrapper-validation-action/commits
uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4
- name: Build Geyser
uses: gradle/gradle-build-action@v2
# See https://github.com/gradle/gradle-build-action/commits
uses: gradle/gradle-build-action@3bfe3a46584a206fb8361cdedd0647b0c4204232
with:
arguments: build
build-root-directory: geyser
- name: Archive artifacts (Geyser Fabric)
uses: actions/upload-artifact@v3
# See https://github.com/actions/upload-artifact/commits
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Fabric
path: geyser/bootstrap/fabric/build/libs/Geyser-Fabric.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Standalone)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Standalone
path: geyser/bootstrap/standalone/build/libs/Geyser-Standalone.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Spigot)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Spigot
path: geyser/bootstrap/spigot/build/libs/Geyser-Spigot.jar
if-no-files-found: error
- name: Archive artifacts (Geyser BungeeCord)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser BungeeCord
path: geyser/bootstrap/bungeecord/build/libs/Geyser-BungeeCord.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Sponge)
uses: actions/upload-artifact@v3
if: success()
with:
name: Geyser Sponge
path: geyser/bootstrap/sponge/build/libs/Geyser-Sponge.jar
if-no-files-found: error
- name: Archive artifacts (Geyser Velocity)
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
if: success()
with:
name: Geyser Velocity

1
.gitignore vendored
View File

@ -251,3 +251,4 @@ locales/
/saved-refresh-tokens.json
/custom_mappings/
/languages/
/custom-skulls.yml

View File

@ -1,6 +1,6 @@
The MIT License
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
@ -18,4 +18,4 @@ 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.
THE SOFTWARE.

View File

@ -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.20.0 - 1.20.30 and Minecraft Java 1.20/1.20.1.
## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.

View File

@ -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.
*

View File

@ -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,13 @@
* @link https://github.com/GeyserMC/Geyser
*/
<<<<<<<< HEAD:core/src/main/java/org/geysermc/geyser/util/PlatformType.java
package org.geysermc.geyser.util;
========
package org.geysermc.geyser.api.bedrock.camera;
>>>>>>>> origin/master:api/src/main/java/org/geysermc/geyser/api/bedrock/camera/CameraShake.java
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum PlatformType {
ANDROID("Android"),
BUNGEECORD("BungeeCord"),
FABRIC("Fabric"),
SPIGOT("Spigot"),
SPONGE("Sponge"),
STANDALONE("Standalone"),
VELOCITY("Velocity");
private final String platformName;
public enum CameraShake {
POSITIONAL,
ROTATIONAL;
}

View File

@ -0,0 +1,143 @@
/*
* 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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.property.CustomBlockProperty;
import org.geysermc.geyser.api.util.CreativeCategory;
import java.util.List;
import java.util.Map;
/**
* This class is used to store data for a custom block.
*/
public interface CustomBlockData {
/**
* Gets the name of the custom block
*
* @return The name of the custom block.
*/
@NonNull String name();
/**
* Gets the identifier of the custom block
*
* @return The identifier of the custom block.
*/
@NonNull String identifier();
/**
* Gets if the custom block is included in the creative inventory
*
* @return If the custom block is included in the creative inventory.
*/
boolean includedInCreativeInventory();
/**
* Gets the item's creative category, or tab id.
*
* @return the item's creative category
*/
@Nullable CreativeCategory creativeCategory();
/**
* Gets the item's creative group.
*
* @return the item's creative group
*/
@Nullable String creativeGroup();
/**
* Gets the components of the custom block
*
* @return The components of the custom block.
*/
@Nullable CustomBlockComponents components();
/**
* Gets the custom block's map of block property names to CustomBlockProperty
* objects
*
* @return The custom block's map of block property names to CustomBlockProperty objects.
*/
@NonNull Map<String, CustomBlockProperty<?>> properties();
/**
* Gets the list of the custom block's permutations
*
* @return The permutations of the custom block.
*/
@NonNull List<CustomBlockPermutation> permutations();
/**
* Gets the custom block's default block state
*
* @return The default block state of the custom block.
*/
@NonNull CustomBlockState defaultBlockState();
/**
* Gets a builder for a custom block state
*
* @return The builder for a custom block state.
*/
CustomBlockState.@NonNull Builder blockStateBuilder();
/**
* Create a Builder for CustomBlockData
*
* @return A CustomBlockData Builder
*/
static CustomBlockData.Builder builder() {
return GeyserApi.api().provider(CustomBlockData.Builder.class);
}
interface Builder {
Builder name(@NonNull String name);
Builder includedInCreativeInventory(boolean includedInCreativeInventory);
Builder creativeCategory(@Nullable CreativeCategory creativeCategory);
Builder creativeGroup(@Nullable String creativeGroup);
Builder components(@NonNull CustomBlockComponents components);
Builder booleanProperty(@NonNull String propertyName);
Builder intProperty(@NonNull String propertyName, List<Integer> values);
Builder stringProperty(@NonNull String propertyName, List<String> values);
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
CustomBlockData build();
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
/**
* This class is used to store a custom block permutations, which contain custom
* block components mapped to a Molang query that should return true or false
*
* @param components The components of the block
* @param condition The Molang query that should return true or false
*/
public record CustomBlockPermutation(@NonNull CustomBlockComponents components, @NonNull String condition) {
}

View File

@ -0,0 +1,75 @@
/*
* 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.api.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Map;
/**
* This class is used to store a custom block state, which contains CustomBlockData
* tied to defined properties and values
*/
public interface CustomBlockState {
/**
* Gets the custom block data associated with the state
*
* @return The custom block data for the state.
*/
@NonNull CustomBlockData block();
/**
* Gets the name of the state
*
* @return The name of the state.
*/
@NonNull String name();
/**
* Gets the given property for the state
*
* @param propertyName the property name
* @return the boolean, int, or string property.
*/
@NonNull <T> T property(@NonNull String propertyName);
/**
* Gets a map of the properties for the state
*
* @return The properties for the state.
*/
@NonNull Map<String, Object> properties();
interface Builder {
Builder booleanProperty(@NonNull String propertyName, boolean value);
Builder intProperty(@NonNull String propertyName, int value);
Builder stringProperty(@NonNull String propertyName, @NonNull String value);
CustomBlockState build();
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.block.custom;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.util.CreativeCategory;
import java.util.List;
/**
* Represents a completely custom block that is not based on an existing vanilla Minecraft block.
*/
public interface NonVanillaCustomBlockData extends CustomBlockData {
/**
* Gets the namespace of the custom block
*
* @return The namespace of the custom block.
*/
@NonNull String namespace();
/**
* Create a Builder for NonVanillaCustomBlockData
*
* @return A NonVanillaCustomBlockData Builder
*/
static NonVanillaCustomBlockData.Builder builder() {
return GeyserApi.api().provider(NonVanillaCustomBlockData.Builder.class);
}
interface Builder extends CustomBlockData.Builder {
Builder namespace(@NonNull String namespace);
@Override
Builder name(@NonNull String name);
@Override
Builder includedInCreativeInventory(boolean includedInCreativeInventory);
@Override
Builder creativeCategory(@Nullable CreativeCategory creativeCategory);
@Override
Builder creativeGroup(@Nullable String creativeGroup);
@Override
Builder components(@NonNull CustomBlockComponents components);
@Override
Builder booleanProperty(@NonNull String propertyName);
@Override
Builder intProperty(@NonNull String propertyName, List<Integer> values);
@Override
Builder stringProperty(@NonNull String propertyName, List<String> values);
@Override
Builder permutations(@NonNull List<CustomBlockPermutation> permutations);
@Override
NonVanillaCustomBlockData build();
}
}

View File

@ -23,39 +23,47 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.sponge.command;
package org.geysermc.geyser.api.block.custom.component;
import net.kyori.adventure.text.Component;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandCause;
import org.spongepowered.api.command.manager.CommandMapping;
/**
* This class is used to store a box component for the selection and
* collision boxes of a custom block.
*
* @param originX The origin X of the box
* @param originY The origin Y of the box
* @param originZ The origin Z of the box
* @param sizeX The size X of the box
* @param sizeY The size Y of the box
* @param sizeZ The size Z of the box
*/
public record BoxComponent(float originX, float originY, float originZ, float sizeX, float sizeY, float sizeZ) {
private static final BoxComponent FULL_BOX = new BoxComponent(-8, 0, -8, 16, 16, 16);
private static final BoxComponent EMPTY_BOX = new BoxComponent(0, 0, 0, 0, 0, 0);
import java.util.Optional;
public class GeyserSpongeCommandManager extends GeyserCommandManager {
public GeyserSpongeCommandManager(GeyserImpl geyser) {
super(geyser);
/**
* Gets a full box component
*
* @return A full box component
*/
public static BoxComponent fullBox() {
return FULL_BOX;
}
@Override
public String description(String command) {
if (!Sponge.isServerAvailable()) {
return "";
}
// Note: The command manager may be replaced at any point during the game lifecycle
return Sponge.server().commandManager().commandMapping(command)
.map(this::description)
.map(Optional::get)
.map(MessageTranslator::convertMessage)
.orElse("");
/**
* Gets an empty box component
*
* @return An empty box component
*/
public static BoxComponent emptyBox() {
return EMPTY_BOX;
}
public Optional<Component> description(CommandMapping mapping) {
return mapping.registrar().shortDescription(CommandCause.create(), mapping);
/**
* Gets if the box component is empty
*
* @return If the box component is empty.
*/
public boolean isEmpty() {
return sizeX == 0 && sizeY == 0 && sizeZ == 0;
}
}

View File

@ -0,0 +1,192 @@
/*
* 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.api.block.custom.component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class is used to store components for a custom block or custom block permutation.
*/
public interface CustomBlockComponents {
/**
* Gets the selection box component
* Equivalent to "minecraft:selection_box"
*
* @return The selection box.
*/
@Nullable BoxComponent selectionBox();
/**
* Gets the collision box component
* Equivalent to "minecraft:collision_box"
* @return The collision box.
*/
@Nullable BoxComponent collisionBox();
/**
* Gets the display name component
* Equivalent to "minecraft:display_name"
*
* @return The display name.
*/
@Nullable String displayName();
/**
* Gets the geometry component
* Equivalent to "minecraft:geometry"
*
* @return The geometry.
*/
@Nullable GeometryComponent geometry();
/**
* Gets the material instances component
* Equivalent to "minecraft:material_instances"
*
* @return The material instances.
*/
@NonNull Map<String, MaterialInstance> materialInstances();
/**
* Gets the placement filter component
* Equivalent to "minecraft:placement_filter"
*
* @return The placement filter.
*/
@Nullable List<PlacementConditions> placementFilter();
/**
* Gets the destructible by mining component
* Equivalent to "minecraft:destructible_by_mining"
*
* @return The destructible by mining value.
*/
@Nullable Float destructibleByMining();
/**
* Gets the friction component
* Equivalent to "minecraft:friction"
*
* @return The friction value.
*/
@Nullable Float friction();
/**
* Gets the light emission component
* Equivalent to "minecraft:light_emission"
*
* @return The light emission value.
*/
@Nullable Integer lightEmission();
/**
* Gets the light dampening component
* Equivalent to "minecraft:light_dampening"
*
* @return The light dampening value.
*/
@Nullable Integer lightDampening();
/**
* Gets the transformation component
* Equivalent to "minecraft:transformation"
*
* @return The transformation.
*/
@Nullable TransformationComponent transformation();
/**
* Gets the unit cube component
* Equivalent to "minecraft:unit_cube"
*
* @return The rotation.
*/
boolean unitCube();
/**
* Gets if the block should place only air
* Equivalent to setting a dummy event to run on "minecraft:on_player_placing"
*
* @return If the block should place only air.
*/
boolean placeAir();
/**
* Gets the set of tags
* Equivalent to "tag:some_tag"
*
* @return The set of tags.
*/
@NonNull Set<String> tags();
/**
* Create a Builder for CustomBlockComponents
*
* @return A CustomBlockComponents Builder
*/
static CustomBlockComponents.Builder builder() {
return GeyserApi.api().provider(CustomBlockComponents.Builder.class);
}
interface Builder {
Builder selectionBox(BoxComponent selectionBox);
Builder collisionBox(BoxComponent collisionBox);
Builder displayName(String displayName);
Builder geometry(GeometryComponent geometry);
Builder materialInstance(@NonNull String name, @NonNull MaterialInstance materialInstance);
Builder placementFilter(List<PlacementConditions> placementConditions);
Builder destructibleByMining(Float destructibleByMining);
Builder friction(Float friction);
Builder lightEmission(Integer lightEmission);
Builder lightDampening(Integer lightDampening);
Builder transformation(TransformationComponent transformation);
Builder unitCube(boolean unitCube);
Builder placeAir(boolean placeAir);
Builder tags(Set<String> tags);
CustomBlockComponents build();
}
}

View File

@ -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.api.block.custom.component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import java.util.Map;
/**
* This class is used to store data for a geometry component.
*/
public interface GeometryComponent {
/**
* Gets the identifier of the geometry
*
* @return The identifier of the geometry.
*/
@NonNull String identifier();
/**
* Gets the bone visibility of the geometry
*
* @return The bone visibility of the geometry.
*/
@Nullable Map<String, String> boneVisibility();
/**
* Creates a builder for GeometryComponent
*
* @return a builder for GeometryComponent.
*/
static GeometryComponent.Builder builder() {
return GeyserApi.api().provider(GeometryComponent.Builder.class);
}
interface Builder {
Builder identifier(@NonNull String identifier);
Builder boneVisibility(@Nullable Map<String, String> boneVisibility);
GeometryComponent build();
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.api.block.custom.component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
/**
* This class is used to store data for a material instance.
*/
public interface MaterialInstance {
/**
* Gets the texture of the block
*
* @return The texture of the block.
*/
@Nullable String texture();
/**
* Gets the render method of the block
*
* @return The render method of the block.
*/
@Nullable String renderMethod();
/**
* Gets if the block should be dimmed on certain faces
*
* @return If the block should be dimmed on certain faces.
*/
boolean faceDimming();
/**
* Gets if the block should have ambient occlusion
*
* @return If the block should have ambient occlusion.
*/
boolean ambientOcclusion();
/**
* Creates a builder for MaterialInstance.
*
* @return a builder for MaterialInstance
*/
static MaterialInstance.Builder builder() {
return GeyserApi.api().provider(MaterialInstance.Builder.class);
}
interface Builder {
Builder texture(@Nullable String texture);
Builder renderMethod(@Nullable String renderMethod);
Builder faceDimming(boolean faceDimming);
Builder ambientOcclusion(boolean ambientOcclusion);
MaterialInstance build();
}
}

View File

@ -21,39 +21,33 @@
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
*/
package org.geysermc.geyser.platform.sponge.command;
package org.geysermc.geyser.api.block.custom.component;
import java.util.LinkedHashMap;
import java.util.Set;
import lombok.AllArgsConstructor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.spongepowered.api.command.CommandCause;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
@AllArgsConstructor
public class SpongeCommandSource implements GeyserCommandSource {
private final CommandCause handle;
@Override
public String name() {
return handle.friendlyIdentifier().orElse(handle.identifier());
/**
* This class is used to store conditions for a placement filter for a custom block.
*
* @param allowedFaces The faces that the block can be placed on
* @param blockFilters The block filters that control what blocks the block can be placed on
*/
public record PlacementConditions(@NonNull Set<Face> allowedFaces, @NonNull LinkedHashMap<String, BlockFilterType> blockFilters) {
public enum Face {
DOWN,
UP,
NORTH,
SOUTH,
WEST,
EAST;
}
@Override
public void sendMessage(@NonNull String message) {
handle.audience().sendMessage(LegacyComponentSerializer.legacySection().deserialize(message));
public enum BlockFilterType {
BLOCK,
TAG
}
@Override
public boolean isConsole() {
return !(handle.cause().root() instanceof ServerPlayer);
}
@Override
public boolean hasPermission(String permission) {
return handle.hasPermission(permission);
}
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.api.block.custom.component;
/**
* This class is used to store the transformation component of a block
*
* @param rx The rotation on the x axis
* @param ry The rotation on the y axis
* @param rz The rotation on the z axis
* @param sx The scale on the x axis
* @param sy The scale on the y axis
* @param sz The scale on the z axis
* @param tx The translation on the x axis
* @param ty The translation on the y axis
* @param tz The translation on the z axis
*/
public record TransformationComponent(int rx, int ry, int rz, float sx, float sy, float sz, float tx, float ty, float tz) {
/**
* Constructs a new TransformationComponent with the rotation values and assumes default scale and translation
*
* @param rx The rotation on the x axis
* @param ry The rotation on the y axis
* @param rz The rotation on the z axis
*/
public TransformationComponent(int rx, int ry, int rz) {
this(rx, ry, rz, 1, 1, 1, 0, 0, 0);
}
/**
* Constructs a new TransformationComponent with the rotation and scale values and assumes default translation
*
* @param rx The rotation on the x axis
* @param ry The rotation on the y axis
* @param rz The rotation on the z axis
* @param sx The scale on the x axis
* @param sy The scale on the y axis
* @param sz The scale on the z axis
*/
public TransformationComponent(int rx, int ry, int rz, float sx, float sy, float sz) {
this(rx, ry, rz, sx, sy, sz, 0, 0, 0);
}
}

View File

@ -0,0 +1,7 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
public record JavaBlockItem(@NonNull String identifier, @NonNegative int javaId, @NonNegative int stackSize) {
}

View File

@ -0,0 +1,111 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
public interface JavaBlockState {
/**
* Gets the identifier of the block state
*
* @return the identifier of the block state
*/
@NonNull String identifier();
/**
* Gets the Java ID of the block state
*
* @return the Java ID of the block state
*/
@NonNegative int javaId();
/**
* Gets the state group ID of the block state
*
* @return the state group ID of the block state
*/
@NonNegative int stateGroupId();
/**
* Gets the block hardness of the block state
*
* @return the block hardness of the block state
*/
@NonNegative float blockHardness();
/**
* Gets whether the block state is waterlogged
*
* @return whether the block state is waterlogged
*/
@NonNull boolean waterlogged();
/**
* Gets the collision of the block state
*
* @return the collision of the block state
*/
@NonNull JavaBoundingBox[] collision();
/**
* Gets whether the block state can be broken with hand
*
* @return whether the block state can be broken with hand
*/
@NonNull boolean canBreakWithHand();
/**
* Gets the pick item of the block state
*
* @return the pick item of the block state
*/
@Nullable String pickItem();
/**
* Gets the piston behavior of the block state
*
* @return the piston behavior of the block state
*/
@Nullable String pistonBehavior();
/**
* Gets whether the block state has block entity
*
* @return whether the block state has block entity
*/
@Nullable boolean hasBlockEntity();
/**
* Creates a new {@link JavaBlockState.Builder} instance
*
* @return a new {@link JavaBlockState.Builder} instance
*/
static JavaBlockState.Builder builder() {
return GeyserApi.api().provider(JavaBlockState.Builder.class);
}
interface Builder {
Builder identifier(@NonNull String identifier);
Builder javaId(@NonNegative int javaId);
Builder stateGroupId(@NonNegative int stateGroupId);
Builder blockHardness(@NonNegative float blockHardness);
Builder waterlogged(@NonNull boolean waterlogged);
Builder collision(@NonNull JavaBoundingBox[] collision);
Builder canBreakWithHand(@NonNull boolean canBreakWithHand);
Builder pickItem(@Nullable String pickItem);
Builder pistonBehavior(@Nullable String pistonBehavior);
Builder hasBlockEntity(@Nullable boolean hasBlockEntity);
JavaBlockState build();
}
}

View File

@ -0,0 +1,6 @@
package org.geysermc.geyser.api.block.custom.nonvanilla;
import org.checkerframework.checker.nullness.qual.NonNull;
public record JavaBoundingBox(@NonNull double middleX, @NonNull double middleY, @NonNull double middleZ, @NonNull double sizeX, @NonNull double sizeY, @NonNull double sizeZ) {
}

View File

@ -0,0 +1,56 @@
/*
* 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.api.block.custom.property;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
/**
* This class is used to store a property of a custom block of a generic type.
*/
public interface CustomBlockProperty<T> {
/**
* Gets the name of the property
*
* @return The name of the property.
*/
@NonNull String name();
/**
* Gets the values of the property
*
* @return The values of the property.
*/
@NonNull List<T> values();
/**
* Gets the type of the property
*
* @return The type of the property.
*/
@NonNull PropertyType type();
}

View File

@ -0,0 +1,79 @@
/*
* 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.api.block.custom.property;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* This class is used to define a custom block property's type.
*/
public class PropertyType {
private static final PropertyType BOOLEAN = new PropertyType(Boolean.class);
private static final PropertyType INTEGER = new PropertyType(Integer.class);
private static final PropertyType STRING = new PropertyType(String.class);
/**
* Gets the property type for a boolean.
*
* @return The property type for a boolean.
*/
@NonNull public static PropertyType booleanProp() {
return BOOLEAN;
}
/**
* Gets the property type for an integer.
*
* @return The property type for an integer.
*/
@NonNull public static PropertyType integerProp() {
return INTEGER;
}
/**
* Gets the property type for a string.
*
* @return The property type for a string.
*/
@NonNull public static PropertyType stringProp() {
return STRING;
}
private final Class<?> typeClass;
/**
* Gets the class of the property type
*
* @return The class of the property type.
*/
@NonNull public Class<?> typeClass() {
return typeClass;
}
private PropertyType(Class<?> typeClass) {
this.typeClass = typeClass;
}
}

View File

@ -29,10 +29,12 @@ import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.api.connection.Connection;
import org.geysermc.geyser.api.bedrock.camera.CameraShake;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.entity.type.GeyserEntity;
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
@ -47,9 +49,50 @@ public interface GeyserConnection extends Connection, CommandSource {
CompletableFuture<@Nullable GeyserEntity> entityByJavaId(@NonNegative int javaId);
/**
* Displays a player entity as emoting to this client.
*
* @param emoter the player entity emoting.
* @param emoteId the emote ID to send to the client.
* @param emoteId the emote ID to send to this client.
*/
void showEmote(@NonNull GeyserPlayerEntity emoter, @NonNull String emoteId);
/**
* Shakes the client's camera.<br><br>
* If the camera is already shaking with the same {@link CameraShake} type, then the additional intensity
* will be layered on top of the existing intensity, with their own distinct durations.<br>
* If the existing shake type is different and the new intensity/duration are not positive, the existing shake only
* switches to the new type. Otherwise, the existing shake is completely overridden.
*
* @param intensity the intensity of the shake. The client has a maximum total intensity of 4.
* @param duration the time in seconds that the shake will occur for
* @param type the type of shake
*/
void shakeCamera(float intensity, float duration, @NonNull CameraShake type);
/**
* Stops all camera shake of any type.
*/
void stopCameraShake();
/**
* Adds the given fog IDs to the fog cache, then sends all fog IDs in the cache to the client.
* <p>
* Fog IDs can be found <a href="https://wiki.bedrock.dev/documentation/fog-ids.html">here</a>
*
* @param fogNameSpaces the fog IDs to add. If empty, the existing cached IDs will still be sent.
*/
void sendFog(String... fogNameSpaces);
/**
* Removes the given fog IDs from the fog cache, then sends all fog IDs in the cache to the client.
*
* @param fogNameSpaces the fog IDs to remove. If empty, all fog IDs will be removed.
*/
void removeFog(String... fogNameSpaces);
/**
* Returns an immutable copy of all fog affects currently applied to this client.
*/
@NonNull
Set<String> fogEffects();
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.connection;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.event.Event;
import java.net.InetSocketAddress;
/**
* Called whenever Geyser gets pinged
*
* This event allows you to modify/obtain the MOTD, maximum player count, and current number of players online,
* Geyser will reply to the client with what was given.
*/
public interface GeyserBedrockPingEvent extends Event {
/**
* Sets the given string as the primary motd, the given string cannot be null.
*
* @param primary the string to set as the primary motd
*/
void primaryMotd(@NonNull String primary);
/**
* Sets the given string as the secondary motd, the given string cannot be null.
* Note: the secondary motd is only used for the LAN game entry.
*
* @param secondary the string to set as the secondary motd
*/
void secondaryMotd(@NonNull String secondary);
/**
* Sets how many players are currently online, the given number cannot be below 0.
*
* @param count the number to set
*/
void playerCount(int count);
/**
* Sets the maximum number of players that can join this server, the given number cannot be below 1.
*
* @param max the number to set
*/
void maxPlayerCount(int max);
/**
* Gets the primary motd.
*
* @return the primary motd string
*/
@Nullable
String primaryMotd();
/**
* Gets the secondary motd.
*
* @return the secondary motd string
*/
@Nullable
String secondaryMotd();
/**
* Gets the current number of players.
*
* @return number of players online
*/
@NonNegative
int playerCount();
/**
* Gets the maximum number of players that can join this server
*
* @return maximum number of players that can join
*/
int maxPlayerCount();
/**
* Gets the {@link InetSocketAddress} of the client pinging us.
*
* @return a {@link InetSocketAddress}
*/
@NonNull
InetSocketAddress address();
}

View File

@ -0,0 +1,76 @@
/*
* 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.api.event.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
import org.geysermc.geyser.api.block.custom.CustomBlockState;
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockItem;
import org.geysermc.geyser.api.block.custom.nonvanilla.JavaBlockState;
import org.geysermc.event.Event;
/**
* Called on Geyser's startup when looking for custom blocks. Custom blocks must be registered through this event.
*
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public abstract class GeyserDefineCustomBlocksEvent implements Event {
/**
* Registers the given {@link CustomBlockData} as a custom block
*
* @param customBlockData the custom block to register
*/
public abstract void register(@NonNull CustomBlockData customBlockData);
/**
* Registers the given {@link CustomBlockState} as an override for the
* given java state identifier
* Java state identifiers are listed in
* https://raw.githubusercontent.com/GeyserMC/mappings/master/blocks.json
*
* @param javaIdentifier the java state identifier to override
* @param customBlockState the custom block state with which to override java state identifier
*/
public abstract void registerOverride(@NonNull String javaIdentifier, @NonNull CustomBlockState customBlockState);
/**
* Registers the given {@link CustomBlockData} as an override for the
* given java item identifier
*
* @param javaIdentifier the java item identifier to override
* @param customBlockData the custom block data with which to override java item identifier
*/
public abstract void registerItemOverride(@NonNull String javaIdentifier, @NonNull CustomBlockData customBlockData);
/**
* Registers the given {@link CustomBlockState} as an override for the
* given {@link JavaBlockState}
*
* @param javaBlockState the java block state for the non-vanilla block
* @param customBlockState the custom block state with which to override java state identifier
*/
public abstract void registerOverride(@NonNull JavaBlockState javaBlockState, @NonNull CustomBlockState customBlockState);
}

View File

@ -0,0 +1,28 @@
package org.geysermc.geyser.api.event.lifecycle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.event.Event;
/**
* Called on Geyser's startup when looking for custom skulls. Custom skulls must be registered through this event.
*
* This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public abstract class GeyserDefineCustomSkullsEvent implements Event {
/**
* The type of texture provided
*/
public enum SkullTextureType {
USERNAME,
UUID,
PROFILE,
SKIN_HASH
}
/**
* Registers the given username, UUID, base64 encoded profile, or skin hash as a custom skull blocks
* @param texture the username, UUID, base64 encoded profile, or skin hash
* @param type the type of texture provided
*/
public abstract void register(@NonNull String texture, @NonNull SkullTextureType type);
}

View File

@ -136,4 +136,12 @@ public interface Extension extends EventRegistrar {
default GeyserApi geyserApi() {
return GeyserApi.api();
}
/**
* Disable the extension.
*/
default void disable() {
this.setEnabled(false);
this.eventBus().unregisterAll();
}
}

View File

@ -129,6 +129,34 @@ public interface NonVanillaCustomItemData extends CustomItemData {
*/
boolean isHat();
/**
* Gets if the item is a foil. This is used to determine if the item should be rendered with an enchantment glint effect.
*
* @return if the item is a foil
*/
boolean isFoil();
/**
* Gets if the item is edible.
*
* @return if the item is edible
*/
boolean isEdible();
/**
* Gets if the food item can always be eaten.
*
* @return if the item is allowed to be eaten all the time
*/
boolean canAlwaysEat();
/**
* Gets if the item is chargable, like a bow.
*
* @return if the item should act like a chargable item
*/
boolean isChargeable();
/**
* @deprecated Use {@link #displayHandheld()} instead.
* Gets if the item is a tool. This is used to set the render type of the item, if the item is handheld.
@ -174,6 +202,14 @@ public interface NonVanillaCustomItemData extends CustomItemData {
Builder hat(boolean isHat);
Builder foil(boolean isFoil);
Builder edible(boolean isEdible);
Builder canAlwaysEat(boolean canAlwaysEat);
Builder chargeable(boolean isChargeable);
/**
* @deprecated Use {@link #displayHandheld(boolean)} instead.
*/

View File

@ -67,4 +67,11 @@ public interface RemoteServer {
*/
@NonNull
AuthType authType();
/**
* Gets if we should attempt to resolve the SRV record for this server.
*
* @return if we should attempt to resolve the SRV record for this server
*/
boolean resolveSrv();
}

View File

@ -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);
}
}

View File

@ -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,15 +23,23 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.sponge;
package org.geysermc.geyser.api.pack;
import org.geysermc.geyser.configuration.GeyserJacksonConfiguration;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.nio.file.Path;
public final class GeyserSpongeConfiguration extends GeyserJacksonConfiguration {
@Override
public Path getFloodgateKeyPath() {
return null; //floodgate isn't available for Sponge
}
/**
* 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();
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,66 @@
/*
* 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;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents the creative menu categories or tabs.
*/
public enum CreativeCategory {
COMMANDS("commands", 1),
CONSTRUCTION("construction", 2),
EQUIPMENT("equipment", 3),
ITEMS("items", 4),
NATURE("nature", 5),
NONE("none", 6);
private final String internalName;
private final int id;
CreativeCategory(String internalName, int id) {
this.internalName = internalName;
this.id = id;
}
/**
* Gets the internal name of the category.
*
* @return the name of the category
*/
@NonNull public String internalName() {
return internalName;
}
/**
* Gets the internal ID of the category.
*
* @return the ID of the category
*/
public int id() {
return id;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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) {
@Deprecated
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");
@Deprecated
public static final PlatformType SPONGE = new PlatformType("Sponge");
public static final PlatformType STANDALONE = new PlatformType("Standalone");
public static final PlatformType VELOCITY = new PlatformType("Velocity");
}

View File

@ -10,6 +10,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)
@ -23,7 +24,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:.*"))
@ -34,4 +34,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
exclude(dependency("io.netty:netty-codec:.*"))
exclude(dependency("io.netty:netty-resolver-dns:.*"))
}
}
}

View File

@ -34,6 +34,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.floodgate.bungee.BungeePlatform;
import org.geysermc.floodgate.bungee.pluginmessage.BungeeSkinApplier;
import org.geysermc.floodgate.core.skin.SkinApplier;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
@ -49,7 +50,6 @@ import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.bungeecord.command.GeyserBungeeCommandExecutor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.PlatformType;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@ -117,26 +117,6 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
@Override
public void onEnable() {
if (getProxy().getConfig().getListeners().size() == 1) {
ListenerInfo listener = getProxy().getConfig().getListeners().toArray(new ListenerInfo[0])[0];
InetSocketAddress javaAddr = listener.getHost();
// By default this should be localhost but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) {
this.geyserConfig.setAutoconfiguredRemote(true);
// Don't use localhost if not listening on all interfaces
if (!javaAddr.getHostString().equals("0.0.0.0") && !javaAddr.getHostString().equals("")) {
this.geyserConfig.getRemote().setAddress(javaAddr.getHostString());
}
this.geyserConfig.getRemote().setPort(javaAddr.getPort());
}
if (geyserConfig.getBedrock().isCloneRemotePort()) {
geyserConfig.getBedrock().setPort(javaAddr.getPort());
}
}
// Force-disable query if enabled, or else Geyser won't enable
for (ListenerInfo info : getProxy().getConfig().getListeners()) {
if (info.isQueryEnabled() && info.getQueryPort() == geyserConfig.getBedrock().port()) {

View File

@ -1,3 +1,5 @@
import net.fabricmc.loom.task.RemapJarTask
plugins {
id("fabric-loom") version "1.0-SNAPSHOT"
id("com.modrinth.minotaur") version "2.+"
@ -33,6 +35,10 @@ dependencies {
}
}
loom {
mixin.defaultRefmapName.set("geyser-fabric-refmap.json")
}
repositories {
mavenLocal()
maven("https://repo.opencollab.dev/maven-releases/")
@ -89,8 +95,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 +117,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", "1.20.1")
loaders.add("fabric")
failSilently.set(true)
dependencies {
required.project("fabric-api")
}
}
}

View File

@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.fabric;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
@ -40,6 +41,8 @@ import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
@ -161,9 +164,37 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
builder.then(Commands.literal(command.getKey())
.executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire
.requires(executor::testPermission));
.requires(executor::testPermission)
// Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(builder);
// Register extension commands
for (Map.Entry<Extension, Map<String, Command>> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
Map<String, Command> extensionCommands = extensionMapEntry.getValue();
if (extensionCommands.isEmpty()) {
continue;
}
// Register help command for just "/<extensionId>"
GeyserFabricCommandExecutor extensionHelpExecutor = new GeyserFabricCommandExecutor(geyser,
(GeyserCommand) extensionCommands.get("help"));
LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
for (Map.Entry<String, Command> command : extensionCommands.entrySet()) {
GeyserFabricCommandExecutor executor = new GeyserFabricCommandExecutor(geyser, (GeyserCommand) command.getValue());
extCmdBuilder.then(Commands.literal(command.getKey())
.executes(executor)
.requires(executor::testPermission)
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(extCmdBuilder);
}
}
@Override
@ -220,7 +251,8 @@ public class GeyserFabricMod implements ModInitializer, GeyserBootstrap {
@NotNull
@Override
public String getServerBindAddress() {
return this.server.getLocalIp();
String ip = this.server.getLocalIp();
return ip != null ? ip : ""; // See issue #3812
}
@Override

View File

@ -75,6 +75,6 @@ public class FabricCommandSender implements GeyserCommandSource {
@Override
public boolean hasPermission(String permission) {
return Permissions.check(source, permission);
return Permissions.check(source, permission, source.getServer().getOperatorUserPermissionLevel());
}
}

View File

@ -53,6 +53,10 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
@Override
public int run(CommandContext context) {
return runWithArgs(context, "");
}
public int runWithArgs(CommandContext context, String args) {
CommandSourceStack source = (CommandSourceStack) context.getSource();
FabricCommandSender sender = new FabricCommandSender(source);
GeyserSession session = getGeyserSession(sender);
@ -68,7 +72,8 @@ public class GeyserFabricCommandExecutor extends GeyserCommandExecutor implement
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
return 0;
}
command.execute(session, sender, new String[0]);
command.execute(session, sender, args.split(" "));
return 0;
}
}

View File

@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.fabric.world;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.core.BlockPos;
@ -72,7 +73,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 +93,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);
});
}
@ -153,6 +154,11 @@ public class GeyserFabricWorldManager extends GeyserWorldManager {
return Permissions.check(player, permission);
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(server.getDefaultGameType().getId());
}
@Nonnull
@Override
public CompletableFuture<com.github.steveice10.opennbt.tag.builtin.CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {
@ -166,7 +172,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

View File

@ -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": "*"
}
}

View File

@ -2,6 +2,7 @@
"required": true,
"package": "org.geysermc.geyser.platform.fabric.mixin",
"compatibilityLevel": "JAVA_16",
"refmap": "geyser-fabric-refmap.json",
"client": [
"client.IntegratedServerMixin"
],

View File

@ -4,7 +4,9 @@ dependencies {
isTransitive = false
}
implementation(libs.adapters.spigot)
implementation(variantOf(libs.adapters.spigot) {
classifier("all") // otherwise the unshaded jar is used without the shaded NMS implementations
})
implementation(libs.commodore)
@ -22,6 +24,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)
@ -36,7 +39,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
@ -58,4 +60,4 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
// Commodore includes Brigadier
exclude(dependency("com.mojang:.*"))
}
}
}

View File

@ -43,6 +43,7 @@ import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.core.skin.SkinApplier;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
@ -68,7 +69,6 @@ import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotNativeWorld
import org.geysermc.geyser.platform.spigot.world.manager.GeyserSpigotWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.PlatformType;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@ -168,20 +168,6 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
return;
}
// By default this should be localhost but may need to be changed in some circumstances
if (this.geyserConfig.getRemote().address().equalsIgnoreCase("auto")) {
geyserConfig.setAutoconfiguredRemote(true);
// Don't use localhost if not listening on all interfaces
if (!Bukkit.getIp().equals("0.0.0.0") && !Bukkit.getIp().equals("")) {
geyserConfig.getRemote().setAddress(Bukkit.getIp());
}
geyserConfig.getRemote().setPort(Bukkit.getPort());
}
if (geyserConfig.getBedrock().isCloneRemotePort()) {
geyserConfig.getBedrock().setPort(Bukkit.getPort());
}
if (Bukkit.getPluginManager().getPlugin("floodgate") != null) {
geyserLogger.severe("WHY DO YOU HAVE FLOODGATE INSTALLED!!!!!!! REMOVE IT!!!!");
}
@ -337,6 +323,12 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
continue;
}
// Avoid registering the same permission twice, e.g. for the extension help commands
if (Bukkit.getPluginManager().getPermission(command.permission()) != null) {
GeyserImpl.getInstance().getLogger().debug("Skipping permission " + command.permission() + " as it is already registered");
continue;
}
Bukkit.getPluginManager().addPermission(new Permission(command.permission(),
GeyserLocale.getLocaleStringLog(command.description()),
command.isSuggestedOpOnly() ? PermissionDefault.OP : PermissionDefault.TRUE));

View File

@ -25,6 +25,7 @@
package org.geysermc.geyser.platform.spigot.world.manager;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import org.bukkit.Bukkit;
@ -171,6 +172,11 @@ public class GeyserSpigotWorldManager extends WorldManager {
return gameRule.getDefaultIntValue();
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission);

View File

@ -1,38 +0,0 @@
dependencies {
api(projects.core)
}
platformRelocate("com.fasterxml.jackson")
platformRelocate("io.netty")
platformRelocate("it.unimi.dsi.fastutil")
platformRelocate("com.google.common")
platformRelocate("com.google.guava")
platformRelocate("net.kyori.adventure.text.serializer.gson.legacyimpl")
platformRelocate("net.kyori.adventure.nbt")
// These dependencies are already present on the platform
provided(libs.sponge.api)
application {
mainClass.set("org.geysermc.geyser.platform.sponge.GeyserSpongeMain")
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
archiveBaseName.set("Geyser-Sponge")
dependencies {
exclude(dependency("com.google.code.gson:.*"))
exclude(dependency("org.yaml:.*"))
exclude(dependency("org.slf4j:.*"))
exclude(dependency("org.ow2.asm:.*"))
// Exclude all Kyori dependencies except the legacy NBT serializer and NBT
exclude(dependency("net.kyori:adventure-api:.*"))
exclude(dependency("net.kyori:examination-api:.*"))
exclude(dependency("net.kyori:examination-string:.*"))
exclude(dependency("net.kyori:adventure-text-serializer-gson:.*"))
exclude(dependency("net.kyori:adventure-text-serializer-legacy:.*"))
exclude(dependency("net.kyori:adventure-text-serializer-plain:.*"))
exclude(dependency("net.kyori:adventure-key:.*"))
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.sponge;
import lombok.Getter;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.spongepowered.api.Platform;
import org.spongepowered.api.Sponge;
import org.spongepowered.plugin.PluginContainer;
import org.spongepowered.plugin.metadata.PluginMetadata;
import org.spongepowered.plugin.metadata.model.PluginContributor;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Getter
public class GeyserSpongeDumpInfo extends BootstrapDumpInfo {
private final String platformName;
private final String platformVersion;
private final boolean onlineMode;
@AsteriskSerializer.Asterisk(isIp = true)
private final String serverIP;
private final int serverPort;
private final List<PluginInfo> plugins;
GeyserSpongeDumpInfo() {
PluginContainer container = Sponge.platform().container(Platform.Component.IMPLEMENTATION);
PluginMetadata platformMeta = container.metadata();
this.platformName = platformMeta.name().orElse("unknown");
this.platformVersion = platformMeta.version().getQualifier();
this.onlineMode = Sponge.server().isOnlineModeEnabled();
Optional<InetSocketAddress> socketAddress = Sponge.server().boundAddress();
this.serverIP = socketAddress.map(InetSocketAddress::getHostString).orElse("unknown");
this.serverPort = socketAddress.map(InetSocketAddress::getPort).orElse(-1);
this.plugins = new ArrayList<>();
for (PluginContainer plugin : Sponge.pluginManager().plugins()) {
PluginMetadata meta = plugin.metadata();
List<String> contributors = meta.contributors().stream().map(PluginContributor::name).collect(Collectors.toList());
this.plugins.add(new PluginInfo(true, meta.name().orElse("unknown"), meta.version().toString(), meta.entrypoint(), contributors));
}
}
}

View File

@ -1,106 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.sponge;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.ping.GeyserPingInfo;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.spongepowered.api.MinecraftVersion;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.event.Cause;
import org.spongepowered.api.event.EventContext;
import org.spongepowered.api.event.SpongeEventFactory;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.StatusClient;
import org.spongepowered.api.profile.GameProfile;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.util.Optional;
public class GeyserSpongePingPassthrough implements IGeyserPingPassthrough {
private static final Cause CAUSE = Cause.of(EventContext.empty(), Sponge.server());
private static Method SpongeStatusResponse_create;
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
// come on Sponge, this is in commons, why not expose it :(
ClientPingServerEvent event;
try {
if (SpongeStatusResponse_create == null) {
Class SpongeStatusResponse = Class.forName("org.spongepowered.common.network.status.SpongeStatusResponse");
Class MinecraftServer = Class.forName("net.minecraft.server.MinecraftServer");
SpongeStatusResponse_create = SpongeStatusResponse.getDeclaredMethod("create", MinecraftServer);
}
Object response = SpongeStatusResponse_create.invoke(null, Sponge.server());
event = SpongeEventFactory.createClientPingServerEvent(CAUSE, new GeyserStatusClient(inetSocketAddress), (ClientPingServerEvent.Response) response);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
Sponge.eventManager().post(event);
GeyserPingInfo geyserPingInfo = new GeyserPingInfo(
MessageTranslator.convertMessage(event.response().description()),
new GeyserPingInfo.Players(
event.response().players().orElseThrow(IllegalStateException::new).max(),
event.response().players().orElseThrow(IllegalStateException::new).online()
),
new GeyserPingInfo.Version(
event.response().version().name(),
GameProtocol.getJavaProtocolVersion()) // thanks for also not exposing this sponge
);
event.response().players().ifPresent(players -> players.profiles().stream()
.map(GameProfile::name)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(geyserPingInfo.getPlayerList()::add)
);
return geyserPingInfo;
}
private record GeyserStatusClient(InetSocketAddress remote) implements StatusClient {
@Override
public InetSocketAddress address() {
return this.remote;
}
@Override
public MinecraftVersion version() {
return Sponge.platform().minecraftVersion();
}
@Override
public Optional<InetSocketAddress> virtualHost() {
return Optional.empty();
}
}
}

View File

@ -1,247 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.sponge;
import com.google.inject.Inject;
import org.apache.logging.log4j.Logger;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandExecutor;
import org.geysermc.geyser.platform.sponge.command.GeyserSpongeCommandManager;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.PlatformType;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.Server;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.ConstructPluginEvent;
import org.spongepowered.api.event.lifecycle.RegisterCommandEvent;
import org.spongepowered.api.event.lifecycle.StartedEngineEvent;
import org.spongepowered.api.event.lifecycle.StoppingEngineEvent;
import org.spongepowered.plugin.PluginContainer;
import org.spongepowered.plugin.builtin.jvm.Plugin;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
@Plugin(value = "geyser")
public class GeyserSpongePlugin implements GeyserBootstrap {
/**
* True if the plugin should be in a disabled state.
* This exists because you can't unregister or disable plugins in Sponge
*/
private boolean enabled = true;
@Inject
private PluginContainer pluginContainer;
@Inject
private Logger logger;
@Inject
@ConfigDir(sharedRoot = false)
private Path configPath;
// Available after construction lifecycle
private GeyserSpongeConfiguration geyserConfig;
private GeyserSpongeLogger geyserLogger;
private GeyserImpl geyser;
private GeyserSpongeCommandManager geyserCommandManager; // Commands are only registered after command registration lifecycle
// Available after StartedEngine lifecycle
private IGeyserPingPassthrough geyserSpongePingPassthrough;
/**
* Only to be used for reloading
*/
@Override
public void onEnable() {
enabled = true;
onConstruction(null);
// new commands cannot be registered, and geyser's command manager does not need be reloaded
onStartedEngine(null);
}
@Override
public void onDisable() {
enabled = false;
if (geyser != null) {
geyser.shutdown();
geyser = null;
}
}
/**
* Construct the configuration, logger, and command manager. command manager will only be filled with commands once
* the connector is started, but it allows us to register events in sponge.
*
* @param event Not used.
*/
@Listener
public void onConstruction(@Nullable ConstructPluginEvent event) {
GeyserLocale.init(this);
File configDir = configPath.toFile();
if (!configDir.exists()) {
configDir.mkdirs();
}
File configFile;
try {
configFile = FileUtils.fileOrCopiedFromResource(new File(configDir, "config.yml"), "config.yml",
(file) -> file.replaceAll("generateduuid", UUID.randomUUID().toString()), this);
this.geyserConfig = FileUtils.loadConfig(configFile, GeyserSpongeConfiguration.class);
} catch (IOException ex) {
logger.error(GeyserLocale.getLocaleStringLog("geyser.config.failed"));
ex.printStackTrace();
onDisable();
return;
}
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyserLogger = new GeyserSpongeLogger(logger, geyserConfig.isDebugMode());
this.geyser = GeyserImpl.load(PlatformType.SPONGE, this, null);
this.geyserCommandManager = new GeyserSpongeCommandManager(geyser);
this.geyserCommandManager.init();
}
/**
* Construct the {@link GeyserSpongeCommandManager} and register the commands
*
* @param event required to register the commands
*/
@Listener
public void onRegisterCommands(@Nonnull RegisterCommandEvent<org.spongepowered.api.command.Command.Raw> event) {
if (enabled) {
event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(geyser, geyserCommandManager.getCommands()), "geyser");
for (Map.Entry<Extension, Map<String, Command>> entry : this.geyserCommandManager.extensionCommands().entrySet()) {
Map<String, Command> commands = entry.getValue();
if (commands.isEmpty()) {
continue;
}
event.register(this.pluginContainer, new GeyserSpongeCommandExecutor(this.geyser, commands), entry.getKey().description().id());
}
}
}
/**
* Configure the config properly if remote address is auto. Start connector and ping passthrough, and register subcommands of /geyser
*
* @param event not required
*/
@Listener
public void onStartedEngine(@Nullable StartedEngineEvent<Server> event) {
if (!enabled) {
return;
}
GeyserImpl.start();
if (geyserConfig.isLegacyPingPassthrough()) {
this.geyserSpongePingPassthrough = GeyserLegacyPingPassthrough.init(geyser);
} else {
this.geyserSpongePingPassthrough = new GeyserSpongePingPassthrough();
}
}
@Listener
public void onEngineStopping(StoppingEngineEvent<Server> event) {
onDisable();
}
@Override
public GeyserSpongeConfiguration getGeyserConfig() {
return geyserConfig;
}
@Override
public GeyserSpongeLogger getGeyserLogger() {
return geyserLogger;
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
}
@Override
public IGeyserPingPassthrough getGeyserPingPassthrough() {
return geyserSpongePingPassthrough;
}
@Override
public Path getConfigFolder() {
return configPath;
}
@Override
public BootstrapDumpInfo getDumpInfo() {
return new GeyserSpongeDumpInfo();
}
@Override
public String getMinecraftServerVersion() {
return Sponge.platform().minecraftVersion().name();
}
@NotNull
@Override
public String getServerBindAddress() {
return Sponge.server().boundAddress().map(InetSocketAddress::getHostString).orElse("");
}
@Override
public int getServerPort() {
return Sponge.server().boundAddress().stream().mapToInt(InetSocketAddress::getPort).findFirst().orElse(-1);
}
@Override
public boolean testFloodgatePluginPresent() {
return false;
}
}

View File

@ -1,112 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.sponge.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.command.CommandCause;
import org.spongepowered.api.command.CommandCompletion;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.parameter.ArgumentReader;
import java.util.*;
import java.util.stream.Collectors;
public class GeyserSpongeCommandExecutor extends GeyserCommandExecutor implements org.spongepowered.api.command.Command.Raw {
public GeyserSpongeCommandExecutor(GeyserImpl geyser, Map<String, Command> commands) {
super(geyser, commands);
}
@Override
public CommandResult process(CommandCause cause, ArgumentReader.Mutable arguments) {
GeyserCommandSource commandSource = new SpongeCommandSource(cause);
GeyserSession session = getGeyserSession(commandSource);
String[] args = arguments.input().split(" ");
// This split operation results in an array of length 1, containing a zero length string, if the input string is empty
if (args.length > 0 && !args[0].isEmpty()) {
GeyserCommand command = getCommand(args[0]);
if (command != null) {
if (!cause.hasPermission(command.permission())) {
cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.permission_fail")).color(NamedTextColor.RED));
return CommandResult.success();
}
if (command.isBedrockOnly() && session == null) {
cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.bedrock_only")).color(NamedTextColor.RED));
return CommandResult.success();
}
command.execute(session, commandSource, args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]);
} else {
cause.audience().sendMessage(Component.text(GeyserLocale.getLocaleStringLog("geyser.bootstrap.command.not_found")).color(NamedTextColor.RED));
}
} else {
getCommand("help").execute(session, commandSource, new String[0]);
}
return CommandResult.success();
}
@Override
public List<CommandCompletion> complete(CommandCause cause, ArgumentReader.Mutable arguments) {
if (arguments.input().split(" ").length == 1) {
return tabComplete(new SpongeCommandSource(cause)).stream().map(CommandCompletion::of).collect(Collectors.toList());
}
return Collections.emptyList();
}
@Override
public boolean canExecute(CommandCause cause) {
return true;
}
@Override
public Optional<Component> shortDescription(CommandCause cause) {
return Optional.of(Component.text("The main command for Geyser."));
}
@Override
public Optional<Component> extendedDescription(CommandCause cause) {
return shortDescription(cause);
}
@Override
public Optional<Component> help(@NotNull CommandCause cause) {
return Optional.of(Component.text("/geyser help"));
}
@Override
public Component usage(CommandCause cause) {
return Component.text("/geyser help");
}
}

View File

@ -1,30 +0,0 @@
{
"loader": {
"name": "java_plain",
"version": "1.0"
},
"license": "MIT",
"plugins": [
{
"id": "${id}",
"name": "${name}-Sponge",
"version": "${version}",
"entrypoint": "org.geysermc.geyser.platform.sponge.GeyserSpongePlugin",
"description": "${description}",
"links": {
"homepage": "${url}"
},
"contributors": [
{
"name": "${author}"
}
],
"dependencies": [
{
"id": "spongeapi",
"version": "8.0.0"
}
]
}
]
}

View File

@ -1,6 +0,0 @@
{
"pack": {
"description": "Geyser for Sponge",
"pack_format": 6
}
}

View File

@ -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)
@ -22,6 +20,13 @@ application {
mainClass.set("org.geysermc.geyser.platform.standalone.GeyserStandaloneBootstrap")
}
tasks.named<Jar>("jar") {
manifest {
// log4j provides multi-release java 9 code which resolves https://github.com/GeyserMC/Geyser/issues/3693
attributes("Multi-Release" to true)
}
}
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
archiveBaseName.set("Geyser-Standalone")

View File

@ -38,6 +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.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandManager;
@ -50,7 +51,6 @@ import org.geysermc.geyser.platform.standalone.gui.GeyserStandaloneGUI;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.LoopbackUtil;
import org.geysermc.geyser.util.PlatformType;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@ -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);

View File

@ -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();
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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"/>

View File

@ -38,6 +38,7 @@ import com.velocitypowered.api.proxy.ProxyServer;
import lombok.Getter;
import net.kyori.adventure.util.Codec;
import org.geysermc.floodgate.core.FloodgatePlatform;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.command.Command;
@ -52,7 +53,6 @@ import org.geysermc.geyser.platform.velocity.command.GeyserVelocityCommandExecut
import org.geysermc.geyser.platform.velocity.floodgate.FloodgateVelocityPlatform;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.FileUtils;
import org.geysermc.geyser.util.PlatformType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;

View File

@ -8,7 +8,7 @@ repositories {
}
dependencies {
implementation("net.kyori", "indra-common", "3.0.1")
implementation("net.kyori", "indra-common", "3.1.1")
implementation("com.github.johnrengelman", "shadow", "7.1.3-SNAPSHOT")
// Within the gradle plugin classpath, there is a version conflict between loom and some other

View File

@ -22,8 +22,8 @@ indra {
tasks {
processResources {
// Spigot, BungeeCord, Velocity, Sponge, Fabric
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "META-INF/sponge_plugins.json", "fabric.mod.json")) {
// Spigot, BungeeCord, Velocity, Fabric
filesMatching(listOf("plugin.yml", "bungee.yml", "velocity-plugin.json", "fabric.mod.json")) {
expand(
"id" to "geyser",
"name" to "Geyser",
@ -34,4 +34,4 @@ tasks {
)
}
}
}
}

View File

@ -5,12 +5,14 @@ plugins {
}
allprojects {
group = "org.geysermc.geyser"
version = "3.0.0-SNAPSHOT"
description = "Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers."
group = properties["group"] as String + "." + properties["id"] as String
version = properties["version"] as String
description = properties["description"] as String
}
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(16))
}
}
@ -18,7 +20,6 @@ val platforms = setOf(
projects.fabric,
projects.bungeecord,
projects.spigot,
projects.sponge,
projects.standalone,
projects.velocity
).map { it.dependencyProject }

View File

@ -6,3 +6,9 @@ dependencies {
api(libs.cumulus)
api(libs.gson)
}
indra {
javaVersions {
target(8)
}
}

View File

@ -22,6 +22,7 @@ dependencies {
implementation(libs.websocket)
api(libs.bundles.protocol)
implementation(libs.blockstateupdater)
api(libs.mcauthlib)
api(libs.mcprotocollib) {
@ -31,7 +32,7 @@ dependencies {
}
implementation(libs.raknet) {
exclude("io.netty", "*");
exclude("io.netty", "*")
}
implementation(libs.netty.resolver.dns)
@ -98,7 +99,7 @@ configure<BlossomExtension> {
}
fun Project.buildNumber(): Int =
System.getenv("BUILD_NUMBER")?.let { Integer.parseInt(it) } ?: -1
(System.getenv("GITHUB_RUN_NUMBER") ?: jenkinsBuildNumber())?.let { Integer.parseInt(it) } ?: -1
inner class GitInfo {
val branch: String
@ -130,3 +131,6 @@ inner class GitInfo {
repository = git?.repository?.config?.getString("remote", "origin", "url") ?: ""
}
}
// todo remove this when we're not using Jenkins anymore
fun jenkinsBuildNumber(): String? = System.getenv("BUILD_NUMBER")

View File

@ -26,9 +26,9 @@
package org.geysermc.connector;
import org.geysermc.api.Geyser;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.util.PlatformType;
import java.util.UUID;

View File

@ -36,19 +36,22 @@ public final class Constants {
public static final String FLOODGATE_DOWNLOAD_LOCATION = "https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/";
public static final String GEYSER_DOWNLOAD_LOCATION = "https://ci.geysermc.org";
public static final String GEYSER_DOWNLOAD_LOCATION = "https://geysermc.org/download";
public static final String UPDATE_PERMISSION = "geyser.update";
static final String SAVED_REFRESH_TOKEN_FILE = "saved-refresh-tokens.json";
public static final String GEYSER_CUSTOM_NAMESPACE = "geyser_custom";
public static final String MINECRAFT_SKIN_SERVER_URL = "https://textures.minecraft.net/texture/";
static {
URI wsUri = null;
try {
wsUri = new URI("wss://api.geysermc.org/ws");
} catch (URISyntaxException e) {
GeyserImpl.getInstance().getLogger().error("Unable to resolve api.geysermc.org! Check your internet connection.");
e.printStackTrace();
}
GLOBAL_API_WS_URI = wsUri;
}
}
}

View File

@ -43,6 +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.geyser.api.util.PlatformType;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.util.FormBuilder;
import org.geysermc.erosion.packet.Packets;
@ -66,9 +67,9 @@ import org.geysermc.geyser.extension.GeyserExtensionManager;
import org.geysermc.geyser.hybrid.HybridProvider;
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;
@ -87,6 +88,7 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@ -221,25 +223,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) {
@ -262,8 +248,8 @@ public class GeyserImpl implements GeyserApi {
SkinProvider.registerCacheImageTask(this);
ResourcePack.loadPacks();
//TODO start
Registries.RESOURCE_PACKS.load();
String geyserUdpPort = System.getProperty("geyserUdpPort", "");
String pluginUdpPort = geyserUdpPort.isEmpty() ? System.getProperty("pluginUdpPort", "") : geyserUdpPort;
if ("-1".equals(pluginUdpPort)) {
@ -413,7 +399,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", () -> {
@ -449,7 +435,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", () -> {
@ -543,6 +529,8 @@ public class GeyserImpl implements GeyserApi {
if (config.isNotifyOnNewBedrockUpdate()) {
VersionCheckUtils.checkForGeyserUpdate(this::getLogger);
}
VersionCheckUtils.checkForOutdatedJava(logger);
}
@Override
@ -623,7 +611,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();
@ -682,6 +670,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;

View File

@ -29,22 +29,38 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.nullness.qual.NonNull;
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;
import org.geysermc.geyser.api.command.CommandSource;
import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCommandsEvent;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.command.defaults.*;
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
import org.geysermc.geyser.command.defaults.DumpCommand;
import org.geysermc.geyser.command.defaults.ExtensionsCommand;
import org.geysermc.geyser.command.defaults.HelpCommand;
import org.geysermc.geyser.command.defaults.ListCommand;
import org.geysermc.geyser.command.defaults.OffhandCommand;
import org.geysermc.geyser.command.defaults.ReloadCommand;
import org.geysermc.geyser.command.defaults.SettingsCommand;
import org.geysermc.geyser.command.defaults.StatisticsCommand;
import org.geysermc.geyser.command.defaults.StopCommand;
import org.geysermc.geyser.command.defaults.VersionCommand;
import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl;
import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.PlatformType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@RequiredArgsConstructor
public class GeyserCommandManager {
@ -91,7 +107,8 @@ public class GeyserCommandManager {
// Register help commands for all extensions with commands
for (Map.Entry<Extension, Map<String, Command>> entry : this.extensionCommands.entrySet()) {
registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp", entry.getKey().description().id(), entry.getValue()));
String id = entry.getKey().description().id();
registerExtensionCommand(entry.getKey(), new HelpCommand(this.geyser, "help", "geyser.commands.exthelp.desc", "geyser.command.exthelp." + id, id, entry.getValue()));
}
}

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.command.defaults;
import com.fasterxml.jackson.databind.JsonNode;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
@ -36,11 +37,20 @@ import org.geysermc.geyser.util.PlatformType;
import org.geysermc.geyser.util.WebUtils;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
public class ConnectionTestCommand extends GeyserCommand {
/*
* The MOTD is temporarily changed during the connection test.
* This allows us to check if we are pinging the correct Geyser instance
*/
public static String CONNECTION_TEST_MOTD = null;
private final GeyserImpl geyser;
private final Random random = new Random();
public ConnectionTestCommand(GeyserImpl geyser, String name, String description, String permission) {
super(name, description, permission);
this.geyser = geyser;
@ -55,23 +65,61 @@ public class ConnectionTestCommand extends GeyserCommand {
}
if (args.length == 0) {
sender.sendMessage("Provide the Bedrock server IP you are trying to connect with. Example: `test.geysermc.org:19132`");
sender.sendMessage("Provide the server IP and port you are trying to test Bedrock connections for. Example: `test.geysermc.org:19132`");
return;
}
// Replace "<" and ">" symbols if they are present to avoid the common issue of people including them
String[] fullAddress = args[0].replace("<", "").replace(">", "").split(":", 2);
// Still allow people to not supply a port and fallback to 19132
String[] fullAddress = args[0].split(":", 2);
int port;
if (fullAddress.length == 2) {
port = Integer.parseInt(fullAddress[1]);
try {
port = Integer.parseInt(fullAddress[1]);
} catch (NumberFormatException e) {
// can occur if e.g. "/geyser connectiontest <ip>:<port> is ran
sender.sendMessage("Not a valid port! Specify a valid numeric port.");
return;
}
} else {
port = 19132;
}
String ip = fullAddress[0];
// Issue: people commonly checking placeholders
if (ip.equals("ip")) {
sender.sendMessage(ip + " is not a valid IP, and instead a placeholder. Please specify the IP to check.");
return;
}
// Issue: checking 0.0.0.0 won't work
if (ip.equals("0.0.0.0")) {
sender.sendMessage("Please specify the IP that you would connect with. 0.0.0.0 in the config tells Geyser to the listen on the server's IPv4.");
return;
}
// Issue: people testing local ip
if (ip.equals("localhost") || ip.startsWith("127.") || ip.startsWith("10.") || ip.startsWith("192.168.")) {
sender.sendMessage("This tool checks if connections from other networks are possible, so you cannot check a local IP.");
return;
}
// Issue: do the ports not line up?
if (port != geyser.getConfig().getBedrock().port()) {
sender.sendMessage("The port you supplied (" + port + ") does not match the port supplied in Geyser's configuration ("
+ geyser.getConfig().getBedrock().port() + "). You can change it under `bedrock` `port`.");
if (fullAddress.length == 2) {
sender.sendMessage("The port you are testing with (" + port + ") is not the same as you set in your Geyser configuration ("
+ geyser.getConfig().getBedrock().port() + ")");
sender.sendMessage("Re-run the command with the port in the config, or change the `bedrock` `port` in the config.");
if (geyser.getConfig().getBedrock().isCloneRemotePort()) {
sender.sendMessage("You have `clone-remote-port` enabled. This option ignores the `bedrock` `port` in the config, and uses the Java server port instead.");
}
} else {
sender.sendMessage("You did not specify the port to check (add it with \":<port>\"), " +
"and the default port 19132 does not match the port in your Geyser configuration ("
+ geyser.getConfig().getBedrock().port() + ")!");
sender.sendMessage("Re-run the command with that port, or change the port in the config under `bedrock` `port`.");
}
}
// Issue: is the `bedrock` `address` in the config different?
@ -79,7 +127,7 @@ public class ConnectionTestCommand extends GeyserCommand {
sender.sendMessage("The address specified in `bedrock` `address` is not \"0.0.0.0\" - this may cause issues unless this is deliberate and intentional.");
}
// Issue: did someone turn on enable-proxy-protocol and they didn't mean it?
// Issue: did someone turn on enable-proxy-protocol, and they didn't mean it?
if (geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
sender.sendMessage("You have the `enable-proxy-protocol` setting enabled. " +
"Unless you're deliberately using additional software that REQUIRES this setting, you may not need it enabled.");
@ -88,7 +136,6 @@ public class ConnectionTestCommand extends GeyserCommand {
CompletableFuture.runAsync(() -> {
try {
// Issue: SRV record?
String ip = fullAddress[0];
String[] record = WebUtils.findSrvRecord(geyser, ip);
if (record != null && !ip.equals(record[3]) && !record[2].equals(String.valueOf(port))) {
sender.sendMessage("Bedrock Edition does not support SRV records. Try connecting to your server using the address " + record[3] + " and the port " + record[2]
@ -102,20 +149,50 @@ public class ConnectionTestCommand extends GeyserCommand {
"See here for steps on how to resolve: " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/#using-geyser-on-the-same-computer");
}
// mcsrvstatus will likely be replaced in the future with our own service where we can also test
// around the OVH workaround without worrying about caching
JsonNode output = WebUtils.getJson("https://api.mcsrvstat.us/bedrock/2/" + args[0]);
// Generate some random, unique bits that another server wouldn't provide
byte[] randomBytes = new byte[2];
this.random.nextBytes(randomBytes);
StringBuilder randomStr = new StringBuilder();
for (byte b : randomBytes) {
randomStr.append(Integer.toHexString(b));
}
String connectionTestMotd = "Geyser Connection Test " + randomStr;
CONNECTION_TEST_MOTD = connectionTestMotd;
long cacheTime = output.get("debug").get("cachetime").asLong();
String when;
if (cacheTime == 0) {
when = "now";
} else {
when = ((System.currentTimeMillis() / 1000L) - cacheTime) + " seconds ago";
sender.sendMessage("Testing server connection now. Please wait...");
JsonNode output;
try {
output = WebUtils.getJson("https://checker.geysermc.org/ping?hostname=" + ip + "&port=" + port);
} finally {
CONNECTION_TEST_MOTD = null;
}
if (output.get("online").asBoolean()) {
sender.sendMessage("Your server is likely online as of " + when + "!");
JsonNode cache = output.get("cache");
String when;
if (cache.get("fromCache").asBoolean()) {
when = cache.get("secondsSince").asInt() + " seconds ago";
} else {
when = "now";
}
if (output.get("success").asBoolean()) {
JsonNode ping = output.get("ping");
JsonNode pong = ping.get("pong");
String remoteMotd = pong.get("motd").asText();
if (!connectionTestMotd.equals(remoteMotd)) {
sender.sendMessage("The MOTD did not match when we pinged the server (we got '" + remoteMotd + "'). " +
"Did you supply the correct IP and port of your server?");
sendLinks(sender);
return;
}
if (ping.get("tcpFirst").asBoolean()) {
sender.sendMessage("Your server hardware likely has some sort of firewall preventing people from joining easily. See https://geysermc.link/ovh-firewall for more information.");
sendLinks(sender);
return;
}
sender.sendMessage("Your server is likely online and working as of " + when + "!");
sendLinks(sender);
return;
}
@ -123,16 +200,16 @@ public class ConnectionTestCommand extends GeyserCommand {
sender.sendMessage("Your server is likely unreachable from outside the network as of " + when + ".");
sendLinks(sender);
} catch (Exception e) {
sender.sendMessage("Error while trying to check your connection!");
sender.sendMessage("An error occurred while trying to check your connection! Check the console for more information.");
geyser.getLogger().error("Error while trying to check your connection!", e);
}
});
}
private void sendLinks(GeyserCommandSource sender) {
sender.sendMessage("If you still have issues, check to see if your hosting provider has a specific setup: " +
"https://wiki.geysermc.org/geyser/supported-hosting-providers/" + ", see this page: "
+ "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on our Discord: " + "https://discord.gg/geysermc");
sender.sendMessage("If you still face issues, check the setup guide for instructions: " +
"https://wiki.geysermc.org/geyser/setup/");
sender.sendMessage("If that does not work, see " + "https://wiki.geysermc.org/geyser/fixing-unable-to-connect-to-world/" + ", or contact us on Discord: " + "https://discord.gg/geysermc");
}
@Override

View File

@ -30,6 +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.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
@ -38,7 +39,6 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.PlatformType;
import org.geysermc.geyser.util.WebUtils;
import java.io.FileOutputStream;

View File

@ -25,6 +25,7 @@
package org.geysermc.geyser.command.defaults;
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;
@ -32,7 +33,6 @@ import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.PlatformType;
import java.util.Collections;
import java.util.Map;
@ -63,7 +63,8 @@ public class HelpCommand extends GeyserCommand {
public void execute(GeyserSession session, GeyserCommandSource sender, String[] args) {
int page = 1;
int maxPage = 1;
String header = GeyserLocale.getPlayerLocaleString("geyser.commands.help.header", sender.locale(), page, maxPage);
String translationKey = this.baseCommand.equals("geyser") ? "geyser.commands.help.header" : "geyser.commands.extensions.header";
String header = GeyserLocale.getPlayerLocaleString(translationKey, sender.locale(), page, maxPage);
sender.sendMessage(header);
this.commands.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {

View File

@ -25,12 +25,12 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.PlatformType;
public class ReloadCommand extends GeyserCommand {

View File

@ -25,12 +25,12 @@
package org.geysermc.geyser.command.defaults;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.PlatformType;
import java.util.Collections;

View File

@ -26,6 +26,8 @@
package org.geysermc.geyser.command.defaults;
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
@ -33,7 +35,6 @@ import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.util.PlatformType;
import org.geysermc.geyser.util.WebUtils;
import java.io.IOException;
@ -84,7 +85,7 @@ public class VersionCommand extends GeyserCommand {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.no_updates", sender.locale()));
} else {
sender.sendMessage(GeyserLocale.getPlayerLocaleString("geyser.commands.version.outdated",
sender.locale(), (latestBuildNum - buildNum), "https://ci.geysermc.org/"));
sender.locale(), (latestBuildNum - buildNum), Constants.GEYSER_DOWNLOAD_LOCATION));
}
} else {
throw new AssertionError("buildNumber missing");

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.configuration;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@JsonIgnoreProperties(ignoreUnknown = true)
@SuppressWarnings("FieldMayBeFinal") // Jackson requires that the fields are not final
public class GeyserCustomSkullConfiguration {
@JsonProperty("player-usernames")
private List<String> playerUsernames;
@JsonProperty("player-uuids")
private List<String> playerUUIDs;
@JsonProperty("player-profiles")
private List<String> playerProfiles;
@JsonProperty("skin-hashes")
private List<String> skinHashes;
public List<String> getPlayerUsernames() {
return Objects.requireNonNullElse(playerUsernames, Collections.emptyList());
}
public List<String> getPlayerUUIDs() {
return Objects.requireNonNullElse(playerUUIDs, Collections.emptyList());
}
public List<String> getPlayerProfiles() {
return Objects.requireNonNullElse(playerProfiles, Collections.emptyList());
}
public List<String> getPlayerSkinHashes() {
return Objects.requireNonNullElse(skinHashes, Collections.emptyList());
}
}

View File

@ -282,6 +282,11 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
return authType;
}
@Override
public boolean resolveSrv() {
return false;
}
@Getter
@JsonProperty("allow-password-authentication")
private boolean passwordAuthentication = true;

View File

@ -27,9 +27,9 @@ package org.geysermc.geyser.dump;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.geyser.api.util.PlatformType;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.text.AsteriskSerializer;
import org.geysermc.geyser.util.PlatformType;
import java.util.List;

View File

@ -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();
@ -777,7 +788,7 @@ public final class EntityDefinitions {
.build();
FOX = EntityDefinition.inherited(FoxEntity::new, ageableEntityBase)
.type(EntityType.FOX)
.height(0.5f).width(1.25f)
.height(0.7f).width(0.6f)
.addTranslator(MetadataType.INT, FoxEntity::setFoxVariant)
.addTranslator(MetadataType.BYTE, FoxEntity::setFoxFlags)
.addTranslator(null) // Trusted player 1
@ -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

View File

@ -66,7 +66,7 @@ public class BoatEntity extends Entity {
private int variant;
// Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it
private final float ROWING_SPEED = 0.05f;
private final float ROWING_SPEED = 0.1f;
public BoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
// Initial rotation is incorrect
@ -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;
});
}

View File

@ -36,6 +36,7 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.math.vector.Vector2f;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
@ -171,7 +172,9 @@ public class Entity implements GeyserEntity {
addEntityPacket.setUniqueEntityId(geyserId);
addEntityPacket.setPosition(position);
addEntityPacket.setMotion(motion);
addEntityPacket.setRotation(getBedrockRotation().toVector2(false)); // TODO: Check this
addEntityPacket.setRotation(Vector2f.from(pitch, yaw));
addEntityPacket.setHeadRotation(headYaw);
addEntityPacket.setBodyRotation(yaw); // TODO: This should be bodyYaw
addEntityPacket.getMetadata().putFlags(flags);
dirtyMetadata.apply(addEntityPacket.getMetadata());
addAdditionalSpawnData(addEntityPacket);
@ -490,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();
}

View File

@ -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();
}
}

View File

@ -35,7 +35,7 @@ import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
@ -83,8 +83,7 @@ public class ItemFrameEntity extends Entity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
NbtMapBuilder blockBuilder = NbtMap.builder()
.putString("name", this.definition.entityType() == EntityType.GLOW_ITEM_FRAME ? "minecraft:glow_frame" : "minecraft:frame")
.putInt("version", session.getBlockMappings().getBlockStateVersion());
.putString("name", this.definition.entityType() == EntityType.GLOW_ITEM_FRAME ? "minecraft:glow_frame" : "minecraft:frame");
NbtMapBuilder statesBuilder = NbtMap.builder()
.putInt("facing_direction", direction.ordinal())
.putByte("item_frame_map_bit", (byte) 0)

View File

@ -41,7 +41,6 @@ import lombok.Setter;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.AttributeData;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
@ -211,10 +210,10 @@ public class LivingEntity extends Entity {
// If an entity has a banner on them, it will be in the helmet slot in Java but the chestplate spot in Bedrock
// But don't overwrite the chestplate if it isn't empty
ItemMapping banner = session.getItemMappings().getStoredItems().banner();
if (ItemDefinition.AIR.equals(chestplate.getDefinition()) && helmet.getDefinition().equals(banner)) {
if (ItemData.AIR.equals(chestplate) && helmet.getDefinition().equals(banner.getBedrockDefinition())) {
chestplate = this.helmet;
helmet = ItemData.AIR;
} else if (chestplate.getDefinition().equals(banner)) {
} else if (chestplate.getDefinition().equals(banner.getBedrockDefinition())) {
// Prevent chestplate banners from showing erroneously
chestplate = ItemData.AIR;
}

View File

@ -249,7 +249,7 @@ public class ArmorStandEntity extends LivingEntity {
@Override
public InteractionResult interactAt(Hand hand) {
if (!isMarker && session.getPlayerInventory().getItemInHand(hand).asItem() != Items.NAME_TAG) {
// Java Edition returns SUCCESS if in spectator mode, but this is overrided with an earlier check on the client
// Java Edition returns SUCCESS if in spectator mode, but this is overridden with an earlier check on the client
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;

View File

@ -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);
}
}
}
}

View File

@ -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);

View File

@ -118,7 +118,7 @@ public class VillagerEntity extends AbstractMerchantEntity {
// The bed block
int blockId = session.getGeyser().getWorldManager().getBlockAt(session, bedPosition);
String fullIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockId, BlockMapping.AIR).getJavaIdentifier();
String fullIdentifier = BlockRegistries.JAVA_BLOCKS.getOrDefault(blockId, BlockMapping.DEFAULT).getJavaIdentifier();
// Set the correct position offset and rotation when sleeping
int bedRotation = 0;

View File

@ -29,7 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanE
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.data.defintions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;

View File

@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.attribute.AttributeTyp
import com.github.steveice10.mc.protocol.data.game.entity.metadata.GlobalPos;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Getter;
import org.cloudburstmc.math.vector.Vector3f;
@ -67,10 +68,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);
@ -113,10 +110,17 @@ public class SessionPlayerEntity extends PlayerEntity {
this.position = position;
}
/**
* Sending any updated flags (sprinting, onFire, etc.) to the client while in spectator is not needed
* Also "fixes" https://github.com/GeyserMC/Geyser/issues/3318
*/
@Override
public void setFlags(ByteEntityMetadata entityMetadata) {
super.setFlags(entityMetadata);
session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING));
// TODO: proper fix, BDS somehow does it? https://paste.gg/p/anonymous/3adfb7612f1540be80fa03a2281f93dc (BDS 1.20.13)
if (!this.session.getGameMode().equals(GameMode.SPECTATOR)) {
super.setFlags(entityMetadata);
session.setSwimmingInWater((entityMetadata.getPrimitiveValue() & 0x10) == 0x10 && getFlag(EntityFlag.SPRINTING));
}
refreshSpeed = true;
}
@ -175,11 +179,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

View File

@ -0,0 +1,93 @@
/*
* 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.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.BedrockPong;
import org.geysermc.geyser.api.event.connection.GeyserBedrockPingEvent;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.net.InetSocketAddress;
import java.util.Objects;
public class GeyserBedrockPingEventImpl implements GeyserBedrockPingEvent {
private final InetSocketAddress address;
private final BedrockPong pong;
public GeyserBedrockPingEventImpl(BedrockPong pong, InetSocketAddress address) {
this.address = address;
this.pong = pong;
}
@Override
public void primaryMotd(@Nonnull String primary) {
pong.motd(Objects.requireNonNull(primary, "Primary MOTD cannot be null"));
}
@Override
public void secondaryMotd(@Nonnull String secondary) {
pong.subMotd(Objects.requireNonNull(secondary, "Secondary MOTD cannot be null"));
}
@Override
public void playerCount(int count) {
if (count < 0) throw new IllegalArgumentException("Player count cannot be below 0");
pong.playerCount(count);
}
@Override
public void maxPlayerCount(int max) {
if (max < 1) throw new IllegalArgumentException("Max player count cannot be below 1");
pong.maximumPlayerCount(max);
}
@Override
public @Nullable String primaryMotd() {
return pong.motd();
}
@Override
public @Nullable String secondaryMotd() {
return pong.subMotd();
}
@Override
public @NonNegative int playerCount() {
return pong.playerCount();
}
@Override
public int maxPlayerCount() {
return pong.maximumPlayerCount();
}
@Override
public @NotNull InetSocketAddress address() {
return address;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -75,7 +75,7 @@ public class AnvilContainer extends Container {
String originalName = ItemUtils.getCustomName(getInput().getNbt());
String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale());
String plainOriginalName = MessageTranslator.convertToPlainTextLenient(originalName, session.locale());
String plainNewName = MessageTranslator.convertToPlainText(rename);
if (!plainOriginalName.equals(plainNewName)) {
// Strip out formatting since Java Edition does not allow it

View File

@ -33,7 +33,7 @@ import org.cloudburstmc.math.vector.Vector3i;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.cloudburstmc.protocol.bedrock.data.defintions.ItemDefinition;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;

View File

@ -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();
}
}
}

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