mirror of
https://github.com/GeyserMC/Geyser.git
synced 2026-01-06 15:41:50 +00:00
Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory
This commit is contained in:
@@ -86,6 +86,11 @@ public class GeyserConnector {
|
||||
public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs
|
||||
public static final String VERSION = "DEV"; // A fallback for running in IDEs
|
||||
|
||||
/**
|
||||
* Oauth client ID for Microsoft authentication
|
||||
*/
|
||||
public static final String OAUTH_CLIENT_ID = "204cefd1-4818-4de1-b98d-513fae875d88";
|
||||
|
||||
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
|
||||
|
||||
private final List<GeyserSession> players = new ArrayList<>();
|
||||
@@ -101,8 +106,8 @@ public class GeyserConnector {
|
||||
private final ScheduledExecutorService generalThreadPool;
|
||||
|
||||
private BedrockServer bedrockServer;
|
||||
private PlatformType platformType;
|
||||
private GeyserBootstrap bootstrap;
|
||||
private final PlatformType platformType;
|
||||
private final GeyserBootstrap bootstrap;
|
||||
|
||||
private Metrics metrics;
|
||||
|
||||
|
||||
@@ -118,6 +118,8 @@ public interface GeyserConfiguration {
|
||||
|
||||
String getAuthType();
|
||||
|
||||
boolean isPasswordAuthentication();
|
||||
|
||||
boolean isUseProxyProtocol();
|
||||
}
|
||||
|
||||
@@ -125,6 +127,12 @@ public interface GeyserConfiguration {
|
||||
String getEmail();
|
||||
|
||||
String getPassword();
|
||||
|
||||
/**
|
||||
* Will be removed after Microsoft accounts are fully migrated
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isMicrosoftAccount();
|
||||
}
|
||||
|
||||
interface IMetricsInfo {
|
||||
|
||||
@@ -149,17 +149,24 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration
|
||||
@JsonProperty("auth-type")
|
||||
private String authType = "online";
|
||||
|
||||
@JsonProperty("allow-password-authentication")
|
||||
private boolean passwordAuthentication = true;
|
||||
|
||||
@JsonProperty("use-proxy-protocol")
|
||||
private boolean useProxyProtocol = false;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true) // DO NOT REMOVE THIS! Otherwise, after we remove microsoft-account configs will not load
|
||||
public static class UserAuthenticationInfo implements IUserAuthenticationInfo {
|
||||
@AsteriskSerializer.Asterisk()
|
||||
private String email;
|
||||
|
||||
@AsteriskSerializer.Asterisk()
|
||||
private String password;
|
||||
|
||||
@JsonProperty("microsoft-account")
|
||||
private boolean microsoftAccount = false;
|
||||
}
|
||||
|
||||
@Getter
|
||||
|
||||
@@ -38,11 +38,11 @@ public class ItemedFireballEntity extends ThrowableEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updatePosition(GeyserSession session) {
|
||||
public void tick(GeyserSession session) {
|
||||
position = position.add(motion);
|
||||
// TODO: While this reduces latency in position updating (needed for better fireball reflecting),
|
||||
// TODO: movement is incredibly stiff. See if the MoveEntityDeltaPacket in 1.16.100 fixes this, and if not,
|
||||
// TODO: only use this laggy movement for fireballs that be reflected
|
||||
// TODO: movement is incredibly stiff.
|
||||
// TODO: Only use this laggy movement for fireballs that be reflected
|
||||
moveAbsoluteImmediate(session, position, rotation, false, true);
|
||||
float drag = getDrag(session);
|
||||
motion = motion.add(acceleration).mul(drag);
|
||||
|
||||
@@ -33,50 +33,35 @@ import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Used as a class for any object-like entity that moves as a projectile
|
||||
*/
|
||||
public class ThrowableEntity extends Entity {
|
||||
public class ThrowableEntity extends Entity implements Tickable {
|
||||
|
||||
private Vector3f lastPosition;
|
||||
/**
|
||||
* Updates the position for the Bedrock client.
|
||||
*
|
||||
* Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions
|
||||
*/
|
||||
protected ScheduledFuture<?> positionUpdater;
|
||||
|
||||
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
this.lastPosition = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the position for the Bedrock client.
|
||||
*
|
||||
* Java clients assume the next positions of moving items. Bedrock needs to be explicitly told positions
|
||||
*/
|
||||
@Override
|
||||
public void spawnEntity(GeyserSession session) {
|
||||
super.spawnEntity(session);
|
||||
positionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
|
||||
if (session.isClosed()) {
|
||||
positionUpdater.cancel(true);
|
||||
return;
|
||||
}
|
||||
updatePosition(session);
|
||||
}, 0, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
||||
}
|
||||
|
||||
protected void updatePosition(GeyserSession session) {
|
||||
public void tick(GeyserSession session) {
|
||||
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
|
||||
float drag = getDrag(session);
|
||||
float gravity = getGravity();
|
||||
motion = motion.mul(drag).down(gravity);
|
||||
}
|
||||
|
||||
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
|
||||
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
|
||||
*
|
||||
@@ -140,7 +125,6 @@ public class ThrowableEntity extends Entity {
|
||||
|
||||
@Override
|
||||
public boolean despawnEntity(GeyserSession session) {
|
||||
positionUpdater.cancel(true);
|
||||
if (entityType == EntityType.THROWN_ENDERPEARL) {
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_TELEPORT);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.entity;
|
||||
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
|
||||
/**
|
||||
* Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds.
|
||||
*/
|
||||
public interface Tickable {
|
||||
void tick(GeyserSession session);
|
||||
}
|
||||
@@ -28,19 +28,28 @@ package org.geysermc.connector.entity.living.monster;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.AttributeData;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import lombok.Data;
|
||||
import org.geysermc.connector.entity.Tickable;
|
||||
import org.geysermc.connector.entity.attribute.AttributeType;
|
||||
import org.geysermc.connector.entity.living.InsentientEntity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.utils.AttributeUtils;
|
||||
import org.geysermc.connector.utils.DimensionUtils;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class EnderDragonEntity extends InsentientEntity {
|
||||
public class EnderDragonEntity extends InsentientEntity implements Tickable {
|
||||
/**
|
||||
* The Ender Dragon has multiple hit boxes, which
|
||||
* are each its own invisible entity
|
||||
@@ -61,9 +70,19 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
private final Segment[] segmentHistory = new Segment[19];
|
||||
private int latestSegment = -1;
|
||||
|
||||
private boolean hovering;
|
||||
private int phase;
|
||||
/**
|
||||
* The number of ticks since the beginning of the phase
|
||||
*/
|
||||
private int phaseTicks;
|
||||
|
||||
private ScheduledFuture<?> partPositionUpdater;
|
||||
private int ticksTillNextGrowl = 100;
|
||||
|
||||
/**
|
||||
* Used to determine when the wing flap sound should be played
|
||||
*/
|
||||
private float wingPosition;
|
||||
private float lastWingPosition;
|
||||
|
||||
public EnderDragonEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
@@ -73,49 +92,67 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
|
||||
@Override
|
||||
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
|
||||
// Phase
|
||||
if (entityMetadata.getId() == 15) {
|
||||
int value = (int) entityMetadata.getValue();
|
||||
if (value == 5) {
|
||||
// Performing breath attack
|
||||
if (entityMetadata.getId() == 15) { // Phase
|
||||
phase = (int) entityMetadata.getValue();
|
||||
phaseTicks = 0;
|
||||
metadata.getFlags().setFlag(EntityFlag.SITTING, isSitting());
|
||||
}
|
||||
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
|
||||
if (entityMetadata.getId() == 8) { // Health
|
||||
// Update the health attribute, so that the death animation gets played
|
||||
// Round health up, so that Bedrock doesn't consider the dragon to be dead when health is between 0 and 1
|
||||
float health = (float) Math.ceil(metadata.getFloat(EntityData.HEALTH));
|
||||
if (phase == 9 && health <= 0) { // Dying phase
|
||||
EntityEventPacket entityEventPacket = new EntityEventPacket();
|
||||
entityEventPacket.setType(EntityEventType.DRAGON_FLAMING);
|
||||
entityEventPacket.setType(EntityEventType.ENDER_DRAGON_DEATH);
|
||||
entityEventPacket.setRuntimeEntityId(geyserId);
|
||||
entityEventPacket.setData(0);
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
}
|
||||
metadata.getFlags().setFlag(EntityFlag.SITTING, value == 5 || value == 6 || value == 7);
|
||||
hovering = value == 10;
|
||||
attributes.put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(health, 200));
|
||||
updateBedrockAttributes(session);
|
||||
}
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an updated list of attributes to the Bedrock client.
|
||||
* This is overwritten to allow the health attribute to differ from
|
||||
* the health specified in the metadata.
|
||||
*
|
||||
* @param session GeyserSession
|
||||
*/
|
||||
@Override
|
||||
public void updateBedrockAttributes(GeyserSession session) {
|
||||
if (!valid) return;
|
||||
|
||||
List<AttributeData> attributes = new ArrayList<>();
|
||||
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> entry : this.attributes.entrySet()) {
|
||||
if (!entry.getValue().getType().isBedrockAttribute())
|
||||
continue;
|
||||
attributes.add(AttributeUtils.getBedrockAttribute(entry.getValue()));
|
||||
}
|
||||
|
||||
UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
|
||||
updateAttributesPacket.setRuntimeEntityId(geyserId);
|
||||
updateAttributesPacket.setAttributes(attributes);
|
||||
session.sendUpstreamPacket(updateAttributesPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(GeyserSession session) {
|
||||
AddEntityPacket addEntityPacket = new AddEntityPacket();
|
||||
addEntityPacket.setIdentifier("minecraft:" + entityType.name().toLowerCase());
|
||||
addEntityPacket.setRuntimeEntityId(geyserId);
|
||||
addEntityPacket.setUniqueEntityId(geyserId);
|
||||
addEntityPacket.setPosition(position);
|
||||
addEntityPacket.setMotion(motion);
|
||||
addEntityPacket.setRotation(getBedrockRotation());
|
||||
addEntityPacket.setEntityType(entityType.getType());
|
||||
addEntityPacket.getMetadata().putAll(metadata);
|
||||
super.spawnEntity(session);
|
||||
|
||||
// Otherwise dragon is always 'dying'
|
||||
addEntityPacket.getAttributes().add(new AttributeData("minecraft:health", 0.0f, 200f, 200f, 200f));
|
||||
|
||||
valid = true;
|
||||
session.sendUpstreamPacket(addEntityPacket);
|
||||
|
||||
head = new EnderDragonPartEntity(entityId + 1, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 1, 1);
|
||||
neck = new EnderDragonPartEntity(entityId + 2, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 3, 3);
|
||||
body = new EnderDragonPartEntity(entityId + 3, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 5, 3);
|
||||
leftWing = new EnderDragonPartEntity(entityId + 4, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
|
||||
rightWing = new EnderDragonPartEntity(entityId + 5, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 4, 2);
|
||||
AtomicLong nextEntityId = session.getEntityCache().getNextEntityId();
|
||||
head = new EnderDragonPartEntity(entityId + 1, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 1, 1);
|
||||
neck = new EnderDragonPartEntity(entityId + 2, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 3, 3);
|
||||
body = new EnderDragonPartEntity(entityId + 3, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 5, 3);
|
||||
leftWing = new EnderDragonPartEntity(entityId + 4, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
|
||||
rightWing = new EnderDragonPartEntity(entityId + 5, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 4, 2);
|
||||
tail = new EnderDragonPartEntity[3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, session.getEntityCache().getNextEntityId().incrementAndGet(), EntityType.ENDER_DRAGON_PART, position, motion, rotation, 2, 2);
|
||||
tail[i] = new EnderDragonPartEntity(entityId + 6 + i, nextEntityId.incrementAndGet(), EntityType.ENDER_DRAGON_PART, 2, 2);
|
||||
}
|
||||
|
||||
allParts = new EnderDragonPartEntity[]{head, neck, body, leftWing, rightWing, tail[0], tail[1], tail[2]};
|
||||
@@ -129,25 +166,25 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
segmentHistory[i].yaw = rotation.getZ();
|
||||
segmentHistory[i].y = position.getY();
|
||||
}
|
||||
|
||||
partPositionUpdater = session.getConnector().getGeneralThreadPool().scheduleAtFixedRate(() -> {
|
||||
pushSegment();
|
||||
updateBoundingBoxes(session);
|
||||
}, 0, 50, TimeUnit.MILLISECONDS);
|
||||
|
||||
session.getConnector().getLogger().debug("Spawned entity " + entityType + " at location " + position + " with id " + geyserId + " (java id " + entityId + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean despawnEntity(GeyserSession session) {
|
||||
partPositionUpdater.cancel(true);
|
||||
|
||||
for (EnderDragonPartEntity part : allParts) {
|
||||
part.despawnEntity(session);
|
||||
}
|
||||
return super.despawnEntity(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick(GeyserSession session) {
|
||||
effectTick(session);
|
||||
if (!metadata.getFlags().getFlag(EntityFlag.NO_AI) && isAlive()) {
|
||||
pushSegment();
|
||||
updateBoundingBoxes(session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the positions of the Ender Dragon's multiple bounding boxes
|
||||
*
|
||||
@@ -163,7 +200,7 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
|
||||
// Lowers the head when the dragon sits/hovers
|
||||
float headDuck;
|
||||
if (hovering || metadata.getFlags().getFlag(EntityFlag.SITTING)) {
|
||||
if (isHovering() || isSitting()) {
|
||||
headDuck = -1f;
|
||||
} else {
|
||||
headDuck = baseSegment.y - getSegment(0).y;
|
||||
@@ -193,6 +230,105 @@ public class EnderDragonEntity extends InsentientEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the particles and sounds of the Ender Dragon
|
||||
* @param session GeyserSession.
|
||||
*/
|
||||
private void effectTick(GeyserSession session) {
|
||||
Random random = ThreadLocalRandom.current();
|
||||
if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
|
||||
if (Math.cos(wingPosition * 2f * Math.PI) <= -0.3f && Math.cos(lastWingPosition * 2f * Math.PI) >= -0.3f) {
|
||||
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
|
||||
playSoundPacket.setSound("mob.enderdragon.flap");
|
||||
playSoundPacket.setPosition(position);
|
||||
playSoundPacket.setVolume(5f);
|
||||
playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
|
||||
session.sendUpstreamPacket(playSoundPacket);
|
||||
}
|
||||
|
||||
if (!isSitting() && !isHovering() && ticksTillNextGrowl-- == 0) {
|
||||
playGrowlSound(session);
|
||||
ticksTillNextGrowl = 200 + random.nextInt(200);
|
||||
}
|
||||
|
||||
lastWingPosition = wingPosition;
|
||||
}
|
||||
if (isAlive()) {
|
||||
if (metadata.getFlags().getFlag(EntityFlag.NO_AI)) {
|
||||
wingPosition = 0.5f;
|
||||
} else if (isHovering() || isSitting()) {
|
||||
wingPosition += 0.1f;
|
||||
} else {
|
||||
double speed = motion.length();
|
||||
wingPosition += 0.2f / (speed * 10f + 1) * Math.pow(2, motion.getY());
|
||||
}
|
||||
|
||||
phaseTicks++;
|
||||
if (phase == 3) { // Landing Phase
|
||||
float headHeight = head.getMetadata().getFloat(EntityData.BOUNDING_BOX_HEIGHT);
|
||||
Vector3f headCenter = head.getPosition().up(headHeight * 0.5f);
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Vector3f particlePos = headCenter.add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f);
|
||||
// This is missing velocity information
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_DRAGONS_BREATH);
|
||||
particlePacket.setPosition(particlePos);
|
||||
session.sendUpstreamPacket(particlePacket);
|
||||
}
|
||||
} else if (phase == 5) { // Sitting Flaming Phase
|
||||
if (phaseTicks % 2 == 0 && phaseTicks < 10) {
|
||||
// Performing breath attack
|
||||
// Entity event DRAGON_FLAMING seems to create particles from the origin of the dragon,
|
||||
// so we need to manually spawn particles
|
||||
for (int i = 0; i < 8; i++) {
|
||||
SpawnParticleEffectPacket spawnParticleEffectPacket = new SpawnParticleEffectPacket();
|
||||
spawnParticleEffectPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
|
||||
spawnParticleEffectPacket.setPosition(head.getPosition().add(random.nextGaussian() / 2f, random.nextGaussian() / 2f, random.nextGaussian() / 2f));
|
||||
spawnParticleEffectPacket.setIdentifier("minecraft:dragon_breath_fire");
|
||||
session.sendUpstreamPacket(spawnParticleEffectPacket);
|
||||
}
|
||||
}
|
||||
} else if (phase == 7) { // Sitting Attacking Phase
|
||||
playGrowlSound(session);
|
||||
} else if (phase == 9) { // Dying Phase
|
||||
// Send explosion particles as the dragon move towards the end portal
|
||||
if (phaseTicks % 10 == 0) {
|
||||
float xOffset = 8f * (random.nextFloat() - 0.5f);
|
||||
float yOffset = 4f * (random.nextFloat() - 0.5f) + 2f;
|
||||
float zOffset = 8f * (random.nextFloat() - 0.5f);
|
||||
Vector3f particlePos = position.add(xOffset, yOffset, zOffset);
|
||||
LevelEventPacket particlePacket = new LevelEventPacket();
|
||||
particlePacket.setType(LevelEventType.PARTICLE_EXPLOSION);
|
||||
particlePacket.setPosition(particlePos);
|
||||
session.sendUpstreamPacket(particlePacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void playGrowlSound(GeyserSession session) {
|
||||
Random random = ThreadLocalRandom.current();
|
||||
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
|
||||
playSoundPacket.setSound("mob.enderdragon.growl");
|
||||
playSoundPacket.setPosition(position);
|
||||
playSoundPacket.setVolume(2.5f);
|
||||
playSoundPacket.setPitch(0.8f + random.nextFloat() * 0.3f);
|
||||
session.sendUpstreamPacket(playSoundPacket);
|
||||
}
|
||||
|
||||
private boolean isAlive() {
|
||||
return metadata.getFloat(EntityData.HEALTH) > 0;
|
||||
}
|
||||
|
||||
private boolean isHovering() {
|
||||
return phase == 10;
|
||||
}
|
||||
|
||||
private boolean isSitting() {
|
||||
return phase == 5 || phase == 6 || phase == 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the current yaw and y into the circular buffer
|
||||
*/
|
||||
|
||||
@@ -32,11 +32,12 @@ import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
|
||||
public class EnderDragonPartEntity extends Entity {
|
||||
public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, float width, float height) {
|
||||
super(entityId, geyserId, entityType, position, motion, rotation);
|
||||
public EnderDragonPartEntity(long entityId, long geyserId, EntityType entityType, float width, float height) {
|
||||
super(entityId, geyserId, entityType, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
|
||||
|
||||
metadata.put(EntityData.BOUNDING_BOX_WIDTH, width);
|
||||
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height);
|
||||
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
|
||||
metadata.getFlags().setFlag(EntityFlag.FIRE_IMMUNE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ package org.geysermc.connector.entity.living.monster;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
@@ -45,11 +47,21 @@ public class EndermanEntity extends MonsterEntity {
|
||||
if (entityMetadata.getId() == 15) {
|
||||
metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue()));
|
||||
}
|
||||
// 'Angry' - mouth open
|
||||
// "Is screaming" - controls sound
|
||||
if (entityMetadata.getId() == 16) {
|
||||
if ((boolean) entityMetadata.getValue()) {
|
||||
LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet();
|
||||
packet.setSound(SoundEvent.STARE);
|
||||
packet.setPosition(this.position);
|
||||
packet.setExtraData(-1);
|
||||
packet.setIdentifier("minecraft:enderman");
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
}
|
||||
// "Is staring/provoked" - controls visuals
|
||||
if (entityMetadata.getId() == 17) {
|
||||
metadata.getFlags().setFlag(EntityFlag.ANGRY, (boolean) entityMetadata.getValue());
|
||||
}
|
||||
// TODO: ID 17 is stared at but I don't believe it's used - maybe only for the sound effect. Check after particle merge
|
||||
super.updateBedrockMetadata(entityMetadata, session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,16 @@ import com.nukkitx.protocol.bedrock.BedrockServerEventHandler;
|
||||
import com.nukkitx.protocol.bedrock.BedrockServerSession;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import org.geysermc.connector.common.ping.GeyserPingInfo;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.common.ping.GeyserPingInfo;
|
||||
import org.geysermc.connector.configuration.GeyserConfiguration;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.connector.network.translators.chat.MessageTranslator;
|
||||
import org.geysermc.connector.ping.IGeyserPingPassthrough;
|
||||
import org.geysermc.connector.utils.LanguageUtils;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||
|
||||
@@ -94,6 +95,20 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler {
|
||||
pong.setMaximumPlayerCount(config.getMaxPlayers());
|
||||
}
|
||||
|
||||
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
|
||||
// We don't know why, though
|
||||
byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8);
|
||||
if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) {
|
||||
// Remove the sub-MOTD first since that only appears locally
|
||||
pong.setSubMotd("");
|
||||
if (motdArray.length > 338) {
|
||||
// If the top MOTD is still too long, we chop it down
|
||||
byte[] newMotdArray = new byte[339];
|
||||
System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length);
|
||||
pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
//Bedrock will not even attempt a connection if the client thinks the server is full
|
||||
//so we have to fake it not being full
|
||||
if (pong.getPlayerCount() >= pong.getMaximumPlayerCount()) {
|
||||
|
||||
@@ -161,6 +161,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
||||
|
||||
if (info != null) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.stored_credentials", session.getAuthData().getName()));
|
||||
session.setMicrosoftAccount(info.isMicrosoftAccount());
|
||||
session.authenticate(info.getEmail(), info.getPassword());
|
||||
|
||||
// TODO send a message to bedrock user telling them they are connected (if nothing like a motd
|
||||
|
||||
@@ -26,8 +26,12 @@
|
||||
package org.geysermc.connector.network.session;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.exception.request.AuthPendingException;
|
||||
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.auth.service.AuthenticationService;
|
||||
import com.github.steveice10.mc.auth.service.MojangAuthenticationService;
|
||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||
import com.github.steveice10.mc.protocol.MinecraftConstants;
|
||||
import com.github.steveice10.mc.protocol.MinecraftProtocol;
|
||||
import com.github.steveice10.mc.protocol.data.SubProtocol;
|
||||
@@ -36,9 +40,9 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Recipe;
|
||||
import com.github.steveice10.mc.protocol.data.game.statistic.Statistic;
|
||||
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
|
||||
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket;
|
||||
import com.github.steveice10.packetlib.BuiltinFlags;
|
||||
import com.github.steveice10.packetlib.Client;
|
||||
@@ -69,6 +73,7 @@ import lombok.Setter;
|
||||
import org.geysermc.common.window.CustomFormWindow;
|
||||
import org.geysermc.common.window.FormWindow;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.entity.Tickable;
|
||||
import org.geysermc.connector.command.CommandSender;
|
||||
import org.geysermc.connector.common.AuthType;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
@@ -113,8 +118,13 @@ public class GeyserSession implements CommandSender {
|
||||
@Setter
|
||||
private BedrockClientData clientData;
|
||||
|
||||
@Deprecated
|
||||
@Setter
|
||||
private boolean microsoftAccount;
|
||||
|
||||
private final SessionPlayerEntity playerEntity;
|
||||
|
||||
private BookEditCache bookEditCache;
|
||||
private ChunkCache chunkCache;
|
||||
private EntityCache entityCache;
|
||||
private EntityEffectCache effectCache;
|
||||
@@ -178,7 +188,11 @@ public class GeyserSession implements CommandSender {
|
||||
@Setter
|
||||
private GameMode gameMode = GameMode.SURVIVAL;
|
||||
|
||||
private final AtomicInteger pendingDimSwitches = new AtomicInteger(0);
|
||||
/**
|
||||
* Keeps track of the world name for respawning.
|
||||
*/
|
||||
@Setter
|
||||
private String worldName = null;
|
||||
|
||||
private boolean sneaking;
|
||||
|
||||
@@ -215,9 +229,6 @@ public class GeyserSession implements CommandSender {
|
||||
@Setter
|
||||
private Vector3i lastInteractionPosition = Vector3i.ZERO;
|
||||
|
||||
private boolean manyDimPackets = false;
|
||||
private ServerRespawnPacket lastDimPacket = null;
|
||||
|
||||
@Setter
|
||||
private Entity ridingVehicleEntity;
|
||||
|
||||
@@ -280,15 +291,14 @@ public class GeyserSession implements CommandSender {
|
||||
private ScheduledFuture<?> bucketScheduledFuture;
|
||||
|
||||
/**
|
||||
* Sends a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
|
||||
* Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
|
||||
*/
|
||||
@Setter
|
||||
private ScheduledFuture<?> movementSendIfIdle;
|
||||
private long lastMovementTimestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* Controls whether the daylight cycle gamerule has been sent to the client, so the sun/moon remain motionless.
|
||||
*/
|
||||
@Setter
|
||||
private boolean daylightCycle = true;
|
||||
|
||||
private boolean reducedDebugInfo = false;
|
||||
@@ -363,12 +373,18 @@ public class GeyserSession implements CommandSender {
|
||||
private List<UUID> selectedEmotes = new ArrayList<>();
|
||||
private final Set<UUID> emotes = new HashSet<>();
|
||||
|
||||
/**
|
||||
* The thread that will run every 50 milliseconds - one Minecraft tick.
|
||||
*/
|
||||
private ScheduledFuture<?> tickThread = null;
|
||||
|
||||
private MinecraftProtocol protocol;
|
||||
|
||||
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
|
||||
this.connector = connector;
|
||||
this.upstream = new UpstreamSession(bedrockServerSession);
|
||||
|
||||
this.bookEditCache = new BookEditCache(this);
|
||||
this.chunkCache = new ChunkCache(this);
|
||||
this.entityCache = new EntityCache(this);
|
||||
this.effectCache = new EntityEffectCache();
|
||||
@@ -473,146 +489,22 @@ public class GeyserSession implements CommandSender {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
if (password != null && !password.isEmpty()) {
|
||||
protocol = new MinecraftProtocol(username, password);
|
||||
AuthenticationService authenticationService;
|
||||
if (microsoftAccount) {
|
||||
authenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
|
||||
} else {
|
||||
authenticationService = new MojangAuthenticationService();
|
||||
}
|
||||
authenticationService.setUsername(username);
|
||||
authenticationService.setPassword(password);
|
||||
authenticationService.login();
|
||||
|
||||
protocol = new MinecraftProtocol(authenticationService);
|
||||
} else {
|
||||
protocol = new MinecraftProtocol(username);
|
||||
}
|
||||
|
||||
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
|
||||
final PublicKey publicKey;
|
||||
|
||||
if (floodgate) {
|
||||
PublicKey key = null;
|
||||
try {
|
||||
key = EncryptionUtil.getKeyFromFile(
|
||||
connector.getConfig().getFloodgateKeyPath(),
|
||||
PublicKey.class
|
||||
);
|
||||
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
|
||||
}
|
||||
publicKey = key;
|
||||
} else publicKey = null;
|
||||
|
||||
if (publicKey != null) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
|
||||
}
|
||||
|
||||
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
|
||||
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
|
||||
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
|
||||
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
|
||||
}
|
||||
// Let Geyser handle sending the keep alive
|
||||
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
||||
downstream.getSession().addListener(new SessionAdapter() {
|
||||
@Override
|
||||
public void packetSending(PacketSendingEvent event) {
|
||||
//todo move this somewhere else
|
||||
if (event.getPacket() instanceof HandshakePacket && floodgate) {
|
||||
String encrypted = "";
|
||||
try {
|
||||
encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
|
||||
clientData.getGameVersion(),
|
||||
authData.getName(),
|
||||
authData.getXboxUUID(),
|
||||
clientData.getDeviceOS().ordinal(),
|
||||
clientData.getLanguageCode(),
|
||||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getSession().getAddress().getAddress().getHostAddress()
|
||||
));
|
||||
} catch (Exception e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
}
|
||||
|
||||
HandshakePacket handshakePacket = event.getPacket();
|
||||
event.setPacket(new HandshakePacket(
|
||||
handshakePacket.getProtocolVersion(),
|
||||
handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
|
||||
handshakePacket.getPort(),
|
||||
handshakePacket.getIntent()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(ConnectedEvent event) {
|
||||
loggingIn = false;
|
||||
loggedIn = true;
|
||||
if (protocol.getProfile() == null) {
|
||||
// Java account is offline
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
||||
return;
|
||||
}
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
|
||||
playerEntity.setUuid(protocol.getProfile().getId());
|
||||
playerEntity.setUsername(protocol.getProfile().getName());
|
||||
|
||||
String locale = clientData.getLanguageCode();
|
||||
|
||||
// Let the user know there locale may take some time to download
|
||||
// as it has to be extracted from a JAR
|
||||
if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
|
||||
// This should probably be left hardcoded as it will only show for en_us clients
|
||||
sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
|
||||
}
|
||||
|
||||
// Download and load the language for the player
|
||||
LocaleUtils.downloadAndLoadLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(DisconnectedEvent event) {
|
||||
loggingIn = false;
|
||||
loggedIn = false;
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
|
||||
if (event.getCause() != null) {
|
||||
event.getCause().printStackTrace();
|
||||
}
|
||||
|
||||
upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetReceived(PacketReceivedEvent event) {
|
||||
if (!closed) {
|
||||
//handle consecutive respawn packets
|
||||
if (event.getPacket().getClass().equals(ServerRespawnPacket.class)) {
|
||||
manyDimPackets = lastDimPacket != null;
|
||||
lastDimPacket = event.getPacket();
|
||||
return;
|
||||
} else if (lastDimPacket != null) {
|
||||
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(lastDimPacket.getClass(), lastDimPacket, GeyserSession.this);
|
||||
lastDimPacket = null;
|
||||
}
|
||||
|
||||
// Required, or else Floodgate players break with Bukkit chunk caching
|
||||
if (event.getPacket() instanceof LoginSuccessPacket) {
|
||||
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
|
||||
playerEntity.setUsername(profile.getName());
|
||||
playerEntity.setUuid(profile.getId());
|
||||
|
||||
// Check if they are not using a linked account
|
||||
if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
|
||||
SkinManager.handleBedrockSkin(playerEntity, clientData);
|
||||
}
|
||||
}
|
||||
|
||||
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetError(PacketErrorEvent event) {
|
||||
connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
|
||||
if (connector.getConfig().isDebugMode())
|
||||
event.getCause().printStackTrace();
|
||||
event.setSuppress(true);
|
||||
}
|
||||
});
|
||||
|
||||
downstream.getSession().connect();
|
||||
connector.addPlayer(this);
|
||||
connectDownstream();
|
||||
} catch (InvalidCredentialsException | IllegalArgumentException e) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", username));
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
||||
@@ -622,6 +514,199 @@ public class GeyserSession implements CommandSender {
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Present a form window to the user asking to log in with another web browser
|
||||
*/
|
||||
public void authenticateWithMicrosoftCode() {
|
||||
if (loggedIn) {
|
||||
connector.getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.auth.already_loggedin", getAuthData().getName()));
|
||||
return;
|
||||
}
|
||||
|
||||
loggingIn = true;
|
||||
// new thread so clients don't timeout
|
||||
new Thread(() -> {
|
||||
try {
|
||||
MsaAuthenticationService msaAuthenticationService = new MsaAuthenticationService(GeyserConnector.OAUTH_CLIENT_ID);
|
||||
|
||||
MsaAuthenticationService.MsCodeResponse response = msaAuthenticationService.getAuthCode();
|
||||
LoginEncryptionUtils.showMicrosoftCodeWindow(this, response);
|
||||
|
||||
// This just looks cool
|
||||
SetTimePacket packet = new SetTimePacket();
|
||||
packet.setTime(16000);
|
||||
sendUpstreamPacket(packet);
|
||||
|
||||
// Wait for the code to validate
|
||||
attemptCodeAuthentication(msaAuthenticationService);
|
||||
} catch (InvalidCredentialsException | IllegalArgumentException e) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.invalid", getAuthData().getName()));
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.invalid.kick", getClientData().getLanguageCode()));
|
||||
} catch (RequestException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll every second to see if the user has successfully signed in
|
||||
*/
|
||||
private void attemptCodeAuthentication(MsaAuthenticationService msaAuthenticationService) {
|
||||
if (loggedIn || closed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
msaAuthenticationService.login();
|
||||
protocol = new MinecraftProtocol(msaAuthenticationService);
|
||||
|
||||
connectDownstream();
|
||||
} catch (RequestException e) {
|
||||
if (!(e instanceof AuthPendingException)) {
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
// Wait one second before trying again
|
||||
connector.getGeneralThreadPool().schedule(() -> attemptCodeAuthentication(msaAuthenticationService), 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After getting whatever credentials needed, we attempt to join the Java server.
|
||||
*/
|
||||
private void connectDownstream() {
|
||||
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
|
||||
final PublicKey publicKey;
|
||||
|
||||
if (floodgate) {
|
||||
PublicKey key = null;
|
||||
try {
|
||||
key = EncryptionUtil.getKeyFromFile(
|
||||
connector.getConfig().getFloodgateKeyPath(),
|
||||
PublicKey.class
|
||||
);
|
||||
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
|
||||
}
|
||||
publicKey = key;
|
||||
} else publicKey = null;
|
||||
|
||||
if (publicKey != null) {
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
|
||||
}
|
||||
|
||||
// Start ticking
|
||||
tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS);
|
||||
|
||||
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
|
||||
if (connector.getConfig().getRemote().isUseProxyProtocol()) {
|
||||
downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true);
|
||||
downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress());
|
||||
}
|
||||
// Let Geyser handle sending the keep alive
|
||||
downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false);
|
||||
downstream.getSession().addListener(new SessionAdapter() {
|
||||
@Override
|
||||
public void packetSending(PacketSendingEvent event) {
|
||||
//todo move this somewhere else
|
||||
if (event.getPacket() instanceof HandshakePacket && floodgate) {
|
||||
String encrypted = "";
|
||||
try {
|
||||
encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
|
||||
clientData.getGameVersion(),
|
||||
authData.getName(),
|
||||
authData.getXboxUUID(),
|
||||
clientData.getDeviceOS().ordinal(),
|
||||
clientData.getLanguageCode(),
|
||||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getSession().getAddress().getAddress().getHostAddress()
|
||||
));
|
||||
} catch (Exception e) {
|
||||
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
|
||||
}
|
||||
|
||||
HandshakePacket handshakePacket = event.getPacket();
|
||||
event.setPacket(new HandshakePacket(
|
||||
handshakePacket.getProtocolVersion(),
|
||||
handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
|
||||
handshakePacket.getPort(),
|
||||
handshakePacket.getIntent()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connected(ConnectedEvent event) {
|
||||
loggingIn = false;
|
||||
loggedIn = true;
|
||||
if (protocol.getProfile() == null) {
|
||||
// Java account is offline
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode()));
|
||||
return;
|
||||
}
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress()));
|
||||
playerEntity.setUuid(protocol.getProfile().getId());
|
||||
playerEntity.setUsername(protocol.getProfile().getName());
|
||||
|
||||
String locale = clientData.getLanguageCode();
|
||||
|
||||
// Let the user know there locale may take some time to download
|
||||
// as it has to be extracted from a JAR
|
||||
if (locale.toLowerCase().equals("en_us") && !LocaleUtils.LOCALE_MAPPINGS.containsKey("en_us")) {
|
||||
// This should probably be left hardcoded as it will only show for en_us clients
|
||||
sendMessage("Loading your locale (en_us); if this isn't already downloaded, this may take some time");
|
||||
}
|
||||
|
||||
// Download and load the language for the player
|
||||
LocaleUtils.downloadAndLoadLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(DisconnectedEvent event) {
|
||||
loggingIn = false;
|
||||
loggedIn = false;
|
||||
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason()));
|
||||
if (event.getCause() != null) {
|
||||
event.getCause().printStackTrace();
|
||||
}
|
||||
|
||||
upstream.disconnect(MessageTranslator.convertMessageLenient(event.getReason()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetReceived(PacketReceivedEvent event) {
|
||||
if (!closed) {
|
||||
// Required, or else Floodgate players break with Bukkit chunk caching
|
||||
if (event.getPacket() instanceof LoginSuccessPacket) {
|
||||
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
|
||||
playerEntity.setUsername(profile.getName());
|
||||
playerEntity.setUuid(profile.getId());
|
||||
|
||||
// Check if they are not using a linked account
|
||||
if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) {
|
||||
SkinManager.handleBedrockSkin(playerEntity, clientData);
|
||||
}
|
||||
}
|
||||
|
||||
PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetError(PacketErrorEvent event) {
|
||||
connector.getLogger().warning(LanguageUtils.getLocaleStringLog("geyser.network.downstream_error", event.getCause().getMessage()));
|
||||
if (connector.getConfig().isDebugMode())
|
||||
event.getCause().printStackTrace();
|
||||
event.setSuppress(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (!daylightCycle) {
|
||||
setDaylightCycle(true);
|
||||
}
|
||||
downstream.getSession().connect();
|
||||
connector.addPlayer(this);
|
||||
}
|
||||
|
||||
public void disconnect(String reason) {
|
||||
if (!closed) {
|
||||
loggedIn = false;
|
||||
@@ -634,6 +719,11 @@ public class GeyserSession implements CommandSender {
|
||||
}
|
||||
}
|
||||
|
||||
if (tickThread != null) {
|
||||
tickThread.cancel(true);
|
||||
}
|
||||
|
||||
this.bookEditCache = null;
|
||||
this.chunkCache = null;
|
||||
this.entityCache = null;
|
||||
this.effectCache = null;
|
||||
@@ -647,6 +737,28 @@ public class GeyserSession implements CommandSender {
|
||||
disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.close", getClientData().getLanguageCode()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every 50 milliseconds - one Minecraft tick.
|
||||
*/
|
||||
public void tick() {
|
||||
// Check to see if the player's position needs updating - a position update should be sent once every 3 seconds
|
||||
if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) {
|
||||
// Recalculate in case something else changed position
|
||||
Vector3d position = collisionManager.adjustBedrockPosition(playerEntity.getPosition(), playerEntity.isOnGround());
|
||||
// A null return value cancels the packet
|
||||
if (position != null) {
|
||||
ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(playerEntity.isOnGround(),
|
||||
position.getX(), position.getY(), position.getZ());
|
||||
sendDownstreamPacket(packet);
|
||||
}
|
||||
lastMovementTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
for (Tickable entity : entityCache.getTickableEntities()) {
|
||||
entity.tick(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAuthenticationData(AuthData authData) {
|
||||
this.authData = authData;
|
||||
}
|
||||
@@ -923,6 +1035,18 @@ public class GeyserSession implements CommandSender {
|
||||
reducedDebugInfo = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the daylight cycle gamerule on the client
|
||||
* This is used in the login screen along-side normal usage
|
||||
*
|
||||
* @param doCycle If the cycle should continue
|
||||
*/
|
||||
public void setDaylightCycle(boolean doCycle) {
|
||||
sendGameRule("dodaylightcycle", doCycle);
|
||||
// Save the value so we don't have to constantly send a daylight cycle gamerule update
|
||||
this.daylightCycle = doCycle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a gamerule value to the client
|
||||
*
|
||||
|
||||
75
connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
vendored
Normal file
75
connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.connector.inventory.GeyserItemStack;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||
|
||||
/**
|
||||
* Manages updating the current writable book.
|
||||
*
|
||||
* Java sends book updates less frequently than Bedrock, and this can cause issues with servers that rate limit
|
||||
* book packets. Because of this, we need to ensure packets are only send every second or so at maximum.
|
||||
*/
|
||||
public class BookEditCache {
|
||||
private final GeyserSession session;
|
||||
@Setter
|
||||
private ClientEditBookPacket packet;
|
||||
/**
|
||||
* Stores the last time a book update packet was sent to the server.
|
||||
*/
|
||||
private long lastBookUpdate;
|
||||
|
||||
public BookEditCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if there is a book edit update to send, and if so, send it.
|
||||
*/
|
||||
public void checkForSend() {
|
||||
if (packet == null) {
|
||||
// No new packet has to be sent
|
||||
return;
|
||||
}
|
||||
// Prevent kicks due to rate limiting - specifically on Spigot servers
|
||||
if ((System.currentTimeMillis() - lastBookUpdate) < 1000) {
|
||||
return;
|
||||
}
|
||||
// Don't send the update if the player isn't not holding a book, shouldn't happen if we catch all interactions
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
|
||||
if (itemStack == null || itemStack.getJavaId() != ItemRegistry.WRITABLE_BOOK.getJavaId()) {
|
||||
packet = null;
|
||||
return;
|
||||
}
|
||||
session.getDownstream().getSession().send(packet);
|
||||
packet = null;
|
||||
lastBookUpdate = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ package org.geysermc.connector.network.session.cache;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.connector.entity.Tickable;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
@@ -40,17 +41,21 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
* for that player (e.g. seeing vanished players from /vanish)
|
||||
*/
|
||||
public class EntityCache {
|
||||
private GeyserSession session;
|
||||
private final GeyserSession session;
|
||||
|
||||
@Getter
|
||||
private Long2ObjectMap<Entity> entities = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
|
||||
/**
|
||||
* A list of all entities that must be ticked.
|
||||
*/
|
||||
private final List<Tickable> tickableEntities = Collections.synchronizedList(new ArrayList<>());
|
||||
private Long2LongMap entityIdTranslations = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
|
||||
private Map<UUID, PlayerEntity> playerEntities = Collections.synchronizedMap(new HashMap<>());
|
||||
private Map<UUID, BossBar> bossBars = Collections.synchronizedMap(new HashMap<>());
|
||||
private Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
|
||||
private final Long2LongMap cachedPlayerEntityLinks = Long2LongMaps.synchronize(new Long2LongOpenHashMap());
|
||||
|
||||
@Getter
|
||||
private AtomicLong nextEntityId = new AtomicLong(2L);
|
||||
private final AtomicLong nextEntityId = new AtomicLong(2L);
|
||||
|
||||
public EntityCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
@@ -59,6 +64,11 @@ public class EntityCache {
|
||||
public void spawnEntity(Entity entity) {
|
||||
if (cacheEntity(entity)) {
|
||||
entity.spawnEntity(session);
|
||||
|
||||
if (entity instanceof Tickable) {
|
||||
// Start ticking it
|
||||
tickableEntities.add((Tickable) entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +86,10 @@ public class EntityCache {
|
||||
if (entity != null && entity.isValid() && (force || entity.despawnEntity(session))) {
|
||||
long geyserId = entityIdTranslations.remove(entity.getEntityId());
|
||||
entities.remove(geyserId);
|
||||
|
||||
if (entity instanceof Tickable) {
|
||||
tickableEntities.remove(entity);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -152,4 +166,8 @@ public class EntityCache {
|
||||
public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) {
|
||||
cachedPlayerEntityLinks.put(playerId, linkedEntityId);
|
||||
}
|
||||
|
||||
public List<Tickable> getTickableEntities() {
|
||||
return tickableEntities;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.bedrock;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.StringTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import com.nukkitx.protocol.bedrock.packet.BookEditPacket;
|
||||
import org.geysermc.connector.inventory.GeyserItemStack;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Translator(packet = BookEditPacket.class)
|
||||
public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(BookEditPacket packet, GeyserSession session) {
|
||||
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand();
|
||||
if (itemStack != null) {
|
||||
CompoundTag tag = itemStack.getNbt() != null ? itemStack.getNbt() : new CompoundTag("");
|
||||
ItemStack bookItem = new ItemStack(itemStack.getJavaId(), itemStack.getAmount(), tag);
|
||||
List<Tag> pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>();
|
||||
|
||||
int page = packet.getPageNumber();
|
||||
// Creative edits the NBT for us
|
||||
if (session.getGameMode() != GameMode.CREATIVE) {
|
||||
switch (packet.getAction()) {
|
||||
case ADD_PAGE: {
|
||||
// Add empty pages in between
|
||||
for (int i = pages.size(); i < page; i++) {
|
||||
pages.add(i, new StringTag("", ""));
|
||||
}
|
||||
pages.add(page, new StringTag("", packet.getText()));
|
||||
break;
|
||||
}
|
||||
// Called whenever a page is modified
|
||||
case REPLACE_PAGE: {
|
||||
if (page < pages.size()) {
|
||||
pages.set(page, new StringTag("", packet.getText()));
|
||||
} else {
|
||||
// Add empty pages in between
|
||||
for (int i = pages.size(); i < page; i++) {
|
||||
pages.add(i, new StringTag("", ""));
|
||||
}
|
||||
pages.add(page, new StringTag("", packet.getText()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DELETE_PAGE: {
|
||||
if (page < pages.size()) {
|
||||
pages.remove(page);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SWAP_PAGES: {
|
||||
int page2 = packet.getSecondaryPageNumber();
|
||||
if (page < pages.size() && page2 < pages.size()) {
|
||||
Collections.swap(pages, page, page2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SIGN_BOOK: {
|
||||
tag.put(new StringTag("author", packet.getAuthor()));
|
||||
tag.put(new StringTag("title", packet.getTitle()));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Remove empty pages at the end
|
||||
while (pages.size() > 0) {
|
||||
StringTag currentPage = (StringTag) pages.get(pages.size() - 1);
|
||||
if (currentPage.getValue() == null || currentPage.getValue().isEmpty()) {
|
||||
pages.remove(pages.size() - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tag.put(new ListTag("pages", pages));
|
||||
session.getPlayerInventory().setItem(36 + session.getPlayerInventory().getHeldItemSlot(), GeyserItemStack.from(bookItem), session);
|
||||
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
|
||||
|
||||
session.getBookEditCache().setPacket(new ClientEditBookPacket(bookItem, packet.getAction() == BookEditPacket.Action.SIGN_BOOK, session.getPlayerInventory().getHeldItemSlot()));
|
||||
// There won't be any more book updates after this, so we can try sending the edit packet immediately
|
||||
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
|
||||
session.getBookEditCache().checkForSend();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,14 +38,13 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerId;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
|
||||
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData;
|
||||
import com.nukkitx.protocol.bedrock.data.inventory.InventorySource;
|
||||
import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.*;
|
||||
import org.geysermc.connector.entity.CommandBlockMinecartEntity;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.entity.ItemFrameEntity;
|
||||
@@ -63,11 +62,23 @@ import org.geysermc.connector.utils.BlockUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* BedrockInventoryTransactionTranslator handles most interactions between the client and the world,
|
||||
* or the client and their inventory.
|
||||
*/
|
||||
@Translator(packet = InventoryTransactionPacket.class)
|
||||
public class BedrockInventoryTransactionTranslator extends PacketTranslator<InventoryTransactionPacket> {
|
||||
|
||||
private static final float MAXIMUM_BLOCK_PLACING_DISTANCE = 64f;
|
||||
private static final int CREATIVE_EYE_HEIGHT_PLACE_DISTANCE = 49;
|
||||
private static final int SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE = 36;
|
||||
private static final float MAXIMUM_BLOCK_DESTROYING_DISTANCE = 36f;
|
||||
|
||||
@Override
|
||||
public void translate(InventoryTransactionPacket packet, GeyserSession session) {
|
||||
// Send book updates before opening inventories
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
switch (packet.getTransactionType()) {
|
||||
case NORMAL:
|
||||
System.out.println(packet);
|
||||
@@ -129,6 +140,46 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
break;
|
||||
}
|
||||
|
||||
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
|
||||
/*
|
||||
Checks to ensure that the range will be accepted by the server.
|
||||
"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
|
||||
*/
|
||||
// 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();
|
||||
EntityFlags flags = session.getPlayerEntity().getMetadata().getFlags();
|
||||
|
||||
// Adjust position for current eye height
|
||||
if (flags.getFlag(EntityFlag.SNEAKING)) {
|
||||
playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 1.27f), 0);
|
||||
} else if (flags.getFlag(EntityFlag.SWIMMING) || flags.getFlag(EntityFlag.GLIDING) || flags.getFlag(EntityFlag.DAMAGE_NEARBY_MOBS)) {
|
||||
// Swimming, gliding, or using the trident spin attack
|
||||
playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 0.4f), 0);
|
||||
} else if (flags.getFlag(EntityFlag.SLEEPING)) {
|
||||
playerPosition = playerPosition.sub(0, (EntityType.PLAYER.getOffset() - 0.2f), 0);
|
||||
} // else, we don't have to modify the position
|
||||
|
||||
float diffX = playerPosition.getX() - packet.getBlockPosition().getX();
|
||||
float diffY = playerPosition.getY() - packet.getBlockPosition().getY();
|
||||
float diffZ = playerPosition.getZ() - packet.getBlockPosition().getZ();
|
||||
if (((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ)) >
|
||||
(session.getGameMode().equals(GameMode.CREATIVE) ? CREATIVE_EYE_HEIGHT_PLACE_DISTANCE : SURVIVAL_EYE_HEIGHT_PLACE_DISTANCE)) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vanilla check
|
||||
if (!(session.getPlayerEntity().getPosition().sub(0, EntityType.PLAYER.getOffset(), 0)
|
||||
.distanceSquared(packet.getBlockPosition().toFloat().add(0.5f, 0.5f, 0.5f)) < MAXIMUM_BLOCK_PLACING_DISTANCE)) {
|
||||
// The client thinks that its blocks have been successfully placed. Restore the server's blocks instead.
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
Block place checks end - client is good to go
|
||||
*/
|
||||
|
||||
ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket(
|
||||
new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()),
|
||||
BlockFace.values()[packet.getBlockFace()],
|
||||
@@ -176,7 +227,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
}
|
||||
}
|
||||
|
||||
Vector3i blockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getBlockFace());
|
||||
ItemEntry handItem = ItemRegistry.getItem(packet.getItemInHand());
|
||||
if (handItem.isBlock()) {
|
||||
session.setLastBlockPlacePosition(blockPos);
|
||||
@@ -200,19 +250,32 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
session.sendDownstreamPacket(useItemPacket);
|
||||
break;
|
||||
case 2:
|
||||
int blockState = session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ());
|
||||
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState);
|
||||
if (session.getGameMode() == GameMode.CREATIVE || (session.getConnector().getConfig().isCacheChunks() && blockHardness == 0)) {
|
||||
session.setLastBlockPlacedId(null);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
||||
session.getConnector().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
|
||||
|
||||
LevelEventPacket blockBreakPacket = new LevelEventPacket();
|
||||
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
|
||||
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
||||
blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
|
||||
session.sendUpstreamPacket(blockBreakPacket);
|
||||
session.setLastBlockPlacedId(null);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
|
||||
// Same deal with vanilla block placing as above.
|
||||
// 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();
|
||||
Vector3f floatBlockPosition = packet.getBlockPosition().toFloat();
|
||||
diffX = playerPosition.getX() - (floatBlockPosition.getX() + 0.5f);
|
||||
diffY = (playerPosition.getY() - EntityType.PLAYER.getOffset()) - (floatBlockPosition.getY() + 0.5f) + 1.5f;
|
||||
diffZ = playerPosition.getZ() - (floatBlockPosition.getZ() + 0.5f);
|
||||
float distanceSquared = diffX * diffX + diffY * diffY + diffZ * diffZ;
|
||||
if (distanceSquared > MAXIMUM_BLOCK_DESTROYING_DISTANCE) {
|
||||
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
||||
return;
|
||||
}
|
||||
|
||||
LevelEventPacket blockBreakPacket = new LevelEventPacket();
|
||||
blockBreakPacket.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
|
||||
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
||||
blockBreakPacket.setData(BlockTranslator.getBedrockBlockId(blockState));
|
||||
session.sendUpstreamPacket(blockBreakPacket);
|
||||
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
|
||||
|
||||
long frameEntityId = ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition());
|
||||
if (frameEntityId != -1 && session.getEntityCache().getEntityByJavaId(frameEntityId) != null) {
|
||||
ClientPlayerInteractEntityPacket attackPacket = new ClientPlayerInteractEntityPacket((int) frameEntityId, InteractAction.ATTACK, session.isSneaking());
|
||||
@@ -286,4 +349,34 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the correct block state from the server without updating the chunk cache.
|
||||
*
|
||||
* @param session the session of the Bedrock client
|
||||
* @param blockPos the block position to restore
|
||||
*/
|
||||
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
|
||||
int javaBlockState = session.getConnector().getWorldManager().getBlockAt(session, blockPos);
|
||||
UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket();
|
||||
updateBlockPacket.setDataLayer(0);
|
||||
updateBlockPacket.setBlockPosition(blockPos);
|
||||
updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(javaBlockState));
|
||||
updateBlockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(updateBlockPacket);
|
||||
|
||||
UpdateBlockPacket updateWaterPacket = new UpdateBlockPacket();
|
||||
updateWaterPacket.setDataLayer(1);
|
||||
updateWaterPacket.setBlockPosition(blockPos);
|
||||
updateWaterPacket.setRuntimeId(BlockTranslator.isWaterlogged(javaBlockState) ? BlockTranslator.BEDROCK_WATER_ID : BlockTranslator.BEDROCK_AIR_ID);
|
||||
updateWaterPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(updateWaterPacket);
|
||||
|
||||
// Reset the item in hand to prevent "missing" blocks
|
||||
InventorySlotPacket slotPacket = new InventorySlotPacket();
|
||||
slotPacket.setContainerId(ContainerId.INVENTORY);
|
||||
slotPacket.setSlot(packet.getHotbarSlot());
|
||||
slotPacket.setItem(packet.getItemInHand());
|
||||
session.sendUpstreamPacket(slotPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
|
||||
return;
|
||||
}
|
||||
|
||||
// Send book update before switching hotbar slot
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot());
|
||||
|
||||
ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot());
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
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.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
|
||||
import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
@@ -40,10 +42,12 @@ import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
import org.geysermc.connector.inventory.GeyserItemStack;
|
||||
import org.geysermc.connector.inventory.PlayerInventory;
|
||||
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.network.translators.collision.CollisionManager;
|
||||
import org.geysermc.connector.network.translators.item.ItemEntry;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.utils.BlockUtils;
|
||||
|
||||
@@ -58,6 +62,11 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
// Send book update before any player action
|
||||
if (packet.getAction() != PlayerActionPacket.Action.RESPAWN) {
|
||||
session.getBookEditCache().checkForSend();
|
||||
}
|
||||
|
||||
Vector3i vector = packet.getBlockPosition();
|
||||
Position position = new Position(vector.getX(), vector.getY(), vector.getZ());
|
||||
|
||||
@@ -126,6 +135,27 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||
break;
|
||||
case START_BREAK:
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
// Start the block breaking animation
|
||||
if (session.getGameMode() != GameMode.CREATIVE) {
|
||||
int blockState = session.getConnector().getWorldManager().getBlockAt(session, vector);
|
||||
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(blockState);
|
||||
LevelEventPacket startBreak = new LevelEventPacket();
|
||||
startBreak.setType(LevelEventType.BLOCK_START_BREAK);
|
||||
startBreak.setPosition(vector.toFloat());
|
||||
PlayerInventory inventory = session.getPlayerInventory();
|
||||
GeyserItemStack item = inventory.getItemInHand();
|
||||
ItemEntry itemEntry = null;
|
||||
CompoundTag nbtData = new CompoundTag("");
|
||||
if (item != null) {
|
||||
itemEntry = item.getItemEntry();
|
||||
nbtData = item.getNbt();
|
||||
}
|
||||
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, blockState, itemEntry, nbtData, session) * 20);
|
||||
startBreak.setData((int) (65535 / breakTime));
|
||||
session.setBreakingBlock(blockState);
|
||||
session.sendUpstreamPacket(startBreak);
|
||||
}
|
||||
|
||||
// Account for fire - the client likes to hit the block behind.
|
||||
Vector3i fireBlockPos = BlockUtils.getBlockPosition(packet.getBlockPosition(), packet.getFace());
|
||||
int blockUp = session.getConnector().getWorldManager().getBlockAt(session, fireBlockPos);
|
||||
@@ -134,37 +164,44 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(fireBlockPos.getX(),
|
||||
fireBlockPos.getY(), fireBlockPos.getZ()), BlockFace.values()[packet.getFace()]);
|
||||
session.sendDownstreamPacket(startBreakingPacket);
|
||||
break;
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.values()[packet.getFace()]);
|
||||
ClientPlayerActionPacket startBreakingPacket = new ClientPlayerActionPacket(PlayerAction.START_DIGGING, position, BlockFace.values()[packet.getFace()]);
|
||||
session.sendDownstreamPacket(startBreakingPacket);
|
||||
break;
|
||||
case CONTINUE_BREAK:
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
LevelEventPacket continueBreakPacket = new LevelEventPacket();
|
||||
continueBreakPacket.setType(LevelEventType.PARTICLE_CRACK_BLOCK);
|
||||
continueBreakPacket.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock()));
|
||||
continueBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
||||
continueBreakPacket.setData((BlockTranslator.getBedrockBlockId(session.getBreakingBlock())) | (packet.getFace() << 24));
|
||||
continueBreakPacket.setPosition(vector.toFloat());
|
||||
session.sendUpstreamPacket(continueBreakPacket);
|
||||
break;
|
||||
case ABORT_BREAK:
|
||||
ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, new Position(packet.getBlockPosition().getX(),
|
||||
packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), BlockFace.DOWN);
|
||||
ClientPlayerActionPacket abortBreakingPacket = new ClientPlayerActionPacket(PlayerAction.CANCEL_DIGGING, position, BlockFace.DOWN);
|
||||
session.sendDownstreamPacket(abortBreakingPacket);
|
||||
LevelEventPacket stopBreak = new LevelEventPacket();
|
||||
stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
stopBreak.setPosition(vector.toFloat());
|
||||
stopBreak.setData(0);
|
||||
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
|
||||
session.sendUpstreamPacket(stopBreak);
|
||||
break;
|
||||
case STOP_BREAK:
|
||||
// Handled in BedrockInventoryTransactionTranslator
|
||||
break;
|
||||
case DIMENSION_CHANGE_SUCCESS:
|
||||
if (session.getPendingDimSwitches().decrementAndGet() == 0) {
|
||||
//sometimes the client doesn't feel like loading
|
||||
PlayStatusPacket spawnPacket = new PlayStatusPacket();
|
||||
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
|
||||
session.sendUpstreamPacket(spawnPacket);
|
||||
entity.updateBedrockAttributes(session);
|
||||
session.getEntityCache().updateBossBars();
|
||||
}
|
||||
//sometimes the client doesn't feel like loading
|
||||
PlayStatusPacket spawnPacket = new PlayStatusPacket();
|
||||
spawnPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
|
||||
session.sendUpstreamPacket(spawnPacket);
|
||||
entity.updateBedrockAttributes(session);
|
||||
session.getEntityCache().updateBossBars();
|
||||
break;
|
||||
case JUMP:
|
||||
session.setJumping(true);
|
||||
|
||||
@@ -33,16 +33,12 @@ import com.nukkitx.math.vector.Vector3d;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.geysermc.connector.common.ChatColor;
|
||||
import org.geysermc.connector.entity.player.PlayerEntity;
|
||||
import org.geysermc.connector.entity.type.EntityType;
|
||||
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.network.translators.collision.CollisionManager;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Translator(packet = MovePlayerPacket.class)
|
||||
public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPacket> {
|
||||
@@ -50,7 +46,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
@Override
|
||||
public void translate(MovePlayerPacket packet, GeyserSession session) {
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
if (!session.isSpawned() || session.getPendingDimSwitches().get() > 0) return;
|
||||
if (!session.isSpawned()) return;
|
||||
|
||||
if (!session.getUpstream().isInitialized()) {
|
||||
MoveEntityAbsolutePacket moveEntityBack = new MoveEntityAbsolutePacket();
|
||||
@@ -63,9 +59,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.getMovementSendIfIdle() != null) {
|
||||
session.getMovementSendIfIdle().cancel(true);
|
||||
}
|
||||
session.setLastMovementTimestamp(System.currentTimeMillis());
|
||||
|
||||
// Send book update before the player moves
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
if (session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityType.PLAYER.getOffset(), 0))) {
|
||||
// head yaw, pitch, head yaw
|
||||
@@ -86,7 +83,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
|
||||
session.sendDownstreamPacket(playerRotationPacket);
|
||||
} else {
|
||||
Vector3d position = adjustBedrockPosition(session, packet.getPosition(), packet.isOnGround());
|
||||
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround());
|
||||
if (position != null) { // A null return value cancels the packet
|
||||
if (isValidMove(session, packet.getMode(), entity.getPosition(), packet.getPosition())) {
|
||||
Packet movePacket;
|
||||
@@ -128,7 +125,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
} else {
|
||||
// Not a valid move
|
||||
session.getConnector().getLogger().debug("Recalculating position...");
|
||||
recalculatePosition(session);
|
||||
session.getCollisionManager().recalculatePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,13 +138,9 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
if (entity.getRightParrot() != null) {
|
||||
entity.getRightParrot().moveAbsolute(session, entity.getPosition(), entity.getRotation(), true, false);
|
||||
}
|
||||
|
||||
// Schedule a position send loop if the player is idle
|
||||
session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session),
|
||||
3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
|
||||
private boolean isValidMove(GeyserSession session, MovePlayerPacket.Mode mode, Vector3f currentPosition, Vector3f newPosition) {
|
||||
if (mode != MovePlayerPacket.Mode.NORMAL)
|
||||
return true;
|
||||
|
||||
@@ -171,81 +164,5 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
|
||||
* the two versions.
|
||||
*
|
||||
* @param session the current GeyserSession
|
||||
* @param bedrockPosition the current Bedrock position of the client
|
||||
* @param onGround whether the Bedrock player is on the ground
|
||||
* @return the position to send to the Java server, or null to cancel sending the packet
|
||||
*/
|
||||
private Vector3d adjustBedrockPosition(GeyserSession session, Vector3f bedrockPosition, boolean onGround) {
|
||||
// We need to parse the float as a string since casting a float to a double causes us to
|
||||
// lose precision and thus, causes players to get stuck when walking near walls
|
||||
double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset();
|
||||
|
||||
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
|
||||
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
|
||||
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
// With chunk caching, we can do some proper collision checks
|
||||
CollisionManager collisionManager = session.getCollisionManager();
|
||||
collisionManager.updatePlayerBoundingBox(position);
|
||||
|
||||
// Correct player position
|
||||
if (!collisionManager.correctPlayerPosition()) {
|
||||
// Cancel the movement if it needs to be cancelled
|
||||
recalculatePosition(session);
|
||||
return null;
|
||||
}
|
||||
|
||||
position = Vector3d.from(collisionManager.getPlayerBoundingBox().getMiddleX(),
|
||||
collisionManager.getPlayerBoundingBox().getMiddleY() - (collisionManager.getPlayerBoundingBox().getSizeY() / 2),
|
||||
collisionManager.getPlayerBoundingBox().getMiddleZ());
|
||||
} else {
|
||||
// When chunk caching is off, we have to rely on this
|
||||
// It rounds the Y position up to the nearest 0.5
|
||||
// This snaps players to snap to the top of stairs and slabs like on Java Edition
|
||||
// However, it causes issues such as the player floating on carpets
|
||||
if (onGround) javaY = Math.ceil(javaY * 2) / 2;
|
||||
position = position.up(javaY - position.getY());
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
|
||||
public void recalculatePosition(GeyserSession session) {
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
// Gravity might need to be reset...
|
||||
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
|
||||
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
entityDataPacket.getMetadata().putAll(entity.getMetadata());
|
||||
session.sendUpstreamPacket(entityDataPacket);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
movePlayerPacket.setPosition(entity.getPosition());
|
||||
movePlayerPacket.setRotation(entity.getBedrockRotation());
|
||||
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
}
|
||||
|
||||
private void sendPositionIfIdle(GeyserSession session) {
|
||||
if (session.isClosed()) return;
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
// Recalculate in case something else changed position
|
||||
Vector3d position = adjustBedrockPosition(session, entity.getPosition(), entity.isOnGround());
|
||||
// A null return value cancels the packet
|
||||
if (position != null) {
|
||||
ClientPlayerPositionPacket packet = new ClientPlayerPositionPacket(session.getPlayerEntity().isOnGround(),
|
||||
position.getX(), position.getY(), position.getZ());
|
||||
session.sendDownstreamPacket(packet);
|
||||
}
|
||||
session.setMovementSendIfIdle(session.getConnector().getGeneralThreadPool().schedule(() -> sendPositionIfIdle(session),
|
||||
3, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,12 @@ import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
|
||||
import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
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 org.geysermc.connector.network.translators.collision.translators.BlockCollision;
|
||||
|
||||
@@ -105,6 +109,7 @@ public class CollisionManager {
|
||||
// According to the Minecraft Wiki, when sneaking:
|
||||
// - In Bedrock Edition, the height becomes 1.65 blocks, allowing movement through spaces as small as 1.75 (2 - 1⁄4) blocks high.
|
||||
// - In Java Edition, the height becomes 1.5 blocks.
|
||||
// TODO: Have this depend on the player's literal bounding box variable
|
||||
if (session.isSneaking()) {
|
||||
playerBoundingBox.setSizeY(1.5);
|
||||
} else {
|
||||
@@ -113,6 +118,65 @@ public class CollisionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the Bedrock position before sending to the Java server to account for inaccuracies in movement between
|
||||
* the two versions.
|
||||
*
|
||||
* @param bedrockPosition the current Bedrock position of the client
|
||||
* @param onGround whether the Bedrock player is on the ground
|
||||
* @return the position to send to the Java server, or null to cancel sending the packet
|
||||
*/
|
||||
public Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround) {
|
||||
// We need to parse the float as a string since casting a float to a double causes us to
|
||||
// lose precision and thus, causes players to get stuck when walking near walls
|
||||
double javaY = bedrockPosition.getY() - EntityType.PLAYER.getOffset();
|
||||
|
||||
Vector3d position = Vector3d.from(Double.parseDouble(Float.toString(bedrockPosition.getX())), javaY,
|
||||
Double.parseDouble(Float.toString(bedrockPosition.getZ())));
|
||||
|
||||
if (session.getConnector().getConfig().isCacheChunks()) {
|
||||
// With chunk caching, we can do some proper collision checks
|
||||
updatePlayerBoundingBox(position);
|
||||
|
||||
// Correct player position
|
||||
if (!correctPlayerPosition()) {
|
||||
// Cancel the movement if it needs to be cancelled
|
||||
recalculatePosition();
|
||||
return null;
|
||||
}
|
||||
|
||||
position = Vector3d.from(playerBoundingBox.getMiddleX(),
|
||||
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
|
||||
playerBoundingBox.getMiddleZ());
|
||||
} else {
|
||||
// When chunk caching is off, we have to rely on this
|
||||
// It rounds the Y position up to the nearest 0.5
|
||||
// This snaps players to snap to the top of stairs and slabs like on Java Edition
|
||||
// However, it causes issues such as the player floating on carpets
|
||||
if (onGround) javaY = Math.ceil(javaY * 2) / 2;
|
||||
position = position.up(javaY - position.getY());
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
|
||||
public void recalculatePosition() {
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
// Gravity might need to be reset...
|
||||
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
|
||||
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
entityDataPacket.getMetadata().putAll(entity.getMetadata());
|
||||
session.sendUpstreamPacket(entityDataPacket);
|
||||
|
||||
MovePlayerPacket movePlayerPacket = new MovePlayerPacket();
|
||||
movePlayerPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
movePlayerPacket.setPosition(entity.getPosition());
|
||||
movePlayerPacket.setRotation(entity.getBedrockRotation());
|
||||
movePlayerPacket.setMode(MovePlayerPacket.Mode.NORMAL);
|
||||
session.sendUpstreamPacket(movePlayerPacket);
|
||||
}
|
||||
|
||||
public List<Vector3i> getPlayerCollidableBlocks() {
|
||||
List<Vector3i> blocks = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -95,6 +95,10 @@ public class ItemRegistry {
|
||||
* Wheat item entry, used in AbstractHorseEntity.java
|
||||
*/
|
||||
public static ItemEntry WHEAT;
|
||||
/**
|
||||
* Writable book item entry, used in BedrockBookEditTranslator.java
|
||||
*/
|
||||
public static ItemEntry WRITABLE_BOOK;
|
||||
|
||||
public static int BARRIER_INDEX = 0;
|
||||
|
||||
@@ -195,6 +199,9 @@ public class ItemRegistry {
|
||||
case "minecraft:wheat":
|
||||
WHEAT = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
case "minecraft:writable_book":
|
||||
WRITABLE_BOOK = ITEM_ENTRIES.get(itemIndex);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -78,9 +78,8 @@ public class BookPagesTranslator extends NbtItemStackTranslator {
|
||||
CompoundTag pageTag = (CompoundTag) tag;
|
||||
|
||||
StringTag textTag = pageTag.get("text");
|
||||
pages.add(new StringTag(MessageTranslator.convertToJavaMessage(textTag.getValue())));
|
||||
pages.add(new StringTag("", textTag.getValue()));
|
||||
}
|
||||
|
||||
itemTag.remove("pages");
|
||||
itemTag.put(new ListTag("pages", pages));
|
||||
}
|
||||
|
||||
@@ -55,12 +55,12 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
|
||||
// are swapping servers
|
||||
String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
|
||||
if (session.isSpawned()) {
|
||||
String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
|
||||
String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension);
|
||||
DimensionUtils.switchDimension(session, fakeDim);
|
||||
DimensionUtils.switchDimension(session, newDimension);
|
||||
|
||||
session.getWorldCache().removeScoreboard();
|
||||
}
|
||||
session.setWorldName(packet.getWorldName());
|
||||
|
||||
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();
|
||||
bedrockPacket.setUniqueEntityId(session.getPlayerEntity().getGeyserId());
|
||||
|
||||
@@ -43,8 +43,6 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
|
||||
@Override
|
||||
public void translate(ServerRespawnPacket packet, GeyserSession session) {
|
||||
Entity entity = session.getPlayerEntity();
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
float maxHealth = entity.getAttributes().containsKey(AttributeType.MAX_HEALTH) ? entity.getAttributes().get(AttributeType.MAX_HEALTH).getValue() : 20f;
|
||||
// Max health must be divisible by two in bedrock
|
||||
@@ -66,18 +64,24 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
|
||||
session.setRaining(false);
|
||||
}
|
||||
|
||||
if (session.isThunder()) {
|
||||
LevelEventPacket stopThunderPacket = new LevelEventPacket();
|
||||
stopThunderPacket.setType(LevelEventType.STOP_THUNDERSTORM);
|
||||
stopThunderPacket.setData(0);
|
||||
stopThunderPacket.setPosition(Vector3f.ZERO);
|
||||
session.sendUpstreamPacket(stopThunderPacket);
|
||||
session.setThunder(false);
|
||||
}
|
||||
|
||||
String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
|
||||
if (!session.getDimension().equals(newDimension)) {
|
||||
DimensionUtils.switchDimension(session, newDimension);
|
||||
} else {
|
||||
if (session.isManyDimPackets()) { //reloading world
|
||||
String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
|
||||
if (!session.getDimension().equals(newDimension) || !packet.getWorldName().equals(session.getWorldName())) {
|
||||
if (!packet.getWorldName().equals(session.getWorldName()) && session.getDimension().equals(newDimension)) {
|
||||
// Switching to a new world (based off the world name change); send a fake dimension change
|
||||
String fakeDim = DimensionUtils.getTemporaryDimension(session.getDimension(), newDimension);
|
||||
DimensionUtils.switchDimension(session, fakeDim);
|
||||
DimensionUtils.switchDimension(session, newDimension);
|
||||
} else {
|
||||
// Handled in JavaPlayerPositionRotationTranslator
|
||||
session.setSpawned(false);
|
||||
}
|
||||
session.setWorldName(packet.getWorldName());
|
||||
DimensionUtils.switchDimension(session, newDimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
|
||||
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
|
||||
import org.geysermc.connector.entity.Entity;
|
||||
@@ -183,6 +183,19 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case LIVING_EQUIPMENT_BREAK_HEAD:
|
||||
case LIVING_EQUIPMENT_BREAK_CHEST:
|
||||
case LIVING_EQUIPMENT_BREAK_LEGS:
|
||||
case LIVING_EQUIPMENT_BREAK_FEET:
|
||||
case LIVING_EQUIPMENT_BREAK_MAIN_HAND:
|
||||
case LIVING_EQUIPMENT_BREAK_OFF_HAND:
|
||||
LevelSoundEvent2Packet equipmentBreakPacket = new LevelSoundEvent2Packet();
|
||||
equipmentBreakPacket.setSound(SoundEvent.BREAK);
|
||||
equipmentBreakPacket.setPosition(entity.getPosition());
|
||||
equipmentBreakPacket.setExtraData(-1);
|
||||
equipmentBreakPacket.setIdentifier("");
|
||||
session.sendUpstreamPacket(equipmentBreakPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
session.sendUpstreamPacket(entityEventPacket);
|
||||
|
||||
@@ -27,8 +27,9 @@ package org.geysermc.connector.network.translators.java.entity.player;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerActionAckPacket;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.nukkitx.math.vector.Vector3f;
|
||||
import com.nukkitx.protocol.bedrock.data.LevelEventType;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
|
||||
@@ -36,11 +37,11 @@ import org.geysermc.connector.inventory.GeyserItemStack;
|
||||
import org.geysermc.connector.inventory.PlayerInventory;
|
||||
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.network.translators.item.ItemEntry;
|
||||
import org.geysermc.connector.network.translators.item.ItemRegistry;
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.network.translators.item.ItemEntry;
|
||||
import org.geysermc.connector.utils.BlockUtils;
|
||||
import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.utils.ChunkUtils;
|
||||
|
||||
@Translator(packet = ServerPlayerActionAckPacket.class)
|
||||
@@ -48,48 +49,54 @@ public class JavaPlayerActionAckTranslator extends PacketTranslator<ServerPlayer
|
||||
|
||||
@Override
|
||||
public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) {
|
||||
LevelEventPacket levelEvent = new LevelEventPacket();
|
||||
switch (packet.getAction()) {
|
||||
case FINISH_DIGGING:
|
||||
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(session.getBreakingBlock());
|
||||
if (session.getGameMode() != GameMode.CREATIVE && blockHardness != 0) {
|
||||
levelEvent.setType(LevelEventType.PARTICLE_DESTROY_BLOCK);
|
||||
levelEvent.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
|
||||
levelEvent.setData(BlockTranslator.getBedrockBlockId(session.getBreakingBlock()));
|
||||
ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition());
|
||||
if (packet.getAction() == PlayerAction.START_DIGGING && !packet.isSuccessful()) {
|
||||
LevelEventPacket stopBreak = new LevelEventPacket();
|
||||
stopBreak.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
stopBreak.setPosition(Vector3f.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
|
||||
stopBreak.setData(0);
|
||||
session.setBreakingBlock(BlockTranslator.JAVA_AIR_ID);
|
||||
session.sendUpstreamPacket(stopBreak);
|
||||
}
|
||||
if (!session.getConnector().getConfig().isCacheChunks()) {
|
||||
LevelEventPacket levelEvent = new LevelEventPacket();
|
||||
switch (packet.getAction()) {
|
||||
case START_DIGGING:
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState());
|
||||
levelEvent.setType(LevelEventType.BLOCK_START_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
PlayerInventory inventory = session.getPlayerInventory();
|
||||
GeyserItemStack item = inventory.getItemInHand();
|
||||
ItemEntry itemEntry = null;
|
||||
CompoundTag nbtData = new CompoundTag("");
|
||||
if (item != null) {
|
||||
itemEntry = item.getItemEntry();
|
||||
nbtData = item.getNbt();
|
||||
}
|
||||
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState(), itemEntry, nbtData, session) * 20);
|
||||
levelEvent.setData((int) (65535 / breakTime));
|
||||
session.setBreakingBlock(packet.getNewState());
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
session.setBreakingBlock(0);
|
||||
}
|
||||
ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition());
|
||||
break;
|
||||
case START_DIGGING:
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
break;
|
||||
}
|
||||
blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState());
|
||||
levelEvent.setType(LevelEventType.BLOCK_START_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
PlayerInventory inventory = session.getPlayerInventory();
|
||||
GeyserItemStack item = inventory.getItemInHand();
|
||||
double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState(), item.getItemEntry(), item.getNbt(), session) * 20);
|
||||
levelEvent.setData((int) (65535 / breakTime));
|
||||
session.setBreakingBlock(packet.getNewState());
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
break;
|
||||
case CANCEL_DIGGING:
|
||||
levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
levelEvent.setData(0);
|
||||
session.setBreakingBlock(0);
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
break;
|
||||
case CANCEL_DIGGING:
|
||||
levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK);
|
||||
levelEvent.setPosition(Vector3f.from(
|
||||
packet.getPosition().getX(),
|
||||
packet.getPosition().getY(),
|
||||
packet.getPosition().getZ()
|
||||
));
|
||||
levelEvent.setData(0);
|
||||
session.setBreakingBlock(0);
|
||||
session.sendUpstreamPacket(levelEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,13 +46,11 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
|
||||
|
||||
@Override
|
||||
public void translate(ServerPlayerPositionRotationPacket packet, GeyserSession session) {
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
if (entity == null)
|
||||
return;
|
||||
|
||||
if (!session.isLoggedIn())
|
||||
return;
|
||||
|
||||
PlayerEntity entity = session.getPlayerEntity();
|
||||
|
||||
if (!session.isSpawned()) {
|
||||
Vector3f pos = Vector3f.from(packet.getX(), packet.getY(), packet.getZ());
|
||||
entity.setPosition(pos);
|
||||
@@ -83,7 +81,7 @@ public class JavaPlayerPositionRotationTranslator extends PacketTranslator<Serve
|
||||
|
||||
ChunkUtils.updateChunkPosition(session, pos.toInt());
|
||||
|
||||
session.getConnector().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
|
||||
session.getConnector().getLogger().debug(LanguageUtils.getLocaleStringLog("geyser.entity.player.spawn", packet.getX(), packet.getY(), packet.getZ()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
package org.geysermc.connector.network.translators.java.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket;
|
||||
import com.nukkitx.math.vector.Vector3i;
|
||||
import com.nukkitx.protocol.bedrock.data.SoundEvent;
|
||||
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
@@ -37,17 +38,17 @@ import org.geysermc.connector.network.translators.sound.BlockSoundInteractionHan
|
||||
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
|
||||
import org.geysermc.connector.utils.ChunkUtils;
|
||||
|
||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockChangePacket;
|
||||
|
||||
@Translator(packet = ServerBlockChangePacket.class)
|
||||
public class JavaBlockChangeTranslator extends PacketTranslator<ServerBlockChangePacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerBlockChangePacket packet, GeyserSession session) {
|
||||
Position pos = packet.getRecord().getPosition();
|
||||
boolean updatePlacement = !(session.getConnector().getConfig().isCacheChunks() && session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()) == packet.getRecord().getBlock());
|
||||
ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), packet.getRecord().getPosition());
|
||||
if (updatePlacement && session.getConnector().getPlatformType() != PlatformType.SPIGOT) {
|
||||
boolean updatePlacement = session.getConnector().getPlatformType() != PlatformType.SPIGOT && // Spigot simply listens for the block place event
|
||||
!(session.getConnector().getConfig().isCacheChunks() &&
|
||||
session.getConnector().getWorldManager().getBlockAt(session, pos) == packet.getRecord().getBlock());
|
||||
ChunkUtils.updateBlock(session, packet.getRecord().getBlock(), pos);
|
||||
if (updatePlacement) {
|
||||
this.checkPlace(session, packet);
|
||||
}
|
||||
this.checkInteract(session, packet);
|
||||
|
||||
@@ -36,14 +36,14 @@ import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.network.translators.sound.SoundRegistry;
|
||||
|
||||
@Translator(packet = ServerPlaySoundPacket.class)
|
||||
public class JavaPlayerPlaySoundTranslator extends PacketTranslator<ServerPlaySoundPacket> {
|
||||
public class JavaPlaySoundTranslator extends PacketTranslator<ServerPlaySoundPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerPlaySoundPacket packet, GeyserSession session) {
|
||||
String packetSound;
|
||||
if(packet.getSound() instanceof BuiltinSound) {
|
||||
if (packet.getSound() instanceof BuiltinSound) {
|
||||
packetSound = ((BuiltinSound) packet.getSound()).getName();
|
||||
} else if(packet.getSound() instanceof CustomSound) {
|
||||
} else if (packet.getSound() instanceof CustomSound) {
|
||||
packetSound = ((CustomSound) packet.getSound()).getName();
|
||||
} else {
|
||||
session.getConnector().getLogger().debug("Unknown sound packet, we were unable to map this. " + packet.toString());
|
||||
@@ -23,7 +23,7 @@
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.connector.network.translators.java.entity.player;
|
||||
package org.geysermc.connector.network.translators.java.world;
|
||||
|
||||
import com.github.steveice10.mc.protocol.data.game.world.sound.BuiltinSound;
|
||||
import com.github.steveice10.mc.protocol.data.game.world.sound.CustomSound;
|
||||
@@ -35,26 +35,35 @@ import org.geysermc.connector.network.translators.Translator;
|
||||
import org.geysermc.connector.network.translators.sound.SoundRegistry;
|
||||
|
||||
@Translator(packet = ServerStopSoundPacket.class)
|
||||
public class JavaPlayerStopSoundTranslator extends PacketTranslator<ServerStopSoundPacket> {
|
||||
public class JavaStopSoundTranslator extends PacketTranslator<ServerStopSoundPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerStopSoundPacket packet, GeyserSession session) {
|
||||
// Runs if all sounds are stopped
|
||||
if (packet.getSound() == null) {
|
||||
StopSoundPacket stopPacket = new StopSoundPacket();
|
||||
stopPacket.setStoppingAllSound(true);
|
||||
stopPacket.setSoundName("");
|
||||
session.sendUpstreamPacket(stopPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
String packetSound;
|
||||
if(packet.getSound() instanceof BuiltinSound) {
|
||||
if (packet.getSound() instanceof BuiltinSound) {
|
||||
packetSound = ((BuiltinSound) packet.getSound()).getName();
|
||||
} else if(packet.getSound() instanceof CustomSound) {
|
||||
} else if (packet.getSound() instanceof CustomSound) {
|
||||
packetSound = ((CustomSound) packet.getSound()).getName();
|
||||
} else {
|
||||
session.getConnector().getLogger().debug("Unknown sound packet, we were unable to map this. " + packet.toString());
|
||||
return;
|
||||
}
|
||||
SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound);
|
||||
SoundRegistry.SoundMapping soundMapping = SoundRegistry.fromJava(packetSound.replace("minecraft:", ""));
|
||||
session.getConnector().getLogger()
|
||||
.debug("[StopSound] Sound mapping " + packetSound + " -> "
|
||||
+ soundMapping + (soundMapping == null ? "[not found]" : "")
|
||||
+ " - " + packet.toString());
|
||||
String playsound;
|
||||
if(soundMapping == null || soundMapping.getPlaysound() == null) {
|
||||
if (soundMapping == null || soundMapping.getPlaysound() == null) {
|
||||
// no mapping
|
||||
session.getConnector().getLogger()
|
||||
.debug("[StopSound] Defaulting to sound server gave us.");
|
||||
@@ -46,17 +46,10 @@ public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimeP
|
||||
session.sendUpstreamPacket(setTimePacket);
|
||||
if (!session.isDaylightCycle() && time >= 0) {
|
||||
// Client thinks there is no daylight cycle but there is
|
||||
setDoDaylightCycleGamerule(session, true);
|
||||
session.setDaylightCycle(true);
|
||||
} else if (session.isDaylightCycle() && time < 0) {
|
||||
// Client thinks there is daylight cycle but there isn't
|
||||
setDoDaylightCycleGamerule(session, false);
|
||||
session.setDaylightCycle(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
|
||||
session.sendGameRule("dodaylightcycle", doCycle);
|
||||
// Save the value so we don't have to constantly send a daylight cycle gamerule update
|
||||
session.setDaylightCycle(doCycle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,7 +87,9 @@ public class BlockTranslator {
|
||||
*/
|
||||
public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID;
|
||||
|
||||
// For block breaking animation math
|
||||
/**
|
||||
* A list of all Java runtime wool IDs, for use with block breaking math and shears
|
||||
*/
|
||||
public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet();
|
||||
public static final int JAVA_RUNTIME_COBWEB_ID;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public class CampfireBlockEntityTranslator extends BlockEntityTranslator {
|
||||
protected NbtMap getItem(CompoundTag tag) {
|
||||
ItemEntry entry = ItemRegistry.getItemEntry((String) tag.get("id").getValue());
|
||||
NbtMapBuilder tagBuilder = NbtMap.builder()
|
||||
.putShort("id", (short) entry.getBedrockId())
|
||||
.putString("Name", entry.getBedrockIdentifier())
|
||||
.putByte("Count", (byte) tag.get("Count").getValue())
|
||||
.putShort("Damage", (short) entry.getBedrockData());
|
||||
tagBuilder.put("tag", NbtMap.builder().build());
|
||||
|
||||
@@ -50,6 +50,7 @@ public class BlockUtils {
|
||||
if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0;
|
||||
if (toolType.equals("")) return 1.0;
|
||||
switch (toolTier) {
|
||||
// https://minecraft.gamepedia.com/Breaking#Speed
|
||||
case "wooden":
|
||||
return 2.0;
|
||||
case "stone":
|
||||
@@ -58,6 +59,8 @@ public class BlockUtils {
|
||||
return 6.0;
|
||||
case "diamond":
|
||||
return 8.0;
|
||||
case "netherite":
|
||||
return 9.0;
|
||||
case "golden":
|
||||
return 12.0;
|
||||
default:
|
||||
|
||||
@@ -57,20 +57,11 @@ public class DimensionUtils {
|
||||
public static void switchDimension(GeyserSession session, String javaDimension) {
|
||||
int bedrockDimension = javaToBedrock(javaDimension);
|
||||
Entity player = session.getPlayerEntity();
|
||||
if (javaDimension.equals(session.getDimension()))
|
||||
return;
|
||||
|
||||
if (session.getMovementSendIfIdle() != null) {
|
||||
session.getMovementSendIfIdle().cancel(true);
|
||||
}
|
||||
|
||||
session.getEntityCache().removeAllEntities();
|
||||
session.getItemFrameCache().clear();
|
||||
session.getLecternCache().clear();
|
||||
session.getSkullCache().clear();
|
||||
if (session.getPendingDimSwitches().getAndIncrement() > 0) {
|
||||
ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true);
|
||||
}
|
||||
|
||||
Vector3i pos = Vector3i.from(0, Short.MAX_VALUE, 0);
|
||||
|
||||
@@ -151,4 +142,20 @@ public class DimensionUtils {
|
||||
// Change dimension ID to the End to allow for building above Bedrock
|
||||
BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fake, temporary dimension we send clients to so we aren't switching to the same dimension without an additional
|
||||
* dimension switch.
|
||||
*
|
||||
* @param currentDimension the current dimension of the player
|
||||
* @param newDimension the new dimension that the player will be transferred to
|
||||
* @return the fake dimension to transfer to
|
||||
*/
|
||||
public static String getTemporaryDimension(String currentDimension, String newDimension) {
|
||||
if (BEDROCK_NETHER_ID == 2) {
|
||||
// Prevents rare instances of Bedrock locking up
|
||||
return javaToBedrock(newDimension) == 2 ? OVERWORLD : NETHER;
|
||||
}
|
||||
return currentDimension.equals(OVERWORLD) ? NETHER : OVERWORLD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.reflections.util.ConfigurationBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.function.Function;
|
||||
@@ -62,7 +63,8 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
public static <T> T loadJson(InputStream src, Class<T> valueType) throws IOException {
|
||||
return GeyserConnector.JSON_MAPPER.readValue(src, valueType);
|
||||
// Read specifically with UTF-8 to allow any non-UTF-encoded JSON to read
|
||||
return GeyserConnector.JSON_MAPPER.readValue(new InputStreamReader(src, StandardCharsets.UTF_8), valueType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -187,7 +187,11 @@ public class LanguageUtils {
|
||||
if (FileUtils.class.getResource("/languages/texts/" + locale + ".properties") == null) {
|
||||
result = false;
|
||||
if (GeyserConnector.getInstance() != null && GeyserConnector.getInstance().getLogger() != null) { // Could be too early for these to be initialized
|
||||
GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language
|
||||
if (locale.equals("en_US")) {
|
||||
GeyserConnector.getInstance().getLogger().error("English locale not found in Geyser. Did you clone the submodules? (git submodule update --init)");
|
||||
} else {
|
||||
GeyserConnector.getInstance().getLogger().warning(locale + " is not a valid Bedrock language."); // We can't translate this since we just loaded an invalid language
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!LOCALE_MAPPINGS.containsKey(locale)) {
|
||||
|
||||
@@ -29,19 +29,18 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||
import com.github.steveice10.mc.auth.service.MsaAuthenticationService;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.nukkitx.network.util.Preconditions;
|
||||
import com.nukkitx.protocol.bedrock.packet.LoginPacket;
|
||||
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
|
||||
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
|
||||
import org.geysermc.common.window.CustomFormBuilder;
|
||||
import org.geysermc.common.window.CustomFormWindow;
|
||||
import org.geysermc.common.window.FormWindow;
|
||||
import org.geysermc.common.window.SimpleFormWindow;
|
||||
import org.geysermc.common.window.*;
|
||||
import org.geysermc.common.window.button.FormButton;
|
||||
import org.geysermc.common.window.component.InputComponent;
|
||||
import org.geysermc.common.window.component.LabelComponent;
|
||||
import org.geysermc.common.window.response.CustomFormResponse;
|
||||
import org.geysermc.common.window.response.ModalFormResponse;
|
||||
import org.geysermc.common.window.response.SimpleFormResponse;
|
||||
import org.geysermc.connector.GeyserConnector;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
@@ -156,13 +155,21 @@ public class LoginEncryptionUtils {
|
||||
session.sendUpstreamPacketImmediately(packet);
|
||||
}
|
||||
|
||||
private static int AUTH_FORM_ID = 1336;
|
||||
private static int AUTH_DETAILS_FORM_ID = 1337;
|
||||
private static final int AUTH_MSA_DETAILS_FORM_ID = 1334;
|
||||
private static final int AUTH_MSA_CODE_FORM_ID = 1335;
|
||||
private static final int AUTH_FORM_ID = 1336;
|
||||
private static final int AUTH_DETAILS_FORM_ID = 1337;
|
||||
|
||||
public static void showLoginWindow(GeyserSession session) {
|
||||
// Set DoDaylightCycle to false so the time doesn't accelerate while we're here
|
||||
session.setDaylightCycle(false);
|
||||
|
||||
String userLanguage = session.getLocale();
|
||||
SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.title", userLanguage), LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.desc", userLanguage));
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login", userLanguage)));
|
||||
if (session.getConnector().getConfig().getRemote().isPasswordAuthentication()) {
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.mojang", userLanguage)));
|
||||
}
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage)));
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
|
||||
|
||||
session.sendForm(window, AUTH_FORM_ID);
|
||||
@@ -179,12 +186,33 @@ public class LoginEncryptionUtils {
|
||||
session.sendForm(window, AUTH_DETAILS_FORM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user between either OAuth code login or manual password authentication
|
||||
*/
|
||||
public static void showMicrosoftAuthenticationWindow(GeyserSession session) {
|
||||
String userLanguage = session.getLocale();
|
||||
SimpleFormWindow window = new SimpleFormWindow(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_login.microsoft", userLanguage), "");
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.browser", userLanguage)));
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.method.password", userLanguage))); // This form won't show if password authentication is disabled
|
||||
window.getButtons().add(new FormButton(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.notice.btn_disconnect", userLanguage)));
|
||||
session.sendForm(window, AUTH_MSA_DETAILS_FORM_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the code that a user must input into their browser
|
||||
*/
|
||||
public static void showMicrosoftCodeWindow(GeyserSession session, MsaAuthenticationService.MsCodeResponse response) {
|
||||
ModalFormWindow msaCodeWindow = new ModalFormWindow("%xbox.signin", "%xbox.signin.website\n%xbox.signin.url\n%xbox.signin.enterCode\n" +
|
||||
response.user_code, "Done", "%menu.disconnect");
|
||||
session.sendForm(msaCodeWindow, LoginEncryptionUtils.AUTH_MSA_CODE_FORM_ID);
|
||||
}
|
||||
|
||||
public static boolean authenticateFromForm(GeyserSession session, GeyserConnector connector, int formId, String formData) {
|
||||
WindowCache windowCache = session.getWindowCache();
|
||||
if (!windowCache.getWindows().containsKey(formId))
|
||||
return false;
|
||||
|
||||
if(formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID) {
|
||||
if (formId == AUTH_MSA_DETAILS_FORM_ID || formId == AUTH_FORM_ID || formId == AUTH_DETAILS_FORM_ID || formId == AUTH_MSA_CODE_FORM_ID) {
|
||||
FormWindow window = windowCache.getWindows().remove(formId);
|
||||
window.setResponse(formData.trim());
|
||||
|
||||
@@ -198,23 +226,57 @@ public class LoginEncryptionUtils {
|
||||
String password = response.getInputResponses().get(2);
|
||||
|
||||
session.authenticate(email, password);
|
||||
|
||||
// Clear windows so authentication data isn't accidentally cached
|
||||
windowCache.getWindows().clear();
|
||||
} else {
|
||||
showLoginDetailsWindow(session);
|
||||
}
|
||||
|
||||
// Clear windows so authentication data isn't accidentally cached
|
||||
windowCache.getWindows().clear();
|
||||
} else if (formId == AUTH_FORM_ID && window instanceof SimpleFormWindow) {
|
||||
boolean isPasswordAuthentication = session.getConnector().getConfig().getRemote().isPasswordAuthentication();
|
||||
int microsoftButton = isPasswordAuthentication ? 1 : 0;
|
||||
int disconnectButton = isPasswordAuthentication ? 2 : 1;
|
||||
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
|
||||
if (response != null) {
|
||||
if (response.getClickedButtonId() == 0) {
|
||||
if (isPasswordAuthentication && response.getClickedButtonId() == 0) {
|
||||
session.setMicrosoftAccount(false);
|
||||
showLoginDetailsWindow(session);
|
||||
} else if(response.getClickedButtonId() == 1) {
|
||||
} else if (response.getClickedButtonId() == microsoftButton) {
|
||||
session.setMicrosoftAccount(true);
|
||||
if (isPasswordAuthentication) {
|
||||
showMicrosoftAuthenticationWindow(session);
|
||||
} else {
|
||||
// Just show the OAuth code
|
||||
session.authenticateWithMicrosoftCode();
|
||||
}
|
||||
} else if (response.getClickedButtonId() == disconnectButton) {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||
}
|
||||
} else {
|
||||
showLoginWindow(session);
|
||||
}
|
||||
} else if (formId == AUTH_MSA_DETAILS_FORM_ID && window instanceof SimpleFormWindow) {
|
||||
SimpleFormResponse response = (SimpleFormResponse) window.getResponse();
|
||||
if (response != null) {
|
||||
if (response.getClickedButtonId() == 0) {
|
||||
session.authenticateWithMicrosoftCode();
|
||||
} else if (response.getClickedButtonId() == 1) {
|
||||
showLoginDetailsWindow(session);
|
||||
} else if (response.getClickedButtonId() == 2) {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||
}
|
||||
} else {
|
||||
showLoginWindow(session);
|
||||
}
|
||||
} else if (formId == AUTH_MSA_CODE_FORM_ID && window instanceof ModalFormWindow) {
|
||||
ModalFormResponse response = (ModalFormResponse) window.getResponse();
|
||||
if (response != null) {
|
||||
if (response.getClickedButtonId() == 1) {
|
||||
session.disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.login.form.disconnect", session.getLocale()));
|
||||
}
|
||||
} else {
|
||||
showMicrosoftAuthenticationWindow(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class ResourcePack {
|
||||
// As we just created the directory it will be empty
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (File file : directory.listFiles()) {
|
||||
if (file.getName().endsWith(".zip") || file.getName().endsWith(".mcpack")) {
|
||||
ResourcePack pack = new ResourcePack();
|
||||
@@ -77,12 +77,15 @@ public class ResourcePack {
|
||||
if (x.getName().contains("manifest.json")) {
|
||||
try {
|
||||
ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class);
|
||||
// Sometimes a pack_manifest file is present and not in a valid format,
|
||||
// but a manifest file is, so we null check through that one
|
||||
if (manifest.getHeader().getUuid() != null) {
|
||||
pack.file = file;
|
||||
pack.manifest = manifest;
|
||||
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
|
||||
|
||||
pack.file = file;
|
||||
pack.manifest = manifest;
|
||||
pack.version = ResourcePackManifest.Version.fromArray(manifest.getHeader().getVersion());
|
||||
|
||||
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
|
||||
PACKS.put(pack.getManifest().getHeader().getUuid().toString(), pack);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -32,10 +32,14 @@ remote:
|
||||
port: 25565
|
||||
# Authentication type. Can be offline, online, or floodgate (see https://github.com/GeyserMC/Geyser/wiki/Floodgate).
|
||||
auth-type: online
|
||||
# Allow for password-based authentication methods through Geyser. Only useful in online mode.
|
||||
# If this is false, users must authenticate to Microsoft using a code provided by Geyser on their desktop.
|
||||
allow-password-authentication: true
|
||||
# Whether to enable PROXY protocol or not while connecting to the server.
|
||||
# This is useful only when:
|
||||
# 1) Your server supports PROXY protocol (it probably doesn't)
|
||||
# 2) You run Velocity or BungeeCord with respective option enabled.
|
||||
# 2) You run Velocity or BungeeCord with the option enabled in the proxy's main config.
|
||||
# IF YOU DON'T KNOW WHAT THIS IS, DON'T TOUCH IT!
|
||||
use-proxy-protocol: false
|
||||
|
||||
# Floodgate uses encryption to ensure use from authorised sources.
|
||||
@@ -51,10 +55,12 @@ floodgate-key-file: public-key.pem
|
||||
# BedrockAccountUsername: # Your Minecraft: Bedrock Edition username
|
||||
# email: javaccountemail@example.com # Your Minecraft: Java Edition email
|
||||
# password: javaccountpassword123 # Your Minecraft: Java Edition password
|
||||
# microsoft-account: true # Whether the account is a Mojang or Microsoft account.
|
||||
#
|
||||
# bluerkelp2:
|
||||
# email: not_really_my_email_address_mr_minecrafter53267@gmail.com
|
||||
# password: "this isn't really my password"
|
||||
# microsoft-account: false
|
||||
|
||||
# Bedrock clients can freeze when opening up the command prompt for the first time if given a lot of commands.
|
||||
# Disabling this will prevent command suggestions from being sent and solve freezing for Bedrock clients.
|
||||
@@ -132,8 +138,7 @@ above-bedrock-nether-building: false
|
||||
force-resource-packs: true
|
||||
|
||||
# Allows Xbox achievements to be unlocked.
|
||||
# This disables certain commands so the Bedrock client can't to "cheat" to get them.
|
||||
# Commands such as /gamemode and /give will not work from Bedrock with this enabled
|
||||
# THIS DISABLES ALL COMMANDS FROM SUCCESSFULLY RUNNING FOR BEDROCK IN-GAME, as otherwise Bedrock thinks you are cheating.
|
||||
xbox-achievements-enabled: false
|
||||
|
||||
# bStats is a stat tracker that is entirely anonymous and tracks only basic information
|
||||
|
||||
Submodule connector/src/main/resources/languages updated: 1a00766840...6f246c24dd
Reference in New Issue
Block a user