mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-09 04:55:01 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f9fe63729 | ||
|
|
f31de65a1d |
No files matched your search
@@ -9,22 +9,16 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
|
||||
import cpw.mods.fml.common.Loader;
|
||||
|
||||
// this is not an API! go check StructureLibAPI instead!
|
||||
public class ChannelDescription {
|
||||
|
||||
// pseudo channel key for editing master channel override from GUI and command. '#' is never allowed in a real
|
||||
// channel name, so this can never collide with a subchannel
|
||||
public static final String MASTER_KEY = "#master";
|
||||
|
||||
private static final ItemStackMap<Set<ChannelDescription>> itemToChannels = new ItemStackMap<>(true);
|
||||
private static final Map<String, ChannelDescription> registry = new HashMap<>();
|
||||
private final String channel;
|
||||
@@ -70,34 +64,6 @@ public class ChannelDescription {
|
||||
itemToChannels.computeIfAbsent(stack, s -> new HashSet<>()).add(inst);
|
||||
}
|
||||
|
||||
public static boolean isSet(ItemStack trigger, String key) {
|
||||
return MASTER_KEY.equals(key) ? ChannelDataAccessor.hasMasterData(trigger)
|
||||
: ChannelDataAccessor.hasSubChannel(trigger, key);
|
||||
}
|
||||
|
||||
public static void setData(ItemStack trigger, String key, int value) {
|
||||
if (MASTER_KEY.equals(key)) ChannelDataAccessor.setMasterData(trigger, value);
|
||||
else ChannelDataAccessor.setChannelData(trigger, key, value);
|
||||
}
|
||||
|
||||
public static void unsetData(ItemStack trigger, String key) {
|
||||
if (MASTER_KEY.equals(key)) ChannelDataAccessor.unsetMasterData(trigger);
|
||||
else ChannelDataAccessor.unsetChannelData(trigger, key);
|
||||
}
|
||||
|
||||
public static Stream<Map.Entry<String, Integer>> iterateData(ItemStack trigger) {
|
||||
Stream<Map.Entry<String, Integer>> channels = ChannelDataAccessor.iterateChannelData(trigger).sorted();
|
||||
if (!ChannelDataAccessor.hasMasterData(trigger)) return channels;
|
||||
return Stream.concat(
|
||||
Stream.of(
|
||||
new AbstractMap.SimpleImmutableEntry<>(MASTER_KEY, ChannelDataAccessor.getMasterData(trigger))),
|
||||
channels);
|
||||
}
|
||||
|
||||
public static int countData(ItemStack trigger) {
|
||||
return ChannelDataAccessor.countChannelData(trigger) + (ChannelDataAccessor.hasMasterData(trigger) ? 1 : 0);
|
||||
}
|
||||
|
||||
public static Map<String, ChannelDescription> getAll() {
|
||||
return Collections.unmodifiableMap(registry);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public final class ChannelPickHandler {
|
||||
|
||||
// Assuming there's only 1 channel per item for now (same behavior as drag and drop)
|
||||
Map.Entry<String, Integer> first = channels.iterator().next();
|
||||
ChannelDescription.setData(holoStack, first.getKey(), first.getValue());
|
||||
ChannelDataAccessor.setChannelData(holoStack, first.getKey(), first.getValue());
|
||||
sendChannelData(player, holoStack, onCursor);
|
||||
player.addChatMessage(
|
||||
new ChatComponentTranslation("structurelib.pickchannel.success", first.getKey(), first.getValue()));
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -15,6 +14,7 @@ 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,7 +62,19 @@ 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.
|
||||
*/
|
||||
@@ -72,12 +84,36 @@ 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.
|
||||
@@ -89,10 +125,6 @@ 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.
|
||||
*/
|
||||
@@ -101,20 +133,25 @@ 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 (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
|
||||
HintGroup dupe = allHints.get(info);
|
||||
if (dupe != null && dupe != currentHints) {
|
||||
allGroups.remove(dupe);
|
||||
removeGroup(dupe);
|
||||
}
|
||||
if (existing != null && ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
|
||||
allGroups.remove(existing);
|
||||
removeGroup(existing);
|
||||
}
|
||||
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,
|
||||
@@ -190,10 +227,13 @@ public class ClientProxy extends CommonProxy {
|
||||
}
|
||||
|
||||
static void removeGroup(HintGroup group) {
|
||||
for (HintParticleInfo hintParticleInfo : group.getHints()) {
|
||||
allHints.remove(hintParticleInfo);
|
||||
if (hintParticleInfo.renderThrough) renderThrough--;
|
||||
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);
|
||||
}
|
||||
storedHintCount -= hints.size();
|
||||
allHintsDirty = true;
|
||||
}
|
||||
|
||||
@@ -227,25 +267,49 @@ public class ClientProxy extends CommonProxy {
|
||||
public void startHinting(World w) {
|
||||
if (!w.isRemote) return;
|
||||
if (currentHints != null) endHinting(w);
|
||||
currentHints = new HintGroup();
|
||||
beginGroup();
|
||||
}
|
||||
|
||||
private void ensureHinting() {
|
||||
if (currentHints == null) currentHints = new HintGroup();
|
||||
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();
|
||||
}
|
||||
|
||||
@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...
|
||||
@@ -311,7 +375,7 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
private static class HintGroup {
|
||||
|
||||
private final List<HintParticleInfo> hints = new LinkedList<>();
|
||||
private final List<HintParticleInfo> hints = new ArrayList<>();
|
||||
private int creationTime = -1;
|
||||
|
||||
public List<HintParticleInfo> getHints() {
|
||||
@@ -335,6 +399,7 @@ 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
|
||||
|
||||
@@ -360,10 +425,7 @@ public class ClientProxy extends CommonProxy {
|
||||
}
|
||||
|
||||
public void setRenderThrough() {
|
||||
if (!this.renderThrough) {
|
||||
ClientProxy.renderThrough += 1;
|
||||
allHintsDirty = true;
|
||||
}
|
||||
// no need to invalidate allHintsForRender: the render pass partitions on this flag per frame
|
||||
this.renderThrough = true;
|
||||
}
|
||||
|
||||
@@ -379,18 +441,14 @@ 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, double eyeX, double eyeY, double eyeZ, int eyeXint, int eyeYint,
|
||||
int eyeZint) {
|
||||
public void draw(Tessellator tes, int transparency, 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),
|
||||
ConfigurationHandler.INSTANCE.getHintTransparency());
|
||||
tes.setColorRGBA((int) (tint[0] * .9F), (int) (tint[1] * .95F), (int) (tint[2] * 1F), transparency);
|
||||
|
||||
double X = (x - eyeXint) + 0.25;
|
||||
double Y = (y - eyeYint) + 0.25;
|
||||
@@ -486,8 +544,18 @@ public class ClientProxy extends CommonProxy {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public double getSquareDistanceTo(Vec3 point) {
|
||||
return point.squareDistanceTo(x + 0.5, y + 0.5, z + 0.5);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,12 +564,6 @@ 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;
|
||||
@@ -523,42 +585,52 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onClientTick(ClientTickEvent e) {
|
||||
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();
|
||||
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 (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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,17 +677,29 @@ 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) {
|
||||
if (allHintsForRender.isEmpty()) return;
|
||||
final int size = allHintsForRender.size();
|
||||
if (size == 0) 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.
|
||||
@@ -635,6 +719,15 @@ 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
|
||||
@@ -643,39 +736,41 @@ 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);
|
||||
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(), cubeDrawn = 0; i
|
||||
< allHintsForRenderSize; i++, cubeDrawn++) {
|
||||
int cubeDrawn = 0;
|
||||
for (int i = from; i < size; i++) {
|
||||
HintParticleInfo hint = allHintsForRender.get(i);
|
||||
if (!hint.isWithinRadius(d0, d1, d2, radiusSq)) continue;
|
||||
if (!hint.isInFrustrum(frustrum)) 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;
|
||||
if (hint.renderThrough) {
|
||||
// drawn in a second pass, so the depth test is toggled once rather than at every boundary
|
||||
deferredRenderThrough.add(hint);
|
||||
continue;
|
||||
}
|
||||
// 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);
|
||||
cubeDrawn = flushIfFull(tes, cubeDrawn);
|
||||
hint.draw(tes, transparency, d0, d1, d2, i0, i1, i2);
|
||||
cubeDrawn++;
|
||||
}
|
||||
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,7 +28,10 @@ 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;
|
||||
@@ -64,6 +67,31 @@ 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",
|
||||
@@ -156,10 +184,22 @@ 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;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,6 @@ public class StructureLib {
|
||||
InventoryUtility.init();
|
||||
|
||||
ChannelDescription.set(CHANNEL_SHOW_ERROR, MOD_ID, "channels.structurelib.show_errors");
|
||||
ChannelDescription.set(ChannelDescription.MASTER_KEY, MOD_ID, "channels.structurelib.master");
|
||||
|
||||
DevelopHelper.onPreInit();
|
||||
}
|
||||
|
||||
+2
-83
@@ -25,89 +25,9 @@ import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
public class ChannelDataAccessor {
|
||||
|
||||
private static final String SECONDARY_HINT_TAG = "channels";
|
||||
private static final String MASTER_OVERRIDE_TAG = "master";
|
||||
|
||||
private ChannelDataAccessor() {}
|
||||
|
||||
/**
|
||||
* Return a trigger item that
|
||||
* <ul>
|
||||
* <li>Still carry all the subchannel in some unspecified ways</li>
|
||||
* <li>Have its master channel data replaced with master channel override</li>
|
||||
* <li>No longer carry the master channel override</li>
|
||||
* </ul>
|
||||
*
|
||||
* If given trigger item has no master channel override, masterStack may or may not be returned to save performance.
|
||||
* Code within StructureLib can depend on this behavior, you cannot.
|
||||
* <p>
|
||||
* Should be called once on the raw trigger item before any {@link #withChannel(ItemStack, String)} call.
|
||||
* StructureLib already does this for hologram projector. If you call
|
||||
* {@link IConstructable#construct(ItemStack, boolean)} or similar methods with a raw trigger item yourself, call
|
||||
* this first.
|
||||
*
|
||||
* @param masterStack raw trigger item
|
||||
* @return a trigger item with new master channel
|
||||
*/
|
||||
public static ItemStack withMasterData(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
if (!hasMasterData(masterStack)) return masterStack;
|
||||
NBTTagCompound tag = (NBTTagCompound) masterStack.stackTagCompound.copy();
|
||||
ItemStack ret = new ItemStack(
|
||||
masterStack.getItem(),
|
||||
tag.getInteger(MASTER_OVERRIDE_TAG),
|
||||
Items.feather.getDamage(masterStack));
|
||||
tag.removeTag(MASTER_OVERRIDE_TAG);
|
||||
if (!tag.hasNoTags()) ret.setTagCompound(tag);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given trigger item contains master channel override
|
||||
*
|
||||
* @param masterStack trigger stack to check
|
||||
* @return true if contains master channel override
|
||||
*/
|
||||
public static boolean hasMasterData(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
return masterStack.hasTagCompound() && masterStack.stackTagCompound.hasKey(MASTER_OVERRIDE_TAG, NBT.TAG_INT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the master channel data from given trigger item. Will use stack size instead if not overridden.
|
||||
*
|
||||
* @param masterStack trigger stack to query from
|
||||
* @return master channel data
|
||||
*/
|
||||
public static int getMasterData(ItemStack masterStack) {
|
||||
if (!hasMasterData(masterStack)) return masterStack.stackSize;
|
||||
return masterStack.stackTagCompound.getInteger(MASTER_OVERRIDE_TAG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the master channel override on given trigger item
|
||||
*
|
||||
* @param masterStack trigger stack to set
|
||||
* @param data positive master channel data
|
||||
*/
|
||||
public static void setMasterData(ItemStack masterStack, int data) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
if (data < 1) throw new IllegalArgumentException("Channel data must be positive");
|
||||
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
|
||||
masterStack.stackTagCompound.setInteger(MASTER_OVERRIDE_TAG, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the master channel override from given trigger item, if it exists
|
||||
*
|
||||
* @param masterStack trigger stack to unset
|
||||
*/
|
||||
public static void unsetMasterData(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
if (masterStack.stackTagCompound == null) return;
|
||||
masterStack.stackTagCompound.removeTag(MASTER_OVERRIDE_TAG);
|
||||
if (masterStack.stackTagCompound.hasNoTags()) masterStack.stackTagCompound = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a trigger item that
|
||||
* <ul>
|
||||
@@ -222,15 +142,14 @@ public class ChannelDataAccessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe all subchannel data and master channel override on given trigger item
|
||||
*
|
||||
* Wipe all subchannel data on given trigger item
|
||||
*
|
||||
* @param masterStack trigger stack to wipe
|
||||
*/
|
||||
public static void wipeChannelData(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
if (masterStack.stackTagCompound != null) {
|
||||
masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
|
||||
masterStack.stackTagCompound.removeTag(MASTER_OVERRIDE_TAG);
|
||||
if (masterStack.stackTagCompound.hasNoTags()) {
|
||||
masterStack.stackTagCompound = null;
|
||||
}
|
||||
|
||||
+1
-2
@@ -28,9 +28,8 @@ public class ConstructableUtility {
|
||||
|
||||
public static boolean handle(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
|
||||
int aSide) {
|
||||
ItemStack effectiveStack = ChannelDataAccessor.withMasterData(aStack);
|
||||
StructureLibAPI.startHinting(aWorld);
|
||||
boolean ret = handle0(effectiveStack, aPlayer, aWorld, aX, aY, aZ, aSide);
|
||||
boolean ret = handle0(aStack, aPlayer, aWorld, aX, aY, aZ, aSide);
|
||||
StructureLibAPI.endHinting(aWorld);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ChannelDescription;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
|
||||
@@ -27,10 +26,7 @@ public class CommandConfigureChannels extends CommandBase {
|
||||
public String getCommandUsage(ICommandSender p_71518_1_) {
|
||||
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
|
||||
+ "/sl_channel <set> <channel name> <value>\n"
|
||||
+ "<channel name> can have space in it. Values below 1 clear the channel\n"
|
||||
+ "Use "
|
||||
+ ChannelDescription.MASTER_KEY
|
||||
+ " as <channel name> to override the projector stack size";
|
||||
+ "<channel name> can have space in it. Values below 1 clear the channel";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,14 +56,14 @@ public class CommandConfigureChannels extends CommandBase {
|
||||
|
||||
switch (args[0]) {
|
||||
case "getall":
|
||||
if (ChannelDescription.countData(heldItem) == 0) {
|
||||
if (!ChannelDataAccessor.hasSubChannel(heldItem)) {
|
||||
sender.addChatMessage(new ChatComponentText("No subchannel"));
|
||||
} else {
|
||||
sender.addChatMessage(
|
||||
new ChatComponentTranslation(
|
||||
"item.structurelib.constructableTrigger.desc.lshift.0",
|
||||
ChannelDataAccessor.countChannelData(heldItem)));
|
||||
ChannelDescription.iterateData(heldItem).map(e -> e.getKey() + ": " + e.getValue())
|
||||
ChannelDataAccessor.iterateChannelData(heldItem).map(e -> e.getKey() + ": " + e.getValue())
|
||||
.map(ChatComponentText::new).forEach(player::addChatMessage);
|
||||
}
|
||||
return;
|
||||
@@ -90,12 +86,9 @@ public class CommandConfigureChannels extends CommandBase {
|
||||
|
||||
switch (args[0]) {
|
||||
case "get":
|
||||
if (ChannelDescription.isSet(heldItem, channel)) sender.addChatMessage(
|
||||
if (ChannelDataAccessor.hasSubChannel(heldItem, channel)) sender.addChatMessage(
|
||||
new ChatComponentText(
|
||||
channel + " value: "
|
||||
+ (ChannelDescription.MASTER_KEY.equals(channel)
|
||||
? ChannelDataAccessor.getMasterData(heldItem)
|
||||
: ChannelDataAccessor.getChannelData(heldItem, channel))));
|
||||
channel + " value: " + ChannelDataAccessor.getChannelData(heldItem, channel)));
|
||||
else sender.addChatMessage(new ChatComponentText(channel + " value: N/A"));
|
||||
break;
|
||||
case "clear":
|
||||
@@ -116,9 +109,9 @@ public class CommandConfigureChannels extends CommandBase {
|
||||
}
|
||||
|
||||
if (value < 1) {
|
||||
ChannelDescription.unsetData(heldItem, channel);
|
||||
ChannelDataAccessor.unsetChannelData(heldItem, channel);
|
||||
} else {
|
||||
ChannelDescription.setData(heldItem, channel, value);
|
||||
ChannelDataAccessor.setChannelData(heldItem, channel, value);
|
||||
}
|
||||
player.inventoryContainer.detectAndSendChanges();
|
||||
sender.addChatMessage(
|
||||
|
||||
@@ -317,7 +317,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
// this will be called from setText of key and value. NEVER UPDATE THE VALUE OF THESE HERE OR GET A
|
||||
// STACKOVERFLOW!
|
||||
String keyText = key.getText();
|
||||
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDescription.isSet(trigger, keyText);
|
||||
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
|
||||
getButtonList().get(ADD_BTN).displayString = existing ? I18n.format(I18N_PREFIX + "set")
|
||||
: I18n.format(I18N_PREFIX + "add");
|
||||
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText())
|
||||
@@ -343,10 +343,10 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
case ADD_BTN:
|
||||
int value = getValue();
|
||||
if (value <= 0) return;
|
||||
ChannelDescription.setData(trigger, key.getText(), value);
|
||||
ChannelDataAccessor.setChannelData(trigger, key.getText(), value);
|
||||
break;
|
||||
case UNSET_BTN:
|
||||
ChannelDescription.unsetData(trigger, key.getText());
|
||||
ChannelDataAccessor.unsetChannelData(trigger, key.getText());
|
||||
break;
|
||||
case WIPE_BTN:
|
||||
if (!wipePending) {
|
||||
@@ -501,7 +501,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
case 1:
|
||||
default: // TODO implement a GUI to select which pair to use, instead of blindly using the first
|
||||
Entry<String, Integer> e = channels.iterator().next();
|
||||
ChannelDescription.setData(trigger, e.getKey(), e.getValue());
|
||||
ChannelDataAccessor.setChannelData(trigger, e.getKey(), e.getValue());
|
||||
key.setText(e.getKey());
|
||||
value.setText(e.getValue().toString());
|
||||
draggedStack.stackSize = 0;
|
||||
@@ -625,12 +625,12 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
|
||||
@Override
|
||||
public Entry<String, Integer> getElementAt(int index) {
|
||||
return ChannelDescription.iterateData(trigger).skip(index).findFirst().orElse(null);
|
||||
return ChannelDataAccessor.iterateChannelData(trigger).sorted().skip(index).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumElements() {
|
||||
return ChannelDescription.countData(trigger);
|
||||
return ChannelDataAccessor.countChannelData(trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -640,7 +640,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
&& mYRelative <= margin / 2 + 5) {
|
||||
Entry<String, Integer> e = getElementAt(elementIndex);
|
||||
if (e != null) {
|
||||
ChannelDescription.unsetData(trigger, e.getKey());
|
||||
ChannelDataAccessor.unsetChannelData(trigger, e.getKey());
|
||||
updateButtons();
|
||||
return false;
|
||||
}
|
||||
@@ -650,7 +650,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
|
||||
@Override
|
||||
protected void prepareDrawElements() {
|
||||
cache = ChannelDescription.iterateData(trigger).collect(Collectors.toList());
|
||||
cache = ChannelDataAccessor.iterateChannelData(trigger).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -671,8 +671,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
drawTexturedModalRect(x + margin + 1, y + margin / 2 + 1, 251, 251, 5, 5);
|
||||
}
|
||||
int keyColor = ChannelDescription.MASTER_KEY.equals(e.getKey()) ? 0xffaa00 : 0xffffff;
|
||||
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, keyColor);
|
||||
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, 0xffffff);
|
||||
int valueX = x + margin + keyMaxWidth + margin;
|
||||
fontRendererObj.drawString(trim(e.getValue().toString(), maxX - valueX), valueX, y + margin / 2, 0xffffff);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import net.minecraft.world.World;
|
||||
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ChannelDescription;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
|
||||
@@ -53,7 +52,8 @@ public class ItemConstructableTrigger extends Item {
|
||||
translateToLocalFormatted(
|
||||
"item.structurelib.constructableTrigger.desc.lshift.0",
|
||||
ChannelDataAccessor.countChannelData(stack)));
|
||||
ChannelDescription.iterateData(stack).map(e -> e.getKey() + ": " + e.getValue()).forEach(tooltip::add);
|
||||
ChannelDataAccessor.iterateChannelData(stack).map(e -> e.getKey() + ": " + e.getValue())
|
||||
.forEach(tooltip::add);
|
||||
} else {
|
||||
// Tooltip text:
|
||||
// Triggers Constructable Interface
|
||||
@@ -67,7 +67,7 @@ public class ItemConstructableTrigger extends Item {
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.2"));
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.3"));
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.4"));
|
||||
if (ChannelDataAccessor.hasSubChannel(stack) || ChannelDataAccessor.hasMasterData(stack))
|
||||
if (ChannelDataAccessor.hasSubChannel(stack))
|
||||
tooltip.add(translateToLocal("item.structurelib.constructableTrigger.desc.5"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ public class SetChannelDataMessage implements IMessage {
|
||||
|
||||
private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
|
||||
private boolean onCursor;
|
||||
// 0 means no master override
|
||||
private int master;
|
||||
|
||||
public SetChannelDataMessage() {}
|
||||
|
||||
@@ -36,13 +34,11 @@ public class SetChannelDataMessage implements IMessage {
|
||||
public SetChannelDataMessage(ItemStack trigger, final boolean onCursor) {
|
||||
this.onCursor = onCursor;
|
||||
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
|
||||
master = ChannelDataAccessor.hasMasterData(trigger) ? ChannelDataAccessor.getMasterData(trigger) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(ByteBuf buf) {
|
||||
onCursor = buf.readBoolean();
|
||||
master = ByteBufUtils.readVarInt(buf, 5);
|
||||
data.clear();
|
||||
int size = ByteBufUtils.readVarShort(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
@@ -53,7 +49,6 @@ public class SetChannelDataMessage implements IMessage {
|
||||
@Override
|
||||
public void toBytes(ByteBuf buf) {
|
||||
buf.writeBoolean(onCursor);
|
||||
ByteBufUtils.writeVarInt(buf, master, 5);
|
||||
ByteBufUtils.writeVarShort(buf, data.size());
|
||||
for (Entry<String, Integer> e : data) {
|
||||
ByteBufUtils.writeUTF8String(buf, e.getKey());
|
||||
@@ -69,7 +64,6 @@ public class SetChannelDataMessage implements IMessage {
|
||||
ItemStack target = message.onCursor ? player.inventory.getItemStack() : player.getHeldItem();
|
||||
if (target != null && target.getItem() instanceof ItemConstructableTrigger) {
|
||||
ChannelDataAccessor.wipeChannelData(target);
|
||||
if (message.master > 0) ChannelDataAccessor.setMasterData(target, message.master);
|
||||
for (Entry<String, Integer> e : message.data) {
|
||||
ChannelDataAccessor.setChannelData(target, e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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;
|
||||
@@ -443,6 +444,8 @@ 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,
|
||||
@@ -453,14 +456,27 @@ public interface IStructureDefinition<T> {
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
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);
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
|
||||
@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;
|
||||
}
|
||||
},
|
||||
"spawnHint");
|
||||
} else {
|
||||
StructureUtility.iterateV2(
|
||||
|
||||
@@ -42,7 +42,6 @@ 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".
|
||||
|
||||
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
|
||||
channels.structurelib.master=Overrides the projector stack size, i.e. the value used by every sub channel that is not set
|
||||
|
||||
structurelib.tooltip.channelvalue=§7Value §b%d§r§7 for subchannel §b%s
|
||||
structurelib.tooltip.indicator_dnd=Drop into GUI or middle-click to select this value for this channel
|
||||
@@ -188,6 +187,12 @@ 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.
|
||||
|
||||
@@ -41,7 +41,6 @@ item.structurelib.constructableTrigger.gui.channels.from=§r模组: §9§o%s
|
||||
item.structurelib.constructableTrigger.gui.info=许多多方块的结构都是可变的,在自动放置时,可以使用子信道来向多方块指明该使用哪一个变体。\n子信道由一个字符串标识、并拥有一个正整数作为值。这个GUI中的信道名支持自动补全到绝大多数已知的子信道。\n你也可以将部分物品从NEI中拖拽至GUI内来设置子信道值为该物品表示的值。这些物品的物品说明中都带有§o表示子信道 X 为 Y§r。
|
||||
|
||||
channels.structurelib.show_errors=添加后,在投影时会高亮标出必然错误的部分
|
||||
channels.structurelib.master=覆盖投影仪堆叠数量,即所有未设置的子信道所使用的值
|
||||
|
||||
structurelib.tooltip.channelvalue=§7表示子信道 §b%2$s§r§7 为 §b%d§r
|
||||
structurelib.tooltip.indicator_dnd=拖入GUI中来选择该值
|
||||
@@ -182,6 +181,12 @@ 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 使用注册表来管理其部分功能。每个注册表中的每一个元素都可以被单独排序、禁用。需要时,注册表的内容将被同步至服务端。
|
||||
|
||||
Reference in new issue
Block a user