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:
Camotoy 2021-09-09 21:27:38 -04:00 committed by GitHub
parent 8461cf76b7
commit f22d286ea1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 661 additions and 15 deletions

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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