aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/gregtech/common
diff options
context:
space:
mode:
authorquerns <33518699+querns@users.noreply.github.com>2023-09-08 15:39:21 -0500
committerGitHub <noreply@github.com>2023-09-08 22:39:21 +0200
commit7f837715a8377b24853bf0fea02f23c210b47cfc (patch)
treeab75df3f15857a83ee653750cb5090225a7c089d /src/main/java/gregtech/common
parente4f17b7b40503fd34d9dae447802453480b9577c (diff)
downloadGT5-Unofficial-7f837715a8377b24853bf0fea02f23c210b47cfc.tar.gz
GT5-Unofficial-7f837715a8377b24853bf0fea02f23c210b47cfc.tar.bz2
GT5-Unofficial-7f837715a8377b24853bf0fea02f23c210b47cfc.zip
Adds status messages, action buttons to UI of multiblock drills (#2270)
* Adds several UI elements to multiblock drills * Spotless * Adds formatNumbers calls in places where it'd been missed * Eliminate wildcard import * Add @NotNull annotations to new fields and methods
Diffstat (limited to 'src/main/java/gregtech/common')
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ConcreteBackfillerBase.java39
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java318
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java85
-rw-r--r--src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java272
4 files changed, 658 insertions, 56 deletions
diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ConcreteBackfillerBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ConcreteBackfillerBase.java
index 1c7f802a38..3e9238003f 100644
--- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ConcreteBackfillerBase.java
+++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_ConcreteBackfillerBase.java
@@ -9,13 +9,20 @@ import static gregtech.api.enums.GT_Values.VN;
import java.util.List;
import net.minecraft.item.ItemStack;
+import net.minecraft.util.StatCollector;
import com.google.common.collect.ImmutableList;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedColumn;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
import gregtech.api.GregTech_API;
import gregtech.api.enums.Materials;
import gregtech.api.interfaces.IHatchElement;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
import gregtech.api.util.GT_Utility;
@@ -23,12 +30,21 @@ public abstract class GT_MetaTileEntity_ConcreteBackfillerBase extends GT_MetaTi
private int mLastXOff = 0, mLastZOff = 0;
+ /** Used to drive the readout in the GUI for the backfiller's current y-level. */
+ private int clientYHead;
+
public GT_MetaTileEntity_ConcreteBackfillerBase(int aID, String aName, String aNameRegional) {
super(aID, aName, aNameRegional);
+ initRecipeResults();
}
public GT_MetaTileEntity_ConcreteBackfillerBase(String aName) {
super(aName);
+ initRecipeResults();
+ }
+
+ private void initRecipeResults() {
+ addResultMessage(STATE_UPWARD, true, "backfiller_working");
}
protected GT_Multiblock_Tooltip_Builder createTooltip(String aStructureName) {
@@ -107,6 +123,7 @@ public abstract class GT_MetaTileEntity_ConcreteBackfillerBase extends GT_MetaTi
} else {
workState = STATE_DOWNWARD;
stopMachine();
+ setShutdownReason(StatCollector.translateToLocal("GT5U.gui.text.backfiller_finished"));
return false;
}
}
@@ -123,7 +140,10 @@ public abstract class GT_MetaTileEntity_ConcreteBackfillerBase extends GT_MetaTi
}
private boolean tryRefillBlock(int aX, int aY, int aZ) {
- if (!tryConsumeFluid()) return false;
+ if (!tryConsumeFluid()) {
+ setRuntimeFailureReason(CheckRecipeResultRegistry.BACKFILLER_NO_CONCRETE);
+ return false;
+ }
getBaseMetaTileEntity().getWorld()
.setBlock(aX, aY, aZ, GregTech_API.sBlockConcretes, 8, 3);
return true;
@@ -136,4 +156,21 @@ public abstract class GT_MetaTileEntity_ConcreteBackfillerBase extends GT_MetaTi
}
return true;
}
+
+ @Override
+ protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
+ super.drawTexts(screenElements, inventorySlot);
+ screenElements
+ .widget(
+ TextWidget
+ .dynamicString(
+ () -> StatCollector.translateToLocalFormatted(
+ "GT5U.gui.text.backfiller_current_area",
+ GT_Utility.formatNumbers(clientYHead)))
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_UPWARD))
+ .widget(new FakeSyncWidget.IntegerSyncer(this::getYHead, newInt -> clientYHead = newInt))
+ .widget(new FakeSyncWidget.IntegerSyncer(() -> workState, newInt -> workState = newInt));
+ }
}
diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java
index 77b5073c86..983e6c5998 100644
--- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java
+++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_DrillerBase.java
@@ -16,12 +16,16 @@ import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_ORE_DRILL_ACT
import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_ORE_DRILL_ACTIVE_GLOW;
import static gregtech.api.enums.Textures.BlockIcons.OVERLAY_FRONT_ORE_DRILL_GLOW;
import static gregtech.api.enums.Textures.BlockIcons.getCasingTextureForId;
+import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY;
import static gregtech.api.util.GT_StructureUtility.buildHatchAdder;
import static gregtech.api.util.GT_StructureUtility.ofFrame;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import net.minecraft.block.Block;
import net.minecraft.entity.player.EntityPlayer;
@@ -29,6 +33,7 @@ import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.StatCollector;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.common.util.ForgeDirection;
@@ -41,10 +46,22 @@ import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructa
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
import com.gtnewhorizon.structurelib.structure.StructureDefinition;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.math.Pos2d;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedColumn;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
import gregtech.api.GregTech_API;
import gregtech.api.enums.ItemList;
import gregtech.api.enums.Materials;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.widgets.GT_DisabledWhileActiveButton;
import gregtech.api.interfaces.IChunkLoader;
import gregtech.api.interfaces.IHatchElement;
import gregtech.api.interfaces.ITexture;
@@ -56,6 +73,7 @@ import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_DataA
import gregtech.api.metatileentity.implementations.GT_MetaTileEntity_Hatch_Energy;
import gregtech.api.objects.GT_ChunkManager;
import gregtech.api.recipe.check.CheckRecipeResult;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
import gregtech.api.recipe.check.SimpleCheckRecipeResult;
import gregtech.api.render.TextureFactory;
import gregtech.api.util.GT_ModHandler;
@@ -127,13 +145,29 @@ public abstract class GT_MetaTileEntity_DrillerBase
return zDrill;
}
+ protected int getYHead() {
+ return yHead;
+ }
+
protected int workState;
- protected static final int STATE_DOWNWARD = 0, STATE_AT_BOTTOM = 1, STATE_UPWARD = 2;
+ protected static final int STATE_DOWNWARD = 0, STATE_AT_BOTTOM = 1, STATE_UPWARD = 2, STATE_ABORT = 3;
protected boolean mChunkLoadingEnabled = true;
protected ChunkCoordIntPair mCurrentChunk = null;
protected boolean mWorkChunkNeedsReload = true;
+ /** Stores default result messages for success/failures of each work state. */
+ private final Map<ResultRegistryKey, CheckRecipeResult> resultRegistry = new HashMap<>();
+
+ /** Allows inheritors to supply custom runtime failure messages. */
+ private CheckRecipeResult runtimeFailure = null;
+
+ /** Allows inheritors to supply custom shutdown failure messages. */
+ private @NotNull String shutdownReason = "";
+
+ /** Allows inheritors to suppress wiping the last error if the machine is forcibly turned off. */
+ protected boolean suppressErrorWipe = false;
+
public GT_MetaTileEntity_DrillerBase(int aID, String aName, String aNameRegional) {
super(aID, aName, aNameRegional);
initFields();
@@ -154,6 +188,16 @@ public abstract class GT_MetaTileEntity_DrillerBase
: W;
casingTextureIndex = getCasingTextureIndex();
workState = STATE_DOWNWARD;
+
+ // Inheritors can overwrite these to add custom operating messages.
+ addResultMessage(STATE_DOWNWARD, true, "deploying_pipe");
+ addResultMessage(STATE_DOWNWARD, false, "extracting_pipe");
+ addResultMessage(STATE_AT_BOTTOM, true, "drilling");
+ addResultMessage(STATE_AT_BOTTOM, false, "no_mining_pipe");
+ addResultMessage(STATE_UPWARD, true, "retracting_pipe");
+ addResultMessage(STATE_UPWARD, false, "drill_generic_finished");
+ addResultMessage(STATE_ABORT, true, "retracting_pipe");
+ addResultMessage(STATE_ABORT, false, "drill_retract_pipes_finished");
}
@Override
@@ -380,6 +424,7 @@ public abstract class GT_MetaTileEntity_DrillerBase
switch (tryLowerPipeState()) {
case 2 -> {
mMaxProgresstime = 0;
+ setRuntimeFailureReason(CheckRecipeResultRegistry.MISSING_MINING_PIPE);
return false;
}
case 3 -> {
@@ -417,6 +462,49 @@ public abstract class GT_MetaTileEntity_DrillerBase
}
}
+ /** Called once when the abort button is clicked. Use to perform any needed cleanup (e.g. unloading chunks.) */
+ protected void onAbort() {}
+
+ protected void abortDrilling() {
+ if (workState != STATE_ABORT) {
+ workState = STATE_ABORT;
+ onAbort();
+ setShutdownReason("");
+
+ if (!isAllowedToWork()) {
+ enableWorking();
+ }
+ }
+ }
+
+ // This is a distinct state from workingUpward, because some inheritors (like concrete backfiller) operate
+ // exclusively on the workingUpward phase. It also allows for more distinct status messages.
+ protected boolean workingToAbortOperation(@NotNull ItemStack aStack, int xDrill, int yDrill, int zDrill, int xPipe,
+ int zPipe, int yHead, int oldYHead) {
+ if (tryPickPipe()) {
+ return true;
+ } else {
+ workState = STATE_DOWNWARD;
+ stopMachine();
+ return false;
+ }
+ }
+
+ @Override
+ public void enableWorking() {
+ super.enableWorking();
+ shutdownReason = "";
+ }
+
+ @Override
+ public void onDisableWorking() {
+ if (suppressErrorWipe) {
+ suppressErrorWipe = false;
+ } else {
+ super.onDisableWorking();
+ }
+ }
+
@Override
@NotNull
public CheckRecipeResult checkProcessing() {
@@ -424,34 +512,88 @@ public abstract class GT_MetaTileEntity_DrillerBase
// Public pipe actions
setElectricityStats();
int oldYHead = yHead;
- if (!checkPipesAndSetYHead() || !isEnergyEnough()) {
+ if (!checkPipesAndSetYHead()) {
stopMachine();
return SimpleCheckRecipeResult.ofFailure("no_mining_pipe");
+ } else if (!isEnergyEnough()) {
+ stopMachine();
+ return SimpleCheckRecipeResult.ofFailure("not_enough_energy");
}
putMiningPipesFromInputsInController();
+
+ final boolean wasSuccessful;
switch (workState) {
- case STATE_DOWNWARD -> {
- return workingDownward(controllerStack, xDrill, yDrill, zDrill, xPipe, zPipe, yHead, oldYHead)
- ? SimpleCheckRecipeResult.ofSuccess("drilling")
- : SimpleCheckRecipeResult.ofFailure("extracting_pipe");
+ case STATE_DOWNWARD -> wasSuccessful = workingDownward(
+ controllerStack,
+ xDrill,
+ yDrill,
+ zDrill,
+ xPipe,
+ zPipe,
+ yHead,
+ oldYHead);
+ case STATE_AT_BOTTOM -> wasSuccessful = workingAtBottom(
+ controllerStack,
+ xDrill,
+ yDrill,
+ zDrill,
+ xPipe,
+ zPipe,
+ yHead,
+ oldYHead);
+ case STATE_UPWARD -> wasSuccessful = workingUpward(
+ controllerStack,
+ xDrill,
+ yDrill,
+ zDrill,
+ xPipe,
+ zPipe,
+ yHead,
+ oldYHead);
+ case STATE_ABORT -> wasSuccessful = workingToAbortOperation(
+ controllerStack,
+ xDrill,
+ yDrill,
+ zDrill,
+ xPipe,
+ zPipe,
+ yHead,
+ oldYHead);
+ default -> wasSuccessful = false;
+ }
- }
- case STATE_AT_BOTTOM -> {
- return workingAtBottom(controllerStack, xDrill, yDrill, zDrill, xPipe, zPipe, yHead, oldYHead)
- ? SimpleCheckRecipeResult.ofSuccess("drilling")
- : SimpleCheckRecipeResult.ofFailure("no_mining_pipe");
- }
- case STATE_UPWARD -> {
- return workingUpward(controllerStack, xDrill, yDrill, zDrill, xPipe, zPipe, yHead, oldYHead)
- ? SimpleCheckRecipeResult.ofSuccess("retracting_pipe")
- : SimpleCheckRecipeResult.ofFailure("no_mining_pipe");
- }
- default -> {
- return SimpleCheckRecipeResult.ofFailure("no_mining_pipe");
- }
+ if (runtimeFailure == null) {
+ return resultRegistry.getOrDefault(
+ new ResultRegistryKey(workState, wasSuccessful),
+ SimpleCheckRecipeResult.ofFailure("no_mining_pipe"));
+ } else {
+ final CheckRecipeResult result;
+ result = runtimeFailure;
+ runtimeFailure = null;
+ return result;
}
}
+ /**
+ * Allow drills to set a specific failure reason specific to their situation. E.g.: out of drilling fluid.
+ * Should be used when the machine doesn't turn off due to the failure.
+ *
+ * @param newFailureReason A new failure reason
+ */
+ protected void setRuntimeFailureReason(@NotNull CheckRecipeResult newFailureReason) {
+ runtimeFailure = newFailureReason;
+ }
+
+ /**
+ * Sets a line in the UI to explain why the drill shut down. E.g.: operation finished.
+ * Should be used when the machine has been turned off due to an operating issue or completion.
+ *
+ * @param newReason The reason for the machine shutdown
+ */
+ protected void setShutdownReason(@NotNull String newReason) {
+ shutdownReason = newReason;
+ }
+
@Override
protected IAlignmentLimits getInitialAlignmentLimits() {
return (d, r, f) -> (d.flag & (ForgeDirection.UP.flag | ForgeDirection.DOWN.flag)) == 0 && r.isNotRotated()
@@ -636,6 +778,89 @@ public abstract class GT_MetaTileEntity_DrillerBase
return survivialBuildPiece(STRUCTURE_PIECE_MAIN, stackSize, 1, 6, 0, elementBudget, env, false, true);
}
+ @Override
+ protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
+ super.drawTexts(screenElements, inventorySlot);
+ screenElements.widget(
+ TextWidget.dynamicString(() -> shutdownReason)
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(widget -> !(getBaseMetaTileEntity().isActive() || shutdownReason.isEmpty())))
+ .widget(new FakeSyncWidget.StringSyncer(() -> shutdownReason, newString -> shutdownReason = newString));
+ }
+
+ /**
+ * Adds additional buttons to the main button row. You do not need to set the position.
+ *
+ * @param builder Only use to attach SyncWidgets.
+ * @param buildContext Context for things like the player.
+ */
+ protected List<ButtonWidget> getAdditionalButtons(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ return ImmutableList.of();
+ }
+
+ @Override
+ public void addUIWidgets(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ super.addUIWidgets(builder, buildContext);
+ final int BUTTON_Y_LEVEL = 91;
+
+ builder.widget(
+ new GT_DisabledWhileActiveButton(this.getBaseMetaTileEntity(), builder)
+ .setOnClick((clickData, widget) -> mChunkLoadingEnabled = !mChunkLoadingEnabled)
+ .setPlayClickSound(true)
+ .setBackground(() -> {
+ if (mChunkLoadingEnabled) {
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD_PRESSED,
+ GT_UITextures.OVERLAY_CHUNK_LOADING };
+ }
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD, GT_UITextures.OVERLAY_CHUNK_LOADING_OFF };
+ })
+ .attachSyncer(
+ new FakeSyncWidget.BooleanSyncer(
+ () -> mChunkLoadingEnabled,
+ newBoolean -> mChunkLoadingEnabled = newBoolean),
+ builder,
+ (widget, val) -> widget.notifyTooltipChange())
+ .dynamicTooltip(
+ () -> ImmutableList.of(
+ StatCollector.translateToLocal(
+ mChunkLoadingEnabled ? "GT5U.gui.button.chunk_loading_on"
+ : "GT5U.gui.button.chunk_loading_off")))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(new Pos2d(80, BUTTON_Y_LEVEL))
+ .setSize(16, 16))
+ .widget(
+ new ButtonWidget().setOnClick((clickData, widget) -> abortDrilling())
+ .setPlayClickSound(true)
+ .setBackground(() -> {
+ if (workState == STATE_ABORT) {
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD_PRESSED,
+ GT_UITextures.OVERLAY_RETRACT_PIPE, GT_UITextures.OVERLAY_BUTTON_LOCKED };
+ }
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD, GT_UITextures.OVERLAY_RETRACT_PIPE };
+ })
+ .attachSyncer(
+ new FakeSyncWidget.IntegerSyncer(() -> workState, (newInt) -> workState = newInt),
+ builder,
+ (widget, integer) -> widget.notifyTooltipChange())
+ .dynamicTooltip(
+ () -> ImmutableList.of(
+ StatCollector.translateToLocalFormatted(
+ workState == STATE_ABORT ? "GT5U.gui.button.drill_retract_pipes_active"
+ : "GT5U.gui.button.drill_retract_pipes")))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setPos(new Pos2d(174, 130))
+ .setSize(16, 16));
+
+ int left = 98;
+ for (ButtonWidget button : getAdditionalButtons(builder, buildContext)) {
+ button.setPos(new Pos2d(left, BUTTON_Y_LEVEL))
+ .setSize(16, 16);
+ builder.widget(button);
+ left += 18;
+ }
+ }
+
protected List<IHatchElement<? super GT_MetaTileEntity_DrillerBase>> getAllowedHatches() {
return ImmutableList.of(
InputHatch,
@@ -667,4 +892,57 @@ public abstract class GT_MetaTileEntity_DrillerBase
return t.mDataAccessHatches.size();
}
}
+
+ /**
+ * Sets or overrides the {@link CheckRecipeResult} for a given work state
+ *
+ * @param state A work state like {@link #STATE_DOWNWARD}.
+ * @param result A previously registered recipe result.
+ */
+ protected void addResultMessage(final int state, @NotNull final CheckRecipeResult result) {
+ resultRegistry.put(new ResultRegistryKey(state, result.wasSuccessful()), result);
+ }
+
+ /**
+ * Sets or overrides the {@link CheckRecipeResult} for a given work state and operation success type.
+ *
+ * @param state A work state like {@link #STATE_DOWNWARD}.
+ * @param wasSuccessful Whether the operation was successful.
+ * @param resultKey An I18N key for the message.
+ */
+ protected void addResultMessage(final int state, final boolean wasSuccessful, @NotNull final String resultKey) {
+ addResultMessage(
+ state,
+ wasSuccessful ? SimpleCheckRecipeResult.ofSuccess(resultKey)
+ : SimpleCheckRecipeResult.ofFailure(resultKey));
+ }
+
+ @SuppressWarnings("ClassCanBeRecord")
+ private final static class ResultRegistryKey {
+
+ private final int state;
+ private final boolean successful;
+
+ public ResultRegistryKey(final int state, final boolean successful) {
+ this.state = state;
+ this.successful = successful;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ResultRegistryKey other)) {
+ return false;
+ }
+
+ return (state == other.state && successful == other.successful);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(state, successful);
+ }
+ }
}
diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java
index 5031a664c7..9a6f2b5e80 100644
--- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java
+++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OilDrillBase.java
@@ -29,16 +29,25 @@ import net.minecraft.util.StatCollector;
import net.minecraft.world.ChunkCoordIntPair;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.common.util.ForgeDirection;
+import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
+import org.jetbrains.annotations.NotNull;
+
import com.google.common.collect.ImmutableList;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedColumn;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
import gregtech.api.enums.SoundResource;
import gregtech.api.interfaces.IHatchElement;
import gregtech.api.interfaces.ITexture;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
import gregtech.api.objects.GT_ChunkManager;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
import gregtech.api.render.TextureFactory;
import gregtech.api.util.GT_Log;
import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
@@ -207,6 +216,7 @@ public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_D
if (pumpResult.getType() != ValidationType.VALID) {
mEUt = 0;
mMaxProgresstime = 0;
+ setRuntimeFailureReason(CheckRecipeResultRegistry.OUTPUT_FULL);
return false;
}
FluidStack tFluid = pumpResult.getResult();
@@ -217,6 +227,7 @@ public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_D
}
GT_ChunkManager.releaseTicket((TileEntity) getBaseMetaTileEntity());
workState = STATE_UPWARD;
+ setShutdownReason(StatCollector.translateToLocal("GT5U.gui.text.drill_exhausted"));
return true;
}
@@ -359,24 +370,84 @@ public abstract class GT_MetaTileEntity_OilDrillBase extends GT_MetaTileEntity_D
+ EnumChatFormatting.RESET,
StatCollector.translateToLocal("GT5U.machines.workarea") + ": "
+ EnumChatFormatting.GREEN
- + (chunkRangeConfig)
+ + GT_Utility.formatNumbers(chunkRangeConfig)
+ " x "
- + (chunkRangeConfig)
+ + GT_Utility.formatNumbers(chunkRangeConfig)
+ EnumChatFormatting.RESET
+ " "
+ StatCollector.translateToLocal("GT5U.machines.chunks"),
- "Drilling fluid: " + EnumChatFormatting.GREEN
- + (mOilId > 0 ? FluidRegistry.getFluid(mOilId)
- .getName() : "None")
- + EnumChatFormatting.RESET,
+ "Drilling fluid: " + EnumChatFormatting.GREEN + getFluidName() + EnumChatFormatting.RESET,
"Drilling flow: " + EnumChatFormatting.GREEN
- + GT_Utility.formatNumbers(this.mMaxProgresstime > 0 ? (mOilFlow / this.mMaxProgresstime) : 0)
+ + getFlowRatePerTick()
+ EnumChatFormatting.RESET
+ " L/t"));
l.addAll(Arrays.asList(super.getInfoData()));
return l.toArray(new String[0]);
}
+ @NotNull
+ protected String getFlowRatePerTick() {
+ return GT_Utility.formatNumbers(this.mMaxProgresstime > 0 ? (mOilFlow / this.mMaxProgresstime) : 0);
+ }
+
+ @NotNull
+ private String getFluidName() {
+ if (mOilId > 0) {
+ final Fluid fluid = FluidRegistry.getFluid(mOilId);
+ return fluid.getLocalizedName(new FluidStack(fluid, 0));
+ }
+ return "None";
+ }
+
+ private @NotNull String clientFluidType = "";
+ private @NotNull String clientPumpRate = "";
+ private @NotNull String clientReservoirContents = "";
+
+ @NotNull
+ private String getReservoirContents() {
+ int amount = 0;
+ for (Chunk chunk : mOilFieldChunks) {
+ final FluidStack fluidStack = undergroundOil(chunk, -1);
+ if (fluidStack != null) {
+ amount += fluidStack.amount;
+ }
+ }
+
+ return StatCollector.translateToLocalFormatted("GT5U.gui.text.pump_recovery", GT_Utility.formatNumbers(amount));
+ }
+
+ @Override
+ protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
+ super.drawTexts(screenElements, inventorySlot);
+ screenElements
+ .widget(
+ TextWidget
+ .dynamicString(
+ () -> StatCollector.translateToLocalFormatted("GT5U.gui.text.pump_fluid_type", clientFluidType))
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_AT_BOTTOM))
+ .widget(
+ TextWidget.dynamicString(
+ () -> StatCollector
+ .translateToLocalFormatted("GT5U.gui.text.pump_rate", EnumChatFormatting.AQUA + clientPumpRate))
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_AT_BOTTOM))
+ .widget(
+ TextWidget.dynamicString(() -> clientReservoirContents)
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(widget -> getBaseMetaTileEntity().isActive() && workState == STATE_AT_BOTTOM))
+ .widget(new FakeSyncWidget.IntegerSyncer(() -> workState, newInt -> workState = newInt))
+ .widget(new FakeSyncWidget.StringSyncer(this::getFluidName, newString -> clientFluidType = newString))
+ .widget(new FakeSyncWidget.StringSyncer(this::getFlowRatePerTick, newString -> clientPumpRate = newString))
+ .widget(
+ new FakeSyncWidget.StringSyncer(
+ this::getReservoirContents,
+ newString -> clientReservoirContents = newString));
+ }
+
@Override
public boolean supportsVoidProtection() {
return true;
diff --git a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java
index dd688e3b0d..8103ff632b 100644
--- a/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java
+++ b/src/main/java/gregtech/common/tileentities/machines/multi/GT_MetaTileEntity_OreDrillingPlantBase.java
@@ -6,6 +6,7 @@ import static gregtech.api.enums.GT_HatchElement.InputHatch;
import static gregtech.api.enums.GT_HatchElement.Maintenance;
import static gregtech.api.enums.GT_HatchElement.OutputBus;
import static gregtech.api.enums.GT_Values.VN;
+import static gregtech.api.metatileentity.BaseTileEntity.TOOLTIP_DELAY;
import java.util.ArrayList;
import java.util.Collection;
@@ -25,15 +26,29 @@ import net.minecraft.world.ChunkPosition;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.FluidStack;
+import org.jetbrains.annotations.NotNull;
+
import com.google.common.collect.ImmutableList;
+import com.gtnewhorizons.modularui.api.drawable.IDrawable;
+import com.gtnewhorizons.modularui.api.math.Alignment;
+import com.gtnewhorizons.modularui.api.screen.ModularWindow;
+import com.gtnewhorizons.modularui.api.screen.UIBuildContext;
+import com.gtnewhorizons.modularui.common.widget.ButtonWidget;
+import com.gtnewhorizons.modularui.common.widget.DynamicPositionedColumn;
+import com.gtnewhorizons.modularui.common.widget.FakeSyncWidget;
+import com.gtnewhorizons.modularui.common.widget.SlotWidget;
+import com.gtnewhorizons.modularui.common.widget.TextWidget;
import gregtech.api.enums.ItemList;
import gregtech.api.enums.Materials;
import gregtech.api.enums.OrePrefixes;
import gregtech.api.enums.SoundResource;
+import gregtech.api.gui.modularui.GT_UITextures;
+import gregtech.api.gui.widgets.GT_DisabledWhileActiveButton;
import gregtech.api.interfaces.IHatchElement;
import gregtech.api.objects.GT_ChunkManager;
import gregtech.api.objects.ItemData;
+import gregtech.api.recipe.check.CheckRecipeResultRegistry;
import gregtech.api.util.GT_Multiblock_Tooltip_Builder;
import gregtech.api.util.GT_OreDictUnificator;
import gregtech.api.util.GT_Recipe;
@@ -48,6 +63,18 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
private int chunkRadiusConfig = getRadiusInChunks();
private boolean replaceWithCobblestone = true;
+ /** Used to drive the remaining ores count in the UI. */
+ private int clientOreListSize = 0;
+
+ /** Used to drive the current chunk number in the UI. */
+ private int clientCurrentChunk = 0;
+
+ /** Used to drive the total chunk count in the UI. */
+ private int clientTotalChunks = 0;
+
+ /** Used to drive the drill's y-level in the UI. */
+ private int clientYHead = 0;
+
GT_MetaTileEntity_OreDrillingPlantBase(int aID, String aName, String aNameRegional) {
super(aID, aName, aNameRegional);
}
@@ -74,26 +101,42 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
}
}
- @Override
- public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ) {
- super.onScrewdriverRightClick(side, aPlayer, aX, aY, aZ);
- if (aPlayer.isSneaking()) {
+ private void adjustChunkRadius(boolean increase) {
+ if (increase) {
+ if (chunkRadiusConfig <= getRadiusInChunks()) {
+ chunkRadiusConfig++;
+ }
+ if (chunkRadiusConfig > getRadiusInChunks()) chunkRadiusConfig = 1;
+ } else {
if (chunkRadiusConfig > 0) {
chunkRadiusConfig--;
}
if (chunkRadiusConfig == 0) chunkRadiusConfig = getRadiusInChunks();
+ }
+
+ if (mCurrentChunk != null && mChunkLoadingEnabled) {
+ GT_ChunkManager.releaseChunk((TileEntity) getBaseMetaTileEntity(), mCurrentChunk);
+ }
+
+ oreBlockPositions.clear();
+ createInitialWorkingChunk();
+ }
+
+ @Override
+ public void onScrewdriverRightClick(ForgeDirection side, EntityPlayer aPlayer, float aX, float aY, float aZ) {
+ super.onScrewdriverRightClick(side, aPlayer, aX, aY, aZ);
+
+ if (getBaseMetaTileEntity().isActive()) {
+ GT_Utility.sendChatToPlayer(aPlayer, StatCollector.translateToLocal("GT5U.machines.workarea_fail"));
} else {
- if (chunkRadiusConfig <= getRadiusInChunks()) {
- chunkRadiusConfig++;
- }
- if (chunkRadiusConfig > getRadiusInChunks()) chunkRadiusConfig = 1;
+ adjustChunkRadius(!aPlayer.isSneaking());
+ GT_Utility.sendChatToPlayer(
+ aPlayer,
+ StatCollector.translateToLocal("GT5U.machines.workareaset") + " "
+ + GT_Utility.formatNumbers((long) chunkRadiusConfig << 4)
+ + " "
+ + StatCollector.translateToLocal("GT5U.machines.radius"));
}
- GT_Utility.sendChatToPlayer(
- aPlayer,
- StatCollector.translateToLocal("GT5U.machines.workareaset") + " "
- + (chunkRadiusConfig << 4)
- + " "
- + StatCollector.translateToLocal("GT5U.machines.radius"));
}
@Override
@@ -118,6 +161,7 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
switch (tryLowerPipeState()) {
case 2 -> {
mMaxProgresstime = 0;
+ setRuntimeFailureReason(CheckRecipeResultRegistry.MISSING_MINING_PIPE);
return false;
}
case 3 -> {
@@ -177,6 +221,7 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
if (!tryConsumeDrillingFluid(simulate)) {
oreBlockPositions.add(0, oreBlockPos);
+ setRuntimeFailureReason(CheckRecipeResultRegistry.NO_DRILLING_FLUID);
return false;
}
if (oreBlock != null && GT_Utility.isOre(oreBlock, oreBlockMetadata)) {
@@ -199,6 +244,7 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
}
ItemStack[] toOutput = getOutputByDrops(oreBlockDrops);
if (simulate && !canOutputAll(toOutput)) {
+ setRuntimeFailureReason(CheckRecipeResultRegistry.OUTPUT_FULL);
return false;
}
mOutputItems = toOutput;
@@ -242,28 +288,109 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
}
private void createInitialWorkingChunk() {
- final int centerX = getXDrill() >> 4;
- final int centerZ = getZDrill() >> 4;
+ mCurrentChunk = getTopLeftChunkCoords();
+ if (mChunkLoadingEnabled) {
+ GT_ChunkManager.requestChunkLoad((TileEntity) getBaseMetaTileEntity(), mCurrentChunk);
+ mWorkChunkNeedsReload = false;
+ }
+ }
+
+ @NotNull
+ private ChunkCoordIntPair getTopLeftChunkCoords() {
+ return getCornerCoords(-1, -1);
+ }
+
+ @NotNull
+ private ChunkCoordIntPair getBottomRightChunkCoords() {
+ return getCornerCoords(1, 1);
+ }
+
+ @NotNull
+ private ChunkCoordIntPair getCornerCoords(int xMultiplier, int zMultiplier) {
+ final ChunkCoordIntPair drillPos = getDrillCoords();
// use corner closest to the drill as mining area center
- final int leftRight = (getXDrill() - (centerX << 4)) < 8 ? 0 : 1;
- final int topBottom = (getZDrill() - (centerZ << 4)) < 8 ? 0 : 1;
- mCurrentChunk = new ChunkCoordIntPair(
- centerX - chunkRadiusConfig + leftRight,
- centerZ - chunkRadiusConfig + topBottom);
- GT_ChunkManager.requestChunkLoad((TileEntity) getBaseMetaTileEntity(), mCurrentChunk);
- mWorkChunkNeedsReload = false;
+ return new ChunkCoordIntPair(
+ drillPos.chunkXPos + xMultiplier * chunkRadiusConfig
+ + ((getXDrill() - (drillPos.chunkXPos << 4)) < 8 ? 0 : 1),
+ drillPos.chunkZPos + zMultiplier * chunkRadiusConfig
+ + ((getZDrill() - (drillPos.chunkZPos << 4)) < 8 ? 0 : 1));
+ }
+
+ @NotNull
+ private ChunkCoordIntPair getDrillCoords() {
+ return new ChunkCoordIntPair(getXDrill() >> 4, getZDrill() >> 4);
+ }
+
+ private int getTotalChunkCount() {
+ final ChunkCoordIntPair topLeft = getTopLeftChunkCoords();
+ final ChunkCoordIntPair bottomRight = getBottomRightChunkCoords();
+ return (bottomRight.chunkXPos - topLeft.chunkXPos) * (bottomRight.chunkZPos - topLeft.chunkZPos);
+ }
+
+ /**
+ * Returns a number corresponding to which chunk the drill is operating on. Only really useful for driving outputs
+ * in the controller UI.
+ *
+ * @return 0 if the miner is not in operation, positive integer corresponding to the chunk currently being drilled
+ */
+ @SuppressWarnings("ExtractMethodRecommender")
+ private int getChunkNumber() {
+ if (mCurrentChunk == null) {
+ return 0;
+ }
+
+ final ChunkCoordIntPair topLeft = getTopLeftChunkCoords();
+ final ChunkCoordIntPair drillPos = getDrillCoords();
+
+ if (workState == STATE_DOWNWARD) {
+ return 1;
+ } else if (workState == STATE_UPWARD) {
+ // Technically, the miner isn't mining anything now; it's retracting the pipes in preparation to end
+ // operation.
+ return 0;
+ }
+
+ int chunkNumber = (chunkRadiusConfig * 2) * (mCurrentChunk.chunkZPos - topLeft.chunkZPos)
+ + mCurrentChunk.chunkXPos
+ - topLeft.chunkXPos
+ + 1;
+
+ // Drills mine the chunk they're in first, so if we're not there yet, bump the number to indicate that it
+ // was already mined.
+ if (mCurrentChunk.chunkZPos < drillPos.chunkZPos
+ || (mCurrentChunk.chunkZPos == drillPos.chunkZPos && mCurrentChunk.chunkXPos < drillPos.chunkXPos)) {
+ chunkNumber += 1;
+ }
+ return chunkNumber;
}
@Override
protected boolean workingUpward(ItemStack aStack, int xDrill, int yDrill, int zDrill, int xPipe, int zPipe,
int yHead, int oldYHead) {
- if (!mChunkLoadingEnabled || oreBlockPositions.isEmpty())
- return super.workingUpward(aStack, xDrill, yDrill, zDrill, xPipe, zPipe, yHead, oldYHead);
- boolean result = tryProcessOreList();
- if (oreBlockPositions.isEmpty()) GT_ChunkManager.releaseTicket((TileEntity) getBaseMetaTileEntity());
+ boolean result;
+ if (!mChunkLoadingEnabled || oreBlockPositions.isEmpty()) {
+ result = super.workingUpward(aStack, xDrill, yDrill, zDrill, xPipe, zPipe, yHead, oldYHead);
+ } else {
+ result = tryProcessOreList();
+ if (oreBlockPositions.isEmpty()) GT_ChunkManager.releaseTicket((TileEntity) getBaseMetaTileEntity());
+ }
+
+ if (!result) {
+ setShutdownReason(StatCollector.translateToLocal("GT5U.gui.text.drill_exhausted"));
+ }
+
return result;
}
+ @Override
+ protected void onAbort() {
+ oreBlockPositions.clear();
+ if (mCurrentChunk != null) {
+ GT_ChunkManager.releaseChunk((TileEntity) getBaseMetaTileEntity(), mCurrentChunk);
+ }
+ mCurrentChunk = null;
+ }
+
private boolean moveToNextChunk(int centerX, int centerZ) {
if (mCurrentChunk == null) return false;
// use corner closest to the drill as mining area center
@@ -449,13 +576,102 @@ public abstract class GT_MetaTileEntity_OreDrillingPlantBase extends GT_MetaTile
}
@Override
+ protected void drawTexts(DynamicPositionedColumn screenElements, SlotWidget inventorySlot) {
+ super.drawTexts(screenElements, inventorySlot);
+ screenElements
+ .widget(
+ TextWidget
+ .dynamicString(
+ () -> StatCollector.translateToLocalFormatted(
+ "GT5U.gui.text.drill_ores_left_chunk",
+ GT_Utility.formatNumbers(clientOreListSize)))
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(
+ widget -> getBaseMetaTileEntity().isActive() && clientOreListSize > 0
+ && workState == STATE_AT_BOTTOM))
+ .widget(
+ TextWidget
+ .dynamicString(
+ () -> StatCollector.translateToLocalFormatted(
+ "GT5U.gui.text.drill_ores_left_layer",
+ GT_Utility.formatNumbers(clientYHead),
+ GT_Utility.formatNumbers(clientOreListSize)))
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(
+ widget -> getBaseMetaTileEntity().isActive() && clientYHead > 0 && workState == STATE_DOWNWARD))
+ .widget(
+ TextWidget
+ .dynamicString(
+ () -> StatCollector.translateToLocalFormatted(
+ "GT5U.gui.text.drill_chunks_left",
+ GT_Utility.formatNumbers(clientCurrentChunk),
+ GT_Utility.formatNumbers(clientTotalChunks)))
+ .setSynced(false)
+ .setTextAlignment(Alignment.CenterLeft)
+ .setEnabled(
+ widget -> getBaseMetaTileEntity().isActive() && clientCurrentChunk > 0
+ && workState == STATE_AT_BOTTOM))
+ .widget(new FakeSyncWidget.IntegerSyncer(oreBlockPositions::size, (newInt) -> clientOreListSize = newInt))
+ .widget(new FakeSyncWidget.IntegerSyncer(this::getTotalChunkCount, (newInt) -> clientTotalChunks = newInt))
+ .widget(new FakeSyncWidget.IntegerSyncer(this::getChunkNumber, (newInt) -> clientCurrentChunk = newInt))
+ .widget(new FakeSyncWidget.IntegerSyncer(() -> workState, (newInt) -> workState = newInt))
+ .widget(new FakeSyncWidget.IntegerSyncer(this::getYHead, (newInt) -> clientYHead = newInt));
+ }
+
+ @Override
+ protected List<ButtonWidget> getAdditionalButtons(ModularWindow.Builder builder, UIBuildContext buildContext) {
+ return ImmutableList.of(
+ (ButtonWidget) new GT_DisabledWhileActiveButton(this.getBaseMetaTileEntity(), builder)
+ .setOnClick((clickData, widget) -> adjustChunkRadius(clickData.mouseButton == 0))
+ .setPlayClickSound(true)
+ .setBackground(GT_UITextures.BUTTON_STANDARD, GT_UITextures.OVERLAY_WORK_AREA)
+ .attachSyncer(
+ new FakeSyncWidget.IntegerSyncer(() -> chunkRadiusConfig, (val) -> chunkRadiusConfig = val),
+ builder,
+ (widget, val) -> widget.notifyTooltipChange())
+ .dynamicTooltip(
+ () -> ImmutableList.of(
+ StatCollector.translateToLocalFormatted(
+ "GT5U.gui.button.ore_drill_radius_1",
+ GT_Utility.formatNumbers((long) chunkRadiusConfig << 4)),
+ StatCollector.translateToLocal("GT5U.gui.button.ore_drill_radius_2")))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setSize(16, 16),
+ (ButtonWidget) new GT_DisabledWhileActiveButton(this.getBaseMetaTileEntity(), builder)
+ .setOnClick((clickData, widget) -> replaceWithCobblestone = !replaceWithCobblestone)
+ .setPlayClickSound(true)
+ .setBackground(() -> {
+ if (replaceWithCobblestone) {
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD,
+ GT_UITextures.OVERLAY_REPLACE_COBBLE_ON };
+ }
+ return new IDrawable[] { GT_UITextures.BUTTON_STANDARD, GT_UITextures.OVERLAY_REPLACE_COBBLE_OFF };
+ })
+ .attachSyncer(
+ new FakeSyncWidget.BooleanSyncer(
+ () -> replaceWithCobblestone,
+ (val) -> replaceWithCobblestone = val),
+ builder,
+ (widget, val) -> widget.notifyTooltipChange())
+ .dynamicTooltip(
+ () -> ImmutableList.of(
+ StatCollector.translateToLocal(
+ replaceWithCobblestone ? "GT5U.gui.button.ore_drill_cobblestone_on"
+ : "GT5U.gui.button.ore_drill_cobblestone_off")))
+ .setTooltipShowUpDelay(TOOLTIP_DELAY)
+ .setSize(16, 16));
+ }
+
+ @Override
protected SoundResource getProcessStartSound() {
return SoundResource.IC2_MACHINES_MINER_OP;
}
@Override
public String[] getInfoData() {
- final int diameter = chunkRadiusConfig * 2;
+ final String diameter = GT_Utility.formatNumbers(chunkRadiusConfig * 2L);
return new String[] {
EnumChatFormatting.BLUE + StatCollector.translateToLocal("GT5U.machines.minermulti")
+ EnumChatFormatting.RESET,