Merge branch 'master' of https://github.com/GeyserMC/Geyser into server-inventory

This commit is contained in:
Camotoy 2021-02-26 00:46:44 -05:00
commit 6be71fc045
No known key found for this signature in database
GPG Key ID: 7EEFB66FE798081F
24 changed files with 1080 additions and 368 deletions

View File

@ -1,57 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!--- DELETING THIS TEMPLATE WILL GET YOUR ISSUE CLOSED! --->
<!--- Please follow this format COMPLETELY and make sure the bug you are reporting has not been reported yet. Reports should contain as much information or context as possible to help us find the problem. Simply creating an issue on a vague topic will not help us at all, and if you are unsure if something should belong here, please contact us on [Discord](http://discord.geysermc.org).-->
<!--- Issues pertaining to connection problems, or anything of that covered on the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues) do not belong here and only clutter this issue tracker. -->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots / Videos**
If applicable, add screenshots to help explain your problem.
**Server Version and Plugins**
If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
If you're running a multi-server instance, or using Geyser Standalone:
- Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
- Please list all plugins on all servers involved.
If this bug occurs on a server you do not control, please fill this in to the best of your knowledge.
**Geyser Dump**
If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you use the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
**Minecraft: Bedrock Edition Version**
The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...).
**Additional Context**
Add any other context about the problem here.

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: Bug report
about: Create a report to help us improve
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report for Geyser! Fill out the following form to your best ability to help us fix the problem.
Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: Steps to reproduce this behaviour
placeholder: |
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Screenshots / Videos
description: If applicable, add screenshots to help explain your problem.
- type: textarea
attributes:
label: Server Version and Plugins
description: |
If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information.
If you're running a multi-server instance or using Geyser Standalone:
* Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all.
* Please list all plugins on all servers involved.
If this bug occurs on a server you do not control, please full this in to the best of your knowledge.
- type: input
attributes:
label: Geyser Dump
description: If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you're using the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue.
- type: input
attributes:
label: Geyser Version
description: What version of Geyser are you running?
placeholder: "For example: 1.2.0-SNAPSHOT (git-master-2d9baf1)"
validations:
required: true
- type: input
attributes:
label: "Minecraft: Bedrock Edition Version"
description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition."
placeholder: "For example: 1.16.201"
- type: textarea
attributes:
label: Additional Context
description: Add any other context about the problem here

View File

@ -1,5 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: GeyserMC Discord
url: http://discord.geysermc.org/
- name: Common Issues
url: https://github.com/GeyserMC/Geyser/wiki/Common-Issues
about: Check the common issues to see if you are not alone with that issue and see how you can fix them.
- name: Frequently Asked Questions
url: https://github.com/GeyserMC/Geyser/wiki/FAQ
about: Look at the FAQ page for answers for frequently asked questions.
- name: Get help on the GeyserMC Discord server
url: https://discord.gg/geysermc
about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord.

View File

@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**What feature do you want?**
Add a description
**Alternatives?**
List any alternatives you might have tried

View File

@ -0,0 +1,21 @@
name: Feature request
about: Suggest an idea for this project
labels: "Feature Request"
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request for Geyser! Fill out the following form to your best ability to help us understand your feature request and greately improve the change of it getting added.
For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or [the Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues).
- type: textarea
attributes:
label: What feature do you want to see added?
description: A clear and concise description of your feature request.
validations:
required: true
- type: textarea
attributes:
label: Are there any alternatives?
description: List any alternatives you might have tried
validations:
required: true

View File

@ -35,6 +35,8 @@ public class AbstractArrowEntity extends Entity {
public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
setMotion(motion);
}
@Override
@ -47,4 +49,20 @@ public class AbstractArrowEntity extends Entity {
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void setRotation(Vector3f rotation) {
// Ignore the rotation sent by the Java server since the
// Java client calculates the rotation from the motion
}
@Override
public void setMotion(Vector3f motion) {
super.setMotion(motion);
double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ());
float yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ()));
float pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed));
rotation = Vector3f.from(yaw, pitch, yaw);
}
}

View File

