Compare commits

..
1 Commits
Author SHA1 Message Date
Armisael5 0b5f4aa433 Add debug hologram projector (#79) 2026-10-21 09:26:09 +08:00
9 changed files with 128 additions and 269 deletions

No files matched your search

@@ -6,6 +6,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -14,7 +15,6 @@ import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.particle.EffectRenderer;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.culling.Frustrum;
@@ -62,19 +62,7 @@ public class ClientProxy extends CommonProxy {
private static final short[] RGBA_NO_TINT = { 255, 255, 255, 255 };
private static final short[] RGBA_RED_TINT = { 255, 128, 128, 0 };
/**
* Vanilla {@link EffectRenderer} keeps at most 4000 particles per layer and evicts the surplus with an
* {@code ArrayList.remove(0)}, so spawning more than that in one projection costs a lot of copying for particles
* that are thrown away before they are ever drawn.
*/
private static final int MAX_SPAWNED_PARTICLES_PER_GROUP = 4000;
private static final Map<HintParticleInfo, HintGroup> allHints = new HashMap<>();
/**
* Total number of hints held across {@link #allGroups} and {@link #currentHints}, i.e. what
* {@link ConfigurationHandler#getMaxRenderCount()} is compared against. Not the same as {@code allHints.size()},
* which is keyed by position and therefore collapses hints that several holograms put at the same spot.
*/
private static int storedHintCount;
/**
* All batches of hints.
*/
@@ -84,36 +72,12 @@ public class ClientProxy extends CommonProxy {
* Current batch of hints. Belongs to the same logical multiblock.
*/
private static HintGroup currentHints;
/**
* Player position and squared radius captured when {@link #currentHints} was opened, so the per-hint radius test is
* plain arithmetic instead of a player lookup and a config lookup.
*/
private static double hintOriginX, hintOriginY, hintOriginZ;
private static double hintRadiusSq;
/**
* Decorative particles spawned for {@link #currentHints} so far, capped at
* {@link #MAX_SPAWNED_PARTICLES_PER_GROUP}.
*/
private static int spawnedParticles;
/**
* Collection of all hints. Sorted from farthest (index 0) to nearest (index size - 1)
* <p>
* 10k should be more than enough, if not a bit wasteful
*/
private static final List<HintParticleInfo> allHintsForRender = new ArrayList<>(10000);
/**
* Hints with {@link HintParticleInfo#renderThrough} set, collected during the main render pass and drawn afterwards
* with the depth test off. Keeping them out of the main pass means the depth test is toggled once per frame rather
* than once per renderThrough boundary in the distance ordering.
*/
private static final List<HintParticleInfo> deferredRenderThrough = new ArrayList<>();
/**
* Sorts farthest first, so transparent hints blend back to front and the nearest
* {@link ConfigurationHandler#getMaxRenderCount()} hints are the tail of the list. Reads the distance cached by
* {@link HintParticleInfo#cacheDistance} rather than recomputing it for every comparison.
*/
private static final Comparator<HintParticleInfo> FARTHEST_FIRST = (a, b) -> Double
.compare(b.distSqToPlayer, a.distSqToPlayer);
/**
* If the diff to current player position is too great sort the allHints. Initial value is very far off below y=0
* but not huge enough to make a NaN.
@@ -125,6 +89,10 @@ public class ClientProxy extends CommonProxy {
* if true rebuild the allHints list from all hintOwners (and sort it)
*/
private static boolean allHintsDirty = false;
/**
* counter keeping track of how many renderThrough hint particle we have. enable optimization if no renderThrough
*/
private static int renderThrough;
/**
* A throttle map for local player. Integrated server will use the throttle map in super class instead.
*/
@@ -133,25 +101,20 @@ public class ClientProxy extends CommonProxy {
@Override
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
ensureHinting();
if (!isWithinHintRadius(x, y, z)) return;
if (storedHintCount >= ConfigurationHandler.INSTANCE.getMaxRenderCount()) return;
HintParticleInfo info = new HintParticleInfo(w, x, y, z, icons, RGBa);
HintGroup existing = allHints.get(info);
// this very projection already covered this position
if (existing == currentHints) return;
// check and remove colliding holograms
if (existing != null && ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
allGroups.remove(existing);
removeGroup(existing);
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
HintGroup dupe = allHints.get(info);
if (dupe != null && dupe != currentHints) {
allGroups.remove(dupe);
removeGroup(dupe);
}
}
allHints.put(info, currentHints);
currentHints.getHints().add(info);
storedHintCount++;
allHintsDirty = true;
if (spawnedParticles >= MAX_SPAWNED_PARTICLES_PER_GROUP) return;
spawnedParticles++;
EntityFX particle = new WeightlessParticleFX(
w,
x + RANDOM.nextFloat() * 0.5F,
@@ -227,13 +190,10 @@ public class ClientProxy extends CommonProxy {
}
static void removeGroup(HintGroup group) {
List<HintParticleInfo> hints = group.getHints();
for (int i = 0, size = hints.size(); i < size; i++) {
HintParticleInfo hint = hints.get(i);
// a later hologram may have claimed this position, in which case the mapping is not ours to drop
if (allHints.get(hint) == group) allHints.remove(hint);
for (HintParticleInfo hintParticleInfo : group.getHints()) {
allHints.remove(hintParticleInfo);
if (hintParticleInfo.renderThrough) renderThrough--;
}
storedHintCount -= hints.size();
allHintsDirty = true;
}
@@ -267,49 +227,25 @@ public class ClientProxy extends CommonProxy {
public void startHinting(World w) {
if (!w.isRemote) return;
if (currentHints != null) endHinting(w);
beginGroup();
currentHints = new HintGroup();
}
private void ensureHinting() {
if (currentHints == null) beginGroup();
}
private static void beginGroup() {
int maxCoexisting = ConfigurationHandler.INSTANCE.getMaxCoexistingHologram();
// >= because we are about to add one more
while (!allGroups.isEmpty() && allGroups.size() >= maxCoexisting) {
removeGroup(allGroups.remove(0));
}
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (player == null) {
hintRadiusSq = Double.POSITIVE_INFINITY;
} else {
hintOriginX = player.posX;
hintOriginY = player.posY;
hintOriginZ = player.posZ;
double radius = ConfigurationHandler.INSTANCE.getMaxRenderRadius();
hintRadiusSq = radius * radius;
}
spawnedParticles = 0;
currentHints = new HintGroup();
if (currentHints == null) currentHints = new HintGroup();
}
@Override
public void endHinting(World w) {
if (!w.isRemote || currentHints == null) return;
while (!allGroups.isEmpty() && allGroups.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
allGroups.remove(0);
}
if (!currentHints.getHints().isEmpty()) allGroups.add(currentHints);
// we use the player existence time here as some worlds don't really advance the time ticker
currentHints.setCreationTime(getCurrentPlayer().ticksExisted);
currentHints = null;
}
private static boolean isWithinHintRadius(int x, int y, int z) {
double dx = x + 0.5 - hintOriginX;
double dy = y + 0.5 - hintOriginY;
double dz = z + 0.5 - hintOriginZ;
return dx * dx + dy * dy + dz * dz <= hintRadiusSq;
}
@Override
public long getOverworldTime() {
// there is no overworld, better just hope current world time is ok...
@@ -375,7 +311,7 @@ public class ClientProxy extends CommonProxy {
private static class HintGroup {
private final List<HintParticleInfo> hints = new ArrayList<>();
private final List<HintParticleInfo> hints = new LinkedList<>();
private int creationTime = -1;
public List<HintParticleInfo> getHints() {
@@ -399,7 +335,6 @@ public class ClientProxy extends CommonProxy {
private final IIcon[] icons;
private short[] tint;
private boolean renderThrough;
private double distSqToPlayer;
private final long creationTime = System.currentTimeMillis(); // use tick time instead maybe
@@ -425,7 +360,10 @@ public class ClientProxy extends CommonProxy {
}
public void setRenderThrough() {
// no need to invalidate allHintsForRender: the render pass partitions on this flag per frame
if (!this.renderThrough) {
ClientProxy.renderThrough += 1;
allHintsDirty = true;
}
this.renderThrough = true;
}
@@ -441,14 +379,18 @@ public class ClientProxy extends CommonProxy {
return frustrum.isBoxInFrustum(x + 0.25, y + 0.25, z + 0.25, x + 0.75, y + 0.75, z + 0.75);
}
public void draw(Tessellator tes, int transparency, double eyeX, double eyeY, double eyeZ, int eyeXint,
int eyeYint, int eyeZint) {
public void draw(Tessellator tes, double eyeX, double eyeY, double eyeZ, int eyeXint, int eyeYint,
int eyeZint) {
double size = 0.5;
int brightness = w.blockExists(x, 0, z) ? w.getLightBrightnessForSkyBlocks(x, y, z, 0) : 0;
tes.setBrightness(brightness);
tes.setColorRGBA((int) (tint[0] * .9F), (int) (tint[1] * .95F), (int) (tint[2] * 1F), transparency);
tes.setColorRGBA(
(int) (tint[0] * .9F),
(int) (tint[1] * .95F),
(int) (tint[2] * 1F),
ConfigurationHandler.INSTANCE.getHintTransparency());
double X = (x - eyeXint) + 0.25;
double Y = (y - eyeYint) + 0.25;
@@ -544,18 +486,8 @@ public class ClientProxy extends CommonProxy {
return creationTime;
}
private void cacheDistance(double playerX, double playerY, double playerZ) {
double dx = x + 0.5 - playerX;
double dy = y + 0.5 - playerY;
double dz = z + 0.5 - playerZ;
distSqToPlayer = dx * dx + dy * dy + dz * dz;
}
private boolean isWithinRadius(double eyeX, double eyeY, double eyeZ, double radiusSq) {
double dx = x + 0.5 - eyeX;
double dy = y + 0.5 - eyeY;
double dz = z + 0.5 - eyeZ;
return dx * dx + dy * dy + dz * dz <= radiusSq;
public double getSquareDistanceTo(Vec3 point) {
return point.squareDistanceTo(x + 0.5, y + 0.5, z + 0.5);
}
}
@@ -564,6 +496,12 @@ public class ClientProxy extends CommonProxy {
static boolean connected = false;
static boolean configSent = false;
private void resetPlayerLocation() {
lastPlayerPos.xCoord = Minecraft.getMinecraft().thePlayer.posX;
lastPlayerPos.yCoord = Minecraft.getMinecraft().thePlayer.posY;
lastPlayerPos.zCoord = Minecraft.getMinecraft().thePlayer.posZ;
}
@SubscribeEvent
public void onPlayerLogIn(FMLNetworkEvent.ClientConnectedToServerEvent e) {
connected = true;
@@ -585,52 +523,42 @@ public class ClientProxy extends CommonProxy {
@SubscribeEvent
public void onClientTick(ClientTickEvent e) {
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
if (e.phase != Phase.END || player == null) return;
boolean sortRequired = false;
// The allGroups is implicitly sorted by creationTime
// here we exploit this ordering to reduce iteration size
int deadline = player.ticksExisted - ConfigurationHandler.INSTANCE.getHintLifespan();
int i;
for (i = 0; i < allGroups.size(); i++) {
if (allGroups.get(i).getCreationTime() > deadline) {
break;
if (e.phase == Phase.END && Minecraft.getMinecraft().thePlayer != null) {
Vec3 playerPos = Minecraft.getMinecraft().thePlayer.getPosition(1);
boolean sortRequired = false;
// The allGroups is implicitly sorted by creationTime
// here we exploit this ordering to reduce iteration size
int deadline = Minecraft.getMinecraft().thePlayer.ticksExisted
- ConfigurationHandler.INSTANCE.getHintLifespan();
int i;
for (i = 0; i < allGroups.size(); i++) {
if (allGroups.get(i).getCreationTime() > deadline) {
break;
}
}
if (i != 0) {
sortRequired = true;
// clear this block and go no further
List<HintGroup> toRemove = allGroups.subList(0, i);
toRemove.forEach(ClientProxy::removeGroup);
toRemove.clear();
}
if (allHintsDirty) {
allHintsForRender.clear();
// we need greatly efficient sequential access and mildly efficient sorting
// rebuild is sort of rare, and it's expected to lag anyway
// so no min-heap here
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
sortRequired = true;
}
if (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2) {
// only redo sort if player moved some distance
// default is 0.1 block
// if there was a full rebuild, go sort it as well
allHintsForRender.sort(Comparator.comparingDouble(info -> -info.getSquareDistanceTo(playerPos)));
resetPlayerLocation();
}
}
if (i != 0) {
sortRequired = true;
// clear this block and go no further
List<HintGroup> toRemove = allGroups.subList(0, i);
toRemove.forEach(ClientProxy::removeGroup);
toRemove.clear();
}
if (allHintsDirty) {
allHintsForRender.clear();
// we need greatly efficient sequential access and mildly efficient sorting
// rebuild is sort of rare, and it's expected to lag anyway
// so no min-heap here
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
allHintsDirty = false;
sortRequired = true;
}
final int size = allHintsForRender.size();
if (size == 0) return;
double px = player.posX, py = player.posY, pz = player.posZ;
if (sortRequired || lastPlayerPos.squareDistanceTo(px, py, pz) > resortThreshold(size)) {
// only redo sort if player moved some distance
// if there was a full rebuild, go sort it as well
for (int j = 0; j < size; j++) allHintsForRender.get(j).cacheDistance(px, py, pz);
allHintsForRender.sort(FARTHEST_FIRST);
lastPlayerPos.xCoord = px;
lastPlayerPos.yCoord = py;
lastPlayerPos.zCoord = pz;
}
}
private static double resortThreshold(int size) {
if (size > 100_000) return 64; // 8 blocks
if (size > 10_000) return 4; // 2 blocks
return 1e-2; // 0.1 blocks
}
}
@@ -677,29 +605,17 @@ public class ClientProxy extends CommonProxy {
if (e.world.isRemote) {
// flush hints. we are in a different world now.
allHintsForRender.clear();
deferredRenderThrough.clear();
allHints.clear();
allGroups.clear();
currentHints = null;
storedHintCount = 0;
allHintsDirty = false;
lastPlayerPos.yCoord = -1e30;
renderThrough = 0;
// clear throttles. hopefully a world switch is enough long as a cool down.
localThrottleMap.clear();
}
}
private static int flushIfFull(Tessellator tes, int cubeDrawn) {
if (cubeDrawn + 1 < 4096 / (6 * 2 * 3)) return cubeDrawn;
tes.draw();
tes.startDrawing(GL11.GL_TRIANGLES);
return 0;
}
@SubscribeEvent
public void onRenderWorldLast(RenderWorldLastEvent e) {
final int size = allHintsForRender.size();
if (size == 0) return;
if (allHintsForRender.isEmpty()) return;
// seriously, I'm not a OpenGL expert, so I'm probably doing a lot of very stupid stuff here.
// Please consider contributing patches if you find something to optimize.
@@ -719,15 +635,6 @@ public class ClientProxy extends CommonProxy {
int i0 = (int) d0, i1 = (int) d1, i2 = (int) d2;
frustrum.setPosition(d0, d1, d2);
// hoist every config lookup out of the hot loop
final int transparency = ConfigurationHandler.INSTANCE.getHintTransparency();
final double radius = ConfigurationHandler.INSTANCE.getMaxRenderRadius();
final double radiusSq = radius * radius;
// allHintsForRender is sorted farthest first, so the nearest hints the budget should keep are the tail of
// the list. Skipping a prefix is O(1) and, unlike counting as we draw, does not make hints pop in and out
// as the player turns around.
final int from = Math.max(0, size - ConfigurationHandler.INSTANCE.getMaxRenderCount());
GL11.glPushMatrix();
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT); // TODO figure out original states
// we need the back facing rendered because the thing is transparent
@@ -736,41 +643,39 @@ public class ClientProxy extends CommonProxy {
GL11.glEnable(GL11.GL_BLEND); // enable blend so it is transparent
GL11.glBlendFunc(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA);
// depth test begin as enabled
boolean renderThrough = false;
Minecraft.getMinecraft().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
GL11.glTranslated(-d0 + i0, -d1 + i1, -d2 + i2);
Tessellator tes = Tessellator.instance;
tes.startDrawing(GL11.GL_TRIANGLES);
int cubeDrawn = 0;
for (int i = from; i < size; i++) {
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(), cubeDrawn = 0; i
< allHintsForRenderSize; i++, cubeDrawn++) {
HintParticleInfo hint = allHintsForRender.get(i);
if (!hint.isWithinRadius(d0, d1, d2, radiusSq)) continue;
if (!hint.isInFrustrum(frustrum)) continue;
if (hint.renderThrough) {
// drawn in a second pass, so the depth test is toggled once rather than at every boundary
deferredRenderThrough.add(hint);
continue;
if (renderThrough != hint.renderThrough) {
if (i > 0) {
p.endStartSection("Draw");
tes.draw();
tes.startDrawing(GL11.GL_TRIANGLES);
cubeDrawn = 0;
p.endStartSection("Prepare");
}
if (hint.renderThrough) GL11.glDisable(GL11.GL_DEPTH_TEST);
else GL11.glEnable(GL11.GL_DEPTH_TEST);
renderThrough = hint.renderThrough;
}
cubeDrawn = flushIfFull(tes, cubeDrawn);
hint.draw(tes, transparency, d0, d1, d2, i0, i1, i2);
cubeDrawn++;
// Minecraft Tessellator draw in batch of 4096. This plays nicely with quads, but for trigs
// it might occasionally end up with one half in first batch and the other half in later batch.
// so we manually flush the buffer here if that would happen.
// 6 faces per particle, 2 trig per face, 3 vertex per trig
if ((cubeDrawn + 1) >= 4096 / (6 * 2 * 3)) {
tes.draw();
tes.startDrawing(GL11.GL_TRIANGLES);
}
hint.draw(tes, d0, d1, d2, i0, i1, i2);
}
p.endStartSection("Draw");
tes.draw();
if (!deferredRenderThrough.isEmpty()) {
p.endStartSection("Prepare");
GL11.glDisable(GL11.GL_DEPTH_TEST);
tes.startDrawing(GL11.GL_TRIANGLES);
cubeDrawn = 0;
for (int i = 0, deferred = deferredRenderThrough.size(); i < deferred; i++) {
cubeDrawn = flushIfFull(tes, cubeDrawn);
deferredRenderThrough.get(i).draw(tes, transparency, d0, d1, d2, i0, i1, i2);
cubeDrawn++;
}
p.endStartSection("Draw");
tes.draw();
deferredRenderThrough.clear();
}
p.endSection();
GL11.glPopAttrib();
@@ -28,10 +28,7 @@ public enum ConfigurationHandler {
private Configuration config;
private int maxCoexistingHologram;
private int maxRenderRadius;
private int maxRenderCount;
private boolean removeCollidingHologram;
private boolean skipCorrectlyPlacedHints;
private int hintLifespan;
private int hintTransparency;
private int autoPlaceBudget;
@@ -67,31 +64,6 @@ public enum ConfigurationHandler {
1,
100,
"An attempt will be made to prune old holograms when a new hologram is about to be projected");
maxRenderRadius = config.getInt(
"maxRenderRadius",
"client.hologram",
512,
1,
Integer.MAX_VALUE,
"Maximum distance from the client player at which projected blocks are kept and rendered.");
maxRenderCount = config.getInt(
"maxRenderCount",
"client.hologram",
1_000_000,
1,
Integer.MAX_VALUE,
"Maximum number of projected blocks kept for rendering across all holograms.\n"
+ "Blocks that are already placed correctly are not projected and do not count towards this.\n"
+ "When a hologram exceeds this, the blocks nearest to the player are the ones kept.");
skipCorrectlyPlacedHints = config.getBoolean(
"skipCorrectlyPlaced",
"client.hologram",
true,
"Do not project a block that is already placed correctly, so the render budget is spent on the blocks\n"
+ "that are actually still missing.\n"
+ "This needs each structure element to be checked while the hologram is built. Elements are\n"
+ "not strictly required to have a side effect free check, so turn this off if a multiblock\n"
+ "misbehaves while being projected.");
removeCollidingHologram = config.getBoolean(
"removeColliding",
"client.hologram",
@@ -184,22 +156,10 @@ public enum ConfigurationHandler {
return maxCoexistingHologram;
}
public int getMaxRenderRadius() {
return maxRenderRadius;
}
public int getMaxRenderCount() {
return maxRenderCount;
}
public boolean isRemoveCollidingHologram() {
return removeCollidingHologram;
}
public boolean isSkipCorrectlyPlacedHints() {
return skipCorrectlyPlacedHints;
}
public int getHintLifespan() {
return hintLifespan;
}
@@ -22,6 +22,7 @@ import com.gtnewhorizon.structurelib.command.CommandConfigureChannels;
import com.gtnewhorizon.structurelib.command.CommandRegistryDebug;
import com.gtnewhorizon.structurelib.item.ItemBlockHint;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.item.ItemDebugConstructableTrigger;
import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
@@ -99,6 +100,7 @@ public class StructureLib {
static Item itemBlockHint;
static Item itemFrontRotationTool;
static Item itemConstructableTrigger;
static Item itemDebugConstructableTrigger;
public static final CreativeTabs creativeTab = new CreativeTabs("structurelib") {
@Override
@@ -119,6 +121,9 @@ public class StructureLib {
GameRegistry.registerItem(
itemConstructableTrigger = new ItemConstructableTrigger(),
itemConstructableTrigger.getUnlocalizedName());
GameRegistry.registerItem(
itemDebugConstructableTrigger = new ItemDebugConstructableTrigger(),
itemDebugConstructableTrigger.getUnlocalizedName());
proxy.preInit(e);
NetworkRegistry.INSTANCE.registerGuiHandler(instance(), new GuiHandler());
@@ -16,6 +16,7 @@ import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.IAlignment;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.item.ItemDebugConstructableTrigger;
import com.gtnewhorizon.structurelib.structure.IItemSource;
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
@@ -73,7 +74,7 @@ public class ConstructableUtility {
if (aPlayer instanceof EntityPlayerMP playerMP) {
// server side and sneaking (already checked above)
// do construct
if (aPlayer.capabilities.isCreativeMode) {
if (aPlayer.capabilities.isCreativeMode || aStack.getItem() instanceof ItemDebugConstructableTrigger) {
constructable.construct(aStack, false);
} else if (constructable instanceof ISurvivalConstructable) {
int built = ((ISurvivalConstructable) constructable).survivalConstruct(
@@ -0,0 +1,14 @@
package com.gtnewhorizon.structurelib.item;
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
/**
* Hologram projector that always performs a creative build, whatever the player's game mode.
*/
public class ItemDebugConstructableTrigger extends ItemConstructableTrigger {
public ItemDebugConstructableTrigger() {
setUnlocalizedName("structurelib.debugConstructableTrigger");
setTextureName(MOD_ID + ":itemDebugConstructableTrigger");
}
}
@@ -12,7 +12,6 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.ConfigurationHandler;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
@@ -444,8 +443,6 @@ public interface IStructureDefinition<T> {
return success;
} else {
if (hintsOnly) {
// elements are not strictly required to have a side effect free check, hence the opt out
final boolean skipCorrectlyPlaced = ConfigurationHandler.INSTANCE.isSkipCorrectlyPlacedHints();
StructureUtility.iterateV2(
elements,
world,
@@ -456,27 +453,14 @@ public interface IStructureDefinition<T> {
basePositionA,
basePositionB,
basePositionC,
new IStructureWalker<T>() {
@Override
public boolean visit(IStructureElement<T> e, World w, int x, int y, int z, int a, int b,
int c) {
if (skipCorrectlyPlaced && e.check(object, w, x, y, z)) return true;
e.spawnHint(object, w, x, y, z, trigger);
if (ChannelDataAccessor.hasSubChannel(trigger, StructureLibAPI.CHANNEL_SHOW_ERROR)
&& !e.couldBeValid(object, w, x, y, z, trigger)) {
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), w, x, y, z);
}
return true;
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.spawnHint(object, world, x, y, z, trigger);
if (ChannelDataAccessor.hasSubChannel(trigger, StructureLibAPI.CHANNEL_SHOW_ERROR)
&& !e.couldBeValid(object, world, x, y, z, trigger)) {
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), world, x, y, z);
}
@Override
public boolean blockNotLoaded(IStructureElement<T> e, World w, int x, int y, int z, int a,
int b, int c) {
e.spawnHint(object, w, x, y, z, trigger);
return true;
}
},
return true;
}),
"spawnHint");
} else {
StructureUtility.iterateV2(
@@ -41,6 +41,8 @@ item.structurelib.constructableTrigger.gui.wipe.confirm=§4Sure?
item.structurelib.constructableTrigger.gui.channels.from=§rMod: §9§o%s
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in their structure and a sub channel allows indication of which variant to use.\n All sub channels have a sequence of lowercase characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\n You can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of "§oIndicates§r §ovalue§r §oX§r §ofor§r §osub§r §ochannel§r §oY§r".
item.structurelib.debugConstructableTrigger.name=Debug Multiblock Structure Hologram Projector
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
structurelib.tooltip.channelvalue=§7Value §b%d§r§7 for subchannel §b%s
@@ -187,12 +189,6 @@ structurelib.config.client.hologram.maxCoexisting=Max Coexisting Holograms
structurelib.config.client.hologram.maxCoexisting.tooltip=Number of holograms that can coexist. Usually all hints generated by one right click belongs to one hologram. An attempt will be made to prune old holograms when a new hologram is about to be projected and this limit would be exceeded.
structurelib.config.client.hologram.removeColliding=Remove Colliding Holograms
structurelib.config.client.hologram.removeColliding.tooltip=Whether an attempt will be made to remove any existing hologram that collides with a new hologram.
structurelib.config.client.hologram.maxRenderRadius=Maximum Hologram Render Radius
structurelib.config.client.hologram.maxRenderRadius.tooltip=Maximum distance from the player at which projected blocks are kept and rendered.
structurelib.config.client.hologram.maxRenderCount=Maximum Hologram Render Count
structurelib.config.client.hologram.maxRenderCount.tooltip=Maximum number of projected blocks kept for rendering across all holograms. Blocks that are already placed correctly are not projected and do not count towards this limit. When a hologram exceeds this limit, the blocks nearest to the player are the ones kept.
structurelib.config.client.hologram.skipCorrectlyPlaced=Skip Correctly Placed Blocks
structurelib.config.client.hologram.skipCorrectlyPlaced.tooltip=Do not project a block that is already placed correctly, so the render budget is spent on the blocks that are actually still missing. This requires each structure element to be checked while the hologram is built. Elements are not strictly required to have a side effect free check, so turn this off if a multiblock misbehaves while being projected.
structurelib.config.registries=Sorted Registries
structurelib.config.registries.tooltip=Sorted Registry is how StructureLib internally keep track of certain features. Each individual elements may be reordered, disabled and enabled at user's command. Changes will be synced to server if needed.\nThis gui can also be accessed in game by shift right clicking air with Multiblock Structure Hologram Projector.
@@ -181,12 +181,6 @@ structurelib.config.client.hologram.maxCoexisting=最多可以同时存在的全
structurelib.config.client.hologram.maxCoexisting.tooltip=这决定最多可以同时存在多少台机器的全息投影。一般一次使用全息投影仪右键生成的所有机器结构细节被视为一个全息投影。当同时存在多个全息投影,并超过此数量时,再次使用全息投影仪会移除旧有的全息投影。
structurelib.config.client.hologram.removeColliding=移除冲突的全息投影
structurelib.config.client.hologram.removeColliding.tooltip=当新的全息投影与已存在的全息投影冲突时,开启此选项时会移除已存在的全息投影。
structurelib.config.client.hologram.maxRenderRadius=全息投影最大渲染半径
structurelib.config.client.hologram.maxRenderRadius.tooltip=只保留并渲染玩家指定距离内的投影方块。
structurelib.config.client.hologram.maxRenderCount=全息投影最大渲染数量
structurelib.config.client.hologram.maxRenderCount.tooltip=所有全息投影合计保留并渲染的投影方块数量上限。已正确放置的方块不会被投影,也不计入此上限。当全息投影超过此上限时,优先保留离玩家最近的方块。
structurelib.config.client.hologram.skipCorrectlyPlaced=跳过已正确放置的方块
structurelib.config.client.hologram.skipCorrectlyPlaced.tooltip=不再投影已经正确放置的方块,从而把渲染额度全部用在真正缺失的方块上。这需要在生成全息投影时检查每一个结构元素。结构元素并不强制要求其检查无副作用,因此若某个多方块在投影时出现异常,可关闭此选项。
structurelib.config.registries=注册表
structurelib.config.registries.tooltip=StructureLib 使用注册表来管理其部分功能。每个注册表中的每一个元素都可以被单独排序、禁用。需要时,注册表的内容将被同步至服务端。
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB