forked from Clones/Controlify
➕ Implement basic container guide (unfinished)
This commit is contained in:
@ -0,0 +1,4 @@
|
||||
package dev.isxander.controlify.gui.guide;
|
||||
|
||||
public class ContainerButtonGuide {
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package dev.isxander.controlify.gui.guide;
|
||||
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record ContainerGuideCtx(@Nullable Slot hoveredSlot, ItemStack holdingItem, boolean cursorOutsideContainer) {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package dev.isxander.controlify.gui.guide;
|
||||
|
||||
import dev.isxander.controlify.api.bind.ControllerBinding;
|
||||
import dev.isxander.controlify.api.guide.ActionPriority;
|
||||
import dev.isxander.controlify.api.guide.GuideActionNameSupplier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record GuideAction<T>(ControllerBinding binding, GuideActionNameSupplier<T> name, ActionPriority priority) implements Comparable<GuideAction<T>> {
|
||||
public GuideAction(ControllerBinding binding, GuideActionNameSupplier<T> name) {
|
||||
this(binding, name, ActionPriority.NORMAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull GuideAction<T> o) {
|
||||
return this.priority().compareTo(o.priority());
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package dev.isxander.controlify.gui.guide;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.api.bind.BindRenderer;
|
||||
import dev.isxander.controlify.gui.DrawSize;
|
||||
import dev.isxander.controlify.gui.layout.RenderComponent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiComponent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.joml.Vector2i;
|
||||
import org.joml.Vector2ic;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class GuideActionRenderer<T> implements RenderComponent {
|
||||
private final GuideAction<T> guideAction;
|
||||
private final boolean rtl;
|
||||
private final boolean textContrast;
|
||||
|
||||
private Optional<Component> name = Optional.empty();
|
||||
|
||||
public GuideActionRenderer(GuideAction<T> action, boolean rtl, boolean textContrast) {
|
||||
this.guideAction = action;
|
||||
this.rtl = rtl;
|
||||
this.textContrast = textContrast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(PoseStack stack, int x, int y, float deltaTime) {
|
||||
if (!isVisible())
|
||||
return;
|
||||
|
||||
Font font = Minecraft.getInstance().font;
|
||||
|
||||
BindRenderer renderer = guideAction.binding().renderer();
|
||||
DrawSize drawSize = renderer.size();
|
||||
int textWidth = font.width(name.get());
|
||||
|
||||
renderer.render(stack, x + (!rtl ? 0 : textWidth + 2), y + drawSize.height() / 2);
|
||||
|
||||
int textX = x + (rtl ? 0 : drawSize.width() + 2);
|
||||
int textY = y + drawSize.height() / 2 - font.lineHeight / 2;
|
||||
|
||||
if (textContrast)
|
||||
GuiComponent.fill(stack, textX - 1, textY - 1, textX + textWidth + 1, textY + font.lineHeight + 1, 0x80000000);
|
||||
font.draw(stack, name.get(), textX, textY, 0xFFFFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector2ic size() {
|
||||
DrawSize bindSize = guideAction.binding().renderer().size();
|
||||
Font font = Minecraft.getInstance().font;
|
||||
|
||||
return new Vector2i(bindSize.width() + 2 + name.map(font::width).orElse(-2), Math.max(bindSize.height(), font.lineHeight));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return name.isPresent() && !guideAction.binding().isUnbound();
|
||||
}
|
||||
|
||||
public void updateName(T ctx) {
|
||||
name = guideAction.name().supply(ctx);
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
package dev.isxander.controlify.gui.guide;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dev.isxander.controlify.api.bind.ControllerBinding;
|
||||
import dev.isxander.controlify.api.guide.ActionPriority;
|
||||
import dev.isxander.controlify.api.guide.GuideActionNameSupplier;
|
||||
import dev.isxander.controlify.api.ingameguide.*;
|
||||
import dev.isxander.controlify.compatibility.ControlifyCompat;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.gui.layout.AnchorPoint;
|
||||
import dev.isxander.controlify.gui.layout.ColumnLayoutComponent;
|
||||
import dev.isxander.controlify.gui.layout.PositionedComponent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.effect.MobEffects;
|
||||
import net.minecraft.world.entity.EquipmentSlot;
|
||||
import net.minecraft.world.entity.projectile.ProjectileUtil;
|
||||
import net.minecraft.world.item.ElytraItem;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.phys.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class InGameButtonGuide implements IngameGuideRegistry {
|
||||
private final Controller<?, ?> controller;
|
||||
private final LocalPlayer player;
|
||||
private final Minecraft minecraft = Minecraft.getInstance();
|
||||
|
||||
private final List<GuideAction<IngameGuideContext>> leftGuides = new ArrayList<>();
|
||||
private final List<GuideAction<IngameGuideContext>> rightGuides = new ArrayList<>();
|
||||
|
||||
private final PositionedComponent<ColumnLayoutComponent<GuideActionRenderer<IngameGuideContext>>> leftLayout;
|
||||
private final PositionedComponent<ColumnLayoutComponent<GuideActionRenderer<IngameGuideContext>>> rightLayout;
|
||||
|
||||
public InGameButtonGuide(Controller<?, ?> controller, LocalPlayer localPlayer) {
|
||||
this.controller = controller;
|
||||
this.player = localPlayer;
|
||||
|
||||
registerDefaultActions();
|
||||
ControlifyEvents.INGAME_GUIDE_REGISTRY.invoker().onRegisterIngameGuide(controller.bindings(), this);
|
||||
|
||||
Collections.sort(leftGuides);
|
||||
Collections.sort(rightGuides);
|
||||
|
||||
leftLayout = new PositionedComponent<>(
|
||||
ColumnLayoutComponent.<GuideActionRenderer<IngameGuideContext>>builder()
|
||||
.spacing(2)
|
||||
.colPadding(4, 4)
|
||||
.elementPosition(ColumnLayoutComponent.ElementPosition.LEFT)
|
||||
.elements(leftGuides.stream().map(guide -> new GuideActionRenderer<>(guide, false, true)).toList())
|
||||
.build(),
|
||||
AnchorPoint.TOP_LEFT,
|
||||
0, 0,
|
||||
AnchorPoint.TOP_LEFT
|
||||
);
|
||||
|
||||
rightLayout = new PositionedComponent<>(
|
||||
ColumnLayoutComponent.<GuideActionRenderer<IngameGuideContext>>builder()
|
||||
.spacing(2)
|
||||
.colPadding(4, 4)
|
||||
.elementPosition(ColumnLayoutComponent.ElementPosition.RIGHT)
|
||||
.elements(rightGuides.stream().map(guide -> new GuideActionRenderer<>(guide, true, true)).toList())
|
||||
.build(),
|
||||
AnchorPoint.TOP_RIGHT,
|
||||
0, 0,
|
||||
AnchorPoint.TOP_RIGHT
|
||||
);
|
||||
}
|
||||
|
||||
public void renderHud(PoseStack poseStack, float tickDelta, int width, int height) {
|
||||
if (!controller.config().showIngameGuide || minecraft.screen != null || minecraft.options.renderDebug)
|
||||
return;
|
||||
|
||||
ControlifyCompat.ifBeginHudBatching();
|
||||
|
||||
leftLayout.render(poseStack, tickDelta);
|
||||
rightLayout.render(poseStack, tickDelta);
|
||||
|
||||
ControlifyCompat.ifEndHudBatching();
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
IngameGuideContext context = new IngameGuideContext(Minecraft.getInstance(), player, minecraft.level, calculateHitResult(), controller);
|
||||
|
||||
leftLayout.getComponent().getChildComponents().forEach(renderer -> renderer.updateName(context));
|
||||
rightLayout.getComponent().getChildComponents().forEach(renderer -> renderer.updateName(context));
|
||||
|
||||
leftLayout.updatePosition();
|
||||
rightLayout.updatePosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGuideAction(ControllerBinding binding, ActionLocation location, GuideActionNameSupplier<IngameGuideContext> supplier) {
|
||||
this.registerGuideAction(binding, location, ActionPriority.NORMAL, supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGuideAction(ControllerBinding binding, ActionLocation location, ActionPriority priority, GuideActionNameSupplier<IngameGuideContext> supplier) {
|
||||
if (location == ActionLocation.LEFT)
|
||||
leftGuides.add(new GuideAction<>(binding, supplier, priority));
|
||||
else
|
||||
rightGuides.add(new GuideAction<>(binding, supplier, priority));
|
||||
}
|
||||
|
||||
private void registerDefaultActions() {
|
||||
var options = Minecraft.getInstance().options;
|
||||
registerGuideAction(controller.bindings().JUMP, ActionLocation.LEFT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.getAbilities().flying)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.fly_up"));
|
||||
|
||||
if (player.isOnGround())
|
||||
return Optional.of(Component.translatable("key.jump"));
|
||||
|
||||
if (player.isInWater())
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.swim_up"));
|
||||
|
||||
if (!player.isOnGround() && !player.isFallFlying() && !player.isInWater() && !player.hasEffect(MobEffects.LEVITATION)) {
|
||||
var chestStack = player.getItemBySlot(EquipmentSlot.CHEST);
|
||||
if (chestStack.is(Items.ELYTRA) && ElytraItem.isFlyEnabled(chestStack))
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.start_elytra"));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().SNEAK, ActionLocation.LEFT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.getVehicle() != null)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.dismount"));
|
||||
if (player.getAbilities().flying)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.fly_down"));
|
||||
if (player.isInWater() && !player.isOnGround())
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.swim_down"));
|
||||
if (ctx.controller().config().toggleSneak) {
|
||||
return Optional.of(Component.translatable(player.input.shiftKeyDown ? "controlify.guide.ingame.stop_sneaking" : "controlify.guide.ingame.start_sneaking"));
|
||||
} else {
|
||||
if (!player.input.shiftKeyDown)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.sneak"));
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().SPRINT, ActionLocation.LEFT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (!options.keySprint.isDown()) {
|
||||
if (!player.input.getMoveVector().equals(Vec2.ZERO)) {
|
||||
if (player.isUnderWater())
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.start_swimming"));
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.start_sprinting"));
|
||||
}
|
||||
} else if (ctx.controller().config().toggleSprint) {
|
||||
if (player.isUnderWater())
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.stop_swimming"));
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.stop_sprinting"));
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().INVENTORY, ActionLocation.RIGHT, (ctx) -> {
|
||||
if (ctx.client().screen == null)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.inventory"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().ATTACK, ActionLocation.RIGHT, (ctx) -> {
|
||||
var hitResult = ctx.hitResult();
|
||||
if (hitResult.getType() == HitResult.Type.ENTITY)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.attack"));
|
||||
if (hitResult.getType() == HitResult.Type.BLOCK)
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.break"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().USE, ActionLocation.RIGHT, (ctx) -> {
|
||||
var hitResult = ctx.hitResult();
|
||||
var player = ctx.player();
|
||||
if (hitResult.getType() == HitResult.Type.ENTITY)
|
||||
if (player.isSpectator())
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.spectate"));
|
||||
else
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.interact"));
|
||||
if (hitResult.getType() == HitResult.Type.BLOCK || player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND))
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.use"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().DROP, ActionLocation.RIGHT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND))
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.drop"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().SWAP_HANDS, ActionLocation.RIGHT, (ctx) -> {
|
||||
var player = ctx.player();
|
||||
if (player.hasItemInSlot(EquipmentSlot.MAINHAND) || player.hasItemInSlot(EquipmentSlot.OFFHAND))
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.swap_hands"));
|
||||
return Optional.empty();
|
||||
});
|
||||
registerGuideAction(controller.bindings().PICK_BLOCK, ActionLocation.RIGHT, (ctx) -> {
|
||||
if (ctx.hitResult().getType() == HitResult.Type.BLOCK && ctx.player().isCreative())
|
||||
return Optional.of(Component.translatable("controlify.guide.ingame.pick_block"));
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
private HitResult calculateHitResult() {
|
||||
double pickRange = minecraft.gameMode.getPickRange();
|
||||
HitResult pickResult = player.pick(pickRange, 1f, false);
|
||||
|
||||
Vec3 eyePos = player.getEyePosition(1f);
|
||||
|
||||
if (minecraft.gameMode.hasFarPickRange()) {
|
||||
pickRange = 6.0;
|
||||
}
|
||||
double maxPickRange = pickResult.getLocation().distanceToSqr(eyePos);
|
||||
|
||||
Vec3 viewVec = player.getViewVector(1f);
|
||||
Vec3 reachVec = eyePos.add(viewVec.x * pickRange, viewVec.y * pickRange, viewVec.z * pickRange);
|
||||
AABB box = player.getBoundingBox().expandTowards(viewVec.scale(pickRange)).inflate(1d, 1d, 1d);
|
||||
|
||||
EntityHitResult entityHitResult = ProjectileUtil.getEntityHitResult(
|
||||
player, eyePos, reachVec, box, (entity) -> !entity.isSpectator() && entity.isPickable(), maxPickRange
|
||||
);
|
||||
|
||||
if (entityHitResult != null && entityHitResult.getLocation().distanceToSqr(eyePos) < pickResult.getLocation().distanceToSqr(eyePos)) {
|
||||
return entityHitResult;
|
||||
} else {
|
||||
return pickResult;
|
||||
}
|
||||
}
|
||||
}
|
@ -118,7 +118,7 @@ public class ColumnLayoutComponent<T extends RenderComponent> extends AbstractLa
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> elementPadding(int padding) {
|
||||
public Builder<T> spacing(int padding) {
|
||||
this.componentPaddingVertical = padding;
|
||||
return this;
|
||||
}
|
||||
|
@ -78,6 +78,12 @@ public class RowLayoutComponent<T extends RenderComponent> extends AbstractLayou
|
||||
.sum() - elementPaddingHorizontal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisible() {
|
||||
return this.getChildComponents().stream()
|
||||
.anyMatch(RenderComponent::isVisible);
|
||||
}
|
||||
|
||||
public static <T extends RenderComponent> Builder<T> builder() {
|
||||
return new Builder<>();
|
||||
}
|
||||
@ -115,7 +121,7 @@ public class RowLayoutComponent<T extends RenderComponent> extends AbstractLayou
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> elementPadding(int padding) {
|
||||
public Builder<T> spacing(int padding) {
|
||||
this.elementPaddingHorizontal = padding;
|
||||
return this;
|
||||
}
|
||||
|
Reference in New Issue
Block a user