RyanHub – file viewer
filename: common/src/main/java/rearth/oritech/client/ui/AugmentSelectionScreen.java
branch: 1.21
back to repo
package rearth.oritech.client.ui;

import io.wispforest.owo.ui.base.BaseOwoScreen;
import io.wispforest.owo.ui.component.Components;
import io.wispforest.owo.ui.component.LabelComponent;
import io.wispforest.owo.ui.component.TextureComponent;
import io.wispforest.owo.ui.container.Containers;
import io.wispforest.owo.ui.container.FlowLayout;
import io.wispforest.owo.ui.core.*;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.joml.Vector2f;
import org.joml.Vector2i;
import rearth.oritech.Oritech;
import rearth.oritech.OritechClient;
import rearth.oritech.block.entity.augmenter.PlayerAugments;
import rearth.oritech.network.NetworkContent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

public class AugmentSelectionScreen extends BaseOwoScreen<FlowLayout> {
    
    private Component lastFocused;
    private LabelComponent noOpButton;
    private FlowLayout root;
    
    private final List<Component> augments = new ArrayList<>();
    private final HashMap<Component, Identifier> augmentIDs = new HashMap<>();
    private final HashMap<Component, Float> augmentSizes = new HashMap<>();
    
    @Override
    protected @NotNull OwoUIAdapter<FlowLayout> createAdapter() {
        return OwoUIAdapter.create(this, Containers::verticalFlow);
    }
    
    @Override
    protected void build(FlowLayout rootComponent) {
        rootComponent.surface(Surface.VANILLA_TRANSLUCENT)
          .horizontalAlignment(HorizontalAlignment.CENTER)
          .verticalAlignment(VerticalAlignment.CENTER);
        
        this.root = rootComponent;
        
        addAugments(rootComponent);
        
        // add tooltip in bot left
        var label = Components.label(Text.translatable("oritech.text.augment_toggle"));
        rootComponent.child(label.positioning(Positioning.relative(2, 90)));
    }
    
    @Override
    public void tick() {
        super.tick();
        
        // update noop text
        if (lastFocused == noOpButton) {
            noOpButton.text(Text.literal("Exit"));
        } else if (lastFocused != null && lastFocused instanceof TextureComponent lastButton) {
            var focusedAugmentId = augmentIDs.get(lastButton);
            if (focusedAugmentId == null) return;
            var focusedAugment = Text.translatable(PlayerModifierScreen.augmentKey(focusedAugmentId));
            noOpButton.text(focusedAugment);
        }
        
    }
    
