Merge entity mounts branch to master (#589)

* Initial support for entity mounts*

* This only works for viewing other players on mounts/vehicles. Currently, mounting on vehicles through Geyser with bedrock does not work at all, though, you can see other Java players on mounts just fine.

* Fix Bedrock player mounting; add minecart offset

* Remove debug code

* Fix boat animation

* Remove debug code

* Add notice of possible steering flip

* Add translator for PlayerInputPacket

* Upload WIP code for BoatEntity.java

* Add animation for rowing on Bedrock side

* Clean up debug code, start on boat movement

* Add notice about flying horses

* Rename BedrockPlayerInputPacket.java to BedrockPlayerInputTranslator.java

* Delete BedrockPlayerInputPacket.java

* Use Translator Annotation again; Thanks to LegacyGamerHD

* Upload ineffective mount-on-login code

* Upload current changes with no debug code

* Change case where applicable

* Change Integer[] to int[]; Change schedule() to execute()

* Don't use Thread.Sleep() and instead call itself again

* Fix players not being linked on login/chunk load

* Little changes

* Minor improvements/fixes to boats

* Remove empty file

* Fix horse flying.

* Various entity mounting fixes

* Add mounting offsets for skeleton and zombie horses

* Another round of entity mount-related fixes

- Add offsets for skeleton and zombie horses (Thanks to tester DirtNasty)
- Boats can now be placed in survival (Thanks again to tester DirtNasty)
- Boats and minecarts can now shake

* Add translating for ServerVehicleMovePacket

* Cleaning up

* More cleaning up

* Add interactive tag support for mountable entities

* Boats move far more nicely

* Add horse heart visuals

* Update interactive tags

Co-authored-by: RednedEpic <redned235@gmail.com>
This commit is contained in:
Camotoy 2020-05-23 17:39:17 -04:00 committed by GitHub
parent d8d9fb7190
commit 59da87a10f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 599 additions and 14 deletions

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.concurrent.TimeUnit;
public class BoatEntity extends Entity {
private boolean isPaddlingLeft;
private float paddleTimeLeft;
private boolean isPaddlingRight;
private float paddleTimeRight;
// Looks too fast and too choppy with 0.1f, which is how I believe the Microsoftian client handles it
private final float ROWING_SPEED = 0.05f;
public BoatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation.add(0, 0, 90));
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Rotation is basically only called when entering/exiting a boat.
// We don't include the rotation (y) as it causes the boat to appear sideways
super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), Vector3f.from(0, 0, rotation.getZ() + 90), isOnGround, teleported);
}
@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
super.moveRelative(session, relX, relY, relZ, Vector3f.from(0, 0, rotation.getZ()), isOnGround);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
// Time since last hit
if (entityMetadata.getId() == 7) {
metadata.put(EntityData.HURT_TIME, entityMetadata.getValue());
}
// Rocking direction
if (entityMetadata.getId() == 8) {
metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue());
}
// 'Health' in Bedrock, damage taken in Java
if (entityMetadata.getId() == 9) {
// Not exactly health but it makes motion in Bedrock
metadata.put(EntityData.HEALTH, 40 - ((int) (float) entityMetadata.getValue()));
}
if (entityMetadata.getId() == 10) {
metadata.put(EntityData.VARIANT, entityMetadata.getValue());
} else if (entityMetadata.getId() == 11) {
isPaddlingLeft = (boolean) entityMetadata.getValue();
if (!isPaddlingLeft) {
metadata.put(EntityData.PADDLE_TIME_LEFT, 0f);
}
else {
// 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)
);
}
}
else if (entityMetadata.getId() == 12) {
isPaddlingRight = (boolean) entityMetadata.getValue();
if (!isPaddlingRight) {
metadata.put(EntityData.PADDLE_TIME_RIGHT, 0f);
} else {
paddleTimeRight = 0f;
session.getConnector().getGeneralThreadPool().execute(() ->
updateRightPaddle(session, entityMetadata)
);
}
} else if (entityMetadata.getId() == 13) {
// Possibly - I don't think this does anything?
metadata.put(EntityData.BOAT_BUBBLE_TIME, entityMetadata.getValue());
}
super.updateBedrockMetadata(entityMetadata, session);
}
public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) {
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;
metadata.put(EntityData.PADDLE_TIME_LEFT, paddleTimeLeft);
super.updateBedrockMetadata(entityMetadata, session);
session.getConnector().getGeneralThreadPool().schedule(() ->
updateLeftPaddle(session, entityMetadata),
100,
TimeUnit.MILLISECONDS
);
}}
public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) {
if (isPaddlingRight) {
paddleTimeRight += ROWING_SPEED;
metadata.put(EntityData.PADDLE_TIME_RIGHT, paddleTimeRight);
super.updateBedrockMetadata(entityMetadata, session);
session.getConnector().getGeneralThreadPool().schedule(() ->
updateRightPaddle(session, entityMetadata),
100,
TimeUnit.MILLISECONDS
);
}}
}

