Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Armor stand fixes #1270

Merged
merged 13 commits into from
Feb 25, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,9 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s
metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers
metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80);

if ((xd & 0x20) == 0x20) {
// Armour stands are handled in their own class
if (!this.is(ArmorStandEntity.class)) {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
}
} else {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
// Armour stands are handled in their own class
if (!this.is(ArmorStandEntity.class)) {
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20);
}

// Shield code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityFlag;
import com.nukkitx.protocol.bedrock.data.inventory.ItemData;
import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.connector.entity.LivingEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
Expand All @@ -40,30 +43,73 @@ public class ArmorStandEntity extends LivingEntity {
private boolean isInvisible = false;
private boolean isSmall = false;

/**
* On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock.
* By having a second entity, we can allow an invisible entity with the name tag.
* (This lets armor on armor stands still show)
*/
private ArmorStandEntity secondEntity = null;
/**
* Whether this is the primary armor stand that holds the armor and not the name tag.
*/
private boolean primaryEntity = true;
/**
* Whether the last position update included the offset.
*/
private boolean lastPositionIncludedOffset = false;
private GeyserSession session;

public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) {
super(entityId, geyserId, entityType, position, motion, rotation);
}

@Override
public void spawnEntity(GeyserSession session) {
this.session = session;
super.spawnEntity(session);
}

@Override
public boolean despawnEntity(GeyserSession session) {
if (secondEntity != null) {
secondEntity.despawnEntity(session);
}
return super.despawnEntity(session);
}

@Override
public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) {
// Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands
if (!isMarker && isInvisible) {
lastPositionIncludedOffset = false;
if (secondEntity != null) {
secondEntity.moveAbsolute(session, position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d), rotation, isOnGround, teleported);
} else if (!isMarker && isInvisible && !metadata.getFlags().getFlag(EntityFlag.INVISIBLE)) { // Means it's not visible
position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d);
lastPositionIncludedOffset = true;
}

super.moveAbsolute(session, position, rotation, isOnGround, teleported);
}

@Override
public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) {
if (relX == 0 && relY == 0 && relZ == 0 && rotation.equals(this.rotation)) return; // Prevents a weird glitch where the armor stand fidgets
super.moveRelative(session, relX, relY, relZ, rotation, isOnGround);
}

@Override
public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) {
super.updateBedrockMetadata(entityMetadata, session);
if (entityMetadata.getId() == 0 && entityMetadata.getType() == MetadataType.BYTE) {
byte xd = (byte) entityMetadata.getValue();

// Check if the armour stand is invisible and store accordingly
if ((xd & 0x20) == 0x20) {
metadata.put(EntityData.SCALE, 0.0f);
isInvisible = true;
if (primaryEntity) {
isInvisible = (xd & 0x20) == 0x20;
updateSecondEntityStatus();
}
} else if (entityMetadata.getId() == 2) {
updateSecondEntityStatus();
} else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) {
byte xd = (byte) entityMetadata.getValue();

Expand Down Expand Up @@ -91,6 +137,158 @@ public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession s
isMarker = true;
}
}
super.updateBedrockMetadata(entityMetadata, session);
if (secondEntity != null) {
secondEntity.updateBedrockMetadata(entityMetadata, session);
}
}

@Override
public void updateBedrockMetadata(GeyserSession session) {
if (secondEntity != null) {
secondEntity.updateBedrockMetadata(session);
}
super.updateBedrockMetadata(session);
}

@Override
public void setHelmet(ItemData helmet) {
super.setHelmet(helmet);
updateSecondEntityStatus();
}

@Override
public void setChestplate(ItemData chestplate) {
super.setChestplate(chestplate);
updateSecondEntityStatus();
}

@Override
public void setLeggings(ItemData leggings) {
super.setLeggings(leggings);
updateSecondEntityStatus();
}

@Override
public void setBoots(ItemData boots) {
super.setBoots(boots);
updateSecondEntityStatus();
}

@Override
public void setHand(ItemData hand) {
super.setHand(hand);
updateSecondEntityStatus();
}

@Override
public void setOffHand(ItemData offHand) {
super.setOffHand(offHand);
updateSecondEntityStatus();
}

/**
* Determine if we need to load or unload the second entity.
*/
private void updateSecondEntityStatus() {
if (!primaryEntity) return;
if (!isInvisible || isMarker) {
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
// Update the position of this armor stand
updatePosition();
}
return;
}
if ((helmet != ItemData.AIR || chestplate != ItemData.AIR || leggings != ItemData.AIR || boots != ItemData.AIR
|| hand != ItemData.AIR || offHand != ItemData.AIR) && !metadata.getString(EntityData.NAMETAG).equals("")) {
if (secondEntity != null) return; // No need to recreate
// Create the second entity. It doesn't need to worry about the items, but it does need to worry about
// the metadata as it will hold the name tag.
secondEntity = new ArmorStandEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(),
EntityType.ARMOR_STAND, position, motion, rotation);
secondEntity.primaryEntity = false;
// Copy metadata
secondEntity.isSmall = isSmall;
secondEntity.getMetadata().putAll(metadata);
// Copy the flags so they aren't the same object in memory
secondEntity.getMetadata().putFlags(metadata.getFlags().copy());
// Guarantee this copy is NOT invisible
secondEntity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false);
// Scale to 0 to show nametag
secondEntity.getMetadata().put(EntityData.SCALE, 0.0f);
// No bounding box as we don't want to interact with this entity
secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f);
secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f);
secondEntity.spawnEntity(session);
// Reset scale of the proper armor stand
metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
// Set the proper armor stand to invisible to show armor
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);

// Update the position of the armor stands
updatePosition();

if (lastPositionIncludedOffset) {
secondEntity.updatePosition();
} else {
secondEntity.updatePositionWithOffset();
}
} else if (metadata.getString(EntityData.NAMETAG).equals("")) {
// We can just make an invisible entity
// Reset scale of the proper armor stand
metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f);
// Set the proper armor stand to invisible to show armor
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true);
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
// Update the position of this armor stand
updatePosition();
}
} else {
// We don't need to make a new entity
updatePositionWithOffset();
metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false);
metadata.put(EntityData.SCALE, 0.0f);
if (secondEntity != null) {
secondEntity.despawnEntity(session);
secondEntity = null;
}
}
this.updateBedrockMetadata(session);
}

/**
* Updates position without calling movement code.
*/
private void updatePosition() {
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
moveEntityPacket.setPosition(lastPositionIncludedOffset ? position.sub(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d) : position);
moveEntityPacket.setRotation(getBedrockRotation());
moveEntityPacket.setOnGround(onGround);
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}

/**
* Updates position without calling movement code and includes the offset.
*/
private void updatePositionWithOffset() {
System.out.println(secondEntity);
MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket();
moveEntityPacket.setRuntimeEntityId(geyserId);
moveEntityPacket.setPosition(primaryEntity && lastPositionIncludedOffset ?
position : position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d));
moveEntityPacket.setRotation(getBedrockRotation());
moveEntityPacket.setOnGround(onGround);
moveEntityPacket.setTeleported(false);
session.sendUpstreamPacket(moveEntityPacket);
}

@Override
public Vector3f getBedrockRotation() {
return Vector3f.from(rotation.getY(), rotation.getX(), rotation.getZ());
}
}