@ -28,6 +28,7 @@ package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -105,27 +106,38 @@ public class BoatEntity extends Entity {
metadata.put(EntityData.VARIANT, entityMetadata.getValue());
} else if (entityMetadata.getId() == 11) {
isPaddlingLeft = (boolean) entityMetadata.getValue();
if (!isPaddlingLeft) {
metadata.put(EntityData.ROW_TIME_LEFT, 0f);
}
else {
if (isPaddlingLeft) {
// Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing
// This is an asynchronous method that emulates Bedrock rowing until "false" is sent.
paddleTimeLeft = 0f;
session.getConnector().getGeneralThreadPool().execute(() ->
updateLeftPaddle(session, entityMetadata)
);
if (!this.passengers.isEmpty()) {
// Get the entity by the first stored passenger and convey motion in this manner
Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong());
if (entity != null) {
session.getConnector().getGeneralThreadPool().execute(() ->
updateLeftPaddle(session, entity)
);
}
}
} else {
// Indicate that the row position should be reset
metadata.put(EntityData.ROW_TIME_LEFT, 0.0f);
}
}
else if (entityMetadata.getId() == 12) {
isPaddlingRight = (boolean) entityMetadata.getValue();
if (!isPaddlingRight) {
metadata.put(EntityData.ROW_TIME_RIGHT, 0f);
} else {
if (isPaddlingRight) {
paddleTimeRight = 0f;
session.getConnector().getGeneralThreadPool().execute(() ->
updateRightPaddle(session, entityMetadata)
);
if (!this.passengers.isEmpty()) {
Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong());
if (entity != null) {
session.getConnector().getGeneralThreadPool().execute(() ->
updateRightPaddle(session, entity)
);
}
}
} else {
metadata.put(EntityData.ROW_TIME_RIGHT, 0.0f);
}
} else if (entityMetadata.getId() == 13) {
// Possibly - I don't think this does anything?
@ -135,27 +147,46 @@ public class BoatEntity extends Entity {
super.updateBedrockMetadata(entityMetadata, session);
}
public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) {
@Override
public void updateBedrockMetadata(GeyserSession session) {
super.updateBedrockMetadata(session);
// As these indicate to reset rowing, remove them until it is time to send them out again.
metadata.remove(EntityData.ROW_TIME_LEFT);
metadata.remove(EntityData.ROW_TIME_RIGHT);
}
private void updateLeftPaddle(GeyserSession session, Entity rower) {
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;
metadata.put(EntityData.ROW_TIME_LEFT, paddleTimeLeft);
super.updateBedrockMetadata(entityMetadata, session);
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft);
session.getConnector().getGeneralThreadPool().schedule(() ->
updateLeftPaddle(session, entityMetadata),
updateLeftPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
);
}}
}
}
public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) {
private void updateRightPaddle(GeyserSession session, Entity rower) {
if (isPaddlingRight) {
paddleTimeRight += ROWING_SPEED;
metadata.put(EntityData.ROW_TIME_RIGHT, paddleTimeRight);
super.updateBedrockMetadata(entityMetadata, session);
sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight);
session.getConnector().getGeneralThreadPool().schedule(() ->
updateRightPaddle(session, entityMetadata),
updateRightPaddle(session, rower),
100,
TimeUnit.MILLISECONDS
);
}}
}
}
private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) {
AnimatePacket packet = new AnimatePacket();
packet.setRuntimeEntityId(rower.getGeyserId());
packet.setAction(action);
packet.setRowingTime(rowTime);
session.sendUpstreamPacket(packet);
}
}

View File

@ -274,13 +274,9 @@ public class Entity {
metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers
metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80);
if ((xd & 0x20) == 0x20) {
// Armour stands are handled in their own class
if (!this.is(ArmorStandEntity.class)) {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
}
} else {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
// Armour stands are handled in their own class
if (!this.is(ArmorStandEntity.class)) {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20);
}
// Shield code

View File

@ -26,43 +26,167 @@
package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.connector.GeyserConnector;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket;
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.BoundingBox;
import org.geysermc.connector.network.translators.collision.CollisionManager;
import org.geysermc.connector.network.translators.collision.CollisionTranslator;
import org.geysermc.connector.network.translators.collision.translators.BlockCollision;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
public class FishingHookEntity extends Entity {
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) {
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class FishingHookEntity extends ThrowableEntity {
private boolean hooked = false;
private final BoundingBox boundingBox;
private boolean inWater = false;
public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, PlayerEntity owner) {
super(entityId, geyserId, entityType, position, motion, rotation);
for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) {
Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId());
if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) {
entity = session.getPlayerEntity();
}
this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25);
if (entity != null) {
this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId());
return;
}
}
// In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change.
// This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock,
// so that it can be handled by moveAbsoluteImmediate.
this.metadata.putFloat(EntityData.BOUNDING_BOX_HEIGHT, 128);
this.metadata.put(EntityData.OWNER_EID, owner.getGeyserId());
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 7) {
Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1);
if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) {
if (entityMetadata.getId() == 7) { // Hooked entity
int hookedEntityId = (int) entityMetadata.getValue() - 1;
Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId);
if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) {
entity = session.getPlayerEntity();
}
if (entity != null) {
metadata.put(EntityData.TARGET_EID, entity.getGeyserId());
hooked = true;
} else {
hooked = false;
}
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
boundingBox.setMiddleX(position.getX());
boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2);
boundingBox.setMiddleZ(position.getZ());
CollisionManager collisionManager = session.getCollisionManager();
List<Vector3i> collidableBlocks = collisionManager.getCollidableBlocks(boundingBox);
boolean touchingWater = false;
boolean collided = false;
for (Vector3i blockPos : collidableBlocks) {
if (0 <= blockPos.getY() && blockPos.getY() <= 255) {
int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos);
BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ());
if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) {
// TODO Push bounding box out of collision to improve movement
collided = true;
}
int waterLevel = BlockStateValues.getWaterLevel(blockID);
if (BlockTranslator.isWaterlogged(blockID)) {
waterLevel = 0;
}
if (waterLevel >= 0) {
double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0;
// Falling water is a full block
if (waterLevel >= 8) {
waterMaxY = blockPos.getY() + 1;
}
if (position.getY() <= waterMaxY) {
touchingWater = true;
}
}
}
}
if (!inWater && touchingWater) {
sendSplashSound(session);
}
inWater = touchingWater;
if (!collided) {
super.moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
} else {
super.moveAbsoluteImmediate(session, this.position, rotation, true, true);
}
}
private void sendSplashSound(GeyserSession session) {
if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) {
float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY()));
if (volume > 1) {
volume = 1;
}
PlaySoundPacket playSoundPacket = new PlaySoundPacket();
playSoundPacket.setSound("random.splash");
playSoundPacket.setPosition(position);
playSoundPacket.setVolume(volume);
playSoundPacket.setPitch(1f + ThreadLocalRandom.current().nextFloat() * 0.3f);
session.sendUpstreamPacket(playSoundPacket);
}
}
@Override
public void tick(GeyserSession session) {
if (hooked || !isInAir(session) && !isInWater(session) || isOnGround()) {
motion = Vector3f.ZERO;
return;
}
float gravity = getGravity(session);
motion = motion.down(gravity);
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
float drag = getDrag(session);
motion = motion.mul(drag);
}
@Override
protected float getGravity(GeyserSession session) {
if (!isInWater(session) && !onGround) {
return 0.03f;
}
return 0;
}
/**
* @param session the session of the Bedrock client.
* @return true if this entity is currently in air.
*/
protected boolean isInAir(GeyserSession session) {
if (session.getConnector().getConfig().isCacheChunks()) {
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return block == BlockTranslator.JAVA_AIR_ID;
}
}
return false;
}
@Override
protected float getDrag(GeyserSession session) {
return 0.92f;
}
}