View file

@ -44,7 +44,6 @@ import com.nukkitx.protocol.bedrock.data.EntityFlag;
import com.nukkitx.protocol.bedrock.data.EntityFlags;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.connector.entity.attribute.Attribute;
@ -85,7 +84,7 @@ public class Entity {
protected boolean valid;
protected LongSet passengers = new LongOpenHashSet();
protected LongOpenHashSet passengers = new LongOpenHashSet();
protected Map<AttributeType, Attribute> attributes = new HashMap<>();
protected EntityDataMap metadata = new EntityDataMap();

View file

@ -25,12 +25,40 @@
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.EntityData;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class MinecartEntity extends Entity {
public MinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
super(entityId, geyserId, entityType, position.add(0d, entityType.getOffset(), 0d), motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 7) {
metadata.put(EntityData.HEALTH, entityMetadata.getValue());
}
// Direction in which the minecart is shaking
if (entityMetadata.getId() == 8) {
metadata.put(EntityData.HURT_DIRECTION, entityMetadata.getValue());
}
// Power in Java, time in Bedrock
if (entityMetadata.getId() == 9) {
metadata.put(EntityData.HURT_TIME, Math.min((int) (float) entityMetadata.getValue(), 15));
}
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
super.moveAbsolute(session, position.add(0d, this.entityType.getOffset(), 0d), rotation, isOnGround, teleported);
}
}

View file

