Rework blast furnace validation (breaking change!)

- Pattern-based validation - layer wise matching
- Remove Reinforced fireproof bricks (breaking change!)
- Add fullbright rendering for coke oven & blast furnace contents
- Minor fix Copper spool (15x16 -> 16x16 texture)
This commit is contained in:
Daniel
2025-07-12 05:16:49 +03:00
parent 0fbcfc79f8
commit 8f4b064de8
38 changed files with 538 additions and 336 deletions

View File

@@ -1,7 +0,0 @@
{
"variants": {
"": {
"model": "tfmg:block/reinforced_fireproof_bricks"
}
}
}

View File

@@ -1,6 +0,0 @@
{
"parent": "minecraft:block/cube_all",
"textures": {
"all": "tfmg:block/fireproof_bricks"
}
}

View File

@@ -81,7 +81,6 @@
"tfmg:blast_furnace_output",
"tfmg:blast_furnace_hatch",
"tfmg:fireproof_bricks",
"tfmg:reinforced_fireproof_bricks",
"tfmg:blast_furnace_reinforcement",
"tfmg:blast_furnace_reinforcement_wall",
"tfmg:rusted_blast_furnace_reinforcement",

View File

@@ -9,7 +9,6 @@
"tfmg:blast_furnace_output",
"tfmg:blast_furnace_hatch",
"tfmg:fireproof_bricks",
"tfmg:reinforced_fireproof_bricks",
"tfmg:blast_furnace_reinforcement",
"tfmg:blast_furnace_reinforcement_wall",
"tfmg:rusted_blast_furnace_reinforcement",

View File

@@ -1,21 +0,0 @@
{
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"name": "tfmg:fireproof_bricks"
}
],
"rolls": 1.0
}
],
"random_sequence": "tfmg:blocks/reinforced_fireproof_bricks"
}

View File

@@ -1,7 +1,6 @@
{
"values": [
"tfmg:blast_furnace_hatch",
"tfmg:reinforced_fireproof_bricks",
"tfmg:steel_block"
]
}

View File

@@ -1,23 +1,17 @@
package com.drmangotea.tfmg.base.events;
import com.drmangotea.tfmg.TFMG;
import com.drmangotea.tfmg.content.electricity.measurement.MultimeterOverlayRenderer;
import com.drmangotea.tfmg.content.engines.engine_controller.EngineControllerClientHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.client.event.*;
import net.minecraftforge.client.event.RenderLevelStageEvent.Stage;
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
import net.minecraftforge.client.gui.overlay.VanillaGuiOverlay;
import net.minecraftforge.event.TickEvent.ClientTickEvent;
import net.minecraftforge.event.TickEvent.Phase;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
import net.minecraftforge.fml.event.lifecycle.FMLLoadCompleteEvent;

View File

@@ -1,41 +0,0 @@
package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace;
public final class BlastFurnaceLayerPatterns {
// Fireproof bricks + corner brick reinforcements
public static final BlastFurnaceLayer THIN_REGULAR = new BlastFurnaceLayer("AAAAAAFBFAABABAAFBFAAAAAA");
// Fireproof bricks + corner brick reinforcements, 1 tuyere
public static final BlastFurnaceLayer THIN_REGULAR_TUYERE = new BlastFurnaceLayer("AA*AAAFTFAABABAAFBFAAAAAA");
// Fireproof bricks all around
public static final BlastFurnaceLayer THICK_REGULAR = new BlastFurnaceLayer("AAAAAABBBAABABAABBBAAAAAA");
// Fireproof bricks all around, 1 tuyere
public static final BlastFurnaceLayer THICK_REGULAR_TUYERE = new BlastFurnaceLayer("AA*AAABTBAABABAABBBAAAAAA");
// Reinforced walls + fireproof brick sides, corner blast furnace reinforcements
public static final BlastFurnaceLayer THIN_REINFORCED = new BlastFurnaceLayer("AARAAARBRARBABRARBRAAARAA");
// Reinforced walls + fireproof brick sides, corner blast furnace reinforcements, 1 tuyere
public static final BlastFurnaceLayer THIN_REINFORCED_TUYERE = new BlastFurnaceLayer("AA*AAARTRARBABRARBRAAARAA");
// Reinforced walls + fireproof bricks all around
public static final BlastFurnaceLayer THICK_REINFORCED = new BlastFurnaceLayer("ARRRARBBBRRBABRRBBBRARRRA");
// Reinforced walls + fireproof bricks all around, 1 tuyere
public static final BlastFurnaceLayer THICK_REINFORCED_TUYERE = new BlastFurnaceLayer("AR*RARBTBRRBABRRBBBRARRRA");
// Fireproof bricks + corner brick reinforcements, 1 output
public static final BlastFurnaceLayer THIN_REGULAR_OUTPUT = new BlastFurnaceLayer("AA*AAAFOFAABABAAFBFAAAAAA");
// Fireproof bricks all around, 1 output
public static final BlastFurnaceLayer THICK_REGULAR_OUTPUT = new BlastFurnaceLayer("AA*AAABOBAABABAABBBAAAAAA");
// Reinforced walls + fireproof brick sides, corner blast furnace reinforcements, 1 output
public static final BlastFurnaceLayer THIN_REINFORCED_BASE = new BlastFurnaceLayer("AA*AAARORARBABRARBRAAARAA");
// Reinforced walls + fireproof bricks all around, 1 output
public static final BlastFurnaceLayer THICK_REINFORCED_OUTPUT = new BlastFurnaceLayer("AR*RARBOBRRBABRRBBBRARRRA");
}

View File