View File

@ -32,19 +32,44 @@ import org.geysermc.connector.network.session.GeyserSession;
public class ItemedFireballEntity extends ThrowableEntity {
private final Vector3f acceleration;
/**
* The number of ticks to advance movement before sending to Bedrock
*/
protected int futureTicks = 3;
public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation);
acceleration = motion;
float magnitude = motion.length();
if (magnitude != 0) {
acceleration = motion.div(magnitude).mul(0.1f);
} else {
acceleration = Vector3f.ZERO;
}
}
private Vector3f tickMovement(GeyserSession session, Vector3f position) {
position = position.add(motion);
float drag = getDrag(session);
motion = motion.add(acceleration).mul(drag);
return position;
}
@Override
protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Advance the position by a few ticks before sending it to Bedrock
Vector3f lastMotion = motion;
Vector3f newPosition = position;
for (int i = 0; i < futureTicks; i++) {
newPosition = tickMovement(session, newPosition);
}
super.moveAbsoluteImmediate(session, newPosition, rotation, isOnGround, teleported);
this.position = position;
this.motion = lastMotion;
}
@Override
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.
// 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);
moveAbsoluteImmediate(session, tickMovement(session, position), rotation, false, false);
}
}

View File

@ -29,20 +29,21 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.network.translators.world.block.BlockStateValues;
/**
* Used as a class for any object-like entity that moves as a projectile
*/
public class ThrowableEntity extends Entity implements Tickable {
private Vector3f lastPosition;
protected Vector3f lastJavaPosition;
public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.lastPosition = position;
this.lastJavaPosition = position;
}
/**
@ -52,22 +53,65 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
@Override
public void tick(GeyserSession session) {
super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround);
moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false);
float drag = getDrag(session);
float gravity = getGravity();
float gravity = getGravity(session);
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);
MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket();
moveEntityDeltaPacket.setRuntimeEntityId(geyserId);
if (isOnGround) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND);
}
setOnGround(isOnGround);
if (teleported) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING);
}
if (this.position.getX() != position.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X);
moveEntityDeltaPacket.setX(position.getX());
}
if (this.position.getY() != position.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y);
moveEntityDeltaPacket.setY(position.getY());
}
if (this.position.getZ() != position.getZ()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z);
moveEntityDeltaPacket.setZ(position.getZ());
}
setPosition(position);
if (this.rotation.getX() != rotation.getX()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW);
moveEntityDeltaPacket.setYaw(rotation.getX());
}
if (this.rotation.getY() != rotation.getY()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH);
moveEntityDeltaPacket.setPitch(rotation.getY());
}
if (this.rotation.getZ() != rotation.getZ()) {
moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW);
moveEntityDeltaPacket.setHeadYaw(rotation.getZ());
}
setRotation(rotation);
if (!moveEntityDeltaPacket.getFlags().isEmpty()) {
session.sendUpstreamPacket(moveEntityDeltaPacket);
}
}
/**
* Get the gravity of this entity type. Used for applying gravity while the entity is in motion.
*
* @param session the session of the Bedrock client.
* @return the amount of gravity to apply to this entity while in motion.
*/
protected float getGravity() {
protected float getGravity(GeyserSession session) {
if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) {
switch (entityType) {
case THROWN_POTION:
@ -76,11 +120,14 @@ public class ThrowableEntity extends Entity implements Tickable {
case THROWN_EXP_BOTTLE:
return 0.07f;
case FIREBALL:
case SHULKER_BULLET:
return 0;
case SNOWBALL:
case THROWN_EGG:
case THROWN_ENDERPEARL:
return 0.03f;
case LLAMA_SPIT:
return 0.06f;
}
}
return 0;
@ -101,11 +148,14 @@ public class ThrowableEntity extends Entity implements Tickable {
case SNOWBALL:
case THROWN_EGG:
case THROWN_ENDERPEARL:
case LLAMA_SPIT:
return 0.99f;
case FIREBALL:
case SMALL_FIREBALL:
case DRAGON_FIREBALL:
return 0.95f;
case SHULKER_BULLET:
return 1;
}
}
return 1;
@ -117,8 +167,10 @@ public class ThrowableEntity extends Entity implements Tickable {
*/
protected boolean isInWater(GeyserSession session) {
if (session.getConnector().getConfig().isCacheChunks()) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return block == BlockTranslator.BEDROCK_WATER_ID;
if (0 <= position.getFloorY() && position.getFloorY() <= 255) {
int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt());
return BlockStateValues.getWaterLevel(block) != -1;
}
}
return false;
}
@ -136,14 +188,13 @@ public class ThrowableEntity extends Entity implements Tickable {
@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
position = lastPosition;
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
lastPosition = position;
moveAbsoluteImmediate(session, lastJavaPosition.add(relX, relY, relZ), rotation, isOnGround, false);
lastJavaPosition = position;
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position, rotation, isOnGround, teleported);
lastPosition = position;
moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported);
lastJavaPosition = position;
}
}