    @Override
    public void render(DrawContext context, int mouseX, int mouseY, float delta) {
        super.render(context, mouseX, mouseY, delta);
        
        if (augments.isEmpty()) return;
        
        var mousingOver = augments.stream().findFirst().get();
        var minDist = Integer.MAX_VALUE;
        
        for (var augment : augments) {
            var mousePos = new Vector2i(mouseX, mouseY);
            var augmentPos = new Vector2i(augment.x() + augment.width() / 2, augment.y() + augment.height() / 2);
            
            var dist = mousePos.distance(augmentPos);
            
            if (augment == noOpButton) {
                dist *= 2;  // give no op less prio
            }
            if (dist < minDist) {
                minDist = (int) dist;
                mousingOver = augment;
            }
        }
        
        lastFocused = mousingOver;
        
        var centerPos = new Vector2i(noOpButton.x() + noOpButton.width() / 2, noOpButton.y() + noOpButton.height() / 2);
        var selectedPos = new Vector2i(lastFocused.x() + lastFocused.width() / 2, lastFocused.y() + lastFocused.height() / 2);
        var mousePos = new Vector2i(mouseX, mouseY);
        
        var mouseLineColor = new Color(150 / 255f, 180 / 255f, 220 / 255f, 0.8f).argb();
        drawLine(context, centerPos, selectedPos, mouseLineColor);
        drawLine(context, selectedPos, mousePos, mouseLineColor);
        
        var screenSize = root.height();
        var innerRadius = 0.175;
        var outerRadius = 0.4;
        var screenSizeX = root.width();
        var screenSizeY = root.height();
        var middleX = screenSizeX / 2f;
        var middleY = screenSizeY / 2f;
        
        var augmentCount = augments.size() - 1;
        var radSizePerElement = Math.toRadians((double) 360 / augmentCount) * 0.8f;
        var screenMiddle = new Vector2i((int) middleX, (int) middleY);
        
        for (int i = 0; i < augments.size(); i++) {
            var augment = augments.get(i);
            var augmentId = augmentIDs.get(augment);
            var augmentData = PlayerAugments.allAugments.get(augmentId);
            if (augmentData == null) continue;
            var isEnabled = augmentData.isEnabled(client.player);
            var active = mousingOver == augment;
            
            var color = new Color(180 / 255f, 30 / 255f, 30 / 255f, 0.3f).argb();  // red
            if (isEnabled) {
                color = new Color(30 / 255f, 180 / 255f, 30 / 255f, 0.3f).argb();  // green
            }
            if (active)
                color = new Color(160 / 255f, 180 / 255f, 220 / 255f, 0.5f).argb(); // white
            
            var augmentRad = (i / (float) augmentCount) * 2 * Math.PI - Math.toRadians(90);
            
            var sizeTarget = 1f;
            if (active) sizeTarget = 1.05f;
            var lastSize = augmentSizes.getOrDefault(augment, 1f);
            
            var usedSize = MathHelper.lerp(0.15, lastSize, sizeTarget);
            augmentSizes.put(augment, (float) usedSize);
            
            var activeInnerRadius = innerRadius / usedSize;
            var activeOuterRadius = outerRadius * usedSize;
            
            if (i != augments.size() - 1)
                drawPieSegmented(context, augmentRad, radSizePerElement, screenMiddle, activeInnerRadius, activeOuterRadius, screenSize, color, 16);
            
        }
        
        var centerSelected = mousingOver == noOpButton;
        var color = new Color(160 / 255f, 180 / 255f, 180 / 255f, 0.3f).argb();
        if (centerSelected) {
            color = new Color(160 / 255f, 180 / 255f, 220 / 255f, 0.5f).argb();
        }
        drawPieSegmented(context, 0, Math.toRadians(360), screenMiddle, 0, innerRadius * 0.6, screenSize, color, 32);
        
    }
    
    private static void drawPieSegmented(DrawContext context, double augmentRad, double radSize, Vector2i screenMiddle, double innerRadius, double outerRadius, double screenSize, int color, int segmentCount) {
        
        // total size
        var segmentSize = radSize / segmentCount;
        var augmentRadBegin = augmentRad - radSize * 0.5f;
        
        for (int i = 0; i < segmentCount; i++) {
            
            var fromRad = augmentRadBegin + segmentSize * i;
            var toRad = fromRad + segmentSize;
            
            var a = new Vector2i(screenMiddle).add(new Vector2i((int) (innerRadius * Math.cos(fromRad) * screenSize), (int) (innerRadius * Math.sin(fromRad) * screenSize)));
            var b = new Vector2i(screenMiddle).add(new Vector2i((int) (outerRadius * Math.cos(fromRad) * screenSize), (int) (outerRadius * Math.sin(fromRad) * screenSize)));
            var c = new Vector2i(screenMiddle).add(new Vector2i((int) (innerRadius * Math.cos(toRad) * screenSize), (int) (innerRadius * Math.sin(toRad) * screenSize)));
            var d = new Vector2i(screenMiddle).add(new Vector2i((int) (outerRadius * Math.cos(toRad) * screenSize), (int) (outerRadius * Math.sin(toRad) * screenSize)));
            drawRect(context, d, b, a, c, color);
        }
    }
    
