CreateMod/src/main/java/com/simibubi/create/content/trains/schedule/ScheduleScreen.java
2023-05-21 21:48:39 +02:00

1086 lines
38 KiB
Java

package com.simibubi.create.content.trains.schedule;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.platform.InputConstants;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f;
import com.simibubi.create.AllPackets;
import com.simibubi.create.Create;
import com.simibubi.create.content.trains.GlobalRailwayManager;
import com.simibubi.create.content.trains.graph.EdgePointType;
import com.simibubi.create.content.trains.graph.TrackGraph;
import com.simibubi.create.content.trains.schedule.condition.ScheduleWaitCondition;
import com.simibubi.create.content.trains.schedule.condition.ScheduledDelay;
import com.simibubi.create.content.trains.schedule.destination.DestinationInstruction;
import com.simibubi.create.content.trains.schedule.destination.ScheduleInstruction;
import com.simibubi.create.content.trains.station.GlobalStation;
import com.simibubi.create.foundation.gui.AllGuiTextures;
import com.simibubi.create.foundation.gui.AllIcons;
import com.simibubi.create.foundation.gui.ModularGuiLine;
import com.simibubi.create.foundation.gui.ModularGuiLineBuilder;
import com.simibubi.create.foundation.gui.UIRenderHelper;
import com.simibubi.create.foundation.gui.element.GuiGameElement;
import com.simibubi.create.foundation.gui.menu.AbstractSimiContainerScreen;
import com.simibubi.create.foundation.gui.menu.GhostItemSubmitPacket;
import com.simibubi.create.foundation.gui.widget.IconButton;
import com.simibubi.create.foundation.gui.widget.Indicator;
import com.simibubi.create.foundation.gui.widget.Indicator.State;
import com.simibubi.create.foundation.gui.widget.Label;
import com.simibubi.create.foundation.gui.widget.SelectionScrollInput;
import com.simibubi.create.foundation.utility.Components;
import com.simibubi.create.foundation.utility.IntAttached;
import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.animation.LerpedFloat;
import com.simibubi.create.foundation.utility.animation.LerpedFloat.Chaser;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.Widget;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.gui.GuiUtils;
public class ScheduleScreen extends AbstractSimiContainerScreen<ScheduleMenu> {
private static final int CARD_HEADER = 22;
private static final int CARD_WIDTH = 195;
private List<Rect2i> extraAreas = Collections.emptyList();
private List<LerpedFloat> horizontalScrolls = new ArrayList<>();
private LerpedFloat scroll = LerpedFloat.linear()
.startWithValue(0);
private Schedule schedule;
private IconButton confirmButton;
private IconButton cyclicButton;
private Indicator cyclicIndicator;
private IconButton resetProgress;
private IconButton skipProgress;
private ScheduleInstruction editingDestination;
private ScheduleWaitCondition editingCondition;
private SelectionScrollInput scrollInput;
private Label scrollInputLabel;
private IconButton editorConfirm, editorDelete;
private ModularGuiLine editorSubWidgets;
private Consumer<Boolean> onEditorClose;
private DestinationSuggestions destinationSuggestions;
public ScheduleScreen(ScheduleMenu menu, Inventory inv, Component title) {
super(menu, inv, title);
schedule = new Schedule();
CompoundTag tag = menu.contentHolder.getOrCreateTag()
.getCompound("Schedule");
if (!tag.isEmpty())
schedule = Schedule.fromTag(tag);
menu.slotsActive = false;
editorSubWidgets = new ModularGuiLine();
}
@Override
protected void init() {
AllGuiTextures bg = AllGuiTextures.SCHEDULE;
setWindowSize(bg.width, bg.height);
super.init();
clearWidgets();
confirmButton = new IconButton(leftPos + bg.width - 42, topPos + bg.height - 24, AllIcons.I_CONFIRM);
confirmButton.withCallback(() -> minecraft.player.closeContainer());
addRenderableWidget(confirmButton);
cyclicIndicator = new Indicator(leftPos + 21, topPos + 196, Components.immutableEmpty());
cyclicIndicator.state = schedule.cyclic ? State.ON : State.OFF;
addRenderableWidget(cyclicIndicator);
cyclicButton = new IconButton(leftPos + 21, topPos + 202, AllIcons.I_REFRESH);
cyclicButton.withCallback(() -> {
schedule.cyclic = !schedule.cyclic;
cyclicIndicator.state = schedule.cyclic ? State.ON : State.OFF;
});
List<Component> tip = cyclicButton.getToolTip();
tip.add(Lang.translateDirect("schedule.loop"));
tip.add(Lang.translateDirect("schedule.loop1")
.withStyle(ChatFormatting.GRAY));
tip.add(Lang.translateDirect("schedule.loop2")
.withStyle(ChatFormatting.GRAY));
addRenderableWidget(cyclicButton);
resetProgress = new IconButton(leftPos + 45, topPos + 202, AllIcons.I_PRIORITY_VERY_HIGH);
resetProgress.withCallback(() -> {
schedule.savedProgress = 0;
resetProgress.active = false;
});
resetProgress.active = schedule.savedProgress > 0 && !schedule.entries.isEmpty();
resetProgress.setToolTip(Lang.translateDirect("schedule.reset"));
addRenderableWidget(resetProgress);
skipProgress = new IconButton(leftPos + 63, topPos + 202, AllIcons.I_PRIORITY_LOW);
skipProgress.withCallback(() -> {
schedule.savedProgress++;
schedule.savedProgress %= schedule.entries.size();
resetProgress.active = schedule.savedProgress > 0;
});
skipProgress.active = schedule.entries.size() > 1;
skipProgress.setToolTip(Lang.translateDirect("schedule.skip"));
addRenderableWidget(skipProgress);
stopEditing();
extraAreas = ImmutableList.of(new Rect2i(leftPos + bg.width, topPos + bg.height - 56, 48, 48));
horizontalScrolls.clear();
for (int i = 0; i < schedule.entries.size(); i++)
horizontalScrolls.add(LerpedFloat.linear()
.startWithValue(0));
}
protected void startEditing(IScheduleInput field, Consumer<Boolean> onClose, boolean allowDeletion) {
onEditorClose = onClose;
confirmButton.visible = false;
cyclicButton.visible = false;
cyclicIndicator.visible = false;
skipProgress.visible = false;
resetProgress.visible = false;
scrollInput = new SelectionScrollInput(leftPos + 56, topPos + 65, 143, 16);
scrollInputLabel = new Label(leftPos + 59, topPos + 69, Components.immutableEmpty()).withShadow();
editorConfirm = new IconButton(leftPos + 56 + 168, topPos + 65 + 22, AllIcons.I_CONFIRM);
if (allowDeletion)
editorDelete = new IconButton(leftPos + 56 - 45, topPos + 65 + 22, AllIcons.I_TRASH);
menu.slotsActive = true;
menu.targetSlotsActive = field.slotsTargeted();
for (int i = 0; i < field.slotsTargeted(); i++) {
ItemStack item = field.getItem(i);
menu.ghostInventory.setStackInSlot(i, item);
AllPackets.getChannel().sendToServer(new GhostItemSubmitPacket(item, i));
}
if (field instanceof ScheduleInstruction instruction) {
int startIndex = 0;
for (int i = 0; i < Schedule.INSTRUCTION_TYPES.size(); i++)
if (Schedule.INSTRUCTION_TYPES.get(i)
.getFirst()
.equals(instruction.getId()))
startIndex = i;
editingDestination = instruction;
updateEditorSubwidgets(editingDestination);
scrollInput.forOptions(Schedule.getTypeOptions(Schedule.INSTRUCTION_TYPES))
.titled(Lang.translateDirect("schedule.instruction_type"))
.writingTo(scrollInputLabel)
.calling(index -> {
ScheduleInstruction newlyCreated = Schedule.INSTRUCTION_TYPES.get(index)
.getSecond()
.get();
if (editingDestination.getId()
.equals(newlyCreated.getId()))
return;
editingDestination = newlyCreated;
updateEditorSubwidgets(editingDestination);
})
.setState(startIndex);
}
if (field instanceof ScheduleWaitCondition cond) {
int startIndex = 0;
for (int i = 0; i < Schedule.CONDITION_TYPES.size(); i++)
if (Schedule.CONDITION_TYPES.get(i)
.getFirst()
.equals(cond.getId()))
startIndex = i;
editingCondition = cond;
updateEditorSubwidgets(editingCondition);
scrollInput.forOptions(Schedule.getTypeOptions(Schedule.CONDITION_TYPES))
.titled(Lang.translateDirect("schedule.condition_type"))
.writingTo(scrollInputLabel)
.calling(index -> {
ScheduleWaitCondition newlyCreated = Schedule.CONDITION_TYPES.get(index)
.getSecond()
.get();
if (editingCondition.getId()
.equals(newlyCreated.getId()))
return;
editingCondition = newlyCreated;
updateEditorSubwidgets(editingCondition);
})
.setState(startIndex);
}
addRenderableWidget(scrollInput);
addRenderableWidget(scrollInputLabel);
addRenderableWidget(editorConfirm);
if (allowDeletion)
addRenderableWidget(editorDelete);
}
private void onDestinationEdited(String text) {
if (destinationSuggestions != null)
destinationSuggestions.updateCommandInfo();
}
protected void stopEditing() {
confirmButton.visible = true;
cyclicButton.visible = true;
cyclicIndicator.visible = true;
skipProgress.visible = true;
resetProgress.visible = true;
if (editingCondition == null && editingDestination == null)
return;
destinationSuggestions = null;
removeWidget(scrollInput);
removeWidget(scrollInputLabel);
removeWidget(editorConfirm);
removeWidget(editorDelete);
IScheduleInput editing = editingCondition == null ? editingDestination : editingCondition;
for (int i = 0; i < editing.slotsTargeted(); i++) {
editing.setItem(i, menu.ghostInventory.getStackInSlot(i));
AllPackets.getChannel().sendToServer(new GhostItemSubmitPacket(ItemStack.EMPTY, i));
}
editorSubWidgets.saveValues(editing.getData());
editorSubWidgets.forEach(this::removeWidget);
editorSubWidgets.clear();
editingCondition = null;
editingDestination = null;
editorConfirm = null;
editorDelete = null;
menu.slotsActive = false;
init();
}
protected void updateEditorSubwidgets(IScheduleInput field) {
destinationSuggestions = null;
menu.targetSlotsActive = field.slotsTargeted();
editorSubWidgets.forEach(this::removeWidget);
editorSubWidgets.clear();
field.initConfigurationWidgets(
new ModularGuiLineBuilder(font, editorSubWidgets, getGuiLeft() + 77, getGuiTop() + 92).speechBubble());
editorSubWidgets.loadValues(field.getData(), this::addRenderableWidget, this::addRenderableOnly);
if (!(field instanceof DestinationInstruction))
return;
editorSubWidgets.forEach(e -> {
if (!(e instanceof EditBox destinationBox))
return;
destinationSuggestions = new DestinationSuggestions(this.minecraft, this, destinationBox, this.font,
getViableStations(field), topPos + 33);
destinationSuggestions.setAllowSuggestions(true);
destinationSuggestions.updateCommandInfo();
destinationBox.setResponder(this::onDestinationEdited);
});
}
private List<IntAttached<String>> getViableStations(IScheduleInput field) {
GlobalRailwayManager railwayManager = Create.RAILWAYS.sided(null);
Set<TrackGraph> viableGraphs = new HashSet<>(railwayManager.trackNetworks.values());
for (ScheduleEntry entry : schedule.entries) {
if (!(entry.instruction instanceof DestinationInstruction destination))
continue;
if (destination == field)
continue;
String filter = destination.getFilterForRegex();
if (filter.isBlank())
continue;
Graphs: for (Iterator<TrackGraph> iterator = viableGraphs.iterator(); iterator.hasNext();) {
TrackGraph trackGraph = iterator.next();
for (GlobalStation station : trackGraph.getPoints(EdgePointType.STATION)) {
if (station.name.matches(filter))
continue Graphs;
}
iterator.remove();
}
}
if (viableGraphs.isEmpty())
viableGraphs = new HashSet<>(railwayManager.trackNetworks.values());
Vec3 position = minecraft.player.position();
Set<String> visited = new HashSet<>();
return viableGraphs.stream()
.flatMap(g -> g.getPoints(EdgePointType.STATION)
.stream())
.filter(station -> station.blockEntityPos != null)
.filter(station -> visited.add(station.name))
.map(station -> IntAttached.with((int) Vec3.atBottomCenterOf(station.blockEntityPos)
.distanceTo(position), station.name))
.toList();
}
@Override
protected void containerTick() {
super.containerTick();
scroll.tickChaser();
for (LerpedFloat lerpedFloat : horizontalScrolls)
lerpedFloat.tickChaser();
if (destinationSuggestions != null)
destinationSuggestions.tick();
schedule.savedProgress =
schedule.entries.isEmpty() ? 0 : Mth.clamp(schedule.savedProgress, 0, schedule.entries.size() - 1);
resetProgress.active = schedule.savedProgress > 0;
skipProgress.active = schedule.entries.size() > 1;
}
@Override
public void render(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) {
partialTicks = minecraft.getFrameTime();
if (menu.slotsActive)
super.render(matrixStack, mouseX, mouseY, partialTicks);
else {
renderBackground(matrixStack);
renderBg(matrixStack, partialTicks, mouseX, mouseY);
for (Widget widget : this.renderables)
widget.render(matrixStack, mouseX, mouseY, partialTicks);
renderForeground(matrixStack, mouseX, mouseY, partialTicks);
}
}
protected void renderSchedule(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) {
UIRenderHelper.swapAndBlitColor(minecraft.getMainRenderTarget(), UIRenderHelper.framebuffer);
UIRenderHelper.drawStretched(matrixStack, leftPos + 33, topPos + 16, 3, 173, -100,
AllGuiTextures.SCHEDULE_STRIP_DARK);
int yOffset = 25;
List<ScheduleEntry> entries = schedule.entries;
float scrollOffset = -scroll.getValue(partialTicks);
for (int i = 0; i <= entries.size(); i++) {
if (schedule.savedProgress == i && !schedule.entries.isEmpty()) {
matrixStack.pushPose();
float expectedY = scrollOffset + topPos + yOffset + 4;
float actualY = Mth.clamp(expectedY, topPos + 18, topPos + 170);
matrixStack.translate(0, actualY, 0);
(expectedY == actualY ? AllGuiTextures.SCHEDULE_POINTER : AllGuiTextures.SCHEDULE_POINTER_OFFSCREEN)
.render(matrixStack, leftPos, 0);
matrixStack.popPose();
}
startStencil(matrixStack, leftPos + 16, topPos + 16, 220, 173);
matrixStack.pushPose();
matrixStack.translate(0, scrollOffset, 0);
if (i == 0 || entries.size() == 0)
UIRenderHelper.drawStretched(matrixStack, leftPos + 33, topPos + 16, 3, 10, -100,
AllGuiTextures.SCHEDULE_STRIP_LIGHT);
if (i == entries.size()) {
if (i > 0)
yOffset += 9;
AllGuiTextures.SCHEDULE_STRIP_END.render(matrixStack, leftPos + 29, topPos + yOffset);
AllGuiTextures.SCHEDULE_CARD_NEW.render(matrixStack, leftPos + 43, topPos + yOffset);
matrixStack.popPose();
endStencil();
break;
}
ScheduleEntry scheduleEntry = entries.get(i);
int cardY = yOffset;
int cardHeight = renderScheduleEntry(matrixStack, scheduleEntry, cardY, mouseX, mouseY, partialTicks);
yOffset += cardHeight;
if (i + 1 < entries.size()) {
AllGuiTextures.SCHEDULE_STRIP_DOTTED.render(matrixStack, leftPos + 29, topPos + yOffset - 3);
yOffset += 10;
}
matrixStack.popPose();
endStencil();
if (!scheduleEntry.instruction.supportsConditions())
continue;
float h = cardHeight - 26;
float y1 = cardY + 24 + scrollOffset;
float y2 = y1 + h;
if (y2 > 189)
h -= y2 - 189;
if (y1 < 16) {
float correction = 16 - y1;
y1 += correction;
h -= correction;
}
if (h <= 0)
continue;
startStencil(matrixStack, leftPos + 43, topPos + y1, 161, h);
matrixStack.pushPose();
matrixStack.translate(0, scrollOffset, 0);
renderScheduleConditions(matrixStack, scheduleEntry, cardY, mouseX, mouseY, partialTicks, cardHeight, i);
matrixStack.popPose();
endStencil();
if (isConditionAreaScrollable(scheduleEntry)) {
startStencil(matrixStack, leftPos + 16, topPos + 16, 220, 173);
matrixStack.pushPose();
matrixStack.translate(0, scrollOffset, 0);
int center = (cardHeight - 8 + CARD_HEADER) / 2;
float chaseTarget = horizontalScrolls.get(i)
.getChaseTarget();
if (!Mth.equal(chaseTarget, 0))
AllGuiTextures.SCHEDULE_SCROLL_LEFT.render(matrixStack, leftPos + 40, topPos + cardY + center);
if (!Mth.equal(chaseTarget, scheduleEntry.conditions.size() - 1))
AllGuiTextures.SCHEDULE_SCROLL_RIGHT.render(matrixStack, leftPos + 203, topPos + cardY + center);
matrixStack.popPose();
endStencil();
}
}
int zLevel = 200;
Matrix4f mat = matrixStack.last()
.pose();
GuiUtils.drawGradientRect(mat, zLevel, leftPos + 16, topPos + 16, leftPos + 16 + 220, topPos + 16 + 10,
0x77000000, 0x00000000);
GuiUtils.drawGradientRect(mat, zLevel, leftPos + 16, topPos + 179, leftPos + 16 + 220, topPos + 179 + 10,
0x00000000, 0x77000000);
UIRenderHelper.swapAndBlitColor(UIRenderHelper.framebuffer, minecraft.getMainRenderTarget());
}
public int renderScheduleEntry(PoseStack matrixStack, ScheduleEntry entry, int yOffset, int mouseX, int mouseY,
float partialTicks) {
int zLevel = -100;
AllGuiTextures light = AllGuiTextures.SCHEDULE_CARD_LIGHT;
AllGuiTextures medium = AllGuiTextures.SCHEDULE_CARD_MEDIUM;
AllGuiTextures dark = AllGuiTextures.SCHEDULE_CARD_DARK;
int cardWidth = CARD_WIDTH;
int cardHeader = CARD_HEADER;
int maxRows = 0;
for (List<ScheduleWaitCondition> list : entry.conditions)
maxRows = Math.max(maxRows, list.size());
boolean supportsConditions = entry.instruction.supportsConditions();
int cardHeight = cardHeader + (supportsConditions ? 24 + maxRows * 18 : 4);
matrixStack.pushPose();
matrixStack.translate(leftPos + 25, topPos + yOffset, 0);
UIRenderHelper.drawStretched(matrixStack, 0, 1, cardWidth, cardHeight - 2, zLevel, light);
UIRenderHelper.drawStretched(matrixStack, 1, 0, cardWidth - 2, cardHeight, zLevel, light);
UIRenderHelper.drawStretched(matrixStack, 1, 1, cardWidth - 2, cardHeight - 2, zLevel, dark);
UIRenderHelper.drawStretched(matrixStack, 2, 2, cardWidth - 4, cardHeight - 4, zLevel, medium);
UIRenderHelper.drawStretched(matrixStack, 2, 2, cardWidth - 4, cardHeader, zLevel,
supportsConditions ? light : medium);
AllGuiTextures.SCHEDULE_CARD_REMOVE.render(matrixStack, cardWidth - 14, 2);
AllGuiTextures.SCHEDULE_CARD_DUPLICATE.render(matrixStack, cardWidth - 14, cardHeight - 14);
int i = schedule.entries.indexOf(entry);
if (i > 0)
AllGuiTextures.SCHEDULE_CARD_MOVE_UP.render(matrixStack, cardWidth, cardHeader - 14);
if (i < schedule.entries.size() - 1)
AllGuiTextures.SCHEDULE_CARD_MOVE_DOWN.render(matrixStack, cardWidth, cardHeader);
UIRenderHelper.drawStretched(matrixStack, 8, 0, 3, cardHeight + 10, zLevel,
AllGuiTextures.SCHEDULE_STRIP_LIGHT);
(supportsConditions ? AllGuiTextures.SCHEDULE_STRIP_TRAVEL : AllGuiTextures.SCHEDULE_STRIP_ACTION)
.render(matrixStack, 4, 6);
if (supportsConditions)
AllGuiTextures.SCHEDULE_STRIP_WAIT.render(matrixStack, 4, 28);
Pair<ItemStack, Component> destination = entry.instruction.getSummary();
renderInput(matrixStack, destination, 26, 5, false, 100);
entry.instruction.renderSpecialIcon(matrixStack, 30, 5);
matrixStack.popPose();
return cardHeight;
}
public void renderScheduleConditions(PoseStack matrixStack, ScheduleEntry entry, int yOffset, int mouseX,
int mouseY, float partialTicks, int cardHeight, int entryIndex) {
int cardWidth = CARD_WIDTH;
int cardHeader = CARD_HEADER;
matrixStack.pushPose();
matrixStack.translate(leftPos + 25, topPos + yOffset, 0);
int xOffset = 26;
float scrollOffset = getConditionScroll(entry, partialTicks, entryIndex);
matrixStack.pushPose();
matrixStack.translate(-scrollOffset, 0, 0);
for (List<ScheduleWaitCondition> list : entry.conditions) {
int maxWidth = getConditionColumnWidth(list);
for (int i = 0; i < list.size(); i++) {
ScheduleWaitCondition scheduleWaitCondition = list.get(i);
Math.max(maxWidth, renderInput(matrixStack, scheduleWaitCondition.getSummary(), xOffset, 29 + i * 18,
i != 0, maxWidth));
scheduleWaitCondition.renderSpecialIcon(matrixStack, xOffset + 4, 29 + i * 18);
}
AllGuiTextures.SCHEDULE_CONDITION_APPEND.render(matrixStack, xOffset + (maxWidth - 10) / 2,
29 + list.size() * 18);
xOffset += maxWidth + 10;
}
AllGuiTextures.SCHEDULE_CONDITION_NEW.render(matrixStack, xOffset - 3, 29);
matrixStack.popPose();
if (xOffset + 16 > cardWidth - 26) {
TransformStack.cast(matrixStack)
.rotateZ(-90);
Matrix4f m = matrixStack.last()
.pose();
GuiUtils.drawGradientRect(m, 200, -cardHeight + 2, 18, -2 - cardHeader, 28, 0x44000000, 0x00000000);
GuiUtils.drawGradientRect(m, 200, -cardHeight + 2, cardWidth - 26, -2 - cardHeader, cardWidth - 16,
0x00000000, 0x44000000);
}
matrixStack.popPose();
}
private boolean isConditionAreaScrollable(ScheduleEntry entry) {
int xOffset = 26;
for (List<ScheduleWaitCondition> list : entry.conditions)
xOffset += getConditionColumnWidth(list) + 10;
return xOffset + 16 > CARD_WIDTH - 26;
}
private float getConditionScroll(ScheduleEntry entry, float partialTicks, int entryIndex) {
float scrollOffset = 0;
float scrollIndex = horizontalScrolls.get(entryIndex)
.getValue(partialTicks);
for (List<ScheduleWaitCondition> list : entry.conditions) {
int maxWidth = getConditionColumnWidth(list);
float partialOfThisColumn = Math.min(1, scrollIndex);
scrollOffset += (maxWidth + 10) * partialOfThisColumn;
scrollIndex -= partialOfThisColumn;
}
return scrollOffset;
}
private int getConditionColumnWidth(List<ScheduleWaitCondition> list) {
int maxWidth = 0;
for (ScheduleWaitCondition scheduleWaitCondition : list)
maxWidth = Math.max(maxWidth, getFieldSize(32, scheduleWaitCondition.getSummary()));
return maxWidth;
}
protected int renderInput(PoseStack matrixStack, Pair<ItemStack, Component> pair, int x, int y, boolean clean,
int minSize) {
ItemStack stack = pair.getFirst();
Component text = pair.getSecond();
boolean hasItem = !stack.isEmpty();
int fieldSize = Math.min(getFieldSize(minSize, pair), 150);
matrixStack.pushPose();
AllGuiTextures left =
clean ? AllGuiTextures.SCHEDULE_CONDITION_LEFT_CLEAN : AllGuiTextures.SCHEDULE_CONDITION_LEFT;
AllGuiTextures middle = AllGuiTextures.SCHEDULE_CONDITION_MIDDLE;
AllGuiTextures item = AllGuiTextures.SCHEDULE_CONDITION_ITEM;
AllGuiTextures right = AllGuiTextures.SCHEDULE_CONDITION_RIGHT;
matrixStack.translate(x, y, 0);
UIRenderHelper.drawStretched(matrixStack, 0, 0, fieldSize, 16, -100, middle);
left.render(matrixStack, clean ? 0 : -3, 0);
right.render(matrixStack, fieldSize - 2, 0);
if (hasItem)
item.render(matrixStack, 3, 0);
if (hasItem) {
item.render(matrixStack, 3, 0);
if (stack.getItem() != Items.STRUCTURE_VOID)
GuiGameElement.of(stack)
.at(4, 0)
.render(matrixStack);
}
if (text != null)
font.drawShadow(matrixStack, font.substrByWidth(text, 120)
.getString(), hasItem ? 28 : 8, 4, 0xff_f2f2ee);
matrixStack.popPose();
return fieldSize;
}
private Component clickToEdit = Lang.translateDirect("gui.schedule.lmb_edit")
.withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.ITALIC);
private Component rClickToDelete = Lang.translateDirect("gui.schedule.rmb_remove")
.withStyle(ChatFormatting.DARK_GRAY, ChatFormatting.ITALIC);
public boolean action(PoseStack ms, double mouseX, double mouseY, int click) {
if (editingCondition != null || editingDestination != null)
return false;
Component empty = Components.immutableEmpty();
int mx = (int) mouseX;
int my = (int) mouseY;
int x = mx - leftPos - 25;
int y = my - topPos - 25;
if (x < 0 || x >= 205)
return false;
if (y < 0 || y >= 173)
return false;
y += scroll.getValue(0);
List<ScheduleEntry> entries = schedule.entries;
for (int i = 0; i < entries.size(); i++) {
ScheduleEntry entry = entries.get(i);
int maxRows = 0;
for (List<ScheduleWaitCondition> list : entry.conditions)
maxRows = Math.max(maxRows, list.size());
int cardHeight = CARD_HEADER + (entry.instruction.supportsConditions() ? 24 + maxRows * 18 : 4);
if (y >= cardHeight + 5) {
y -= cardHeight + 10;
if (y < 0)
return false;
continue;
}
int fieldSize = getFieldSize(100, entry.instruction.getSummary());
if (x > 25 && x <= 25 + fieldSize && y > 4 && y <= 20) {
List<Component> components = new ArrayList<>();
components.addAll(entry.instruction.getTitleAs("instruction"));
components.add(empty);
components.add(clickToEdit);
renderTooltip(ms, components, Optional.empty(), mx, my);
if (click == 0)
startEditing(entry.instruction, confirmed -> {
if (confirmed)
entry.instruction = editingDestination;
}, false);
return true;
}
if (x > 180 && x <= 192) {
if (y > 0 && y <= 14) {
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.remove_entry")), Optional.empty(),
mx, my);
if (click == 0) {
entries.remove(entry);
init();
}
return true;
}
if (y > cardHeight - 14) {
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.duplicate")), Optional.empty(), mx,
my);
if (click == 0) {
entries.add(entries.indexOf(entry), entry.clone());
init();
}
return true;
}
}
if (x > 194) {
if (y > 7 && y <= 20 && i > 0) {
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.move_up")), Optional.empty(), mx,
my);
if (click == 0) {
entries.remove(entry);
entries.add(i - 1, entry);
init();
}
return true;
}
if (y > 20 && y <= 33 && i < entries.size() - 1) {
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.move_down")), Optional.empty(), mx,
my);
if (click == 0) {
entries.remove(entry);
entries.add(i + 1, entry);
init();
}
return true;
}
}
int center = (cardHeight - 8 + CARD_HEADER) / 2;
if (y > center - 1 && y <= center + 7 && isConditionAreaScrollable(entry)) {
float chaseTarget = horizontalScrolls.get(i)
.getChaseTarget();
if (x > 12 && x <= 19 && !Mth.equal(chaseTarget, 0)) {
if (click == 0)
horizontalScrolls.get(i)
.chase(chaseTarget - 1, 0.5f, Chaser.EXP);
return true;
}
if (x > 177 && x <= 184 && !Mth.equal(chaseTarget, entry.conditions.size() - 1)) {
if (click == 0)
horizontalScrolls.get(i)
.chase(chaseTarget + 1, 0.5f, Chaser.EXP);
return true;
}
}
x -= 18;
y -= 28;
if (x < 0 || y < 0 || x > 160)
return false;
x += getConditionScroll(entry, 0, i) - 8;
List<List<ScheduleWaitCondition>> columns = entry.conditions;
for (int j = 0; j < columns.size(); j++) {
List<ScheduleWaitCondition> conditions = columns.get(j);
if (x < 0)
return false;
int w = getConditionColumnWidth(conditions);
if (x >= w) {
x -= w + 10;
continue;
}
int row = y / 18;
if (row < conditions.size() && row >= 0) {
boolean canRemove = conditions.size() > 1 || columns.size() > 1;
List<Component> components = new ArrayList<>();
components.add(Lang.translateDirect("schedule.condition_type")
.withStyle(ChatFormatting.GRAY));
ScheduleWaitCondition condition = conditions.get(row);
components.addAll(condition.getTitleAs("condition"));
components.add(empty);
components.add(clickToEdit);
if (canRemove)
components.add(rClickToDelete);
renderTooltip(ms, components, Optional.empty(), mx, my);
if (canRemove && click == 1) {
conditions.remove(row);
if (conditions.isEmpty())
columns.remove(conditions);
}
if (click == 0)
startEditing(condition, confirmed -> {
conditions.remove(row);
if (confirmed) {
conditions.add(row, editingCondition);
return;
}
if (conditions.isEmpty())
columns.remove(conditions);
}, canRemove);
return true;
}
if (y > 18 * conditions.size() && y <= 18 * conditions.size() + 10 && x >= w / 2 - 5 && x < w / 2 + 5) {
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.add_condition")), Optional.empty(),
mx, my);
if (click == 0)
startEditing(new ScheduledDelay(), confirmed -> {
if (confirmed)
conditions.add(editingCondition);
}, true);
return true;
}
return false;
}
if (x < 0 || x > 15 || y > 20)
return false;
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.alternative_condition")), Optional.empty(),
mx, my);
if (click == 0)
startEditing(new ScheduledDelay(), confirmed -> {
if (!confirmed)
return;
ArrayList<ScheduleWaitCondition> conditions = new ArrayList<>();
conditions.add(editingCondition);
columns.add(conditions);
}, true);
return true;
}
if (x < 18 || x > 33 || y > 14)
return false;
renderTooltip(ms, ImmutableList.of(Lang.translateDirect("gui.schedule.add_entry")), Optional.empty(), mx, my);
if (click == 0)
startEditing(new DestinationInstruction(), confirmed -> {
if (!confirmed)
return;
ScheduleEntry entry = new ScheduleEntry();
ScheduledDelay delay = new ScheduledDelay();
ArrayList<ScheduleWaitCondition> initialConditions = new ArrayList<>();
initialConditions.add(delay);
entry.instruction = editingDestination;
entry.conditions.add(initialConditions);
schedule.entries.add(entry);
}, true);
return true;
}
private int getFieldSize(int minSize, Pair<ItemStack, Component> pair) {
ItemStack stack = pair.getFirst();
Component text = pair.getSecond();
boolean hasItem = !stack.isEmpty();
return Math.max((text == null ? 0 : font.width(text)) + (hasItem ? 20 : 0) + 16, minSize);
}
protected void startStencil(PoseStack matrixStack, float x, float y, float w, float h) {
RenderSystem.clear(GL30.GL_STENCIL_BUFFER_BIT | GL30.GL_DEPTH_BUFFER_BIT, Minecraft.ON_OSX);
GL11.glDisable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilMask(~0);
RenderSystem.clear(GL11.GL_STENCIL_BUFFER_BIT, Minecraft.ON_OSX);
GL11.glEnable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilOp(GL11.GL_REPLACE, GL11.GL_KEEP, GL11.GL_KEEP);
RenderSystem.stencilMask(0xFF);
RenderSystem.stencilFunc(GL11.GL_NEVER, 1, 0xFF);
matrixStack.pushPose();
matrixStack.translate(x, y, 0);
matrixStack.scale(w, h, 1);
GuiUtils.drawGradientRect(matrixStack.last()
.pose(), -100, 0, 0, 1, 1, 0xff000000, 0xff000000);
matrixStack.popPose();
GL11.glEnable(GL11.GL_STENCIL_TEST);
RenderSystem.stencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
RenderSystem.stencilFunc(GL11.GL_EQUAL, 1, 0xFF);
}
protected void endStencil() {
GL11.glDisable(GL11.GL_STENCIL_TEST);
}
@Override
public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) {
if (destinationSuggestions != null
&& destinationSuggestions.mouseClicked((int) pMouseX, (int) pMouseY, pButton))
return true;
if (editorConfirm != null && editorConfirm.isMouseOver(pMouseX, pMouseY) && onEditorClose != null) {
onEditorClose.accept(true);
stopEditing();
return true;
}
if (editorDelete != null && editorDelete.isMouseOver(pMouseX, pMouseY) && onEditorClose != null) {
onEditorClose.accept(false);
stopEditing();
return true;
}
if (action(new PoseStack(), pMouseX, pMouseY, pButton))
return true;
return super.mouseClicked(pMouseX, pMouseY, pButton);
}
@Override
public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) {
if (destinationSuggestions != null && destinationSuggestions.keyPressed(pKeyCode, pScanCode, pModifiers))
return true;
if (editingCondition == null && editingDestination == null)
return super.keyPressed(pKeyCode, pScanCode, pModifiers);
InputConstants.Key mouseKey = InputConstants.getKey(pKeyCode, pScanCode);
boolean hitEnter = getFocused() instanceof EditBox && (pKeyCode == 257 || pKeyCode == 335);
boolean hitE = getFocused() == null && minecraft.options.keyInventory.isActiveAndMatches(mouseKey);
if (hitE || hitEnter) {
onEditorClose.accept(true);
stopEditing();
return true;
}
return super.keyPressed(pKeyCode, pScanCode, pModifiers);
}
@Override
public boolean mouseScrolled(double pMouseX, double pMouseY, double pDelta) {
if (destinationSuggestions != null && destinationSuggestions.mouseScrolled(Mth.clamp(pDelta, -1.0D, 1.0D)))
return true;
if (editingCondition != null || editingDestination != null)
return super.mouseScrolled(pMouseX, pMouseY, pDelta);
if (hasShiftDown()) {
List<ScheduleEntry> entries = schedule.entries;
int y = (int) (pMouseY - topPos - 25 + scroll.getValue());
for (int i = 0; i < entries.size(); i++) {
ScheduleEntry entry = entries.get(i);
int maxRows = 0;
for (List<ScheduleWaitCondition> list : entry.conditions)
maxRows = Math.max(maxRows, list.size());
int cardHeight = CARD_HEADER + 24 + maxRows * 18;
if (y >= cardHeight) {
y -= cardHeight + 9;
if (y < 0)
break;
continue;
}
if (!isConditionAreaScrollable(entry))
break;
if (y < 24)
break;
if (pMouseX < leftPos + 25)
break;
if (pMouseX > leftPos + 205)
break;
float chaseTarget = horizontalScrolls.get(i)
.getChaseTarget();
if (pDelta > 0 && !Mth.equal(chaseTarget, 0)) {
horizontalScrolls.get(i)
.chase(chaseTarget - 1, 0.5f, Chaser.EXP);
return true;
}
if (pDelta < 0 && !Mth.equal(chaseTarget, entry.conditions.size() - 1)) {
horizontalScrolls.get(i)
.chase(chaseTarget + 1, 0.5f, Chaser.EXP);
return true;
}
return false;
}
}
float chaseTarget = scroll.getChaseTarget();
float max = 40 - 173;
for (ScheduleEntry scheduleEntry : schedule.entries) {
int maxRows = 0;
for (List<ScheduleWaitCondition> list : scheduleEntry.conditions)
maxRows = Math.max(maxRows, list.size());
max += CARD_HEADER + 24 + maxRows * 18 + 10;
}
if (max > 0) {
chaseTarget -= pDelta * 12;
chaseTarget = Mth.clamp(chaseTarget, 0, max);
scroll.chase((int) chaseTarget, 0.7f, Chaser.EXP);
} else
scroll.chase(0, 0.7f, Chaser.EXP);
return super.mouseScrolled(pMouseX, pMouseY, pDelta);
}
@Override
protected void renderForeground(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks) {
if (destinationSuggestions != null) {
matrixStack.pushPose();
matrixStack.translate(0, 0, 500);
destinationSuggestions.render(matrixStack, mouseX, mouseY);
matrixStack.popPose();
}
super.renderForeground(matrixStack, mouseX, mouseY, partialTicks);
GuiGameElement.of(menu.contentHolder).<GuiGameElement
.GuiRenderBuilder>at(leftPos + AllGuiTextures.SCHEDULE.width, topPos + AllGuiTextures.SCHEDULE.height - 56,
-200)
.scale(3)
.render(matrixStack);
action(matrixStack, mouseX, mouseY, -1);
if (editingCondition == null && editingDestination == null)
return;
int x = leftPos + 53;
int y = topPos + 87;
if (mouseX < x || mouseY < y || mouseX >= x + 120 || mouseY >= y + 18)
return;
IScheduleInput rendered = editingCondition == null ? editingDestination : editingCondition;
for (int i = 0; i < Math.max(1, rendered.slotsTargeted()); i++) {
List<Component> secondLineTooltip = rendered.getSecondLineTooltip(i);
if (secondLineTooltip == null || (hoveredSlot != menu.getSlot(36 + i) || !hoveredSlot.getItem()
.isEmpty()))
continue;
renderTooltip(matrixStack, secondLineTooltip, Optional.empty(), mouseX, mouseY);
}
}
@Override
protected void renderBg(PoseStack pPoseStack, float pPartialTick, int pMouseX, int pMouseY) {
AllGuiTextures.SCHEDULE.render(pPoseStack, leftPos, topPos);
FormattedCharSequence formattedcharsequence = title.getVisualOrderText();
int center = leftPos + (AllGuiTextures.SCHEDULE.width - 8) / 2;
font.draw(pPoseStack, formattedcharsequence, (float) (center - font.width(formattedcharsequence) / 2),
(float) topPos + 4, 0x505050);
renderSchedule(pPoseStack, pMouseX, pMouseY, pPartialTick);
if (editingCondition == null && editingDestination == null)
return;
this.fillGradient(pPoseStack, 0, 0, this.width, this.height, -1072689136, -804253680);
AllGuiTextures.SCHEDULE_EDITOR.render(pPoseStack, leftPos - 2, topPos + 40);
AllGuiTextures.PLAYER_INVENTORY.render(pPoseStack, leftPos + 38, topPos + 122);
font.draw(pPoseStack, playerInventoryTitle, leftPos + 46, topPos + 128, 0x505050);
formattedcharsequence = editingCondition == null ? Lang.translateDirect("schedule.instruction.editor")
.getVisualOrderText()
: Lang.translateDirect("schedule.condition.editor")
.getVisualOrderText();
font.draw(pPoseStack, formattedcharsequence, (float) (center - font.width(formattedcharsequence) / 2),
(float) topPos + 44, 0x505050);
IScheduleInput rendered = editingCondition == null ? editingDestination : editingCondition;
for (int i = 0; i < rendered.slotsTargeted(); i++)
AllGuiTextures.SCHEDULE_EDITOR_ADDITIONAL_SLOT.render(pPoseStack, leftPos + 53 + 20 * i, topPos + 87);
if (rendered.slotsTargeted() == 0 && !rendered.renderSpecialIcon(pPoseStack, leftPos + 54, topPos + 88)) {
Pair<ItemStack, Component> summary = rendered.getSummary();
ItemStack icon = summary.getFirst();
if (icon.isEmpty())
icon = rendered.getSecondLineIcon();
if (icon.isEmpty())
AllGuiTextures.SCHEDULE_EDITOR_INACTIVE_SLOT.render(pPoseStack, leftPos + 53, topPos + 87);
else
GuiGameElement.of(icon)
.at(leftPos + 54, topPos + 88)
.render(pPoseStack);
}
pPoseStack.pushPose();
pPoseStack.translate(0, getGuiTop() + 87, 0);
editorSubWidgets.renderWidgetBG(getGuiLeft() + 77, pPoseStack);
pPoseStack.popPose();
}
@Override
public void removed() {
super.removed();
AllPackets.getChannel().sendToServer(new ScheduleEditPacket(schedule));
}
@Override
public List<Rect2i> getExtraAreas() {
return extraAreas;
}
public Font getFont() {
return font;
}
}