View File

@ -35,6 +35,8 @@ public class WitherSkullEntity extends ItemedFireballEntity {
public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
this.futureTicks = 1;
}
@Override

View File

@ -29,6 +29,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import lombok.Getter;
import org.geysermc.connector.entity.LivingEntity;
import org.geysermc.connector.entity.type.EntityType;
@ -42,15 +45,68 @@ public class ArmorStandEntity extends LivingEntity {
private boolean isInvisible = false;
private boolean isSmall = false;
/**
* On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock.
* By having a second entity, we can allow an invisible entity with the name tag.
* (This lets armor on armor stands still show)
*/
private ArmorStandEntity secondEntity = null;
/**
* Whether this is the primary armor stand that holds the armor and not the name tag.
*/
private boolean primaryEntity = true;
/**
* Whether the entity's position must be updated to included the offset.
*
* This should be true when the Java server marks the armor stand as invisible, but we shrink the entity
* to allow the nametag to appear. Basically:
* - Is visible: this is irrelevant (false)
* - Has armor, no name: false
* - Has armor, has name: false, with a second entity
* - No armor, no name: false
* - No armor, yes name: true
*/
private boolean positionRequiresOffset = false;
/**
* Whether we should update the position of this armor stand after metadata updates.
*/
private boolean positionUpdateRequired = false;
private GeyserSession session;
public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void spawnEntity(GeyserSession session) {
this.session = session;
this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX());
super.spawnEntity(session);
}
@Override
public boolean despawnEntity(GeyserSession session) {
if (secondEntity != null) {
secondEntity.despawnEntity(session);
}
return super.despawnEntity(session);
}
@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
if (secondEntity != null) {
secondEntity.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
}
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands
if (!isMarker && isInvisible && passengers.isEmpty()) {
position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
if (secondEntity != null) {
secondEntity.moveAbsolute(session, applyOffsetToPosition(position), rotation, isOnGround, teleported);
} else if (positionRequiresOffset) {
// Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands
position = applyOffsetToPosition(position);
}
super.moveAbsolute(session, position, Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()), isOnGround, teleported);
@ -58,47 +114,245 @@ public class ArmorStandEntity extends LivingEntity {
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 0 && entityMetadata.getType() == MetadataType.BYTE) {
byte xd = (byte) entityMetadata.getValue();
// Check if the armour stand is invisible and store accordingly
if ((xd & 0x20) == 0x20) {
metadata.put(EntityData.SCALE, 0.0f);
isInvisible = true;
if (primaryEntity) {
isInvisible = (xd & 0x20) == 0x20;
updateSecondEntityStatus(false);
}
} else if (entityMetadata.getId() == 2 || entityMetadata.getId() == 3) {
updateSecondEntityStatus(false);
} else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) {
byte xd = (byte) entityMetadata.getValue();
// isSmall
if ((xd & 0x01) == 0x01) {
isSmall = true;
boolean newIsSmall = (xd & 0x01) == 0x01;
if ((newIsSmall != isSmall) && positionRequiresOffset) {
// Fix new inconsistency with offset
this.position = fixOffsetForSize(position, newIsSmall);
positionUpdateRequired = true;
}
isSmall = newIsSmall;
if (isSmall) {
if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) {
float scale = metadata.getFloat(EntityData.SCALE);
if (scale != 0.55f && scale != 0.0f) {
metadata.put(EntityData.SCALE, 0.55f);
}
if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) {
if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.5f) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f);
}
} else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) {
} else if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.25f) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth());
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight());
}
// setMarker
if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) {
boolean oldIsMarker = isMarker;
isMarker = (xd & 0x10) == 0x10;
if (isMarker) {
metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
isMarker = true;
}
if (oldIsMarker != isMarker) {
updateSecondEntityStatus(false);
}
}
super.updateBedrockMetadata(entityMetadata, session);
if (secondEntity != null) {
secondEntity.updateBedrockMetadata(entityMetadata, session);
}
}
@Override
public void spawnEntity(GeyserSession session) {
this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX());
super.spawnEntity(session);
public void updateBedrockMetadata(GeyserSession session) {
if (secondEntity != null) {
secondEntity.updateBedrockMetadata(session);
}
super.updateBedrockMetadata(session);
if (positionUpdateRequired) {
positionUpdateRequired = false;
updatePosition();
}
}
@Override
public void setHelmet(ItemData helmet) {
super.setHelmet(helmet);
updateSecondEntityStatus(true);
}
@Override
public void setChestplate(ItemData chestplate) {
super.setChestplate(chestplate);
updateSecondEntityStatus(true);
}
@Override
public void setLeggings(ItemData leggings) {
super.setLeggings(leggings);
updateSecondEntityStatus(true);
}
@Override
public void setBoots(ItemData boots) {
super.setBoots(boots);
updateSecondEntityStatus(true);
}
@Override
public void setHand(ItemData hand) {
super.setHand(hand);
updateSecondEntityStatus(true);
}
@Override
public void setOffHand(ItemData offHand) {
super.setOffHand(offHand);
updateSecondEntityStatus(true);
}
/**
* Determine if we need to load or unload the second entity.
*
* @param sendMetadata whether to send a metadata update after a change.
*/
private void updateSecondEntityStatus(boolean sendMetadata) {
// A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows.
if (!primaryEntity) return;
if (!isInvisible || isMarker) {
// It is either impossible to show armor, or the armor stand isn't invisible. We good.
updateOffsetRequirement(false);
if (positionUpdateRequired) {
positionUpdateRequired = false;
updatePosition();
}
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
return;
}
//boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty() || metadata.getByte(EntityData.NAMETAG_ALWAYS_SHOW, (byte) -1) == (byte) 0; - may not be necessary?
boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty();
if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR)
|| !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) {
// If the second entity exists, no need to recreate it.
// We can't stuff this check above or else it'll fall into another else case and delete the second entity
if (secondEntity != null) return;
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
// the metadata as it will hold the name tag.
secondEntity = new ArmorStandEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(),
EntityType.ARMOR_STAND, position, motion, rotation);
secondEntity.primaryEntity = false;
if (!this.positionRequiresOffset) {
// Ensure the offset is applied for the 0 scale
secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position);
}
// Copy metadata
secondEntity.isSmall = isSmall;
secondEntity.getMetadata().putAll(metadata);
// Copy the flags so they aren't the same object in memory
secondEntity.getMetadata().putFlags(metadata.getFlags().copy());
// Guarantee this copy is NOT invisible
secondEntity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
// Scale to 0 to show nametag
secondEntity.getMetadata().put(EntityData.SCALE, 0.0f);
// No bounding box as we don't want to interact with this entity
secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
secondEntity.spawnEntity(session);
// Reset scale of the proper armor stand
this.metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
// Set the proper armor stand to invisible to show armor
this.metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
// Update the position of the armor stand
updateOffsetRequirement(false);
} else if (isNametagEmpty) {
// We can just make an invisible entity
// Reset scale of the proper armor stand
metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
// Set the proper armor stand to invisible to show armor
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
// Update offset
updateOffsetRequirement(false);
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
} else {
// Nametag is not empty and there is no armor
// We don't need to make a new entity
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
metadata.put(EntityData.SCALE, 0.0f);
// As the above is applied, we need an offset
updateOffsetRequirement(true);
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
}
if (sendMetadata) {
this.updateBedrockMetadata(session);
}
}
/**
* @return the selected position with the position offset applied.
*/
private Vector3f applyOffsetToPosition(Vector3f position) {
return position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
}
/**
* @return an adjusted offset for the new small status.
*/
private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) {
position = removeOffsetFromPosition(position);
return position.add(0d, entityType.getHeight() * (isNowSmall ? 0.55d : 1d), 0d);
}
/**
* @return the selected position with the position offset removed.
*/
private Vector3f removeOffsetFromPosition(Vector3f position) {
return position.sub(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
}
/**
* Set the offset to a new value; if it changed, update the position, too.
*/
private void updateOffsetRequirement(boolean newValue) {
if (newValue != positionRequiresOffset) {
this.positionRequiresOffset = newValue;
if (positionRequiresOffset) {
this.position = applyOffsetToPosition(position);
} else {
this.position = removeOffsetFromPosition(position);
}
positionUpdateRequired = true;
}
}
/**
* Updates position without calling movement code.
*/
private void updatePosition() {
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
moveEntityPacket.setPosition(position);
moveEntityPacket.setRotation(Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()));
moveEntityPacket.setOnGround(onGround);
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}
}