@ -97,6 +97,11 @@ public class PlayerEntity extends LivingEntity {
addPlayerPacket.setPlatformChatId("");
addPlayerPacket.getMetadata().putAll(metadata);
long linkedEntityId = session.getEntityCache().getCachedPlayerEntityLink(entityId);
if (linkedEntityId != -1) {
addPlayerPacket.getEntityLinks().add(new EntityLink(session.getEntityCache().getEntityByJavaId(linkedEntityId).getGeyserId(), geyserId, EntityLink.Type.RIDER, false));
}
valid = true;
session.sendUpstreamPacket(addPlayerPacket);

View file

@ -38,6 +38,7 @@ public enum AttributeType {
MOVEMENT_SPEED("generic.movementSpeed", "minecraft:movement", 0f, 1024f, 0.1f),
FLYING_SPEED("generic.flyingSpeed", "minecraft:movement", 0.0f, 1024.0f, 0.4000000059604645f),
ATTACK_DAMAGE("generic.attackDamage", "minecraft:attack_damage", 0f, 2048f, 1f),
HORSE_JUMP_STRENGTH("horse.jumpStrength", "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f),
// Java Attributes
ARMOR("generic.armor", null, 0f, 30f, 0f),

View file

@ -27,25 +27,69 @@ package org.geysermc.connector.entity.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.Attribute;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.connector.entity.attribute.AttributeType;
import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.AttributeUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class AbstractHorseEntity extends AnimalEntity {
// For updating the horse visual easier
private float health = 20f;
public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 8) {
health = (float) entityMetadata.getValue();
updateBedrockAttributes(session);
}
if (entityMetadata.getId() == 16) {
byte xd = (byte) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x02) == 0x02);
metadata.getFlags().setFlag(EntityFlag.SADDLED, (xd & 0x04) == 0x04);
metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x10) == 0x10);
metadata.getFlags().setFlag(EntityFlag.STANDING, (xd & 0x20) == 0x20);
}
// Needed to control horses
metadata.getFlags().setFlag(EntityFlag.CAN_POWER_JUMP, true);
metadata.getFlags().setFlag(EntityFlag.WASD_CONTROLLED, true);
super.updateBedrockMetadata(entityMetadata, session);
}
@Override
public void updateBedrockAttributes(GeyserSession session) {
if (!valid) return;
float maxHealth = attributes.containsKey(AttributeType.MAX_HEALTH) ? attributes.get(AttributeType.MAX_HEALTH).getValue() : 20f;
List<com.nukkitx.protocol.bedrock.data.Attribute> attributesLocal = new ArrayList<>();
for (Map.Entry<AttributeType, org.geysermc.connector.entity.attribute.Attribute> entry : this.attributes.entrySet()) {
if (!entry.getValue().getType().isBedrockAttribute())
continue;
attributesLocal.add(AttributeUtils.getBedrockAttribute(entry.getValue()));
}
attributesLocal.add(new Attribute("minecraft:health", 0.0f, maxHealth, health, maxHealth));
UpdateAttributesPacket updateAttributesPacket = new UpdateAttributesPacket();
updateAttributesPacket.setRuntimeEntityId(geyserId);
updateAttributesPacket.setAttributes(attributesLocal);
session.sendUpstreamPacket(updateAttributesPacket);
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.connector.entity.living.animal.horse;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
@ -41,8 +42,9 @@ public class HorseEntity extends AbstractHorseEntity {
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 18) {
metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue());
metadata.put(EntityData.MARK_VARIANT, (((int) entityMetadata.getValue()) >> 8) % 5);
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -119,22 +119,22 @@ public enum EntityType {
SNOWBALL(ThrowableEntity.class, 81, 0.25f),
THROWN_EGG(ThrowableEntity.class, 82, 0.25f, 0.25f, 0.25f, 0f, "minecraft:egg"),
PAINTING(PaintingEntity.class, 83, 0f),
MINECART(MinecartEntity.class, 84, 0.7f, 0.98f),
MINECART(MinecartEntity.class, 84, 0.7f, 0.98f, 0.98f, 0.35f),
FIREBALL(ItemedFireballEntity.class, 85, 1.0f),
THROWN_POTION(ThrowableEntity.class, 86, 0.25f, 0.25f, 0.25f, 0f, "minecraft:splash_potion"),
THROWN_ENDERPEARL(ThrowableEntity.class, 87, 0.25f, 0.25f, 0.25f, 0f, "minecraft:ender_pearl"),
LEASH_KNOT(LeashKnotEntity.class, 88, 0.5f, 0.375f),
WITHER_SKULL(Entity.class, 89, 0.3125f),
BOAT(Entity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f),
BOAT(BoatEntity.class, 90, 0.7f, 1.6f, 1.6f, 0.35f),
WITHER_SKULL_DANGEROUS(Entity.class, 91, 0f),
LIGHTNING_BOLT(Entity.class, 93, 0f),
SMALL_FIREBALL(ItemedFireballEntity.class, 94, 0.3125f),
AREA_EFFECT_CLOUD(AreaEffectCloudEntity.class, 95, 0.5f, 1.0f),
MINECART_HOPPER(MinecartEntity.class, 96, 0.7f, 0.98f, 0.98f, 0f, "minecraft:hopper_minecart"),
MINECART_TNT(MinecartEntity.class, 97, 0.7f, 0.98f, 0.98f, 0f, "minecraft:tnt_minecart"),
MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0f, "minecraft:chest_minecart"),
MINECART_HOPPER(MinecartEntity.class, 96, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:hopper_minecart"),
MINECART_TNT(MinecartEntity.class, 97, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:tnt_minecart"),
MINECART_CHEST(MinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:chest_minecart"),
MINECART_COMMAND_BLOCK(MinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0f, "minecraft:command_block_minecart"),
MINECART_COMMAND_BLOCK(MinecartEntity.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),
EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f),

View file

@ -58,6 +58,7 @@ import org.geysermc.common.AuthType;
import org.geysermc.common.window.FormWindow;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.remote.RemoteServer;
@ -157,6 +158,9 @@ public class GeyserSession implements CommandSender {
private boolean manyDimPackets = false;
private ServerRespawnPacket lastDimPacket = null;
@Setter
private Entity ridingVehicleEntity;
@Setter
private int craftSlot = 0;

View file

@ -26,8 +26,6 @@
package org.geysermc.connector.network.session.cache;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.Getter;
import org.geysermc.connector.entity.Entity;
@ -49,6 +47,7 @@ public class EntityCache {
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());
@Getter
private AtomicLong nextEntityId = new AtomicLong(2L);
@ -148,4 +147,12 @@ public class EntityCache {
playerEntities = null;
bossBars = null;
}
public long getCachedPlayerEntityLink(long playerId) {
return cachedPlayerEntityLinks.getOrDefault(playerId, -1);
}
public void addCachedPlayerEntityLink(long playerId, long linkedEntityId) {
cachedPlayerEntityLinks.put(playerId, linkedEntityId);
}
}

View file

@ -31,6 +31,7 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerSwingArmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSteerBoatPacket;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
import java.util.concurrent.TimeUnit;
@ -38,6 +39,9 @@ import java.util.concurrent.TimeUnit;
@Translator(packet = AnimatePacket.class)
public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
private boolean isSteeringLeft;
private boolean isSteeringRight;
@Override
public void translate(AnimatePacket packet, GeyserSession session) {
// Stop the player sending animations before they have fully spawned into the server
@ -54,6 +58,18 @@ public class BedrockAnimateTranslator extends PacketTranslator<AnimatePacket> {
TimeUnit.MILLISECONDS
);
break;
// These two might need to be flipped, but my recommendation is getting moving working first
case ROW_LEFT:
// Packet value is a float of how long one has been rowing, so we convert that into a boolean
isSteeringLeft = packet.getRowingTime() > 0.0;
ClientSteerBoatPacket steerLeftPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft);
session.sendDownstreamPacket(steerLeftPacket);
break;
case ROW_RIGHT:
isSteeringRight = packet.getRowingTime() > 0.0;
ClientSteerBoatPacket steerRightPacket = new ClientSteerBoatPacket(isSteeringRight, isSteeringLeft);
session.sendDownstreamPacket(steerRightPacket);
break;
}
}
}

