Support offhand interactions with entities

This commit is contained in:
Camotoy 2022-02-24 22:49:10 -05:00
parent 7bd5b59565
commit 7d1ec5c41a
No known key found for this signature in database
GPG Key ID: 7EEFB66FE798081F
74 changed files with 1860 additions and 654 deletions

View File

@ -65,9 +65,9 @@ public final class EntityDefinitions {
public static final EntityDefinition<ChickenEntity> CHICKEN;
public static final EntityDefinition<AbstractFishEntity> COD;
public static final EntityDefinition<CommandBlockMinecartEntity> COMMAND_BLOCK_MINECART;
public static final EntityDefinition<AnimalEntity> COW;
public static final EntityDefinition<CowEntity> COW;
public static final EntityDefinition<CreeperEntity> CREEPER;
public static final EntityDefinition<WaterEntity> DOLPHIN;
public static final EntityDefinition<DolphinEntity> DOLPHIN;
public static final EntityDefinition<ChestedHorseEntity> DONKEY;
public static final EntityDefinition<FireballEntity> DRAGON_FIREBALL;
public static final EntityDefinition<ZombieEntity> DROWNED;
@ -132,7 +132,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<ThrowableEntity> SHULKER_BULLET;
public static final EntityDefinition<MonsterEntity> SILVERFISH;
public static final EntityDefinition<SkeletonEntity> SKELETON;
public static final EntityDefinition<AbstractHorseEntity> SKELETON_HORSE;
public static final EntityDefinition<SkeletonHorseEntity> SKELETON_HORSE;
public static final EntityDefinition<SlimeEntity> SLIME;
public static final EntityDefinition<FireballEntity> SMALL_FIREBALL;
public static final EntityDefinition<ThrowableItemEntity> SNOWBALL;
@ -160,7 +160,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<WolfEntity> WOLF;
public static final EntityDefinition<ZoglinEntity> ZOGLIN;
public static final EntityDefinition<ZombieEntity> ZOMBIE;
public static final EntityDefinition<AbstractHorseEntity> ZOMBIE_HORSE;
public static final EntityDefinition<ZombieHorseEntity> ZOMBIE_HORSE;
public static final EntityDefinition<ZombieVillagerEntity> ZOMBIE_VILLAGER;
public static final EntityDefinition<ZombifiedPiglinEntity> ZOMBIFIED_PIGLIN;
@ -459,7 +459,7 @@ public final class EntityDefinitions {
.addTranslator(MetadataType.BOOLEAN, (entity, entityMetadata) -> entity.setFlag(EntityFlag.POWERED, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.addTranslator(MetadataType.BOOLEAN, CreeperEntity::setIgnited)
.build();
DOLPHIN = EntityDefinition.inherited(WaterEntity::new, mobEntityBase)
DOLPHIN = EntityDefinition.inherited(DolphinEntity::new, mobEntityBase)
.type(EntityType.DOLPHIN)
.height(0.6f).width(0.9f)
//TODO check
@ -723,7 +723,7 @@ public final class EntityDefinitions {
.type(EntityType.CHICKEN)
.height(0.7f).width(0.4f)
.build();
COW = EntityDefinition.inherited(AnimalEntity::new, ageableEntityBase)
COW = EntityDefinition.inherited(CowEntity::new, ageableEntityBase)
.type(EntityType.COW)
.height(1.4f).width(0.9f)
.build();
@ -745,14 +745,14 @@ public final class EntityDefinitions {
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BOOLEAN, GoatEntity::setScreamer)
.build();
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase) // TODO remove class
MOOSHROOM = EntityDefinition.inherited(MooshroomEntity::new, ageableEntityBase)
.type(EntityType.MOOSHROOM)
.height(1.4f).width(0.9f)
.addTranslator(MetadataType.STRING, (entity, entityMetadata) -> entity.getDirtyMetadata().put(EntityData.VARIANT, entityMetadata.getValue().equals("brown") ? 1 : 0))
.addTranslator(MetadataType.STRING, MooshroomEntity::setVariant)
.build();
OCELOT = EntityDefinition.inherited(OcelotEntity::new, ageableEntityBase)
.type(EntityType.OCELOT)
.height(0.35f).width(0.3f)
.height(0.7f).width(0.6f)
.addTranslator(MetadataType.BOOLEAN, (ocelotEntity, entityMetadata) -> ocelotEntity.setFlag(EntityFlag.TRUSTING, ((BooleanEntityMetadata) entityMetadata).getPrimitiveValue()))
.build();
PANDA = EntityDefinition.inherited(PandaEntity::new, ageableEntityBase)
@ -783,7 +783,7 @@ public final class EntityDefinitions {
.build();
SHEEP = EntityDefinition.inherited(SheepEntity::new, ageableEntityBase)
.type(EntityType.SHEEP)
.heightAndWidth(0.9f)
.height(1.3f).width(0.9f)
.addTranslator(MetadataType.BYTE, SheepEntity::setSheepFlags)
.build();
STRIDER = EntityDefinition.inherited(StriderEntity::new, ageableEntityBase)
@ -832,11 +832,11 @@ public final class EntityDefinitions {
.height(1.6f).width(1.3965f)
.addTranslator(MetadataType.INT, HorseEntity::setHorseVariant)
.build();
SKELETON_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase)
SKELETON_HORSE = EntityDefinition.inherited(SkeletonHorseEntity::new, abstractHorseEntityBase)
.type(EntityType.SKELETON_HORSE)
.height(1.6f).width(1.3965f)
.build();
ZOMBIE_HORSE = EntityDefinition.inherited(abstractHorseEntityBase.factory(), abstractHorseEntityBase)
ZOMBIE_HORSE = EntityDefinition.inherited(ZombieHorseEntity::new, abstractHorseEntityBase)
.type(EntityType.ZOMBIE_HORSE)
.height(1.6f).width(1.3965f)
.build();

View File

@ -1,293 +0,0 @@
/*
* Copyright (c) 2019-2022 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.geyser.entity;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import lombok.Getter;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.living.MobEntity;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.HorseEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity;
import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import java.util.EnumSet;
import java.util.Set;
public class InteractiveTagManager {
/**
* All entity types that can be leashed on Java Edition
*/
private static final Set<EntityType> LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.AXOLOTL, EntityType.BEE, EntityType.CAT, EntityType.CHICKEN,
EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.GOAT, EntityType.GLOW_SQUID, EntityType.HOGLIN,
EntityType.HORSE, EntityType.SKELETON_HORSE, EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA,
EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG,
EntityType.POLAR_BEAR, EntityType.RABBIT, EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.SQUID, EntityType.STRIDER,
EntityType.WOLF, EntityType.ZOGLIN);
private static final Set<EntityType> SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE,
EntityType.ZOMBIE_HORSE, EntityType.MULE);
/**
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
*
* @param session the Bedrock client session
* @param interactEntity the entity that the client is currently facing.
*/
public static void updateTag(GeyserSession session, Entity interactEntity) {
ItemMapping mapping = session.getPlayerInventory().getItemInHand().getMapping(session);
String javaIdentifierStripped = mapping.getJavaIdentifier().replace("minecraft:", "");
EntityType entityType = interactEntity.getDefinition().entityType();
if (entityType == null) {
// Likely a technical entity; we don't need to worry about this
return;
}
InteractiveTag interactiveTag = InteractiveTag.NONE;
if (interactEntity instanceof MobEntity mobEntity && mobEntity.getLeashHolderBedrockId() == session.getPlayerEntity().getGeyserId()) {
// Unleash the entity
interactiveTag = InteractiveTag.REMOVE_LEASH;
} else if (javaIdentifierStripped.equals("saddle") && !interactEntity.getFlag(EntityFlag.SADDLED) &&
((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(entityType) && interactEntity.getFlag(EntityFlag.TAMED) && !session.isSneaking()) ||
entityType == EntityType.PIG || entityType == EntityType.STRIDER)) {
// Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed)
interactiveTag = InteractiveTag.SADDLE;
} else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null &&
session.getPlayerInventory().getItemInHand().getNbt().contains("display")) {
// Holding a named name tag
interactiveTag = InteractiveTag.NAME;
} else if (interactEntity instanceof MobEntity mobEntity &&javaIdentifierStripped.equals("lead")
&& LEASHABLE_MOB_TYPES.contains(entityType) && mobEntity.getLeashHolderBedrockId() == -1L) {
// Holding a leash and the mob is leashable for sure
// (Plugins can change this behavior so that's something to look into in the far far future)
interactiveTag = InteractiveTag.LEASH;
} else if (interactEntity instanceof AnimalEntity && ((AnimalEntity) interactEntity).canEat(javaIdentifierStripped, mapping)) {
// This animal can be fed
interactiveTag = InteractiveTag.FEED;
} else {
switch (entityType) {
case BOAT:
if (interactEntity.getPassengers().size() < 2) {
interactiveTag = InteractiveTag.BOARD_BOAT;
}
break;
case CAT:
if (interactEntity.getFlag(EntityFlag.TAMED) &&
((CatEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
break;
}
break;
case MOOSHROOM:
// Shear the mooshroom
if (javaIdentifierStripped.equals("shears")) {
interactiveTag = InteractiveTag.MOOSHROOM_SHEAR;
break;
}
// Bowls are acceptable here
else if (javaIdentifierStripped.equals("bowl")) {
interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW;
break;
}
// Fall down to COW as this works on mooshrooms
case COW:
if (javaIdentifierStripped.equals("bucket")) {
// Milk the cow
interactiveTag = InteractiveTag.MILK;
}
break;
case CREEPER:
if (javaIdentifierStripped.equals("flint_and_steel")) {
// Today I learned that you can ignite a creeper with flint and steel! Huh.
interactiveTag = InteractiveTag.IGNITE_CREEPER;
}
break;
case DONKEY:
case LLAMA:
case MULE:
if (interactEntity.getFlag(EntityFlag.TAMED) && !interactEntity.getFlag(EntityFlag.CHESTED)
&& javaIdentifierStripped.equals("chest")) {
// Can attach a chest
interactiveTag = InteractiveTag.ATTACH_CHEST;
break;
}
// Intentional fall-through
case HORSE:
case SKELETON_HORSE:
case TRADER_LLAMA:
case ZOMBIE_HORSE:
boolean tamed = interactEntity.getFlag(EntityFlag.TAMED);
if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || interactEntity.getFlag(EntityFlag.CHESTED))) {
interactiveTag = InteractiveTag.OPEN_CONTAINER;
break;
}
if (!interactEntity.getFlag(EntityFlag.BABY)) {
// Can't ride a baby
if (tamed) {
interactiveTag = InteractiveTag.RIDE_HORSE;
} else if (mapping.getJavaId() == 0) {
// Can't hide an untamed entity without having your hand empty
interactiveTag = InteractiveTag.MOUNT;
}
}
break;
case MINECART:
if (interactEntity.getPassengers().isEmpty()) {
interactiveTag = InteractiveTag.RIDE_MINECART;
}
break;
case CHEST_MINECART:
case COMMAND_BLOCK_MINECART:
case HOPPER_MINECART:
interactiveTag = InteractiveTag.OPEN_CONTAINER;
break;
case PIG:
if (interactEntity.getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.MOUNT;
}
break;
case PIGLIN:
if (!interactEntity.getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) {
interactiveTag = InteractiveTag.BARTER;
}
break;
case SHEEP:
if (!interactEntity.getFlag(EntityFlag.SHEARED)) {
if (javaIdentifierStripped.equals("shears")) {
// Shear the sheep
interactiveTag = InteractiveTag.SHEAR;
} else if (javaIdentifierStripped.contains("_dye")) {
// Dye the sheep
interactiveTag = InteractiveTag.DYE;
}
}
break;
case STRIDER:
if (interactEntity.getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.RIDE_STRIDER;
}
break;
case VILLAGER:
VillagerEntity villager = (VillagerEntity) interactEntity;
if (villager.isCanTradeWith() && !villager.isBaby()) { // Not a nitwit, has a profession and is not a baby
interactiveTag = InteractiveTag.TRADE;
}
break;
case WANDERING_TRADER:
interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably.
break;
case WOLF:
if (javaIdentifierStripped.equals("bone") && !interactEntity.getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame
interactiveTag = InteractiveTag.TAME;
} else if (interactEntity.getFlag(EntityFlag.TAMED) &&
((WolfEntity) interactEntity).getOwnerBedrockId() == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
interactiveTag = interactEntity.getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
break;
case ZOMBIE_VILLAGER:
// We can't guarantee the existence of the weakness effect so we just always show it.
if (javaIdentifierStripped.equals("golden_apple")) {
interactiveTag = InteractiveTag.CURE;
}
break;
default:
break;
}
}
session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue());
session.getPlayerEntity().updateBedrockMetadata();
}
/**
* All interactive tags in enum form. For potential API usage.
*/
public enum InteractiveTag {
NONE((Void) null),
IGNITE_CREEPER("creeper"),
EDIT,
LEAVE_BOAT("exit.boat"),
FEED,
FISH("fishing"),
MILK,
MOOSHROOM_SHEAR("mooshear"),
MOOSHROOM_MILK_STEW("moostew"),
BOARD_BOAT("ride.boat"),
RIDE_MINECART("ride.minecart"),
RIDE_HORSE("ride.horse"),
RIDE_STRIDER("ride.strider"),
SHEAR,
SIT,
STAND,
TALK,
TAME,
DYE,
CURE,
OPEN_CONTAINER("opencontainer"),
CREATE_MAP("createMap"),
TAKE_PICTURE("takepicture"),
SADDLE,
MOUNT,
BOOST,
WRITE,
LEASH,
REMOVE_LEASH("unleash"),
NAME,
ATTACH_CHEST("attachchest"),
TRADE,
POSE_ARMOR_STAND("armorstand.pose"),
EQUIP_ARMOR_STAND("armorstand.equip"),
READ,
WAKE_VILLAGER("wakevillager"),
BARTER;
/**
* The full string that should be passed on to the client.
*/
@Getter
private final String value;
InteractiveTag(Void isNone) {
this.value = "";
}
InteractiveTag(String value) {
this.value = "action.interact." + value;
}
InteractiveTag() {
this.value = "action.interact." + name().toLowerCase();
}
}
}

View File

@ -27,6 +27,7 @@ package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.packet.AnimatePacket;
@ -35,6 +36,8 @@ import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -158,6 +161,27 @@ public class BoatEntity extends Entity {
}
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (session.isSneaking()) {
return InteractiveTag.NONE;
} else if (passengers.size() < 2) {
return InteractiveTag.BOARD_BOAT;
} else {
return InteractiveTag.NONE;
}
}
@Override
public InteractionResult interact(Hand hand) {
if (session.isSneaking()) {
return InteractionResult.PASS;
} else {
// TODO: the client also checks for "out of control" ticks
return InteractionResult.SUCCESS;
}
}
private void updateLeftPaddle(GeyserSession session, Entity rower) {
if (isPaddlingLeft) {
paddleTimeLeft += ROWING_SPEED;

View File

@ -25,10 +25,16 @@
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
@ -55,4 +61,30 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity {
dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getCommandBlockRuntimeId());
dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6);
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (session.canUseCommandBlocks()) {
return InteractiveTag.OPEN_CONTAINER;
} else {
return InteractiveTag.NONE;
}
}
@Override
public InteractionResult interact(Hand hand) {
if (session.canUseCommandBlocks()) {
// Client-side GUI required
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(Vector3i.ZERO);
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.COMMAND_BLOCK);
openPacket.setUniqueEntityId(geyserId);
session.sendUpstreamPacket(openPacket);
return InteractionResult.SUCCESS;
} else {
return InteractionResult.PASS;
}
}
}