View File

@ -118,7 +118,7 @@ public enum EntityType {
TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"),
TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f),
CAT(CatEntity.class, 75, 0.35f, 0.3f),
SHULKER_BULLET(Entity.class, 76, 0.3125f),
SHULKER_BULLET(ThrowableEntity.class, 76, 0.3125f),
FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"),
CHALKBOARD(Entity.class, 78, 0f),
DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f),
@ -145,7 +145,7 @@ public enum EntityType {
MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"),
MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"),
LINGERING_POTION(ThrowableEntity.class, 101, 0f),
LLAMA_SPIT(Entity.class, 102, 0.25f),
LLAMA_SPIT(ThrowableEntity.class, 102, 0.25f),
EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"),
EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"),
VEX(VexEntity.class, 105, 0.8f, 0.4f),

View File

@ -177,24 +177,24 @@ public class CollisionManager {
session.sendUpstreamPacket(movePlayerPacket);
}
public List<Vector3i> getPlayerCollidableBlocks() {
public List<Vector3i> getCollidableBlocks(BoundingBox box) {
List<Vector3i> blocks = new ArrayList<>();
Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(),
playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2),
playerBoundingBox.getMiddleZ());
Vector3d position = Vector3d.from(box.getMiddleX(),
box.getMiddleY() - (box.getSizeY() / 2),
box.getMiddleZ());
// Loop through all blocks that could collide with the player
int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE));
int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE);
// Loop through all blocks that could collide
int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE));
int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE);
// Y extends 0.5 blocks down because of fence hitboxes
int minCollisionY = (int) Math.floor(position.getY() - 0.5);
int maxCollisionY = (int) Math.floor(position.getY() + playerBoundingBox.getSizeY());
int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY());
int minCollisionZ = (int) Math.floor(position.getZ() - ((playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE));
int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE);
int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE));
int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE);
for (int y = minCollisionY; y < maxCollisionY + 1; y++) {
for (int x = minCollisionX; x < maxCollisionX + 1; x++) {
@ -207,6 +207,10 @@ public class CollisionManager {
return blocks;
}
public List<Vector3i> getPlayerCollidableBlocks() {
return getCollidableBlocks(playerBoundingBox);
}
/**
* Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be
* cancelled

View File

@ -0,0 +1,124 @@
/*
* 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.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.utils.FireworkColor;
import org.geysermc.connector.utils.MathUtils;
/**
* Stores common code for firework rockets and firework stars.
*/
public abstract class FireworkBaseTranslator extends NbtItemStackTranslator {
protected CompoundTag translateExplosionToBedrock(CompoundTag explosion, String newName) {
CompoundTag newExplosionData = new CompoundTag(newName);
if (explosion.get("Type") != null) {
newExplosionData.put(new ByteTag("FireworkType", MathUtils.convertByte(explosion.get("Type").getValue())));
}
if (explosion.get("Colors") != null) {
int[] oldColors = (int[]) explosion.get("Colors").getValue();
byte[] colors = new byte[oldColors.length];
int i = 0;
for (int color : oldColors) {
colors[i++] = FireworkColor.fromJavaID(color).getBedrockID();
}
newExplosionData.put(new ByteArrayTag("FireworkColor", colors));
}
if (explosion.get("FadeColors") != null) {
int[] oldColors = (int[]) explosion.get("FadeColors").getValue();
byte[] colors = new byte[oldColors.length];
int i = 0;
for (int color : oldColors) {
colors[i++] = FireworkColor.fromJavaID(color).getBedrockID();
}
newExplosionData.put(new ByteArrayTag("FireworkFade", colors));
}
if (explosion.get("Trail") != null) {
newExplosionData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(explosion.get("Trail").getValue())));
}
if (explosion.get("Flicker") != null) {
newExplosionData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(explosion.get("Flicker").getValue())));
}
return newExplosionData;
}
protected CompoundTag translateExplosionToJava(CompoundTag explosion, String newName) {
CompoundTag newExplosionData = new CompoundTag(newName);
if (explosion.get("FireworkType") != null) {
newExplosionData.put(new ByteTag("Type", MathUtils.convertByte(explosion.get("FireworkType").getValue())));
}
if (explosion.get("FireworkColor") != null) {
byte[] oldColors = (byte[]) explosion.get("FireworkColor").getValue();
int[] colors = new int[oldColors.length];
int i = 0;
for (byte color : oldColors) {
colors[i++] = FireworkColor.fromBedrockID(color).getJavaID();
}
newExplosionData.put(new IntArrayTag("Colors", colors));
}
if (explosion.get("FireworkFade") != null) {
byte[] oldColors = (byte[]) explosion.get("FireworkFade").getValue();
int[] colors = new int[oldColors.length];
int i = 0;
for (byte color : oldColors) {
colors[i++] = FireworkColor.fromBedrockID(color).getJavaID();
}
newExplosionData.put(new IntArrayTag("FadeColors", colors));
}
if (explosion.get("FireworkTrail") != null) {
newExplosionData.put(new ByteTag("Trail", MathUtils.convertByte(explosion.get("FireworkTrail").getValue())));
}
if (explosion.get("FireworkFlicker") != null) {
newExplosionData.put(new ByteTag("Flicker", MathUtils.convertByte(explosion.get("FireworkFlicker").getValue())));
}
return newExplosionData;
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.ByteTag;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.ListTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.utils.MathUtils;
@ItemRemapper
public class FireworkRocketTranslator extends FireworkBaseTranslator {
@Override
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
CompoundTag fireworks = itemTag.get("Fireworks");
if (fireworks == null) {
return;
}
if (fireworks.get("Flight") != null) {
fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())));
}
ListTag explosions = fireworks.get("Explosions");
if (explosions == null) {
return;
}
for (Tag effect : explosions.getValue()) {
CompoundTag effectData = (CompoundTag) effect;
CompoundTag newEffectData = translateExplosionToBedrock(effectData, "");
explosions.remove(effectData);
explosions.add(newEffectData);
}
}
@Override
public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
CompoundTag fireworks = itemTag.get("Fireworks");
if (fireworks == null) {
return;
}
if (fireworks.contains("Flight")) {
fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())));
}
ListTag explosions = fireworks.get("Explosions");
if (explosions == null) {
return;
}
for (Tag effect : explosions.getValue()) {
CompoundTag effectData = (CompoundTag) effect;
CompoundTag newEffectData = translateExplosionToJava(effectData, "");
explosions.remove(effect);
explosions.add(newEffectData);
}
}
@Override
public boolean acceptItem(ItemEntry itemEntry) {
return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier());
}
}

