Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-26 00:00:41 +01:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into feature/1.16.2
Dieser Commit ist enthalten in:
Commit
2dc71382e7
1
.gitignore
vendored
1
.gitignore
vendored
@ -241,3 +241,4 @@ config.yml
|
||||
logs/
|
||||
public-key.pem
|
||||
locales/
|
||||
cache/
|
@ -39,6 +39,9 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set
|
||||
- [ ] Beacon
|
||||
- [ ] Cartography Table
|
||||
- [ ] Stonecutter
|
||||
- [ ] Command Block
|
||||
- [ ] Structure Block
|
||||
- [ ] Horse Inventory
|
||||
- Some Entity Flags
|
||||
|
||||
## Compiling
|
||||
|
@ -25,17 +25,19 @@
|
||||
|
||||
package org.geysermc.platform.spigot.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.Block;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.WorldManager;
|
||||
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.utils.GameRule;
|
||||
import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13;
|
||||
import us.myles.ViaVersion.protocols.protocol1_16_2to1_16_1.data.MappingData;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class GeyserSpigotWorldManager extends WorldManager {
|
||||
public class GeyserSpigotWorldManager extends GeyserWorldManager {
|
||||
|
||||
private final boolean isLegacy;
|
||||
// You need ViaVersion to connect to an older server with Geyser.
|
||||
@ -70,4 +72,19 @@ public class GeyserSpigotWorldManager extends WorldManager {
|
||||
return BlockTranslator.AIR;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
|
||||
return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
|
||||
return Integer.parseInt(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(GeyserSession session, String permission) {
|
||||
return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission);
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,11 @@ public class GeyserSpongeConfiguration implements GeyserConfiguration {
|
||||
return node.getNode("cache-chunks").getBoolean(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCacheImages() {
|
||||
return node.getNode("cache-skins").getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAboveBedrockNetherBuilding() {
|
||||
return node.getNode("above-bedrock-nether-building").getBoolean(false);
|
||||
|
@ -96,6 +96,12 @@
|
||||
<version>8.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nukkitx.fastutil</groupId>
|
||||
<artifactId>fastutil-object-object-maps</artifactId>
|
||||
<version>8.3.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
@ -36,6 +36,9 @@ public interface GeyserLogger {
|
||||
|
||||
/**
|
||||
* Logs a severe message and an exception to console
|
||||
*
|
||||
* @param message the message to log
|
||||
* @param error the error to throw
|
||||
*/
|
||||
void severe(String message, Throwable error);
|
||||
|
||||
@ -48,6 +51,9 @@ public interface GeyserLogger {
|
||||
|
||||
/**
|
||||
* Logs an error message and an exception to console
|
||||
*
|
||||
* @param message the message to log
|
||||
* @param error the error to throw
|
||||
*/
|
||||
void error(String message, Throwable error);
|
||||
|
||||
|
@ -30,14 +30,14 @@ import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.connector.configuration.GeyserConfiguration;
|
||||
import org.geysermc.connector.GeyserLogger;
|
||||
import org.geysermc.connector.command.CommandManager;
|
||||
import org.geysermc.connector.network.translators.world.CachedChunkManager;
|
||||
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
|
||||
import org.geysermc.connector.network.translators.world.WorldManager;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface GeyserBootstrap {
|
||||
|
||||
CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager();
|
||||
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
|
||||
|
||||
/**
|
||||
* Called when the GeyserBootstrap is enabled
|
||||
|
@ -77,6 +77,8 @@ public interface GeyserConfiguration {
|
||||
|
||||
boolean isCacheChunks();
|
||||
|
||||
int getCacheImages();
|
||||
|
||||
IMetricsInfo getMetrics();
|
||||
|
||||
interface IBedrockConfiguration {
|
||||
|
@ -87,6 +87,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@JsonProperty("cache-chunks")
|
||||
private boolean cacheChunks;
|
||||
|
||||
@JsonProperty("cache-images")
|
||||
private int cacheImages = 0;
|
||||
|
||||
@JsonProperty("above-bedrock-nether-building")
|
||||
private boolean aboveBedrockNetherBuilding;
|
||||
|
||||
|
@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
|
||||
import com.github.steveice10.mc.protocol.data.message.TextMessage;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
|
||||
import com.nukkitx.protocol.bedrock.data.AttributeData;
|
||||
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
|
||||
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
||||
@ -38,7 +39,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.attribute.Attribute;
|
||||
import org.geysermc.connector.entity.attribute.AttributeType;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
@ -47,12 +47,8 @@ import org.geysermc.connector.network.session.cache.EntityEffectCache;
|
||||
import org.geysermc.connector.scoreboard.Team;
|
||||
import org.geysermc.connector.utils.AttributeUtils;
|
||||
import org.geysermc.connector.utils.MessageUtils;
|
||||
import org.geysermc.connector.utils.SkinUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter @Setter
|
||||
@ -61,7 +57,7 @@ public class PlayerEntity extends LivingEntity {
|
||||
private UUID uuid;
|
||||
private String username;
|
||||
private long lastSkinUpdate = -1;
|
||||
private boolean playerList = true;
|
||||
private boolean playerList = true; // Player is in the player list
|
||||
private final EntityEffectCache effectCache;
|
||||
|
||||
private Entity leftParrot;
|
||||
@ -97,7 +93,7 @@ public class PlayerEntity extends LivingEntity {
|
||||
addPlayerPacket.setMotion(motion);
|
||||
addPlayerPacket.setHand(hand);
|
||||
addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL);
|
||||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.VISITOR);
|
||||
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
|
||||
addPlayerPacket.setDeviceId("");
|
||||
addPlayerPacket.setPlatformChatId("");
|
||||
addPlayerPacket.getMetadata().putAll(metadata);
|
||||
@ -117,30 +113,12 @@ public class PlayerEntity extends LivingEntity {
|
||||
public void sendPlayer(GeyserSession session) {
|
||||
if(session.getEntityCache().getPlayerEntity(uuid) == null)
|
||||
return;
|
||||
if (getLastSkinUpdate() == -1) {
|
||||
if (playerList) {
|
||||
PlayerListPacket playerList = new PlayerListPacket();
|
||||
playerList.setAction(PlayerListPacket.Action.ADD);
|
||||
playerList.getEntries().add(SkinUtils.buildDefaultEntry(profile, geyserId));
|
||||
session.sendUpstreamPacket(playerList);
|
||||
}
|
||||
}
|
||||
|
||||
if (session.getUpstream().isInitialized() && session.getEntityCache().getEntityByGeyserId(geyserId) == null) {
|
||||
session.getEntityCache().spawnEntity(this);
|
||||
} else {
|
||||
spawnEntity(session);
|
||||
}
|
||||
|
||||
if (!playerList) {
|
||||
// remove from playerlist if player isn't on playerlist
|
||||
GeyserConnector.getInstance().getGeneralThreadPool().execute(() -> {
|
||||
PlayerListPacket playerList = new PlayerListPacket();
|
||||
playerList.setAction(PlayerListPacket.Action.REMOVE);
|
||||
playerList.getEntries().add(new PlayerListPacket.Entry(uuid));
|
||||
session.sendUpstreamPacket(playerList);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -232,7 +210,7 @@ public class PlayerEntity extends LivingEntity {
|
||||
|
||||
if (entityMetadata.getId() == 2) {
|
||||
// System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet());
|
||||
for (Team team : session.getScoreboardCache().getScoreboard().getTeams().values()) {
|
||||
for (Team team : session.getWorldCache().getScoreboard().getTeams().values()) {
|
||||
// session.getConnector().getLogger().info("team name " + team.getName());
|
||||
// session.getConnector().getLogger().info("team entities " + team.getEntities());
|
||||
}
|
||||
@ -241,7 +219,7 @@ public class PlayerEntity extends LivingEntity {
|
||||
if (name != null) {
|
||||
username = MessageUtils.getBedrockMessage(name);
|
||||
}
|
||||
Team team = session.getScoreboardCache().getScoreboard().getTeamFor(username);
|
||||
Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
|
||||
if (team != null) {
|
||||
// session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix());
|
||||
metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());
|
||||
|
@ -53,14 +53,21 @@ public class ShulkerEntity extends GolemEntity {
|
||||
metadata.put(EntityData.SHULKER_ATTACH_POS, Vector3i.from(position.getX(), position.getY(), position.getZ()));
|
||||
}
|
||||
}
|
||||
//TODO Outdated metadata flag SHULKER_PEAK_HEIGHT
|
||||
// if (entityMetadata.getId() == 17) {
|
||||
// int height = (byte) entityMetadata.getValue();
|
||||
// metadata.put(EntityData.SHULKER_PEAK_HEIGHT, height);
|
||||
// }
|
||||
|
||||
if (entityMetadata.getId() == 17) {
|
||||
int height = (byte) entityMetadata.getValue();
|
||||
metadata.put(EntityData.SHULKER_PEEK_ID, height);
|
||||
}
|
||||
|
||||
if (entityMetadata.getId() == 18) {
|
||||
int color = Math.abs((byte) entityMetadata.getValue() - 15);
|
||||
metadata.put(EntityData.VARIANT, color);
|
||||
byte color = (byte) entityMetadata.getValue();
|
||||
if (color == 16) {
|
||||
// 16 is default on both editions
|
||||
metadata.put(EntityData.VARIANT, 16);
|
||||
} else {
|
||||
// Every other shulker color is offset 15 in bedrock edition
|
||||
metadata.put(EntityData.VARIANT, Math.abs(color - 15));
|
||||
}
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
|
||||
import org.geysermc.connector.utils.LoginEncryptionUtils;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
import org.geysermc.connector.utils.SettingsUtils;
|
||||
|
||||
public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
@ -91,6 +92,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(ModalFormResponsePacket packet) {
|
||||
if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) {
|
||||
return SettingsUtils.handleSettingsForm(session, packet.getFormData());
|
||||
}
|
||||
|
||||
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ import com.nukkitx.math.vector.*;
|
||||
import com.nukkitx.protocol.bedrock.BedrockPacket;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServerSession;
|
||||
import com.nukkitx.protocol.bedrock.data.*;
|
||||
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
|
||||
@ -54,6 +55,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.common.window.CustomFormWindow;
|
||||
import org.geysermc.common.window.FormWindow;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
@ -79,9 +81,7 @@ import java.net.InetSocketAddress;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Getter
|
||||
@ -102,7 +102,7 @@ public class GeyserSession implements CommandSender {
|
||||
private ChunkCache chunkCache;
|
||||
private EntityCache entityCache;
|
||||
private InventoryCache inventoryCache;
|
||||
private ScoreboardCache scoreboardCache;
|
||||
private WorldCache worldCache;
|
||||
private WindowCache windowCache;
|
||||
@Setter
|
||||
private TeleportCache teleportCache;
|
||||
@ -191,6 +191,41 @@ public class GeyserSession implements CommandSender {
|
||||
|
||||
private MinecraftProtocol protocol;
|
||||
|
||||
private boolean reducedDebugInfo = false;
|
||||
|
||||
@Setter
|
||||
private CustomFormWindow settingsForm;
|
||||
|
||||
/**
|
||||
* The op permission level set by the server
|
||||
*/
|
||||
@Setter
|
||||
private int opPermissionLevel = 0;
|
||||
|
||||
/**
|
||||
* If the current player can fly
|
||||
*/
|
||||
@Setter
|
||||
private boolean canFly = false;
|
||||
|
||||
/**
|
||||
* If the current player is flying
|
||||
*/
|
||||
@Setter
|
||||
private boolean flying = false;
|
||||
|
||||
/**
|
||||
* If the current player is in noclip
|
||||
*/
|
||||
@Setter
|
||||
private boolean noClip = false;
|
||||
|
||||
/**
|
||||
* If the current player can not interact with the world
|
||||
*/
|
||||
@Setter
|
||||
private boolean worldImmutable = false;
|
||||
|
||||
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
|
||||
this.connector = connector;
|
||||
this.upstream = new UpstreamSession(bedrockServerSession);
|
||||
@ -198,7 +233,7 @@ public class GeyserSession implements CommandSender {
|
||||
this.chunkCache = new ChunkCache(this);
|
||||
this.entityCache = new EntityCache(this);
|
||||
this.inventoryCache = new InventoryCache(this);
|
||||
this.scoreboardCache = new ScoreboardCache(this);
|
||||
this.worldCache = new WorldCache(this);
|
||||
this.windowCache = new WindowCache(this);
|
||||
|
||||
this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
|
||||
@ -249,17 +284,12 @@ public class GeyserSession implements CommandSender {
|
||||
attributes.add(new AttributeData("minecraft:movement", 0.0f, 1024f, 0.1f, 0.1f));
|
||||
attributesPacket.setAttributes(attributes);
|
||||
upstream.sendPacket(attributesPacket);
|
||||
}
|
||||
|
||||
public void fetchOurSkin(PlayerListPacket.Entry entry) {
|
||||
PlayerSkinPacket playerSkinPacket = new PlayerSkinPacket();
|
||||
playerSkinPacket.setUuid(authData.getUUID());
|
||||
playerSkinPacket.setSkin(entry.getSkin());
|
||||
playerSkinPacket.setOldSkinName("OldName");
|
||||
playerSkinPacket.setNewSkinName("NewName");
|
||||
playerSkinPacket.setTrustedSkin(true);
|
||||
upstream.sendPacket(playerSkinPacket);
|
||||
getConnector().getLogger().debug("Sending skin for " + playerEntity.getUsername() + " " + authData.getUUID());
|
||||
// Only allow the server to send health information
|
||||
// Setting this to false allows natural regeneration to work false but doesn't break it being true
|
||||
GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket();
|
||||
gamerulePacket.getGameRules().add(new GameRuleData<>("naturalregeneration", false));
|
||||
upstream.sendPacket(gamerulePacket);
|
||||
}
|
||||
|
||||
public void login() {
|
||||
@ -445,7 +475,7 @@ public class GeyserSession implements CommandSender {
|
||||
|
||||
this.chunkCache = null;
|
||||
this.entityCache = null;
|
||||
this.scoreboardCache = null;
|
||||
this.worldCache = null;
|
||||
this.inventoryCache = null;
|
||||
this.windowCache = null;
|
||||
|
||||
@ -610,4 +640,69 @@ public class GeyserSession implements CommandSender {
|
||||
connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cached value for the reduced debug info gamerule.
|
||||
* This also toggles the coordinates display
|
||||
*
|
||||
* @param value The new value for reducedDebugInfo
|
||||
*/
|
||||
public void setReducedDebugInfo(boolean value) {
|
||||
worldCache.setShowCoordinates(!value);
|
||||
reducedDebugInfo = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a gamerule value to the client
|
||||
*
|
||||
* @param gameRule The gamerule to send
|
||||
* @param value The value of the gamerule
|
||||
*/
|
||||
public void sendGameRule(String gameRule, Object value) {
|
||||
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
|
||||
gameRulesChangedPacket.getGameRules().add(new GameRuleData<>(gameRule, value));
|
||||
upstream.sendPacket(gameRulesChangedPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given session's player has a permission
|
||||
*
|
||||
* @param permission The permission node to check
|
||||
* @return true if the player has the requested permission, false if not
|
||||
*/
|
||||
public Boolean hasPermission(String permission) {
|
||||
return connector.getWorldManager().hasPermission(this, permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an AdventureSettingsPacket to the client with the latest flags
|
||||
*/
|
||||
public void sendAdventureSettings() {
|
||||
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
|
||||
adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId());
|
||||
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
|
||||
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
|
||||
|
||||
Set<AdventureSetting> flags = new HashSet<>();
|
||||
if (canFly) {
|
||||
flags.add(AdventureSetting.MAY_FLY);
|
||||
}
|
||||
|
||||
if (flying) {
|
||||
flags.add(AdventureSetting.FLYING);
|
||||
}
|
||||
|
||||
if (worldImmutable) {
|
||||
flags.add(AdventureSetting.WORLD_IMMUTABLE);
|
||||
}
|
||||
|
||||
if (noClip) {
|
||||
flags.add(AdventureSetting.NO_CLIP);
|
||||
}
|
||||
|
||||
flags.add(AdventureSetting.AUTO_JUMP);
|
||||
|
||||
adventureSettingsPacket.getSettings().addAll(flags);
|
||||
sendUpstreamPacket(adventureSettingsPacket);
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,9 @@
|
||||
|
||||
package org.geysermc.connector.network.session.cache;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.scoreboard.Objective;
|
||||
import org.geysermc.connector.scoreboard.Scoreboard;
|
||||
@ -33,11 +35,18 @@ import org.geysermc.connector.scoreboard.Scoreboard;
|
||||
import java.util.Collection;
|
||||
|
||||
@Getter
|
||||
public class ScoreboardCache {
|
||||
public class WorldCache {
|
||||
|
||||
private GeyserSession session;
|
||||
|
||||
@Setter
|
||||
private Difficulty difficulty = Difficulty.EASY;
|
||||
|
||||
private boolean showCoordinates = true;
|
||||
|
||||
private Scoreboard scoreboard;
|
||||
|
||||
public ScoreboardCache(GeyserSession session) {
|
||||
public WorldCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
this.scoreboard = new Scoreboard(session);
|
||||
}
|
||||
@ -52,4 +61,14 @@ public class ScoreboardCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to hide or show the coordinates
|
||||
*
|
||||
* @param value True to show, false to hide
|
||||
*/
|
||||
public void setShowCoordinates(boolean value) {
|
||||
showCoordinates = value;
|
||||
session.sendGameRule("showcoordinates", value);
|
||||
}
|
||||
}
|
@ -23,15 +23,25 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.world;
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.utils.SettingsUtils;
|
||||
|
||||
public class CachedChunkManager extends WorldManager {
|
||||
@Translator(packet = ServerSettingsRequestPacket.class)
|
||||
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
return session.getChunkCache().getBlockAt(new Position(x, y, z));
|
||||
public void translate(ServerSettingsRequestPacket packet, GeyserSession session) {
|
||||
SettingsUtils.buildForm(session);
|
||||
|
||||
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
|
||||
serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData());
|
||||
serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID);
|
||||
session.sendUpstreamPacket(serverSettingsResponsePacket);
|
||||
}
|
||||
}
|
@ -44,8 +44,8 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
|
||||
|
||||
for (PlayerEntity entity : session.getEntityCache().getEntitiesByType(PlayerEntity.class)) {
|
||||
if (!entity.isValid()) {
|
||||
// async skin loading
|
||||
SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session));
|
||||
SkinUtils.requestAndHandleSkinAndCape(entity, session, null);
|
||||
entity.sendPlayer(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
package org.geysermc.connector.network.translators.bedrock.entity;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.WindowType;
|
@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
|
||||
@ -116,6 +116,18 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||
// Handled in BedrockInventoryTransactionTranslator
|
||||
break;
|
||||
case START_BREAK:
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
if (packet.getFace() == BlockFace.UP.ordinal()) {
|
||||
int blockUp = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().add(0, 1, 0));
|
||||
String identifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockUp);
|
||||
if (identifier.startsWith("minecraft:fire") || identifier.startsWith("minecraft:soul_fire")) {
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY() + 1, packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
|
||||
session.sendDownstreamPacket(startBreakingPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
|
||||
session.sendDownstreamPacket(startBreakingPacket);
|
@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.packet.EmotePacket;
|
||||
import org.geysermc.connector.GeyserConnector;
|
@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
|
@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
package org.geysermc.connector.network.translators.bedrock.entity.player;
|
||||
|
||||
import com.nukkitx.math.vector.Vector3d;
|
||||
import org.geysermc.connector.common.ChatColor;
|
@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.bedrock;
|
||||
package org.geysermc.connector.network.translators.bedrock.world;
|
||||
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
|
@ -214,13 +214,6 @@ public class ItemRegistry {
|
||||
return itemEntry;
|
||||
}
|
||||
}
|
||||
// If item find was unsuccessful first time, we try again while ignoring damage
|
||||
// Fixes piston, sticky pistons, dispensers and droppers turning into air from creative inventory
|
||||
for (ItemEntry itemEntry : ITEM_ENTRIES.values()) {
|
||||
if (itemEntry.getBedrockId() == data.getId()) {
|
||||
return itemEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// This will hide the message when the player clicks with an empty hand
|
||||
if (data.getId() != 0 && data.getDamage() != 0) {
|
||||
|
@ -40,5 +40,7 @@ public class JavaDifficultyTranslator extends PacketTranslator<ServerDifficultyP
|
||||
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
|
||||
setDifficultyPacket.setDifficulty(packet.getDifficulty().ordinal());
|
||||
session.sendUpstreamPacket(setDifficultyPacket);
|
||||
|
||||
session.getWorldCache().setDifficulty(packet.getDifficulty());
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
|
||||
DimensionUtils.switchDimension(session, fakeDim);
|
||||
DimensionUtils.switchDimension(session, newDimension);
|
||||
|
||||
session.getScoreboardCache().removeScoreboard();
|
||||
session.getWorldCache().removeScoreboard();
|
||||
}
|
||||
|
||||
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();
|
||||
|
@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.PlayerEntity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
@ -51,6 +52,33 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
switch (packet.getStatus()) {
|
||||
case PLAYER_ENABLE_REDUCED_DEBUG:
|
||||
session.setReducedDebugInfo(true);
|
||||
return;
|
||||
case PLAYER_DISABLE_REDUCED_DEBUG:
|
||||
session.setReducedDebugInfo(false);
|
||||
return;
|
||||
case PLAYER_OP_PERMISSION_LEVEL_0:
|
||||
session.setOpPermissionLevel(0);
|
||||
session.sendAdventureSettings();
|
||||
return;
|
||||
case PLAYER_OP_PERMISSION_LEVEL_1:
|
||||
session.setOpPermissionLevel(1);
|
||||
session.sendAdventureSettings();
|
||||
return;
|
||||
case PLAYER_OP_PERMISSION_LEVEL_2:
|
||||
session.setOpPermissionLevel(2);
|
||||
session.sendAdventureSettings();
|
||||
return;
|
||||
case PLAYER_OP_PERMISSION_LEVEL_3:
|
||||
session.setOpPermissionLevel(3);
|
||||
session.sendAdventureSettings();
|
||||
return;
|
||||
case PLAYER_OP_PERMISSION_LEVEL_4:
|
||||
session.setOpPermissionLevel(4);
|
||||
session.sendAdventureSettings();
|
||||
return;
|
||||
|
||||
// EntityEventType.HURT sends extra data depending on the type of damage. However this appears to have no visual changes
|
||||
case LIVING_BURN:
|
||||
case LIVING_DROWN:
|
||||
|
@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
|
||||
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
@ -43,24 +44,12 @@ public class JavaPlayerAbilitiesTranslator extends PacketTranslator<ServerPlayer
|
||||
|
||||
@Override
|
||||
public void translate(ServerPlayerAbilitiesPacket packet, GeyserSession session) {
|
||||
Entity entity = session.getPlayerEntity();
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
Set<AdventureSetting> playerFlags = new ObjectOpenHashSet<>();
|
||||
playerFlags.add(AdventureSetting.AUTO_JUMP);
|
||||
if (packet.isCanFly())
|
||||
playerFlags.add(AdventureSetting.MAY_FLY);
|
||||
|
||||
if (packet.isFlying())
|
||||
playerFlags.add(AdventureSetting.FLYING);
|
||||
|
||||
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
|
||||
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
|
||||
// Required or the packet simply is not sent
|
||||
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
|
||||
adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId());
|
||||
adventureSettingsPacket.getSettings().addAll(playerFlags);
|
||||
session.sendUpstreamPacket(adventureSettingsPacket);
|
||||
session.setCanFly(packet.isCanFly());
|
||||
session.setFlying(packet.isFlying());
|
||||
session.sendAdventureSettings();
|
||||
}
|
||||
}
|
||||
|
@ -82,18 +82,7 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
|
||||
playerEntity.setPlayerList(true);
|
||||
playerEntity.setValid(true);
|
||||
|
||||
PlayerListPacket.Entry playerListEntry = SkinUtils.buildCachedEntry(entry.getProfile(), playerEntity.getGeyserId());
|
||||
if (self) {
|
||||
// Copy the entry with our identity instead.
|
||||
PlayerListPacket.Entry copy = new PlayerListPacket.Entry(session.getAuthData().getUUID());
|
||||
copy.setName(playerListEntry.getName());
|
||||
copy.setEntityId(playerListEntry.getEntityId());
|
||||
copy.setSkin(playerListEntry.getSkin());
|
||||
copy.setXuid(playerListEntry.getXuid());
|
||||
copy.setPlatformChatId(playerListEntry.getPlatformChatId());
|
||||
copy.setTeacher(playerListEntry.isTeacher());
|
||||
playerListEntry = copy;
|
||||
}
|
||||
PlayerListPacket.Entry playerListEntry = SkinUtils.buildCachedEntry(session, entry.getProfile(), playerEntity.getGeyserId());
|
||||
|
||||
translate.getEntries().add(playerListEntry);
|
||||
break;
|
||||
@ -103,15 +92,20 @@ public class JavaPlayerListEntryTranslator extends PacketTranslator<ServerPlayer
|
||||
// remove from tablist but player entity is still there
|
||||
entity.setPlayerList(false);
|
||||
} else {
|
||||
// just remove it from caching
|
||||
if (entity == null) {
|
||||
// just remove it from caching
|
||||
session.getEntityCache().removePlayerEntity(entry.getProfile().getId());
|
||||
} else {
|
||||
entity.setPlayerList(false);
|
||||
session.getEntityCache().removeEntity(entity, false);
|
||||
}
|
||||
}
|
||||
translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId()));
|
||||
if (entity == session.getPlayerEntity()) {
|
||||
// If removing ourself we use our AuthData UUID
|
||||
translate.getEntries().add(new PlayerListPacket.Entry(session.getAuthData().getUUID()));
|
||||
} else {
|
||||
translate.getEntries().add(new PlayerListPacket.Entry(entry.getProfile().getId()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -54,9 +54,9 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator<ServerSpawnPlaye
|
||||
entity.setRotation(rotation);
|
||||
session.getEntityCache().cacheEntity(entity);
|
||||
|
||||
// async skin loading
|
||||
if (session.getUpstream().isInitialized()) {
|
||||
SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session));
|
||||
entity.sendPlayer(session);
|
||||
SkinUtils.requestAndHandleSkinAndCape(entity, session, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ public class JavaDisplayScoreboardTranslator extends PacketTranslator<ServerDisp
|
||||
|
||||
@Override
|
||||
public void translate(ServerDisplayScoreboardPacket packet, GeyserSession session) {
|
||||
session.getScoreboardCache().getScoreboard().registerNewObjective(
|
||||
session.getWorldCache().getScoreboard().registerNewObjective(
|
||||
packet.getName(), packet.getPosition()
|
||||
);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
package org.geysermc.connector.network.translators.java.scoreboard;
|
||||
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.session.cache.ScoreboardCache;
|
||||
import org.geysermc.connector.network.session.cache.WorldCache;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.scoreboard.Objective;
|
||||
@ -41,7 +41,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
|
||||
|
||||
@Override
|
||||
public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) {
|
||||
ScoreboardCache cache = session.getScoreboardCache();
|
||||
WorldCache cache = session.getWorldCache();
|
||||
Scoreboard scoreboard = cache.getScoreboard();
|
||||
|
||||
Objective objective = scoreboard.getObjective(packet.getName());
|
||||
|
@ -47,7 +47,7 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
|
||||
public void translate(ServerTeamPacket packet, GeyserSession session) {
|
||||
GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers()));
|
||||
|
||||
Scoreboard scoreboard = session.getScoreboardCache().getScoreboard();
|
||||
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
|
||||
Team team = scoreboard.getTeam(packet.getTeamName());
|
||||
switch (packet.getAction()) {
|
||||
case CREATE:
|
||||
@ -65,21 +65,21 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
|
||||
.setSuffix(MessageUtils.getTranslatedBedrockMessage(packet.getSuffix(), session.getClientData().getLanguageCode()))
|
||||
.setUpdateType(UpdateType.UPDATE);
|
||||
} else {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
|
||||
GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
|
||||
}
|
||||
break;
|
||||
case ADD_PLAYER:
|
||||
if(team != null){
|
||||
if (team != null) {
|
||||
team.addEntities(packet.getPlayers());
|
||||
} else {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
|
||||
GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
|
||||
}
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
if(team != null){
|
||||
if (team != null) {
|
||||
team.removeEntities(packet.getPlayers());
|
||||
} else {
|
||||
GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
|
||||
GeyserConnector.getInstance().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.translator.team.failed_not_registered", packet.getAction(), packet.getTeamName()));
|
||||
}
|
||||
break;
|
||||
case REMOVE:
|
||||
|
@ -42,7 +42,7 @@ public class JavaUpdateScoreTranslator extends PacketTranslator<ServerUpdateScor
|
||||
@Override
|
||||
public void translate(ServerUpdateScorePacket packet, GeyserSession session) {
|
||||
try {
|
||||
Scoreboard scoreboard = session.getScoreboardCache().getScoreboard();
|
||||
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
|
||||
|
||||
Objective objective = scoreboard.getObjective(packet.getObjective());
|
||||
if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) {
|
||||
|
@ -41,6 +41,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
@ -55,7 +56,7 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
|
||||
|
||||
@Override
|
||||
public void translate(ServerNotifyClientPacket packet, GeyserSession session) {
|
||||
Entity entity = session.getPlayerEntity();
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
@ -75,39 +76,17 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
|
||||
session.sendUpstreamPacket(stopRainPacket);
|
||||
break;
|
||||
case CHANGE_GAMEMODE:
|
||||
Set<AdventureSetting> playerFlags = new ObjectOpenHashSet<>();
|
||||
GameMode gameMode = (GameMode) packet.getValue();
|
||||
if (gameMode == GameMode.ADVENTURE)
|
||||
playerFlags.add(AdventureSetting.WORLD_IMMUTABLE);
|
||||
|
||||
if (gameMode == GameMode.CREATIVE)
|
||||
playerFlags.add(AdventureSetting.MAY_FLY);
|
||||
|
||||
if (gameMode == GameMode.SPECTATOR) {
|
||||
playerFlags.add(AdventureSetting.MAY_FLY);
|
||||
playerFlags.add(AdventureSetting.NO_CLIP);
|
||||
playerFlags.add(AdventureSetting.FLYING);
|
||||
playerFlags.add(AdventureSetting.WORLD_IMMUTABLE);
|
||||
gameMode = GameMode.CREATIVE; // spectator doesnt exist on bedrock
|
||||
}
|
||||
|
||||
playerFlags.add(AdventureSetting.AUTO_JUMP);
|
||||
session.setNoClip(gameMode == GameMode.SPECTATOR);
|
||||
session.setWorldImmutable(gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR);
|
||||
session.sendAdventureSettings();
|
||||
|
||||
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
|
||||
playerGameTypePacket.setGamemode(gameMode.ordinal());
|
||||
session.sendUpstreamPacket(playerGameTypePacket);
|
||||
session.setGameMode(gameMode);
|
||||
|
||||
// We need to delay this because otherwise it's overridden by the adventure settings from the abilities packet
|
||||
session.getConnector().getGeneralThreadPool().schedule(() -> {
|
||||
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
|
||||
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
|
||||
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
|
||||
adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId());
|
||||
adventureSettingsPacket.getSettings().addAll(playerFlags);
|
||||
session.sendUpstreamPacket(adventureSettingsPacket);
|
||||
}, 50, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Update the crafting grid to add/remove barriers for creative inventory
|
||||
PlayerInventoryTranslator.updateCraftingGrid(session, session.getInventory());
|
||||
break;
|
||||
|
@ -78,7 +78,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator<ServerSpawnPar
|
||||
int r = (int) (data.getRed()*255);
|
||||
int g = (int) (data.getGreen()*255);
|
||||
int b = (int) (data.getBlue()*255);
|
||||
particle.setType(LevelEventType.PARTICLE_FALLING_DUST);
|
||||
particle.setType(LevelEventType.PARTICLE_REDSTONE);
|
||||
particle.setData(((0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff));
|
||||
particle.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
|
||||
session.sendUpstreamPacket(particle);
|
||||
|
@ -67,9 +67,7 @@ public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimeP
|
||||
}
|
||||
|
||||
private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
|
||||
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
|
||||
gameRulesChangedPacket.getGameRules().add(new GameRuleData<>("dodaylightcycle", doCycle));
|
||||
session.sendUpstreamPacket(gameRulesChangedPacket);
|
||||
session.sendGameRule("dodaylightcycle", doCycle);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.GameRule;
|
||||
|
||||
public class GeyserWorldManager extends WorldManager {
|
||||
|
||||
private static final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>();
|
||||
|
||||
@Override
|
||||
public int getBlockAt(GeyserSession session, int x, int y, int z) {
|
||||
return session.getChunkCache().getBlockAt(new Position(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGameRule(GeyserSession session, String name, Object value) {
|
||||
session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value));
|
||||
gameruleCache.put(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
|
||||
String value = gameruleCache.get(gameRule.getJavaID());
|
||||
if (value != null) {
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
return gameRule.getDefaultValue() != null ? (Boolean) gameRule.getDefaultValue() : false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
|
||||
String value = gameruleCache.get(gameRule.getJavaID());
|
||||
if (value != null) {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
return gameRule.getDefaultValue() != null ? (int) gameRule.getDefaultValue() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerGameMode(GeyserSession session, GameMode gameMode) {
|
||||
session.sendDownstreamPacket(new ClientChatPacket("/gamemode " + gameMode.name().toLowerCase()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDifficulty(GeyserSession session, Difficulty difficulty) {
|
||||
session.sendDownstreamPacket(new ClientChatPacket("/difficulty " + difficulty.name().toLowerCase()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(GeyserSession session, String permission) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -26,8 +26,11 @@
|
||||
package org.geysermc.connector.network.translators.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.GameRule;
|
||||
|
||||
/**
|
||||
* Class that manages or retrieves various information
|
||||
@ -70,4 +73,56 @@ public abstract class WorldManager {
|
||||
* @return the block state at the specified location
|
||||
*/
|
||||
public abstract int getBlockAt(GeyserSession session, int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Updates a gamerule value on the Java server
|
||||
*
|
||||
* @param session The session of the user that requested the change
|
||||
* @param name The gamerule to change
|
||||
* @param value The new value for the gamerule
|
||||
*/
|
||||
public abstract void setGameRule(GeyserSession session, String name, Object value);
|
||||
|
||||
/**
|
||||
* Get a gamerule value as a boolean
|
||||
*
|
||||
* @param session The session of the user that requested the value
|
||||
* @param gameRule The gamerule to fetch the value of
|
||||
* @return The boolean representation of the value
|
||||
*/
|
||||
public abstract Boolean getGameRuleBool(GeyserSession session, GameRule gameRule);
|
||||
|
||||
/**
|
||||
* Get a gamerule value as an integer
|
||||
*
|
||||
* @param session The session of the user that requested the value
|
||||
* @param gameRule The gamerule to fetch the value of
|
||||
* @return The integer representation of the value
|
||||
*/
|
||||
public abstract int getGameRuleInt(GeyserSession session, GameRule gameRule);
|
||||
|
||||
/**
|
||||
* Change the game mode of the given session
|
||||
*
|
||||
* @param session The session of the player to change the game mode of
|
||||
* @param gameMode The game mode to change the player to
|
||||
*/
|
||||
public abstract void setPlayerGameMode(GeyserSession session, GameMode gameMode);
|
||||
|
||||
/**
|
||||
* Change the difficulty of the Java server
|
||||
*
|
||||
* @param session The session of the user that requested the change
|
||||
* @param difficulty The difficulty to change to
|
||||
*/
|
||||
public abstract void setDifficulty(GeyserSession session, Difficulty difficulty);
|
||||
|
||||
/**
|
||||
* Checks if the given session's player has a permission
|
||||
*
|
||||
* @param session The session of the player to check the permission of
|
||||
* @param permission The permission node to check
|
||||
* @return True if the player has the requested permission, false if not
|
||||
*/
|
||||
public abstract boolean hasPermission(GeyserSession session, String permission);
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ public class FileUtils {
|
||||
*
|
||||
* @param src File to load
|
||||
* @param valueType Class to load file into
|
||||
* @param <T> the type
|
||||
* @return The data as the given class
|
||||
* @throws IOException if the config could not be loaded
|
||||
*/
|
||||
|
123
connector/src/main/java/org/geysermc/connector/utils/GameRule.java
Normale Datei
123
connector/src/main/java/org/geysermc/connector/utils/GameRule.java
Normale Datei
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* This enum stores each gamerule along with the value type and the default.
|
||||
* It is used to construct the list for the settings menu
|
||||
*/
|
||||
public enum GameRule {
|
||||
ANNOUNCEADVANCEMENTS("announceAdvancements", Boolean.class, true), // JE only
|
||||
COMMANDBLOCKOUTPUT("commandBlockOutput", Boolean.class, true),
|
||||
DISABLEELYTRAMOVEMENTCHECK("disableElytraMovementCheck", Boolean.class, false), // JE only
|
||||
DISABLERAIDS("disableRaids", Boolean.class, false), // JE only
|
||||
DODAYLIGHTCYCLE("doDaylightCycle", Boolean.class, true),
|
||||
DOENTITYDROPS("doEntityDrops", Boolean.class, true),
|
||||
DOFIRETICK("doFireTick", Boolean.class, true),
|
||||
DOIMMEDIATERESPAWN("doImmediateRespawn", Boolean.class, false),
|
||||
DOINSOMNIA("doInsomnia", Boolean.class, true),
|
||||
DOLIMITEDCRAFTING("doLimitedCrafting", Boolean.class, false), // JE only
|
||||
DOMOBLOOT("doMobLoot", Boolean.class, true),
|
||||
DOMOBSPAWNING("doMobSpawning", Boolean.class, true),
|
||||
DOPATROLSPAWNING("doPatrolSpawning", Boolean.class, true), // JE only
|
||||
DOTILEDROPS("doTileDrops", Boolean.class, true),
|
||||
DOTRADERSPAWNING("doTraderSpawning", Boolean.class, true), // JE only
|
||||
DOWEATHERCYCLE("doWeatherCycle", Boolean.class, true),
|
||||
DROWNINGDAMAGE("drowningDamage", Boolean.class, true),
|
||||
FALLDAMAGE("fallDamage", Boolean.class, true),
|
||||
FIREDAMAGE("fireDamage", Boolean.class, true),
|
||||
FORGIVEDEADPLAYERS("forgiveDeadPlayers", Boolean.class, true), // JE only
|
||||
KEEPINVENTORY("keepInventory", Boolean.class, false),
|
||||
LOGADMINCOMMANDS("logAdminCommands", Boolean.class, true), // JE only
|
||||
MAXCOMMANDCHAINLENGTH("maxCommandChainLength", Integer.class, 65536),
|
||||
MAXENTITYCRAMMING("maxEntityCramming", Integer.class, 24), // JE only
|
||||
MOBGRIEFING("mobGriefing", Boolean.class, true),
|
||||
NATURALREGENERATION("naturalRegeneration", Boolean.class, true),
|
||||
RANDOMTICKSPEED("randomTickSpeed", Integer.class, 3),
|
||||
REDUCEDDEBUGINFO("reducedDebugInfo", Boolean.class, false), // JE only
|
||||
SENDCOMMANDFEEDBACK("sendCommandFeedback", Boolean.class, true),
|
||||
SHOWDEATHMESSAGES("showDeathMessages", Boolean.class, true),
|
||||
SPAWNRADIUS("spawnRadius", Integer.class, 10),
|
||||
SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", Boolean.class, true), // JE only
|
||||
UNIVERSALANGER("universalAnger", Boolean.class, false), // JE only
|
||||
|
||||
UNKNOWN("unknown", Object.class);
|
||||
|
||||
private static final GameRule[] VALUES = values();
|
||||
|
||||
@Getter
|
||||
private String javaID;
|
||||
|
||||
@Getter
|
||||
private Class<?> type;
|
||||
|
||||
@Getter
|
||||
private Object defaultValue;
|
||||
|
||||
GameRule(String javaID, Class<?> type) {
|
||||
this(javaID, type, null);
|
||||
}
|
||||
|
||||
GameRule(String javaID, Class<?> type, Object defaultValue) {
|
||||
this.javaID = javaID;
|
||||
this.type = type;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string to an object of the correct type for the current gamerule
|
||||
*
|
||||
* @param value The string value to convert
|
||||
* @return The converted and formatted value
|
||||
*/
|
||||
public Object convertValue(String value) {
|
||||
if (type.equals(Boolean.class)) {
|
||||
return Boolean.parseBoolean(value);
|
||||
} else if (type.equals(Integer.class)) {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a game rule by the given Java ID
|
||||
*
|
||||
* @param id The ID of the gamerule
|
||||
* @return A {@link GameRule} object representing the requested ID or {@link GameRule#UNKNOWN}
|
||||
*/
|
||||
public static GameRule fromJavaID(String id) {
|
||||
for (GameRule gamerule : VALUES) {
|
||||
if (gamerule.javaID.equals(id)) {
|
||||
return gamerule;
|
||||
}
|
||||
}
|
||||
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
@ -136,6 +136,9 @@ public class InventoryUtils {
|
||||
/**
|
||||
* Returns a barrier block with custom name and lore to explain why
|
||||
* part of the inventory is unusable.
|
||||
*
|
||||
* @param description the description
|
||||
* @return the unusable space block
|
||||
*/
|
||||
public static ItemData createUnusableSpaceBlock(String description) {
|
||||
NbtMapBuilder root = NbtMap.builder();
|
||||
|
@ -122,7 +122,7 @@ public class LanguageUtils {
|
||||
formatString = key;
|
||||
}
|
||||
|
||||
return MessageFormat.format(formatString.replace("&", "\u00a7"), values);
|
||||
return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,7 +95,7 @@ public class MessageUtils {
|
||||
* @param messages A {@link List} of {@link Message} to parse
|
||||
* @param locale A locale loaded to get the message for
|
||||
* @param parent A {@link Message} to use as the parent (can be null)
|
||||
* @return
|
||||
* @return the translation parameters
|
||||
*/
|
||||
public static List<String> getTranslationParams(List<Message> messages, String locale, Message parent) {
|
||||
List<String> strings = new ArrayList<>();
|
||||
@ -160,10 +160,10 @@ public class MessageUtils {
|
||||
* Translate a given {@link TranslationMessage} to the given locale
|
||||
*
|
||||
* @param message The {@link Message} to send
|
||||
* @param locale
|
||||
* @param shouldTranslate
|
||||
* @param parent
|
||||
* @return
|
||||
* @param locale the locale
|
||||
* @param shouldTranslate if the message should be translated
|
||||
* @param parent the parent message
|
||||
* @return the given translation message translated from the given locale
|
||||
*/
|
||||
public static String getTranslatedBedrockMessage(Message message, String locale, boolean shouldTranslate, Message parent) {
|
||||
JsonParser parser = new JsonParser();
|
||||
|
163
connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
Normale Datei
163
connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java
Normale Datei
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.utils;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
|
||||
import org.geysermc.common.window.CustomFormBuilder;
|
||||
import org.geysermc.common.window.CustomFormWindow;
|
||||
import org.geysermc.common.window.button.FormImage;
|
||||
import org.geysermc.common.window.component.DropdownComponent;
|
||||
import org.geysermc.common.window.component.InputComponent;
|
||||
import org.geysermc.common.window.component.LabelComponent;
|
||||
import org.geysermc.common.window.component.ToggleComponent;
|
||||
import org.geysermc.common.window.response.CustomFormResponse;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SettingsUtils {
|
||||
|
||||
// Used in UpstreamPacketHandler.java
|
||||
public static final int SETTINGS_FORM_ID = 1338;
|
||||
|
||||
/**
|
||||
* Build a settings form for the given session and store it for later
|
||||
*
|
||||
* @param session The session to build the form for
|
||||
*/
|
||||
public static void buildForm(GeyserSession session) {
|
||||
// Cache the language for cleaner access
|
||||
String language = session.getClientData().getLanguageCode();
|
||||
|
||||
CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language));
|
||||
builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png"));
|
||||
|
||||
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language)));
|
||||
builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates())));
|
||||
|
||||
|
||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
|
||||
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language)));
|
||||
|
||||
DropdownComponent gamemodeDropdown = new DropdownComponent();
|
||||
gamemodeDropdown.setText("%createWorldScreen.gameMode.personal");
|
||||
gamemodeDropdown.setOptions(new ArrayList<>());
|
||||
for (GameMode gamemode : GameMode.values()) {
|
||||
gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode);
|
||||
}
|
||||
builder.addComponent(gamemodeDropdown);
|
||||
|
||||
DropdownComponent difficultyDropdown = new DropdownComponent();
|
||||
difficultyDropdown.setText("%options.difficulty");
|
||||
difficultyDropdown.setOptions(new ArrayList<>());
|
||||
for (Difficulty difficulty : Difficulty.values()) {
|
||||
difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty);
|
||||
}
|
||||
builder.addComponent(difficultyDropdown);
|
||||
}
|
||||
|
||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
|
||||
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language)));
|
||||
for (GameRule gamerule : GameRule.values()) {
|
||||
if (gamerule.equals(GameRule.UNKNOWN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the relevant form item based on the gamerule type
|
||||
if (Boolean.class.equals(gamerule.getType())) {
|
||||
builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule)));
|
||||
} else if (Integer.class.equals(gamerule.getType())) {
|
||||
builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session.setSettingsForm(builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the settings form response
|
||||
*
|
||||
* @param session The session that sent the response
|
||||
* @param response The response string to parse
|
||||
* @return True if the form was parsed correctly, false if not
|
||||
*/
|
||||
public static boolean handleSettingsForm(GeyserSession session, String response) {
|
||||
CustomFormWindow settingsForm = session.getSettingsForm();
|
||||
settingsForm.setResponse(response);
|
||||
|
||||
CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse();
|
||||
int offset = 0;
|
||||
|
||||
offset++; // Client settings title
|
||||
|
||||
session.getWorldCache().setShowCoordinates(settingsResponse.getToggleResponses().get(offset));
|
||||
offset++;
|
||||
|
||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
|
||||
offset++; // Server settings title
|
||||
|
||||
GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
|
||||
if (gameMode != null && gameMode != session.getGameMode()) {
|
||||
session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode);
|
||||
}
|
||||
offset++;
|
||||
|
||||
Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
|
||||
if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) {
|
||||
session.getConnector().getWorldManager().setDifficulty(session, difficulty);
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
|
||||
offset++; // Game rule title
|
||||
|
||||
for (GameRule gamerule : GameRule.values()) {
|
||||
if (gamerule.equals(GameRule.UNKNOWN)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Boolean.class.equals(gamerule.getType())) {
|
||||
Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue();
|
||||
if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) {
|
||||
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
|
||||
}
|
||||
} else if (Integer.class.equals(gamerule.getType())) {
|
||||
int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset));
|
||||
if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) {
|
||||
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
|
||||
}
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ package org.geysermc.connector.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
@ -40,9 +42,11 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@ -54,22 +58,26 @@ public class SkinProvider {
|
||||
public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN);
|
||||
public static final byte[] ALEX_SKIN = new ProvidedSkin("bedrock/skin/skin_alex.png").getSkin();
|
||||
public static final Skin EMPTY_SKIN_ALEX = new Skin(-1, "alex", ALEX_SKIN);
|
||||
private static Map<UUID, Skin> cachedSkins = new ConcurrentHashMap<>();
|
||||
private static Map<UUID, CompletableFuture<Skin>> requestedSkins = new ConcurrentHashMap<>();
|
||||
private static final Cache<String, Skin> cachedSkins = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
|
||||
private static final Map<String, CompletableFuture<Skin>> requestedSkins = new ConcurrentHashMap<>();
|
||||
|
||||
public static final Cape EMPTY_CAPE = new Cape("", "no-cape", new byte[0], -1, true);
|
||||
private static Map<String, Cape> cachedCapes = new ConcurrentHashMap<>();
|
||||
private static Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
|
||||
private static final Cache<String, Cape> cachedCapes = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
private static final Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
|
||||
|
||||
public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false);
|
||||
private static Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
|
||||
|
||||
public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars();
|
||||
public static String EARS_GEOMETRY;
|
||||
public static String EARS_GEOMETRY_SLIM;
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes
|
||||
|
||||
static {
|
||||
/* Load in the normal ears geometry */
|
||||
@ -102,22 +110,44 @@ public class SkinProvider {
|
||||
}
|
||||
|
||||
EARS_GEOMETRY_SLIM = earsDataBuilder.toString();
|
||||
}
|
||||
|
||||
public static boolean hasSkinCached(UUID uuid) {
|
||||
return cachedSkins.containsKey(uuid);
|
||||
// Schedule Daily Image Expiry if we are caching them
|
||||
if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) {
|
||||
GeyserConnector.getInstance().getGeneralThreadPool().scheduleAtFixedRate(() -> {
|
||||
File cacheFolder = Paths.get("cache", "images").toFile();
|
||||
if (!cacheFolder.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
final long expireTime = ((long)GeyserConnector.getInstance().getConfig().getCacheImages()) * ((long)1000 * 60 * 60 * 24);
|
||||
for (File imageFile : Objects.requireNonNull(cacheFolder.listFiles())) {
|
||||
if (imageFile.lastModified() < System.currentTimeMillis() - expireTime) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
imageFile.delete();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
GeyserConnector.getInstance().getLogger().debug(String.format("Removed %d cached image files as they have expired", count));
|
||||
}
|
||||
}, 10, 1440, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasCapeCached(String capeUrl) {
|
||||
return cachedCapes.containsKey(capeUrl);
|
||||
return cachedCapes.getIfPresent(capeUrl) != null;
|
||||
}
|
||||
|
||||
public static Skin getCachedSkin(UUID uuid) {
|
||||
return cachedSkins.getOrDefault(uuid, EMPTY_SKIN);
|
||||
public static Skin getCachedSkin(String skinUrl) {
|
||||
Skin skin = cachedSkins.getIfPresent(skinUrl);
|
||||
return skin != null ? skin : EMPTY_SKIN;
|
||||
}
|
||||
|
||||
public static Cape getCachedCape(String capeUrl) {
|
||||
return capeUrl != null ? cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE) : EMPTY_CAPE;
|
||||
Cape cape = capeUrl != null ? cachedCapes.getIfPresent(capeUrl) : EMPTY_CAPE;
|
||||
return cape != null ? cape : EMPTY_CAPE;
|
||||
}
|
||||
|
||||
public static CompletableFuture<SkinAndCape> requestSkinAndCape(UUID playerId, String skinUrl, String capeUrl) {
|
||||
@ -137,28 +167,26 @@ public class SkinProvider {
|
||||
|
||||
public static CompletableFuture<Skin> requestSkin(UUID playerId, String textureUrl, boolean newThread) {
|
||||
if (textureUrl == null || textureUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_SKIN);
|
||||
if (requestedSkins.containsKey(playerId)) return requestedSkins.get(playerId); // already requested
|
||||
if (requestedSkins.containsKey(textureUrl)) return requestedSkins.get(textureUrl); // already requested
|
||||
|
||||
if ((System.currentTimeMillis() - CACHE_INTERVAL) < cachedSkins.getOrDefault(playerId, EMPTY_SKIN).getRequestedOn()) {
|
||||
// no need to update, still cached
|
||||
return CompletableFuture.completedFuture(cachedSkins.get(playerId));
|
||||
Skin cachedSkin = cachedSkins.getIfPresent(textureUrl);
|
||||
if (cachedSkin != null) {
|
||||
return CompletableFuture.completedFuture(cachedSkin);
|
||||
}
|
||||
|
||||
CompletableFuture<Skin> future;
|
||||
if (newThread) {
|
||||
future = CompletableFuture.supplyAsync(() -> supplySkin(playerId, textureUrl), EXECUTOR_SERVICE)
|
||||
.whenCompleteAsync((skin, throwable) -> {
|
||||
if (!cachedSkins.getOrDefault(playerId, EMPTY_SKIN).getTextureUrl().equals(textureUrl)) {
|
||||
skin.updated = true;
|
||||
cachedSkins.put(playerId, skin);
|
||||
}
|
||||
requestedSkins.remove(skin.getSkinOwner());
|
||||
skin.updated = true;
|
||||
cachedSkins.put(textureUrl, skin);
|
||||
requestedSkins.remove(textureUrl);
|
||||
});
|
||||
requestedSkins.put(playerId, future);
|
||||
requestedSkins.put(textureUrl, future);
|
||||
} else {
|
||||
Skin skin = supplySkin(playerId, textureUrl);
|
||||
future = CompletableFuture.completedFuture(skin);
|
||||
cachedSkins.put(playerId, skin);
|
||||
cachedSkins.put(textureUrl, skin);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
@ -168,11 +196,9 @@ public class SkinProvider {
|
||||
if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested
|
||||
|
||||
boolean officialCape = provider == CapeProvider.MINECRAFT;
|
||||
boolean validCache = (System.currentTimeMillis() - CACHE_INTERVAL) < cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE).getRequestedOn();
|
||||
|
||||
if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) {
|
||||
// the cape is an official cape (static) or the cape doesn't need a update yet
|
||||
return CompletableFuture.completedFuture(cachedCapes.get(capeUrl));
|
||||
Cape cachedCape = cachedCapes.getIfPresent(capeUrl);
|
||||
if (cachedCape != null) {
|
||||
return CompletableFuture.completedFuture(cachedCape);
|
||||
}
|
||||
|
||||
CompletableFuture<Cape> future;
|
||||
@ -245,7 +271,10 @@ public class SkinProvider {
|
||||
}
|
||||
|
||||
public static CompletableFuture<Cape> requestBedrockCape(UUID playerID, boolean newThread) {
|
||||
Cape bedrockCape = cachedCapes.getOrDefault(playerID.toString() + ".Bedrock", EMPTY_CAPE);
|
||||
Cape bedrockCape = cachedCapes.getIfPresent(playerID.toString() + ".Bedrock");
|
||||
if (bedrockCape == null) {
|
||||
bedrockCape = EMPTY_CAPE;
|
||||
}
|
||||
return CompletableFuture.completedFuture(bedrockCape);
|
||||
}
|
||||
|
||||
@ -256,7 +285,7 @@ public class SkinProvider {
|
||||
|
||||
public static void storeBedrockSkin(UUID playerID, String skinID, byte[] skinData) {
|
||||
Skin skin = new Skin(playerID, skinID, skinData, System.currentTimeMillis(), true, false);
|
||||
cachedSkins.put(playerID, skin);
|
||||
cachedSkins.put(skin.getTextureUrl(), skin);
|
||||
}
|
||||
|
||||
public static void storeBedrockCape(UUID playerID, byte[] capeData) {
|
||||
@ -276,7 +305,7 @@ public class SkinProvider {
|
||||
* @param skin The skin to cache
|
||||
*/
|
||||
public static void storeEarSkin(UUID playerID, Skin skin) {
|
||||
cachedSkins.put(playerID, skin);
|
||||
cachedSkins.put(skin.getTextureUrl(), skin);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -290,11 +319,12 @@ public class SkinProvider {
|
||||
}
|
||||
|
||||
private static Skin supplySkin(UUID uuid, String textureUrl) {
|
||||
byte[] skin = EMPTY_SKIN.getSkinData();
|
||||
try {
|
||||
skin = requestImage(textureUrl, null);
|
||||
byte[] skin = requestImage(textureUrl, null);
|
||||
return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false);
|
||||
} catch (Exception ignored) {} // just ignore I guess
|
||||
return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false);
|
||||
|
||||
return new Skin(uuid, "empty", EMPTY_SKIN.getSkinData(), System.currentTimeMillis(), false, false);
|
||||
}
|
||||
|
||||
private static Cape supplyCape(String capeUrl, CapeProvider provider) {
|
||||
@ -356,11 +386,38 @@ public class SkinProvider {
|
||||
return existingSkin;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception {
|
||||
BufferedImage image = downloadImage(imageUrl, provider);
|
||||
GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl);
|
||||
BufferedImage image = null;
|
||||
|
||||
// if the requested image is an cape
|
||||
// First see if we have a cached file. We also update the modification stamp so we know when the file was last used
|
||||
File imageFile = Paths.get("cache", "images", UUID.nameUUIDFromBytes(imageUrl.getBytes()).toString() + ".png").toFile();
|
||||
if (imageFile.exists()) {
|
||||
try {
|
||||
GeyserConnector.getInstance().getLogger().debug("Reading cached image from file " + imageFile.getPath() + " for " + imageUrl);
|
||||
imageFile.setLastModified(System.currentTimeMillis());
|
||||
image = ImageIO.read(imageFile);
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
// If no image we download it
|
||||
if (image == null) {
|
||||
image = downloadImage(imageUrl, provider);
|
||||
GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl);
|
||||
|
||||
// Write to cache if we are allowed
|
||||
if (GeyserConnector.getInstance().getConfig().getCacheImages() > 0) {
|
||||
imageFile.getParentFile().mkdirs();
|
||||
try {
|
||||
ImageIO.write(image, "png", imageFile);
|
||||
GeyserConnector.getInstance().getLogger().debug("Writing cached skin to file " + imageFile.getPath() + " for " + imageUrl);
|
||||
} catch (IOException e) {
|
||||
GeyserConnector.getInstance().getLogger().error("Failed to write cached skin to file " + imageFile.getPath() + " for " + imageUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the requested image is a cape
|
||||
if (provider != null) {
|
||||
while(image.getWidth() > 64) {
|
||||
image = scale(image);
|
||||
|
@ -35,6 +35,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.connector.common.AuthType;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||
@ -47,18 +48,21 @@ import java.util.function.Consumer;
|
||||
|
||||
public class SkinUtils {
|
||||
|
||||
public static PlayerListPacket.Entry buildCachedEntry(GameProfile profile, long geyserId) {
|
||||
public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, GameProfile profile, long geyserId) {
|
||||
GameProfileData data = GameProfileData.from(profile);
|
||||
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl());
|
||||
|
||||
SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy(data.isAlex());
|
||||
|
||||
SkinProvider.Skin skin = SkinProvider.getCachedSkin(data.getSkinUrl());
|
||||
|
||||
return buildEntryManually(
|
||||
session,
|
||||
profile.getId(),
|
||||
profile.getName(),
|
||||
geyserId,
|
||||
profile.getIdAsString(),
|
||||
SkinProvider.getCachedSkin(profile.getId()).getSkinData(),
|
||||
skin.getTextureUrl(),
|
||||
skin.getSkinData(),
|
||||
cape.getCapeId(),
|
||||
cape.getCapeData(),
|
||||
geometry.getGeometryName(),
|
||||
@ -66,12 +70,13 @@ public class SkinUtils {
|
||||
);
|
||||
}
|
||||
|
||||
public static PlayerListPacket.Entry buildDefaultEntry(GameProfile profile, long geyserId) {
|
||||
public static PlayerListPacket.Entry buildDefaultEntry(GeyserSession session, GameProfile profile, long geyserId) {
|
||||
return buildEntryManually(
|
||||
session,
|
||||
profile.getId(),
|
||||
profile.getName(),
|
||||
geyserId,
|
||||
profile.getIdAsString(),
|
||||
"default",
|
||||
SkinProvider.STEVE_SKIN,
|
||||
SkinProvider.EMPTY_CAPE.getCapeId(),
|
||||
SkinProvider.EMPTY_CAPE.getCapeData(),
|
||||
@ -80,20 +85,38 @@ public class SkinUtils {
|
||||
);
|
||||
}
|
||||
|
||||
public static PlayerListPacket.Entry buildEntryManually(UUID uuid, String username, long geyserId,
|
||||
public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId,
|
||||
String skinId, byte[] skinData,
|
||||
String capeId, byte[] capeData,
|
||||
String geometryName, String geometryData) {
|
||||
SerializedSkin serializedSkin = SerializedSkin.of(
|
||||
skinId, geometryName, ImageData.of(skinData), Collections.emptyList(),
|
||||
ImageData.of(capeData), geometryData, "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, uuid.toString()
|
||||
ImageData.of(capeData), geometryData, "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
|
||||
);
|
||||
|
||||
PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid);
|
||||
// This attempts to find the xuid of the player so profile images show up for xbox accounts
|
||||
String xuid = "";
|
||||
for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) {
|
||||
if (player.getPlayerEntity().getUuid().equals(uuid)) {
|
||||
xuid = player.getAuthData().getXboxUUID();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerListPacket.Entry entry;
|
||||
|
||||
// If we are building a PlayerListEntry for our own session we use our AuthData UUID instead of the Java UUID
|
||||
// as bedrock expects to get back its own provided uuid
|
||||
if (session.getPlayerEntity().getUuid().equals(uuid)) {
|
||||
entry = new PlayerListPacket.Entry(session.getAuthData().getUUID());
|
||||
} else {
|
||||
entry = new PlayerListPacket.Entry(uuid);
|
||||
}
|
||||
|
||||
entry.setName(username);
|
||||
entry.setEntityId(geyserId);
|
||||
entry.setSkin(serializedSkin);
|
||||
entry.setXuid("");
|
||||
entry.setXuid(xuid);
|
||||
entry.setPlatformChatId("");
|
||||
entry.setTeacher(false);
|
||||
entry.setTrustedSkin(true);
|
||||
@ -201,48 +224,34 @@ public class SkinUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.getLastSkinUpdate() < skin.getRequestedOn()) {
|
||||
entity.setLastSkinUpdate(skin.getRequestedOn());
|
||||
entity.setLastSkinUpdate(skin.getRequestedOn());
|
||||
|
||||
if (session.getUpstream().isInitialized()) {
|
||||
PlayerListPacket.Entry updatedEntry = buildEntryManually(
|
||||
entity.getUuid(),
|
||||
entity.getUsername(),
|
||||
entity.getGeyserId(),
|
||||
entity.getUuid().toString(),
|
||||
skin.getSkinData(),
|
||||
cape.getCapeId(),
|
||||
cape.getCapeData(),
|
||||
geometry.getGeometryName(),
|
||||
geometry.getGeometryData()
|
||||
);
|
||||
if (session.getUpstream().isInitialized()) {
|
||||
PlayerListPacket.Entry updatedEntry = buildEntryManually(
|
||||
session,
|
||||
entity.getUuid(),
|
||||
entity.getUsername(),
|
||||
entity.getGeyserId(),
|
||||
skin.getTextureUrl(),
|
||||
skin.getSkinData(),
|
||||
cape.getCapeId(),
|
||||
cape.getCapeData(),
|
||||
geometry.getGeometryName(),
|
||||
geometry.getGeometryData()
|
||||
);
|
||||
|
||||
// If it is our skin we replace the UUID with the authdata UUID
|
||||
if (session.getPlayerEntity() == entity) {
|
||||
// Copy the entry with our identity instead.
|
||||
PlayerListPacket.Entry copy = new PlayerListPacket.Entry(session.getAuthData().getUUID());
|
||||
copy.setName(updatedEntry.getName());
|
||||
copy.setEntityId(updatedEntry.getEntityId());
|
||||
copy.setSkin(updatedEntry.getSkin());
|
||||
copy.setXuid(updatedEntry.getXuid());
|
||||
copy.setPlatformChatId(updatedEntry.getPlatformChatId());
|
||||
copy.setTeacher(updatedEntry.isTeacher());
|
||||
updatedEntry = copy;
|
||||
}
|
||||
|
||||
PlayerListPacket playerAddPacket = new PlayerListPacket();
|
||||
playerAddPacket.setAction(PlayerListPacket.Action.ADD);
|
||||
playerAddPacket.getEntries().add(updatedEntry);
|
||||
session.sendUpstreamPacket(playerAddPacket);
|
||||
|
||||
if (!entity.isPlayerList()) {
|
||||
PlayerListPacket playerRemovePacket = new PlayerListPacket();
|
||||
playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE);
|
||||
playerRemovePacket.getEntries().add(updatedEntry);
|
||||
session.sendUpstreamPacket(playerRemovePacket);
|
||||
|
||||
PlayerListPacket playerAddPacket = new PlayerListPacket();
|
||||
playerAddPacket.setAction(PlayerListPacket.Action.ADD);
|
||||
playerAddPacket.getEntries().add(updatedEntry);
|
||||
session.sendUpstreamPacket(playerAddPacket);
|
||||
|
||||
if(entity.getUuid().equals(session.getPlayerEntity().getUuid())) {
|
||||
session.fetchOurSkin(updatedEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -94,6 +94,10 @@ show-cooldown: true
|
||||
# Geyser has direct access to the server itself.
|
||||
cache-chunks: false
|
||||
|
||||
# Specify how many days images will be cached to disk to save downloading them from the internet.
|
||||
# A value of 0 is disabled. (Default: 0)
|
||||
cache-images: 0
|
||||
|
||||
# Bedrock prevents building and displaying blocks above Y127 in the Nether -
|
||||
# enabling this config option works around that by changing the Nether dimension ID
|
||||
# to the End ID. The main downside to this is that the sky will resemble that of
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren