feat: implemented debug renderer entities

This commit is contained in:
Andrey Terentev 2025-04-04 23:38:52 +07:00
parent bdc0aeb31e
commit ac61442dda
18 changed files with 421 additions and 12 deletions

View File

@ -29,6 +29,7 @@ public class DebugCommands {
registerPosCommand(); registerPosCommand();
registerTeleportCommand(); registerTeleportCommand();
registerGameModeCommand(); registerGameModeCommand();
DebugRendererCommand.register();
log.info("Registered debug commands"); log.info("Registered debug commands");
} }

View File

@ -0,0 +1,62 @@
package ru.dragonestia.msb3.api.command;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.CommandSender;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.CommandContext;
import net.minestom.server.command.builder.arguments.ArgumentEnum;
import net.minestom.server.command.builder.arguments.ArgumentString;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import ru.dragonestia.msb3.api.player.MsbPlayer;
public class DebugRendererCommand extends Command {
private final ArgumentEnum<Option> argOption = ArgumentType.Enum("option", Option.class).setFormat(ArgumentEnum.Format.LOWER_CASED);
private final ArgumentString argLayerName = ArgumentType.String("layer");
public DebugRendererCommand() {
super("debug_render");
setDefaultExecutor(this::defaultExecutor);
argOption.setCallback((sender, ex) -> {
sender.sendMessage(Component.text("Invalid option", NamedTextColor.RED));
});
addSyntax(this::execute, argOption, argLayerName);
}
public static void register() {
MinecraftServer.getCommandManager().register(new DebugRendererCommand());
}
private void defaultExecutor(CommandSender sender, CommandContext ctx) {
var player = (Player) sender;
player.sendMessage("Debug Render commands:");
player.sendMessage("/debug_render show <layer> - Show debug layer");
player.sendMessage("/debug_render hide <layer> - Hide debug layer");
}
private void execute(CommandSender sender, CommandContext ctx) {
var player = (MsbPlayer) sender;
var option = ctx.get(argOption);
var layerName = ctx.get(argLayerName);
if (option == Option.SHOW) {
player.addDebugRenderLayer(layerName);
player.sendMessage(Component.text("Debug layer '%s' was showed for you".formatted(layerName), NamedTextColor.YELLOW));
return;
}
player.removeDebugRenderLayer(layerName);
player.sendMessage(Component.text("Debug layer '%s' was hidden for you".formatted(layerName), NamedTextColor.YELLOW));
}
public enum Option {
SHOW,
HIDE,
}
}

View File

@ -0,0 +1,11 @@
package ru.dragonestia.msb3.api.debug;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import ru.dragonestia.msb3.api.util.Env;
@UtilityClass
public class Debug {
@Getter private final boolean enabled = Env.bool("MSB3_DEBUG").orElse(false);
}

View File