View File

@ -0,0 +1,96 @@
/*
* 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.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.IntArrayTag;
import com.github.steveice10.opennbt.tag.builtin.IntTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
@ItemRemapper
public class FireworkStarTranslator extends FireworkBaseTranslator {
@Override
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
Tag explosion = itemTag.get("Explosion");
if (explosion instanceof CompoundTag) {
CompoundTag newExplosion = translateExplosionToBedrock((CompoundTag) explosion, "FireworksItem");
itemTag.remove("Explosion");
itemTag.put(newExplosion);
Tag color = ((CompoundTag) explosion).get("Colors");
if (color instanceof IntArrayTag) {
// Determine the custom color, if any.
// Mostly replicates Java's own rendering code, as Java determines the final firework star color client-side
// while Bedrock determines it server-side.
int[] colors = ((IntArrayTag) color).getValue();
if (colors.length == 0) {
return;
}
int finalColor;
if (colors.length == 1) {
finalColor = colors[0];
} else {
int r = 0;
int g = 0;
int b = 0;
for (int fireworkColor : colors) {
r += (fireworkColor & (255 << 16)) >> 16;
g += (fireworkColor & (255 << 8)) >> 8;
b += fireworkColor & 255;
}
r /= colors.length;
g /= colors.length;
b /= colors.length;
finalColor = r << 16 | g << 8 | b;
}
itemTag.put(new IntTag("customColor", finalColor));
}
}
}
@Override
public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
Tag explosion = itemTag.get("FireworksItem");
if (explosion instanceof CompoundTag) {
CompoundTag newExplosion = translateExplosionToJava((CompoundTag) explosion, "Explosion");
itemTag.remove("FireworksItem");
itemTag.put(newExplosion);
}
// Remove custom color, if any, since this only exists on Bedrock
itemTag.remove("customColor");
}
@Override
public boolean acceptItem(ItemEntry itemEntry) {
return "minecraft:firework_star".equals(itemEntry.getJavaIdentifier());
}
}

View File

@ -1,164 +0,0 @@
/*
* 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.item.translators.nbt;
import com.github.steveice10.opennbt.tag.builtin.*;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.ItemRemapper;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.NbtItemStackTranslator;
import org.geysermc.connector.utils.FireworkColor;
import org.geysermc.connector.utils.MathUtils;
@ItemRemapper
public class FireworkTranslator extends NbtItemStackTranslator {
@Override
public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("Fireworks")) {
return;
}
CompoundTag fireworks = itemTag.get("Fireworks");
if (fireworks.get("Flight") != null) {
fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())));
}
ListTag explosions = fireworks.get("Explosions");
if (explosions == null) {
return;
}
for (Tag effect : explosions.getValue()) {
CompoundTag effectData = (CompoundTag) effect;
CompoundTag newEffectData = new CompoundTag("");
if (effectData.get("Type") != null) {
newEffectData.put(new ByteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue())));
}
if (effectData.get("Colors") != null) {
int[] oldColors = (int[]) effectData.get("Colors").getValue();
byte[] colors = new byte[oldColors.length];
int i = 0;
for (int color : oldColors) {
colors[i++] = FireworkColor.fromJavaID(color).getBedrockID();
}
newEffectData.put(new ByteArrayTag("FireworkColor", colors));
}
if (effectData.get("FadeColors") != null) {
int[] oldColors = (int[]) effectData.get("FadeColors").getValue();
byte[] colors = new byte[oldColors.length];
int i = 0;
for (int color : oldColors) {
colors[i++] = FireworkColor.fromJavaID(color).getBedrockID();
}
newEffectData.put(new ByteArrayTag("FireworkFade", colors));
}
if (effectData.get("Trail") != null) {
newEffectData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue())));
}
if (effectData.get("Flicker") != null) {
newEffectData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue())));
}
explosions.remove(effect);
explosions.add(newEffectData);
}
}
@Override
public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) {
if (!itemTag.contains("Fireworks")) {
return;
}
CompoundTag fireworks = itemTag.get("Fireworks");
if (fireworks.contains("Flight")) {
fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue())));
}
if (!itemTag.contains("Explosions")) {
return;
}
ListTag explosions = fireworks.get("Explosions");
for (Tag effect : explosions.getValue()) {
CompoundTag effectData = (CompoundTag) effect;
CompoundTag newEffectData = new CompoundTag("");
if (effectData.get("FireworkType") != null) {
newEffectData.put(new ByteTag("Type", MathUtils.convertByte(effectData.get("FireworkType").getValue())));
}
if (effectData.get("FireworkColor") != null) {
byte[] oldColors = (byte[]) effectData.get("FireworkColor").getValue();
int[] colors = new int[oldColors.length];
int i = 0;
for (byte color : oldColors) {
colors[i++] = FireworkColor.fromBedrockID(color).getJavaID();
}
newEffectData.put(new IntArrayTag("Colors", colors));
}
if (effectData.get("FireworkFade") != null) {
byte[] oldColors = (byte[]) effectData.get("FireworkFade").getValue();
int[] colors = new int[oldColors.length];
int i = 0;
for (byte color : oldColors) {
colors[i++] = FireworkColor.fromBedrockID(color).getJavaID();
}
newEffectData.put(new IntArrayTag("FadeColors", colors));
}
if (effectData.get("FireworkTrail") != null) {
newEffectData.put(new ByteTag("Trail", MathUtils.convertByte(effectData.get("FireworkTrail").getValue())));
}
if (effectData.get("FireworkFlicker") != null) {
newEffectData.put(new ByteTag("Flicker", MathUtils.convertByte(effectData.get("FireworkFlicker").getValue())));
}
explosions.remove(effect);
explosions.add(newEffectData);
}
}
@Override
public boolean acceptItem(ItemEntry itemEntry) {
return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier());
}
}

View File

@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.*;
import org.geysermc.connector.entity.player.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -69,8 +70,18 @@ public class JavaSpawnEntityTranslator extends PacketTranslator<ServerSpawnEntit
type, position, motion, rotation, (HangingDirection) packet.getData());
} else if (packet.getType() == EntityType.FISHING_BOBBER) {
// Fishing bobbers need the owner for the line
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
type, position, motion, rotation, (ProjectileData) packet.getData());
int ownerEntityId = ((ProjectileData) packet.getData()).getOwnerId();
Entity owner = session.getEntityCache().getEntityByJavaId(ownerEntityId);
if (owner == null && session.getPlayerEntity().getEntityId() == ownerEntityId) {
owner = session.getPlayerEntity();
}
// Java clients only spawn fishing hooks with a player as its owner
if (owner instanceof PlayerEntity) {
entity = new FishingHookEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),
type, position, motion, rotation, (PlayerEntity) owner);
} else {
return;
}
} else if (packet.getType() == EntityType.BOAT) {
// Initial rotation is incorrect
entity = new BoatEntity(packet.getEntityId(), session.getEntityCache().getNextEntityId().incrementAndGet(),

View File

@ -50,75 +50,77 @@ public class BlockStateValues {
private static final Int2ByteMap SKULL_ROTATIONS = new Int2ByteOpenHashMap();
private static final Int2IntMap SKULL_WALL_DIRECTIONS = new Int2IntOpenHashMap();
private static final Int2ByteMap SHULKERBOX_DIRECTIONS = new Int2ByteOpenHashMap();
private static final Int2IntMap WATER_LEVEL = new Int2IntOpenHashMap();
/**
* Determines if the block state contains Bedrock block information
*
* @param entry The String to JsonNode map used in BlockTranslator
* @param javaId The Java Identifier of the block
* @param javaBlockState the Java Block State of the block
* @param blockData JsonNode of info about the block from blocks.json
*/
public static void storeBlockStateValues(Map.Entry<String, JsonNode> entry, int javaBlockState) {
JsonNode bannerColor = entry.getValue().get("banner_color");
public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) {
JsonNode bannerColor = blockData.get("banner_color");
if (bannerColor != null) {
BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue());
return; // There will never be a banner color and a skull variant
}
JsonNode bedColor = entry.getValue().get("bed_color");
JsonNode bedColor = blockData.get("bed_color");
if (bedColor != null) {
BED_COLORS.put(javaBlockState, (byte) bedColor.intValue());
return;
}
if (entry.getKey().contains("command_block")) {
COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0);
if (javaId.contains("command_block")) {
COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0);
return;
}
if (entry.getValue().get("double_chest_position") != null) {
boolean isX = (entry.getValue().get("x") != null);
boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) ||
(entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean()));
boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left"));
if (blockData.get("double_chest_position") != null) {
boolean isX = (blockData.get("x") != null);
boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) ||
(blockData.get("z") != null && blockData.get("z").asBoolean()));
boolean isLeft = (blockData.get("double_chest_position").asText().contains("left"));
DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft));
return;
}
if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) {
FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", ""));
if (javaId.contains("potted_") || javaId.contains("flower_pot")) {
FLOWER_POT_VALUES.put(javaBlockState, javaId.replace("potted_", ""));
return;
}
if (entry.getKey().startsWith("minecraft:lectern")) {
LECTERN_BOOK_STATES.put(javaBlockState, entry.getKey().contains("has_book=true"));
if (javaId.startsWith("minecraft:lectern")) {
LECTERN_BOOK_STATES.put(javaBlockState, javaId.contains("has_book=true"));
return;
}
JsonNode notePitch = entry.getValue().get("note_pitch");
JsonNode notePitch = blockData.get("note_pitch");
if (notePitch != null) {
NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue());
NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue());
return;
}
if (entry.getKey().contains("piston")) {
if (javaId.contains("piston")) {
// True if extended, false if not
PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true"));
IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky"));
PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true"));
IS_STICKY_PISTON.put(javaBlockState, javaId.contains("sticky"));
return;
}
JsonNode skullVariation = entry.getValue().get("variation");
JsonNode skullVariation = blockData.get("variation");
if (skullVariation != null) {
SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue());
}
JsonNode skullRotation = entry.getValue().get("skull_rotation");
JsonNode skullRotation = blockData.get("skull_rotation");
if (skullRotation != null) {
SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue());
}
if (entry.getKey().contains("wall_skull") || entry.getKey().contains("wall_head")) {
String direction = entry.getKey().substring(entry.getKey().lastIndexOf("facing=") + 7);
if (javaId.contains("wall_skull") || javaId.contains("wall_head")) {
String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7);
int rotation = 0;
switch (direction.substring(0, direction.length() - 1)) {
case "north":
@ -137,10 +139,16 @@ public class BlockStateValues {
SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation);
}
JsonNode shulkerDirection = entry.getValue().get("shulker_direction");
JsonNode shulkerDirection = blockData.get("shulker_direction");
if (shulkerDirection != null) {
BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue());
}
if (javaId.startsWith("minecraft:water")) {
String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1);
int level = Integer.parseInt(strLevel);
WATER_LEVEL.put(javaBlockState, level);
}
}
/**
@ -273,4 +281,15 @@ public class BlockStateValues {
public static byte getShulkerBoxDirection(int state) {
return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1);
}
/**
* Get the level of water from the block state.
* This is used in FishingHookEntity to create splash sounds when the hook hits the water.
*
* @param state BlockState of the block
* @return The water level or -1 if the block isn't water
*/
public static int getWaterLevel(int state) {
return WATER_LEVEL.getOrDefault(state, -1);
}
}

View File

@ -185,7 +185,7 @@ public class BlockTranslator {
JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId);
BlockStateValues.storeBlockStateValues(entry, javaRuntimeId);
BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue());
String cleanJavaIdentifier = entry.getKey().split("\\[")[0];

View File

@ -147,6 +147,12 @@ public class LocaleUtils {
}
}
} catch (IOException ignored) { }
if (clientJarInfo == null) {
// Likely failed to download
GeyserConnector.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null.");
return;
}
targetHash = clientJarInfo.getSha1();
} else {
curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile));
@ -168,9 +174,13 @@ public class LocaleUtils {
return;
}
// Get the hash and download the locale
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
try {
// Get the hash and download the locale
String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash();
WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString());
} catch (Exception e) {
GeyserConnector.getInstance().getLogger().error("Unable to download locale file hash", e);
}
}
/**

View File

@ -88,8 +88,7 @@ public class WebUtils {
}
public static String post(String reqURL, String postContent) throws IOException {
URL url = null;
url = new URL(reqURL);
URL url = new URL(reqURL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "text/plain");