diff --git a/src/main/java/com/hbm/blocks/BlockDummyable.java b/src/main/java/com/hbm/blocks/BlockDummyable.java index fa604e66e..9508350b5 100644 --- a/src/main/java/com/hbm/blocks/BlockDummyable.java +++ b/src/main/java/com/hbm/blocks/BlockDummyable.java @@ -443,8 +443,10 @@ public abstract class BlockDummyable extends BlockContainer implements ICustomBl y = pos[1]; z = pos[2]; - for(AxisAlignedBB aabb :this.bounding) { - AxisAlignedBB boxlet = getAABBRotationOffset(aabb, x + 0.5, y, z + 0.5, ForgeDirection.getOrientation(world.getBlockMetadata(x, y, z) - offset).getRotation(ForgeDirection.UP)); + ForgeDirection rot = ForgeDirection.getOrientation(world.getBlockMetadata(x, y, z) - offset).getRotation(ForgeDirection.UP); + + for(AxisAlignedBB aabb : this.bounding) { + AxisAlignedBB boxlet = getAABBRotationOffset(aabb, x + 0.5, y, z + 0.5, rot); if(entityBounding.intersectsWith(boxlet)) { list.add(boxlet); @@ -469,6 +471,7 @@ public abstract class BlockDummyable extends BlockContainer implements ICustomBl return AxisAlignedBB.getBoundingBox(aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ).offset(x + 0.5, y + 0.5, z + 0.5); } + // Don't mutate the xyz parameters, or the interaction max distance will bite you @Override public MovingObjectPosition collisionRayTrace(World world, int x, int y, int z, Vec3 startVec, Vec3 endVec) { if(!this.useDetailedHitbox()) { @@ -480,12 +483,10 @@ public abstract class BlockDummyable extends BlockContainer implements ICustomBl if(pos == null) return super.collisionRayTrace(world, x, y, z, startVec, endVec); - x = pos[0]; - y = pos[1]; - z = pos[2]; + ForgeDirection rot = ForgeDirection.getOrientation(world.getBlockMetadata(pos[0], pos[1], pos[2]) - offset).getRotation(ForgeDirection.UP); - for(AxisAlignedBB aabb :this.bounding) { - AxisAlignedBB boxlet = getAABBRotationOffset(aabb, x + 0.5, y, z + 0.5, ForgeDirection.getOrientation(world.getBlockMetadata(x, y, z) - offset).getRotation(ForgeDirection.UP)); + for(AxisAlignedBB aabb : this.bounding) { + AxisAlignedBB boxlet = getAABBRotationOffset(aabb, pos[0] + 0.5, pos[1], pos[2] + 0.5, rot); MovingObjectPosition intercept = boxlet.calculateIntercept(startVec, endVec); if(intercept != null) { @@ -529,10 +530,10 @@ public abstract class BlockDummyable extends BlockContainer implements ICustomBl double dZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * (double)interp; float exp = 0.002F; - int meta = world.getBlockMetadata(x, y, z); + ForgeDirection rot = ForgeDirection.getOrientation(world.getBlockMetadata(x, y, z) - offset).getRotation(ForgeDirection.UP); ICustomBlockHighlight.setup(); - for(AxisAlignedBB aabb : this.bounding) RenderGlobal.drawOutlinedBoundingBox(getAABBRotationOffset(aabb.expand(exp, exp, exp), 0, 0, 0, ForgeDirection.getOrientation(meta - offset).getRotation(ForgeDirection.UP)).getOffsetBoundingBox(x - dX + 0.5, y - dY, z - dZ + 0.5), -1); + for(AxisAlignedBB aabb : this.bounding) RenderGlobal.drawOutlinedBoundingBox(getAABBRotationOffset(aabb.expand(exp, exp, exp), 0, 0, 0, rot).getOffsetBoundingBox(x - dX + 0.5, y - dY, z - dZ + 0.5), -1); ICustomBlockHighlight.cleanup(); } diff --git a/src/main/java/com/hbm/config/BombConfig.java b/src/main/java/com/hbm/config/BombConfig.java index 64ee42417..7db3c427c 100644 --- a/src/main/java/com/hbm/config/BombConfig.java +++ b/src/main/java/com/hbm/config/BombConfig.java @@ -26,8 +26,7 @@ public class BombConfig { public static int fDelay = 4; public static int limitExplosionLifespan = 0; public static boolean chunkloading = true; - public static boolean parallelization = true; - public static boolean accumulatedDestruction = true; + public static int explosionAlgorithm = 2; public static void loadFromConfig(Configuration config) { @@ -95,8 +94,7 @@ public class BombConfig { falloutDelayProp.comment = "How many ticks to wait for the next fallout chunk computation"; fDelay = falloutDelayProp.getInt(); - chunkloading = CommonConfig.createConfigBool(config, CATEGORY_NUKE, "6.XX_enableChunkLoading", "Allows all types of procedural explosions to keep the central chunk loaded.", true); - parallelization = CommonConfig.createConfigBool(config, CATEGORY_NUKE, "6.XX_enableParallelization", "Allows explosions to use multiple threads.", true); - accumulatedDestruction = CommonConfig.createConfigBool(config, CATEGORY_NUKE, "6.XX_enableAccumulatedDestruction", "Enables the accumulated destruction model for explosions. Blocks accumulate damage and are only destroyed once their resistance is exceeded.\nMore physically accurate, slightly slower. Requires enableParallelization = true.", true); + chunkloading = CommonConfig.createConfigBool(config, CATEGORY_NUKE, "6.05_enableChunkLoading", "Allows all types of procedural explosions to keep the central chunk loaded.", true); + explosionAlgorithm = CommonConfig.createConfigInt(config, CATEGORY_NUKE, "6.06_explosionAlgorithm", "Configures the algorithm of mk5 explosion. \n0 = Legacy, 1 = Threaded DDA, 2 = Threaded DDA with damage accumulation.", 2); } } diff --git a/src/main/java/com/hbm/entity/logic/EntityNukeExplosionMK5.java b/src/main/java/com/hbm/entity/logic/EntityNukeExplosionMK5.java index 95965c95c..dac30b1db 100644 --- a/src/main/java/com/hbm/entity/logic/EntityNukeExplosionMK5.java +++ b/src/main/java/com/hbm/entity/logic/EntityNukeExplosionMK5.java @@ -70,7 +70,7 @@ public class EntityNukeExplosionMK5 extends EntityExplosionChunkloading { if(explosion == null) { explosionStart = System.currentTimeMillis(); - if (BombConfig.parallelization) { + if (BombConfig.explosionAlgorithm == 1 || BombConfig.explosionAlgorithm == 2) { explosion = new ExplosionNukeRayParallelized(worldObj, posX, posY, posZ, strength, speed, length); } else { @@ -82,20 +82,17 @@ public class EntityNukeExplosionMK5 extends EntityExplosionChunkloading { if(!explosion.isComplete()) { explosion.cacheChunksTick(BombConfig.mk5); explosion.destructionTick(BombConfig.mk5); - } else if(fallout) { - if(GeneralConfig.enableExtendedLogging && explosionStart != 0) - MainRegistry.logger.log(Level.INFO, "[NUKE] Explosion complete. Time elapsed: " + (System.currentTimeMillis() - explosionStart) + "ms"); - EntityFalloutRain fallout = new EntityFalloutRain(this.worldObj); - fallout.posX = this.posX; - fallout.posY = this.posY; - fallout.posZ = this.posZ; - fallout.setScale((int)(this.length * 2.5 + falloutAdd) * BombConfig.falloutRange / 100); - - this.worldObj.spawnEntityInWorld(fallout); - - this.clearChunkLoader(); - this.setDead(); } else { + if(GeneralConfig.enableExtendedLogging && explosionStart != 0) + MainRegistry.logger.log(Level.INFO, "[NUKE] Explosion complete. Time elapsed: {}ms", (System.currentTimeMillis() - explosionStart)); + if(fallout) { + EntityFalloutRain fallout = new EntityFalloutRain(this.worldObj); + fallout.posX = this.posX; + fallout.posY = this.posY; + fallout.posZ = this.posZ; + fallout.setScale((int)(this.length * 2.5 + falloutAdd) * BombConfig.falloutRange / 100); + this.worldObj.spawnEntityInWorld(fallout); + } this.clearChunkLoader(); this.setDead(); } @@ -153,7 +150,7 @@ public class EntityNukeExplosionMK5 extends EntityExplosionChunkloading { public static EntityNukeExplosionMK5 statFac(World world, int r, double x, double y, double z) { if(GeneralConfig.enableExtendedLogging && !world.isRemote) - MainRegistry.logger.log(Level.INFO, "[NUKE] Initialized explosion at " + x + " / " + y + " / " + z + " with strength " + r + "!"); + MainRegistry.logger.log(Level.INFO, "[NUKE] Initialized explosion at {} / {} / {} with strength {}!", x, y, z, r); if(r == 0) r = 25; diff --git a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java index 6cab5a01c..08c322adf 100644 --- a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java +++ b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java @@ -3,6 +3,9 @@ package com.hbm.explosion; import com.hbm.config.BombConfig; import com.hbm.interfaces.IExplosionRay; import com.hbm.main.MainRegistry; +import com.hbm.util.ChunkKey; +import com.hbm.util.ConcurrentBitSet; +import com.hbm.util.SubChunkSnapshot; import net.minecraft.block.Block; import net.minecraft.init.Blocks; import net.minecraft.util.Vec3; @@ -15,14 +18,14 @@ import org.apache.logging.log4j.Level; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.DoubleAdder; +import static com.hbm.util.SubChunkSnapshot.getSnapshot; + public class ExplosionNukeRayParallelized implements IExplosionRay { private static final int WORLD_HEIGHT = 256; private static final int BITSET_SIZE = 16 * WORLD_HEIGHT * 16; - private static final int WORDS_PER_SET = BITSET_SIZE >>> 6; // (16*256*16)/64 protected final World world; private final double explosionX, explosionY, explosionZ; @@ -85,7 +88,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { Thread.currentThread().interrupt(); } finally { collectFinished = true; - if (BombConfig.accumulatedDestruction) { + if (BombConfig.explosionAlgorithm == 2) { pool.submit(this::runConsolidation); } else { consolidationFinished = true; @@ -112,7 +115,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { ChunkKey ck = cacheQueue.poll(); if (ck == null) break; snapshots.computeIfAbsent(ck, key -> { - SubChunkSnapshot snap = createSubChunk(key.pos, key.subY); + SubChunkSnapshot snap = getSnapshot(this.world, key.pos, key.subY); return snap == null ? SubChunkSnapshot.EMPTY : snap; }); } @@ -146,37 +149,33 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { ExtendedBlockStorage storage = storages[subY]; if (storage == null) continue; - int yPrimeMin = 255 - ((subY << 4) + 15); - int startBit = yPrimeMin << 8; - int yPrimeMax = 255 - (subY << 4); - int endBit = (yPrimeMax << 8) | 0xFF; + int startBit = (WORLD_HEIGHT - 1 - ((subY << 4) + 15)) << 8; + int endBit = ((WORLD_HEIGHT - 1 - (subY << 4)) << 8) | 0xFF; int bit = bs.nextSetBit(startBit); - if (bit < 0 || bit > endBit) continue; while (bit >= 0 && bit <= endBit && System.nanoTime() < deadline) { - int yGlobal = 255 - (bit >>> 8); + int yGlobal = WORLD_HEIGHT - 1 - (bit >>> 8); int xGlobal = (cp.chunkXPos << 4) | ((bit >>> 4) & 0xF); int zGlobal = (cp.chunkZPos << 4) | (bit & 0xF); - - if (world.getTileEntity(xGlobal, yGlobal, zGlobal) != null) { - chunk.removeTileEntity(xGlobal & 0xF, yGlobal, zGlobal & 0xF); // world Y - world.removeTileEntity(xGlobal, yGlobal, zGlobal); - } - int xLocal = xGlobal & 0xF; int yLocal = yGlobal & 0xF; int zLocal = zGlobal & 0xF; - storage.func_150818_a(xLocal, yLocal, zLocal, Blocks.air); - storage.setExtBlockMetadata(xLocal, yLocal, zLocal, 0); - chunkModified = true; + if (storage.getBlockByExtId(xLocal, yLocal, zLocal) != Blocks.air) { + if (world.getTileEntity(xGlobal, yGlobal, zGlobal) != null) { + world.removeTileEntity(xGlobal, yGlobal, zGlobal); + } - world.notifyBlocksOfNeighborChange(xGlobal, yGlobal, zGlobal, Blocks.air); - world.markBlockForUpdate(xGlobal, yGlobal, zGlobal); + storage.func_150818_a(xLocal, yLocal, zLocal, Blocks.air); + storage.setExtBlockMetadata(xLocal, yLocal, zLocal, 0); + chunkModified = true; - world.updateLightByType(EnumSkyBlock.Sky, xGlobal, yGlobal, zGlobal); - world.updateLightByType(EnumSkyBlock.Block, xGlobal, yGlobal, zGlobal); + world.notifyBlocksOfNeighborChange(xGlobal, yGlobal, zGlobal, Blocks.air); + world.markBlockForUpdate(xGlobal, yGlobal, zGlobal); + world.updateLightByType(EnumSkyBlock.Sky, xGlobal, yGlobal, zGlobal); + world.updateLightByType(EnumSkyBlock.Block, xGlobal, yGlobal, zGlobal); + } bs.clear(bit); bit = bs.nextSetBit(bit + 1); } @@ -186,7 +185,6 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { chunk.setChunkModified(); world.markBlockRangeForRenderUpdate(cp.chunkXPos << 4, 0, cp.chunkZPos << 4, (cp.chunkXPos << 4) | 15, WORLD_HEIGHT - 1, (cp.chunkZPos << 4) | 15); } - if (bs.isEmpty()) { destructionMap.remove(cp); for (int sy = 0; sy < (WORLD_HEIGHT >> 4); sy++) { @@ -244,49 +242,6 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (this.orderedChunks != null) this.orderedChunks.clear(); } - private SubChunkSnapshot createSubChunk(ChunkCoordIntPair cpos, int subY) { - if (!world.getChunkProvider().chunkExists(cpos.chunkXPos, cpos.chunkZPos)) { - return SubChunkSnapshot.EMPTY; - } - Chunk chunk = world.getChunkFromChunkCoords(cpos.chunkXPos, cpos.chunkZPos); - ExtendedBlockStorage ebs = chunk.getBlockStorageArray()[subY]; - if (ebs == null || ebs.isEmpty()) { - return SubChunkSnapshot.EMPTY; - } - - short[] data = new short[16 * 16 * 16]; - List palette = new ArrayList<>(); - palette.add(Blocks.air); - Map idxMap = new HashMap<>(); - idxMap.put(Blocks.air, (short) 0); - boolean allAir = true; - - for (int ly = 0; ly < 16; ly++) { - for (int lz = 0; lz < 16; lz++) { - for (int lx = 0; lx < 16; lx++) { - Block block = ebs.getBlockByExtId(lx, ly, lz); - int idx; - if (block == Blocks.air) { - idx = 0; - } else { - allAir = false; - Short e = idxMap.get(block); - if (e == null) { - idxMap.put(block, (short) palette.size()); - palette.add(block); - idx = palette.size() - 1; - } else { - idx = e; - } - } - data[(ly << 8) | (lz << 4) | lx] = (short) idx; - } - } - } - if (allAir) return SubChunkSnapshot.EMPTY; - return new SubChunkSnapshot(palette.toArray(new Block[0]), data); - } - private List generateSphereRays(int count) { List list = new ArrayList<>(count); if (count <= 0) return list; @@ -316,7 +271,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { continue; } - ConcurrentBitSet chunkDestructionBitSet = destructionMap.computeIfAbsent(cp, k -> new ConcurrentBitSet()); + ConcurrentBitSet chunkDestructionBitSet = destructionMap.computeIfAbsent(cp, k -> new ConcurrentBitSet(BITSET_SIZE)); Iterator> damageEntryIterator = accumulator.entrySet().iterator(); while (damageEntryIterator.hasNext()) { @@ -330,7 +285,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { continue; } - int yGlobal = 255 - (bitIndex >>> 8); + int yGlobal = WORLD_HEIGHT - 1 - (bitIndex >>> 8); int subY = yGlobal >> 4; if (subY < 0) { @@ -370,91 +325,6 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { consolidationFinished = true; } - private static class ChunkKey { - final ChunkCoordIntPair pos; - final int subY; - - ChunkKey(int cx, int cz, int sy) { - this.pos = new ChunkCoordIntPair(cx, cz); - this.subY = sy; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ChunkKey)) return false; - ChunkKey k = (ChunkKey) o; - return subY == k.subY && pos.equals(k.pos); - } - - @Override - public int hashCode() { - return Objects.hash(pos.chunkXPos, pos.chunkZPos, subY); - } - } - - private static class SubChunkSnapshot { - private static final SubChunkSnapshot EMPTY = new SubChunkSnapshot(new Block[]{Blocks.air}, null); - private final Block[] palette; - private final short[] data; - - SubChunkSnapshot(Block[] p, short[] d) { - this.palette = p; - this.data = d; - } - - Block getBlock(int x, int y, int z) { - if (this == EMPTY || data == null) return Blocks.air; - short idx = data[(y << 8) | (z << 4) | x]; - return (idx >= 0 && idx < palette.length) ? palette[idx] : Blocks.air; - } - } - - private static final class ConcurrentBitSet { - private final AtomicLongArray words = new AtomicLongArray(WORDS_PER_SET); - - void set(int bit) { - if (bit < 0 || bit >= BITSET_SIZE) return; - int wd = bit >>> 6; - long m = 1L << (bit & 63); - while (true) { - long o = words.get(wd); - long u = o | m; - if (o == u || words.compareAndSet(wd, o, u)) return; - } - } - - void clear(int bit) { - if (bit < 0 || bit >= BITSET_SIZE) return; - int wd = bit >>> 6; - long m = ~(1L << (bit & 63)); - while (true) { - long oldWord = words.get(wd); - long newWord = oldWord & m; - if (oldWord == newWord || words.compareAndSet(wd, oldWord, newWord)) { - return; - } - } - } - - int nextSetBit(int from) { - if (from < 0) from = 0; - int wd = from >>> 6; - if (wd >= WORDS_PER_SET) return -1; - long w = words.get(wd) & (~0L << (from & 63)); - while (true) { - if (w != 0) return (wd << 6) + Long.numberOfTrailingZeros(w); - if (++wd == WORDS_PER_SET) return -1; - w = words.get(wd); - } - } - - boolean isEmpty() { - for (int i = 0; i < WORDS_PER_SET; i++) if (words.get(i) != 0) return false; - return true; - } - } - private static class ChunkDamageAccumulator { // key = bitIndex, value = total accumulated damage private final ConcurrentHashMap damageMap = new ConcurrentHashMap<>(); @@ -602,7 +472,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { energy -= damageDealt; if (damageDealt > 0) { int bitIndex = ((WORLD_HEIGHT - 1 - y) << 8) | ((x & 0xF) << 4) | (z & 0xF); - if (BombConfig.accumulatedDestruction) { + if (BombConfig.explosionAlgorithm == 2) { ChunkCoordIntPair chunkPos = ck.pos; ChunkDamageAccumulator chunkAccumulator = accumulatedDamageMap.computeIfAbsent(chunkPos, k -> new ChunkDamageAccumulator()); @@ -611,7 +481,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (energy > 0) { ConcurrentBitSet bs = destructionMap.computeIfAbsent( ck.pos, - posKey -> new ConcurrentBitSet() + posKey -> new ConcurrentBitSet(BITSET_SIZE) ); bs.set(bitIndex); } diff --git a/src/main/java/com/hbm/interfaces/IExplosionRay.java b/src/main/java/com/hbm/interfaces/IExplosionRay.java index d164b6b27..5c883c82e 100644 --- a/src/main/java/com/hbm/interfaces/IExplosionRay.java +++ b/src/main/java/com/hbm/interfaces/IExplosionRay.java @@ -1,11 +1,31 @@ package com.hbm.interfaces; +/** + * Interface for procedural explosions. + * @author mlbv + */ public interface IExplosionRay { - boolean isComplete(); + /** + * Called every tick. Caches the chunks affected by the explosion. + * All heavy calculations are recommended to be done off the main thread. + * @param processTimeMs maximum time to process in this tick + */ + void cacheChunksTick(int processTimeMs); - void cacheChunksTick(int processTime); - - void destructionTick(int processTime); + /** + * Called every tick to apply block destruction to the affected chunks. + * @param processTimeMs maximum time to process in this tick + */ + void destructionTick(int processTimeMs); + /** + * Immediately cancels the explosion. + */ void cancel(); + + + /** + * @return true if the explosion is finished or cancelled. + */ + boolean isComplete(); } diff --git a/src/main/java/com/hbm/inventory/gui/GUIMachineChemicalPlant.java b/src/main/java/com/hbm/inventory/gui/GUIMachineChemicalPlant.java index 5bc4c5a93..d2812ab1f 100644 --- a/src/main/java/com/hbm/inventory/gui/GUIMachineChemicalPlant.java +++ b/src/main/java/com/hbm/inventory/gui/GUIMachineChemicalPlant.java @@ -41,7 +41,7 @@ public class GUIMachineChemicalPlant extends GuiInfoContainer { protected void mouseClicked(int x, int y, int button) { super.mouseClicked(x, y, button); - if(this.checkClick(x, y, 7, 125, 18, 18)) GUIScreenRecipeSelector.openSelector(ChemicalPlantRecipes.INSTANCE, chemplant, "", 0, this); + if(this.checkClick(x, y, 7, 125, 18, 18)) GUIScreenRecipeSelector.openSelector(ChemicalPlantRecipes.INSTANCE, chemplant, chemplant.chemplantModule.recipe, 0, this); } @Override diff --git a/src/main/java/com/hbm/inventory/gui/GUIScreenRecipeSelector.java b/src/main/java/com/hbm/inventory/gui/GUIScreenRecipeSelector.java index ecb69ba16..9fedd4ef0 100644 --- a/src/main/java/com/hbm/inventory/gui/GUIScreenRecipeSelector.java +++ b/src/main/java/com/hbm/inventory/gui/GUIScreenRecipeSelector.java @@ -1,25 +1,45 @@ package com.hbm.inventory.gui; +import java.util.ArrayList; +import java.util.List; + +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; import com.hbm.interfaces.IControlReceiver; +import com.hbm.inventory.recipes.loader.GenericRecipe; import com.hbm.inventory.recipes.loader.GenericRecipes; import com.hbm.lib.RefStrings; import cpw.mods.fml.common.FMLCommonHandler; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.renderer.RenderHelper; import net.minecraft.util.ResourceLocation; public class GUIScreenRecipeSelector extends GuiScreen { protected static final ResourceLocation texture = new ResourceLocation(RefStrings.MODID + ":textures/gui/processing/gui_recipe_selector.png"); + //basic GUI setup protected int xSize = 176; protected int ySize = 132; protected int guiLeft; protected int guiTop; - + // search crap + protected GenericRecipes recipeSet; + protected List recipes = new ArrayList(); + protected GuiTextField search; + protected int pageIndex; + protected int size; + protected String selection; + // callback + protected int index; + protected IControlReceiver tile; protected GuiScreen previousScreen; public static void openSelector(GenericRecipes recipeSet, IControlReceiver tile, String selection, int index, GuiScreen previousScreen) { @@ -27,7 +47,13 @@ public class GUIScreenRecipeSelector extends GuiScreen { } public GUIScreenRecipeSelector(GenericRecipes recipeSet, IControlReceiver tile, String selection, int index, GuiScreen previousScreen) { + this.recipeSet = recipeSet; + this.tile = tile; + this.selection = selection; + this.index = index; this.previousScreen = previousScreen; + + regenerateRecipes(); } @Override @@ -35,6 +61,41 @@ public class GUIScreenRecipeSelector extends GuiScreen { super.initGui(); this.guiLeft = (this.width - this.xSize) / 2; this.guiTop = (this.height - this.ySize) / 2; + + Keyboard.enableRepeatEvents(true); + this.search = new GuiTextField(this.fontRendererObj, guiLeft + 28, guiTop + 111, 102, 12); + this.search.setTextColor(-1); + this.search.setDisabledTextColour(-1); + this.search.setEnableBackgroundDrawing(false); + this.search.setMaxStringLength(32); + } + + private void regenerateRecipes() { + + this.recipes.clear(); + this.recipes.addAll(recipeSet.recipeOrderedList); + + resetPaging(); + } + + private void search(String search) { + this.recipes.clear(); + + if(search.isEmpty()) { + this.recipes.addAll(recipeSet.recipeOrderedList); + } else { + for(Object o : recipeSet.recipeOrderedList) { + GenericRecipe recipe = (GenericRecipe) o; + if(recipe.matchesSearch(search)) this.recipes.add(recipe); + } + } + + resetPaging(); + } + + private void resetPaging() { + this.pageIndex = 0; + this.size = Math.max(0, (int)Math.ceil((this.recipes.size() - 40) / 8D)); } @Override @@ -44,6 +105,16 @@ public class GUIScreenRecipeSelector extends GuiScreen { GL11.glDisable(GL11.GL_LIGHTING); this.drawGuiContainerForegroundLayer(mouseX, mouseY); GL11.glEnable(GL11.GL_LIGHTING); + this.handleScroll(); + } + + protected void handleScroll() { + + if(!Mouse.isButtonDown(0) && !Mouse.isButtonDown(1) && Mouse.next()) { + int scroll = Mouse.getEventDWheel(); + if(scroll > 0 && this.pageIndex > 0) this.pageIndex--; + if(scroll < 0 && this.pageIndex < this.size) this.pageIndex++; + } } private void drawGuiContainerForegroundLayer(int x, int y) { @@ -54,14 +125,53 @@ public class GUIScreenRecipeSelector extends GuiScreen { GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); Minecraft.getMinecraft().getTextureManager().bindTexture(texture); drawTexturedModalRect(guiLeft, guiTop, 0, 0, xSize, ySize); + + for(int i = pageIndex * 8; i < pageIndex * 8 + 40; i++) { + if(i >= recipes.size()) break; + + int ind = i - pageIndex * 8; + + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + RenderHelper.enableGUIStandardItemLighting(); + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + + GenericRecipe recipe = recipes.get(i); + + FontRenderer font = recipe.getIcon().getItem().getFontRenderer(recipe.getIcon()); + if(font == null) font = fontRendererObj; + + itemRender.zLevel = 100.0F; + itemRender.renderItemAndEffectIntoGUI(font, this.mc.getTextureManager(), recipe.getIcon(), guiLeft + 8 + 18 * (ind % 8), guiTop + 18 + 18 * (ind / 8)); + + itemRender.zLevel = 0.0F; + + GL11.glEnable(GL11.GL_ALPHA_TEST); + GL11.glDisable(GL11.GL_LIGHTING); + this.mc.getTextureManager().bindTexture(texture); + + if(recipe.name.equals(this.selection)) + this.drawTexturedModalRect(guiLeft + 7 + 18 * (ind % 8), guiTop + 17 + 18 * (ind / 8), 192, 0, 18, 18); + } } @Override protected void keyTyped(char typedChar, int keyCode) { + + if(this.search.textboxKeyTyped(typedChar, keyCode)) { + search(this.search.getText()); + return; + } + if(keyCode == 1 || keyCode == this.mc.gameSettings.keyBindInventory.getKeyCode()) { FMLCommonHandler.instance().showGuiScreen(previousScreen); } } + @Override + public void onGuiClosed() { + Keyboard.enableRepeatEvents(false); + } + @Override public boolean doesGuiPauseGame() { return false; } } diff --git a/src/main/java/com/hbm/inventory/recipes/ChemicalPlantRecipes.java b/src/main/java/com/hbm/inventory/recipes/ChemicalPlantRecipes.java index 6aaf0e3cd..95f029458 100644 --- a/src/main/java/com/hbm/inventory/recipes/ChemicalPlantRecipes.java +++ b/src/main/java/com/hbm/inventory/recipes/ChemicalPlantRecipes.java @@ -3,6 +3,9 @@ package com.hbm.inventory.recipes; import com.hbm.inventory.FluidStack; import com.hbm.inventory.RecipesCommon.ComparableStack; import com.hbm.inventory.fluid.Fluids; +import com.hbm.inventory.material.MaterialShapes; +import com.hbm.inventory.material.Mats; +import com.hbm.inventory.material.NTMMaterial; import com.hbm.inventory.recipes.loader.GenericRecipe; import com.hbm.inventory.recipes.loader.GenericRecipes; import com.hbm.items.ModItems; @@ -40,5 +43,12 @@ public class ChemicalPlantRecipes extends GenericRecipes { pool.add(new ChanceOutput(new ItemStack(ModItems.billet_cobalt), 5)); }}) .setOutputFluids(new FluidStack(Fluids.BIOGAS, 2000))); + + for(NTMMaterial mat : Mats.orderedList) { + if(mat.autogen.contains(MaterialShapes.CASTPLATE)) this.register(new GenericRecipe(mat.getUnlocalizedName() + ".plate").setup(60, 100).setOutputItems(new ChanceOutput(new ItemStack(ModItems.plate_cast, 1, mat.id)))); + if(mat.autogen.contains(MaterialShapes.WELDEDPLATE)) this.register(new GenericRecipe(mat.getUnlocalizedName() + ".weld").setup(60, 100).setOutputItems(new ChanceOutput(new ItemStack(ModItems.plate_welded, 1, mat.id)))); + if(mat.autogen.contains(MaterialShapes.DENSEWIRE)) this.register(new GenericRecipe(mat.getUnlocalizedName() + ".wire").setup(60, 100).setOutputItems(new ChanceOutput(new ItemStack(ModItems.wire_dense, 1, mat.id)))); + if(mat.autogen.contains(MaterialShapes.MECHANISM)) this.register(new GenericRecipe(mat.getUnlocalizedName() + ".mechanism").setup(60, 100).setOutputItems(new ChanceOutput(new ItemStack(ModItems.part_mechanism, 1, mat.id)))); + } } } diff --git a/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipe.java b/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipe.java index 324f1ce4b..9199580d1 100644 --- a/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipe.java +++ b/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipe.java @@ -1,5 +1,7 @@ package com.hbm.inventory.recipes.loader; +import java.util.Locale; + import com.hbm.inventory.FluidStack; import com.hbm.inventory.RecipesCommon.AStack; import com.hbm.inventory.recipes.loader.GenericRecipes.ChanceOutput; @@ -21,7 +23,7 @@ public class GenericRecipe { public FluidStack[] outputFluid; public int duration; public long power; - public ItemStack icon; + protected ItemStack icon; public boolean writeIcon = false; public boolean customLocalization = false; @@ -32,6 +34,7 @@ public class GenericRecipe { public GenericRecipe setDuration(int duration) { this.duration = duration; return this; } public GenericRecipe setPower(long power) { this.power = power; return this; } public GenericRecipe setup(int duration, long power) { return this.setDuration(duration).setPower(power); } + public GenericRecipe setupNamed(int duration, long power) { return this.setDuration(duration).setPower(power).setNamed(); } public GenericRecipe setIcon(ItemStack icon) { this.icon = icon; this.writeIcon = true; return this; } public GenericRecipe setIcon(Item item, int meta) { return this.setIcon(new ItemStack(item, 1, meta)); } public GenericRecipe setIcon(Item item) { return this.setIcon(new ItemStack(item)); } @@ -60,10 +63,11 @@ public class GenericRecipe { } public String getName() { - if(customLocalization) { - return I18nUtil.resolveKey(name); - } - + if(customLocalization) return I18nUtil.resolveKey(name); return this.getIcon().getDisplayName(); } + + public boolean matchesSearch(String substring) { + return getName().toLowerCase(Locale.US).contains(substring.toLowerCase(Locale.US)); + } } diff --git a/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipes.java b/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipes.java index 2c9ce4596..5c33d09d4 100644 --- a/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipes.java +++ b/src/main/java/com/hbm/inventory/recipes/loader/GenericRecipes.java @@ -72,6 +72,7 @@ public abstract class GenericRecipes extends Serializab if(this.hasPower()) recipe.setPower(obj.get("power").getAsLong()); if(obj.has("icon")) recipe.setIcon(this.readItemStack(obj.get("icon").getAsJsonArray())); + if(obj.has("named") && obj.get("named").getAsBoolean()) recipe.setNamed(); readExtraData(element, recipe); @@ -117,6 +118,8 @@ public abstract class GenericRecipes extends Serializab this.writeItemStack(recipe.icon, writer); } + if(recipe.customLocalization) writer.name("named").value(true); + writeExtraData(recipe, writer); } diff --git a/src/main/java/com/hbm/util/ChunkKey.java b/src/main/java/com/hbm/util/ChunkKey.java new file mode 100644 index 000000000..62c2f7c9a --- /dev/null +++ b/src/main/java/com/hbm/util/ChunkKey.java @@ -0,0 +1,32 @@ +package com.hbm.util; + +import net.minecraft.world.ChunkCoordIntPair; + +import java.util.Objects; + +/** + * Unique identifier for sub-chunks. + * @Author mlbv + */ +public class ChunkKey { + public final ChunkCoordIntPair pos; + public final int subY; + + public ChunkKey(int cx, int cz, int sy) { + this.pos = new ChunkCoordIntPair(cx, cz); + this.subY = sy; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ChunkKey)) return false; + ChunkKey k = (ChunkKey) o; + return subY == k.subY && pos.equals(k.pos); + } + + @Override + public int hashCode() { + return Objects.hash(pos.chunkXPos, pos.chunkZPos, subY); + } +} diff --git a/src/main/java/com/hbm/util/ConcurrentBitSet.java b/src/main/java/com/hbm/util/ConcurrentBitSet.java new file mode 100644 index 000000000..531c851ae --- /dev/null +++ b/src/main/java/com/hbm/util/ConcurrentBitSet.java @@ -0,0 +1,70 @@ +package com.hbm.util; + +import java.util.concurrent.atomic.AtomicLongArray; +import java.util.concurrent.atomic.LongAdder; + +public class ConcurrentBitSet { + private final AtomicLongArray words; + private final int size; + private final LongAdder bitCount = new LongAdder(); + + public ConcurrentBitSet(int size) { + this.size = size; + int wordCount = (size + 63) >>> 6; + this.words = new AtomicLongArray(wordCount); + } + + public void set(int bit) { + if (bit < 0 || bit >= size) return; + int wordIndex = bit >>> 6; + long mask = 1L << (bit & 63); + while (true) { + long oldWord = words.get(wordIndex); + long newWord = oldWord | mask; + if (oldWord == newWord) return; + if (words.compareAndSet(wordIndex, oldWord, newWord)) { + bitCount.increment(); + return; + } + } + } + + public void clear(int bit) { + if (bit < 0 || bit >= size) return; + int wordIndex = bit >>> 6; + long mask = ~(1L << (bit & 63)); + while (true) { + long oldWord = words.get(wordIndex); + long newWord = oldWord & mask; + if (oldWord == newWord) return; + if (words.compareAndSet(wordIndex, oldWord, newWord)) { + bitCount.decrement(); + return; + } + } + } + + public int nextSetBit(int from) { + if (from < 0) from = 0; + int wordIndex = from >>> 6; + if (wordIndex >= words.length()) return -1; + long word = words.get(wordIndex) & (~0L << (from & 63)); + while (true) { + if (word != 0) { + int idx = (wordIndex << 6) + Long.numberOfTrailingZeros(word); + return (idx < size) ? idx : -1; + } + wordIndex++; + if (wordIndex >= words.length()) return -1; + word = words.get(wordIndex); + } + } + + public boolean isEmpty() { + return bitCount.sum() == 0; + } + + public long cardinality() { + return bitCount.sum(); + } +} diff --git a/src/main/java/com/hbm/util/SubChunkSnapshot.java b/src/main/java/com/hbm/util/SubChunkSnapshot.java new file mode 100644 index 000000000..6e7b99cc0 --- /dev/null +++ b/src/main/java/com/hbm/util/SubChunkSnapshot.java @@ -0,0 +1,120 @@ +package com.hbm.util; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A snapshot of a 16×16×16 sub-chunk. + * @Author mlbv + */ +public class SubChunkSnapshot { + /** + * A sub-chunk that contains only air. + */ + public static final SubChunkSnapshot EMPTY = new SubChunkSnapshot(new Block[]{Blocks.air}, null); + private final Block[] palette; + private final short[] data; + + private SubChunkSnapshot(Block[] p, short[] d) { + this.palette = p; + this.data = d; + } + + /** + * Creates a SubChunkSnapshot from a loaded chunk. + * + * @param world + * The World instance from which to retrieve the chunk. + * @param cpos + * The ChunkCoordIntPair identifying the chunk coordinates (x, z). + * @param subY + * The vertical sub-chunk index (0–15) within the chunk. + * @return + * A SubChunkSnapshot containing the palette and block data for the sub-chunk, + * or SubChunkSnapshot.EMPTY if the region is unloaded or contains only air. + */ + public static SubChunkSnapshot getSnapshot(World world, ChunkCoordIntPair cpos, int subY) { + if (!world.getChunkProvider().chunkExists(cpos.chunkXPos, cpos.chunkZPos)) { + return SubChunkSnapshot.EMPTY; + } + return getOrLoadSnapshot(world, cpos, subY); + } + + /** + * Creates a SubChunkSnapshot. + * + * @param world + * The World instance from which to retrieve the chunk. + * @param cpos + * The ChunkCoordIntPair identifying the chunk coordinates (x, z). + * @param subY + * The vertical sub-chunk index (0–15) within the chunk. + * @return + * A SubChunkSnapshot containing the palette and block data for the sub-chunk, + * or SubChunkSnapshot.EMPTY if the region contains only air. + */ + public static SubChunkSnapshot getOrLoadSnapshot(World world, ChunkCoordIntPair cpos, int subY){ + Chunk chunk = world.getChunkFromChunkCoords(cpos.chunkXPos, cpos.chunkZPos); + ExtendedBlockStorage ebs = chunk.getBlockStorageArray()[subY]; + if (ebs == null || ebs.isEmpty()) return SubChunkSnapshot.EMPTY; + + short[] data = new short[16 * 16 * 16]; + List palette = new ArrayList<>(); + palette.add(Blocks.air); + Map idxMap = new HashMap<>(); + idxMap.put(Blocks.air, (short) 0); + boolean allAir = true; + + for (int ly = 0; ly < 16; ly++) { + for (int lz = 0; lz < 16; lz++) { + for (int lx = 0; lx < 16; lx++) { + Block block = ebs.getBlockByExtId(lx, ly, lz); + int idx; + if (block == Blocks.air) { + idx = 0; + } else { + allAir = false; + Short e = idxMap.get(block); + if (e == null) { + idxMap.put(block, (short) palette.size()); + palette.add(block); + idx = palette.size() - 1; + } else { + idx = e; + } + } + data[(ly << 8) | (lz << 4) | lx] = (short) idx; + } + } + } + if (allAir) return SubChunkSnapshot.EMPTY; + return new SubChunkSnapshot(palette.toArray(new Block[0]), data); + } + + /** + * Retrieves the Block at the specified local coordinates within this sub-chunk snapshot. + * + * @param x + * The local x-coordinate within the sub-chunk (0–15). + * @param y + * The local y-coordinate within the sub-chunk (0–15). + * @param z + * The local z-coordinate within the sub-chunk (0–15). + * @return + * The Block instance at the given position. + */ + public Block getBlock(int x, int y, int z) { + if (this == EMPTY || data == null) return Blocks.air; + short idx = data[(y << 8) | (z << 4) | x]; + return (idx >= 0 && idx < palette.length) ? palette[idx] : Blocks.air; + } +}