From f082b3b9f64574d45cc9d9850cd370f2a6301d4d Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 13 Mar 2025 01:36:02 +0700 Subject: [PATCH] feat: added Human entity --- .../ru/dragonestia/msb3/api/entity/Human.java | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java new file mode 100644 index 0000000..8261ed5 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java @@ -0,0 +1,138 @@ +package ru.dragonestia.msb3.api.entity; + +import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.*; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.metadata.display.AbstractDisplayMeta; +import net.minestom.server.entity.metadata.display.TextDisplayMeta; +import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket; +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket; +import net.minestom.server.network.packet.server.play.TeamsPacket; +import net.minestom.server.scoreboard.Team; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class Human extends EntityCreature { + + private static final AtomicInteger freeId = new AtomicInteger(0); + private static final Map npcTeams = new ConcurrentHashMap<>(); + private static final Point displayNamePositionOffset = new Vec(0, 2, 0); + + @Getter private Component name; + private final String username; + @Getter private final PlayerSkin skin; + private final NamedTextColor teamColor; + private final PlayerInfoUpdatePacket createPacket; + private final PlayerInfoRemovePacket removePacket; + private final Team team; + private Entity displayNameEntity; + + public Human(String name, PlayerSkin skin) { + this(Component.text(name, NamedTextColor.YELLOW), skin); + } + + public Human(Component name, PlayerSkin skin) { + this(name, skin, NamedTextColor.YELLOW); + } + + public Human(Component name, PlayerSkin skin, NamedTextColor teamColor) { + super(EntityType.PLAYER); + this.name = name; + this.username = "Human_" + freeId.incrementAndGet(); + this.skin = skin; + this.teamColor = teamColor; + this.createPacket = getCreatePacket(); + this.removePacket = new PlayerInfoRemovePacket(getUuid()); + this.team = pickTeam(); + + getEntityMeta().setCustomNameVisible(false); + setBoundingBox(0.6, 1.8, 0.6); + getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.2F); + } + + private PlayerInfoUpdatePacket getCreatePacket() { + var properties = List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())); + return new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.ADD_PLAYER, + new PlayerInfoUpdatePacket.Entry(getUuid(), username, properties, false, 0, GameMode.CREATIVE, name, null, 0)); + } + + @Override + public CompletableFuture setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) { + return super.setInstance(instance, spawnPosition) + .thenRun(this::getDisplayNameEntity); + } + + @Override + public @NotNull CompletableFuture teleport(@NotNull Pos position, @NotNull Vec velocity, long @Nullable [] chunks, int flags, boolean shouldConfirm) { + return super.teleport(position, velocity, chunks, flags, shouldConfirm) + .thenRun(() -> getDisplayNameEntity().teleport(position.add(displayNamePositionOffset), chunks, flags, shouldConfirm)); + } + + @Override + public void refreshPosition(@NotNull Pos newPosition, boolean ignoreView, boolean sendPackets) { + super.refreshPosition(newPosition, ignoreView, sendPackets); + getDisplayNameEntity().refreshPosition(newPosition.add(displayNamePositionOffset), ignoreView, sendPackets); + } + + @Override + protected void remove(boolean permanent) { + super.remove(permanent); + if (displayNameEntity != null) displayNameEntity.remove(); + } + + @Override + public void updateNewViewer(@NotNull Player player) { + player.sendPacket(createPacket); + super.updateNewViewer(player); + player.sendPacket(new TeamsPacket(team.getTeamName(), new TeamsPacket.AddEntitiesToTeamAction(List.of(username)))); + } + + @Override + public void updateOldViewer(@NotNull Player player) { + player.sendPacket(removePacket); + super.updateOldViewer(player); + } + + private Team pickTeam() { + return npcTeams.computeIfAbsent(teamColor.asHexString(), $ -> MinecraftServer.getTeamManager() + .createBuilder("NPC_r%s_g%s_b%s".formatted(teamColor.red(), teamColor.green(), teamColor.blue())) + .collisionRule(TeamsPacket.CollisionRule.ALWAYS) + .teamColor(teamColor) + .nameTagVisibility(TeamsPacket.NameTagVisibility.NEVER) + .build()); + } + + private synchronized Entity getDisplayNameEntity() { + if (displayNameEntity == null) { + displayNameEntity = new Entity(EntityType.TEXT_DISPLAY); + var meta = (TextDisplayMeta) displayNameEntity.getEntityMeta(); + meta.setText(name); + meta.setHasNoGravity(true); + meta.setBillboardRenderConstraints(AbstractDisplayMeta.BillboardConstraints.VERTICAL); + displayNameEntity.setInstance(instance, getPosition().add(displayNamePositionOffset)); + } + return displayNameEntity; + } + + public void setName(Component name) { + this.name = name; + if (displayNameEntity != null) { + var meta = (TextDisplayMeta) displayNameEntity.getEntityMeta(); + meta.setText(name); + } + } +}