SubtitleOverlay.java

net.minecraft.client.gui.components.SubtitleOverlay

信息

  • 全限定名:net.minecraft.client.gui.components.SubtitleOverlay
  • 类型:public class
  • 包:net.minecraft.client.gui.components
  • 源码路径:src/main/java/net/minecraft/client/gui/components/SubtitleOverlay.java
  • 起始行号:L25
  • 实现:SoundEventListener
  • 职责:

    TODO

字段/常量

  • DISPLAY_TIME

    • 类型: long
    • 修饰符: private static final
    • 源码定位: L26
    • 说明:

      TODO

  • minecraft

    • 类型: Minecraft
    • 修饰符: private final
    • 源码定位: L27
    • 说明:

      TODO

  • subtitles

    • 类型: List<SubtitleOverlay.Subtitle>
    • 修饰符: private final
    • 源码定位: L28
    • 说明:

      TODO

  • isListening

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L29
    • 说明:

      TODO

  • audibleSubtitles

    • 类型: List<SubtitleOverlay.Subtitle>
    • 修饰符: private final
    • 源码定位: L30
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.gui.components.SubtitleOverlay.SoundPlayedAt

    • 类型: record
    • 修饰符: package-private
    • 源码定位: L137
    • 说明:

      TODO

  • net.minecraft.client.gui.components.SubtitleOverlay.Subtitle

    • 类型: class
    • 修饰符: static
    • 源码定位: L141
    • 说明:

      TODO

构造器

public SubtitleOverlay(Minecraft minecraft) @ L32

  • 构造器名:SubtitleOverlay
  • 源码定位:L32
  • 修饰符:public

参数:

  • minecraft: Minecraft

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void extractRenderState(GuiGraphicsExtractor graphics) @ L36

  • 方法名:extractRenderState
  • 源码定位:L36
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor

说明:

TODO

public void onPlaySound(SoundInstance sound, WeighedSoundEvents soundEvent, float range) @ L119

  • 方法名:onPlaySound
  • 源码定位:L119
  • 返回类型:void
  • 修饰符:public

参数:

  • sound: SoundInstance
  • soundEvent: WeighedSoundEvents
  • range: float

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
public class SubtitleOverlay implements SoundEventListener {
    private static final long DISPLAY_TIME = 3000L;
    private final Minecraft minecraft;
    private final List<SubtitleOverlay.Subtitle> subtitles = Lists.newArrayList();
    private boolean isListening;
    private final List<SubtitleOverlay.Subtitle> audibleSubtitles = new ArrayList<>();
 
    public SubtitleOverlay(Minecraft minecraft) {
        this.minecraft = minecraft;
    }
 
    public void extractRenderState(GuiGraphicsExtractor graphics) {
        SoundManager soundManager = this.minecraft.getSoundManager();
        if (!this.isListening && this.minecraft.options.showSubtitles().get()) {
            soundManager.addListener(this);
            this.isListening = true;
        } else if (this.isListening && !this.minecraft.options.showSubtitles().get()) {
            soundManager.removeListener(this);
            this.isListening = false;
        }
 
        if (this.isListening) {
            ListenerTransform listener = soundManager.getListenerTransform();
            Vec3 position = listener.position();
            Vec3 forwards = listener.forward();
            Vec3 right = listener.right();
            this.audibleSubtitles.clear();
 
            for (SubtitleOverlay.Subtitle subtitle : this.subtitles) {
                if (subtitle.isAudibleFrom(position)) {
                    this.audibleSubtitles.add(subtitle);
                }
            }
 
            if (!this.audibleSubtitles.isEmpty()) {
                int row = 0;
                int width = 0;
                double displayTimeMultiplier = this.minecraft.options.notificationDisplayTime().get();
                Iterator<SubtitleOverlay.Subtitle> iterator = this.audibleSubtitles.iterator();
 
                while (iterator.hasNext()) {
                    SubtitleOverlay.Subtitle subtitlex = iterator.next();
                    subtitlex.purgeOldInstances(3000.0 * displayTimeMultiplier);
                    if (!subtitlex.isStillActive()) {
                        iterator.remove();
                    } else {
                        width = Math.max(width, this.minecraft.font.width(subtitlex.getText()));
                    }
                }
 
                width += this.minecraft.font.width("<") + this.minecraft.font.width(" ") + this.minecraft.font.width(">") + this.minecraft.font.width(" ");
                if (!this.audibleSubtitles.isEmpty()) {
                    graphics.nextStratum();
                }
 
                for (SubtitleOverlay.Subtitle subtitlex : this.audibleSubtitles) {
                    int alpha = 255;
                    Component text = subtitlex.getText();
                    SubtitleOverlay.SoundPlayedAt closestRecentLocation = subtitlex.getClosest(position);
                    if (closestRecentLocation != null) {
                        Vec3 delta = closestRecentLocation.location.subtract(position).normalize();
                        double rightness = right.dot(delta);
                        double forwardness = forwards.dot(delta);
                        boolean inView = forwardness > 0.5;
                        int halfWidth = width / 2;
                        int height = 9;
                        int halfHeight = height / 2;
                        float scale = 1.0F;
                        int textWidth = this.minecraft.font.width(text);
                        int brightness = Mth.floor(
                            Mth.clampedLerp((float)(Util.getMillis() - closestRecentLocation.time) / (float)(3000.0 * displayTimeMultiplier), 255.0F, 75.0F)
                        );
                        graphics.pose().pushMatrix();
                        graphics.pose().translate(graphics.guiWidth() - halfWidth * 1.0F - 2.0F, graphics.guiHeight() - 35 - row * (height + 1) * 1.0F);
                        graphics.pose().scale(1.0F, 1.0F);
                        graphics.fill(-halfWidth - 1, -halfHeight - 1, halfWidth + 1, halfHeight + 1, this.minecraft.options.getBackgroundColor(0.8F));
                        int textColor = ARGB.color(255, brightness, brightness, brightness);
                        if (!inView) {
                            if (rightness > 0.0) {
                                graphics.text(this.minecraft.font, ">", halfWidth - this.minecraft.font.width(">"), -halfHeight, textColor);
                            } else if (rightness < 0.0) {
                                graphics.text(this.minecraft.font, "<", -halfWidth, -halfHeight, textColor);
                            }
                        }
 
                        graphics.text(this.minecraft.font, text, -textWidth / 2, -halfHeight, textColor);
                        graphics.pose().popMatrix();
                        row++;
                    }
                }
            }
        }
    }
 
