|
25 | 25 |
|
26 | 26 | package org.geysermc.geyser.entity.type; |
27 | 27 |
|
| 28 | +import java.util.Collections; |
| 29 | +import java.util.EnumSet; |
| 30 | +import java.util.List; |
| 31 | +import java.util.Objects; |
| 32 | +import java.util.Optional; |
| 33 | +import java.util.UUID; |
28 | 34 | import lombok.AccessLevel; |
29 | 35 | import lombok.Getter; |
30 | 36 | import lombok.Setter; |
|
35 | 41 | import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; |
36 | 42 | import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType; |
37 | 43 | import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; |
38 | | -import org.cloudburstmc.protocol.bedrock.packet.*; |
| 44 | +import org.cloudburstmc.protocol.bedrock.packet.AddEntityPacket; |
| 45 | +import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket; |
| 46 | +import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket; |
| 47 | +import org.cloudburstmc.protocol.bedrock.packet.MoveEntityDeltaPacket; |
| 48 | +import org.cloudburstmc.protocol.bedrock.packet.RemoveEntityPacket; |
| 49 | +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; |
39 | 50 | import org.geysermc.geyser.api.entity.type.GeyserEntity; |
40 | 51 | import org.geysermc.geyser.entity.EntityDefinition; |
41 | 52 | import org.geysermc.geyser.entity.GeyserDirtyMetadata; |
42 | 53 | import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager; |
43 | 54 | import org.geysermc.geyser.item.Items; |
| 55 | +import org.geysermc.geyser.scoreboard.Team; |
44 | 56 | import org.geysermc.geyser.session.GeyserSession; |
45 | 57 | import org.geysermc.geyser.translator.text.MessageTranslator; |
46 | 58 | import org.geysermc.geyser.util.EntityUtils; |
|
55 | 67 | import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand; |
56 | 68 | import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; |
57 | 69 |
|
58 | | -import java.util.*; |
59 | | - |
60 | 70 | @Getter |
61 | 71 | @Setter |
62 | 72 | public class Entity implements GeyserEntity { |
63 | | - |
64 | 73 | private static final boolean PRINT_ENTITY_SPAWN_DEBUG = Boolean.parseBoolean(System.getProperty("Geyser.PrintEntitySpawnDebug", "false")); |
65 | 74 |
|
66 | 75 | protected final GeyserSession session; |
67 | 76 |
|
68 | 77 | protected int entityId; |
69 | 78 | protected final long geyserId; |
70 | 79 | protected UUID uuid; |
| 80 | + /** |
| 81 | + * Do not call this setter directly! |
| 82 | + * This will bypass the scoreboard and setting the metadata |
| 83 | + */ |
| 84 | + @Setter(AccessLevel.NONE) |
| 85 | + protected String nametag = ""; |
71 | 86 |
|
72 | 87 | protected Vector3f position; |
73 | 88 | protected Vector3f motion; |
@@ -97,7 +112,7 @@ public class Entity implements GeyserEntity { |
97 | 112 | @Setter(AccessLevel.NONE) |
98 | 113 | private float boundingBoxWidth; |
99 | 114 | @Setter(AccessLevel.NONE) |
100 | | - protected String nametag = ""; |
| 115 | + private String displayName; |
101 | 116 | @Setter(AccessLevel.NONE) |
102 | 117 | protected boolean silent = false; |
103 | 118 | /* Metadata end */ |
@@ -126,11 +141,12 @@ public class Entity implements GeyserEntity { |
126 | 141 |
|
127 | 142 | public Entity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { |
128 | 143 | this.session = session; |
| 144 | + this.definition = definition; |
| 145 | + this.displayName = standardDisplayName(); |
129 | 146 |
|
130 | 147 | this.entityId = entityId; |
131 | 148 | this.geyserId = geyserId; |
132 | 149 | this.uuid = uuid; |
133 | | - this.definition = definition; |
134 | 150 | this.motion = motion; |
135 | 151 | this.yaw = yaw; |
136 | 152 | this.pitch = pitch; |
@@ -341,7 +357,7 @@ public final void setFlag(EntityFlag flag, boolean value) { |
341 | 357 | * Sends the Bedrock metadata to the client |
342 | 358 | */ |
343 | 359 | public void updateBedrockMetadata() { |
344 | | - if (!valid) { |
| 360 | + if (!isValid()) { |
345 | 361 | return; |
346 | 362 | } |
347 | 363 |
|
@@ -410,17 +426,81 @@ protected short getMaxAir() { |
410 | 426 | return 300; |
411 | 427 | } |
412 | 428 |
|
| 429 | + public String teamIdentifier() { |
| 430 | + return uuid.toString(); |
| 431 | + } |
| 432 | + |
413 | 433 | public void setDisplayName(EntityMetadata<Optional<Component>, ?> entityMetadata) { |
| 434 | + // displayName is shown when always display name is enabled. Either with or without team. |
| 435 | + // That's why there are both a displayName and a nametag variable. |
| 436 | + // Displayname is ignored for players, and is always their username. |
414 | 437 | Optional<Component> name = entityMetadata.getValue(); |
415 | 438 | if (name.isPresent()) { |
416 | | - nametag = MessageTranslator.convertMessage(name.get(), session.locale()); |
417 | | - dirtyMetadata.put(EntityDataTypes.NAME, nametag); |
418 | | - } else if (!nametag.isEmpty()) { |
419 | | - // Clear nametag |
420 | | - dirtyMetadata.put(EntityDataTypes.NAME, ""); |
| 439 | + String displayName = MessageTranslator.convertMessage(name.get(), session.locale()); |
| 440 | + this.displayName = displayName; |
| 441 | + setNametag(displayName, true); |
| 442 | + return; |
421 | 443 | } |
| 444 | + |
| 445 | + // if no displayName is set, use entity name (ENDER_DRAGON -> Ender Dragon) |
| 446 | + // maybe we can/should use a translatable here instead? |
| 447 | + this.displayName = standardDisplayName(); |
| 448 | + setNametag(null, true); |
| 449 | + } |
| 450 | + |
| 451 | + protected String standardDisplayName() { |
| 452 | + return EntityUtils.translatedEntityName(definition.entityType(), session); |
422 | 453 | } |
423 | 454 |
|
| 455 | + protected void setNametag(@Nullable String nametag, boolean fromDisplayName) { |
| 456 | + // ensure that the team format is used when nametag changes |
| 457 | + if (nametag != null && fromDisplayName) { |
| 458 | + var team = session.getWorldCache().getScoreboard().getTeamFor(teamIdentifier()); |
| 459 | + if (team != null) { |
| 460 | + updateNametag(team); |
| 461 | + return; |
| 462 | + } |
| 463 | + } |
| 464 | + |
| 465 | + if (nametag == null) { |
| 466 | + nametag = ""; |
| 467 | + } |
| 468 | + boolean changed = !Objects.equals(this.nametag, nametag); |
| 469 | + this.nametag = nametag; |
| 470 | + // we only update metadata if the value has changed |
| 471 | + if (!changed) { |
| 472 | + return; |
| 473 | + } |
| 474 | + |
| 475 | + dirtyMetadata.put(EntityDataTypes.NAME, nametag); |
| 476 | + // if nametag (player with team) is hidden for player, so should the score (belowname) |
| 477 | + scoreVisibility(!nametag.isEmpty()); |
| 478 | + } |
| 479 | + |
| 480 | + public void updateNametag(@Nullable Team team) { |
| 481 | + // allow LivingEntity+ to have a different visibility check |
| 482 | + updateNametag(team, true); |
| 483 | + } |
| 484 | + |
| 485 | + protected void updateNametag(@Nullable Team team, boolean visible) { |
| 486 | + if (team != null) { |
| 487 | + String newNametag; |
| 488 | + // (team) visibility is LivingEntity+, team displayName is Entity+ |
| 489 | + if (visible) { |
| 490 | + newNametag = team.displayName(getDisplayName()); |
| 491 | + } else { |
| 492 | + // The name is not visible to the session player; clear name |
| 493 | + newNametag = ""; |
| 494 | + } |
| 495 | + setNametag(newNametag, false); |
| 496 | + return; |
| 497 | + } |
| 498 | + // The name has reset, if it was previously something else |
| 499 | + setNametag(null, false); |
| 500 | + } |
| 501 | + |
| 502 | + protected void scoreVisibility(boolean show) {} |
| 503 | + |
424 | 504 | public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) { |
425 | 505 | dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0)); |
426 | 506 | } |
|
0 commit comments