    private static void drawLine(DrawContext context, Vector2i from, Vector2i to, int color) {
        
        if (from.distanceSquared(to) < 0.1) return;
        
        var matrices = context.getMatrices();
        matrices.push();
        
        var pos = matrices.peek().getPositionMatrix();
        var normal = getNormalVector(from, to).normalize();
        var offset = normal.mul(1);
        var zIndex = 0;
        
        var buffer = context.getVertexConsumers().getBuffer(RenderLayer.getGui());
        buffer.vertex(pos, from.x - offset.x, from.y - offset.y, zIndex).color(color);
        buffer.vertex(pos, from.x + offset.x, from.y + offset.y, zIndex).color(color);
        buffer.vertex(pos, to.x + offset.x, to.y + offset.y, zIndex).color(color);
        buffer.vertex(pos, to.x - offset.x, to.y - offset.y, zIndex).color(color);
        context.draw();
        
        matrices.pop();
    }
    
    private static void drawRect(DrawContext context, Vector2i a, Vector2i b, Vector2i c, Vector2i d, int color) {
        
        var matrices = context.getMatrices();
        matrices.push();
        
        var pos = matrices.peek().getPositionMatrix();
        var zIndex = 0;
        
        var buffer = context.getVertexConsumers().getBuffer(RenderLayer.getGui());
        buffer.vertex(pos, a.x, a.y, zIndex).color(color);
        buffer.vertex(pos, b.x, b.y, zIndex).color(color);
        buffer.vertex(pos, c.x, c.y, zIndex).color(color);
        buffer.vertex(pos, d.x, d.y, zIndex).color(color);
        context.draw();
        
        matrices.pop();
    }
    
    public static Vector2f getNormalVector(Vector2i point1, Vector2i point2) {
        int dx = point2.x - point1.x;
        int dy = point2.y - point1.y;
        
        // A 90-degree rotation can be achieved by swapping x and y and negating one of them
        return new Vector2f(-dy, dx);
    }
    
    private void addAugments(FlowLayout parent) {
        
        var player = Objects.requireNonNull(this.client).player;
        
        var augmentsToAdd = new ArrayList<PlayerAugments.PlayerAugment>();
        
        for (var augment : PlayerAugments.allAugments.values()) {
            var isInstalled = augment.isInstalled(player);
            var isToggleable = augment.toggleable;
            
            if (!isInstalled || !isToggleable) continue;
            
            augmentsToAdd.add(augment);
            
        }
        
        var augmentCount = augmentsToAdd.size();
        var radius = 30;
        
        var screenSizeX = this.width;
        var screenSizeY = this.height;
        var sideRelative = screenSizeY / (float) screenSizeX;
        
        for (int i = 0; i < augmentsToAdd.size(); i++) {
            var augment = augmentsToAdd.get(i);
            var angleRad = (i / (float) augmentCount) * 2 * Math.PI - Math.toRadians(90);
            var offsetX = radius * Math.cos(angleRad);
            var offsetY = radius * Math.sin(angleRad);
            
            final var id = augment.id;
            var iconTexture = Oritech.id("textures/gui/augments/" + id.getPath() + ".png");
            var label = Components.texture(iconTexture, 0, 0, 24, 24, 24, 24);
            label.positioning(Positioning.relative((int) (50 + offsetX * sideRelative), (int) (50 + offsetY)));
            label.sizing(Sizing.fixed(screenSizeY / 12));
            
            augments.add(label);
            parent.child(label);
            augmentIDs.put(label, id);
            augmentSizes.put(label, 1f);
            
        }
        
        var noOpLabel = Components.label(Text.literal("Nothing"));
        noOpLabel.positioning(Positioning.relative(50, 50));
        augments.add(noOpLabel);
        noOpButton = noOpLabel;
        parent.child(noOpLabel);
        
    }
    
    private void toggleAugment(Identifier id) {
        NetworkContent.UI_CHANNEL.clientHandle().send(new NetworkContent.AugmentPlayerTogglePacket(id));
        
        var instance = PlayerAugments.allAugments.get(id);
        if (instance.autoSync) {
            // directly toggle on client, instead of sending the toggle command to server, and then syncing to client
            instance.toggle(this.client.player);
        }
    }
    
    @Override
    public void close() {
        
        if (lastFocused != null && augmentIDs.containsKey(lastFocused)) {
            var id = augmentIDs.get(lastFocused);
            toggleAugment(id);
        }
        
        OritechClient.activeScreen = null;
        super.close();
    }
}