@@ -2,6 +2,7 @@ package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace;
import com.drmangotea.tfmg.base.TFMGUtils;
import com.drmangotea.tfmg.config.TFMGConfigs;
import com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace.util.BlastFurnaceValidator;
import com.drmangotea.tfmg.datagen.TFMGDamageSources;
import com.drmangotea.tfmg.recipes.IndustrialBlastingRecipe;
import com.drmangotea.tfmg.registry.TFMGBlocks;
@@ -28,6 +29,7 @@ import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
@@ -66,6 +68,7 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
public static final int STORAGE_SPACE = 64;
public LerpedFloat coalCokeHeight = LerpedFloat.linear();
boolean isReinforced = false;
boolean isActive = false;
public BlastFurnaceOutputBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
@@ -112,10 +115,6 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
}
}
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
private int calculateProcessingTime(IndustrialBlastingRecipe recipe) {
int baseDuration = recipe.getProcessingDuration() * 20;
double timeModifier = TFMGConfigs.common().machines.blastFurnaceMaxHeight.get() /
@@ -215,8 +214,7 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
public void executeRecipe() {
RecipeWrapper inventoryIn = new RecipeWrapper(inputInventory);
Optional<IndustrialBlastingRecipe> optional = TFMGRecipeTypes.INDUSTRIAL_BLASTING.find(inventoryIn, level);
Optional<IndustrialBlastingRecipe> optional = TFMGRecipeTypes.INDUSTRIAL_BLASTING.find(new RecipeWrapper(inputInventory), level);
if (optional.isEmpty())
return;
@@ -240,6 +238,13 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
timer /= 2;
}
private boolean isActuallyActive(IndustrialBlastingRecipe recipe) {
return timer > 0 && fuel > 0
&& (recipe.getFluidResults().isEmpty() || primaryTank.getSpace() >= recipe.getPrimaryResult().getAmount())
&& (recipe.getFluidResults().size() < 2 || secondaryTank.getSpace() >= recipe.getSecondaryResult().getAmount())
&& (recipe.hotAirUsage <= 0 || (tuyereBE != null && tuyereBE.tank.getFluidAmount() >= recipe.hotAirUsage && tuyereBE.tank.getFluid().getFluid().isSame(TFMGFluids.HOT_AIR.getSource())));
}
@Override
public void tick() {
super.tick();
@@ -254,6 +259,8 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
if (getSize() < 3)
return;
Optional<IndustrialBlastingRecipe> optionalRecipe = TFMGRecipeTypes.INDUSTRIAL_BLASTING.find(new RecipeWrapper(inputInventory), level);
if (fuel <= 0 && !fuelInventory.getItem(0).isEmpty()) {
ItemStack fuelStack = fuelInventory.getItem(0);
if (fuelStack.is(TFMGTags.TFMGItemTags.BLAST_FURNACE_FUEL.tag)) {
@@ -271,20 +278,16 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
}
if (timer > -1) {
RecipeWrapper inventoryIn = new RecipeWrapper(inputInventory);
Optional<IndustrialBlastingRecipe> optional = TFMGRecipeTypes.INDUSTRIAL_BLASTING.find(inventoryIn, level);
if (optional.isEmpty()) {
if (optionalRecipe.isEmpty()) {
timer = -1;
return;
}
IndustrialBlastingRecipe recipe = optional.get();
IndustrialBlastingRecipe recipe = optionalRecipe.get();
if (timer == 0) {
if (canProcess(recipe)) {
int itemsUsed = 1;
int fluxUsed = 1;
if (!(primaryTank.getSpace() >= recipe.getPrimaryResult().getAmount()))
return;
@@ -305,16 +308,24 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
setChanged();
}
}
if (timer > 0 && fuel > 0) {
if (recipe.hotAirUsage > 0 && (tuyerePos == null || !level.getBlockState(tuyerePos).is(TFMGBlocks.BLAST_FURNACE_HATCH.get()))) {
tuyereBE = null;
return;
}
if (tuyereBE == null && tuyerePos != null)
tuyereBE = (BlastFurnaceHatchBlockEntity) level.getBlockEntity(tuyerePos);
if (tuyereBE.tank.getFluidAmount() < recipe.hotAirUsage || !tuyereBE.tank.getFluid().getFluid().isSame(TFMGFluids.HOT_AIR.getSource()))
return;
if (timer > 0 && fuel > 0) {
if (recipe.hotAirUsage > 0) {
if (tuyerePos == null || !level.getBlockState(tuyerePos).is(TFMGBlocks.BLAST_FURNACE_HATCH.get())) {
tuyereBE = null;
return;
}
if (tuyereBE == null) {
if (!(level.getBlockEntity(tuyerePos) instanceof BlastFurnaceHatchBlockEntity hatch)) return;
tuyereBE = hatch;
}
if (tuyereBE.tank.getFluidAmount() < recipe.hotAirUsage ||
!tuyereBE.tank.getFluid().getFluid().isSame(TFMGFluids.HOT_AIR.getSource())) {
return;
}
}
tuyereBE.tank.getFluidInTank(0).setAmount(Math.max(tuyereBE.tank.getFluidInTank(0).getAmount() - recipe.hotAirUsage, 0));
@@ -335,6 +346,7 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
}
}
}
isActive = optionalRecipe.map(this::isActuallyActive).orElse(false);
}
public void makeParticles() {
@@ -348,14 +360,10 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
}
private boolean canProcess(IndustrialBlastingRecipe recipe) {
//if (fuel == 0)
// return false;
if (!primaryTank.getFluid().isEmpty() && !primaryTank.getFluid().getFluid().isSame(recipe.getPrimaryResult().getFluid()))
return false;
if (!secondaryTank.getFluid().isEmpty() && !secondaryTank.getFluid().getFluid().isSame(recipe.getSecondaryResult().getFluid()))
return false;
return true;
return secondaryTank.getFluid().isEmpty() || secondaryTank.getFluid().getFluid().isSame(recipe.getSecondaryResult().getFluid());
}
@Override
@@ -479,88 +487,18 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
public int getSize() {
BlockPos middlePos = getBlockPos().relative(getBlockState().getValue(FACING).getOpposite());
tuyerePos = null;
if ((isValidWall(middlePos) == FurnaceBlockType.NONE))
return 0;
int size = 0;
int normalAmount = 0;
int reinforcedAmount = 0;
for (int i = 0; i <= TFMGConfigs.common().machines.blastFurnaceMaxHeight.get(); i++) {
BlockPos checkedPos = middlePos.above(i).east().south();
for (int j = 0; j < 3; j++) {
for (int y = 0; y < 3; y++) {
FurnaceBlockType wall = isValidWall(checkedPos);
FurnaceBlockType support = isValidSupport(checkedPos);
if (checkedPos.getX() == middlePos.getX() ^ checkedPos.getZ() == middlePos.getZ()) {
if (!(i == 0 && level.getBlockState(checkedPos).is(TFMGBlocks.BLAST_FURNACE_OUTPUT.get()))) {
if (wall == FurnaceBlockType.NONE) {
isReinforced = normalAmount == 0 && reinforcedAmount > 0;
return size;
} else {
if (wall == FurnaceBlockType.REGULAR) {
normalAmount++;
} else reinforcedAmount++;
}
}
} else if (checkedPos.getX() == middlePos.getX() && checkedPos.getZ() == middlePos.getZ()) {
if (!level.getBlockState(checkedPos).isAir() && i != 0) {
isReinforced = normalAmount == 0 && reinforcedAmount > 0;
return size;
}
} else if (support == FurnaceBlockType.NONE) {
isReinforced = normalAmount == 0 && reinforcedAmount > 0;
return size;
} else {
if (support == FurnaceBlockType.REGULAR) {
normalAmount++;
} else reinforcedAmount++;
}
checkedPos = checkedPos.west();
}
checkedPos = checkedPos.north();
checkedPos = checkedPos.east(3);
}
size++;
if (this.isRemoved()) { // Critical check
return 0; // Skip validation if block entity is destroyed
}
return size;
}
// Create validator and validate the furnace structure
BlastFurnaceValidator validator = new BlastFurnaceValidator(getBlockPos(), level);
BlastFurnaceValidator.ValidationResult result = validator.validateFurnace();
public FurnaceBlockType isValidWall(BlockPos pos) {
// Update entity state
this.isReinforced = result.isReinforced();
this.tuyerePos = validator.getTuyerePos();
BlockState state = level.getBlockState(pos);
if (state.is(TFMGBlocks.BLAST_FURNACE_HATCH.get())) {
if (tuyerePos != null)
return FurnaceBlockType.NONE;
tuyerePos = pos;
}
if (state.is(TFMGTags.TFMGBlockTags.REINFORCED_BLAST_FURNACE_WALL.tag))
return FurnaceBlockType.REINFORCED;
if (state.is(TFMGTags.TFMGBlockTags.BLAST_FURNACE_WALL.tag))
return FurnaceBlockType.REGULAR;
return FurnaceBlockType.NONE;
}
public FurnaceBlockType isValidSupport(BlockPos pos) {
BlockState state = level.getBlockState(pos);
if (state.is(TFMGTags.TFMGBlockTags.REINFORCED_BLAST_FURNACE_SUPPORT.tag))
return FurnaceBlockType.REINFORCED;
if (state.is(TFMGTags.TFMGBlockTags.BLAST_FURNACE_SUPPORT.tag))
return FurnaceBlockType.REGULAR;
return FurnaceBlockType.NONE;
return result.height();
}
@Nonnull
@@ -572,11 +510,4 @@ public class BlastFurnaceOutputBlockEntity extends SmartBlockEntity implements I
return fluidCapability.cast();
return super.getCapability(cap, side);
}
enum FurnaceBlockType {
NONE,
REGULAR,
REINFORCED
}
}

View File

@@ -4,8 +4,10 @@ import com.drmangotea.tfmg.registry.TFMGPartialModels;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.simibubi.create.foundation.blockEntity.renderer.SafeBlockEntityRenderer;
import dev.engine_room.flywheel.lib.model.baked.PartialModel;
import net.createmod.catnip.render.CachedBuffers;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
@@ -23,18 +25,26 @@ public class BlastFurnaceRenderer extends SafeBlockEntityRenderer<BlastFurnaceOu
protected void renderSafe(BlastFurnaceOutputBlockEntity be, float partialTicks, PoseStack ms, MultiBufferSource buffer,
int light, int overlay) {
if (be.getLevel() == null) return;
BlockState blockState = be.getBlockState();
float coalCokeLevel = be.coalCokeHeight.getValue() / 64;
boolean isActive = be.isActive;
Direction facing = blockState.getValue(FACING);
PartialModel cokeModel = isActive? TFMGPartialModels.COAL_COKE_DUST_LAYER_GLOWING:
TFMGPartialModels.COAL_COKE_DUST_LAYER;
int lightLevel = isActive? LightTexture.FULL_BRIGHT : LevelRenderer.getLightColor(be.getLevel(), be.getBlockPos().above().relative(facing.getOpposite()));
VertexConsumer vb = buffer.getBuffer(RenderType.solid());
Direction facing = blockState.getValue(FACING);
if (coalCokeLevel > 0)
if (be.getSize() >= 3) {
CachedBuffers.partial(TFMGPartialModels.COAL_COKE_DUST_LAYER, blockState)
.light(LevelRenderer.getLightColor(be.getLevel(), be.getBlockPos().above().relative(facing.getOpposite())))
CachedBuffers.partial(cokeModel, blockState)
.light(lightLevel)
.center()
.rotateYDegrees(facing.getAxis() == Direction.Axis.X ? facing.getCounterClockWise().toYRot() : facing.getClockWise().toYRot())
.translateY(coalCokeLevel + 1.1f)

View File

@@ -1,14 +1,9 @@
package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace.reinforcement;
import com.drmangotea.tfmg.base.blocks.TFMGHorizontalDirectionalBlock;
import com.drmangotea.tfmg.base.TFMGShapes;
import com.drmangotea.tfmg.registry.TFMGBlocks;
import com.drmangotea.tfmg.base.blocks.TFMGHorizontalDirectionalBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
@@ -18,54 +13,8 @@ public class BlastFurnaceReinforcementWallBlock extends TFMGHorizontalDirectiona
super(p_54120_);
}
@Override
public void onPlace(BlockState blockState, Level level, BlockPos pos, BlockState blockState1, boolean b) {
changeFireproofBricks(level, pos, blockState.getValue(FACING).getOpposite(), true);
super.onPlace(blockState, level, pos, blockState1, b);
}
@Override
public void onRemove(BlockState blockState, Level level, BlockPos pos, BlockState blockState1, boolean b) {
changeFireproofBricks(level, pos, blockState.getValue(FACING).getOpposite(), false);
super.onRemove(blockState, level, pos, blockState1, b);
}
@Override
public VoxelShape getShape(BlockState p_60555_, BlockGetter p_60556_, BlockPos p_60557_, CollisionContext p_60558_) {
return TFMGShapes.BLAST_FURNACE_REINFORCEMENT_WALL.get(p_60555_.getValue(FACING));
}
@Override
public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos neighbor, boolean b) {
BlockState stateBehind = level.getBlockState(pos.relative(state.getValue(FACING).getOpposite()));
if(stateBehind.is(TFMGBlocks.FIREPROOF_BRICKS.get()))
changeFireproofBricks((Level) level, pos, state.getValue(FACING).getOpposite(), true);
super.neighborChanged(state, level, pos, block, neighbor, b);
}
@Override
public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) {
BlockState stateBehind = level.getBlockState(pos.relative(state.getValue(FACING).getOpposite()));
if(stateBehind.is(TFMGBlocks.FIREPROOF_BRICKS.get()))
changeFireproofBricks((Level) level, pos, state.getValue(FACING).getOpposite(), false);
super.onNeighborChange(state, level, pos, neighbor);
}
public static void changeFireproofBricks(Level level, BlockPos pos, Direction direction, boolean reinforce){
BlockState state = level.getBlockState(pos.relative(direction));
if(reinforce&&state.is(TFMGBlocks.FIREPROOF_BRICKS.get())){
level.setBlock(pos.relative(direction), TFMGBlocks.REINFORCED_FIREPROOF_BRICKS.getDefaultState(), 2);
}
if(!reinforce&&state.is(TFMGBlocks.REINFORCED_FIREPROOF_BRICKS.get())){
level.setBlock(pos.relative(direction), TFMGBlocks.FIREPROOF_BRICKS.getDefaultState(), 2);
}
}
}

View File

@@ -1,14 +1,7 @@
package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace;
import com.drmangotea.tfmg.registry.TFMGBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace.util;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
@@ -28,28 +21,21 @@ import java.util.Set;
*/
public class BlastFurnaceLayer {
private final Set<String> validRotations;
private final Set<String> layerRotations;
public static final int LAYER_SIZE = 5;
public static final int LAYER_AREA = LAYER_SIZE * LAYER_SIZE;
private static final Map<Block, Character> BLOCK_SYMBOLS = Map.of(
TFMGBlocks.BLAST_FURNACE_HATCH.get(), 'T',
TFMGBlocks.FIREPROOF_BRICKS.get(),'B',
TFMGBlocks.FIREPROOF_BRICK_REINFORCEMENT.get(), 'F',
TFMGBlocks.BLAST_FURNACE_REINFORCEMENT.get(), 'R',
TFMGBlocks.RUSTED_BLAST_FURNACE_REINFORCEMENT.get(), 'R',
TFMGBlocks.BLAST_FURNACE_REINFORCEMENT_WALL.get(), 'W',
TFMGBlocks.RUSTED_BLAST_FURNACE_REINFORCEMENT_WALL.get(), 'W',
TFMGBlocks.BLAST_FURNACE_OUTPUT.get(), 'O',
Blocks.AIR, 'A'
);
public BlastFurnaceLayer(String layer) {
if (layer == null || layer.length() != 25) {
if (layer == null || layer.length() != LAYER_AREA) {
throw new IllegalArgumentException(
String.format("Layer must be a non-null 25 length string (got %s)",
String.format("Layer must be a non-null %s length string (got %s)",
LAYER_AREA,
layer == null ? "null" : layer.length())
);
}
validRotations = computeRotations(layer);
layerRotations = Collections.unmodifiableSet(computeRotations(layer));
}
private Set<String> computeRotations(String baseLayer) {
@@ -95,30 +81,44 @@ public class BlastFurnaceLayer {
return new String(rotated);
}
boolean isValidLayer(BlockPos center, Level level) {
String builtLayer = flattenLayer(center, level);
return validRotations.contains(builtLayer);
public boolean matchesAnyPattern(Set<String> patterns) {
return patterns.stream().anyMatch(pattern ->
layerRotations.stream().anyMatch(rotation ->
matchesWithWildcards(rotation, pattern)
)
);
}
/**
* Scans a 5x5 square of blocks in the world and converts it to a layer String.
* @param center center BlockPos of a layer.
* @param level Level (world).
* @return row-major String representation of the scanned layer.
*/
String flattenLayer(BlockPos center, Level level) {
int size = 5; // 5x5 layers max
StringBuilder sb = new StringBuilder(size * size);
for (int dz = -2; dz <= 2; dz++) { // Z = rows
for (int dx = -2; dx <= 2; dx++) { // X = columns
BlockPos pos = center.offset(dx, 0, dz);
sb.append(getBlockSymbol(level.getBlockState(pos)));
private boolean matchesWithWildcards(String actualLayer, String pattern) {
if (actualLayer.length() != pattern.length()) return false;
for (int i = 0; i < actualLayer.length(); i++) {
char p = pattern.charAt(i);
char a = actualLayer.charAt(i);
// '*' in pattern matches any character (including air)
if (p != '*' && p != a) {
return false;
}
if (pattern.contains("T") && !actualLayer.contains("T")) {
return false; // Tuyere required but missing
}
if (pattern.contains("O") && !actualLayer.contains("O")) {
return false; // Output required but missing
}
}
return sb.toString();
return true;
}
char getBlockSymbol(BlockState state) {
return BLOCK_SYMBOLS.getOrDefault(state.getBlock(), '*');
public boolean isBaseLayer() {
return matchesAnyPattern(BlastFurnaceLayerPatterns.BASE_LAYERS);
}
public boolean isWallLayer() {
return matchesAnyPattern(BlastFurnaceLayerPatterns.WALL_LAYERS);
}
public boolean isReinforced() {
return matchesAnyPattern(BlastFurnaceLayerPatterns.REINFORCED_LAYERS);
}
}

View File

@@ -0,0 +1,130 @@
package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace.util;
import java.util.Set;
public final class BlastFurnaceLayerPatterns {
// Fireproof bricks + corner brick reinforcements
public static final String THIN_REGULAR =
"*****" + // Row 1
"*FBF*" + // Row 2
"*BAB*" + // Row 3
"*FBF*" + // Row 4
"*****"; // Row 5
// Fireproof bricks + corner brick reinforcements, 1 tuyere
public static final String THIN_REGULAR_TUYERE =
"*****" + // Row 1 (wildcard for tuyere access)
"*FTF*" + // Row 2 (T = tuyere)
"*BAB*" + // Row 3
"*FBF*" + // Row 4
"*****"; // Row 5
// Fireproof bricks all around
public static final String THICK_REGULAR =
"*****" + // Row 1
"*BBB*" + // Row 2
"*BAB*" + // Row 3
"*BBB*" + // Row 4
"*****"; // Row 5
// Fireproof bricks all around, 1 tuyere
public static final String THICK_REGULAR_TUYERE =
"*****" + // Row 1 (wildcard for tuyere access)
"*BTB*" + // Row 2 (T = tuyere)
"*BAB*" + // Row 3
"*BBB*" + // Row 4
"*****"; // Row 5
// Reinforced walls + fireproof brick sides, corner blast furnace reinforcements
public static final String THIN_REINFORCED =
"**W**" + // Row 1
"*RBR*" + // Row 2
"WBABW" + // Row 3
"*RBR*" + // Row 4
"**W**"; // Row 5
// Reinforced walls + fireproof brick sides, corner blast furnace reinforcements, 1 tuyere
public static final String THIN_REINFORCED_TUYERE =
"*****" + // Row 1 (wildcard for tuyere access)
"*RTR*" + // Row 2 (T = tuyere)
"WBABW" + // Row 3
"*RBR*" + // Row 4
"**W**"; // Row 5
// Reinforced walls + fireproof bricks all around, 1 tuyere
public static final String THICK_REINFORCED =
"*WWW*" + // Row 1
"WBBBW" + // Row 2
"WBABW" + // Row 3
"WBBBW" + // Row 4
"*WWW*"; // Row 5
// Reinforced walls + fireproof bricks all around, 1 tuyere
public static final String THICK_REINFORCED_TUYERE =
"*W*W*" + // Row 1 (wildcard for tuyere access)
"WBTBW" + // Row 2 (T = tuyere)
"WBABW" + // Row 3
"WBBBW" + // Row 4
"*WWW*"; // Row 5
// Fireproof bricks + corner brick reinforcements, 1 output
public static final String THIN_REGULAR_OUTPUT =
"*****" + // Row 1 (wildcard for output access)
"*FOF*" + // Row 2 (O = output)
"*BBB*" + // Row 3
"*FBF*" + // Row 4
"*****"; // Row 5
// Fireproof bricks all around, 1 output
public static final String THICK_REGULAR_OUTPUT =
"*****" + // Row 1 (wildcard for output access)
"*BOB*" + // Row 2 (O = output)
"*BBB*" + // Row 3
"*BBB*" + // Row 4
"*****"; // Row 5
// Reinforced walls + fireproof brick sides, corner blast furnace reinforcements, 1 output
public static final String THIN_REINFORCED_OUTPUT =
"*****" + // Row 1 (wildcard for output access)
"*ROR*" + // Row 2 (O = output)
"WBBBW" + // Row 3
"*RBR*" + // Row 4
"**W**"; // Row 5
// Reinforced walls + fireproof bricks all around, 1 output
public static final String THICK_REINFORCED_OUTPUT =
"*W*W*" + // Row 1 (wildcard for output access)
"WBOBW" + // Row 2 (O = output)
"WBBBW" + // Row 3
"WBBBW" + // Row 4
"*WWW*"; // Row 5
public static final Set<String> REINFORCED_LAYERS = Set.of(
THIN_REINFORCED,
THIN_REINFORCED_TUYERE,
THIN_REINFORCED_OUTPUT,
THICK_REINFORCED,
THICK_REINFORCED_TUYERE,
THICK_REINFORCED_OUTPUT
);
public static final Set<String> BASE_LAYERS = Set.of(
THIN_REGULAR_OUTPUT,
THICK_REGULAR_OUTPUT,
THIN_REINFORCED_OUTPUT,
THICK_REINFORCED_OUTPUT
);
public static final Set<String> WALL_LAYERS = Set.of(
THIN_REINFORCED,
THIN_REINFORCED_TUYERE,
THICK_REINFORCED,
THICK_REINFORCED_TUYERE,
THIN_REGULAR,
THIN_REGULAR_TUYERE,
THICK_REGULAR,
THICK_REGULAR_TUYERE
);
}

View File

@@ -0,0 +1,140 @@
package com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace.util;
import com.drmangotea.tfmg.config.TFMGConfigs;
import com.drmangotea.tfmg.registry.TFMGBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Map;
import static com.drmangotea.tfmg.content.machinery.metallurgy.blast_furnace.util.BlastFurnaceLayer.LAYER_SIZE;
import static net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING;
public class BlastFurnaceValidator {
private Level level;
private BlockPos tuyerePos;
private BlockPos outputPos;
private BlockPos startingOutputPos;
private static final Map<Block, Character> BLOCK_SYMBOLS = Map.of(
TFMGBlocks.BLAST_FURNACE_HATCH.get(), 'T',
TFMGBlocks.FIREPROOF_BRICKS.get(),'B',
TFMGBlocks.FIREPROOF_BRICK_REINFORCEMENT.get(), 'F',
TFMGBlocks.BLAST_FURNACE_REINFORCEMENT.get(), 'R',
TFMGBlocks.RUSTED_BLAST_FURNACE_REINFORCEMENT.get(), 'R',
TFMGBlocks.BLAST_FURNACE_REINFORCEMENT_WALL.get(), 'W',
TFMGBlocks.RUSTED_BLAST_FURNACE_REINFORCEMENT_WALL.get(), 'W',
TFMGBlocks.BLAST_FURNACE_OUTPUT.get(), 'O',
Blocks.AIR, 'A'
);
public BlastFurnaceValidator(BlockPos outputPos, Level levelIn) {
level = levelIn;
startingOutputPos = outputPos;
}
public record ValidationResult(int height, boolean isReinforced) {}
public record LayerScanResult(String pattern, BlockPos tuyerePos, BlockPos outputPos) {}
public ValidationResult validateFurnace() {
// Early exit if the block is air or invalid
BlockState outputState = level.getBlockState(startingOutputPos);
if (!outputState.is(TFMGBlocks.BLAST_FURNACE_OUTPUT.get())) {
return new ValidationResult(0, false);
}
resetState();
BlockPos baseCenterPos = startingOutputPos.relative(level.getBlockState(startingOutputPos).getValue(FACING).getOpposite());
// Validate base layer (must contain output)
LayerScanResult layerScan = scanLayer(baseCenterPos);
//
BlastFurnaceLayer layer = new BlastFurnaceLayer(layerScan.pattern());
if (!layer.isBaseLayer() || !validSpecialBlocks(layerScan, true)) {
return new ValidationResult(0, false);
}
int height = 1;
boolean isReinforced = layer.isReinforced();
// Validate stacked layers
int maxHeight = TFMGConfigs.common().machines.blastFurnaceMaxHeight.get();
for (int i = 1; i < maxHeight; i++) {
layerScan = scanLayer(baseCenterPos.above(i));
layer = new BlastFurnaceLayer(layerScan.pattern());
if (!layer.isWallLayer() || !validSpecialBlocks(layerScan, false)) {
break;
}
height++;
isReinforced &= layer.isReinforced();
}
return new ValidationResult(height, isReinforced);
}
private void resetState() {
tuyerePos = null;
outputPos = null;
}
/**
* Scans a 5x5 square of blocks in the world and converts it to a layer String.
* @param center center BlockPos of a layer.
* @return row-major String representation of the scanned layer.
*/
private LayerScanResult scanLayer(BlockPos center) {
StringBuilder pattern = new StringBuilder(25);
BlockPos tuyerePos = null;
BlockPos outputPos = null;
for (int dz = -LAYER_SIZE / 2; dz <= LAYER_SIZE / 2; dz++) { // Z = rows
for (int dx = -LAYER_SIZE / 2; dx <= LAYER_SIZE / 2; dx++) { // X = columns
BlockPos pos = center.offset(dx, 0, dz);
char symbol = getBlockSymbol(level.getBlockState(pos));
if (symbol == 'T') tuyerePos = pos;
else if (symbol == 'O') outputPos = pos;
pattern.append(symbol);
}
}
return new LayerScanResult(pattern.toString(), tuyerePos, outputPos);
}
private boolean validSpecialBlocks(LayerScanResult scan, boolean isBaseLayer) {
// Handle output (only allowed in base layer)
if (scan.outputPos() != null) {
if (!isBaseLayer || outputPos != null) return false; // Output in wrong layer or duplicate
outputPos = scan.outputPos();
}
// Handle tuyere
if (scan.tuyerePos() != null) {
if (tuyerePos != null) return false; // Duplicate tuyere
tuyerePos = scan.tuyerePos();
}
return true;
}
public BlockPos getTuyerePos() {
return tuyerePos;
}
public static char getBlockSymbol(BlockState state) {
return BLOCK_SYMBOLS.getOrDefault(state.getBlock(), '*');
}
}

View File

@@ -2,33 +2,78 @@ package com.drmangotea.tfmg.content.machinery.metallurgy.coke_oven;
import com.drmangotea.tfmg.registry.TFMGBlocks;
import com.drmangotea.tfmg.registry.TFMGPartialModels;
import dev.engine_room.flywheel.lib.model.baked.PartialModel;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.foundation.blockEntity.renderer.SafeBlockEntityRenderer;
import dev.engine_room.flywheel.lib.model.baked.PartialModel;
import net.createmod.catnip.render.CachedBuffers;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.state.BlockState;
import static net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING;
import static com.drmangotea.tfmg.content.machinery.metallurgy.coke_oven.CokeOvenBlock.CONTROLLER_TYPE;
import static net.minecraft.world.level.block.HorizontalDirectionalBlock.FACING;
public class CokeOvenRenderer extends SafeBlockEntityRenderer<CokeOvenBlockEntity> {
public CokeOvenRenderer(BlockEntityRendererProvider.Context context) {}
@Override
protected void renderSafe(CokeOvenBlockEntity be, float partialTicks, PoseStack ms, MultiBufferSource buffer, int light, int overlay) {
BlockState blockState = be.getBlockState();
if(be.getLevel().getBlockState(be.getBlockPos().relative(blockState.getValue(FACING))).is(TFMGBlocks.COKE_OVEN.get()))
if (be.getLevel() == null) return;
if (be.getLevel().getBlockState(be.getBlockPos().relative(blockState.getValue(FACING))).is(TFMGBlocks.COKE_OVEN.get()))
return;
renderDoors(be, ms, buffer);
renderFire(be, ms, buffer);
}
private void renderFire(CokeOvenBlockEntity be, PoseStack ms, MultiBufferSource buffer) {
BlockState state = be.getBlockState();
// Pick model variant based on controller type
PartialModel fireModel = switch (state.getValue(CONTROLLER_TYPE)) {
case TOP_ON -> TFMGPartialModels.COKE_OVEN_FIRE_TOP;
case MIDDLE_ON -> TFMGPartialModels.COKE_OVEN_FIRE_MIDDLE;
case BOTTOM_ON -> TFMGPartialModels.COKE_OVEN_FIRE_BOTTOM;
default -> null;
};
if (fireModel == null)
return;
ms.pushPose();
// Fullbright animated fire
CachedBuffers.partial(fireModel, state)
.light(LightTexture.FULL_BRIGHT)
.center()
.rotateYDegrees(state.getValue(FACING).getAxis() == Direction.Axis.Z ? Math.abs(state.getValue(FACING).toYRot()-180) : state.getValue(FACING).toYRot())
.uncenter()
.renderInto(ms, buffer.getBuffer(RenderType.translucent()));
ms.popPose();
}
private void renderDoors(CokeOvenBlockEntity be, PoseStack ms, MultiBufferSource buffer) {
BlockState state = be.getBlockState();
int lightInFront = LevelRenderer.getLightColor(be.getLevel(), be.getBlockPos().relative(be.getBlockState().getValue(FACING)));
PartialModel right_door = TFMGPartialModels.COKE_OVEN_DOOR_RIGHT;
PartialModel left_door = TFMGPartialModels.COKE_OVEN_DOOR_LEFT;
switch (blockState.getValue(CONTROLLER_TYPE)){
switch (state.getValue(CONTROLLER_TYPE)){
case TOP_ON -> {
right_door = TFMGPartialModels.COKE_OVEN_DOOR_RIGHT_TOP;
left_door = TFMGPartialModels.COKE_OVEN_DOOR_LEFT_TOP;
@@ -44,21 +89,24 @@ public class CokeOvenRenderer extends SafeBlockEntityRenderer<CokeOvenBlockEntit
case CASUAL -> {}
}
CachedBuffers.partial(right_door, blockState)
.light(lightInFront)
.center()
.rotateYDegrees(blockState.getValue(FACING).getAxis() == Direction.Axis.Z ? Math.abs(blockState.getValue(FACING).toYRot()-180) : blockState.getValue(FACING).toYRot())
.translateZ(-0.5f)
.translateX(-0.5f)
.rotateYDegrees(be.doorAngle.getValue())
.translateZ(0.5f)
.translateX(0.5f)
.uncenter()
.renderInto(ms, buffer.getBuffer(RenderType.cutoutMipped()));
CachedBuffers.partial(left_door, blockState)
.light(lightInFront)
ms.pushPose();
CachedBuffers.partial(right_door, state)
.light(lightInFront)
.center()
.rotateYDegrees(blockState.getValue(FACING).getAxis() == Direction.Axis.Z ? Math.abs(blockState.getValue(FACING).toYRot()-180) : blockState.getValue(FACING).toYRot())
.rotateYDegrees(state.getValue(FACING).getAxis() == Direction.Axis.Z ? Math.abs(state.getValue(FACING).toYRot()-180) : state.getValue(FACING).toYRot())
.translateZ(-0.5f)
.translateX(-0.5f)
.rotateYDegrees(be.doorAngle.getValue())
.translateZ(0.5f)
.translateX(0.5f)
.uncenter()
.renderInto(ms, buffer.getBuffer(RenderType.cutoutMipped()));
ms.popPose();
ms.pushPose();
CachedBuffers.partial(left_door, state)
.light(lightInFront)
.center()
.rotateYDegrees(state.getValue(FACING).getAxis() == Direction.Axis.Z ? Math.abs(state.getValue(FACING).toYRot()-180) : state.getValue(FACING).toYRot())
.translateZ(-0.5f)
.translateX(0.5f)
.rotateYDegrees(-be.doorAngle.getValue())
@@ -66,8 +114,7 @@ public class CokeOvenRenderer extends SafeBlockEntityRenderer<CokeOvenBlockEntit
.translateX(-0.5f)
.uncenter()
.renderInto(ms, buffer.getBuffer(RenderType.cutoutMipped()));
// ms.popPose();
ms.popPose();
}
}
}

View File

@@ -697,6 +697,7 @@ public class TFMGBlocks {
.item()
.transform(customItemModel())
.register();
@SuppressWarnings("'addLayer(java.util.function.Supplier<java.util.function.Supplier<net.minecraft.client.renderer.RenderType>>)' is deprecated and marked for removal ")
public static final BlockEntry<FireboxBlock> FIREBOX =
REGISTRATE.block("firebox", FireboxBlock::new)
@@ -781,16 +782,6 @@ public class TFMGBlocks {
.build()
.register();
public static final BlockEntry<Block> REINFORCED_FIREPROOF_BRICKS = REGISTRATE.block("reinforced_fireproof_bricks", Block::new)
.initialProperties(() -> Blocks.NETHER_BRICKS)
.properties(p -> p.requiresCorrectToolForDrops())
.transform(pickaxeOnly())
.tag(TFMGTags.TFMGBlockTags.REINFORCED_BLAST_FURNACE_WALL.tag)
.tag(BlockTags.NEEDS_STONE_TOOL)
.blockstate(simpleCubeAll("fireproof_bricks"))
.loot((lt, block) -> lt.dropOther(block, TFMGBlocks.FIREPROOF_BRICKS.get().asItem()))
.register();
public static final BlockEntry<Block> BLAST_FURNACE_REINFORCEMENT = REGISTRATE.block("blast_furnace_reinforcement", Block::new)
.initialProperties(() -> Blocks.IRON_BLOCK)
.properties(p -> p.requiresCorrectToolForDrops())
@@ -858,6 +849,7 @@ public class TFMGBlocks {
.item()
.transform(customItemModel())
.register();
@SuppressWarnings("'addLayer(java.util.function.Supplier<java.util.function.Supplier<net.minecraft.client.renderer.RenderType>>)' is deprecated and marked for removal ")
public static final BlockEntry<BlastStoveBlock> BLAST_STOVE =
REGISTRATE.block("blast_stove", BlastStoveBlock::new)

View File

@@ -25,6 +25,9 @@ public class TFMGPartialModels {
AIR_INTAKE_FRAME_CLOSED = block("air_intake/frame_closed"),
AIR_INTAKE_MEDIUM = block("air_intake/block_medium"),
AIR_INTAKE_LARGE = block("air_intake/block_large"),
COKE_OVEN_FIRE_BOTTOM = block("coke_oven/fire_bottom"),
COKE_OVEN_FIRE_MIDDLE = block("coke_oven/fire_middle"),
COKE_OVEN_FIRE_TOP = block("coke_oven/fire_top"),
COKE_OVEN_DOOR_LEFT = block("coke_oven/door_left"),
COKE_OVEN_DOOR_RIGHT = block("coke_oven/door_right"),
COKE_OVEN_DOOR_LEFT_BOTTOM = block("coke_oven/door_left_bottom"),
@@ -34,6 +37,7 @@ public class TFMGPartialModels {
COKE_OVEN_DOOR_LEFT_TOP = block("coke_oven/door_left_top"),
COKE_OVEN_DOOR_RIGHT_TOP = block("coke_oven/door_right_top"),
COAL_COKE_DUST_LAYER = block("coal_coke_dust_layer"),
COAL_COKE_DUST_LAYER_GLOWING = block("coal_coke_dust_layer_glowing"),
POLARIZER_DIAL = block("polarizer/dial"),
STEEL_FLYWHEEL = block("steel_flywheel/block"),
ALUMINUM_FLYWHEEL = block("aluminum_flywheel/block"),
@@ -169,4 +173,4 @@ public class TFMGPartialModels {
public static void init() {
}
}
}

View File

@@ -0,0 +1,20 @@
{
"credit": "Made with Blockbench",
"textures": {
"0": "tfmg:block/coal_coke_dust_glowing",
"particle": "tfmg:block/coal_coke_dust_glowing"
},
"elements": [
{
"from": [0, 0.1, 0],
"to": [16, 0.1, 16],
"faces": {
"north": {"uv": [0, 0, 16, 0], "texture": "#0"},
"east": {"uv": [0, 0, 16, 0], "texture": "#0"},
"south": {"uv": [0, 0, 16, 0], "texture": "#0"},
"west": {"uv": [0, 0, 16, 0], "texture": "#0"},
"up": {"uv": [0, 0, 16, 16], "texture": "#0"}
}
}
]
}

View File

@@ -0,0 +1,18 @@
{
"format_version": "1.21.6",
"credit": "Made with Blockbench",
"textures": {
"0": "tfmg:block/coke_oven/coke_oven_front_bottom_fire",
"particle": "tfmg:block/coke_oven/coke_oven_front_bottom_fire"
},
"elements": [
{
"from": [4, 4, -0.1],
"to": [12, 16, -0.1],
"rotation": {"angle": 0, "axis": "y", "origin": [14, 0, -2]},
"faces": {
"north": {"uv": [5, 0, 11, 12], "texture": "#0"}
}
}
]
}

View File

@@ -0,0 +1,18 @@
{
"format_version": "1.21.6",
"credit": "Made with Blockbench",
"textures": {
"0": "tfmg:block/coke_oven/coke_oven_front_middle_fire",
"particle": "tfmg:block/coke_oven/coke_oven_front_middle_fire"
},
"elements": [
{
"from": [4, 0, -0.1],
"to": [12, 16, -0.1],
"rotation": {"angle": 0, "axis": "y", "origin": [14, 0, -2]},
"faces": {
"north": {"uv": [5, 0, 11, 16], "texture": "#0"}
}
}
]
}

View File

@@ -0,0 +1,18 @@
{
"format_version": "1.21.6",
"credit": "Made with Blockbench",
"textures": {
"0": "tfmg:block/coke_oven/coke_oven_front_top_fire",
"particle": "tfmg:block/coke_oven/coke_oven_front_top_fire"
},
"elements": [
{
"from": [4, 0, -0.1],
"to": [12, 9, -0.1],
"rotation": {"angle": 0, "axis": "y", "origin": [14, 0, -2]},
"faces": {
"north": {"uv": [5, 7, 11, 16], "texture": "#0"}
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

View File

@@ -0,0 +1,6 @@
{
"animation": {
"frametime": 2,
"interpolate": true
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 B

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 B

View File

@@ -0,0 +1,6 @@
{
"animation": {
"frametime": 2,
"interpolate": true
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

View File

@@ -0,0 +1,6 @@
{
"animation": {
"frametime": 2,
"interpolate": true
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 B

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 542 B