Skip to content

Commit ef4acb1

Browse files
authored
Scoreboard rework (#4947)
* Initial version of the great scoreboard rework * Fixed some issues and added some initial tests * Addressed review * Added CubeCraft's scoreboard as a test, and fixed a discovered bug * Removed var usage for primitives and String, removed star imports
1 parent c656e41 commit ef4acb1

40 files changed

+4824
-1096
lines changed

core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ dependencies {
6161

6262
// Test
6363
testImplementation(libs.junit)
64+
testImplementation(libs.mockito)
6465

6566
// Annotation Processors
6667
compileOnly(projects.ap)

core/src/main/java/org/geysermc/geyser/entity/type/Entity.java

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525

2626
package org.geysermc.geyser.entity.type;
2727

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;
2834
import lombok.AccessLevel;
2935
import lombok.Getter;
3036
import lombok.Setter;
@@ -35,12 +41,18 @@
3541
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
3642
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
3743
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;
3950
import org.geysermc.geyser.api.entity.type.GeyserEntity;
4051
import org.geysermc.geyser.entity.EntityDefinition;
4152
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
4253
import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
4354
import org.geysermc.geyser.item.Items;
55+
import org.geysermc.geyser.scoreboard.Team;
4456
import org.geysermc.geyser.session.GeyserSession;
4557
import org.geysermc.geyser.translator.text.MessageTranslator;
4658
import org.geysermc.geyser.util.EntityUtils;
@@ -55,19 +67,22 @@
5567
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
5668
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
5769

58-
import java.util.*;
59-
6070
@Getter
6171
@Setter
6272
public class Entity implements GeyserEntity {
63-
6473
private static final boolean PRINT_ENTITY_SPAWN_DEBUG = Boolean.parseBoolean(System.getProperty("Geyser.PrintEntitySpawnDebug", "false"));
6574

6675
protected final GeyserSession session;
6776

6877
protected int entityId;
6978
protected final long geyserId;
7079
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 = "";
7186

7287
protected Vector3f position;
7388
protected Vector3f motion;
@@ -97,7 +112,7 @@ public class Entity implements GeyserEntity {
97112
@Setter(AccessLevel.NONE)
98113
private float boundingBoxWidth;
99114
@Setter(AccessLevel.NONE)
100-
protected String nametag = "";
115+
private String displayName;
101116
@Setter(AccessLevel.NONE)
102117
protected boolean silent = false;
103118
/* Metadata end */
@@ -126,11 +141,12 @@ public class Entity implements GeyserEntity {
126141

127142
public Entity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
128143
this.session = session;
144+
this.definition = definition;
145+
this.displayName = standardDisplayName();
129146

130147
this.entityId = entityId;
131148
this.geyserId = geyserId;
132149
this.uuid = uuid;
133-
this.definition = definition;
134150
this.motion = motion;
135151
this.yaw = yaw;
136152
this.pitch = pitch;
@@ -341,7 +357,7 @@ public final void setFlag(EntityFlag flag, boolean value) {
341357
* Sends the Bedrock metadata to the client
342358
*/
343359
public void updateBedrockMetadata() {
344-
if (!valid) {
360+
if (!isValid()) {
345361
return;
346362
}
347363

@@ -410,17 +426,81 @@ protected short getMaxAir() {
410426
return 300;
411427
}
412428

429+
public String teamIdentifier() {
430+
return uuid.toString();
431+
}
432+
413433
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.
414437
Optional<Component> name = entityMetadata.getValue();
415438
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;
421443
}
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);
422453
}
423454

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+
424504
public void setDisplayNameVisible(BooleanEntityMetadata entityMetadata) {
425505
dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) (entityMetadata.getPrimitiveValue() ? 1 : 0));
426506
}

core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525

2626
package org.geysermc.geyser.entity.type;
2727

28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.List;
31+
import java.util.Optional;
32+
import java.util.UUID;
2833
import lombok.AccessLevel;
2934
import lombok.Getter;
3035
import lombok.Setter;
@@ -45,6 +50,7 @@
4550
import org.geysermc.geyser.inventory.GeyserItemStack;
4651
import org.geysermc.geyser.item.Items;
4752
import org.geysermc.geyser.registry.type.ItemMapping;
53+
import org.geysermc.geyser.scoreboard.Team;
4854
import org.geysermc.geyser.session.GeyserSession;
4955
import org.geysermc.geyser.translator.item.ItemTranslator;
5056
import org.geysermc.geyser.util.AttributeUtils;
@@ -65,12 +71,9 @@
6571
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
6672
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
6773

68-
import java.util.*;
69-
7074
@Getter
7175
@Setter
7276
public class LivingEntity extends Entity {
73-
7477
protected ItemData helmet = ItemData.AIR;
7578
protected ItemData chestplate = ItemData.AIR;
7679
protected ItemData leggings = ItemData.AIR;
@@ -150,6 +153,16 @@ protected void initializeMetadata() {
150153
dirtyMetadata.put(EntityDataTypes.STRUCTURAL_INTEGRITY, 1);
151154
}
152155

156+
@Override
157+
public void updateNametag(@Nullable Team team) {
158+
// if name not visible, don't mark it as visible
159+
updateNametag(team, team == null || team.isVisibleFor(session.getPlayerEntity().getUsername()));
160+
}
161+
162+
public void hideNametag() {
163+
setNametag("", false);
164+
}
165+
153166
public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) {
154167
byte xd = entityMetadata.getPrimitiveValue();
155168

core/src/main/java/org/geysermc/geyser/entity/type/living/animal/RabbitEntity.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,21 @@
2525

2626
package org.geysermc.geyser.entity.type.living.animal;
2727

28+
import net.kyori.adventure.key.Key;
2829
import org.checkerframework.checker.nullness.qual.Nullable;
2930
import org.cloudburstmc.math.vector.Vector3f;
3031
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
3132
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
3233
import org.geysermc.geyser.entity.EntityDefinition;
3334
import org.geysermc.geyser.session.GeyserSession;
3435
import org.geysermc.geyser.session.cache.tags.ItemTag;
36+
import org.geysermc.geyser.util.EntityUtils;
3537
import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.type.IntEntityMetadata;
3638

3739
import java.util.UUID;
3840

3941
public class RabbitEntity extends AnimalEntity {
42+
private boolean isKillerBunny;
4043

4144
public RabbitEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
4245
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
@@ -46,7 +49,7 @@ public void setRabbitVariant(IntEntityMetadata entityMetadata) {
4649
int variant = entityMetadata.getPrimitiveValue();
4750

4851
// Change the killer bunny to display as white since it only exists on Java Edition
49-
boolean isKillerBunny = variant == 99;
52+
isKillerBunny = variant == 99;
5053
if (isKillerBunny) {
5154
variant = 1;
5255
}
@@ -56,6 +59,14 @@ public void setRabbitVariant(IntEntityMetadata entityMetadata) {
5659
dirtyMetadata.put(EntityDataTypes.VARIANT, variant);
5760
}
5861

62+
@Override
63+
protected String standardDisplayName() {
64+
if (isKillerBunny) {
65+
return EntityUtils.translatedEntityName(Key.key("killer_bunny"), session);
66+
}
67+
return super.standardDisplayName();
68+
}
69+
5970
@Override
6071
protected float getAdultSize() {
6172
return 0.55f;
@@ -71,4 +82,4 @@ protected float getBabySize() {
7182
protected ItemTag getFoodTag() {
7283
return ItemTag.RABBIT_FOOD;
7384
}
74-
}
85+
}

0 commit comments

Comments
 (0)