feat: added Human entity

This commit is contained in:
Andrey Terentev 2025-03-13 01:36:02 +07:00
parent ba36b2ab1d
commit f082b3b9f6

View File

@ -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<String, Team> 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<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
return super.setInstance(instance, spawnPosition)
.thenRun(this::getDisplayNameEntity);
}
@Override
public @NotNull CompletableFuture<Void> 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);
}
}
}