    @Override
    public void onPlaySound(SoundInstance sound, WeighedSoundEvents soundEvent, float range) {
        if (soundEvent.getSubtitle() != null) {
            Component text = soundEvent.getSubtitle();
            if (!this.subtitles.isEmpty()) {
                for (SubtitleOverlay.Subtitle subtitle : this.subtitles) {
                    if (subtitle.getText().equals(text)) {
                        subtitle.refresh(new Vec3(sound.getX(), sound.getY(), sound.getZ()));
                        return;
                    }
                }
            }
 
            this.subtitles.add(new SubtitleOverlay.Subtitle(text, range, new Vec3(sound.getX(), sound.getY(), sound.getZ())));
        }
    }
 
    @OnlyIn(Dist.CLIENT)
    record SoundPlayedAt(Vec3 location, long time) {
    }
 
    @OnlyIn(Dist.CLIENT)
    static class Subtitle {
        private final Component text;
        private final float range;
        private final List<SubtitleOverlay.SoundPlayedAt> playedAt = new ArrayList<>();
 
        public Subtitle(Component text, float range, Vec3 location) {
            this.text = text;
            this.range = range;
            this.playedAt.add(new SubtitleOverlay.SoundPlayedAt(location, Util.getMillis()));
        }
 
        public Component getText() {
            return this.text;
        }
 
        public SubtitleOverlay.@Nullable SoundPlayedAt getClosest(Vec3 position) {
            if (this.playedAt.isEmpty()) {
                return null;
            } else {
                return this.playedAt.size() == 1
                    ? this.playedAt.getFirst()
                    : this.playedAt.stream().min(Comparator.comparingDouble(soundPlayedAt -> soundPlayedAt.location().distanceTo(position))).orElse(null);
            }
        }
 
        public void refresh(Vec3 location) {
            this.playedAt.removeIf(soundPlayedAt -> location.equals(soundPlayedAt.location()));
            this.playedAt.add(new SubtitleOverlay.SoundPlayedAt(location, Util.getMillis()));
        }
 
        public boolean isAudibleFrom(Vec3 camera) {
            if (Float.isInfinite(this.range)) {
                return true;
            } else if (this.playedAt.isEmpty()) {
                return false;
            } else {
                SubtitleOverlay.SoundPlayedAt closest = this.getClosest(camera);
                return closest == null ? false : camera.closerThan(closest.location, this.range);
            }
        }
 
        public void purgeOldInstances(double maxAge) {
            long currentTime = Util.getMillis();
            this.playedAt.removeIf(soundPlayedAt -> currentTime - soundPlayedAt.time() > maxAge);
        }
 
        public boolean isStillActive() {
            return !this.playedAt.isEmpty();
        }
    }
}

引用的其他类