Crash-proof nixie tubes

- Add many checks when reading NBT to prevent crashes
- Store rendered strings and only update when necessary
- Reorganize some code and rename some members
This commit is contained in:
PepperBell 2021-06-23 12:34:12 -07:00
parent c9f9be73ff
commit 0bc1a71558
3 changed files with 123 additions and 83 deletions

View file

@ -154,7 +154,7 @@ public class NixieTubeBlock extends HorizontalBlock implements ITE<NixieTubeTile
return;
withTileEntityDo(worldIn, pos, te -> {
if (te.reactsToRedstone())
te.displayRedstoneStrength(getPower(worldIn, pos));
te.updateRedstoneStrength(getPower(worldIn, pos));
});
}

View file

@ -20,7 +20,7 @@ import net.minecraft.util.text.Style;
public class NixieTubeRenderer extends SafeTileEntityRenderer<NixieTubeTileEntity> {
Random r = new Random();
private Random r = new Random();
public NixieTubeRenderer(TileEntityRendererDispatcher dispatcher) {
super(dispatcher);
@ -38,7 +38,7 @@ public class NixieTubeRenderer extends SafeTileEntityRenderer<NixieTubeTileEntit
float height = blockState.get(NixieTubeBlock.CEILING) ? 2 : 6;
float scale = 1 / 20f;
Couple<String> s = te.getVisibleText();
Couple<String> s = te.getDisplayedStrings();
ms.push();
ms.translate(-4 / 16f, 0, 0);

View file

@ -1,23 +1,20 @@
package com.simibubi.create.content.logistics.block.redstone;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.simibubi.create.foundation.item.TooltipHelper;
import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.block.BlockState;
import net.minecraft.command.CommandSource;
import net.minecraft.command.ICommandSource;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
@ -28,19 +25,23 @@ import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TextComponentUtils;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.util.Constants.NBT;
public class NixieTubeTileEntity extends SmartTileEntity {
Optional<Pair<ITextComponent, Integer>> customText;
JsonElement rawCustomText;
Couple<String> renderText;
private static final Couple<String> EMPTY = Couple.create("", "");
int redstoneStrength;
private boolean hasCustomText;
private int redstoneStrength;
private JsonElement rawCustomText;
private int customTextIndex;
private ITextComponent parsedCustomText;
private Couple<String> displayedStrings;
public NixieTubeTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn);
hasCustomText = false;
redstoneStrength = 0;
customText = Optional.empty();
}
@Override
@ -48,117 +49,156 @@ public class NixieTubeTileEntity extends SmartTileEntity {
super.tick();
// Dynamic text components have to be ticked manually and re-sent to the client
if (customText.isPresent() && world instanceof ServerWorld) {
Pair<ITextComponent, Integer> textSection = customText.get();
textSection.setFirst(updateDynamicTextComponents(ITextComponent.Serializer.fromJson(rawCustomText)));
Couple<String> currentText = getVisibleText();
if (renderText != null && renderText.equals(currentText))
return;
renderText = currentText;
sendData();
if (world instanceof ServerWorld && hasCustomText) {
Couple<String> currentStrings = displayedStrings;
parsedCustomText = parseCustomText();
updateDisplayedStrings();
if (currentStrings == null || !currentStrings.equals(displayedStrings))
sendData();
}
}
@Override
public void initialize() {
if (world.isRemote)
updateDisplayedStrings();
}
//
public void clearCustomText() {
if (!customText.isPresent())
return;
displayRedstoneStrength(0);
public boolean reactsToRedstone() {
return !hasCustomText;
}
public void displayCustomNameOf(ItemStack stack, int nixiePositionInRow) {
CompoundNBT compoundnbt = stack.getChildTag("display");
if (compoundnbt != null && compoundnbt.contains("Name", 8)) {
JsonElement fromJson = getJsonFromString(compoundnbt.getString("Name"));
ITextComponent displayed = ITextComponent.Serializer.fromJson(fromJson);
if (this.world instanceof ServerWorld)
displayed = updateDynamicTextComponents(displayed);
this.customText = Optional.of(Pair.of(displayed, nixiePositionInRow));
this.rawCustomText = fromJson;
notifyUpdate();
}
public Couple<String> getDisplayedStrings() {
if (displayedStrings == null)
return EMPTY;
return displayedStrings;
}
public void displayRedstoneStrength(int signalStrength) {
customText = Optional.empty();
public void updateRedstoneStrength(int signalStrength) {
clearCustomText();
redstoneStrength = signalStrength;
notifyUpdate();
}
public boolean reactsToRedstone() {
return !customText.isPresent();
public void displayCustomNameOf(ItemStack stack, int nixiePositionInRow) {
CompoundNBT compoundnbt = stack.getChildTag("display");
if (compoundnbt != null && compoundnbt.contains("Name", NBT.TAG_STRING)) {
hasCustomText = true;
rawCustomText = getJsonFromString(compoundnbt.getString("Name"));
customTextIndex = nixiePositionInRow;
parsedCustomText = parseCustomText();
notifyUpdate();
}
}
public Couple<String> getVisibleText() {
if (!customText.isPresent())
return Couple.create(redstoneStrength < 10 ? "0" : "1", redstoneStrength % 10 + "");
String fullText = TooltipHelper.getUnformattedDeepText(customText.get()
.getFirst());
int index = customText.get()
.getSecond() * 2;
return Couple.create(charOrEmpty(fullText, index), charOrEmpty(fullText, index + 1));
public void updateDisplayedStrings() {
if (!hasCustomText) {
displayedStrings = Couple.create(redstoneStrength < 10 ? "0" : "1", String.valueOf(redstoneStrength % 10));
} else {
String fullText = parsedCustomText.getString();
int index = customTextIndex * 2;
displayedStrings = Couple.create(charOrEmpty(fullText, index), charOrEmpty(fullText, index + 1));
}
}
public void clearCustomText() {
hasCustomText = false;
rawCustomText = null;
customTextIndex = 0;
parsedCustomText = null;
}
//
@Override
protected void fromTag(BlockState state, CompoundNBT nbt, boolean clientPacket) {
customText = Optional.empty();
redstoneStrength = nbt.getInt("RedstoneStrength");
if (nbt.contains("CustomText")) {
ITextComponent displayed = ITextComponent.Serializer.fromJson(nbt.getString("CustomText"));
rawCustomText = getJsonFromString(nbt.getString("RawCustomText"));
customText = Optional.of(Pair.of(displayed, nbt.getInt("CustomTextIndex")));
}
super.fromTag(state, nbt, clientPacket);
if (nbt.contains("RawCustomText", NBT.TAG_STRING)) {
rawCustomText = getJsonFromString(nbt.getString("RawCustomText"));
// Check if string forms valid JSON
if (rawCustomText != null && !rawCustomText.isJsonNull()) {
ITextComponent deserializedComponent = parseCustomText();
// Check if JSON forms valid component
if (deserializedComponent != null) {
try {
// Try to deserialize previously parsed component
parsedCustomText = ITextComponent.Serializer.fromJson(nbt.getString("CustomText"));
} catch (JsonParseException e) {
//
}
if (parsedCustomText == null) {
// Use test component to ensure field isn't null
parsedCustomText = deserializedComponent;
}
hasCustomText = true;
customTextIndex = nbt.getInt("CustomTextIndex");
}
}
}
if (!hasCustomText) {
clearCustomText();
redstoneStrength = nbt.getInt("RedstoneStrength");
}
if (clientPacket)
updateDisplayedStrings();
}
@Override
protected void write(CompoundNBT nbt, boolean clientPacket) {
super.write(nbt, clientPacket);
nbt.putInt("RedstoneStrength", redstoneStrength);
if (customText.isPresent()) {
if (hasCustomText) {
nbt.putString("RawCustomText", rawCustomText.toString());
nbt.putString("CustomText", ITextComponent.Serializer.toJson(customText.get()
.getFirst()));
nbt.putInt("CustomTextIndex", customText.get()
.getSecond());
nbt.putInt("CustomTextIndex", customTextIndex);
nbt.putString("CustomText", ITextComponent.Serializer.toJson(parsedCustomText));
} else {
nbt.putInt("RedstoneStrength", redstoneStrength);
}
}
private JsonElement getJsonFromString(String string) {
return new JsonParser().parse(string);
}
protected ITextComponent updateDynamicTextComponents(ITextComponent customText) {
try {
return TextComponentUtils.parse(this.getCommandSource(null), customText,
(Entity) null, 0);
} catch (CommandSyntaxException e) {
return new JsonParser().parse(string);
} catch (JsonParseException e) {
return null;
}
return customText;
}
// From SignTileEntity
protected CommandSource getCommandSource(@Nullable ServerPlayerEntity p_195539_1_) {
String s = p_195539_1_ == null ? "Sign"
: p_195539_1_.getName()
.getString();
ITextComponent itextcomponent =
(ITextComponent) (p_195539_1_ == null ? new StringTextComponent("Sign") : p_195539_1_.getDisplayName());
return new CommandSource(ICommandSource.field_213139_a_,
new Vector3d((double) this.pos.getX() + 0.5D, (double) this.pos.getY() + 0.5D,
(double) this.pos.getZ() + 0.5D),
Vector2f.ZERO, (ServerWorld) this.world, 2, s, itextcomponent, this.world.getServer(), p_195539_1_);
}
private String charOrEmpty(String string, int index) {
return string.length() <= index ? " " : string.substring(index, index + 1);
}
protected ITextComponent parseCustomText() {
try {
return parseDynamicComponent(ITextComponent.Serializer.fromJson(rawCustomText));
} catch (JsonParseException e) {
return null;
}
}
protected ITextComponent parseDynamicComponent(ITextComponent customText) {
if (world instanceof ServerWorld) {
try {
return TextComponentUtils.parse(getCommandSource(null), customText, null, 0);
} catch (CommandSyntaxException e) {
//
}
}
return customText;
}
// From SignTileEntity
public CommandSource getCommandSource(@Nullable ServerPlayerEntity p_195539_1_) {
String s = p_195539_1_ == null ? "Nixie Tube" : p_195539_1_.getName().getString();
ITextComponent itextcomponent = (ITextComponent)(p_195539_1_ == null ? new StringTextComponent("Nixie Tube") : p_195539_1_.getDisplayName());
return new CommandSource(ICommandSource.field_213139_a_, Vector3d.ofCenter(this.pos), Vector2f.ZERO, (ServerWorld)this.world, 2, s, itextcomponent, this.world.getServer(), p_195539_1_);
}
@Override
public void addBehaviours(List<TileEntityBehaviour> behaviours) {}