@ -1,4 +1,4 @@
package ru.dragonestia.msb3.api.util; package ru.dragonestia.msb3.api.debug;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.kyori.adventure.sound.Sound; import net.kyori.adventure.sound.Sound;
@ -11,10 +11,8 @@ import net.minestom.server.sound.SoundEvent;
@UtilityClass @UtilityClass
public class DebugMessage { public class DebugMessage {
private final boolean disabled = !Env.bool("MSB3_DEBUG").orElse(false);
public void send(Player player, String message) { public void send(Player player, String message) {
if (disabled) return; if (!Debug.isEnabled()) return;
player.sendMessage(Component.text() player.sendMessage(Component.text()
.append(Component.text("[DEBUG] ", TextColor.color(0xFFC909), TextDecoration.BOLD)) .append(Component.text("[DEBUG] ", TextColor.color(0xFFC909), TextDecoration.BOLD))
@ -23,7 +21,7 @@ public class DebugMessage {
} }
public void sendError(Player player, String message) { public void sendError(Player player, String message) {
if (disabled) return; if (!Debug.isEnabled()) return;
var sound = Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_BIT, Sound.Source.NEUTRAL, 1f, 0f); var sound = Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_BIT, Sound.Source.NEUTRAL, 1f, 0f);
player.playSound(sound); player.playSound(sound);

View File

@ -14,7 +14,7 @@ import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber;
import ru.dragonestia.msb3.api.ui.TalksThemes; import ru.dragonestia.msb3.api.ui.TalksThemes;
import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer;
import ru.dragonestia.msb3.api.ui.dialogue.DialogueTheme; import ru.dragonestia.msb3.api.ui.dialogue.DialogueTheme;
import ru.dragonestia.msb3.api.util.DebugMessage; import ru.dragonestia.msb3.api.debug.DebugMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -10,7 +10,7 @@ import net.minestom.server.entity.Player;
import ru.dragonestia.msb3.api.player.PlayerContext; import ru.dragonestia.msb3.api.player.PlayerContext;
import ru.dragonestia.msb3.api.player.defaults.TalksContext; import ru.dragonestia.msb3.api.player.defaults.TalksContext;
import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer;
import ru.dragonestia.msb3.api.util.DebugMessage; import ru.dragonestia.msb3.api.debug.DebugMessage;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;

View File

@ -7,7 +7,7 @@ import lombok.Setter;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer;
import ru.dragonestia.msb3.api.util.DebugMessage; import ru.dragonestia.msb3.api.debug.DebugMessage;
import java.util.Map; import java.util.Map;

View File

@ -4,7 +4,7 @@ import lombok.extern.log4j.Log4j2;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import ru.dragonestia.msb3.api.dialog.DialogButtonClick; import ru.dragonestia.msb3.api.dialog.DialogButtonClick;
import ru.dragonestia.msb3.api.dialog.DialogRegistry; import ru.dragonestia.msb3.api.dialog.DialogRegistry;
import ru.dragonestia.msb3.api.util.DebugMessage; import ru.dragonestia.msb3.api.debug.DebugMessage;
import java.util.Map; import java.util.Map;

View File

@ -0,0 +1,10 @@
package ru.dragonestia.msb3.api.entity;
import net.minestom.server.coordinate.Point;
public interface OffsetPosition {
void setOffsetPosition(Point offset);
Point getOffsetPosition();
}

View File

@ -0,0 +1,29 @@
package ru.dragonestia.msb3.api.entity.debug;
import net.kyori.adventure.text.Component;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.display.BlockDisplayMeta;
import net.minestom.server.instance.block.Block;
public class DebugCollider extends DebugRendererEntity {
private DebugHologram hologram;
public DebugCollider(String layerName, Block block, Point size) {
super(EntityType.BLOCK_DISPLAY, layerName);
var meta = (BlockDisplayMeta) getEntityMeta();
meta.setScale(Vec.fromPoint(size));
meta.setBlockState(block);
meta.setScale(Vec.fromPoint(size));
}
public DebugCollider(String layerName, Block block, Point size, Component text) {
this(layerName, block, size);
var meta = (BlockDisplayMeta) getEntityMeta();
addNestedEntity(hologram = new DebugHologram(layerName, text), meta.getScale().div(2));
}
}

View File

@ -0,0 +1,18 @@
package ru.dragonestia.msb3.api.entity.debug;
import net.kyori.adventure.text.Component;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.display.AbstractDisplayMeta;
import net.minestom.server.entity.metadata.display.TextDisplayMeta;
public class DebugHologram extends DebugRendererEntity {
public DebugHologram(String layerName, Component text) {
super(EntityType.TEXT_DISPLAY, layerName);
var meta = (TextDisplayMeta) getEntityMeta();
meta.setText(text);
meta.setBillboardRenderConstraints(AbstractDisplayMeta.BillboardConstraints.CENTER);
meta.setSeeThrough(true);
}
}

View File

@ -0,0 +1,64 @@
package ru.dragonestia.msb3.api.entity.debug;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import net.kyori.adventure.text.format.TextColor;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EntityType;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle;
@Log4j2
public class DebugLine extends DebugRendererEntity {
@Getter private Point startPos;
@Getter private Point endPos;
private double dist;
private long lastTick = 0;
private final Particle particle;
public DebugLine(String layerName, Point start, Point end, TextColor color) {
super(EntityType.TEXT_DISPLAY, layerName);
this.startPos = start;
this.endPos = end;
updateDist();
particle = Particle.DUST.withColor(color).withScale(1);
}
@Override
public void tick(long time) {
super.tick(time);
if (time - lastTick < 500) return;
lastTick = time;
if (getViewers().isEmpty()) return;
double currentDist = 0;
while ((currentDist += 0.7) < dist) {
var pos = Vec.fromPoint(endPos.sub(startPos))
.normalize()
.mul(currentDist)
.add(startPos);
var pk = new ParticlePacket(particle, pos.x(), pos.y(), pos.z(), 0, 0, 0, 0, 1);
getViewers().forEach(player -> player.sendPacket(pk));
}
}
private void updateDist() {
dist = endPos.distance(startPos);
}
public void setStartPos(Point startPos) {
this.startPos = startPos;
updateDist();
}
public void setEndPos(Point endPos) {
this.endPos = endPos;
updateDist();
}
}

View File

@ -0,0 +1,33 @@
package ru.dragonestia.msb3.api.entity.debug;
import net.kyori.adventure.text.Component;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.display.AbstractDisplayMeta;
import net.minestom.server.entity.metadata.display.BlockDisplayMeta;
import net.minestom.server.instance.block.Block;
public class DebugMarker extends DebugRendererEntity {
public DebugMarker(String layerName, Block block) {
super(EntityType.BLOCK_DISPLAY, layerName);
var meta = (BlockDisplayMeta) getEntityMeta();
meta.setBlockState(block);
meta.setBillboardRenderConstraints(AbstractDisplayMeta.BillboardConstraints.CENTER);
meta.setTranslation(new Pos(-0.5, -0.5, -0.5));
meta.setLeftRotation(new float[] { 0.33333f, 0.33333f, 0.33333f, 0.8f });
}
public DebugMarker(String layerName, Block block, Component text) {
this(layerName, block);
addNestedEntity(new DebugHologram(layerName, text));
}
public void setScale(double x, double y, double z) {
var meta = (BlockDisplayMeta) getEntityMeta();
meta.setScale(new Vec(x, y, z));
}
}

View File

@ -0,0 +1,96 @@
package ru.dragonestia.msb3.api.entity.debug;
import lombok.Getter;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.display.AbstractDisplayMeta;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.dragonestia.msb3.api.debug.Debug;
import ru.dragonestia.msb3.api.entity.OffsetPosition;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Getter
public class DebugRendererEntity extends Entity implements OffsetPosition {
private final String layerName;
private final List<Entity> nestedEntities = new ArrayList<>();
private Point offsetPosition = Pos.ZERO;
public DebugRendererEntity(@NotNull EntityType entityType, String layerName) {
super(entityType);
this.layerName = layerName;
setNoGravity(true);
if (getEntityMeta() instanceof AbstractDisplayMeta meta) {
meta.setBrightnessOverride(1);
meta.setBrightness(15, 15);
}
}
public void addNestedEntity(Entity entity) {
nestedEntities.add(entity);
}
public void addNestedEntity(Entity entity, Point offset) {
if (entity instanceof OffsetPosition target) target.setOffsetPosition(offset);
nestedEntities.add(entity);
}
@Override
public CompletableFuture<Void> setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) {
return super.setInstance(instance, spawnPosition)
.thenRun(() -> {
if (!Debug.isEnabled()) {
remove();
return;
}
for (var entity: nestedEntities) {
if (entity instanceof OffsetPosition offsetPosition) {
entity.setInstance(instance, spawnPosition.add(offsetPosition.getOffsetPosition()));
} else {
entity.setInstance(instance, spawnPosition);
}
}
});
}
@Override
public @NotNull CompletableFuture<Void> teleport(@NotNull Pos position, long @Nullable [] chunks, int flags, boolean shouldConfirm) {
return super.teleport(position, chunks, flags, shouldConfirm)
.thenRun(() -> {
for (var entity: nestedEntities) {
if (entity instanceof OffsetPosition offsetPosition) {
entity.teleport(position.add(offsetPosition.getOffsetPosition()), chunks, flags, shouldConfirm);
} else {
entity.teleport(position, chunks, flags, shouldConfirm);
}
}
});
}
@Override
public void remove() {
super.remove();
for (var entity: nestedEntities) {
entity.remove();
}
}
@Override
public void setOffsetPosition(Point offset) {
offsetPosition = offset;
}
@Override
public Point getOffsetPosition() {
return offsetPosition;
}
}

View File

@ -0,0 +1,31 @@
package ru.dragonestia.msb3.api.entity.rule;
import net.minestom.server.entity.Entity;
import java.util.UUID;
public abstract class PlayerViewerRule {
private final UUID id = UUID.randomUUID();
public final UUID getId() {
return id;
}
public abstract boolean canView(Entity entity);
@Override
public final int hashCode() {
return id.hashCode();
}
@Override
public final boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null) return false;
if (obj instanceof PlayerViewerRule other) {
return this.id.equals(other.id);
}
return false;
}
}

View File

@ -0,0 +1,18 @@
package ru.dragonestia.msb3.api.math;
import net.minestom.server.coordinate.Vec;
public record Quaternion(float w, float x, float y, float z) {
public static Quaternion fromVec(Vec vec, double angle) {
double halfAngle = angle / 2.0;
double sinHalfAngle = Math.sin(halfAngle);
double qX = vec.x() * sinHalfAngle;
double qY = vec.y() * sinHalfAngle;
double qZ = vec.z() * sinHalfAngle;
double qW = Math.cos(halfAngle);
return new Quaternion((float) qW, (float) qX, (float) qY, (float) qZ);
}
}

View File

@ -5,19 +5,23 @@ import net.minestom.server.entity.Player;
import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.GameProfile;
import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerConnection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import ru.dragonestia.msb3.api.entity.debug.DebugRendererEntity;
import ru.dragonestia.msb3.api.entity.rule.PlayerViewerRule;
import ru.dragonestia.msb3.api.util.UncheckedRunnable; import ru.dragonestia.msb3.api.util.UncheckedRunnable;
import java.time.Instant; import java.time.Instant;
import java.util.LinkedHashMap; import java.util.*;
import java.util.Map;
public class MsbPlayer extends Player { public class MsbPlayer extends Player {
private final Map<String, PlayerContext> contexts = new LinkedHashMap<>(); private final Map<String, PlayerContext> contexts = new LinkedHashMap<>();
@Getter private final Instant startSessionTime = Instant.now(); @Getter private final Instant startSessionTime = Instant.now();
private final Set<PlayerViewerRule> viewerRules = Collections.synchronizedSet(new HashSet<>());
private final Set<String> debugRenderLayers = Collections.synchronizedSet(new HashSet<>());
public MsbPlayer(@NotNull PlayerConnection playerConnection, @NotNull GameProfile gameProfile) { public MsbPlayer(@NotNull PlayerConnection playerConnection, @NotNull GameProfile gameProfile) {
super(playerConnection, gameProfile); super(playerConnection, gameProfile);
updateViewerRules();
} }
void putContext(PlayerContext ctx) { void putContext(PlayerContext ctx) {
@ -55,4 +59,38 @@ public class MsbPlayer extends Player {
UncheckedRunnable.runIgnoreException(ctx::dispose); UncheckedRunnable.runIgnoreException(ctx::dispose);
} }
} }
public void addDebugRenderLayer(String layerName) {
debugRenderLayers.add(layerName);
updateViewerRules();
}
public void removeDebugRenderLayer(String layerName) {
debugRenderLayers.remove(layerName);
updateViewerRules();
}
public void appendViewRule(PlayerViewerRule rule) {
viewerRules.add(rule);
updateViewerRules();
}
public void removeViewRule(PlayerViewerRule rule) {
viewerRules.remove(rule);
updateViewerRules();
}
private void updateViewerRules() {
updateViewerRule(entity -> {
if (entity instanceof DebugRendererEntity debugRenderer) {
return debugRenderLayers.contains(debugRenderer.getLayerName());
}
for (var rule: viewerRules) {
if (!rule.canView(entity)) return false;
}
return true;
});
}
} }

View File

@ -24,7 +24,7 @@ import ru.dragonestia.msb3.api.item.ItemUtil;
import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber; import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber;
import ru.dragonestia.msb3.api.resource.dialog.GlyphPositions; import ru.dragonestia.msb3.api.resource.dialog.GlyphPositions;
import ru.dragonestia.msb3.api.resource.dialog.TextureProperties; import ru.dragonestia.msb3.api.resource.dialog.TextureProperties;
import ru.dragonestia.msb3.api.util.DebugMessage; import ru.dragonestia.msb3.api.debug.DebugMessage;
import ru.dragonestia.msb3.api.util.StringUtil; import ru.dragonestia.msb3.api.util.StringUtil;
import ru.dragonestia.msb3.resource.glyph.GlyphComponent; import ru.dragonestia.msb3.resource.glyph.GlyphComponent;
import ru.dragonestia.msb3.resource.glyph.GlyphComponentBuilder; import ru.dragonestia.msb3.resource.glyph.GlyphComponentBuilder;