View file

@ -25,6 +25,8 @@
package org.geysermc.connector.network.translators.bedrock;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@ -32,7 +34,9 @@ import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
import com.nukkitx.protocol.bedrock.packet.InteractPacket;
import org.geysermc.connector.network.translators.item.ItemTranslator;
@ -59,6 +63,58 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
InteractAction.ATTACK, Hand.MAIN_HAND);
session.sendDownstreamPacket(attackPacket);
break;
case LEAVE_VEHICLE:
ClientPlayerStatePacket sneakPacket = new ClientPlayerStatePacket((int) entity.getEntityId(), PlayerState.START_SNEAKING);
session.sendDownstreamPacket(sneakPacket);
session.setRidingVehicleEntity(null);
break;
case MOUSEOVER:
// Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc
if (packet.getRuntimeEntityId() != 0) {
Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId());
if (interactEntity == null)
return;
String interactiveTag;
switch (interactEntity.getEntityType()) {
case PIG:
if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = "action.interact.mount";
} else interactiveTag = "";
break;
case HORSE:
case SKELETON_HORSE:
case ZOMBIE_HORSE:
case DONKEY:
case MULE:
case LLAMA:
case TRADER_LLAMA:
if (interactEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) {
interactiveTag = "action.interact.ride.horse";
} else {
interactiveTag = "action.interact.mount";
}
break;
case BOAT:
interactiveTag = "action.interact.ride.boat";
break;
case MINECART:
interactiveTag = "action.interact.ride.minecart";
break;
default:
return; // No need to process any further since there is no interactive tag
}
session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag);
session.getPlayerEntity().updateBedrockMetadata(session);
} else {
if (!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == null) ||
!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == "")) {
// No interactive tag should be sent
session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG);
session.getPlayerEntity().updateBedrockMetadata(session);
}
}
break;
}
}
}

View file

