mirror of
https://github.com/GTNewHorizons/NotEnoughItems.git
synced 2026-11-14 01:14:07 +00:00
Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fab584b8c1 | ||
|
|
0fc657c9d9 | ||
|
|
0e6f01f84d | ||
|
|
1478f21eb4 | ||
|
|
dab939acb7 | ||
|
|
4d63ed13dc | ||
|
|
76b62072ab | ||
|
|
42c47e5a89 | ||
|
|
8d8f8370fc | ||
|
|
753fc0da3a | ||
|
|
ef8cac9c39 | ||
|
|
aa8f1c8adb | ||
|
|
b29494935d | ||
|
|
d6789c44d4 | ||
|
|
d07059cb5a | ||
|
|
b7dcd0fb3d | ||
|
|
d04718c5c6 |
No files matched your search
@@ -8,7 +8,6 @@ import org.lwjgl.opengl.GL11;
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
import codechicken.nei.ItemsGrid.ItemsGridSlot;
|
||||
import codechicken.nei.api.GuiInfo;
|
||||
import codechicken.nei.api.INEIGuiHandler;
|
||||
import codechicken.nei.api.ShortcutInputHandler;
|
||||
import codechicken.nei.drawable.DrawableBuilder;
|
||||
import codechicken.nei.drawable.DrawableResource;
|
||||
@@ -56,14 +55,12 @@ public abstract class AbstractSubpanel<T extends ItemsGrid<? extends ItemsGridSl
|
||||
}
|
||||
|
||||
protected void updateLinePadding() {
|
||||
final GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
|
||||
final INEIGuiHandler[] handlers = GuiInfo.getGuiHandlersSnapshot();
|
||||
final Rectangle4i rect = new Rectangle4i(this.x, this.y - 1, 10, 2);
|
||||
GuiContainer guiContainer = NEIClientUtils.getGuiContainer();
|
||||
this.linePaddingStart = 0;
|
||||
|
||||
while (this.linePaddingStart < this.w && GuiInfo.hideItemPanelSlot(guiContainer, rect, handlers)) {
|
||||
while (GuiInfo
|
||||
.hideItemPanelSlot(guiContainer, new Rectangle4i(this.x + this.linePaddingStart, this.y - 1, 10, 2))) {
|
||||
this.linePaddingStart += 10;
|
||||
rect.x += 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
package codechicken.nei;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
|
||||
import codechicken.lib.vec.Rectangle4i;
|
||||
|
||||
final class GridOverlapScanner {
|
||||
|
||||
private static boolean scanRequested = true;
|
||||
private static boolean scanFrame = true;
|
||||
private static boolean wasShowTooltip = true;
|
||||
private static WeakReference<GuiContainer> lastGui = new WeakReference<>(null);
|
||||
|
||||
private boolean invalidated = true;
|
||||
|
||||
private final Rectangle4i rect = new Rectangle4i();
|
||||
private boolean[] checked = new boolean[0];
|
||||
private int[] queue = new int[0];
|
||||
private int tail;
|
||||
|
||||
private Predicate<Rectangle4i> isHidden;
|
||||
private boolean[] hidden;
|
||||
private int hiddenCount;
|
||||
private int left;
|
||||
private int top;
|
||||
private int slotSize;
|
||||
private int columns;
|
||||
|
||||
public static void requestScan() {
|
||||
scanRequested = true;
|
||||
}
|
||||
|
||||
public static void beginFrame(GuiContainer gui, boolean shouldShowTooltip) {
|
||||
|
||||
if (wasShowTooltip != shouldShowTooltip) {
|
||||
scanRequested = true;
|
||||
}
|
||||
|
||||
wasShowTooltip = shouldShowTooltip;
|
||||
|
||||
if (lastGui.get() != gui) {
|
||||
lastGui = new WeakReference<>(gui);
|
||||
scanRequested = true;
|
||||
}
|
||||
|
||||
scanFrame = scanRequested;
|
||||
scanRequested = false;
|
||||
}
|
||||
|
||||
public static boolean isScanFrame() {
|
||||
return scanFrame;
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
this.invalidated = true;
|
||||
}
|
||||
|
||||
public boolean shouldRefresh() {
|
||||
final boolean refresh = this.invalidated || scanFrame;
|
||||
this.invalidated = false;
|
||||
return refresh;
|
||||
}
|
||||
|
||||
public int scan(Predicate<Rectangle4i> isHidden, int left, int top, int slotSize, int rows, int columns,
|
||||
boolean seedLeft, boolean seedRight, boolean[] hidden) {
|
||||
final int size = rows * columns;
|
||||
|
||||
if (this.checked.length < size) {
|
||||
this.checked = new boolean[size];
|
||||
this.queue = new int[size];
|
||||
} else {
|
||||
Arrays.fill(this.checked, 0, size, false);
|
||||
}
|
||||
|
||||
this.isHidden = isHidden;
|
||||
this.hidden = hidden;
|
||||
this.hiddenCount = 0;
|
||||
this.tail = 0;
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
this.slotSize = slotSize;
|
||||
this.columns = columns;
|
||||
|
||||
for (int r = 0; r < rows; r++) {
|
||||
if (seedLeft) {
|
||||
probe(r, 0);
|
||||
}
|
||||
if (seedRight) {
|
||||
probe(r, columns - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int head = 0; head < this.tail; head++) {
|
||||
final int row = this.queue[head] / columns;
|
||||
final int column = this.queue[head] % columns;
|
||||
|
||||
for (int r = Math.max(0, row - 1); r <= Math.min(rows - 1, row + 1); r++) {
|
||||
for (int c = Math.max(0, column - 1); c <= Math.min(columns - 1, column + 1); c++) {
|
||||
probe(r, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < columns; c++) {
|
||||
if (this.checked[r * columns + c]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int c1 = c;
|
||||
while (c1 + 1 < columns && !this.checked[r * columns + c1 + 1]) {
|
||||
c1++;
|
||||
}
|
||||
|
||||
int r1 = r;
|
||||
while (r1 + 1 < rows && isUnchecked(r1 + 1, c, c1)) {
|
||||
r1++;
|
||||
}
|
||||
|
||||
scanArea(r, r1, c, c1);
|
||||
c = c1;
|
||||
}
|
||||
}
|
||||
|
||||
this.isHidden = null;
|
||||
this.hidden = null;
|
||||
|
||||
return this.hiddenCount;
|
||||
}
|
||||
|
||||
private void probe(int row, int column) {
|
||||
final int idx = row * this.columns + column;
|
||||
|
||||
if (!this.checked[idx]) {
|
||||
this.checked[idx] = true;
|
||||
|
||||
if (test(row, row, column, column)) {
|
||||
this.hidden[idx] = true;
|
||||
this.queue[this.tail++] = idx;
|
||||
this.hiddenCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scanArea(int r0, int r1, int c0, int c1) {
|
||||
|
||||
if (!test(r0, r1, c0, c1)) {
|
||||
for (int r = r0; r <= r1; r++) {
|
||||
Arrays.fill(this.checked, r * this.columns + c0, r * this.columns + c1 + 1, true);
|
||||
}
|
||||
} else if (r0 == r1 && c0 == c1) {
|
||||
this.checked[r0 * this.columns + c0] = true;
|
||||
this.hidden[r0 * this.columns + c0] = true;
|
||||
this.hiddenCount++;
|
||||
} else if (r1 - r0 >= c1 - c0) {
|
||||
final int mid = (r0 + r1) / 2;
|
||||
scanArea(r0, mid, c0, c1);
|
||||
scanArea(mid + 1, r1, c0, c1);
|
||||
} else {
|
||||
final int mid = (c0 + c1) / 2;
|
||||
scanArea(r0, r1, c0, mid);
|
||||
scanArea(r0, r1, mid + 1, c1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUnchecked(int row, int c0, int c1) {
|
||||
for (int c = c0; c <= c1; c++) {
|
||||
if (this.checked[row * this.columns + c]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean test(int r0, int r1, int c0, int c1) {
|
||||
this.rect.set(
|
||||
this.left + c0 * this.slotSize,
|
||||
this.top + r0 * this.slotSize,
|
||||
(c1 - c0 + 1) * this.slotSize,
|
||||
(r1 - r0 + 1) * this.slotSize);
|
||||
return this.isHidden.test(this.rect);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,12 +127,7 @@ public class ItemZoom extends Widget implements IContainerInputHandler {
|
||||
this.displayName = "Unnamed";
|
||||
}
|
||||
|
||||
// Copying the stack copies its whole NBT, so it is only done when the hovered stack changes
|
||||
final boolean sameStack = this.stack != null && this.stack.isItemEqual(stack)
|
||||
&& ItemStack.areItemStackTagsEqual(this.stack, stack);
|
||||
if (!sameStack) {
|
||||
this.stack = StackInfo.withAmount(stack, 0);
|
||||
}
|
||||
this.stack = StackInfo.loadFromNBT(StackInfo.itemStackToNBT(stack), 0);
|
||||
} else {
|
||||
this.stack = null;
|
||||
this.displayName = "";
|
||||
|
||||
@@ -282,8 +282,6 @@ public abstract class ItemsGrid<T extends ItemsGrid.ItemsGridSlot, M extends Ite
|
||||
|
||||
protected boolean[] invalidSlotMap;
|
||||
|
||||
private final GridOverlapScanner overlapScanner = new GridOverlapScanner();
|
||||
|
||||
protected ScreenCapture screenCapture = null;
|
||||
|
||||
public ArrayList<ItemStack> getItems() {
|
||||
@@ -361,13 +359,12 @@ public abstract class ItemsGrid<T extends ItemsGrid.ItemsGridSlot, M extends Ite
|
||||
width = newWidth;
|
||||
height = newHeight;
|
||||
|
||||
this.columns = newWidth / SLOT_SIZE;
|
||||
this.rows = newHeight / SLOT_SIZE;
|
||||
columns = width / SLOT_SIZE;
|
||||
rows = height / SLOT_SIZE;
|
||||
|
||||
paddingLeft = (width % SLOT_SIZE) / 2;
|
||||
|
||||
if (changed) {
|
||||
this.overlapScanner.invalidate();
|
||||
onGridChanged();
|
||||
}
|
||||
}
|
||||
@@ -408,42 +405,55 @@ public abstract class ItemsGrid<T extends ItemsGrid.ItemsGridSlot, M extends Ite
|
||||
}
|
||||
|
||||
public void refresh(GuiContainer gui) {
|
||||
if (this.overlapScanner.shouldRefresh()) {
|
||||
final int gridRenderingCacheMode = getGridRenderingCacheMode();
|
||||
if (gridRenderingCacheMode <= 0 || screenCapture == null || screenCapture.needRefresh(gridRenderingCacheMode)) {
|
||||
updateGuiOverlapSlots(gui);
|
||||
}
|
||||
shiftPage(0);
|
||||
}
|
||||
|
||||
protected void updateGuiOverlapSlots(GuiContainer gui) {
|
||||
final boolean[] oldSlotMap = this.invalidSlotMap;
|
||||
final int size = this.rows * this.columns;
|
||||
final boolean[] slotMap = new boolean[size];
|
||||
final boolean[] oldSlotMap = invalidSlotMap;
|
||||
final int oldRows = rows;
|
||||
final int oldColumns = columns;
|
||||
|
||||
this.perPage = size;
|
||||
invalidSlotMap = new boolean[rows * columns];
|
||||
perPage = columns * rows;
|
||||
|
||||
if (gui != null && size > 0) {
|
||||
if (gui != null) {
|
||||
final INEIGuiHandler[] handlers = GuiInfo.getGuiHandlersSnapshot();
|
||||
final Rectangle4i origin = getSlotRect(0, 0);
|
||||
|
||||
this.perPage -= this.overlapScanner.scan(
|
||||
rect -> GuiInfo.hideItemPanelSlot(gui, rect, handlers),
|
||||
origin.x,
|
||||
origin.y,
|
||||
SLOT_SIZE,
|
||||
this.rows,
|
||||
this.columns,
|
||||
origin.x + this.columns * SLOT_SIZE > gui.guiLeft,
|
||||
origin.x < gui.guiLeft + gui.xSize,
|
||||
slotMap);
|
||||
if (NEIClientConfig.optimizeGuiOverlapComputation()) {
|
||||
checkGuiOverlap(gui, handlers, 0, columns - 2, 1);
|
||||
checkGuiOverlap(gui, handlers, columns - 1, 1, -1);
|
||||
} else {
|
||||
checkGuiOverlap(gui, handlers, 0, columns, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.invalidSlotMap = slotMap;
|
||||
|
||||
if (!Arrays.equals(oldSlotMap, slotMap)) {
|
||||
if (oldRows != rows || oldColumns != columns || !Arrays.equals(oldSlotMap, invalidSlotMap)) {
|
||||
onGridChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkGuiOverlap(GuiContainer gui, INEIGuiHandler[] handlers, int start, int end, int dir) {
|
||||
boolean validColumn = false;
|
||||
|
||||
for (int c = start; c != end && (!NEIClientConfig.optimizeGuiOverlapComputation() || !validColumn); c += dir) {
|
||||
validColumn = true;
|
||||
|
||||
for (int r = 0; r < rows; r++) {
|
||||
final int idx = columns * r + c;
|
||||
if (idx >= 0 && idx < invalidSlotMap.length
|
||||
&& !invalidSlotMap[idx]
|
||||
&& GuiInfo.hideItemPanelSlot(gui, getSlotRect(r, c), handlers)) {
|
||||
invalidSlotMap[idx] = true;
|
||||
validColumn = false;
|
||||
perPage--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T getSlotMouseOver(int mousex, int mousey) {
|
||||
if (!contains(mousex, mousey)) {
|
||||
return null;
|
||||
|
||||
@@ -388,8 +388,6 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
|
||||
}
|
||||
|
||||
public static void layout(GuiContainer gui) {
|
||||
GridOverlapScanner.beginFrame(gui, GuiContainerManager.shouldShowTooltip(gui));
|
||||
|
||||
VisiblityData visiblity = new VisiblityData();
|
||||
visiblity.showNEI = !isHidden();
|
||||
visiblity.showBookmarkPanel = !isBookmarkPanelHidden();
|
||||
@@ -823,7 +821,6 @@ public class LayoutManager implements IContainerInputHandler, IContainerTooltipH
|
||||
|
||||
if (!isEnabled()) return;
|
||||
|
||||
GridOverlapScanner.requestScan();
|
||||
AutoFocusWidget.instance.guiTick();
|
||||
|
||||
for (Widget widget : drawWidgets) widget.update();
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import net.minecraft.world.storage.SaveFormatComparator;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
@@ -57,6 +58,8 @@ import codechicken.nei.config.OptionToggleButtonBoubs;
|
||||
import codechicken.nei.config.OptionUtilities;
|
||||
import codechicken.nei.config.preset.GuiPresetList;
|
||||
import codechicken.nei.event.NEIConfigsLoadedEvent;
|
||||
import codechicken.nei.moboverlay.MobOverlayScan;
|
||||
import codechicken.nei.moboverlay.MobOverlayStyle;
|
||||
import codechicken.nei.recipe.IRecipeHandler;
|
||||
import codechicken.nei.recipe.InformationHandler;
|
||||
import codechicken.nei.recipe.RecipeCatalysts;
|
||||
@@ -442,6 +445,45 @@ public class NEIClientConfig {
|
||||
tag.getTag("world.overlays.lock").getBooleanValue(true);
|
||||
API.addOption(new OptionToggleButton("world.overlays.lock", true));
|
||||
|
||||
tag.getTag("world.overlays.mobOverlayStyle")
|
||||
.setComment("Mark on each block of the mob spawn overlay: " + MobOverlayStyle.formatAllNames())
|
||||
.getValue(MobOverlayStyle.DEFAULT.formatName());
|
||||
API.addOption(new OptionCycled("world.overlays.mobOverlayStyle", MobOverlayStyle.values().length, true) {
|
||||
|
||||
@Override
|
||||
public int value() {
|
||||
return MobOverlayStyle.parseStyle(getTag().getValue()).ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(int value) {
|
||||
getTag().setValue(MobOverlayStyle.values()[value].formatName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getButtonText() {
|
||||
final MobOverlayStyle style = MobOverlayStyle.parseStyle(getTag().getValue());
|
||||
return StatCollector.translateToLocal(style.getLangKey());
|
||||
}
|
||||
});
|
||||
|
||||
tag.getTag("world.overlays.mobOverlayRadius")
|
||||
.setComment("Blocks scanned on each side of the player by the mob spawn overlay")
|
||||
.getIntValue(MobOverlayScan.DEFAULT_RADIUS);
|
||||
API.addOption(
|
||||
new OptionIntegerField(
|
||||
"world.overlays.mobOverlayRadius",
|
||||
MobOverlayScan.MIN_RADIUS,
|
||||
MobOverlayScan.MAX_RADIUS));
|
||||
tag.getTag("world.overlays.mobOverlayVerticalRadius")
|
||||
.setComment("Blocks scanned above and below the player by the mob spawn overlay")
|
||||
.getIntValue(MobOverlayScan.DEFAULT_VERTICAL_RADIUS);
|
||||
API.addOption(
|
||||
new OptionIntegerField(
|
||||
"world.overlays.mobOverlayVerticalRadius",
|
||||
MobOverlayScan.MIN_VERTICAL_RADIUS,
|
||||
MobOverlayScan.MAX_VERTICAL_RADIUS));
|
||||
|
||||
tag.getTag("inventory.itempanelScale").getIntValue(100);
|
||||
ItemsPanelGrid.updateScale();
|
||||
API.addOption(new OptionIntegerField("inventory.itempanelScale", 1, 1000) {
|
||||
@@ -498,6 +540,10 @@ public class NEIClientConfig {
|
||||
tag.getTag("inventory.showItemQuantityWidget").setComment("Show Item Quantity Widget").getBooleanValue(true);
|
||||
API.addOption(new OptionToggleButton("inventory.showItemQuantityWidget", true));
|
||||
|
||||
tag.getTag("inventory.optimize_gui_overlap_computation").setComment("Optimize computation for GUI overlap")
|
||||
.getBooleanValue(false);
|
||||
API.addOption(new OptionToggleButton("inventory.optimize_gui_overlap_computation", true));
|
||||
|
||||
tag.getTag("inventory.autocrafting").setComment("Autocrafting").getBooleanValue(true);
|
||||
API.addOption(new OptionToggleButton("inventory.autocrafting", true));
|
||||
|
||||
@@ -1156,6 +1202,10 @@ public class NEIClientConfig {
|
||||
return getBooleanSetting("inventory.bookmarks.craftingTreeZoomWithoutModifier");
|
||||
}
|
||||
|
||||
public static boolean optimizeGuiOverlapComputation() {
|
||||
return getBooleanSetting("inventory.optimize_gui_overlap_computation");
|
||||
}
|
||||
|
||||
public static boolean showCycledIngredientsTooltip() {
|
||||
return getBooleanSetting("inventory.guirecipe.cycledIngredientsTooltip");
|
||||
}
|
||||
|
||||
@@ -29,9 +29,6 @@ public abstract class PanelWidget<T extends ItemsGrid<? extends ItemsGridSlot, ?
|
||||
|
||||
protected T grid;
|
||||
|
||||
private int headerPaddingLeft;
|
||||
private int headerPaddingRight;
|
||||
|
||||
public ArrayList<ItemStack> getItems() {
|
||||
return this.grid.getItems();
|
||||
}
|
||||
@@ -108,15 +105,26 @@ public abstract class PanelWidget<T extends ItemsGrid<? extends ItemsGridSlot, ?
|
||||
|
||||
protected int resizeHeader(GuiContainer gui) {
|
||||
final int BUTTON_SIZE = 16;
|
||||
int paddingLeft = 0;
|
||||
int paddingRight = 0;
|
||||
|
||||
if (GridOverlapScanner.isScanFrame()) {
|
||||
updateHeaderPadding(gui);
|
||||
while (paddingLeft < w && GuiInfo.hideItemPanelSlot(
|
||||
gui,
|
||||
new Rectangle4i(x + paddingLeft, y, ItemsGrid.SLOT_SIZE, ItemsGrid.SLOT_SIZE))) {
|
||||
paddingLeft += ItemsGrid.SLOT_SIZE;
|
||||
}
|
||||
|
||||
final int paddingLeft = this.headerPaddingLeft;
|
||||
final int paddingRight = this.headerPaddingRight;
|
||||
while (paddingRight < w && GuiInfo.hideItemPanelSlot(
|
||||
gui,
|
||||
new Rectangle4i(
|
||||
x + w - paddingRight - ItemsGrid.SLOT_SIZE,
|
||||
y,
|
||||
ItemsGrid.SLOT_SIZE,
|
||||
ItemsGrid.SLOT_SIZE))) {
|
||||
paddingRight += ItemsGrid.SLOT_SIZE;
|
||||
}
|
||||
|
||||
if (paddingLeft + paddingRight >= this.w) {
|
||||
if (paddingLeft + paddingRight >= w) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -124,35 +132,16 @@ public abstract class PanelWidget<T extends ItemsGrid<? extends ItemsGridSlot, ?
|
||||
pagePrev.h = pageNext.h = BUTTON_SIZE;
|
||||
pagePrev.y = pageNext.y = y;
|
||||
|
||||
pagePrev.x = this.x + paddingLeft;
|
||||
pageNext.x = this.x + this.w - pageNext.w - paddingRight;
|
||||
pagePrev.x = x + paddingLeft;
|
||||
pageNext.x = x + w - pageNext.w - paddingRight;
|
||||
|
||||
pageLabel.x = this.x + paddingLeft + (this.w - paddingLeft - paddingRight) / 2;
|
||||
pageLabel.y = this.y + 5;
|
||||
pageLabel.x = x + paddingLeft + (w - paddingLeft - paddingRight) / 2;
|
||||
pageLabel.y = y + 5;
|
||||
pageLabel.text = getLabelText();
|
||||
|
||||
return BUTTON_SIZE + 2;
|
||||
}
|
||||
|
||||
private void updateHeaderPadding(GuiContainer gui) {
|
||||
final INEIGuiHandler[] handlers = GuiInfo.getGuiHandlersSnapshot();
|
||||
final Rectangle4i slot = new Rectangle4i(this.x, this.y, ItemsGrid.SLOT_SIZE, ItemsGrid.SLOT_SIZE);
|
||||
this.headerPaddingLeft = 0;
|
||||
this.headerPaddingRight = 0;
|
||||
|
||||
while (this.headerPaddingLeft < this.w && GuiInfo.hideItemPanelSlot(gui, slot, handlers)) {
|
||||
this.headerPaddingLeft += ItemsGrid.SLOT_SIZE;
|
||||
slot.x += ItemsGrid.SLOT_SIZE;
|
||||
}
|
||||
|
||||
slot.x = this.x + this.w - ItemsGrid.SLOT_SIZE;
|
||||
|
||||
while (this.headerPaddingRight < this.w && GuiInfo.hideItemPanelSlot(gui, slot, handlers)) {
|
||||
this.headerPaddingRight += ItemsGrid.SLOT_SIZE;
|
||||
slot.x -= ItemsGrid.SLOT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int resizeFooter(GuiContainer gui);
|
||||
|
||||
public void setVisible() {
|
||||
|
||||
@@ -13,18 +13,21 @@ import net.minecraft.entity.EnumCreatureType;
|
||||
import net.minecraft.entity.passive.EntityPig;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.EnumSkyBlock;
|
||||
import net.minecraft.world.SpawnerAnimals;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.biome.BiomeGenBase;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.cardinalstar.cubicchunks.world.api.IMinMaxHeight;
|
||||
|
||||
import codechicken.lib.math.MathHelper;
|
||||
import codechicken.nei.KeyManager.IKeyStateTracker;
|
||||
import codechicken.nei.moboverlay.MobOverlayDisplayList;
|
||||
import codechicken.nei.moboverlay.MobOverlayFace;
|
||||
import codechicken.nei.moboverlay.MobOverlayScan;
|
||||
import codechicken.nei.moboverlay.MobOverlayStyle;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
|
||||
public class WorldOverlayRenderer implements IKeyStateTracker {
|
||||
@@ -32,8 +35,9 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
|
||||
public static int chunkOverlay = 0;
|
||||
|
||||
public static int mobOverlay = 0;
|
||||
private static byte[] mobSpawnCache;
|
||||
private static long mobOverlayUpdateTime;
|
||||
private static final MobOverlayScan MOB_OVERLAY_SCAN = new MobOverlayScan();
|
||||
private static final MobOverlayFace MARKED_FACE = new MobOverlayFace();
|
||||
private static final MobOverlayDisplayList MOB_OVERLAY_LIST = new MobOverlayDisplayList();
|
||||
|
||||
public static void reset() {
|
||||
mobOverlay = 0;
|
||||
@@ -82,51 +86,6 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
|
||||
GL11.glPopAttrib();
|
||||
}
|
||||
|
||||
private static void buildMobSpawnOverlay(Entity entity) {
|
||||
if (mobSpawnCache == null) {
|
||||
mobSpawnCache = new byte[33 * 33 * 33]; // 35kB
|
||||
}
|
||||
|
||||
World world = entity.worldObj;
|
||||
int worldHeight = world.getHeight();
|
||||
|
||||
int x1 = (int) entity.posX;
|
||||
int z1 = (int) entity.posZ;
|
||||
int y1 = (int) MathHelper.clip(entity.posY, 16, worldHeight - 16);
|
||||
|
||||
for (int i = 0; i <= 32; i++) {
|
||||
int x = x1 - 16 + i;
|
||||
for (int j = 0; j <= 32; j++) {
|
||||
int z = z1 - 16 + j;
|
||||
int bufIndex = (i * 33 + j) * 33;
|
||||
|
||||
Chunk chunk = world.getChunkFromBlockCoords(x, z);
|
||||
BiomeGenBase biome = world.getBiomeGenForCoords(x, z);
|
||||
if (biome.getSpawnableList(EnumCreatureType.monster).isEmpty() || biome.getSpawningChance() <= 0) {
|
||||
mobSpawnCache[bufIndex] = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int maxHeight = chunk.worldObj.getHeightValue(x, z);
|
||||
|
||||
for (int k = 0; k <= 32; k++) {
|
||||
int y = y1 - 16 + k;
|
||||
|
||||
if (y >= worldHeight) {
|
||||
mobSpawnCache[bufIndex + k] = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (y > maxHeight) {
|
||||
mobSpawnCache[bufIndex + k] = -1;
|
||||
break;
|
||||
}
|
||||
mobSpawnCache[bufIndex + k] = getSpawnMode(chunk, x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static double getOverlayYOffset(World world, int x, int y, int z) {
|
||||
Block block = world.getBlock(x, y, z);
|
||||
if (block instanceof BlockSnow) {
|
||||
@@ -136,72 +95,97 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
|
||||
return 0.02;
|
||||
}
|
||||
|
||||
/**
|
||||
* The model view matrix is relative to ({@code intOffsetX}, {@code intOffsetY}, {@code intOffsetZ}), the camera
|
||||
* position cast to {@code int}.
|
||||
*/
|
||||
private static void renderMobSpawnOverlay(Entity entity, int intOffsetX, int intOffsetY, int intOffsetZ) {
|
||||
if (mobOverlay == 0) {
|
||||
if (mobSpawnCache != null) {
|
||||
mobSpawnCache = null;
|
||||
}
|
||||
MOB_OVERLAY_SCAN.clear();
|
||||
MOB_OVERLAY_LIST.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
long worldTime = entity.worldObj.getTotalWorldTime();
|
||||
if (mobSpawnCache == null || mobOverlayUpdateTime != worldTime) {
|
||||
mobOverlayUpdateTime = worldTime;
|
||||
buildMobSpawnOverlay(entity);
|
||||
MOB_OVERLAY_SCAN.update(
|
||||
entity,
|
||||
NEIClientConfig.getIntSetting("world.overlays.mobOverlayRadius"),
|
||||
NEIClientConfig.getIntSetting("world.overlays.mobOverlayVerticalRadius"));
|
||||
final MobOverlayStyle style = MobOverlayStyle
|
||||
.parseStyle(NEIClientConfig.getSetting("world.overlays.mobOverlayStyle").getValue());
|
||||
final long worldTicks = entity.worldObj.getTotalWorldTime();
|
||||
if (MOB_OVERLAY_LIST.isCompileDue(MOB_OVERLAY_SCAN.getModesVersion(), style, worldTicks)) {
|
||||
compileMobOverlayList(entity.worldObj, style, worldTicks);
|
||||
}
|
||||
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
beginMobOverlayDraw(style);
|
||||
MOB_OVERLAY_LIST.draw(intOffsetX, intOffsetY, intOffsetZ);
|
||||
endMobOverlayDraw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a marked face to {@link #MOB_OVERLAY_LIST} for each block of {@link #MOB_OVERLAY_SCAN} where mobs can spawn.
|
||||
*/
|
||||
private static void compileMobOverlayList(World world, MobOverlayStyle style, long worldTicks) {
|
||||
MOB_OVERLAY_LIST.beginCompile(MOB_OVERLAY_SCAN, style, worldTicks);
|
||||
final float alpha = style.computeAlpha();
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawing(style.glMode);
|
||||
final int span = MOB_OVERLAY_SCAN.getSpan();
|
||||
for (int i = 0; i < span; i++) {
|
||||
for (int j = 0; j < span; j++) {
|
||||
for (int k = 0; k < MOB_OVERLAY_SCAN.getHeight(); k++) {
|
||||
if (MOB_OVERLAY_SCAN.readSpawnMode(i, k, j) == 0) continue;
|
||||
|
||||
final double yOffset = getOverlayYOffset(
|
||||
world,
|
||||
MOB_OVERLAY_SCAN.getMinX() + i,
|
||||
MOB_OVERLAY_SCAN.getMinY() + k,
|
||||
MOB_OVERLAY_SCAN.getMinZ() + j);
|
||||
MARKED_FACE.moveTo(i, k + yOffset, j);
|
||||
loadNeighbourModes(i, k, j);
|
||||
MARKED_FACE.applyColor(style.colouring, alpha);
|
||||
style.addVertices(MARKED_FACE);
|
||||
}
|
||||
}
|
||||
}
|
||||
tess.draw();
|
||||
MOB_OVERLAY_LIST.endCompile();
|
||||
}
|
||||
|
||||
private static void beginMobOverlayDraw(MobOverlayStyle style) {
|
||||
final ResourceLocation texture = style.texture;
|
||||
if (texture == null) {
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
} else {
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture(texture);
|
||||
}
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
GL11.glLineWidth(1.5F);
|
||||
// the outline of `MobOverlayStyle.CONNECTED` lies in the plane of the tint. With depth writes on, the depth
|
||||
// test discards most of the outline
|
||||
GL11.glDepthMask(false);
|
||||
}
|
||||
|
||||
int curSpawnMode = 2;
|
||||
|
||||
World world = entity.worldObj;
|
||||
int x1 = (int) entity.posX - intOffsetX;
|
||||
int z1 = (int) entity.posZ - intOffsetZ;
|
||||
int y1 = (int) MathHelper.clip(entity.posY, 16, world.getHeight() - 16) - intOffsetY;
|
||||
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawing(GL11.GL_LINES);
|
||||
tess.setColorOpaque(255, 0, 0);
|
||||
|
||||
for (int i = 0; i <= 32; i++) {
|
||||
int x = x1 - 16 + i;
|
||||
for (int j = 0; j <= 32; j++) {
|
||||
int z = z1 - 16 + j;
|
||||
int cacheIndex = (i * 33 + j) * 33;
|
||||
|
||||
for (int k = 0; k <= 32; k++) {
|
||||
int y = y1 - 16 + k;
|
||||
int spawnMode = mobSpawnCache[cacheIndex + k];
|
||||
if (spawnMode == 0) {
|
||||
continue;
|
||||
} else if (spawnMode == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (spawnMode != curSpawnMode) {
|
||||
if (spawnMode == 1) {
|
||||
tess.setColorOpaque(255, 255, 0);
|
||||
} else {
|
||||
tess.setColorOpaque(255, 0, 0);
|
||||
}
|
||||
curSpawnMode = spawnMode;
|
||||
}
|
||||
|
||||
double yOff = getOverlayYOffset(world, x + intOffsetX, y + intOffsetY, z + intOffsetZ);
|
||||
|
||||
tess.addVertex(x, y + yOff, z);
|
||||
tess.addVertex(x + 1, y + yOff, z + 1);
|
||||
tess.addVertex(x + 1, y + yOff, z);
|
||||
tess.addVertex(x, y + yOff, z + 1);
|
||||
}
|
||||
private static void loadNeighbourModes(int i, int k, int j) {
|
||||
final int span = MOB_OVERLAY_SCAN.getSpan();
|
||||
for (int offsetX = -1; offsetX <= 1; offsetX++) {
|
||||
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
|
||||
final int neighbourI = i + offsetX;
|
||||
final int neighbourJ = j + offsetZ;
|
||||
final boolean isInScan = neighbourI >= 0 && neighbourI < span && neighbourJ >= 0 && neighbourJ < span;
|
||||
final byte spawnMode = isInScan ? MOB_OVERLAY_SCAN.readSpawnMode(neighbourI, k, neighbourJ)
|
||||
: MobOverlayFace.OUTSIDE_SCAN;
|
||||
MARKED_FACE.setNeighbourMode(offsetX, offsetZ, spawnMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tess.draw();
|
||||
/** Undo the GL state changes of {@link #beginMobOverlayDraw}, except the texture binding. */
|
||||
private static void endMobOverlayDraw() {
|
||||
GL11.glDepthMask(true);
|
||||
GL11.glEnable(GL11.GL_LIGHTING);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
@@ -210,7 +194,7 @@ public class WorldOverlayRenderer implements IKeyStateTracker {
|
||||
private static final Entity dummyEntity = new EntityPig(null);
|
||||
private static final AxisAlignedBB aabb = AxisAlignedBB.getBoundingBox(0, 0, 0, 0, 0, 0);
|
||||
|
||||
private static byte getSpawnMode(Chunk chunk, int x, int y, int z) {
|
||||
public static byte getSpawnMode(Chunk chunk, int x, int y, int z) {
|
||||
if (y >= chunk.worldObj.getHeight()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import codechicken.nei.BookmarkPanel.BookmarkViewMode;
|
||||
import codechicken.nei.ItemSorter;
|
||||
@@ -131,7 +132,7 @@ public class BookmarkGridGenerator {
|
||||
itemIndex++;
|
||||
}
|
||||
|
||||
} else if (group.crafting != null && group.crafting.recipeToItems.containsKey(bookmarkItem.recipeId)) {
|
||||
} else if (group.crafting != null && group.crafting.itemToRecipe.values().contains(bookmarkItem.recipeId)) {
|
||||
absoluteSlotIndex = generateCollapsedRecipe(prevBookmarkItem, itemIndex, absoluteSlotIndex, borderGrid);
|
||||
prevBookmarkItem = bookmarkItem;
|
||||
|
||||
@@ -217,7 +218,8 @@ public class BookmarkGridGenerator {
|
||||
}
|
||||
|
||||
private List<Integer> getShadows(BookmarkGroup group, RecipeId recipeId) {
|
||||
return new ArrayList<>(group.crafting.recipeToItems.getOrDefault(recipeId, Collections.emptyList()));
|
||||
return group.crafting.itemToRecipe.entrySet().stream().filter(entry -> recipeId.equals(entry.getValue()))
|
||||
.map(entry -> entry.getKey()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private int nextSlotIndex(int index, BookmarkItem previousMeta, BookmarkItem meta, BookmarkViewMode viewMode) {
|
||||
|
||||
@@ -111,7 +111,6 @@ public class RecipeChainDetails {
|
||||
|
||||
public final Map<Integer, BookmarkChainItem> calculatedItems = new HashMap<>();
|
||||
public final Map<Integer, RecipeId> itemToRecipe = new HashMap<>();
|
||||
public final Map<RecipeId, List<Integer>> recipeToItems = new HashMap<>();
|
||||
public final Set<RecipeId> outputRecipes = new HashSet<>();
|
||||
public final Set<RecipeId> recipeInMiddle = new HashSet<>();
|
||||
|
||||
@@ -125,7 +124,6 @@ public class RecipeChainDetails {
|
||||
}
|
||||
|
||||
this.itemToRecipe.clear();
|
||||
this.recipeToItems.clear();
|
||||
this.outputRecipes.clear();
|
||||
this.recipeInMiddle.clear();
|
||||
this.calculatedItems.clear();
|
||||
@@ -182,10 +180,6 @@ public class RecipeChainDetails {
|
||||
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, RecipeId> entry : this.itemToRecipe.entrySet()) {
|
||||
this.recipeToItems.computeIfAbsent(entry.getValue(), recipeId -> new ArrayList<>()).add(entry.getKey());
|
||||
}
|
||||
|
||||
for (Map.Entry<Integer, BookmarkItem> entry : chainItems.entrySet()) {
|
||||
final int itemIndex = entry.getKey();
|
||||
if (itemIndex >= 0 && this.calculatedItems.containsKey(itemIndex)) {
|
||||
|
||||
@@ -37,8 +37,8 @@ public class SortableItem {
|
||||
&& (group.viewMode == BookmarkViewMode.TODO_LIST || group.crafting != null);
|
||||
this.crossGroup = !group.collapsed && group.viewMode == BookmarkViewMode.TODO_LIST
|
||||
&& bookmarkItem.type != BookmarkItemType.INGREDIENT;
|
||||
this.isCollapsedRecipe = group.crafting != null && this.bookmarkItem.recipeId != null
|
||||
&& group.crafting.recipeToItems.containsKey(this.bookmarkItem.recipeId);
|
||||
this.isCollapsedRecipe = group.crafting != null && group.crafting.itemToRecipe.values().stream()
|
||||
.anyMatch(recipeId -> this.bookmarkItem.equalsRecipe(recipeId, this.bookmarkItem.groupId));
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package codechicken.nei.moboverlay;
|
||||
|
||||
import net.minecraft.client.renderer.GLAllocation;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/** The mob spawn overlay quads, compiled into a GL display list. */
|
||||
public final class MobOverlayDisplayList {
|
||||
|
||||
private int listId = -1;
|
||||
private int modesVersion;
|
||||
private MobOverlayStyle style;
|
||||
private int minX;
|
||||
private int minY;
|
||||
private int minZ;
|
||||
private long compiledTicks;
|
||||
|
||||
/**
|
||||
* Compiles after a {@link MobOverlayScan#getModesVersion} change are debounced to one per {@code MIN_COMPILE_TICKS}
|
||||
* ticks, because the version changes several times per second while the player moves fast, and a compile takes a
|
||||
* millisecond or more.
|
||||
*/
|
||||
public boolean isCompileDue(int currentModesVersion, MobOverlayStyle currentStyle, long worldTicks) {
|
||||
final int MIN_COMPILE_TICKS = 5;
|
||||
if (listId < 0 || style != currentStyle) return true;
|
||||
|
||||
// after a dimension change or a server switch, `worldTicks` can be lower than `compiledTicks`
|
||||
final boolean isWaitOver = worldTicks - compiledTicks >= MIN_COMPILE_TICKS || worldTicks < compiledTicks;
|
||||
return modesVersion != currentModesVersion && isWaitOver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quad positions are relative to block ({@link MobOverlayScan#getMinX}, {@link MobOverlayScan#getMinY},
|
||||
* {@link MobOverlayScan#getMinZ}) of {@code scan}.
|
||||
*/
|
||||
public void beginCompile(MobOverlayScan scan, MobOverlayStyle newStyle, long worldTicks) {
|
||||
if (listId < 0) listId = GLAllocation.generateDisplayLists(1);
|
||||
modesVersion = scan.getModesVersion();
|
||||
style = newStyle;
|
||||
minX = scan.getMinX();
|
||||
minY = scan.getMinY();
|
||||
minZ = scan.getMinZ();
|
||||
compiledTicks = worldTicks;
|
||||
GL11.glNewList(listId, GL11.GL_COMPILE);
|
||||
}
|
||||
|
||||
public void endCompile() {
|
||||
GL11.glEndList();
|
||||
}
|
||||
|
||||
/** The model view matrix is relative to block ({@code cameraX}, {@code cameraY}, {@code cameraZ}). */
|
||||
public void draw(int cameraX, int cameraY, int cameraZ) {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glTranslated(minX - cameraX, minY - cameraY, minZ - cameraZ);
|
||||
GL11.glCallList(listId);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (listId < 0) return;
|
||||
|
||||
GLAllocation.deleteDisplayLists(listId);
|
||||
listId = -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package codechicken.nei.moboverlay;
|
||||
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
|
||||
/** Vertex builder for one block's top face in the mob spawn overlay. */
|
||||
public final class MobOverlayFace {
|
||||
|
||||
public static final byte OUTSIDE_SCAN = -2;
|
||||
|
||||
private static final int NEIGHBOURHOOD_SIZE = 3;
|
||||
|
||||
private final byte[] neighbourModes = new byte[NEIGHBOURHOOD_SIZE * NEIGHBOURHOOD_SIZE];
|
||||
private double x;
|
||||
private double y;
|
||||
private double z;
|
||||
|
||||
/** {@code (x, y, z)} is the north-west corner of the block's top face. */
|
||||
public void moveTo(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public void setNeighbourMode(int offsetX, int offsetZ, byte spawnMode) {
|
||||
neighbourModes[indexNeighbour(offsetX, offsetZ)] = spawnMode;
|
||||
}
|
||||
|
||||
public void applyColor(MobOverlayStyle.Colouring colouring, float alpha) {
|
||||
final boolean isNightOnly = neighbourModes[indexNeighbour(0, 0)] == MobOverlayScan.NIGHT_ONLY;
|
||||
final int rgb = switch (colouring) {
|
||||
case OVERLAY -> isNightOnly ? 0xFFFF00 : 0xFF0000;
|
||||
case STICKER -> isNightOnly ? 0xFFFFFF : 0xFF7373;
|
||||
};
|
||||
Tessellator.instance.setColorRGBA_I(rgb, Math.round(alpha * 255));
|
||||
}
|
||||
|
||||
public boolean isJoined(int offsetX, int offsetZ) {
|
||||
final byte neighbourMode = neighbourModes[indexNeighbour(offsetX, offsetZ)];
|
||||
// no outline along the edge of the scan
|
||||
return neighbourMode == neighbourModes[indexNeighbour(0, 0)] || neighbourMode == OUTSIDE_SCAN;
|
||||
}
|
||||
|
||||
public void addFullFace() {
|
||||
addVertex(0, 0);
|
||||
addVertex(0, 1);
|
||||
addVertex(1, 1);
|
||||
addVertex(1, 0);
|
||||
}
|
||||
|
||||
/** Add one cell of a 3 x 3 grid over the face. The outer rows and columns of the grid are {@code width} wide. */
|
||||
public void addCell(int offsetX, int offsetZ, double width) {
|
||||
final double west = computeCellStart(offsetX, width);
|
||||
final double east = 1 - computeCellStart(-offsetX, width);
|
||||
final double north = computeCellStart(offsetZ, width);
|
||||
final double south = 1 - computeCellStart(-offsetZ, width);
|
||||
addVertex(west, north);
|
||||
addVertex(west, south);
|
||||
addVertex(east, south);
|
||||
addVertex(east, north);
|
||||
}
|
||||
|
||||
/** The texture is upright to a player facing north (-z). */
|
||||
public void addTexturedFace() {
|
||||
addTexturedVertex(0, 0);
|
||||
addTexturedVertex(0, 1);
|
||||
addTexturedVertex(1, 1);
|
||||
addTexturedVertex(1, 0);
|
||||
}
|
||||
|
||||
/** @param offsetX 0 at the west edge, 1 at the east edge */
|
||||
public void addVertex(double offsetX, double offsetZ) {
|
||||
Tessellator.instance.addVertex(x + offsetX, y, z + offsetZ);
|
||||
}
|
||||
|
||||
private void addTexturedVertex(double offsetX, double offsetZ) {
|
||||
Tessellator.instance.setTextureUV(offsetX, offsetZ);
|
||||
addVertex(offsetX, offsetZ);
|
||||
}
|
||||
|
||||
private static int indexNeighbour(int offsetX, int offsetZ) {
|
||||
return (offsetX + 1) * NEIGHBOURHOOD_SIZE + offsetZ + 1;
|
||||
}
|
||||
|
||||
private static double computeCellStart(int offset, double width) {
|
||||
if (offset < 0) return 0;
|
||||
return offset == 0 ? width : 1 - width;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package codechicken.nei.moboverlay;
|
||||
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EnumCreatureType;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.world.IWorldAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.biome.BiomeGenBase;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import codechicken.lib.math.MathHelper;
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.WorldOverlayRenderer;
|
||||
|
||||
/**
|
||||
* Spawn modes in a box around the player, from {@link WorldOverlayRenderer#getSpawnMode}: 0 for no spawns, 1 for night
|
||||
* only and 2 for any time. The box moves with the player once the player is a few blocks from the box's centre, and
|
||||
* {@link #update} scans only the blocks that enter the box. {@link #update} rescans the column of a block or light
|
||||
* change on the next tick. On each tick, {@link #update} also rescans a slice of the columns, for changes with no block
|
||||
* update, such as a boat that moves into the box.
|
||||
*/
|
||||
public final class MobOverlayScan {
|
||||
|
||||
public static final byte NIGHT_ONLY = 1;
|
||||
public static final int MIN_RADIUS = 8;
|
||||
public static final int MAX_RADIUS = 64;
|
||||
public static final int DEFAULT_RADIUS = 32;
|
||||
public static final int MIN_VERTICAL_RADIUS = 4;
|
||||
public static final int MAX_VERTICAL_RADIUS = 64;
|
||||
public static final int DEFAULT_VERTICAL_RADIUS = 16;
|
||||
private static final int RECENTRE_DISTANCE = 4;
|
||||
private static final int VERTICAL_RECENTRE_DISTANCE = 2;
|
||||
private static final int RESCAN_TICKS = 40;
|
||||
|
||||
private final IWorldAccess changeListener = new ChangeListener();
|
||||
private final Column column = new Column();
|
||||
private int radius;
|
||||
private int verticalRadius;
|
||||
private int span;
|
||||
private int height;
|
||||
// a ring buffer over the box, indexed by `findCell()`
|
||||
private byte[] modes = new byte[0];
|
||||
// indexed by `findColumn(x, z)`
|
||||
private boolean[] dirty = new boolean[0];
|
||||
private boolean hasDirtyColumns;
|
||||
private int minX;
|
||||
private int minY;
|
||||
private int minZ;
|
||||
// the slice rescan of `update()` continues from this column, numbered `(x - minX) * span + (z - minZ)`
|
||||
private int nextRescanColumn;
|
||||
private int modesVersion;
|
||||
private boolean hasModes;
|
||||
private long scannedTick;
|
||||
// world of `modes`, with `changeListener` added
|
||||
private World scannedWorld;
|
||||
|
||||
public void clear() {
|
||||
if (scannedWorld != null) scannedWorld.removeWorldAccess(changeListener);
|
||||
radius = 0;
|
||||
verticalRadius = 0;
|
||||
span = 0;
|
||||
height = 0;
|
||||
modes = new byte[0];
|
||||
dirty = new boolean[0];
|
||||
hasModes = false;
|
||||
scannedWorld = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan every column in this call after {@link #clear}, a change of either radius, a world change, or a move of
|
||||
* {@link #getSpan} blocks or more across or {@link #getHeight} blocks or more up or down. {@code newRadius} is
|
||||
* clamped between {@link #MIN_RADIUS} and {@link #MAX_RADIUS}, and {@code newVerticalRadius} between
|
||||
* {@link #MIN_VERTICAL_RADIUS} and {@link #MAX_VERTICAL_RADIUS}.
|
||||
*/
|
||||
public void update(Entity entity, int newRadius, int newVerticalRadius) {
|
||||
final World world = entity.worldObj;
|
||||
final int clampedRadius = (int) MathHelper.clip(newRadius, MIN_RADIUS, MAX_RADIUS);
|
||||
final int clampedVerticalRadius = (int) MathHelper
|
||||
.clip(newVerticalRadius, MIN_VERTICAL_RADIUS, MAX_VERTICAL_RADIUS);
|
||||
if (radius != clampedRadius || verticalRadius != clampedVerticalRadius) {
|
||||
resize(clampedRadius, clampedVerticalRadius);
|
||||
}
|
||||
final int centreX = MathHelper.floor_double(entity.posX);
|
||||
final int centreY = (int) MathHelper.clip(entity.posY, verticalRadius, world.getHeight() - verticalRadius);
|
||||
final int centreZ = MathHelper.floor_double(entity.posZ);
|
||||
final boolean isNearBox = Math.abs(centreX - radius - minX) < span && Math.abs(centreZ - radius - minZ) < span
|
||||
&& Math.abs(centreY - verticalRadius - minY) < height;
|
||||
if (!(hasModes && world == scannedWorld && isNearBox)) {
|
||||
if (world != scannedWorld) watchWorld(world);
|
||||
scanFullBox(centreX, centreY, centreZ);
|
||||
return;
|
||||
}
|
||||
|
||||
final long worldTicks = world.getTotalWorldTime();
|
||||
if (worldTicks == scannedTick) return;
|
||||
|
||||
scannedTick = worldTicks;
|
||||
boolean isChanged = moveBox(centreX, centreY, centreZ);
|
||||
if (hasDirtyColumns) isChanged |= rescanDirtyColumns();
|
||||
isChanged |= rescanSlice();
|
||||
if (isChanged) modesVersion++;
|
||||
}
|
||||
|
||||
/** The version changes when a spawn mode changes or the box moves. */
|
||||
public int getModesVersion() {
|
||||
return modesVersion;
|
||||
}
|
||||
|
||||
public int getSpan() {
|
||||
return span;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getMinX() {
|
||||
return minX;
|
||||
}
|
||||
|
||||
public int getMinY() {
|
||||
return minY;
|
||||
}
|
||||
|
||||
public int getMinZ() {
|
||||
return minZ;
|
||||
}
|
||||
|
||||
/** {@code i} and {@code j} are from 0 to {@link #getSpan}, and {@code k} is from 0 to {@link #getHeight}. */
|
||||
public byte readSpawnMode(int i, int k, int j) {
|
||||
return modes[findCell(minX + i, minY + k, minZ + j)];
|
||||
}
|
||||
|
||||
private void resize(int newRadius, int newVerticalRadius) {
|
||||
radius = newRadius;
|
||||
verticalRadius = newVerticalRadius;
|
||||
span = 2 * newRadius + 1;
|
||||
height = 2 * newVerticalRadius + 1;
|
||||
modes = new byte[span * span * height];
|
||||
dirty = new boolean[span * span];
|
||||
hasModes = false;
|
||||
}
|
||||
|
||||
private void scanFullBox(int centreX, int centreY, int centreZ) {
|
||||
final long startNanos = System.nanoTime();
|
||||
minX = centreX - radius;
|
||||
minY = centreY - verticalRadius;
|
||||
minZ = centreZ - radius;
|
||||
for (int x = minX; x < minX + span; x++) {
|
||||
for (int z = minZ; z < minZ + span; z++) {
|
||||
scanColumn(x, z);
|
||||
}
|
||||
}
|
||||
hasModes = true;
|
||||
modesVersion++;
|
||||
scannedTick = scannedWorld.getTotalWorldTime();
|
||||
NEIClientConfig.logger.info(
|
||||
"moboverlay.scan.full radius={} verticalRadius={} columns={} micros={}",
|
||||
radius,
|
||||
verticalRadius,
|
||||
span * span,
|
||||
(System.nanoTime() - startNanos) / 1000);
|
||||
}
|
||||
|
||||
private void watchWorld(World world) {
|
||||
if (scannedWorld != null) scannedWorld.removeWorldAccess(changeListener);
|
||||
world.addWorldAccess(changeListener);
|
||||
scannedWorld = world;
|
||||
}
|
||||
|
||||
/** @return whether the box moved */
|
||||
private boolean moveBox(int centreX, int centreY, int centreZ) {
|
||||
boolean isMoved = false;
|
||||
final int offsetX = centreX - radius - minX;
|
||||
if (Math.abs(offsetX) > RECENTRE_DISTANCE) {
|
||||
final int enteringX = offsetX > 0 ? minX + span : minX + offsetX;
|
||||
minX += offsetX;
|
||||
for (int x = enteringX; x < enteringX + Math.abs(offsetX); x++) {
|
||||
for (int z = minZ; z < minZ + span; z++) {
|
||||
scanColumn(x, z);
|
||||
}
|
||||
}
|
||||
isMoved = true;
|
||||
}
|
||||
|
||||
final int offsetZ = centreZ - radius - minZ;
|
||||
if (Math.abs(offsetZ) > RECENTRE_DISTANCE) {
|
||||
final int enteringZ = offsetZ > 0 ? minZ + span : minZ + offsetZ;
|
||||
minZ += offsetZ;
|
||||
for (int x = minX; x < minX + span; x++) {
|
||||
for (int z = enteringZ; z < enteringZ + Math.abs(offsetZ); z++) {
|
||||
scanColumn(x, z);
|
||||
}
|
||||
}
|
||||
isMoved = true;
|
||||
}
|
||||
|
||||
final int offsetY = centreY - verticalRadius - minY;
|
||||
if (Math.abs(offsetY) > VERTICAL_RECENTRE_DISTANCE) {
|
||||
final int enteringY = offsetY > 0 ? minY + height : minY + offsetY;
|
||||
minY += offsetY;
|
||||
for (int x = minX; x < minX + span; x++) {
|
||||
for (int z = minZ; z < minZ + span; z++) {
|
||||
column.moveTo(x, z);
|
||||
column.scan(enteringY, enteringY + Math.abs(offsetY));
|
||||
}
|
||||
}
|
||||
isMoved = true;
|
||||
}
|
||||
return isMoved;
|
||||
}
|
||||
|
||||
private boolean rescanDirtyColumns() {
|
||||
hasDirtyColumns = false;
|
||||
boolean isChanged = false;
|
||||
for (int x = minX; x < minX + span; x++) {
|
||||
for (int z = minZ; z < minZ + span; z++) {
|
||||
if (dirty[findColumn(x, z)]) isChanged |= scanColumn(x, z);
|
||||
}
|
||||
}
|
||||
return isChanged;
|
||||
}
|
||||
|
||||
private boolean rescanSlice() {
|
||||
final int columnCount = span * span;
|
||||
final int sliceEnd = Math.min(columnCount, nextRescanColumn + (columnCount + RESCAN_TICKS - 1) / RESCAN_TICKS);
|
||||
boolean isChanged = false;
|
||||
for (; nextRescanColumn < sliceEnd; nextRescanColumn++) {
|
||||
final int x = minX + nextRescanColumn / span;
|
||||
final int z = minZ + nextRescanColumn % span;
|
||||
isChanged |= scanColumn(x, z);
|
||||
}
|
||||
if (nextRescanColumn == columnCount) nextRescanColumn = 0;
|
||||
return isChanged;
|
||||
}
|
||||
|
||||
/** @return whether a spawn mode changed */
|
||||
private boolean scanColumn(int x, int z) {
|
||||
column.moveTo(x, z);
|
||||
dirty[findColumn(x, z)] = false;
|
||||
return column.scan(minY, minY + height);
|
||||
}
|
||||
|
||||
private int findColumn(int x, int z) {
|
||||
return Math.floorMod(x, span) * span + Math.floorMod(z, span);
|
||||
}
|
||||
|
||||
private int findCell(int x, int y, int z) {
|
||||
return findColumn(x, z) * height + Math.floorMod(y, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link #moveTo} reads the chunk, the height map and the biome of a column, and {@link #scan} reads the column's
|
||||
* blocks.
|
||||
*/
|
||||
private final class Column {
|
||||
|
||||
private int x;
|
||||
private int z;
|
||||
private Chunk chunk;
|
||||
private int topY;
|
||||
private boolean canBiomeSpawn;
|
||||
|
||||
private void moveTo(int newX, int newZ) {
|
||||
x = newX;
|
||||
z = newZ;
|
||||
final BiomeGenBase biome = scannedWorld.getBiomeGenForCoords(x, z);
|
||||
canBiomeSpawn = !biome.getSpawnableList(EnumCreatureType.monster).isEmpty()
|
||||
&& biome.getSpawningChance() > 0;
|
||||
chunk = scannedWorld.getChunkFromBlockCoords(x, z);
|
||||
topY = Math.min(scannedWorld.getHeight() - 1, scannedWorld.getHeightValue(x, z));
|
||||
}
|
||||
|
||||
/** @return whether a spawn mode changed */
|
||||
private boolean scan(int fromY, int toY) {
|
||||
boolean isChanged = false;
|
||||
for (int y = fromY; y < toY; y++) {
|
||||
final byte spawnMode = canBiomeSpawn && y <= topY ? WorldOverlayRenderer.getSpawnMode(chunk, x, y, z)
|
||||
: 0;
|
||||
final int cell = findCell(x, y, z);
|
||||
isChanged |= modes[cell] != spawnMode;
|
||||
modes[cell] = spawnMode;
|
||||
}
|
||||
return isChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/** Marks the columns of the block and light changes in {@link #scannedWorld}. */
|
||||
private final class ChangeListener implements IWorldAccess {
|
||||
|
||||
@Override
|
||||
public void markBlockForUpdate(int x, int y, int z) {
|
||||
markChanged(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markBlockForRenderUpdate(int x, int y, int z) {
|
||||
markChanged(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markBlockRangeForRenderUpdate(int fromX, int fromY, int fromZ, int toX, int toY, int toZ) {
|
||||
for (int x = Math.max(fromX, minX); x <= Math.min(toX, minX + span - 1); x++) {
|
||||
for (int z = Math.max(fromZ, minZ); z <= Math.min(toZ, minZ + span - 1); z++) {
|
||||
markColumn(x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(String soundName, double x, double y, double z, float volume, float pitch) {}
|
||||
|
||||
@Override
|
||||
public void playSoundToNearExcept(EntityPlayer player, String soundName, double x, double y, double z,
|
||||
float volume, float pitch) {}
|
||||
|
||||
@Override
|
||||
public void spawnParticle(String particleName, double x, double y, double z, double velocityX, double velocityY,
|
||||
double velocityZ) {}
|
||||
|
||||
@Override
|
||||
public void onEntityCreate(Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void onEntityDestroy(Entity entity) {}
|
||||
|
||||
@Override
|
||||
public void playRecord(String recordName, int x, int y, int z) {}
|
||||
|
||||
@Override
|
||||
public void broadcastSound(int soundId, int x, int y, int z, int data) {}
|
||||
|
||||
@Override
|
||||
public void playAuxSFX(EntityPlayer player, int sfxType, int x, int y, int z, int data) {}
|
||||
|
||||
@Override
|
||||
public void destroyBlockPartially(int breakerId, int x, int y, int z, int progress) {}
|
||||
|
||||
@Override
|
||||
public void onStaticEntitiesChanged() {}
|
||||
|
||||
private void markChanged(int x, int y, int z) {
|
||||
// spawn modes depend on the block below and the block above
|
||||
final boolean isInBox = x >= minX && x < minX + span && z >= minZ && z < minZ + span;
|
||||
if (isInBox && y >= minY - 1 && y <= minY + height) markColumn(x, z);
|
||||
}
|
||||
|
||||
private void markColumn(int x, int z) {
|
||||
dirty[findColumn(x, z)] = true;
|
||||
hasDirtyColumns = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package codechicken.nei.moboverlay;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Each style is a mark drawn on every block where mobs can spawn. {@code world.overlays.mobOverlayStyle} is saved as
|
||||
* {@link #formatName()}.
|
||||
*/
|
||||
public enum MobOverlayStyle {
|
||||
|
||||
/** NEI's original cross from corner to corner. */
|
||||
X(GL11.GL_LINES) {
|
||||
|
||||
@Override
|
||||
public void addVertices(MobOverlayFace face) {
|
||||
face.addVertex(0, 0);
|
||||
face.addVertex(1, 1);
|
||||
face.addVertex(1, 0);
|
||||
face.addVertex(0, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* A light tint, with a stronger outline around each area of one spawn mode, like a connected texture.
|
||||
*/
|
||||
CONNECTED(GL11.GL_QUADS) {
|
||||
|
||||
@Override
|
||||
public void addVertices(MobOverlayFace face) {
|
||||
final double OUTLINE_WIDTH = 1.0 / 16;
|
||||
face.addFullFace();
|
||||
face.applyColor(Colouring.OVERLAY, MARK_ALPHA);
|
||||
for (int offsetX = -1; offsetX <= 1; offsetX++) {
|
||||
for (int offsetZ = -1; offsetZ <= 1; offsetZ++) {
|
||||
// a corner cell is drawn when either edge next to the corner is outlined, and on an inside corner
|
||||
final boolean isOutline = !(face.isJoined(offsetX, offsetZ) && face.isJoined(offsetX, 0)
|
||||
&& face.isJoined(0, offsetZ));
|
||||
if (isOutline) face.addCell(offsetX, offsetZ, OUTLINE_WIDTH);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** A small square in the middle of the block, with no tint. */
|
||||
SQUARE(GL11.GL_QUADS) {
|
||||
|
||||
@Override
|
||||
public void addVertices(MobOverlayFace face) {
|
||||
final double CENTRE = 0.5;
|
||||
final double HALF_SIDE = 7.0 / 64;
|
||||
face.addVertex(CENTRE - HALF_SIDE, CENTRE - HALF_SIDE);
|
||||
face.addVertex(CENTRE - HALF_SIDE, CENTRE + HALF_SIDE);
|
||||
face.addVertex(CENTRE + HALF_SIDE, CENTRE + HALF_SIDE);
|
||||
face.addVertex(CENTRE + HALF_SIDE, CENTRE - HALF_SIDE);
|
||||
}
|
||||
},
|
||||
|
||||
/** The GTNH logo without the globe. */
|
||||
GTNH_LOGO("gtnh_logo", Colouring.OVERLAY) {
|
||||
|
||||
@Override
|
||||
public void addVertices(MobOverlayFace face) {
|
||||
face.addTexturedFace();
|
||||
}
|
||||
},
|
||||
|
||||
/** The pufferfish sticker. */
|
||||
PUFFERFISH("pufferfish", Colouring.STICKER) {
|
||||
|
||||
@Override
|
||||
public void addVertices(MobOverlayFace face) {
|
||||
face.addTexturedFace();
|
||||
}
|
||||
};
|
||||
|
||||
public static final MobOverlayStyle DEFAULT = CONNECTED;
|
||||
|
||||
private static final float TINT_ALPHA = 0.15F;
|
||||
// the square and the outline cover a small part of the block and need more opacity than the tint to stay visible
|
||||
private static final float MARK_ALPHA = 0.45F;
|
||||
|
||||
public final int glMode;
|
||||
@Nullable
|
||||
public final ResourceLocation texture;
|
||||
public final Colouring colouring;
|
||||
|
||||
MobOverlayStyle(int glMode) {
|
||||
this.glMode = glMode;
|
||||
this.texture = null;
|
||||
this.colouring = Colouring.OVERLAY;
|
||||
}
|
||||
|
||||
MobOverlayStyle(String textureName, Colouring colouring) {
|
||||
this.glMode = GL11.GL_QUADS;
|
||||
this.texture = new ResourceLocation("nei", "textures/overlay/" + textureName + ".png");
|
||||
this.colouring = colouring;
|
||||
}
|
||||
|
||||
/** Case-insensitive. Returns {@link #DEFAULT} for an unknown name. */
|
||||
public static MobOverlayStyle parseStyle(String rawStyle) {
|
||||
for (MobOverlayStyle candidate : values()) {
|
||||
if (candidate.formatName().equalsIgnoreCase(rawStyle)) return candidate;
|
||||
}
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
/** @return the {@link #formatName} of every style, separated by commas */
|
||||
public static String formatAllNames() {
|
||||
final StringJoiner names = new StringJoiner(", ");
|
||||
for (MobOverlayStyle style : values()) {
|
||||
names.add(style.formatName());
|
||||
}
|
||||
return names.toString();
|
||||
}
|
||||
|
||||
public String formatName() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
public String getLangKey() {
|
||||
return switch (this) {
|
||||
case X -> "nei.options.world.overlays.mobOverlayStyle.x";
|
||||
case CONNECTED -> "nei.options.world.overlays.mobOverlayStyle.connected";
|
||||
case SQUARE -> "nei.options.world.overlays.mobOverlayStyle.square";
|
||||
case GTNH_LOGO -> "nei.options.world.overlays.mobOverlayStyle.gtnh_logo";
|
||||
case PUFFERFISH -> "nei.options.world.overlays.mobOverlayStyle.pufferfish";
|
||||
};
|
||||
}
|
||||
|
||||
public float computeAlpha() {
|
||||
return switch (this) {
|
||||
case CONNECTED -> TINT_ALPHA;
|
||||
case SQUARE -> MARK_ALPHA;
|
||||
// the alpha is in the PNGs
|
||||
case X, GTNH_LOGO, PUFFERFISH -> 1.0F;
|
||||
};
|
||||
}
|
||||
|
||||
public abstract void addVertices(MobOverlayFace face);
|
||||
|
||||
public enum Colouring {
|
||||
/** Yellow where mobs spawn only at night, red where mobs spawn at any time. */
|
||||
OVERLAY,
|
||||
/** The texture's colours, tinted red where mobs spawn at any time. */
|
||||
STICKER
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -17,7 +16,7 @@ import net.minecraftforge.fluids.FluidStack;
|
||||
import net.minecraftforge.fluids.IFluidContainerItem;
|
||||
|
||||
import codechicken.nei.ClientHandler;
|
||||
import codechicken.nei.ItemStackMap.StackMetaKey;
|
||||
import codechicken.nei.ItemStackMap;
|
||||
import codechicken.nei.LRUCache;
|
||||
import codechicken.nei.api.IStackStringifyHandler;
|
||||
import codechicken.nei.api.ItemInfo;
|
||||
@@ -32,7 +31,7 @@ public class StackInfo {
|
||||
private static final FluidStack NULL_FLUID = new FluidStack(FluidRegistry.WATER, 0);
|
||||
public static final List<IStackStringifyHandler> stackStringifyHandlers = new ArrayList<>();
|
||||
private static final Map<String, HashMap<String, String[]>> guidfilters = new HashMap<>();
|
||||
private static final Map<Item, Map<StackMetaKey, String>> guidcache = new ConcurrentHashMap<>();
|
||||
private static final ItemStackMap<String> guidcache = new ItemStackMap<>();
|
||||
private static final LRUCache<ItemStackKey, FluidStack> fluidcache = new LRUCache<>(200);
|
||||
private static boolean isPausedItemDamageSound = false;
|
||||
|
||||
@@ -183,9 +182,7 @@ public class StackInfo {
|
||||
}
|
||||
|
||||
public static String getItemStackGUID(ItemStack stack) {
|
||||
final Item stackItem = stack.getItem();
|
||||
final Map<StackMetaKey, String> itemGuids = stackItem != null ? guidcache.get(stackItem) : null;
|
||||
String guid = itemGuids != null ? itemGuids.get(new StackMetaKey(stack)) : null;
|
||||
String guid = guidcache.get(stack);
|
||||
|
||||
if (guid != null) {
|
||||
return guid;
|
||||
@@ -193,7 +190,7 @@ public class StackInfo {
|
||||
|
||||
final NBTTagCompound nbTag = itemStackToNBT(stack, false);
|
||||
|
||||
if (nbTag == null || stack.getItem() == null) {
|
||||
if (nbTag == null) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
@@ -242,15 +239,16 @@ public class StackInfo {
|
||||
}
|
||||
});
|
||||
|
||||
guid = keys.toString();
|
||||
synchronized (guidcache) {
|
||||
guidcache.put(stack, keys.toString());
|
||||
}
|
||||
} else {
|
||||
guid = NBTHelper.toString(nbTag);
|
||||
synchronized (guidcache) {
|
||||
guidcache.put(stack, NBTHelper.toString(nbTag));
|
||||
}
|
||||
}
|
||||
|
||||
guidcache.computeIfAbsent(stack.getItem(), item -> new ConcurrentHashMap<>())
|
||||
.put(new StackMetaKey(stack.copy()), guid);
|
||||
|
||||
return guid;
|
||||
return guidcache.get(stack);
|
||||
}
|
||||
|
||||
public static void loadGuidFilters() {
|
||||
|
||||
@@ -10,9 +10,6 @@ import net.minecraftforge.fluids.Fluid;
|
||||
import net.minecraftforge.fluids.FluidRegistry;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
|
||||
import codechicken.nei.api.IStackStringifyHandler;
|
||||
import codechicken.nei.item.ItemFluidDisplay;
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
@@ -25,11 +22,6 @@ public class GTFluidStackStringifyHandler implements IStackStringifyHandler {
|
||||
protected static Class<?> gtMetaGeneratedTool = null;
|
||||
protected static Field playSound = null;
|
||||
|
||||
private static final Supplier<Item> AE2FC_FLUID_PACKET = Suppliers
|
||||
.memoize(() -> GameRegistry.findItem("ae2fc", "fluid_packet"));
|
||||
private static final Supplier<Item> AE2FC_FLUID_DROP = Suppliers
|
||||
.memoize(() -> GameRegistry.findItem("ae2fc", "fluid_drop"));
|
||||
|
||||
static {
|
||||
try {
|
||||
final ClassLoader loader = GTFluidStackStringifyHandler.class.getClassLoader();
|
||||
@@ -102,11 +94,11 @@ public class GTFluidStackStringifyHandler implements IStackStringifyHandler {
|
||||
return new FluidStack(fluid, 0);
|
||||
}
|
||||
|
||||
} else if (item == AE2FC_FLUID_PACKET.get()) {
|
||||
} else if (item == GameRegistry.findItem("ae2fc", "fluid_packet")) {
|
||||
NBTTagCompound nbtTag = stack.getTagCompound();
|
||||
|
||||
return FluidStack.loadFluidStackFromNBT((NBTTagCompound) nbtTag.getTag("FluidStack"));
|
||||
} else if (item == AE2FC_FLUID_DROP.get()) {
|
||||
} else if (item == GameRegistry.findItem("ae2fc", "fluid_drop")) {
|
||||
NBTTagCompound nbtTag = stack.getTagCompound();
|
||||
Fluid fluid = FluidRegistry.getFluid(nbtTag.getString("Fluid").toLowerCase());
|
||||
|
||||
@@ -121,9 +113,9 @@ public class GTFluidStackStringifyHandler implements IStackStringifyHandler {
|
||||
|
||||
@Override
|
||||
public ItemStack normalizeRecipeQueryStack(ItemStack stack) {
|
||||
final Item item = stack.getItem();
|
||||
|
||||
if (item == AE2FC_FLUID_DROP.get() || item == AE2FC_FLUID_PACKET.get()) {
|
||||
if (stack.getItem() == GameRegistry.findItem("ae2fc", "fluid_drop")
|
||||
|| stack.getItem() == GameRegistry.findItem("ae2fc", "fluid_packet")) {
|
||||
final FluidStack fluidStack = getFluid(stack);
|
||||
|
||||
if (fluidStack != null) {
|
||||
@@ -147,7 +139,8 @@ public class GTFluidStackStringifyHandler implements IStackStringifyHandler {
|
||||
@Override
|
||||
public boolean isFluidDisplayItem(ItemStack stack) {
|
||||
final Item item = stack.getItem();
|
||||
return (itemFluidDisplay != null && itemFluidDisplay.isInstance(item)) || item == AE2FC_FLUID_PACKET.get();
|
||||
return (itemFluidDisplay != null && itemFluidDisplay.isInstance(item))
|
||||
|| item == GameRegistry.findItem("ae2fc", "fluid_packet");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,9 +26,6 @@ public class FavoriteStorage {
|
||||
protected final Map<RecipeId, Long> recipeIdToItemHash = new HashMap<>();
|
||||
protected final Map<Long, ItemStack> itemHashToItemStack = new HashMap<>();
|
||||
|
||||
/** The key of the last looked up stack. Its NBT copies the whole stack, which is looked up every frame. */
|
||||
private static volatile LookupKey lastLookupKey = null;
|
||||
|
||||
public void clear() {
|
||||
this.itemStackToRecipeId.clear();
|
||||
this.fluidToRecipeId.clear();
|
||||
@@ -98,7 +95,7 @@ public class FavoriteStorage {
|
||||
RecipeId recipeId = this.itemHashToRecipeId.get(getItemKey(stack));
|
||||
|
||||
if (recipeId == null) {
|
||||
recipeId = this.itemStackToRecipeId.get(getLookupKey(stack));
|
||||
recipeId = this.itemStackToRecipeId.get(StackInfo.itemStackToNBT(stack, false));
|
||||
}
|
||||
|
||||
if (recipeId == null) {
|
||||
@@ -108,28 +105,6 @@ public class FavoriteStorage {
|
||||
return recipeId;
|
||||
}
|
||||
|
||||
private static NBTTagCompound getLookupKey(ItemStack stack) {
|
||||
LookupKey key = lastLookupKey;
|
||||
|
||||
if (key == null || !key.stack.isItemEqual(stack) || !ItemStack.areItemStackTagsEqual(key.stack, stack)) {
|
||||
key = new LookupKey(stack.copy(), StackInfo.itemStackToNBT(stack, false));
|
||||
lastLookupKey = key;
|
||||
}
|
||||
|
||||
return key.nbt;
|
||||
}
|
||||
|
||||
private static class LookupKey {
|
||||
|
||||
private final ItemStack stack;
|
||||
private final NBTTagCompound nbt;
|
||||
|
||||
private LookupKey(ItemStack stack, NBTTagCompound nbt) {
|
||||
this.stack = stack;
|
||||
this.nbt = nbt;
|
||||
}
|
||||
}
|
||||
|
||||
public ItemStack getItemStack(RecipeId recipeId) {
|
||||
NBTTagCompound itemStackNBT = this.recipeIdToItemStack.get(recipeId);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.minecraft.creativetab.CreativeTabs;
|
||||
@@ -129,8 +128,8 @@ public class ItemUntranslator {
|
||||
private static final String CONFIG_FILE = "untranslator.cfg";
|
||||
private static final ItemUntranslator instance = new ItemUntranslator();
|
||||
|
||||
private final Map<String, String> secondNames = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> processedNames = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> secondNames = new HashMap<>();
|
||||
private final Map<String, String> processedNames = new HashMap<>();
|
||||
|
||||
private ItemUntranslator() {}
|
||||
|
||||
@@ -242,19 +241,14 @@ public class ItemUntranslator {
|
||||
return EnumChatFormatting.getTextWithoutFormattingCodes(name).trim();
|
||||
}
|
||||
|
||||
public String getItemStackDisplayName(ItemStack stack) {
|
||||
public synchronized String getItemStackDisplayName(ItemStack stack) {
|
||||
|
||||
if (stack == null || this.secondNames.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final String guidKey = StackInfo.getItemStackGUID(stack);
|
||||
final String displayName = this.processedNames.get(guidKey);
|
||||
|
||||
return displayName != null ? displayName : processDisplayName(stack, guidKey);
|
||||
}
|
||||
|
||||
private synchronized String processDisplayName(ItemStack stack, String guidKey) {
|
||||
return this.processedNames.computeIfAbsent(guidKey, stackKey -> {
|
||||
String displayName = this.secondNames.get(stackKey);
|
||||
|
||||
|
||||
@@ -412,6 +412,9 @@ nei.options.inventory.search.subsetsSearchMode=Item Subsets Search
|
||||
nei.options.inventory.search.subsetsSearchMode.0=Always
|
||||
nei.options.inventory.search.subsetsSearchMode.1=Prefix: %s
|
||||
nei.options.inventory.search.subsetsSearchMode.2=Never
|
||||
nei.options.inventory.optimize_gui_overlap_computation=Optimize computation for GUI overlap
|
||||
nei.options.inventory.optimize_gui_overlap_computation.true=Yes
|
||||
nei.options.inventory.optimize_gui_overlap_computation.false=No
|
||||
nei.options.inventory.autocrafting=Autocrafting
|
||||
nei.options.inventory.autocrafting.true=Enabled
|
||||
nei.options.inventory.autocrafting.false=Disabled
|
||||
@@ -563,6 +566,16 @@ nei.options.world.overlays=Overlays Settings
|
||||
nei.options.world.overlays.lock=Lock overlays between dimensions
|
||||
nei.options.world.overlays.lock.true=Enabled
|
||||
nei.options.world.overlays.lock.false=Disabled
|
||||
nei.options.world.overlays.mobOverlayStyle=Mob Spawn Overlay Style
|
||||
nei.options.world.overlays.mobOverlayStyle.x=X
|
||||
nei.options.world.overlays.mobOverlayStyle.connected=Connected
|
||||
nei.options.world.overlays.mobOverlayStyle.square=Square
|
||||
nei.options.world.overlays.mobOverlayStyle.gtnh_logo=GTNH Logo
|
||||
nei.options.world.overlays.mobOverlayStyle.pufferfish=Pufferfish
|
||||
nei.options.world.overlays.mobOverlayRadius=Mob Spawn Overlay Horizontal Radius
|
||||
nei.options.world.overlays.mobOverlayRadius.tip=Horizontal block radius scanned around you. Range 8-64 blocks
|
||||
nei.options.world.overlays.mobOverlayVerticalRadius=Mob Spawn Overlay Vertical Radius
|
||||
nei.options.world.overlays.mobOverlayVerticalRadius.tip=Blocks scanned above and below you. Range 4-64 blocks
|
||||
|
||||
nei.options.tools=Tools
|
||||
nei.options.tools.export_configs=Default Config Files
|
||||
|
||||
@@ -251,6 +251,9 @@ nei.options.inventory.search.subsetsSearchMode=Поиск по подмноже
|
||||
nei.options.inventory.search.subsetsSearchMode.0=Всегда
|
||||
nei.options.inventory.search.subsetsSearchMode.1=Префикс: %s
|
||||
nei.options.inventory.search.subsetsSearchMode.2=Никогда
|
||||
nei.options.inventory.optimize_gui_overlap_computation=Оптимизация вычислений при перекрытии GUI
|
||||
nei.options.inventory.optimize_gui_overlap_computation.true=Да
|
||||
nei.options.inventory.optimize_gui_overlap_computation.false=Нет
|
||||
nei.options.inventory.guirecipe.shiftOverlayRecipe=Требовать удержание Shift для перемещения предметов при отображении рецепта
|
||||
nei.options.inventory.guirecipe.shiftOverlayRecipe.true=Да
|
||||
nei.options.inventory.guirecipe.shiftOverlayRecipe.false=Нет
|
||||
|
||||
@@ -359,6 +359,9 @@ nei.options.inventory.search.subsetsSearchMode=Пошук у підгрупах
|
||||
nei.options.inventory.search.subsetsSearchMode.0=Завжди
|
||||
nei.options.inventory.search.subsetsSearchMode.1=Префікс: %s
|
||||
nei.options.inventory.search.subsetsSearchMode.2=Ніколи
|
||||
nei.options.inventory.optimize_gui_overlap_computation=Оптимізувати обчислення перекриття інтерфейсів
|
||||
nei.options.inventory.optimize_gui_overlap_computation.true=Так
|
||||
nei.options.inventory.optimize_gui_overlap_computation.false=Ні
|
||||
nei.options.inventory.autocrafting=Автокрафт
|
||||
nei.options.inventory.autocrafting.true=Увімкнено
|
||||
nei.options.inventory.autocrafting.false=Вимкнено
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"texture": {
|
||||
"blur": true,
|
||||
"clamp": true
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"texture": {
|
||||
"blur": false,
|
||||
"clamp": true
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -15,7 +16,7 @@ import org.junit.jupiter.api.Test;
|
||||
public class ItemsGridTest {
|
||||
|
||||
@Test
|
||||
void refreshesCaptureOnlyWhenGridChanged() {
|
||||
void synchronizesOverlapAndCaptureRefresh() {
|
||||
TestGrid grid = new TestGrid();
|
||||
ItemsGrid.ScreenCapture capture = mock(ItemsGrid.ScreenCapture.class);
|
||||
grid.screenCapture = capture;
|
||||
@@ -23,48 +24,22 @@ public class ItemsGridTest {
|
||||
grid.setGridSize(1, 2, 18, 18);
|
||||
grid.setGridSize(1, 2, 18, 18);
|
||||
verify(capture, times(1)).refreshBuffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshesOverlapOnlyOnGridChangeOrScanFrame() {
|
||||
TestGrid grid = new TestGrid();
|
||||
|
||||
GridOverlapScanner.requestScan();
|
||||
GridOverlapScanner.beginFrame(null, true);
|
||||
grid.setGridSize(1, 2, 18, 18);
|
||||
when(capture.needRefresh(1)).thenReturn(false, true);
|
||||
grid.refresh(null);
|
||||
grid.refresh(null);
|
||||
assertEquals(1, grid.overlapRefreshes);
|
||||
|
||||
GridOverlapScanner.beginFrame(null, true);
|
||||
grid.refresh(null);
|
||||
assertEquals(1, grid.overlapRefreshes);
|
||||
|
||||
grid.setGridSize(1, 2, 36, 18);
|
||||
grid.refresh(null);
|
||||
assertEquals(2, grid.overlapRefreshes);
|
||||
|
||||
GridOverlapScanner.requestScan();
|
||||
GridOverlapScanner.beginFrame(null, true);
|
||||
grid.refresh(null);
|
||||
assertEquals(3, grid.overlapRefreshes);
|
||||
|
||||
GridOverlapScanner.beginFrame(null, false);
|
||||
grid.refresh(null);
|
||||
assertEquals(4, grid.overlapRefreshes);
|
||||
|
||||
GridOverlapScanner.beginFrame(null, false);
|
||||
grid.refresh(null);
|
||||
assertEquals(4, grid.overlapRefreshes);
|
||||
|
||||
GridOverlapScanner.beginFrame(null, true);
|
||||
grid.refresh(null);
|
||||
assertEquals(5, grid.overlapRefreshes);
|
||||
}
|
||||
|
||||
private static final class TestGrid extends ItemsGrid<ItemsGrid.ItemsGridSlot, ItemsGrid.MouseContext> {
|
||||
|
||||
private int overlapRefreshes;
|
||||
|
||||
@Override
|
||||
protected int getGridRenderingCacheMode() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateGuiOverlapSlots(GuiContainer gui) {
|
||||
overlapRefreshes++;
|
||||
|
||||
Reference in new issue
Block a user