View File

@ -30,15 +30,14 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags;
import com.nukkitx.protocol.bedrock.packet.AddEntityPacket;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import com.nukkitx.protocol.bedrock.packet.RemoveEntityPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@ -48,6 +47,8 @@ import org.geysermc.geyser.entity.GeyserDirtyMetadata;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.MathUtils;
import java.util.Collections;
@ -467,12 +468,68 @@ public class Entity {
}
}
public boolean isAlive() {
return this.valid;
}
/**
* Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride")
*/
public final void updateInteractiveTag() {
InteractiveTag tag = InteractiveTag.NONE;
for (Hand hand: EntityUtils.HANDS) {
tag = testInteraction(hand);
if (tag != InteractiveTag.NONE) {
break;
}
}
session.getPlayerEntity().getDirtyMetadata().put(EntityData.INTERACTIVE_TAG, tag.getValue());
session.getPlayerEntity().updateBedrockMetadata();
}
/**
* Test interacting with the given hand to see if we should send a tag to the Bedrock client.
* Should usually mirror {@link #interact(Hand)} without any side effects.
*/
protected InteractiveTag testInteraction(Hand hand) {
return InteractiveTag.NONE;
}
/**
* Simulates interacting with an entity. The code here should mirror Java Edition code to the best of its ability,
* to ensure packet parity as well as functionality parity (such as sound effect responses).
*/
public InteractionResult interact(Hand hand) {
return InteractionResult.PASS;
}
/**
* Simulates interacting with this entity at a specific click point. As of Java Edition 1.18.1, this is only used for armor stands.
*/
public InteractionResult interactAt(Hand hand) {
return InteractionResult.PASS;
}
/**
* Send an entity event of the specified type to the Bedrock player from this entity.
*/
public final void playEntityEvent(EntityEventType type) {
playEntityEvent(type, 0);
}
/**
* Send an entity event of the specified type with the specified data to the Bedrock player from this entity.
*/
public final void playEntityEvent(EntityEventType type, int data) {
EntityEventPacket packet = new EntityEventPacket();
packet.setRuntimeEntityId(geyserId);
packet.setType(type);
packet.setData(data);
session.sendUpstreamPacket(packet);
}
@SuppressWarnings("unchecked")
public <I extends Entity> I as(Class<I> entityClass) {
return entityClass.isInstance(this) ? (I) this : null;
}
public <I extends Entity> boolean is(Class<I> entityClass) {
return entityClass.isInstance(this);
}
}

View File

@ -26,11 +26,13 @@
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.level.block.BlockStateValues;
import org.geysermc.geyser.util.InteractionResult;
import java.util.UUID;
@ -42,6 +44,7 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
}
public void setHasFuel(BooleanEntityMetadata entityMetadata) {
// Note: Java ticks this entity and gives it particles if it has fuel
hasFuel = entityMetadata.getPrimitiveValue();
updateDefaultBlockMetadata();
}
@ -51,4 +54,10 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity {
dirtyMetadata.put(EntityData.DISPLAY_ITEM, session.getBlockMappings().getBedrockBlockId(hasFuel ? BlockStateValues.JAVA_FURNACE_LIT_ID : BlockStateValues.JAVA_FURNACE_ID));
dirtyMetadata.put(EntityData.DISPLAY_OFFSET, 6);
}
@Override
public InteractionResult interact(Hand hand) {
// Always works since you can "push" it this way
return InteractionResult.SUCCESS;
}
}

View File

@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.object.Direction;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
@ -42,6 +43,8 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.item.ItemTranslator;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.UUID;
@ -205,6 +208,11 @@ public class ItemFrameEntity extends Entity {
changed = false;
}
@Override
public InteractionResult interact(Hand hand) {
return InventoryUtils.isEmpty(heldItem) && session.getPlayerInventory().getItemInHand(hand).isEmpty() ? InteractionResult.PASS : InteractionResult.SUCCESS;
}
/**
* Finds the Java entity ID of an item frame from its Bedrock position.
* @param position position of item frame in Bedrock.

View File

@ -25,9 +25,11 @@
package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import java.util.UUID;
@ -38,4 +40,9 @@ public class LeashKnotEntity extends Entity {
super(session, entityId, geyserId, uuid, definition, position.add(0.5f, 0.25f, 0.5f), motion, yaw, pitch, headYaw);
}
@Override
public InteractionResult interact(Hand hand) {
// Un-leashing the knot
return InteractionResult.SUCCESS;
}
}

View File

@ -33,6 +33,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.AttributeData;
@ -48,10 +51,12 @@ import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.AttributeUtils;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.InteractionResult;
import java.util.ArrayList;
import java.util.Collections;
@ -169,6 +174,36 @@ public class LivingEntity extends Entity {
return new AttributeData(GeyserAttributeType.HEALTH.getBedrockIdentifier(), 0f, this.maxHealth, (float) Math.ceil(this.health), this.maxHealth);
}
@Override
public boolean isAlive() {
return this.valid && health > 0f;
}
@Override
public InteractionResult interact(Hand hand) {
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
if (itemStack.getJavaId() == session.getItemMappings().getStoredItems().nameTag()) {
InteractionResult result = checkInteractWithNameTag(itemStack);
if (result.consumesAction()) {
return result;
}
}
return super.interact(hand);
}
/**
* Checks to see if a nametag interaction would go through.
*/
protected final InteractionResult checkInteractWithNameTag(GeyserItemStack itemStack) {
CompoundTag nbt = itemStack.getNbt();
if (nbt != null && nbt.get("display") instanceof CompoundTag displayTag && displayTag.get("Name") instanceof StringTag) {
// The mob shall be named
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
public void updateArmor(GeyserSession session) {
if (!valid) return;

View File

@ -27,10 +27,14 @@ package org.geysermc.geyser.entity.type;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import java.util.UUID;
@ -64,4 +68,39 @@ public class MinecartEntity extends Entity {
// Note: minecart rotation on rails does not care about the actual rotation value
return Vector3f.from(0, yaw, 0);
}
@Override
protected InteractiveTag testInteraction(Hand hand) {
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {
return InteractiveTag.OPEN_CONTAINER;
} else {
if (session.isSneaking()) {
return InteractiveTag.NONE;
} else if (!passengers.isEmpty()) {
// Can't enter if someone is inside
return InteractiveTag.NONE;
} else {
// Attempt to enter
return InteractiveTag.RIDE_MINECART;
}
}
}
@Override
public InteractionResult interact(Hand hand) {
if (definition == EntityDefinitions.CHEST_MINECART || definition == EntityDefinitions.HOPPER_MINECART) {
// Opening the UI of this minecart
return InteractionResult.SUCCESS;
} else {
if (session.isSneaking()) {
return InteractionResult.PASS;
} else if (!passengers.isEmpty()) {
// Can't enter if someone is inside
return InteractionResult.PASS;
} else {
// Attempt to enter
return InteractionResult.SUCCESS;
}
}
}
}

View File

@ -28,8 +28,12 @@ package org.geysermc.geyser.entity.type.living;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AbstractFishEntity extends WaterEntity {
@ -42,4 +46,14 @@ public class AbstractFishEntity extends WaterEntity {
setFlag(EntityFlag.CAN_CLIMB, false);
setFlag(EntityFlag.HAS_GRAVITY, false);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (EntityUtils.attemptToBucket(session, this, itemInHand)) {
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -36,4 +36,9 @@ public class AmbientEntity extends MobEntity {
public AmbientEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
protected boolean canBeLeashed() {
return false;
}
}

View File

@ -28,6 +28,8 @@ package org.geysermc.geyser.entity.type.living;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Rotation;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
@ -39,6 +41,7 @@ import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import java.util.Optional;
import java.util.UUID;
@ -237,6 +240,16 @@ public class ArmorStandEntity extends LivingEntity {
}
}
@Override
public InteractionResult interactAt(Hand hand) {
if (!isMarker && session.getPlayerInventory().getItemInHand(hand).getJavaId() != session.getItemMappings().getStoredItems().nameTag()) {
// Java Edition returns SUCCESS if in spectator mode, but this is overrided with an earlier check on the client
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
}
}
@Override
public void setHelmet(ItemData helmet) {
super.setHelmet(helmet);

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2019-2022 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.geyser.entity.type.living;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class DolphinEntity extends WaterEntity {
public DolphinEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
protected boolean canBeLeashed() {
return true;
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
return InteractiveTag.FEED;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!itemInHand.isEmpty() && session.getTagCache().isFish(itemInHand)) {
// Feed
return InteractionResult.SUCCESS;
}
return super.mobInteract(itemInHand);
}
}

View File

@ -29,8 +29,11 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class IronGolemEntity extends GolemEntity {
@ -42,4 +45,18 @@ public class IronGolemEntity extends GolemEntity {
// Required, or else the overlay is black
dirtyMetadata.put(EntityData.COLOR_2, (byte) 0);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().ironIngot()) {
if (health < maxHealth) {
// Healing the iron golem
return InteractionResult.SUCCESS;
} else {
return InteractionResult.PASS;
}
}
return super.mobInteract(itemInHand);
}
}

View File

@ -26,14 +26,21 @@
package org.geysermc.geyser.entity.type.living;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.LivingEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class MobEntity extends LivingEntity {
@ -62,4 +69,95 @@ public class MobEntity extends LivingEntity {
this.leashHolderBedrockId = bedrockId;
dirtyMetadata.put(EntityData.LEASH_HOLDER_EID, bedrockId);
}
@Override
protected final InteractiveTag testInteraction(Hand hand) {
if (!isAlive()) {
// dead lol
return InteractiveTag.NONE;
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
return InteractiveTag.REMOVE_LEASH;
} else {
GeyserItemStack itemStack = session.getPlayerInventory().getItemInHand(hand);
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
if (itemStack.getJavaId() == storedItems.lead() && canBeLeashed()) {
// We shall leash
return InteractiveTag.LEASH;
} else if (itemStack.getJavaId() == storedItems.nameTag()) {
InteractionResult result = checkInteractWithNameTag(itemStack);
if (result.consumesAction()) {
return InteractiveTag.NAME;
}
}
InteractiveTag tag = testMobInteraction(itemStack);
return tag != InteractiveTag.NONE ? tag : super.testInteraction(hand);
}
}
@Override
public final InteractionResult interact(Hand hand) {
if (!isAlive()) {
// dead lol
return InteractionResult.PASS;
} else if (leashHolderBedrockId == session.getPlayerEntity().getGeyserId()) {
// TODO looks like the client assumes it will go through and removes the attachment itself?
return InteractionResult.SUCCESS;
} else {
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand(hand);
InteractionResult result = checkPriorityInteractions(itemInHand);
if (result.consumesAction()) {
return result;
} else {
InteractionResult mobResult = mobInteract(itemInHand);
return mobResult.consumesAction() ? mobResult : super.interact(hand);
}
}
}
private InteractionResult checkPriorityInteractions(GeyserItemStack itemInHand) {
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
if (itemInHand.getJavaId() == storedItems.lead() && canBeLeashed()) {
// We shall leash
return InteractionResult.SUCCESS;
} else if (itemInHand.getJavaId() == storedItems.nameTag()) {
InteractionResult result = checkInteractWithNameTag(itemInHand);
if (result.consumesAction()) {
return result;
}
} else {
ItemMapping mapping = itemInHand.getMapping(session);
if (mapping.getJavaIdentifier().endsWith("_spawn_egg")) {
// Using the spawn egg on this entity to create a child
return InteractionResult.CONSUME;
}
}
return InteractionResult.PASS;
}
@Nonnull
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return InteractiveTag.NONE;
}
@Nonnull
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return InteractionResult.PASS;
}
protected boolean canBeLeashed() {
return isNotLeashed() && !isEnemy();
}
protected final boolean isNotLeashed() {
return leashHolderBedrockId == -1L;
}
/**
* Returns if the entity is hostile. Used to determine if it can be leashed.
*/
protected boolean isEnemy() {
return false;
}
}

View File

@ -42,4 +42,9 @@ public class SlimeEntity extends MobEntity {
public void setScale(IntEntityMetadata entityMetadata) {
dirtyMetadata.put(EntityData.SCALE, 0.10f + entityMetadata.getPrimitiveValue());
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -29,8 +29,12 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ByteEnti
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SnowGolemEntity extends GolemEntity {
@ -44,4 +48,24 @@ public class SnowGolemEntity extends GolemEntity {
// Handle the visibility of the pumpkin
setFlag(EntityFlag.SHEARED, (xd & 0x10) != 0x10);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) {
// Shearing the snow golem
return InteractiveTag.SHEAR;
}
return InteractiveTag.NONE;
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (session.getItemMappings().getStoredItems().shears() == itemInHand.getJavaId() && isAlive() && !getFlag(EntityFlag.SHEARED)) {
// Shearing the snow golem
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
}

View File

@ -120,6 +120,11 @@ public class SquidEntity extends WaterEntity implements Tickable {
return Vector3f.from(pitch, yaw, yaw);
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
private void checkInWater() {
if (getFlag(EntityFlag.RIDING)) {
inWater = false;

View File

@ -36,4 +36,9 @@ public class WaterEntity extends CreatureEntity {
public WaterEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
protected boolean canBeLeashed() {
return false;
}
}

View File

@ -26,11 +26,17 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AnimalEntity extends AgeableEntity {
@ -39,6 +45,12 @@ public class AnimalEntity extends AgeableEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
public final boolean canEat(GeyserItemStack itemStack) {
ItemMapping mapping = itemStack.getMapping(session);
String handIdentifier = mapping.getJavaIdentifier();
return canEat(handIdentifier.replace("minecraft:", ""), mapping);
}
/**
* @param javaIdentifierStripped the stripped Java identifier of the item that is potential breeding food. For example,
* <code>wheat</code>.
@ -48,4 +60,28 @@ public class AnimalEntity extends AgeableEntity {
// This is what it defaults to. OK.
return javaIdentifierStripped.equals("wheat");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (canEat(itemInHand)) {
return InteractiveTag.FEED;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (canEat(itemInHand)) {
// FEED
if (getFlag(EntityFlag.BABY)) {
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
return InteractionResult.SUCCESS;
} else {
return InteractionResult.CONSUME;
}
}
return super.mobInteract(itemInHand);
}
}

View File

@ -31,9 +31,13 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AxolotlEntity extends AnimalEntity {
@ -63,4 +67,19 @@ public class AxolotlEntity extends AnimalEntity {
protected int getMaxAir() {
return 6000;
}
@Override
protected boolean canBeLeashed() {
return true;
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (EntityUtils.attemptToBucket(session, this, itemInHand)) {
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2022 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.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CowEntity extends AnimalEntity {
public CowEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
return super.testMobInteraction(itemInHand);
}
return InteractiveTag.MILK;
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (getFlag(EntityFlag.BABY) || !itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
return super.mobInteract(itemInHand);
}
session.playSoundEvent(SoundEvent.MILK, position);
return InteractionResult.SUCCESS;
}
}

View File

@ -28,17 +28,20 @@ package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import lombok.Getter;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import javax.annotation.Nonnull;
import java.util.UUID;
public class GoatEntity extends AnimalEntity {
private static final float LONG_JUMPING_HEIGHT = 1.3f * 0.7f;
private static final float LONG_JUMPING_WIDTH = 0.9f * 0.7f;
@Getter
private boolean isScreamer;
public GoatEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
@ -59,4 +62,15 @@ public class GoatEntity extends AnimalEntity {
super.setDimensions(pose);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.BABY) && itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
session.playSoundEvent(isScreamer ? SoundEvent.MILK_SCREAMER : SoundEvent.MILK, position);
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -56,4 +56,14 @@ public class HoglinEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("crimson_fungus");
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -25,15 +25,62 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.ObjectEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class MooshroomEntity extends AnimalEntity {
private boolean isBrown = false;
public MooshroomEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
public void setVariant(ObjectEntityMetadata<String> entityMetadata) {
isBrown = entityMetadata.getValue().equals("brown");
dirtyMetadata.put(EntityData.VARIANT, isBrown ? 1 : 0);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
if (!isBaby()) {
if (itemInHand.getJavaId() == storedItems.bowl()) {
// Stew
return InteractiveTag.MOOSHROOM_MILK_STEW;
} else if (isAlive() && itemInHand.getJavaId() == storedItems.shears()) {
// Shear items
return InteractiveTag.MOOSHROOM_SHEAR;
}
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
StoredItemMappings storedItems = session.getItemMappings().getStoredItems();
boolean isBaby = isBaby();
if (!isBaby && itemInHand.getJavaId() == storedItems.bowl()) {
// Stew
return InteractionResult.SUCCESS;
} else if (!isBaby && isAlive() && itemInHand.getJavaId() == storedItems.shears()) {
// Shear items
return InteractionResult.SUCCESS;
} else if (isBrown && session.getTagCache().isSmallFlower(itemInHand) && itemInHand.getMapping(session).isHasSuspiciousStewEffect()) {
// ?
return InteractionResult.SUCCESS;
}
return super.mobInteract(itemInHand);
}
}

View File

@ -26,10 +26,15 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class OcelotEntity extends AnimalEntity {
@ -42,4 +47,26 @@ public class OcelotEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) {
// Attempt to feed
return InteractiveTag.FEED;
} else {
return super.testMobInteraction(itemInHand);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TRUSTING) && canEat(itemInHand) && session.getPlayerEntity().getPosition().distanceSquared(position) < 9f) {
// Attempt to feed
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -33,14 +33,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;
public class PandaEntity extends AnimalEntity {
private int mainGene;
private int hiddenGene;
private Gene mainGene = Gene.NORMAL;
private Gene hiddenGene = Gene.NORMAL;
public PandaEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@ -61,12 +66,12 @@ public class PandaEntity extends AnimalEntity {
}
public void setMainGene(ByteEntityMetadata entityMetadata) {
mainGene = entityMetadata.getPrimitiveValue();
mainGene = Gene.fromId(entityMetadata.getPrimitiveValue());
updateAppearance();
}
public void setHiddenGene(ByteEntityMetadata entityMetadata) {
hiddenGene = entityMetadata.getPrimitiveValue();
hiddenGene = Gene.fromId(entityMetadata.getPrimitiveValue());
updateAppearance();
}
@ -86,23 +91,81 @@ public class PandaEntity extends AnimalEntity {
return javaIdentifierStripped.equals("bamboo");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (mainGene == Gene.WORRIED && session.isThunder()) {
return InteractiveTag.NONE;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (mainGene == Gene.WORRIED && session.isThunder()) {
// Huh!
return InteractionResult.PASS;
} else if (getFlag(EntityFlag.LAYING_DOWN)) {
// Stop the panda from laying down
// TODO laying up is client-side?
return InteractionResult.SUCCESS;
} else if (canEat(itemInHand)) {
if (getFlag(EntityFlag.BABY)) {
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
}
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
@Override
protected boolean canBeLeashed() {
return false;
}
/**
* Update the panda's appearance, and take into consideration the recessive brown and weak traits that only show up
* when both main and hidden genes match
*/
private void updateAppearance() {
if (mainGene == 4 || mainGene == 5) {
// Main gene is a recessive trait
if (mainGene.isRecessive) {
if (mainGene == hiddenGene) {
// Main and hidden genes match; this is what the panda looks like.
dirtyMetadata.put(EntityData.VARIANT, mainGene);
dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal());
} else {
// Genes have no effect on appearance
dirtyMetadata.put(EntityData.VARIANT, 0);
dirtyMetadata.put(EntityData.VARIANT, Gene.NORMAL.ordinal());
}
} else {
// No need to worry about hidden gene
dirtyMetadata.put(EntityData.VARIANT, mainGene);
dirtyMetadata.put(EntityData.VARIANT, mainGene.ordinal());
}
}
enum Gene {
NORMAL(false),
LAZY(false),
WORRIED(false),
PLAYFUL(false),
BROWN(true),
WEAK(true),
AGGRESSIVE(false);
private static final Gene[] VALUES = values();
private final boolean isRecessive;
Gene(boolean isRecessive) {
this.isRecessive = isRecessive;
}
@Nullable
private static Gene fromId(int id) {
if (id < 0 || id >= VALUES.length) {
return NORMAL;
}
return VALUES[id];
}
}
}

View File

@ -26,10 +26,16 @@
package org.geysermc.geyser.entity.type.living.animal;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class PigEntity extends AnimalEntity {
@ -42,4 +48,37 @@ public class PigEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount
return InteractiveTag.MOUNT;
} else {
InteractiveTag superTag = super.testMobInteraction(itemInHand);
if (superTag != InteractiveTag.NONE) {
return superTag;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction()
? InteractiveTag.SADDLE : InteractiveTag.NONE;
}
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount
return InteractionResult.SUCCESS;
} else {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand);
}
}
}
}

View File

@ -30,19 +30,69 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.ItemUtils;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SheepEntity extends AnimalEntity {
private int color;
public SheepEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
public void setSheepFlags(ByteEntityMetadata entityMetadata) {
byte xd = ((ByteEntityMetadata) entityMetadata).getPrimitiveValue();
byte xd = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.SHEARED, (xd & 0x10) == 0x10);
dirtyMetadata.put(EntityData.COLOR, xd);
color = xd & 15;
dirtyMetadata.put(EntityData.COLOR, (byte) color);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) {
return InteractiveTag.SHEAR;
} else {
InteractiveTag tag = super.testMobInteraction(itemInHand);
if (tag != InteractiveTag.NONE) {
return tag;
} else {
int color = ItemUtils.getDyeColor(itemInHand.getJavaId());
if (canDye(color)) {
return InteractiveTag.DYE;
}
return InteractiveTag.NONE;
}
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().shears()) {
return InteractionResult.CONSUME;
} else {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
int color = ItemUtils.getDyeColor(itemInHand.getJavaId());
if (canDye(color)) {
// Dyeing the sheep
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
}
}
private boolean canDye(int color) {
return color != -1 && color != this.color && !getFlag(EntityFlag.SHEARED);
}
}

View File

@ -30,9 +30,14 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class StriderEntity extends AnimalEntity {
@ -90,4 +95,37 @@ public class StriderEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("warped_fungus");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount Strider
return InteractiveTag.RIDE_STRIDER;
} else {
InteractiveTag tag = super.testMobInteraction(itemInHand);
if (tag != InteractiveTag.NONE) {
return tag;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand).consumesAction()
? InteractiveTag.SADDLE : InteractiveTag.NONE;
}
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (!canEat(itemInHand) && getFlag(EntityFlag.SADDLED) && passengers.isEmpty() && !session.isSneaking()) {
// Mount Strider
return InteractionResult.SUCCESS;
} else {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
return EntityUtils.attemptToSaddle(session, this, itemInHand);
}
}
}
}

View File

@ -52,4 +52,9 @@ public class TurtleEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("seagrass");
}
@Override
protected boolean canBeLeashed() {
return false;
}
}

View File

@ -37,9 +37,13 @@ import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.Set;
import java.util.UUID;
@ -122,4 +126,154 @@ public class AbstractHorseEntity extends AnimalEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
boolean isBaby = isBaby();
if (!isBaby) {
if (getFlag(EntityFlag.TAMED) && session.isSneaking()) {
return InteractiveTag.OPEN_CONTAINER;
}
if (!passengers.isEmpty()) {
return super.testMobInteraction(itemInHand);
}
}
if (!itemInHand.isEmpty()) {
if (canEat(itemInHand)) {
return InteractiveTag.FEED;
}
if (testSaddle(itemInHand)) {
return InteractiveTag.SADDLE;
}
if (!getFlag(EntityFlag.TAMED)) {
// Horse will become mad
return InteractiveTag.NONE;
}
if (testForChest(itemInHand)) {
return InteractiveTag.ATTACH_CHEST;
}
if (additionalTestForInventoryOpen(itemInHand) || !isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) {
// Will open the inventory to be saddled
return InteractiveTag.OPEN_CONTAINER;
}
}
if (isBaby) {
return super.testMobInteraction(itemInHand);
} else {
return InteractiveTag.MOUNT;
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
boolean isBaby = isBaby();
if (!isBaby) {
if (getFlag(EntityFlag.TAMED) && session.isSneaking()) {
// Will open the inventory
return InteractionResult.SUCCESS;
}
if (!passengers.isEmpty()) {
return super.mobInteract(itemInHand);
}
}
if (!itemInHand.isEmpty()) {
if (canEat(itemInHand)) {
if (isBaby) {
playEntityEvent(EntityEventType.BABY_ANIMAL_FEED);
}
return InteractionResult.CONSUME;
}
if (testSaddle(itemInHand)) {
return InteractionResult.SUCCESS;
}
if (!getFlag(EntityFlag.TAMED)) {
// Horse will become mad
return InteractionResult.SUCCESS;
}
if (testForChest(itemInHand)) {
// TODO looks like chest is also handled client side
return InteractionResult.SUCCESS;
}
// Note: yes, this code triggers for llamas too. lol (as of Java Edition 1.18.1)
if (additionalTestForInventoryOpen(itemInHand) || (!isBaby && !getFlag(EntityFlag.SADDLED) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle())) {
// Will open the inventory to be saddled
return InteractionResult.SUCCESS;
}
}
if (isBaby) {
return super.mobInteract(itemInHand);
} else {
// Attempt to mount
// TODO client-set flags sitting standing?
return InteractionResult.SUCCESS;
}
}
protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) {
return isAlive() && !getFlag(EntityFlag.BABY) && getFlag(EntityFlag.TAMED);
}
protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) {
return false;
}
protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) {
return itemInHand.getMapping(session).getJavaIdentifier().endsWith("_horse_armor");
}
/* Just a place to stuff common code for the undead variants without having duplicate code */
protected final InteractiveTag testUndeadHorseInteraction(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TAMED)) {
return InteractiveTag.NONE;
} else if (isBaby()) {
return testMobInteraction(itemInHand);
} else if (session.isSneaking()) {
return InteractiveTag.OPEN_CONTAINER;
} else if (!passengers.isEmpty()) {
return testMobInteraction(itemInHand);
} else {
if (session.getItemMappings().getStoredItems().saddle() == itemInHand.getJavaId()) {
return InteractiveTag.OPEN_CONTAINER;
}
if (testSaddle(itemInHand)) {
return InteractiveTag.SADDLE;
}
return InteractiveTag.RIDE_HORSE;
}
}
protected final InteractionResult undeadHorseInteract(@Nonnull GeyserItemStack itemInHand) {
if (!getFlag(EntityFlag.TAMED)) {
return InteractionResult.PASS;
} else if (isBaby()) {
return mobInteract(itemInHand);
} else if (session.isSneaking()) {
// Opens inventory
return InteractionResult.SUCCESS;
} else if (!passengers.isEmpty()) {
return mobInteract(itemInHand);
} else {
// The client tests for saddle but it doesn't matter for us at this point.
return InteractionResult.SUCCESS;
}
}
}

View File

@ -26,9 +26,12 @@
package org.geysermc.geyser.entity.type.living.animal.horse;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ChestedHorseEntity extends AbstractHorseEntity {
@ -41,4 +44,21 @@ public class ChestedHorseEntity extends AbstractHorseEntity {
protected int getContainerBaseSize() {
return 16;
}
@Override
protected boolean testSaddle(@Nonnull GeyserItemStack itemInHand) {
// Not checked here
return false;
}
@Override
protected boolean testForChest(@Nonnull GeyserItemStack itemInHand) {
return itemInHand.getJavaId() == session.getItemMappings().getStoredItems().chest() && !getFlag(EntityFlag.CHESTED);
}
@Override
protected boolean additionalTestForInventoryOpen(@Nonnull GeyserItemStack itemInHand) {
// Armor won't work on these
return false;
}
}

View File

@ -31,8 +31,8 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.MobArmorEquipmentPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019-2022 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.geyser.entity.type.living.animal.horse;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SkeletonHorseEntity extends AbstractHorseEntity {
public SkeletonHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return testUndeadHorseInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return undeadHorseInteract(itemInHand);
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2019-2022 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.geyser.entity.type.living.animal.horse;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ZombieHorseEntity extends AbstractHorseEntity {
public ZombieHorseEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
return testUndeadHorseInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
return undeadHorseInteract(itemInHand);
}
}

View File

@ -32,9 +32,13 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CatEntity extends TameableEntity {
@ -98,4 +102,28 @@ public class CatEntity extends TameableEntity {
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
boolean tamed = getFlag(EntityFlag.TAMED);
if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Toggle sitting
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
} else {
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractiveTag.NONE : InteractiveTag.FEED;
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
boolean tamed = getFlag(EntityFlag.TAMED);
if (tamed && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
return InteractionResult.SUCCESS;
} else {
// Attempt to feed
return !canEat(itemInHand) || health >= maxHealth && tamed ? InteractionResult.PASS : InteractionResult.SUCCESS;
}
}
}

View File

@ -26,10 +26,15 @@
package org.geysermc.geyser.entity.type.living.animal.tameable;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ParrotEntity extends TameableEntity {
@ -40,6 +45,46 @@ public class ParrotEntity extends TameableEntity {
@Override
public boolean canEat(String javaIdentifierStripped, ItemMapping mapping) {
return javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie");
return false;
}
private boolean isTameFood(String javaIdentifierStripped) {
return javaIdentifierStripped.contains("seeds");
}
private boolean isPoisonousFood(String javaIdentifierStripped) {
return javaIdentifierStripped.equals("cookie");
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", "");
boolean tame = getFlag(EntityFlag.TAMED);
if (!tame && isTameFood(javaIdentifierStripped)) {
return InteractiveTag.FEED;
} else if (isPoisonousFood(javaIdentifierStripped)) {
return InteractiveTag.FEED;
} else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Sitting/standing
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifierStripped = itemInHand.getMapping(session).getJavaIdentifier().replace("minecraft:", "");
boolean tame = getFlag(EntityFlag.TAMED);
if (!tame && isTameFood(javaIdentifierStripped)) {
return InteractionResult.SUCCESS;
} else if (isPoisonousFood(javaIdentifierStripped)) {
return InteractionResult.SUCCESS;
} else if (onGround && tame && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Sitting/standing
return InteractionResult.SUCCESS;
}
return super.mobInteract(itemInHand);
}
}

View File

@ -64,14 +64,21 @@ public class TameableEntity extends AnimalEntity {
Entity entity = session.getEntityCache().getPlayerEntity(entityMetadata.getValue().get());
// Used as both a check since the player isn't in the entity cache and a normal fallback
if (entity == null) {
entity = session.getPlayerEntity();
// Set to tame, but indicate that we are not the player that owns this
ownerBedrockId = Long.MAX_VALUE;
} else {
// Translate to entity ID
ownerBedrockId = entity.getGeyserId();
}
// Translate to entity ID
ownerBedrockId = entity.getGeyserId();
} else {
// Reset
ownerBedrockId = 0L;
}
dirtyMetadata.put(EntityData.OWNER_EID, ownerBedrockId);
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
}

View File

@ -32,9 +32,14 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import org.geysermc.geyser.util.ItemUtils;
import javax.annotation.Nonnull;
import java.util.Set;
import java.util.UUID;
@ -90,4 +95,45 @@ public class WolfEntity extends TameableEntity {
// Cannot be a baby to eat these foods
return WOLF_FOODS.contains(javaIdentifierStripped) && !isBaby();
}
@Override
protected boolean canBeLeashed() {
return !getFlag(EntityFlag.ANGRY) && super.canBeLeashed();
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (getFlag(EntityFlag.ANGRY)) {
return InteractiveTag.NONE;
}
if (itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame
return InteractiveTag.TAME;
} else {
int color = ItemUtils.getDyeColor(itemInHand.getJavaId());
if (color != -1) {
// If this fails, as of Java Edition 1.18.1, you cannot toggle sit/stand
if (color != this.collarColor) {
return InteractiveTag.DYE;
}
} else if (getFlag(EntityFlag.TAMED) && ownerBedrockId == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
return getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
}
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (ownerBedrockId == session.getPlayerEntity().getGeyserId() || getFlag(EntityFlag.TAMED)
|| itemInHand.getMapping(session).getJavaIdentifier().equals("minecraft:bone") && !getFlag(EntityFlag.ANGRY)) {
// Sitting toggle or feeding; not angry
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
}
}
}

View File

@ -26,10 +26,16 @@
package org.geysermc.geyser.entity.type.living.merchant;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.living.AgeableEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class AbstractMerchantEntity extends AgeableEntity {
@ -37,4 +43,37 @@ public class AbstractMerchantEntity extends AgeableEntity {
public AbstractMerchantEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
protected boolean canBeLeashed() {
return false;
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
if (!javaIdentifier.equals("minecraft:villager_spawn_egg")
&& (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING) && ((VillagerEntity) this).isCanTradeWith())) {
// An additional check we know cannot work
if (!isBaby()) {
return InteractiveTag.TRADE;
}
}
return super.testMobInteraction(itemInHand);
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
String javaIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
if (!javaIdentifier.equals("minecraft:villager_spawn_egg")
&& (definition != EntityDefinitions.VILLAGER || !getFlag(EntityFlag.SLEEPING))
&& (definition != EntityDefinitions.WANDERING_TRADER || !getFlag(EntityFlag.BABY))) {
// Trading time
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -33,52 +33,49 @@ import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import lombok.Getter;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Optional;
import java.util.UUID;
public class VillagerEntity extends AbstractMerchantEntity {
/**
* A map of Java profession IDs to Bedrock IDs
*/
public static final Int2IntMap VILLAGER_PROFESSIONS = new Int2IntOpenHashMap();
private static final int[] VILLAGER_PROFESSIONS = new int[15];
/**
* A map of all Java region IDs (plains, savanna...) to Bedrock
*/
public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap();
private static final int[] VILLAGER_REGIONS = new int[7];
static {
// Java villager profession IDs -> Bedrock
VILLAGER_PROFESSIONS.put(0, 0);
VILLAGER_PROFESSIONS.put(1, 8);
VILLAGER_PROFESSIONS.put(2, 11);
VILLAGER_PROFESSIONS.put(3, 6);
VILLAGER_PROFESSIONS.put(4, 7);
VILLAGER_PROFESSIONS.put(5, 1);
VILLAGER_PROFESSIONS.put(6, 2);
VILLAGER_PROFESSIONS.put(7, 4);
VILLAGER_PROFESSIONS.put(8, 12);
VILLAGER_PROFESSIONS.put(9, 5);
VILLAGER_PROFESSIONS.put(10, 13);
VILLAGER_PROFESSIONS.put(11, 14);
VILLAGER_PROFESSIONS.put(12, 3);
VILLAGER_PROFESSIONS.put(13, 10);
VILLAGER_PROFESSIONS.put(14, 9);
VILLAGER_PROFESSIONS[0] = 0;
VILLAGER_PROFESSIONS[1] = 8;
VILLAGER_PROFESSIONS[2] = 11;
VILLAGER_PROFESSIONS[3] = 6;
VILLAGER_PROFESSIONS[4] = 7;
VILLAGER_PROFESSIONS[5] = 1;
VILLAGER_PROFESSIONS[6] = 2;
VILLAGER_PROFESSIONS[7] = 4;
VILLAGER_PROFESSIONS[8] = 12;
VILLAGER_PROFESSIONS[9] = 5;
VILLAGER_PROFESSIONS[10] = 13;
VILLAGER_PROFESSIONS[11] = 14;
VILLAGER_PROFESSIONS[12] = 3;
VILLAGER_PROFESSIONS[13] = 10;
VILLAGER_PROFESSIONS[14] = 9;
VILLAGER_REGIONS.put(0, 1);
VILLAGER_REGIONS.put(1, 2);
VILLAGER_REGIONS.put(2, 0);
VILLAGER_REGIONS.put(3, 3);
VILLAGER_REGIONS.put(4, 4);
VILLAGER_REGIONS.put(5, 5);
VILLAGER_REGIONS.put(6, 6);
VILLAGER_REGIONS[0] = 1;
VILLAGER_REGIONS[1] = 2;
VILLAGER_REGIONS[2] = 0;
VILLAGER_REGIONS[3] = 3;
VILLAGER_REGIONS[4] = 4;
VILLAGER_REGIONS[5] = 5;
VILLAGER_REGIONS[6] = 6;
}
private Vector3i bedPosition;
@ -95,12 +92,12 @@ public class VillagerEntity extends AbstractMerchantEntity {
public void setVillagerData(EntityMetadata<VillagerData, ?> entityMetadata) {
VillagerData villagerData = entityMetadata.getValue();
// Profession
int profession = VILLAGER_PROFESSIONS.get(villagerData.getProfession());
int profession = getBedrockProfession(villagerData.getProfession());
canTradeWith = profession != 14 && profession != 0; // Not a notwit and not professionless
dirtyMetadata.put(EntityData.VARIANT, profession);
//metadata.put(EntityData.SKIN_ID, villagerData.getType()); Looks like this is modified but for any reason?
// Region
dirtyMetadata.put(EntityData.MARK_VARIANT, VILLAGER_REGIONS.get(villagerData.getType()));
dirtyMetadata.put(EntityData.MARK_VARIANT, getBedrockRegion(villagerData.getType()));
// Trade tier - different indexing in Bedrock
dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
}
@ -158,4 +155,12 @@ public class VillagerEntity extends AbstractMerchantEntity {
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}
public static int getBedrockProfession(int javaProfession) {
return javaProfession >= 0 && javaProfession < VILLAGER_PROFESSIONS.length ? VILLAGER_PROFESSIONS[javaProfession] : 0;
}
public static int getBedrockRegion(int javaRegion) {
return javaRegion >= 0 && javaRegion < VILLAGER_REGIONS.length ? VILLAGER_REGIONS[javaRegion] : 0;
}
}

View File

@ -28,10 +28,15 @@ package org.geysermc.geyser.entity.type.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.IntEntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class CreeperEntity extends MonsterEntity {
@ -55,4 +60,26 @@ public class CreeperEntity extends MonsterEntity {
ignitedByFlintAndSteel = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) {
return InteractiveTag.IGNITE_CREEPER;
} else {
return super.testMobInteraction(itemInHand);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().flintAndSteel()) {
// Ignite creeper
session.playSoundEvent(SoundEvent.IGNITE, position);
return InteractionResult.SUCCESS;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -150,6 +150,11 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
return super.despawnEntity();
}
@Override
protected boolean isEnemy() {
return true;
}
@Override
public void tick() {
effectTick();
@ -288,10 +293,6 @@ public class EnderDragonEntity extends MobEntity implements Tickable {
session.sendUpstreamPacket(playSoundPacket);
}
private boolean isAlive() {
return health > 0;
}
private boolean isHovering() {
return phase == 10;
}

View File

@ -44,4 +44,9 @@ public class GhastEntity extends FlyingEntity {
// If the ghast is attacking
dirtyMetadata.put(EntityData.CHARGE_AMOUNT, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -37,4 +37,9 @@ public class MonsterEntity extends CreatureEntity {
public MonsterEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -48,4 +48,9 @@ public class PhantomEntity extends FlyingEntity {
setBoundingBoxHeight(boundsScale * definition.height());
dirtyMetadata.put(EntityData.SCALE, modelScale);
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -30,8 +30,12 @@ import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class PiglinEntity extends BasePiglinEntity {
@ -64,4 +68,30 @@ public class PiglinEntity extends BasePiglinEntity {
super.updateOffHand(session);
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
InteractiveTag tag = super.testMobInteraction(itemInHand);
if (tag != InteractiveTag.NONE) {
return tag;
} else {
return canGiveGoldTo(itemInHand) ? InteractiveTag.BARTER : InteractiveTag.NONE;
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
InteractionResult superResult = super.mobInteract(itemInHand);
if (superResult.consumesAction()) {
return superResult;
} else {
return canGiveGoldTo(itemInHand) ? InteractionResult.SUCCESS : InteractionResult.PASS;
}
}
private boolean canGiveGoldTo(@Nonnull GeyserItemStack itemInHand) {
return !getFlag(EntityFlag.BABY) && itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldIngot() && !getFlag(EntityFlag.ADMIRING);
}
}

View File

@ -65,4 +65,9 @@ public class ShulkerEntity extends GolemEntity {
dirtyMetadata.put(EntityData.VARIANT, Math.abs(color - 15));
}
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -55,4 +55,14 @@ public class ZoglinEntity extends MonsterEntity {
float scale = getFlag(EntityFlag.BABY) ? 0.55f : 1f;
return scale * definition.height();
}
@Override
protected boolean canBeLeashed() {
return isNotLeashed();
}
@Override
protected boolean isEnemy() {
return true;
}
}

View File

@ -33,33 +33,56 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.type.living.merchant.VillagerEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
import javax.annotation.Nonnull;
import java.util.UUID;
public class ZombieVillagerEntity extends ZombieEntity {
private boolean isTransforming;
public ZombieVillagerEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
}
public void setTransforming(BooleanEntityMetadata entityMetadata) {
isTransforming = entityMetadata.getPrimitiveValue();
setFlag(EntityFlag.IS_TRANSFORMING, isTransforming);
setFlag(EntityFlag.IS_TRANSFORMING, entityMetadata.getPrimitiveValue());
setFlag(EntityFlag.SHAKING, isShaking());
}
public void setZombieVillagerData(EntityMetadata<VillagerData, ?> entityMetadata) {
VillagerData villagerData = entityMetadata.getValue();
dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.VILLAGER_PROFESSIONS.get(villagerData.getProfession())); // Actually works properly with the OptionalPack
dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType()));
dirtyMetadata.put(EntityData.VARIANT, VillagerEntity.getBedrockProfession(villagerData.getProfession())); // Actually works properly with the OptionalPack
dirtyMetadata.put(EntityData.MARK_VARIANT, VillagerEntity.getBedrockRegion(villagerData.getType()));
// Used with the OptionalPack
dirtyMetadata.put(EntityData.TRADE_TIER, villagerData.getLevel() - 1);
}
@Override
protected boolean isShaking() {
return isTransforming || super.isShaking();
return getFlag(EntityFlag.IS_TRANSFORMING) || super.isShaking();
}
@Nonnull
@Override
protected InteractiveTag testMobInteraction(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) {
return InteractiveTag.CURE;
} else {
return super.testMobInteraction(itemInHand);
}
}
@Nonnull
@Override
protected InteractionResult mobInteract(@Nonnull GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().goldenApple()) {
// The client doesn't know if the entity has weakness as that's not usually sent over the network
return InteractionResult.CONSUME;
} else {
return super.mobInteract(itemInHand);
}
}
}

View File

@ -25,6 +25,7 @@
package org.geysermc.geyser.inventory;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.GeyserImpl;
@ -61,6 +62,10 @@ public class PlayerInventory extends Inventory {
cursor = newCursor;
}
public GeyserItemStack getItemInHand(@Nonnull Hand hand) {
return hand == Hand.OFF_HAND ? getOffhand() : getItemInHand();
}
public GeyserItemStack getItemInHand() {
if (36 + heldItemSlot > this.size) {
GeyserImpl.getInstance().getLogger().debug("Held item slot was larger than expected!");

View File

@ -41,16 +41,27 @@ public class StoredItemMappings {
private final ItemMapping bamboo;
private final ItemMapping banner;
private final ItemMapping barrier;
private final int bowl;
private final int chest;
private final ItemMapping compass;
private final ItemMapping crossbow;
private final ItemMapping enchantedBook;
private final ItemMapping fishingRod;
private final int flintAndSteel;
private final int goldenApple;
private final int goldIngot;
private final int ironIngot;
private final int lead;
private final ItemMapping lodestoneCompass;
private final ItemMapping milkBucket;
private final int nameTag;
private final ItemMapping powderSnowBucket;
private final ItemMapping playerHead;
private final ItemMapping egg;
private final int saddle;
private final int shears;
private final ItemMapping shield;
private final int waterBucket;
private final ItemMapping wheat;
private final ItemMapping writableBook;
@ -58,16 +69,27 @@ public class StoredItemMappings {
this.bamboo = load(itemMappings, "bamboo");
this.banner = load(itemMappings, "white_banner"); // As of 1.17.10, all banners have the same Bedrock ID
this.barrier = load(itemMappings, "barrier");
this.bowl = load(itemMappings, "bowl").getJavaId();
this.chest = load(itemMappings, "chest").getJavaId();
this.compass = load(itemMappings, "compass");
this.crossbow = load(itemMappings, "crossbow");
this.enchantedBook = load(itemMappings, "enchanted_book");
this.fishingRod = load(itemMappings, "fishing_rod");
this.flintAndSteel = load(itemMappings, "flint_and_steel").getJavaId();
this.goldenApple = load(itemMappings, "golden_apple").getJavaId();
this.goldIngot = load(itemMappings, "gold_ingot").getJavaId();
this.ironIngot = load(itemMappings, "iron_ingot").getJavaId();
this.lead = load(itemMappings, "lead").getJavaId();
this.lodestoneCompass = load(itemMappings, "lodestone_compass");
this.milkBucket = load(itemMappings, "milk_bucket");
this.nameTag = load(itemMappings, "name_tag").getJavaId();
this.powderSnowBucket = load(itemMappings, "powder_snow_bucket");
this.playerHead = load(itemMappings, "player_head");
this.egg = load(itemMappings, "egg");
this.saddle = load(itemMappings, "saddle").getJavaId();
this.shears = load(itemMappings, "shears").getJavaId();
this.shield = load(itemMappings, "shield");
this.waterBucket = load(itemMappings, "water_bucket").getJavaId();
this.wheat = load(itemMappings, "wheat");
this.writableBook = load(itemMappings, "writable_book");
}

View File

@ -38,10 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
import com.nukkitx.protocol.bedrock.v471.Bedrock_v471;
import com.nukkitx.protocol.bedrock.v475.Bedrock_v475;
import com.nukkitx.protocol.bedrock.v486.Bedrock_v486;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.*;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
@ -49,6 +46,8 @@ import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.registry.Registries;
import org.geysermc.geyser.registry.type.*;
import org.geysermc.geyser.util.ItemUtils;
import org.geysermc.geyser.util.collection.FixedInt2IntMap;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@ -84,6 +83,10 @@ public class ItemRegistryPopulator {
throw new AssertionError("Unable to load Java runtime item IDs", e);
}
// We can reduce some operations as Java information is the same across all palette versions
boolean firstMappingsPass = true;
Int2IntMap dyeColors = new FixedInt2IntMap();
/* Load item palette */
for (Map.Entry<String, PaletteVersion> palette : PALETTE_VERSIONS.entrySet()) {
TypeReference<List<PaletteItem>> paletteEntriesType = new TypeReference<>() {};
@ -369,7 +372,8 @@ public class ItemRegistryPopulator {
.bedrockData(mappingItem.getBedrockData())
.bedrockBlockId(bedrockBlockId)
.stackSize(stackSize)
.maxDamage(mappingItem.getMaxDamage());
.maxDamage(mappingItem.getMaxDamage())
.hasSuspiciousStewEffect(mappingItem.isHasSuspiciousStewEffect());
if (mappingItem.getRepairMaterials() != null) {
mappingBuilder = mappingBuilder.repairMaterials(new ObjectOpenHashSet<>(mappingItem.getRepairMaterials()));
@ -417,6 +421,10 @@ public class ItemRegistryPopulator {
itemNames.add(javaIdentifier);
if (firstMappingsPass && mappingItem.getDyeColor() != -1) {
dyeColors.put(itemIndex, mappingItem.getDyeColor());
}
itemIndex++;
}
@ -512,6 +520,10 @@ public class ItemRegistryPopulator {
.build();
Registries.ITEMS.register(palette.getValue().protocolVersion(), itemMappings);
firstMappingsPass = false;
}
ItemUtils.setDyeColors(dyeColors);
}
}

View File

@ -44,4 +44,6 @@ public class GeyserMappingItem {
@JsonProperty("tool_tier") String toolTier;
@JsonProperty("max_damage") int maxDamage = 0;
@JsonProperty("repair_materials") List<String> repairMaterials;
@JsonProperty("has_suspicious_stew_effect") boolean hasSuspiciousStewEffect = false;
@JsonProperty("dye_color") int dyeColor = -1;
}

View File

@ -39,7 +39,7 @@ import java.util.Set;
public class ItemMapping {
public static final ItemMapping AIR = new ItemMapping("minecraft:air", "minecraft:air", 0, 0, 0,
BlockRegistries.BLOCKS.forVersion(MinecraftProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()).getBedrockAirId(),
64, null, null, null, 0, null);
64, null, null, null, 0, null, false);
String javaIdentifier;
String bedrockIdentifier;
@ -63,6 +63,8 @@ public class ItemMapping {
Set<String> repairMaterials;
boolean hasSuspiciousStewEffect;
/**
* Gets if this item is a block.
*

View File

@ -84,7 +84,6 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.connection.GeyserConnection;
import org.geysermc.geyser.command.CommandSender;
import org.geysermc.geyser.configuration.EmoteOffhandWorkaroundOption;
import org.geysermc.geyser.entity.InteractiveTagManager;
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
@ -449,6 +448,9 @@ public class GeyserSession implements GeyserConnection, CommandSender {
*/
private boolean flying = false;
@Setter
private boolean instabuild = false;
/**
* Caches current rain status.
*/
@ -1081,7 +1083,7 @@ public class GeyserSession implements GeyserConnection, CommandSender {
if (mouseoverEntity != null) {
// Horses, etc can change their property depending on if you're sneaking
InteractiveTagManager.updateTag(this, mouseoverEntity);
mouseoverEntity.updateInteractiveTag();
}
}
@ -1531,4 +1533,17 @@ public class GeyserSession implements GeyserConnection, CommandSender {
packet.getFogStack().addAll(this.fogNameSpaces);
sendUpstreamPacket(packet);
}
public boolean canUseCommandBlocks() {
return instabuild && opPermissionLevel >= 2;
}
public void playSoundEvent(SoundEvent sound, Vector3f position) {
LevelSoundEvent2Packet packet = new LevelSoundEvent2Packet();
packet.setPosition(position);
packet.setSound(sound);
packet.setIdentifier(":");
packet.setExtraData(-1);
sendUpstreamPacket(packet);
}
}

View File

@ -28,16 +28,19 @@ package org.geysermc.geyser.session.cache;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.registry.type.BlockMapping;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Map;
/**
* Manages information sent from the {@link ClientboundUpdateTagsPacket}. If that packet is not sent, all lists here
* will remain empty, matching Java Edition behavior.
*/
@ParametersAreNonnullByDefault
public class TagCache {
/* Blocks */
private IntList leaves;
@ -54,9 +57,11 @@ public class TagCache {
/* Items */
private IntList axolotlTemptItems;
private IntList fishes;
private IntList flowers;
private IntList foxFood;
private IntList piglinLoved;
private IntList smallFlowers;
public TagCache() {
// Ensure all lists are non-null
@ -79,9 +84,11 @@ public class TagCache {
Map<String, int[]> itemTags = packet.getTags().get("minecraft:item");
this.axolotlTemptItems = IntList.of(itemTags.get("minecraft:axolotl_tempt_items"));
this.fishes = IntList.of(itemTags.get("minecraft:fishes"));
this.flowers = IntList.of(itemTags.get("minecraft:flowers"));
this.foxFood = IntList.of(itemTags.get("minecraft:fox_food"));
this.piglinLoved = IntList.of(itemTags.get("minecraft:piglin_loved"));
this.smallFlowers = IntList.of(itemTags.get("minecraft:small_flowers"));
// Hack btw
boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1;
@ -105,15 +112,21 @@ public class TagCache {
this.requiresDiamondTool = IntLists.emptyList();
this.axolotlTemptItems = IntLists.emptyList();
this.fishes = IntLists.emptyList();
this.flowers = IntLists.emptyList();
this.foxFood = IntLists.emptyList();
this.piglinLoved = IntLists.emptyList();
this.smallFlowers = IntLists.emptyList();
}
public boolean isAxolotlTemptItem(ItemMapping itemMapping) {
return axolotlTemptItems.contains(itemMapping.getJavaId());
}
public boolean isFish(GeyserItemStack itemStack) {
return fishes.contains(itemStack.getJavaId());
}
public boolean isFlower(ItemMapping mapping) {
return flowers.contains(mapping.getJavaId());
}
@ -126,6 +139,10 @@ public class TagCache {
return piglinLoved.contains(mapping.getJavaId());
}
public boolean isSmallFlower(GeyserItemStack itemStack) {
return smallFlowers.contains(itemStack.getJavaId());
}
public boolean isAxeEffective(BlockMapping blockMapping) {
return axeEffective.contains(blockMapping.getJavaBlockId());
}

View File

@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector2d;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.PlayerFogPacket;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.entity.EntityDefinitions;
@ -38,7 +37,6 @@ import org.geysermc.geyser.entity.type.player.PlayerEntity;
import org.geysermc.geyser.session.GeyserSession;
import javax.annotation.Nonnull;
import java.util.Collections;
public class WorldBorder {
private static final double DEFAULT_WORLD_BORDER_SIZE = 5.9999968E7D;
@ -131,11 +129,14 @@ public class WorldBorder {
}
/**
* @return true as long the entity is within the world limits.
* @return true as long as the player entity is within the world limits.
*/
public boolean isInsideBorderBoundaries() {
Vector3f entityPosition = session.getPlayerEntity().getPosition();
return entityPosition.getX() > minX && entityPosition.getX() < maxX && entityPosition.getZ() > minZ && entityPosition.getZ() < maxZ;
return isInsideBorderBoundaries(session.getPlayerEntity().getPosition());
}
public boolean isInsideBorderBoundaries(Vector3f position) {
return position.getX() > minX && position.getX() < maxX && position.getZ() > minZ && position.getZ() < maxZ;
}
/**

View File

@ -33,10 +33,7 @@ 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.PlayerAction;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClickPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket;
import com.github.steveice10.mc.protocol.packet.ingame.serverbound.player.*;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.LevelEventType;
@ -45,7 +42,6 @@ import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.ItemFrameEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
@ -58,8 +54,9 @@ import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
import org.geysermc.geyser.util.BlockUtils;
import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.List;
@ -151,14 +148,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
if (session.getBlockMappings().isItemFrame(packet.getBlockRuntimeId())) {
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
if (itemFrameEntity != null) {
int entityId = itemFrameEntity.getEntityId();
Vector3f vector = packet.getClickPosition();
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(entityId,
InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking());
ServerboundInteractPacket interactAtPacket = new ServerboundInteractPacket(entityId,
InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamPacket(interactPacket);
session.sendDownstreamPacket(interactAtPacket);
processEntityInteraction(session, packet, itemFrameEntity);
break;
}
}
@ -397,27 +387,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
//https://wiki.vg/Protocol#Interact_Entity
switch (packet.getActionType()) {
case 0: //Interact
if (entity instanceof CommandBlockMinecartEntity) {
// The UI is handled client-side on Java Edition
// Ensure OP permission level and gamemode is appropriate
if (session.getOpPermissionLevel() < 2 || session.getGameMode() != GameMode.CREATIVE) return;
ContainerOpenPacket openPacket = new ContainerOpenPacket();
openPacket.setBlockPosition(Vector3i.ZERO);
openPacket.setId((byte) 1);
openPacket.setType(ContainerType.COMMAND_BLOCK);
openPacket.setUniqueEntityId(entity.getGeyserId());
session.sendUpstreamPacket(openPacket);
break;
}
Vector3f vector = packet.getClickPosition().sub(entity.getPosition());
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(entity.getEntityId(),
InteractAction.INTERACT, Hand.MAIN_HAND, session.isSneaking());
ServerboundInteractPacket interactAtPacket = new ServerboundInteractPacket(entity.getEntityId(),
InteractAction.INTERACT_AT, vector.getX(), vector.getY(), vector.getZ(), Hand.MAIN_HAND, session.isSneaking());
session.sendDownstreamPacket(interactPacket);
session.sendDownstreamPacket(interactAtPacket);
EntitySoundInteractionTranslator.handleEntityInteraction(session, packet.getClickPosition(), entity);
processEntityInteraction(session, packet, entity);
break;
case 1: //Attack
if (entity.getDefinition() == EntityDefinitions.ENDER_DRAGON) {
@ -437,6 +407,46 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}
}
private void processEntityInteraction(GeyserSession session, InventoryTransactionPacket packet, Entity entity) {
Vector3f entityPosition = entity.getPosition();
if (!session.getWorldBorder().isInsideBorderBoundaries(entityPosition)) {
// No transaction is able to go through (as of Java Edition 1.18.1)
return;
}
Vector3f clickPosition = packet.getClickPosition().sub(entityPosition);
boolean isSpectator = session.getGameMode() == GameMode.SPECTATOR;
for (Hand hand : EntityUtils.HANDS) {
session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(),
InteractAction.INTERACT_AT, clickPosition.getX(), clickPosition.getY(), clickPosition.getZ(),
hand, session.isSneaking()));
InteractionResult result;
if (isSpectator) {
result = InteractionResult.PASS;
} else {
result = entity.interactAt(hand);
}
if (!result.consumesAction()) {
session.sendDownstreamPacket(new ServerboundInteractPacket(entity.getEntityId(),
InteractAction.INTERACT, hand, session.isSneaking()));
if (!isSpectator) {
result = entity.interact(hand);
}
}
if (result.consumesAction()) {
if (result.shouldSwing() && hand == Hand.OFF_HAND) {
// Currently, Bedrock will send us the arm swing packet in most cases. But it won't for offhand.
session.sendDownstreamPacket(new ServerboundSwingPacket(hand));
// Note here to look into sending the animation packet back to Bedrock
}
return;
}
}
}
/**
* Restore the correct block state from the server without updating the chunk cache.
*

View File

@ -34,7 +34,6 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.util.CooldownUtils;
import org.geysermc.geyser.entity.InteractiveTagManager;
import java.util.concurrent.TimeUnit;
@ -70,7 +69,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
// Update the interactive tag, if an entity is present
if (session.getMouseoverEntity() != null) {
InteractiveTagManager.updateTag(session, session.getMouseoverEntity());
session.getMouseoverEntity().updateInteractiveTag();
}
}
}

View File

@ -40,7 +40,6 @@ import org.geysermc.geyser.entity.type.living.animal.horse.AbstractHorseEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.geyser.entity.InteractiveTagManager;
@Translator(packet = InteractPacket.class)
public class BedrockInteractTranslator extends PacketTranslator<InteractPacket> {
@ -84,7 +83,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
return;
}
InteractiveTagManager.updateTag(session, interactEntity);
interactEntity.updateInteractiveTag();
} else {
if (session.getMouseoverEntity() != null) {
// No interactive tag should be sent

View File

@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.InteractiveTagManager;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -60,8 +59,9 @@ public class JavaSetEntityDataTranslator extends PacketTranslator<ClientboundSet
entity.updateBedrockMetadata();
// Update the interactive tag, if necessary
if (session.getMouseoverEntity() != null && session.getMouseoverEntity().getEntityId() == entity.getEntityId()) {
InteractiveTagManager.updateTag(session, entity);
Entity mouseoverEntity = session.getMouseoverEntity();
if (mouseoverEntity != null && mouseoverEntity.getEntityId() == entity.getEntityId()) {
mouseoverEntity.updateInteractiveTag();
}
}
}

View File

@ -37,6 +37,7 @@ public class JavaPlayerAbilitiesTranslator extends PacketTranslator<ClientboundP
public void translate(GeyserSession session, ClientboundPlayerAbilitiesPacket packet) {
session.setCanFly(packet.isCanFly());
session.setFlying(packet.isFlying());
session.setInstabuild(packet.isCreative());
session.sendAdventureSettings();
}
}

View File

@ -1,93 +0,0 @@
/*
* Copyright (c) 2019-2022 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.geyser.translator.sound;
import com.nukkitx.math.vector.Vector3f;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.registry.Registries;
import java.util.Map;
/**
* Sound interaction handler for when an entity is right-clicked.
*/
public interface EntitySoundInteractionTranslator extends SoundInteractionTranslator<Entity> {
/**
* Handles the block interaction when a player
* right-clicks an entity.
*
* @param session the session interacting with the block
* @param position the position of the block
* @param entity the entity interacted with
*/
static void handleEntityInteraction(GeyserSession session, Vector3f position, Entity entity) {
// If we need to get the hand identifier, only get it once and save it to a variable
String handIdentifier = null;
for (Map.Entry<SoundTranslator, SoundInteractionTranslator<?>> interactionEntry : Registries.SOUND_TRANSLATORS.get().entrySet()) {
if (!(interactionEntry.getValue() instanceof EntitySoundInteractionTranslator)) {
continue;
}
if (interactionEntry.getKey().entities().length != 0) {
boolean contains = false;
for (String entityIdentifier : interactionEntry.getKey().entities()) {
if (entity.getDefinition().entityType().name().toLowerCase().contains(entityIdentifier)) {
contains = true;
break;
}
}
if (!contains) continue;
}
GeyserItemStack itemInHand = session.getPlayerInventory().getItemInHand();
if (interactionEntry.getKey().items().length != 0) {
if (itemInHand.isEmpty()) {
continue;
}
if (handIdentifier == null) {
// Don't get the identifier unless we need it
handIdentifier = itemInHand.getMapping(session).getJavaIdentifier();
}
boolean contains = false;
for (String itemIdentifier : interactionEntry.getKey().items()) {
if (handIdentifier.contains(itemIdentifier)) {
contains = true;
break;
}
}
if (!contains) continue;
}
if (session.isSneaking() && !interactionEntry.getKey().ignoreSneakingWhileHolding()) {
if (!itemInHand.isEmpty()) {
continue;
}
}
((EntitySoundInteractionTranslator) interactionEntry.getValue()).translate(session, position, entity);
}
}
}

View File

@ -54,17 +54,6 @@ public @interface SoundTranslator {
*/
String[] items() default {};
/**
* The identifier(s) that the interacted entity must have.
* Leave empty to ignore.
*
* Only applies to interaction handlers that are an
* instance of {@link EntitySoundInteractionTranslator}.
*
* @return the value the item in the player's hand must contain
*/
String[] entities() default {};
/**
* Controls if the interaction should still be
* called even if the player is sneaking while

View File

@ -1,57 +0,0 @@
/*
* Copyright (c) 2019-2022 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.geyser.translator.sound.entity;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.entity.type.living.animal.OcelotEntity;
import org.geysermc.geyser.entity.type.living.animal.tameable.CatEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
import org.geysermc.geyser.translator.sound.SoundTranslator;
@SoundTranslator
public class FeedBabySoundInteractionTranslator implements EntitySoundInteractionTranslator {
@Override
public void translate(GeyserSession session, Vector3f position, Entity entity) {
if (entity instanceof AnimalEntity animalEntity && !(entity instanceof CatEntity || entity instanceof OcelotEntity)) {
String handIdentifier = session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier();
boolean isBaby = animalEntity.isBaby();
if (isBaby && animalEntity.canEat(handIdentifier.replace("minecraft:", ""),
session.getPlayerInventory().getItemInHand().getMapping(session))) {
// Play the "feed child" effect
EntityEventPacket feedEvent = new EntityEventPacket();
feedEvent.setRuntimeEntityId(entity.getGeyserId());
feedEvent.setType(EntityEventType.BABY_ANIMAL_FEED);
session.sendUpstreamPacket(feedEvent);
}
}
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright (c) 2019-2022 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.geyser.translator.sound.entity;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.living.animal.GoatEntity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.sound.EntitySoundInteractionTranslator;
import org.geysermc.geyser.translator.sound.SoundTranslator;
@SoundTranslator(entities = {"cow", "goat"}, items = "bucket")
public class MilkEntitySoundInteractionTranslator implements EntitySoundInteractionTranslator {
@Override
public void translate(GeyserSession session, Vector3f position, Entity value) {
if (!session.getPlayerInventory().getItemInHand().getMapping(session).getJavaIdentifier().equals("minecraft:bucket")) {
return;
}
if (value.getFlag(EntityFlag.BABY)) {
return;
}
SoundEvent milkSound;
if (value instanceof GoatEntity && ((GoatEntity) value).isScreamer()) {
milkSound = SoundEvent.MILK_SCREAMER;
} else {
milkSound = SoundEvent.MILK;
}
LevelSoundEventPacket levelSoundEventPacket = new LevelSoundEventPacket();
levelSoundEventPacket.setPosition(position);
levelSoundEventPacket.setBabySound(false);
levelSoundEventPacket.setRelativeVolumeDisabled(false);
levelSoundEventPacket.setIdentifier(":");
levelSoundEventPacket.setSound(milkSound);
levelSoundEventPacket.setExtraData(-1);
session.sendUpstreamPacket(levelSoundEventPacket);
}
}

View File

@ -26,18 +26,25 @@
package org.geysermc.geyser.util;
import com.github.steveice10.mc.protocol.data.game.entity.Effect;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand;
import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.entity.type.living.ArmorStandEntity;
import org.geysermc.geyser.entity.type.living.animal.AnimalEntity;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Locale;
public final class EntityUtils {
/**
* A constant array of the two hands that a player can interact with an entity.
*/
public static final Hand[] HANDS = Hand.values();
/**
* @return a new String array of all known effect identifiers
@ -197,6 +204,30 @@ public final class EntityUtils {
}
}
/**
* Determine if an action would result in a successful bucketing of the given entity.
*/
public static boolean attemptToBucket(GeyserSession session, Entity entityToBucket, GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().waterBucket() && entityToBucket.isAlive()) {
//TODO check bucket sound
return true;
}
return false;
}
/**
* Attempt to determine the result of saddling the given entity.
*/
public static InteractionResult attemptToSaddle(GeyserSession session, Entity entityToSaddle, GeyserItemStack itemInHand) {
if (itemInHand.getJavaId() == session.getItemMappings().getStoredItems().saddle()) {
if (entityToSaddle.isAlive() && !entityToSaddle.getFlag(EntityFlag.SADDLED) && !entityToSaddle.getFlag(EntityFlag.BABY)) {
// Saddle
return InteractionResult.SUCCESS;
}
}
return InteractionResult.PASS;
}
private EntityUtils() {
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2019-2022 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.geyser.util;
/**
* Used as a mirror of Java Edition's own interaction enum.
*/
public enum InteractionResult {
CONSUME(true),
/**
* Indicates that the action does nothing, or in rare cases is not a priority.
*/
PASS(false),
/**
* Indicates that the action does something, and don't try to find another action to process.
*/
SUCCESS(true);
private final boolean consumesAction;
InteractionResult(boolean consumesAction) {
this.consumesAction = consumesAction;
}
public boolean consumesAction() {
return consumesAction;
}
public boolean shouldSwing() {
return this == SUCCESS;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2019-2022 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.geyser.util;
import lombok.Getter;
import java.util.Locale;
/**
* All interactive tags in enum form. For potential API usage.
*/
public enum InteractiveTag {
NONE((Void) null),
IGNITE_CREEPER("creeper"),
EDIT,
LEAVE_BOAT("exit.boat"),
FEED,
FISH("fishing"),
MILK,
MOOSHROOM_SHEAR("mooshear"),
MOOSHROOM_MILK_STEW("moostew"),
BOARD_BOAT("ride.boat"),
RIDE_MINECART("ride.minecart"),
RIDE_HORSE("ride.horse"),
RIDE_STRIDER("ride.strider"),
SHEAR,
SIT,
STAND,
TALK,
TAME,
DYE,
CURE,
OPEN_CONTAINER("opencontainer"),
CREATE_MAP("createMap"),
TAKE_PICTURE("takepicture"),
SADDLE,
MOUNT,
BOOST,
WRITE,
LEASH,
REMOVE_LEASH("unleash"),
NAME,
ATTACH_CHEST("attachchest"),
TRADE,
POSE_ARMOR_STAND("armorstand.pose"),
EQUIP_ARMOR_STAND("armorstand.equip"),
READ,
WAKE_VILLAGER("wakevillager"),
BARTER;
/**
* The full string that should be passed on to the client.
*/
@Getter
private final String value;
InteractiveTag(Void isNone) {
this.value = "";
}
InteractiveTag(String value) {
this.value = "action.interact." + value;
}
InteractiveTag() {
this.value = "action.interact." + name().toLowerCase(Locale.ROOT);
}
}

View File

@ -162,6 +162,13 @@ public class InventoryUtils {
return item1.equals(item2, false, true, true);
}
/**
* Checks to see if an item stack represents air or has no count.
*/
public static boolean isEmpty(@Nullable ItemStack itemStack) {
return itemStack == null || itemStack.getId() == ItemMapping.AIR.getJavaId() || itemStack.getAmount() <= 0;
}
/**
* Returns a barrier block with custom name and lore to explain why
* part of the inventory is unusable.

View File

@ -26,9 +26,11 @@
package org.geysermc.geyser.util;
import com.github.steveice10.opennbt.tag.builtin.*;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.geysermc.geyser.session.GeyserSession;
public class ItemUtils {
private static Int2IntMap DYE_COLORS = null;
public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) {
ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments"));
@ -73,4 +75,19 @@ public class ItemUtils {
}
return null;
}
/**
* Return the dye color associated with this Java item ID, if any. Returns -1 if no dye color exists for this item.
*/
public static int getDyeColor(int javaId) {
return DYE_COLORS.get(javaId);
}
public static void setDyeColors(Int2IntMap dyeColors) {
if (DYE_COLORS != null) {
throw new RuntimeException();
}
dyeColors.defaultReturnValue(-1);
DYE_COLORS = dyeColors;
}
}