@ -96,6 +96,13 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
packet.getClickPosition().getX(), packet.getClickPosition().getY(), packet.getClickPosition().getZ(),
false);
session.sendDownstreamPacket(blockPacket);
// Otherwise boats will not be able to be placed in survival
if (packet.getItemInHand() != null && packet.getItemInHand().getId() == ItemTranslator.BOAT) {
ClientPlayerUseItemPacket itemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND);
session.sendDownstreamPacket(itemPacket);
}
Vector3i blockPos = packet.getBlockPosition();
// TODO: Find a better way to do this?
switch (packet.getFace()) {

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*
*/
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientVehicleMovePacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
// Used for horses
@Translator(packet = MoveEntityAbsolutePacket.class)
public class BedrockMoveEntityAbsoluteTranslator extends PacketTranslator<MoveEntityAbsolutePacket> {
@Override
public void translate(MoveEntityAbsolutePacket packet, GeyserSession session) {
ClientVehicleMovePacket clientVehicleMovePacket = new ClientVehicleMovePacket(
packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ(),
packet.getRotation().getY() - 90, packet.getRotation().getX()
);
session.sendDownstreamPacket(clientVehicleMovePacket);
}
}

View file

@ -0,0 +1,21 @@
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientSteerVehiclePacket;
import com.nukkitx.protocol.bedrock.packet.PlayerInputPacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
// Makes minecarts respond to player input
@Translator(packet = PlayerInputPacket.class)
public class BedrockPlayerInputTranslator extends PacketTranslator<PlayerInputPacket> {
@Override
public void translate(PlayerInputPacket packet, GeyserSession session) {
ClientSteerVehiclePacket clientSteerVehiclePacket = new ClientSteerVehiclePacket(
packet.getInputMotion().getX(), packet.getInputMotion().getY(), packet.isJumping(), packet.isSneaking()
);
session.sendDownstreamPacket(clientSteerVehiclePacket);
}
}

View file

@ -48,6 +48,8 @@ public class ItemTranslator {
// Shield ID, used in Entity.java
public static final int SHIELD = 829;
// Boat ID, used in BedrockInventoryTransactionTranslator.java
public static final int BOAT = 333;
public void init() {
Reflections ref = new Reflections("org.geysermc.connector.network.translators.item");

View file

@ -26,6 +26,7 @@
package org.geysermc.connector.network.translators.java.entity;
import org.geysermc.connector.entity.Entity;
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;
@ -42,7 +43,10 @@ public class JavaEntityPositionRotationTranslator extends PacketTranslator<Serve
entity = session.getPlayerEntity();
}
if (entity == null) return;
entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround());
if (entity.getEntityType() == EntityType.BOAT) {
entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw() - 90, packet.getPitch(), packet.isOnGround());
} else {
entity.moveRelative(session, packet.getMoveX(), packet.getMoveY(), packet.getMoveZ(), packet.getYaw(), packet.getPitch(), packet.isOnGround());
}
}
}

View file

@ -67,6 +67,9 @@ public class JavaEntityPropertiesTranslator extends PacketTranslator<ServerEntit
case GENERIC_KNOCKBACK_RESISTANCE:
entity.getAttributes().put(AttributeType.KNOCKBACK_RESISTANCE, AttributeType.KNOCKBACK_RESISTANCE.getAttribute((float) AttributeUtils.calculateValue(attribute)));
break;
case HORSE_JUMP_STRENGTH:
entity.getAttributes().put(AttributeType.HORSE_JUMP_STRENGTH, AttributeType.HORSE_JUMP_STRENGTH.getAttribute((float) AttributeUtils.calculateValue(attribute)));
break;
}
}

View file

@ -0,0 +1,153 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntitySetPassengersPacket;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.EntityData;
import com.nukkitx.protocol.bedrock.data.EntityFlag;
import com.nukkitx.protocol.bedrock.data.EntityLink;
import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.geysermc.connector.entity.Entity;
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 java.util.Arrays;
@Translator(packet = ServerEntitySetPassengersPacket.class)
public class JavaEntitySetPassengersTranslator extends PacketTranslator<ServerEntitySetPassengersPacket> {
@Override
public void translate(ServerEntitySetPassengersPacket packet, GeyserSession session) {
Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId());
if (entity == null) return;
LongOpenHashSet passengers = entity.getPassengers().clone();
boolean rider = true;
for (long passengerId : packet.getPassengerIds()) {
Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId);
if (passengerId == session.getPlayerEntity().getEntityId()) {
passenger = session.getPlayerEntity();
session.setRidingVehicleEntity(entity);
}
// Passenger hasn't loaded in and entity link needs to be set later
if (passenger == null && passengerId != 0) {
session.getEntityCache().addCachedPlayerEntityLink(passengerId, packet.getEntityId());
}
if (passenger == null) {
continue;
}
EntityLink.Type type = rider ? EntityLink.Type.RIDER : EntityLink.Type.PASSENGER;
SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
linkPacket.setEntityLink(new EntityLink(entity.getGeyserId(), passenger.getGeyserId(), type, false));
session.sendUpstreamPacket(linkPacket);
passengers.add(passengerId);
// Head rotation on boats
if (entity.getEntityType() == EntityType.BOAT) {
passenger.getMetadata().put(EntityData.RIDER_ROTATION_LOCKED, (byte) 1);
passenger.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 90f);
passenger.getMetadata().put(EntityData.RIDER_MIN_ROTATION, !passengers.isEmpty() ? -90f : 0f);
} else {
passenger.getMetadata().remove(EntityData.RIDER_ROTATION_LOCKED);
passenger.getMetadata().remove(EntityData.RIDER_MAX_ROTATION);
passenger.getMetadata().remove(EntityData.RIDER_MIN_ROTATION);
}
passenger.updateBedrockMetadata(session);
this.updateOffset(passenger, entity.getEntityType(), session, rider, true);
rider = false;
}
entity.setPassengers(passengers);
for (long passengerId : entity.getPassengers()) {
Entity passenger = session.getEntityCache().getEntityByJavaId(passengerId);
if (passenger == null) {
continue;
}
if (Arrays.stream(packet.getPassengerIds()).noneMatch(id -> id == passengerId)) {
SetEntityLinkPacket linkPacket = new SetEntityLinkPacket();
linkPacket.setEntityLink(new EntityLink(entity.getGeyserId(), passenger.getGeyserId(), EntityLink.Type.REMOVE, false));
session.sendUpstreamPacket(linkPacket);
passengers.remove(passenger.getEntityId());
this.updateOffset(passenger, entity.getEntityType(), session, false, false);
}
}
if (entity.getEntityType() == EntityType.HORSE) {
entity.getMetadata().put(EntityData.RIDER_SEAT_POSITION, Vector3f.from(0.0f, 2.3200102f, -0.2f));
entity.getMetadata().put(EntityData.RIDER_MAX_ROTATION, 181.0f);
entity.updateBedrockMetadata(session);
}
}
private void updateOffset(Entity passenger, EntityType mountType, GeyserSession session, boolean rider, boolean riding) {
// Without these, Bedrock players will find themselves in the floor when mounting
float yOffset = 0;
switch (mountType) {
case BOAT:
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : -0.2f;
break;
case MINECART:
yOffset = passenger.getEntityType() == EntityType.PLAYER ? 1.02001f : 0f;
break;
case DONKEY:
yOffset = 2.1f;
break;
case HORSE:
case SKELETON_HORSE:
case ZOMBIE_HORSE:
case MULE:
yOffset = 2.3f;
break;
case LLAMA:
case TRADER_LLAMA:
yOffset = 2.5f;
break;
case PIG:
yOffset = 1.85001f;
break;
}
Vector3f offset = Vector3f.from(0f, yOffset, 0f);
if (rider) {
offset.add(Vector3f.from(0.2, 0, 0));
} else {
offset.add(Vector3f.from(-0.6, 0, 0));
}
passenger.getMetadata().getFlags().setFlag(EntityFlag.RIDING, riding);
if (riding) {
passenger.getMetadata().put(EntityData.RIDER_SEAT_POSITION, offset);
}
passenger.updateBedrockMetadata(session);
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*
*/
package org.geysermc.connector.network.translators.java.world;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerVehicleMovePacket;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@Translator(packet = ServerVehicleMovePacket.class)
public class JavaVehicleMoveTranslator extends PacketTranslator<ServerVehicleMovePacket> {
@Override
public void translate(ServerVehicleMovePacket packet, GeyserSession session) {
Entity entity = session.getRidingVehicleEntity();
if (entity == null) return;
entity.moveAbsolute(session, Vector3f.from(packet.getX(), packet.getY(), packet.getZ()), packet.getYaw(), packet.getPitch(), false, false);
}
}