mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Add world border translation (#1839)
As Bedrock does not have world border support, this PR translates what the Java server sends us for a world border into particles and fog, while also preventing the Bedrock client from moving outside of the world border. Co-authored-by: Luke <32024335+lukeeey@users.noreply.github.com> Co-authored-by: ofunny <play@ofunny.world>
This commit is contained in:
parent
8461cf76b7
commit
f22d286ea1
14 changed files with 661 additions and 15 deletions
|
@ -81,6 +81,13 @@ public class BoatEntity extends Entity {
|
||||||
session.sendUpstreamPacket(moveEntityPacket);
|
session.sendUpstreamPacket(moveEntityPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the boat without making the adjustments needed to translate from Java
|
||||||
|
*/
|
||||||
|
public void moveAbsoluteWithoutAdjustments(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||||
|
super.moveAbsolute(session, position, Vector3f.from(rotation.getX(), 0, rotation.getX()), isOnGround, teleported);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
|
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
|
||||||
super.moveRelative(session, relX, relY, relZ, Vector3f.from(rotation.getX(), 0, rotation.getX()), isOnGround);
|
super.moveRelative(session, relX, relY, relZ, Vector3f.from(rotation.getX(), 0, rotation.getX()), isOnGround);
|
||||||
|
|
|
@ -88,7 +88,9 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
|
public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
|
||||||
|
if (connector.getConfig().isDebugMode()) {
|
||||||
connector.getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.pinged", inetSocketAddress));
|
connector.getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.network.pinged", inetSocketAddress));
|
||||||
|
}
|
||||||
|
|
||||||
GeyserConfiguration config = connector.getConfig();
|
GeyserConfiguration config = connector.getConfig();
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,12 @@ public class GeyserSession implements CommandSender {
|
||||||
|
|
||||||
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
|
private final Int2ObjectMap<TeleportCache> teleportMap = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
private final WorldBorder worldBorder;
|
||||||
|
/**
|
||||||
|
* Whether simulated fog has been sent to the client or not.
|
||||||
|
*/
|
||||||
|
private boolean isInWorldBorderWarningArea = false;
|
||||||
|
|
||||||
private final PlayerInventory playerInventory;
|
private final PlayerInventory playerInventory;
|
||||||
@Setter
|
@Setter
|
||||||
private Inventory openInventory;
|
private Inventory openInventory;
|
||||||
|
@ -452,6 +458,8 @@ public class GeyserSession implements CommandSender {
|
||||||
this.tagCache = new TagCache();
|
this.tagCache = new TagCache();
|
||||||
this.worldCache = new WorldCache(this);
|
this.worldCache = new WorldCache(this);
|
||||||
|
|
||||||
|
this.worldBorder = new WorldBorder(this);
|
||||||
|
|
||||||
this.collisionManager = new CollisionManager(this);
|
this.collisionManager = new CollisionManager(this);
|
||||||
|
|
||||||
this.playerEntity = new SessionPlayerEntity(this);
|
this.playerEntity = new SessionPlayerEntity(this);
|
||||||
|
@ -928,6 +936,25 @@ public class GeyserSession implements CommandSender {
|
||||||
lastMovementTimestamp = System.currentTimeMillis();
|
lastMovementTimestamp = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (worldBorder.isResizing()) {
|
||||||
|
worldBorder.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!worldBorder.isWithinWarningBoundaries()) {
|
||||||
|
// Show particles representing where the world border is
|
||||||
|
worldBorder.drawWall();
|
||||||
|
// Set the mood
|
||||||
|
if (!isInWorldBorderWarningArea) {
|
||||||
|
isInWorldBorderWarningArea = true;
|
||||||
|
WorldBorder.sendFog(this, "minecraft:fog_crimson_forest");
|
||||||
|
}
|
||||||
|
} else if (isInWorldBorderWarningArea) {
|
||||||
|
// Clear fog as we are outside the world border now
|
||||||
|
WorldBorder.removeFog(this);
|
||||||
|
isInWorldBorderWarningArea = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (Tickable entity : entityCache.getTickableEntities()) {
|
for (Tickable entity : entityCache.getTickableEntities()) {
|
||||||
entity.tick(this);
|
entity.tick(this);
|
||||||
}
|
}
|
||||||
|
|
291
connector/src/main/java/org/geysermc/connector/network/session/cache/WorldBorder.java
vendored
Normal file
291
connector/src/main/java/org/geysermc/connector/network/session/cache/WorldBorder.java
vendored
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.session.cache;
|
||||||
|
|
||||||
|
import com.nukkitx.math.GenericMath;
|
||||||
|
import com.nukkitx.math.vector.Vector2f;
|
||||||
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
|
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||||
|
import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||||
|
import org.geysermc.connector.entity.type.EntityType;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class WorldBorder {
|
||||||
|
private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D;
|
||||||
|
|
||||||
|
@Getter @Setter
|
||||||
|
private @Nonnull Vector2f center = Vector2f.ZERO;
|
||||||
|
/**
|
||||||
|
* The diameter in blocks of the world border before it got changed or similar to newDiameter if not changed.
|
||||||
|
*/
|
||||||
|
@Getter @Setter
|
||||||
|
private double oldDiameter = DEFAULT_WORLD_BORDER_SIZE;
|
||||||
|
/**
|
||||||
|
* The diameter in blocks of the new world border.
|
||||||
|
*/
|
||||||
|
@Getter @Setter
|
||||||
|
private double newDiameter = DEFAULT_WORLD_BORDER_SIZE;
|
||||||
|
/**
|
||||||
|
* The speed to apply an expansion/shrinking of the world border.
|
||||||
|
* When a client joins they get the actual border oldDiameter and the time left to reach the newDiameter.
|
||||||
|
*/
|
||||||
|
@Getter @Setter
|
||||||
|
private long speed = 0;
|
||||||
|
/**
|
||||||
|
* The time in seconds before a shrinking world border would hit a not moving player.
|
||||||
|
* Creates the same visual warning effect as warningBlocks.
|
||||||
|
*/
|
||||||
|
@Getter @Setter
|
||||||
|
private int warningDelay = 15;
|
||||||
|
/**
|
||||||
|
* Block length before you reach the border to show warning particles.
|
||||||
|
*/
|
||||||
|
@Getter @Setter
|
||||||
|
private int warningBlocks = 5;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean resizing;
|
||||||
|
private double currentDiameter;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Boundaries of the actual world border.
|
||||||
|
* (The will get updated on expanding or shrinking)
|
||||||
|
*/
|
||||||
|
private double minX = 0.0D;
|
||||||
|
private double minZ = 0.0D;
|
||||||
|
private double maxX = 0.0D;
|
||||||
|
private double maxZ = 0.0D;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The boundaries for the for the warning visuals.
|
||||||
|
*/
|
||||||
|
private double warningMaxX = 0.0D;
|
||||||
|
private double warningMaxZ = 0.0D;
|
||||||
|
private double warningMinX = 0.0D;
|
||||||
|
private double warningMinZ = 0.0D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To track when to send wall particle packets.
|
||||||
|
*/
|
||||||
|
private int currentWallTick;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the world border is resizing, this variable saves how many ticks have progressed in the resizing
|
||||||
|
*/
|
||||||
|
private long lastUpdatedWorldBorderTime = 0;
|
||||||
|
|
||||||
|
private final GeyserSession session;
|
||||||
|
|
||||||
|
public WorldBorder(GeyserSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true as long the entity is within the world limits.
|
||||||
|
*/
|
||||||
|
public boolean isInsideBorderBoundaries() {
|
||||||
|
Vector3f entityPosition = session.getPlayerEntity().getPosition();
|
||||||
|
return entityPosition.getX() > minX && entityPosition.getX() < maxX && entityPosition.getZ() > minZ && entityPosition.getZ() < maxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirms that the entity is within world border boundaries when they move.
|
||||||
|
* Otherwise, if {@code adjustPosition} is true, this function will push the player back.
|
||||||
|
*
|
||||||
|
* @return if this player was indeed against the world border. Will return false if no world border was defined for us.
|
||||||
|
*/
|
||||||
|
public boolean isPassingIntoBorderBoundaries(Vector3f newPosition, boolean adjustPosition) {
|
||||||
|
boolean isInWorldBorder = isPassingIntoBorderBoundaries(newPosition);
|
||||||
|
if (isInWorldBorder && adjustPosition) {
|
||||||
|
PlayerEntity playerEntity = session.getPlayerEntity();
|
||||||
|
// Move the player back, but allow gravity to take place
|
||||||
|
// Teleported = true makes going back better, but disconnects the player from their mounted entity
|
||||||
|
playerEntity.moveAbsolute(session,
|
||||||
|
Vector3f.from(playerEntity.getPosition().getX(), (newPosition.getY() - EntityType.PLAYER.getOffset()), playerEntity.getPosition().getZ()),
|
||||||
|
playerEntity.getRotation(), playerEntity.isOnGround(), session.getRidingVehicleEntity() == null);
|
||||||
|
}
|
||||||
|
return isInWorldBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPassingIntoBorderBoundaries(Vector3f newEntityPosition) {
|
||||||
|
int entityX = GenericMath.floor(newEntityPosition.getX());
|
||||||
|
int entityZ = GenericMath.floor(newEntityPosition.getZ());
|
||||||
|
Vector3f currentEntityPosition = session.getPlayerEntity().getPosition();
|
||||||
|
// Make sure we can't move out of the world border, but if we're out of the world border, we can move in
|
||||||
|
return (entityX == (int) minX && currentEntityPosition.getX() > newEntityPosition.getX()) ||
|
||||||
|
(entityX == (int) maxX && currentEntityPosition.getX() < newEntityPosition.getX()) ||
|
||||||
|
(entityZ == (int) minZ && currentEntityPosition.getZ() > newEntityPosition.getZ()) ||
|
||||||
|
(entityZ == (int) maxZ && currentEntityPosition.getZ() < newEntityPosition.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link #isInsideBorderBoundaries()} but using the warning boundaries.
|
||||||
|
*
|
||||||
|
* @return true as long the entity is within the world limits and not in the warning zone at the edge to the border.
|
||||||
|
*/
|
||||||
|
public boolean isWithinWarningBoundaries() {
|
||||||
|
Vector3f entityPosition = session.getPlayerEntity().getPosition();
|
||||||
|
return entityPosition.getX() > warningMinX && entityPosition.getX() < warningMaxX && entityPosition.getZ() > warningMinZ && entityPosition.getZ() < warningMaxZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the world border's minimum and maximum properties
|
||||||
|
*/
|
||||||
|
public void update() {
|
||||||
|
/*
|
||||||
|
* Setting the correct boundary of our world border's square.
|
||||||
|
*/
|
||||||
|
double radius;
|
||||||
|
if (resizing) {
|
||||||
|
radius = this.currentDiameter / 2.0D;
|
||||||
|
} else {
|
||||||
|
radius = this.newDiameter / 2.0D;
|
||||||
|
}
|
||||||
|
this.minX = center.getX() - radius;
|
||||||
|
this.minZ = center.getY() - radius; // Mapping 2D vector to 3D coordinates >> Y becomes Z
|
||||||
|
this.maxX = center.getX() + radius;
|
||||||
|
this.maxZ = center.getY() + radius; // Mapping 2D vector to 3D coordinates >> Y becomes Z
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Caching the warning boundaries.
|
||||||
|
*/
|
||||||
|
this.warningMinX = this.minX + this.warningBlocks;
|
||||||
|
this.warningMinZ = this.minZ + this.warningBlocks;
|
||||||
|
this.warningMaxX = this.maxX - this.warningBlocks;
|
||||||
|
this.warningMaxZ = this.maxZ - this.warningBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resize() {
|
||||||
|
if (this.lastUpdatedWorldBorderTime >= this.speed) {
|
||||||
|
// Diameter has now updated to the new diameter
|
||||||
|
this.resizing = false;
|
||||||
|
this.lastUpdatedWorldBorderTime = 0;
|
||||||
|
} else if (resizing) {
|
||||||
|
this.currentDiameter = this.oldDiameter + ((double) this.lastUpdatedWorldBorderTime / (double) this.speed) * (this.newDiameter - this.oldDiameter);
|
||||||
|
this.lastUpdatedWorldBorderTime += 50;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResizing(boolean resizing) {
|
||||||
|
this.resizing = resizing;
|
||||||
|
if (!resizing) {
|
||||||
|
this.lastUpdatedWorldBorderTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final LevelEventType WORLD_BORDER_PARTICLE = LevelEventType.PARTICLE_DENY_BLOCK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a wall of particles where the world border resides
|
||||||
|
*/
|
||||||
|
public void drawWall() {
|
||||||
|
if (currentWallTick++ != 20) {
|
||||||
|
// Only draw a wall once every second
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentWallTick = 0;
|
||||||
|
Vector3f entityPosition = session.getPlayerEntity().getPosition();
|
||||||
|
float particlePosX = entityPosition.getX();
|
||||||
|
float particlePosY = entityPosition.getY();
|
||||||
|
float particlePosZ = entityPosition.getZ();
|
||||||
|
|
||||||
|
if (entityPosition.getX() > warningMaxX) {
|
||||||
|
drawWall(Vector3f.from(maxX, particlePosY, particlePosZ), true);
|
||||||
|
}
|
||||||
|
if (entityPosition.getX() < warningMinX) {
|
||||||
|
drawWall(Vector3f.from(minX, particlePosY, particlePosZ), true);
|
||||||
|
}
|
||||||
|
if (entityPosition.getZ() > warningMaxZ) {
|
||||||
|
drawWall(Vector3f.from(particlePosX, particlePosY, maxZ), false);
|
||||||
|
}
|
||||||
|
if (entityPosition.getZ() < warningMinZ) {
|
||||||
|
drawWall(Vector3f.from(particlePosX, particlePosY, minZ), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawWall(Vector3f position, boolean drawWallX) {
|
||||||
|
int initialY = (int) (position.getY() - EntityType.PLAYER.getOffset() - 1);
|
||||||
|
for (int y = initialY; y < (initialY + 5); y++) {
|
||||||
|
if (drawWallX) {
|
||||||
|
float x = position.getX();
|
||||||
|
for (int z = (int) position.getZ() - 3; z < ((int) position.getZ() + 3); z++) {
|
||||||
|
if (z < minZ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (z > maxZ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWorldBorderParticle(x, y, z);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
float z = position.getZ();
|
||||||
|
for (int x = (int) position.getX() - 3; x < ((int) position.getX() + 3); x++) {
|
||||||
|
if (x < minX) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (x > maxX) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendWorldBorderParticle(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendWorldBorderParticle(float x, float y, float z) {
|
||||||
|
LevelEventPacket effectPacket = new LevelEventPacket();
|
||||||
|
effectPacket.setPosition(Vector3f.from(x, y, z));
|
||||||
|
effectPacket.setType(WORLD_BORDER_PARTICLE);
|
||||||
|
session.getUpstream().sendPacket(effectPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the following fog IDs to the client
|
||||||
|
*/
|
||||||
|
public static void sendFog(GeyserSession session, String... fogNameSpaces) {
|
||||||
|
PlayerFogPacket packet = new PlayerFogPacket();
|
||||||
|
Collections.addAll(packet.getFogStack(), fogNameSpaces);
|
||||||
|
session.sendUpstreamPacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear any additional fog sent to the client
|
||||||
|
*/
|
||||||
|
public static void removeFog(GeyserSession session) {
|
||||||
|
session.sendUpstreamPacket(new PlayerFogPacket());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -144,6 +144,12 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
"Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess),
|
"Not in range" doesn't refer to how far a vanilla client goes (that's a whole other mess),
|
||||||
but how much a server will accept from the client maximum
|
but how much a server will accept from the client maximum
|
||||||
*/
|
*/
|
||||||
|
// Blocks cannot be placed or destroyed outside of the world border
|
||||||
|
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
||||||
|
restoreCorrectBlock(session, blockPos, packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch
|
// CraftBukkit+ check - see https://github.com/PaperMC/Paper/blob/458db6206daae76327a64f4e2a17b67a7e38b426/Spigot-Server-Patches/0532-Move-range-check-for-block-placing-up.patch
|
||||||
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
||||||
|
|
||||||
|
@ -289,6 +295,11 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||||
session.setLastBlockPlacePosition(null);
|
session.setLastBlockPlacePosition(null);
|
||||||
|
|
||||||
// Same deal with vanilla block placing as above.
|
// Same deal with vanilla block placing as above.
|
||||||
|
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
||||||
|
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player.
|
// This is working out the distance using 3d Pythagoras and the extra value added to the Y is the sneaking height of a java player.
|
||||||
playerPosition = session.getPlayerEntity().getPosition();
|
playerPosition = session.getPlayerEntity().getPosition();
|
||||||
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat();
|
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat();
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
package org.geysermc.connector.network.translators.bedrock;
|
package org.geysermc.connector.network.translators.bedrock;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientVehicleMovePacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientVehicleMovePacket;
|
||||||
|
import com.nukkitx.math.vector.Vector3f;
|
||||||
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
||||||
import org.geysermc.connector.entity.BoatEntity;
|
import org.geysermc.connector.entity.BoatEntity;
|
||||||
import org.geysermc.connector.entity.type.EntityType;
|
import org.geysermc.connector.entity.type.EntityType;
|
||||||
|
@ -43,6 +44,23 @@ public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator<MoveEn
|
||||||
public void translate(GeyserSession session, MoveEntityAbsolutePacket packet) {
|
public void translate(GeyserSession session, MoveEntityAbsolutePacket packet) {
|
||||||
session.setLastVehicleMoveTimestamp(System.currentTimeMillis());
|
session.setLastVehicleMoveTimestamp(System.currentTimeMillis());
|
||||||
|
|
||||||
|
if (session.getRidingVehicleEntity() != null && session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), false)) {
|
||||||
|
Vector3f position = Vector3f.from(session.getRidingVehicleEntity().getPosition().getX(), packet.getPosition().getY(),
|
||||||
|
session.getRidingVehicleEntity().getPosition().getZ());
|
||||||
|
if (session.getRidingVehicleEntity() instanceof BoatEntity) {
|
||||||
|
// Undo the changes usually applied to the boat
|
||||||
|
session.getRidingVehicleEntity().as(BoatEntity.class).moveAbsoluteWithoutAdjustments(session,
|
||||||
|
position, session.getRidingVehicleEntity().getRotation(),
|
||||||
|
session.getRidingVehicleEntity().isOnGround(), true);
|
||||||
|
} else {
|
||||||
|
// This doesn't work if teleported is false
|
||||||
|
session.getRidingVehicleEntity().moveAbsolute(session, position,
|
||||||
|
session.getRidingVehicleEntity().getRotation(),
|
||||||
|
session.getRidingVehicleEntity().isOnGround(), true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float y = packet.getPosition().getY();
|
float y = packet.getPosition().getY();
|
||||||
if (session.getRidingVehicleEntity() instanceof BoatEntity) {
|
if (session.getRidingVehicleEntity() instanceof BoatEntity) {
|
||||||
// Remove the offset to prevents boats from looking like they're floating in water
|
// Remove the offset to prevents boats from looking like they're floating in water
|
||||||
|
|
|
@ -92,6 +92,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||||
|
|
||||||
session.sendDownstreamPacket(playerRotationPacket);
|
session.sendDownstreamPacket(playerRotationPacket);
|
||||||
} else {
|
} else {
|
||||||
|
if (session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) {
|
if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) {
|
||||||
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround());
|
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround());
|
||||||
if (position != null) { // A null return value cancels the packet
|
if (position != null) { // A null return value cancels the packet
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java.world.border;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.border.ServerInitializeBorderPacket;
|
||||||
|
import com.nukkitx.math.vector.Vector2f;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
|
||||||
|
@Translator(packet = ServerInitializeBorderPacket.class)
|
||||||
|
public class JavaInitializeBorderTranslator extends PacketTranslator<ServerInitializeBorderPacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ServerInitializeBorderPacket packet) {
|
||||||
|
WorldBorder worldBorder = session.getWorldBorder();
|
||||||
|
worldBorder.setCenter(Vector2f.from(packet.getNewCenterX(), packet.getNewCenterZ()));
|
||||||
|
worldBorder.setOldDiameter(packet.getOldSize());
|
||||||
|
worldBorder.setNewDiameter(packet.getNewSize());
|
||||||
|
worldBorder.setSpeed(packet.getLerpTime());
|
||||||
|
worldBorder.setWarningDelay(packet.getWarningTime());
|
||||||
|
worldBorder.setWarningBlocks(packet.getWarningBlocks());
|
||||||
|
worldBorder.setResizing(packet.getLerpTime() > 0);
|
||||||
|
|
||||||
|
worldBorder.update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java.world.border;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.border.ServerSetBorderCenterPacket;
|
||||||
|
import com.nukkitx.math.vector.Vector2f;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
|
||||||
|
@Translator(packet = ServerSetBorderCenterPacket.class)
|
||||||
|
public class JavaSetBorderCenterPacket extends PacketTranslator<ServerSetBorderCenterPacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ServerSetBorderCenterPacket packet) {
|
||||||
|
WorldBorder worldBorder = session.getWorldBorder();
|
||||||
|
worldBorder.setCenter(Vector2f.from(packet.getNewCenterX(), packet.getNewCenterZ()));
|
||||||
|
|
||||||
|
worldBorder.update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java.world.border;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.border.ServerSetBorderLerpSizePacket;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
|
||||||
|
@Translator(packet = ServerSetBorderLerpSizePacket.class)
|
||||||
|
public class JavaSetBorderLerpSizeTranslator extends PacketTranslator<ServerSetBorderLerpSizePacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ServerSetBorderLerpSizePacket packet) {
|
||||||
|
WorldBorder worldBorder = session.getWorldBorder();
|
||||||
|
worldBorder.setOldDiameter(packet.getOldSize());
|
||||||
|
worldBorder.setNewDiameter(packet.getNewSize());
|
||||||
|
worldBorder.setSpeed(packet.getLerpTime());
|
||||||
|
worldBorder.setResizing(true);
|
||||||
|
|
||||||
|
worldBorder.update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java.world.border;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.border.ServerSetBorderSizePacket;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
|
||||||
|
@Translator(packet = ServerSetBorderSizePacket.class)
|
||||||
|
public class JavaSetBorderSizeTranslator extends PacketTranslator<ServerSetBorderSizePacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ServerSetBorderSizePacket packet) {
|
||||||
|
WorldBorder worldBorder = session.getWorldBorder();
|
||||||
|
worldBorder.setOldDiameter(packet.getSize());
|
||||||
|
worldBorder.setNewDiameter(packet.getSize());
|
||||||
|
worldBorder.setResizing(false);
|
||||||
|
|
||||||
|
worldBorder.update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java.world.border;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.border.ServerSetBorderWarningDelayPacket;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
|
||||||
|
@Translator(packet = ServerSetBorderWarningDelayPacket.class)
|
||||||
|
public class JavaSetBorderWarningDelayTranslator extends PacketTranslator<ServerSetBorderWarningDelayPacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ServerSetBorderWarningDelayPacket packet) {
|
||||||
|
WorldBorder worldBorder = session.getWorldBorder();
|
||||||
|
worldBorder.setWarningDelay(packet.getWarningDelay());
|
||||||
|
|
||||||
|
worldBorder.update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* @author GeyserMC
|
||||||
|
* @link https://github.com/GeyserMC/Geyser
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geysermc.connector.network.translators.java.world.border;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.border.ServerSetBorderWarningDistancePacket;
|
||||||
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.cache.WorldBorder;
|
||||||
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
|
import org.geysermc.connector.network.translators.Translator;
|
||||||
|
|
||||||
|
@Translator(packet = ServerSetBorderWarningDistancePacket.class)
|
||||||
|
public class JavaSetBorderWarningDistanceTranslator extends PacketTranslator<ServerSetBorderWarningDistancePacket> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translate(GeyserSession session, ServerSetBorderWarningDistancePacket packet) {
|
||||||
|
WorldBorder worldBorder = session.getWorldBorder();
|
||||||
|
worldBorder.setWarningBlocks(packet.getWarningBlocks());
|
||||||
|
|
||||||
|
worldBorder.update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -117,20 +117,26 @@ public class LanguageUtils {
|
||||||
if (formatString == null) {
|
if (formatString == null) {
|
||||||
properties = LOCALE_MAPPINGS.get(getDefaultLocale());
|
properties = LOCALE_MAPPINGS.get(getDefaultLocale());
|
||||||
formatString = properties.getProperty(key);
|
formatString = properties.getProperty(key);
|
||||||
}
|
|
||||||
|
|
||||||
// Try and get the key from en_US (this should only ever happen in development)
|
// Try and get the key from en_US (this should only ever happen in development)
|
||||||
if (formatString == null) {
|
if (formatString == null) {
|
||||||
properties = LOCALE_MAPPINGS.get("en_US");
|
properties = LOCALE_MAPPINGS.get("en_US");
|
||||||
formatString = properties.getProperty(key);
|
formatString = properties.getProperty(key);
|
||||||
}
|
|
||||||
|
|
||||||
// Final fallback
|
// Final fallback
|
||||||
if (formatString == null) {
|
if (formatString == null) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return MessageFormat.format(formatString.replace("'", "''").replace("&", "\u00a7"), values);
|
String message = formatString.replace("&", "\u00a7");
|
||||||
|
if (values == null || values.length == 0) {
|
||||||
|
// Nothing to replace
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageFormat.format(message.replace("'", "''"), values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,12 +146,15 @@ public class LanguageUtils {
|
||||||
* @return The formatted locale
|
* @return The formatted locale
|
||||||
*/
|
*/
|
||||||
public static String formatLocale(String locale) {
|
public static String formatLocale(String locale) {
|
||||||
try {
|
// Currently, all valid Geyser locales follow the same pattern of ll_CC, where ll is the language and
|
||||||
String[] parts = locale.toLowerCase().split("_");
|
// CC is the country
|
||||||
return parts[0] + "_" + parts[1].toUpperCase();
|
if (locale.length() != 5 || locale.indexOf('_') != 2) {
|
||||||
} catch (Exception e) {
|
// Invalid locale
|
||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
String language = locale.substring(0, 2);
|
||||||
|
String country = locale.substring(3);
|
||||||
|
return language.toLowerCase(Locale.ENGLISH) + "_" + country.toUpperCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue