Add more interactive tags (mobile buttons) (#1443)

* Add more interactive tags (mobile buttons)

This expands our support for showing the interactive tags on touchscreen and console setups. This is not complete - specifically, the food compatibility of creatures needs to be expanded upon (I will work on this later and does not stop this PR from being mergable). This also includes:

- Creepers who are ignited with flint and steel now show up properly
- Zombie villagers now shake properly when converting and show their region outfits

* Add more food choices and add more panda entity metadata

* Re-add eating flag

* Remove debug line

* Refactor dimension usage, finish interactive tag usage, bees

* Print statements... ._.

* Don't make eating item packet data a non-constant

* Move BAMBOO to ItemRegistry

* Add missing break

* Make changes

* Minor final changes
This commit is contained in:
Camotoy 2020-11-11 19:28:45 -05:00 committed by GitHub
parent 3676dd185f
commit e748240a02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 643 additions and 110 deletions

View file

@ -103,11 +103,11 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap {
case "--config": case "--config":
case "-c": case "-c":
if (i >= args.length - 1) { if (i >= args.length - 1) {
System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.confignotspecified"), "-c")); System.err.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_not_specified"), "-c"));
return; return;
} }
configFilenameOpt = args[i+1]; i++; configFilenameOpt = args[i+1]; i++;
System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.configspecified"), configFilenameOpt)); System.out.println(MessageFormat.format(LanguageUtils.getLocaleStringLog("geyser.bootstrap.args.config_specified"), configFilenameOpt));
break; break;
case "--help": case "--help":
case "-h": case "-h":

View file

@ -179,8 +179,7 @@ public class GeyserConnector {
remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort);
authType = AuthType.getByName(config.getRemote().getAuthType()); authType = AuthType.getByName(config.getRemote().getAuthType());
if (config.isAboveBedrockNetherBuilding()) DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether
DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether
// https://github.com/GeyserMC/Geyser/issues/957 // https://github.com/GeyserMC/Geyser/issues/957
RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu();

View file

@ -67,8 +67,6 @@ public class Entity {
protected long entityId; protected long entityId;
protected long geyserId; protected long geyserId;
protected String dimension;
protected Vector3f position; protected Vector3f position;
protected Vector3f motion; protected Vector3f motion;
@ -100,7 +98,6 @@ public class Entity {
this.rotation = rotation; this.rotation = rotation;
this.valid = false; this.valid = false;
this.dimension = "minecraft:overworld";
setPosition(position); setPosition(position);

View file

@ -27,7 +27,10 @@ package org.geysermc.connector.entity.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f; 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.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
@ -41,10 +44,23 @@ public class BeeEntity extends AnimalEntity {
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 16) { if (entityMetadata.getId() == 16) {
byte xd = (byte) entityMetadata.getValue(); byte xd = (byte) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); // Bee is performing sting attack; trigger animation
if ((xd & 0x02) == 0x02) {
EntityEventPacket packet = new EntityEventPacket();
packet.setRuntimeEntityId(geyserId);
packet.setType(EntityEventType.ATTACK_START);
packet.setData(0);
session.sendUpstreamPacket(packet);
}
// If the bee has stung
metadata.put(EntityData.MARK_VARIANT, (xd & 0x04) == 0x04 ? 1 : 0);
// If the bee has nectar or not // If the bee has nectar or not
metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08); metadata.getFlags().setFlag(EntityFlag.POWERED, (xd & 0x08) == 0x08);
} }
if (entityMetadata.getId() == 17) {
// Converting "anger time" to a boolean
metadata.getFlags().setFlag(EntityFlag.ANGRY, (int) entityMetadata.getValue() > 0);
}
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
} }

View file

@ -41,7 +41,7 @@ public class FoxEntity extends AnimalEntity {
@Override @Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 16) { if (entityMetadata.getId() == 16) {
metadata.put(EntityData.VARIANT, (int) entityMetadata.getValue()); metadata.put(EntityData.VARIANT, entityMetadata.getValue());
} }
if (entityMetadata.getId() == 17) { if (entityMetadata.getId() == 17) {
byte xd = (byte) entityMetadata.getValue(); byte xd = (byte) entityMetadata.getValue();

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.DimensionUtils;
public class HoglinEntity extends AnimalEntity {
public HoglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 16) {
// Immune to zombification?
// Apply shaking effect if not in the nether and zombification is possible
metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER));
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -27,23 +27,75 @@ package org.geysermc.connector.entity.living.animal;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f; 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.EntityFlag;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.item.ItemRegistry;
public class PandaEntity extends AnimalEntity { public class PandaEntity extends AnimalEntity {
private int mainGene;
private int hiddenGene;
public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public PandaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
} }
@Override @Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 18) {
metadata.getFlags().setFlag(EntityFlag.EATING, (int) entityMetadata.getValue() > 0);
metadata.put(EntityData.EATING_COUNTER, entityMetadata.getValue());
if ((int) entityMetadata.getValue() != 0) {
// Particles and sound
EntityEventPacket packet = new EntityEventPacket();
packet.setRuntimeEntityId(geyserId);
packet.setType(EntityEventType.EATING_ITEM);
packet.setData(ItemRegistry.BAMBOO.getBedrockId() << 16);
session.sendUpstreamPacket(packet);
}
}
if (entityMetadata.getId() == 19) {
mainGene = (int) (byte) entityMetadata.getValue();
updateAppearance();
}
if (entityMetadata.getId() == 20) {
hiddenGene = (int) (byte) entityMetadata.getValue();
updateAppearance();
}
if (entityMetadata.getId() == 21) { if (entityMetadata.getId() == 21) {
byte xd = (byte) entityMetadata.getValue(); byte xd = (byte) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.SNEEZING, (xd & 0x02) == 0x02);
metadata.getFlags().setFlag(EntityFlag.EATING, (xd & 0x04) == 0x04); metadata.getFlags().setFlag(EntityFlag.ROLLING, (xd & 0x04) == 0x04);
metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x08) == 0x08);
// Required to put these both for sitting to actually show
metadata.put(EntityData.SITTING_AMOUNT, (xd & 0x08) == 0x08 ? 1f : 0f);
metadata.put(EntityData.SITTING_AMOUNT_PREVIOUS, (xd & 0x08) == 0x08 ? 1f : 0f);
metadata.getFlags().setFlag(EntityFlag.LAYING_DOWN, (xd & 0x10) == 0x10);
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }
/**
* 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 == hiddenGene) {
// Main and hidden genes match; this is what the panda looks like.
metadata.put(EntityData.VARIANT, mainGene);
} else {
// Genes have no effect on appearance
metadata.put(EntityData.VARIANT, 0);
}
} else {
// No need to worry about hidden gene
metadata.put(EntityData.VARIANT, mainGene);
}
}
} }

View file

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

View file

@ -34,6 +34,8 @@ import org.geysermc.connector.network.session.GeyserSession;
public class CatEntity extends TameableEntity { public class CatEntity extends TameableEntity {
private byte collarColor;
public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public CatEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
} }
@ -45,6 +47,13 @@ public class CatEntity extends TameableEntity {
@Override @Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 16) {
// Update collar color if tamed
if (metadata.getFlags().getFlag(EntityFlag.TAMED)) {
metadata.put(EntityData.COLOR, collarColor);
}
}
if (entityMetadata.getId() == 18) { if (entityMetadata.getId() == 18) {
// Different colors in Java and Bedrock for some reason // Different colors in Java and Bedrock for some reason
int variantColor; int variantColor;
@ -67,11 +76,11 @@ public class CatEntity extends TameableEntity {
metadata.put(EntityData.VARIANT, variantColor); metadata.put(EntityData.VARIANT, variantColor);
} }
if (entityMetadata.getId() == 21) { if (entityMetadata.getId() == 21) {
collarColor = (byte) (int) entityMetadata.getValue();
// Needed or else wild cats are a red color // Needed or else wild cats are a red color
if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { if (metadata.getFlags().getFlag(EntityFlag.TAMED)) {
metadata.put(EntityData.COLOR, (byte) (int) entityMetadata.getValue()); metadata.put(EntityData.COLOR, collarColor);
} }
} }
super.updateBedrockMetadata(entityMetadata, session);
} }
} }

View file

@ -29,10 +29,13 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.living.animal.AnimalEntity;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import java.util.UUID;
public class TameableEntity extends AnimalEntity { public class TameableEntity extends AnimalEntity {
public TameableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public TameableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
@ -46,11 +49,22 @@ public class TameableEntity extends AnimalEntity {
metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01); metadata.getFlags().setFlag(EntityFlag.SITTING, (xd & 0x01) == 0x01);
metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02); metadata.getFlags().setFlag(EntityFlag.ANGRY, (xd & 0x02) == 0x02);
metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04); metadata.getFlags().setFlag(EntityFlag.TAMED, (xd & 0x04) == 0x04);
// Must be set for wolf collar color to work }
// Extending it to all entities to prevent future bugs
if (metadata.getFlags().getFlag(EntityFlag.TAMED)) { // Note: Must be set for wolf collar color to work
metadata.put(EntityData.OWNER_EID, session.getPlayerEntity().getGeyserId()); if (entityMetadata.getId() == 17) {
} // Can't de-tame an entity so no resetting the owner ID if (entityMetadata.getValue() != null) {
// Owner UUID of entity
Entity entity = session.getEntityCache().getPlayerEntity((UUID) entityMetadata.getValue());
// Used as both a check since the player isn't in the entity cache and a normal fallback
if (entity == null) {
entity = session.getPlayerEntity();
}
// Translate to entity ID
metadata.put(EntityData.OWNER_EID, entity.getGeyserId());
} else {
metadata.put(EntityData.OWNER_EID, 0L); // Reset
}
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);
} }

View file

@ -26,27 +26,32 @@
package org.geysermc.connector.entity.living.merchant; package org.geysermc.connector.entity.living.merchant;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.math.vector.Vector3i; import com.nukkitx.math.vector.Vector3i;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlags; import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.WorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class VillagerEntity extends AbstractMerchantEntity { public class VillagerEntity extends AbstractMerchantEntity {
/**
* A map of Java profession IDs to Bedrock IDs
*/
private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap(); private static final Int2IntMap VILLAGER_VARIANTS = new Int2IntOpenHashMap();
private static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap(); /**
* A map of all Java region IDs (plains, savanna...) to Bedrock
*/
public static final Int2IntMap VILLAGER_REGIONS = new Int2IntOpenHashMap();
static { static {
// Java villager profession IDs -> Bedrock // Java villager profession IDs -> Bedrock

View file

@ -1,11 +1,25 @@
package org.geysermc.connector.entity.living.monster; package org.geysermc.connector.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.DimensionUtils;
public class BasePiglinEntity extends MonsterEntity { public class BasePiglinEntity extends MonsterEntity {
public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public BasePiglinEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
} }
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 15) {
// Immune to zombification?
// Apply shaking effect if not in the nether and zombification is possible
metadata.getFlags().setFlag(EntityFlag.SHAKING, !((boolean) entityMetadata.getValue()) && !session.getDimension().equals(DimensionUtils.NETHER));
}
super.updateBedrockMetadata(entityMetadata, session);
}
} }

View file

@ -33,6 +33,12 @@ import org.geysermc.connector.network.session.GeyserSession;
public class CreeperEntity extends MonsterEntity { public class CreeperEntity extends MonsterEntity {
/**
* Whether the creeper has been ignited and is using ID 17.
* In this instance we ignore ID 15 since it's sending us -1 which confuses poor Bedrock.
*/
private boolean ignitedByFlintAndSteel = false;
public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { public CreeperEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation); super(entityId, geyserId, entityType, position, motion, rotation);
} }
@ -40,13 +46,16 @@ public class CreeperEntity extends MonsterEntity {
@Override @Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 15) { if (entityMetadata.getId() == 15) {
metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1); if (!ignitedByFlintAndSteel) {
metadata.getFlags().setFlag(EntityFlag.IGNITED, (int) entityMetadata.getValue() == 1);
}
} }
if (entityMetadata.getId() == 16) { if (entityMetadata.getId() == 16) {
metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue()); metadata.getFlags().setFlag(EntityFlag.POWERED, (boolean) entityMetadata.getValue());
} }
if (entityMetadata.getId() == 17) { if (entityMetadata.getId() == 17) {
metadata.getFlags().setFlag(EntityFlag.IGNITED, (boolean) entityMetadata.getValue()); ignitedByFlintAndSteel = (boolean) entityMetadata.getValue();
metadata.getFlags().setFlag(EntityFlag.IGNITED, ignitedByFlintAndSteel);
} }
super.updateBedrockMetadata(entityMetadata, session); super.updateBedrockMetadata(entityMetadata, session);

View file

@ -41,7 +41,7 @@ public class PiglinEntity extends BasePiglinEntity {
@Override @Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 15) { if (entityMetadata.getId() == 16) {
boolean isBaby = (boolean) entityMetadata.getValue(); boolean isBaby = (boolean) entityMetadata.getValue();
if (isBaby) { if (isBaby) {
metadata.put(EntityData.SCALE, .55f); metadata.put(EntityData.SCALE, .55f);

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.entity.living.monster;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData;
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.connector.entity.living.merchant.VillagerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
public class ZombieVillagerEntity extends ZombieEntity {
public ZombieVillagerEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}
@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
if (entityMetadata.getId() == 18) {
metadata.getFlags().setFlag(EntityFlag.IS_TRANSFORMING, (boolean) entityMetadata.getValue());
metadata.getFlags().setFlag(EntityFlag.SHAKING, (boolean) entityMetadata.getValue());
}
if (entityMetadata.getId() == 19) {
VillagerData villagerData = (VillagerData) entityMetadata.getValue();
// Region - only one used on Bedrock
metadata.put(EntityData.MARK_VARIANT, VillagerEntity.VILLAGER_REGIONS.get(villagerData.getType()));
}
super.updateBedrockMetadata(entityMetadata, session);
}
}

View file

@ -77,7 +77,7 @@ public enum EntityType {
GHAST(GhastEntity.class, 41, 4.0f), GHAST(GhastEntity.class, 41, 4.0f),
MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f), MAGMA_CUBE(MagmaCubeEntity.class, 42, 0.51f),
BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f), BLAZE(BlazeEntity.class, 43, 1.8f, 0.6f),
ZOMBIE_VILLAGER(ZombieEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f), ZOMBIE_VILLAGER(ZombieVillagerEntity.class, 44, 1.8f, 0.6f, 0.6f, 1.62f, "minecraft:zombie_villager_v2"),
WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f), WITCH(RaidParticipantEntity.class, 45, 1.8f, 0.6f, 0.6f, 1.62f),
STRAY(AbstractSkeletonEntity.class, 46, 1.8f, 0.6f, 0.6f, 1.62f), STRAY(AbstractSkeletonEntity.class, 46, 1.8f, 0.6f, 0.6f, 1.62f),
HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f), HUSK(ZombieEntity.class, 47, 1.8f, 0.6f, 0.6f, 1.62f),
@ -153,7 +153,7 @@ public enum EntityType {
FOX(FoxEntity.class, 121, 0.5f, 1.25f), FOX(FoxEntity.class, 121, 0.5f, 1.25f),
BEE(BeeEntity.class, 122, 0.6f, 0.6f), BEE(BeeEntity.class, 122, 0.6f, 0.6f),
STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"), STRIDER(StriderEntity.class, 125, 1.7f, 0.9f, 0f, 0f, "minecraft:strider"),
HOGLIN(AnimalEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"), HOGLIN(HoglinEntity.class, 124, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:hoglin"),
ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"), ZOGLIN(ZoglinEntity.class, 126, 1.4f, 1.3965f, 1.3965f, 0f, "minecraft:zoglin"),
PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"), PIGLIN(PiglinEntity.class, 123, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin"),
PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"), PIGLIN_BRUTE(BasePiglinEntity.class, 127, 1.95f, 0.6f, 0.6f, 0f, "minecraft:piglin_brute"),

View file

@ -146,6 +146,13 @@ public class GeyserSession implements CommandSender {
@Setter @Setter
private boolean jumping; private boolean jumping;
/**
* The dimension of the player.
* As all entities are in the same world, this can be safely applied to all other entities.
*/
@Setter
private String dimension = DimensionUtils.OVERWORLD;
@Setter @Setter
private int breakingBlock; private int breakingBlock;
@ -629,7 +636,7 @@ public class GeyserSession implements CommandSender {
startGamePacket.setRotation(Vector2f.from(1, 1)); startGamePacket.setRotation(Vector2f.from(1, 1));
startGamePacket.setSeed(-1); startGamePacket.setSeed(-1);
startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(playerEntity.getDimension())); startGamePacket.setDimensionId(DimensionUtils.javaToBedrock(dimension));
startGamePacket.setGeneratorId(1); startGamePacket.setGeneratorId(1);
startGamePacket.setLevelGameType(GameType.SURVIVAL); startGamePacket.setLevelGameType(GameType.SURVIVAL);
startGamePacket.setDifficulty(1); startGamePacket.setDifficulty(1);

View file

@ -25,27 +25,65 @@
package org.geysermc.connector.network.translators.bedrock.entity.player; package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; 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.InteractAction;
import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.inventory.ContainerType;
import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket;
import com.nukkitx.protocol.bedrock.packet.InteractPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket;
import lombok.Getter;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry;
import java.util.Arrays;
import java.util.List;
@Translator(packet = InteractPacket.class) @Translator(packet = InteractPacket.class)
public class BedrockInteractTranslator extends PacketTranslator<InteractPacket> { public class BedrockInteractTranslator extends PacketTranslator<InteractPacket> {
/**
* A list of all foods a horse/donkey can eat on Java Edition.
* Used to display interactive tag if needed.
*/
private static final List<String> DONKEY_AND_HORSE_FOODS = Arrays.asList("golden_apple", "enchanted_golden_apple",
"golden_carrot", "sugar", "apple", "wheat", "hay_block");
/**
* A list of all flowers. Used for feeding bees.
*/
private static final List<String> FLOWERS = Arrays.asList("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet",
"red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose",
"sunflower", "lilac", "rose_bush", "peony");
/**
* All entity types that can be leashed on Java Edition
*/
private static final List<EntityType> LEASHABLE_MOB_TYPES = Arrays.asList(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN,
EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, 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.STRIDER, EntityType.WOLF, EntityType.ZOGLIN);
private static final List<EntityType> SADDLEABLE_WHEN_TAMED_MOB_TYPES = Arrays.asList(EntityType.DONKEY, EntityType.HORSE,
EntityType.ZOMBIE_HORSE, EntityType.MULE);
/**
* A list of all foods a wolf can eat on Java Edition.
* Used to display interactive tag if needed.
*/
private static final List<String> WOLF_FOODS = Arrays.asList("pufferfish", "tropical_fish", "chicken", "cooked_chicken",
"porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton",
"cooked_rabbit");
@Override @Override
public void translate(InteractPacket packet, GeyserSession session) { public void translate(InteractPacket packet, GeyserSession session) {
Entity entity; Entity entity;
@ -84,50 +122,232 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
if (interactEntity == null) if (interactEntity == null)
return; return;
EntityDataMap entityMetadata = interactEntity.getMetadata(); EntityDataMap entityMetadata = interactEntity.getMetadata();
ItemEntry itemEntry = session.getInventory().getItemInHand() == null ? ItemEntry.AIR : ItemRegistry.getItem(session.getInventory().getItemInHand());
String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", "");
String interactiveTag; // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen
switch (interactEntity.getEntityType()) { // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food
case BOAT: InteractiveTag interactiveTag = InteractiveTag.NONE;
interactiveTag = "action.interact.ride.boat"; if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) {
break; // Unleash the entity
case DONKEY: interactiveTag = InteractiveTag.REMOVE_LEASH;
case HORSE: } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) &&
case LLAMA: ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) ||
case MULE: interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) {
case SKELETON_HORSE: // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed)
case TRADER_LLAMA: interactiveTag = InteractiveTag.SADDLE;
case ZOMBIE_HORSE: } else if (javaIdentifierStripped.equals("name_tag") && session.getInventory().getItemInHand().getNbt() != null &&
if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { session.getInventory().getItemInHand().getNbt().contains("display")) {
interactiveTag = "action.interact.ride.horse"; // Holding a named name tag
} else { interactiveTag = InteractiveTag.NAME;
interactiveTag = "action.interact.mount"; } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) &&
} entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == -1L) {
break; // Holding a leash and the mob is leashable for sure
case MINECART: // (Plugins can change this behavior so that's something to look into in the far far future)
interactiveTag = "action.interact.ride.minecart"; interactiveTag = InteractiveTag.LEASH;
break; } else {
case PIG: switch (interactEntity.getEntityType()) {
if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { case BEE:
interactiveTag = "action.interact.mount"; if (FLOWERS.contains(javaIdentifierStripped)) {
} else interactiveTag = ""; interactiveTag = InteractiveTag.FEED;
break; }
case VILLAGER: break;
if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 case BOAT:
&& entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby interactiveTag = InteractiveTag.BOARD_BOAT;
interactiveTag = "action.interact.trade"; break;
} else interactiveTag = ""; case CAT:
break; if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) {
case WANDERING_TRADER: interactiveTag = InteractiveTag.FEED;
interactiveTag = "action.interact.trade"; // Since you can always trade with a wandering villager, presumably. } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) &&
break; entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) {
default: // Tamed and owned by player - can sit/stand
return; // No need to process any further since there is no interactive tag interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT;
break;
}
break;
case CHICKEN:
if (javaIdentifierStripped.contains("seeds")) {
interactiveTag = InteractiveTag.FEED;
}
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("wheat")) {
interactiveTag = InteractiveTag.FEED;
} else 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 (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().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:
// have another switch statement as, while these share mount attributes they don't share food
switch (interactEntity.getEntityType()) {
case LLAMA:
case TRADER_LLAMA:
if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) {
interactiveTag = InteractiveTag.FEED;
break;
}
case DONKEY:
case HORSE:
// Undead can't eat
if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) {
interactiveTag = InteractiveTag.FEED;
break;
}
}
if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) {
// Can't ride a baby
if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) {
interactiveTag = InteractiveTag.RIDE_HORSE;
} else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) {
// Can't hide an untamed entity without having your hand empty
interactiveTag = InteractiveTag.MOUNT;
}
}
break;
case FOX:
if (javaIdentifierStripped.equals("sweet_berries")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case HOGLIN:
if (javaIdentifierStripped.equals("crimson_fungus")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case MINECART:
interactiveTag = InteractiveTag.RIDE_MINECART;
break;
case MINECART_CHEST:
case MINECART_COMMAND_BLOCK:
case MINECART_HOPPER:
interactiveTag = InteractiveTag.OPEN_CONTAINER;
break;
case OCELOT:
if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case PANDA:
if (javaIdentifierStripped.equals("bamboo")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case PARROT:
if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case PIG:
if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) {
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.MOUNT;
}
break;
case PIGLIN:
if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) {
interactiveTag = InteractiveTag.BARTER;
}
break;
case RABBIT:
if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case SHEEP:
if (javaIdentifierStripped.equals("wheat")) {
interactiveTag = InteractiveTag.FEED;
} else if (!entityMetadata.getFlags().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 (javaIdentifierStripped.equals("warped_fungus")) {
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) {
interactiveTag = InteractiveTag.RIDE_STRIDER;
}
break;
case TURTLE:
if (javaIdentifierStripped.equals("seagrass")) {
interactiveTag = InteractiveTag.FEED;
}
break;
case VILLAGER:
if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0
&& entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // 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") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) {
// Bone and untamed - can tame
interactiveTag = InteractiveTag.TAME;
} else if (WOLF_FOODS.contains(javaIdentifierStripped)) {
// Compatible food in hand - feed
// Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this
interactiveTag = InteractiveTag.FEED;
} else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) &&
entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) {
// Tamed and owned by player - can sit/stand
interactiveTag = entityMetadata.getFlags().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().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag); session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue());
session.getPlayerEntity().updateBedrockMetadata(session); session.getPlayerEntity().updateBedrockMetadata(session);
} else { } else {
if (!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == null) || if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) {
!(session.getPlayerEntity().getMetadata().get(EntityData.INTERACTIVE_TAG) == "")) {
// No interactive tag should be sent // No interactive tag should be sent
session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG);
session.getPlayerEntity().updateBedrockMetadata(session); session.getPlayerEntity().updateBedrockMetadata(session);
@ -147,4 +367,65 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
break; break;
} }
} }
/**
* All interactive tags in enum form. For potential API usage.
*/
public enum InteractiveTag {
NONE(true),
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(boolean isNone) {
this.value = "";
}
InteractiveTag(String value) {
this.value = "action.interact." + value;
}
InteractiveTag() {
this.value = "action.interact." + name().toLowerCase();
}
}
} }

View file

@ -55,6 +55,10 @@ public class ItemRegistry {
public static final List<StartGamePacket.ItemEntry> ITEMS = new ArrayList<>(); public static final List<StartGamePacket.ItemEntry> ITEMS = new ArrayList<>();
public static final Int2ObjectMap<ItemEntry> ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); public static final Int2ObjectMap<ItemEntry> ITEM_ENTRIES = new Int2ObjectOpenHashMap<>();
/**
* Bamboo item entry, used in PandaEntity.java
*/
public static ItemEntry BAMBOO;
/** /**
* Boat item entry, used in BedrockInventoryTransactionTranslator.java * Boat item entry, used in BedrockInventoryTransactionTranslator.java
*/ */
@ -146,6 +150,9 @@ public class ItemRegistry {
case "minecraft:barrier": case "minecraft:barrier":
BARRIER_INDEX = itemIndex; BARRIER_INDEX = itemIndex;
break; break;
case "minecraft:bamboo":
BAMBOO = ITEM_ENTRIES.get(itemIndex);
break;
case "minecraft:oak_boat": case "minecraft:oak_boat":
BOAT = ITEM_ENTRIES.get(itemIndex); BOAT = ITEM_ENTRIES.get(itemIndex);
break; break;

View file

@ -55,7 +55,7 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
// are swapping servers // are swapping servers
String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
if (session.isSpawned()) { if (session.isSpawned()) {
String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
DimensionUtils.switchDimension(session, fakeDim); DimensionUtils.switchDimension(session, fakeDim);
DimensionUtils.switchDimension(session, newDimension); DimensionUtils.switchDimension(session, newDimension);
@ -96,7 +96,7 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData())); session.sendDownstreamPacket(new ClientPluginMessagePacket("minecraft:brand", PluginMessageUtils.getGeyserBrandData()));
if (!newDimension.equals(entity.getDimension())) { if (!newDimension.equals(session.getDimension())) {
DimensionUtils.switchDimension(session, newDimension); DimensionUtils.switchDimension(session, newDimension);
} }
} }

View file

@ -67,11 +67,11 @@ public class JavaRespawnTranslator extends PacketTranslator<ServerRespawnPacket>
} }
String newDimension = DimensionUtils.getNewDimension(packet.getDimension()); String newDimension = DimensionUtils.getNewDimension(packet.getDimension());
if (!entity.getDimension().equals(newDimension)) { if (!session.getDimension().equals(newDimension)) {
DimensionUtils.switchDimension(session, newDimension); DimensionUtils.switchDimension(session, newDimension);
} else { } else {
if (session.isManyDimPackets()) { //reloading world if (session.isManyDimPackets()) { //reloading world
String fakeDim = entity.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD; String fakeDim = session.getDimension().equals(DimensionUtils.OVERWORLD) ? DimensionUtils.NETHER : DimensionUtils.OVERWORLD;
DimensionUtils.switchDimension(session, fakeDim); DimensionUtils.switchDimension(session, fakeDim);
DimensionUtils.switchDimension(session, newDimension); DimensionUtils.switchDimension(session, newDimension);
} else { } else {

View file

@ -62,7 +62,7 @@ public class JavaEntityAttachTranslator extends PacketTranslator<ServerEntityAtt
if ((attachedToId == null || packet.getAttachedToId() == 0)) { if ((attachedToId == null || packet.getAttachedToId() == 0)) {
// Is not being leashed // Is not being leashed
holderId.getMetadata().getFlags().setFlag(EntityFlag.LEASHED, false); holderId.getMetadata().getFlags().setFlag(EntityFlag.LEASHED, false);
holderId.getMetadata().put(EntityData.LEASH_HOLDER_EID, 0); holderId.getMetadata().put(EntityData.LEASH_HOLDER_EID, -1L);
holderId.updateBedrockMetadata(session); holderId.updateBedrockMetadata(session);
EntityEventPacket eventPacket = new EntityEventPacket(); EntityEventPacket eventPacket = new EntityEventPacket();
eventPacket.setRuntimeEntityId(holderId.getGeyserId()); eventPacket.setRuntimeEntityId(holderId.getGeyserId());

View file

@ -25,15 +25,13 @@
package org.geysermc.connector.network.translators.java.entity; package org.geysermc.connector.network.translators.java.entity;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack;
import com.github.steveice10.mc.protocol.data.game.world.particle.ItemParticleData;
import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityStatusPacket;
import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.LevelEventType;
import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet;
import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket; import com.nukkitx.protocol.bedrock.packet.SetEntityMotionPacket;
@ -42,9 +40,7 @@ import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.network.translators.item.ItemEntry;
import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.item.ItemTranslator;
@Translator(packet = ServerEntityStatusPacket.class) @Translator(packet = ServerEntityStatusPacket.class)
public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntityStatusPacket> { public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntityStatusPacket> {
@ -141,9 +137,15 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
case TAMEABLE_TAMING_SUCCEEDED: case TAMEABLE_TAMING_SUCCEEDED:
entityEventPacket.setType(EntityEventType.TAME_SUCCEEDED); entityEventPacket.setType(EntityEventType.TAME_SUCCEEDED);
break; break;
case ZOMBIE_VILLAGER_CURE: case ZOMBIE_VILLAGER_CURE: // Played when a zombie bites the golden apple
entityEventPacket.setType(EntityEventType.ZOMBIE_VILLAGER_CURE); LevelSoundEvent2Packet soundPacket = new LevelSoundEvent2Packet();
break; soundPacket.setSound(SoundEvent.REMEDY);
soundPacket.setPosition(entity.getPosition());
soundPacket.setExtraData(-1);
soundPacket.setIdentifier("");
soundPacket.setRelativeVolumeDisabled(false);
session.sendUpstreamPacket(soundPacket);
return;
case ANIMAL_EMIT_HEARTS: case ANIMAL_EMIT_HEARTS:
entityEventPacket.setType(EntityEventType.LOVE_PARTICLES); entityEventPacket.setType(EntityEventType.LOVE_PARTICLES);
break; break;

View file

@ -46,7 +46,7 @@ public class JavaMapDataTranslator extends PacketTranslator<ServerMapDataPacket>
boolean shouldStore = false; boolean shouldStore = false;
mapItemDataPacket.setUniqueMapId(packet.getMapId()); mapItemDataPacket.setUniqueMapId(packet.getMapId());
mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); mapItemDataPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
mapItemDataPacket.setLocked(packet.isLocked()); mapItemDataPacket.setLocked(packet.isLocked());
mapItemDataPacket.setScale(packet.getScale()); mapItemDataPacket.setScale(packet.getScale());

View file

@ -94,7 +94,7 @@ public class JavaSpawnParticleTranslator extends PacketTranslator<ServerSpawnPar
if (stringParticle != null) { if (stringParticle != null) {
SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket(); SpawnParticleEffectPacket stringPacket = new SpawnParticleEffectPacket();
stringPacket.setIdentifier(stringParticle); stringPacket.setIdentifier(stringParticle);
stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); stringPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ())); stringPacket.setPosition(Vector3f.from(packet.getX(), packet.getY(), packet.getZ()));
session.sendUpstreamPacket(stringPacket); session.sendUpstreamPacket(stringPacket);
} }

View file

@ -42,7 +42,7 @@ public class JavaSpawnPositionTranslator extends PacketTranslator<ServerSpawnPos
SetSpawnPositionPacket spawnPositionPacket = new SetSpawnPositionPacket(); SetSpawnPositionPacket spawnPositionPacket = new SetSpawnPositionPacket();
spawnPositionPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ())); spawnPositionPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), packet.getPosition().getY(), packet.getPosition().getZ()));
spawnPositionPacket.setSpawnForced(true); spawnPositionPacket.setSpawnForced(true);
spawnPositionPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getPlayerEntity().getDimension())); spawnPositionPacket.setDimensionId(DimensionUtils.javaToBedrock(session.getDimension()));
spawnPositionPacket.setSpawnType(SetSpawnPositionPacket.Type.WORLD_SPAWN); spawnPositionPacket.setSpawnType(SetSpawnPositionPacket.Type.WORLD_SPAWN);
session.sendUpstreamPacket(spawnPositionPacket); session.sendUpstreamPacket(spawnPositionPacket);
} }

View file

@ -41,15 +41,23 @@ public class DimensionUtils {
// Changes if the above-bedrock Nether building workaround is applied // Changes if the above-bedrock Nether building workaround is applied
private static int BEDROCK_NETHER_ID = 1; private static int BEDROCK_NETHER_ID = 1;
// Static references to all vanilla dimensions /**
* String reference to vanilla Java overworld dimension identifier
*/
public static final String OVERWORLD = "minecraft:overworld"; public static final String OVERWORLD = "minecraft:overworld";
/**
* String reference to vanilla Java nether dimension identifier
*/
public static final String NETHER = "minecraft:the_nether"; public static final String NETHER = "minecraft:the_nether";
/**
* String reference to vanilla Java end dimension identifier
*/
public static final String THE_END = "minecraft:the_end"; public static final String THE_END = "minecraft:the_end";
public static void switchDimension(GeyserSession session, String javaDimension) { public static void switchDimension(GeyserSession session, String javaDimension) {
int bedrockDimension = javaToBedrock(javaDimension); int bedrockDimension = javaToBedrock(javaDimension);
Entity player = session.getPlayerEntity(); Entity player = session.getPlayerEntity();
if (javaDimension.equals(player.getDimension())) if (javaDimension.equals(session.getDimension()))
return; return;
if (session.getMovementSendIfIdle() != null) { if (session.getMovementSendIfIdle() != null) {
@ -69,7 +77,7 @@ public class DimensionUtils {
changeDimensionPacket.setRespawn(true); changeDimensionPacket.setRespawn(true);
changeDimensionPacket.setPosition(pos.toFloat()); changeDimensionPacket.setPosition(pos.toFloat());
session.sendUpstreamPacket(changeDimensionPacket); session.sendUpstreamPacket(changeDimensionPacket);
player.setDimension(javaDimension); session.setDimension(javaDimension);
player.setPosition(pos.toFloat()); player.setPosition(pos.toFloat());
session.setSpawned(false); session.setSpawned(false);
session.setLastChunkPosition(null); session.setLastChunkPosition(null);
@ -115,23 +123,30 @@ public class DimensionUtils {
/** /**
* Determines the new dimension based on the {@link CompoundTag} sent by either the {@link com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket} * Determines the new dimension based on the {@link CompoundTag} sent by either the {@link com.github.steveice10.mc.protocol.packet.ingame.server.ServerJoinGamePacket}
* or {@link com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket}. * or {@link com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket}.
*
* @param dimensionTag the packet's dimension tag. * @param dimensionTag the packet's dimension tag.
* @return the dimension identifier. * @return the dimension identifier.
*/ */
public static String getNewDimension(CompoundTag dimensionTag) { public static String getNewDimension(CompoundTag dimensionTag) {
if (dimensionTag == null || dimensionTag.isEmpty()) { if (dimensionTag == null || dimensionTag.isEmpty()) {
GeyserConnector.getInstance().getLogger().debug("Dimension tag was null or empty."); GeyserConnector.getInstance().getLogger().debug("Dimension tag was null or empty.");
return "minecraft:overworld"; return OVERWORLD;
} }
if (dimensionTag.getValue().get("effects") != null) { if (dimensionTag.getValue().get("effects") != null) {
return ((StringTag) dimensionTag.getValue().get("effects")).getValue(); return ((StringTag) dimensionTag.getValue().get("effects")).getValue();
} }
GeyserConnector.getInstance().getLogger().debug("Effects portion of the tag was null or empty."); GeyserConnector.getInstance().getLogger().debug("Effects portion of the tag was null or empty.");
return "minecraft:overworld"; return OVERWORLD;
} }
public static void changeBedrockNetherId() { /**
* The Nether dimension in Bedrock does not permit building above Y128 - the Bedrock above the dimension.
* This workaround sets the Nether as the End dimension to ignore this limit.
*
* @param isAboveNetherBedrockBuilding true if we should apply The End workaround
*/
public static void changeBedrockNetherId(boolean isAboveNetherBedrockBuilding) {
// Change dimension ID to the End to allow for building above Bedrock // Change dimension ID to the End to allow for building above Bedrock
BEDROCK_NETHER_ID = 2; BEDROCK_NETHER_ID = isAboveNetherBedrockBuilding ? 2 : 1;
} }
} }

View file

@ -243,12 +243,13 @@ public class LocaleUtils {
*/ */
public static String getLocaleString(String messageText, String locale) { public static String getLocaleString(String messageText, String locale) {
Map<String, String> localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase()); Map<String, String> localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(locale.toLowerCase());
if (localeStrings == null)
localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale());
if (localeStrings == null) { if (localeStrings == null) {
// Don't cause a NPE if the locale is STILL missing localeStrings = LocaleUtils.LOCALE_MAPPINGS.get(LanguageUtils.getDefaultLocale());
GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale()); if (localeStrings == null) {
return messageText; // Don't cause a NPE if the locale is STILL missing
GeyserConnector.getInstance().getLogger().debug("MISSING DEFAULT LOCALE: " + LanguageUtils.getDefaultLocale());
return messageText;
}
} }
return localeStrings.getOrDefault(messageText, messageText); return localeStrings.getOrDefault(messageText, messageText);