diff --git a/src/generated/resources/assets/ae2/blockstates/crystal_resonance_generator.json b/src/generated/resources/assets/ae2/blockstates/crystal_resonance_generator.json new file mode 100644 index 00000000000..83b2f9a105f --- /dev/null +++ b/src/generated/resources/assets/ae2/blockstates/crystal_resonance_generator.json @@ -0,0 +1,31 @@ +{ + "variants": { + "facing=down": { + "model": "ae2:block/crystal_resonance_generator", + "x": 180 + }, + "facing=east": { + "model": "ae2:block/crystal_resonance_generator", + "x": 90, + "y": 90 + }, + "facing=north": { + "model": "ae2:block/crystal_resonance_generator", + "x": 90 + }, + "facing=south": { + "model": "ae2:block/crystal_resonance_generator", + "x": 90, + "y": 180 + }, + "facing=up": { + "model": "ae2:block/crystal_resonance_generator", + "x": 360 + }, + "facing=west": { + "model": "ae2:block/crystal_resonance_generator", + "x": 90, + "y": 270 + } + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/ae2/lang/en_us.json b/src/generated/resources/assets/ae2/lang/en_us.json index 5cb250969ed..0876cd8560c 100644 --- a/src/generated/resources/assets/ae2/lang/en_us.json +++ b/src/generated/resources/assets/ae2/lang/en_us.json @@ -127,6 +127,7 @@ "block.ae2.crafting_unit": "Crafting Unit", "block.ae2.crank": "Wooden Crank", "block.ae2.creative_energy_cell": "Creative Energy Cell", + "block.ae2.crystal_resonance_generator": "Crystal Resonance Generator", "block.ae2.cut_quartz_block": "Cut Certus Quartz Block", "block.ae2.cut_quartz_slab": "Cut Certus Quartz Slab", "block.ae2.cut_quartz_stairs": "Cut Certus Quartz Stairs", @@ -1003,5 +1004,6 @@ "waila.ae2.P2PUnlinked": "Unlinked", "waila.ae2.Showing": "Showing", "waila.ae2.Stored": "Stored: %s / %s", + "waila.ae2.Suppressed": "Suppressed", "waila.ae2.Unlocked": "Unlocked" } \ No newline at end of file diff --git a/src/generated/resources/assets/ae2/models/item/crystal_resonance_generator.json b/src/generated/resources/assets/ae2/models/item/crystal_resonance_generator.json new file mode 100644 index 00000000000..f637b637a28 --- /dev/null +++ b/src/generated/resources/assets/ae2/models/item/crystal_resonance_generator.json @@ -0,0 +1,3 @@ +{ + "parent": "ae2:block/crystal_resonance_generator" +} \ No newline at end of file diff --git a/src/generated/resources/data/ae2/advancements/recipes/misc/network/crystal_resonance_generator.json b/src/generated/resources/data/ae2/advancements/recipes/misc/network/crystal_resonance_generator.json new file mode 100644 index 00000000000..e2d81f1a3f0 --- /dev/null +++ b/src/generated/resources/data/ae2/advancements/recipes/misc/network/crystal_resonance_generator.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_fluix_block": { + "conditions": { + "items": [ + { + "items": [ + "ae2:fluix_block" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "ae2:network/crystal_resonance_generator" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_the_recipe", + "has_fluix_block" + ] + ], + "rewards": { + "recipes": [ + "ae2:network/crystal_resonance_generator" + ] + } +} \ No newline at end of file diff --git a/src/generated/resources/data/ae2/loot_tables/blocks/crystal_resonance_generator.json b/src/generated/resources/data/ae2/loot_tables/blocks/crystal_resonance_generator.json new file mode 100644 index 00000000000..dec54d25501 --- /dev/null +++ b/src/generated/resources/data/ae2/loot_tables/blocks/crystal_resonance_generator.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "ae2:crystal_resonance_generator" + } + ], + "rolls": 1.0 + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/ae2/recipes/network/crystal_resonance_generator.json b/src/generated/resources/data/ae2/recipes/network/crystal_resonance_generator.json new file mode 100644 index 00000000000..44cab648637 --- /dev/null +++ b/src/generated/resources/data/ae2/recipes/network/crystal_resonance_generator.json @@ -0,0 +1,26 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "c": { + "tag": "forge:ingots/copper" + }, + "f": { + "item": "ae2:fluix_block" + }, + "i": { + "tag": "forge:ingots/iron" + }, + "q": { + "item": "ae2:charged_certus_quartz_crystal" + } + }, + "pattern": [ + "cfc", + "cqc", + "iii" + ], + "result": { + "item": "ae2:crystal_resonance_generator" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/blocks/mineable/pickaxe.json b/src/generated/resources/data/minecraft/tags/blocks/mineable/pickaxe.json index b3a071667cc..a477dea6b08 100644 --- a/src/generated/resources/data/minecraft/tags/blocks/mineable/pickaxe.json +++ b/src/generated/resources/data/minecraft/tags/blocks/mineable/pickaxe.json @@ -44,6 +44,7 @@ "ae2:io_port", "ae2:condenser", "ae2:energy_acceptor", + "ae2:crystal_resonance_generator", "ae2:vibration_chamber", "ae2:growth_accelerator", "ae2:energy_cell", diff --git a/src/main/java/appeng/api/ids/AEBlockIds.java b/src/main/java/appeng/api/ids/AEBlockIds.java index 5ec5c2ca097..b98863cca6d 100644 --- a/src/main/java/appeng/api/ids/AEBlockIds.java +++ b/src/main/java/appeng/api/ids/AEBlockIds.java @@ -71,6 +71,8 @@ public final class AEBlockIds { public static final ResourceLocation IO_PORT = id("io_port"); public static final ResourceLocation CONDENSER = id("condenser"); public static final ResourceLocation ENERGY_ACCEPTOR = id("energy_acceptor"); + public static final ResourceLocation CRYSTAL_RESONANCE_GENERATOR = id("crystal_resonance_generator"); + public static final ResourceLocation VIBRATION_CHAMBER = id("vibration_chamber"); public static final ResourceLocation GROWTH_ACCELERATOR = id("growth_accelerator"); public static final ResourceLocation ENERGY_CELL = id("energy_cell"); diff --git a/src/main/java/appeng/api/networking/energy/IPassiveEnergyGenerator.java b/src/main/java/appeng/api/networking/energy/IPassiveEnergyGenerator.java new file mode 100644 index 00000000000..4c0356f2a9b --- /dev/null +++ b/src/main/java/appeng/api/networking/energy/IPassiveEnergyGenerator.java @@ -0,0 +1,44 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013 AlgorithmX2 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package appeng.api.networking.energy; + +import appeng.api.networking.IGridNodeService; + +/** + * Passively provide power to the system. This happens at a constant rate, and is usually restricted to one generator + * per energy grid (which reaches past the normal grid due to quartz fiber). + */ +public interface IPassiveEnergyGenerator extends IGridNodeService { + + /** + * AE per tick that is generated by this passive generator. + */ + double getRate(); + + /** + * Set to true to indicate this generator is supressed by another on the same energy grid. + */ + void setSuppressed(boolean suppressed); + +} diff --git a/src/main/java/appeng/block/networking/CrystalResonanceGeneratorBlock.java b/src/main/java/appeng/block/networking/CrystalResonanceGeneratorBlock.java new file mode 100644 index 00000000000..fe37994661c --- /dev/null +++ b/src/main/java/appeng/block/networking/CrystalResonanceGeneratorBlock.java @@ -0,0 +1,177 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.block.networking; + +import java.util.Locale; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition.Builder; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import appeng.api.orientation.IOrientationStrategy; +import appeng.api.orientation.OrientationStrategies; +import appeng.api.orientation.RelativeSide; +import appeng.block.AEBaseEntityBlock; +import appeng.blockentity.networking.CrystalResonanceGeneratorBlockEntity; + +public class CrystalResonanceGeneratorBlock extends AEBaseEntityBlock + implements SimpleWaterloggedBlock { + + public enum State implements StringRepresentable { + OFF, ON, HAS_CHANNEL; + + @Override + public String getSerializedName() { + return this.name().toLowerCase(Locale.ROOT); + } + } + + private static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + + public CrystalResonanceGeneratorBlock() { + super(glassProps().noOcclusion().forceSolidOn()); + this.registerDefaultState(this.defaultBlockState().setValue(WATERLOGGED, false)); + } + + @Override + protected void createBlockStateDefinition(Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return getVoxelShape(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + return getVoxelShape(state); + } + + @NotNull + private VoxelShape getVoxelShape(BlockState state) { + var orientation = getOrientation(state); + var forward = orientation.getSide(RelativeSide.FRONT); + + double minX = 0; + double minY = 0; + double minZ = 0; + double maxX = 1; + double maxY = 1; + double maxZ = 1; + + switch (forward) { + case DOWN -> { + minZ = minX = 3.0 / 16.0; + maxZ = maxX = 13.0 / 16.0; + maxY = 1.0; + minY = 5.0 / 16.0; + } + case EAST -> { + minZ = minY = 3.0 / 16.0; + maxZ = maxY = 13.0 / 16.0; + maxX = 11.0 / 16.0; + minX = 0.0; + } + case NORTH -> { + minY = minX = 3.0 / 16.0; + maxY = maxX = 13.0 / 16.0; + maxZ = 1.0; + minZ = 5.0 / 16.0; + } + case SOUTH -> { + minY = minX = 3.0 / 16.0; + maxY = maxX = 13.0 / 16.0; + maxZ = 11.0 / 16.0; + minZ = 0.0; + } + case UP -> { + minZ = minX = 3.0 / 16.0; + maxZ = maxX = 13.0 / 16.0; + maxY = 11.0 / 16.0; + minY = 0.0; + } + case WEST -> { + minZ = minY = 3.0 / 16.0; + maxZ = maxY = 13.0 / 16.0; + maxX = 1.0; + minX = 5.0 / 16.0; + } + default -> { + } + } + + return Shapes.create(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + return true; + } + + @Override + public IOrientationStrategy getOrientationStrategy() { + return OrientationStrategies.facing(); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + var fluidState = context.getLevel().getFluidState(context.getClickedPos()); + return super.getStateForPlacement(context) + .setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER); + } + + @Override + public FluidState getFluidState(BlockState blockState) { + return blockState.getValue(WATERLOGGED).booleanValue() + ? Fluids.WATER.getSource(false) + : super.getFluidState(blockState); + } + + @Override + public BlockState updateShape(BlockState blockState, Direction facing, BlockState facingState, LevelAccessor level, + BlockPos currentPos, BlockPos facingPos) { + if (blockState.getValue(WATERLOGGED).booleanValue()) { + level.scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickDelay(level)); + } + + return super.updateShape(blockState, facing, facingState, level, currentPos, facingPos); + } + +} diff --git a/src/main/java/appeng/blockentity/networking/CrystalResonanceGeneratorBlockEntity.java b/src/main/java/appeng/blockentity/networking/CrystalResonanceGeneratorBlockEntity.java new file mode 100644 index 00000000000..77a01a2f4b5 --- /dev/null +++ b/src/main/java/appeng/blockentity/networking/CrystalResonanceGeneratorBlockEntity.java @@ -0,0 +1,101 @@ +/* + * This file is part of Applied Energistics 2. + * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. + * + * Applied Energistics 2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Applied Energistics 2 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Applied Energistics 2. If not, see . + */ + +package appeng.blockentity.networking; + +import java.util.EnumSet; +import java.util.Set; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import appeng.api.networking.energy.IPassiveEnergyGenerator; +import appeng.api.orientation.BlockOrientation; +import appeng.api.orientation.RelativeSide; +import appeng.api.util.AECableType; +import appeng.blockentity.grid.AENetworkBlockEntity; +import appeng.core.AEConfig; + +public class CrystalResonanceGeneratorBlockEntity extends AENetworkBlockEntity { + // This needs to be synchronized to allow visual indication / Jade tooltips + private boolean suppressed; + + public CrystalResonanceGeneratorBlockEntity(BlockEntityType blockEntityType, BlockPos pos, + BlockState blockState) { + super(blockEntityType, pos, blockState); + // we're supposed to be *generating* power, so no consumption + getMainNode().setIdlePowerUsage(0); + getMainNode().addService(IPassiveEnergyGenerator.class, new IPassiveEnergyGenerator() { + @Override + public double getRate() { + return AEConfig.instance().getCrystalResonanceGeneratorRate(); + } + + @Override + public void setSuppressed(boolean suppressed) { + if (suppressed != CrystalResonanceGeneratorBlockEntity.this.suppressed) { + CrystalResonanceGeneratorBlockEntity.this.suppressed = suppressed; + markForUpdate(); + } + } + }); + } + + public boolean isSuppressed() { + return suppressed; + } + + @Override + protected boolean readFromStream(FriendlyByteBuf data) { + super.readFromStream(data); + this.suppressed = data.readBoolean(); + return false; + } + + @Override + protected void writeToStream(FriendlyByteBuf data) { + super.writeToStream(data); + data.writeBoolean(this.suppressed); + } + + @Override + protected void saveVisualState(CompoundTag data) { + super.saveVisualState(data); + data.putBoolean("suppressed", this.suppressed); + } + + @Override + protected void loadVisualState(CompoundTag data) { + super.loadVisualState(data); + this.suppressed = data.getBoolean("suppressed"); + } + + @Override + public Set getGridConnectableSides(BlockOrientation orientation) { + return EnumSet.of(orientation.getSide(RelativeSide.BACK)); + } + + @Override + public AECableType getCableConnectionType(Direction dir) { + return AECableType.SMART; + } +} diff --git a/src/main/java/appeng/core/AEConfig.java b/src/main/java/appeng/core/AEConfig.java index ecc2ec52710..31d5f96eb48 100644 --- a/src/main/java/appeng/core/AEConfig.java +++ b/src/main/java/appeng/core/AEConfig.java @@ -300,6 +300,10 @@ public double getGridEnergyStoragePerNode() { return COMMON.gridEnergyStoragePerNode.get(); } + public double getCrystalResonanceGeneratorRate() { + return COMMON.crystalResonanceGeneratorRate.get(); + } + public void save() { } @@ -687,6 +691,7 @@ private static class CommonConfig { public final DoubleOption powerRatioForgeEnergy; public final DoubleOption powerUsageMultiplier; public final DoubleOption gridEnergyStoragePerNode; + public final DoubleOption crystalResonanceGeneratorRate; // Vibration Chamber public final DoubleOption vibrationChamberBaseEnergyPerFuelTick; @@ -782,6 +787,8 @@ public CommonConfig(ConfigSection root) { powerUsageMultiplier = PowerRatios.addDouble("UsageMultiplier", 1.0, 0.01, Double.MAX_VALUE); gridEnergyStoragePerNode = PowerRatios.addDouble("GridEnergyStoragePerNode", 25, 1, 1000000, "How much energy can the internal grid buffer storage per node attached to the grid."); + crystalResonanceGeneratorRate = PowerRatios.addDouble("CrystalResonanceGeneratorRate", 5, 0, 1000000, + "How much energy a crystal resonance generator generates per tick."); ConfigSection Condenser = root.subsection("Condenser"); condenserMatterBallsPower = Condenser.addInt("MatterBalls", 256); diff --git a/src/main/java/appeng/core/definitions/AEBlockEntities.java b/src/main/java/appeng/core/definitions/AEBlockEntities.java index 8b2cc502c7c..696d39566bf 100644 --- a/src/main/java/appeng/core/definitions/AEBlockEntities.java +++ b/src/main/java/appeng/core/definitions/AEBlockEntities.java @@ -57,6 +57,7 @@ import appeng.blockentity.networking.CableBusBlockEntity; import appeng.blockentity.networking.ControllerBlockEntity; import appeng.blockentity.networking.CreativeEnergyCellBlockEntity; +import appeng.blockentity.networking.CrystalResonanceGeneratorBlockEntity; import appeng.blockentity.networking.EnergyAcceptorBlockEntity; import appeng.blockentity.networking.EnergyCellBlockEntity; import appeng.blockentity.networking.WirelessAccessPointBlockEntity; @@ -118,6 +119,10 @@ public final class AEBlockEntities { CondenserBlockEntity::new, AEBlocks.CONDENSER); public static final BlockEntityType ENERGY_ACCEPTOR = create("energy_acceptor", EnergyAcceptorBlockEntity.class, EnergyAcceptorBlockEntity::new, AEBlocks.ENERGY_ACCEPTOR); + public static final BlockEntityType CRYSTAL_RESONANCE_GENERATOR = create( + "crystal_resonance_generator", + CrystalResonanceGeneratorBlockEntity.class, CrystalResonanceGeneratorBlockEntity::new, + AEBlocks.CRYSTAL_RESONANCE_GENERATOR); public static final BlockEntityType VIBRATION_CHAMBER = create("vibration_chamber", VibrationChamberBlockEntity.class, VibrationChamberBlockEntity::new, AEBlocks.VIBRATION_CHAMBER); public static final BlockEntityType GROWTH_ACCELERATOR = create( diff --git a/src/main/java/appeng/core/definitions/AEBlocks.java b/src/main/java/appeng/core/definitions/AEBlocks.java index 6fea68ed050..fcfe7a9ea3a 100644 --- a/src/main/java/appeng/core/definitions/AEBlocks.java +++ b/src/main/java/appeng/core/definitions/AEBlocks.java @@ -71,6 +71,7 @@ import appeng.block.networking.CableBusBlock; import appeng.block.networking.ControllerBlock; import appeng.block.networking.CreativeEnergyCellBlock; +import appeng.block.networking.CrystalResonanceGeneratorBlock; import appeng.block.networking.EnergyAcceptorBlock; import appeng.block.networking.EnergyCellBlock; import appeng.block.networking.EnergyCellBlockItem; @@ -174,6 +175,8 @@ public final class AEBlocks { public static final BlockDefinition IO_PORT = block("ME IO Port", AEBlockIds.IO_PORT, IOPortBlock::new); public static final BlockDefinition CONDENSER = block("Matter Condenser", AEBlockIds.CONDENSER, CondenserBlock::new); public static final BlockDefinition ENERGY_ACCEPTOR = block("Energy Acceptor", AEBlockIds.ENERGY_ACCEPTOR, EnergyAcceptorBlock::new); + public static final BlockDefinition CRYSTAL_RESONANCE_GENERATOR = block("Crystal Resonance Generator", AEBlockIds.CRYSTAL_RESONANCE_GENERATOR, CrystalResonanceGeneratorBlock::new); + public static final BlockDefinition VIBRATION_CHAMBER = block("Vibration Chamber", AEBlockIds.VIBRATION_CHAMBER, VibrationChamberBlock::new); public static final BlockDefinition GROWTH_ACCELERATOR = block("Growth Accelerator", AEBlockIds.GROWTH_ACCELERATOR, GrowthAcceleratorBlock::new); public static final BlockDefinition ENERGY_CELL = block("Energy Cell", AEBlockIds.ENERGY_CELL, () -> new EnergyCellBlock(200000, 800, 200), EnergyCellBlockItem::new); diff --git a/src/main/java/appeng/core/localization/InGameTooltip.java b/src/main/java/appeng/core/localization/InGameTooltip.java index b4bd1be4d5a..95b02da1e6c 100644 --- a/src/main/java/appeng/core/localization/InGameTooltip.java +++ b/src/main/java/appeng/core/localization/InGameTooltip.java @@ -48,6 +48,7 @@ public enum InGameTooltip implements LocalizationEnum { P2PUnlinked("Unlinked"), Showing("Showing"), Stored("Stored: %s / %s"), + Suppressed("Suppressed"), Unlocked("Unlocked"); private final String englishText; diff --git a/src/main/java/appeng/datagen/providers/models/BlockModelProvider.java b/src/main/java/appeng/datagen/providers/models/BlockModelProvider.java index d8d91a63be7..9aae4e60156 100644 --- a/src/main/java/appeng/datagen/providers/models/BlockModelProvider.java +++ b/src/main/java/appeng/datagen/providers/models/BlockModelProvider.java @@ -63,6 +63,7 @@ protected void registerStatesAndModels() { Variant.variant().with(VariantProperties.MODEL, inscriber.getLocation())) .with(createFacingSpinDispatch()); + crystalResonanceGenerator(); wirelessAccessPoint(); craftingMonitor(); quartzGrowthAccelerator(); @@ -173,6 +174,27 @@ private void craftingMonitor() { })); } + private void crystalResonanceGenerator() { + var builder = getVariantBuilder(AEBlocks.CRYSTAL_RESONANCE_GENERATOR.block()); + + var modelFile = models().getExistingFile(AppEng.makeId("block/crystal_resonance_generator")); + + for (var facing : Direction.values()) { + var rotation = BlockOrientation.get(facing, 0); + builder.partialState() + .with(BlockStateProperties.FACING, facing) + .setModels(ConfiguredModel.builder() + .modelFile(modelFile) + // The original is facing "up" while we generally assume models are facing north + // but this looks better as an item model + .rotationX(rotation.getAngleX() + 90) + .rotationY(rotation.getAngleY()) + .build()); + } + + simpleBlockItem(AEBlocks.CRYSTAL_RESONANCE_GENERATOR.block(), modelFile); + } + private void wirelessAccessPoint() { var builder = getMultipartBuilder(AEBlocks.WIRELESS_ACCESS_POINT.block()); diff --git a/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java b/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java index 7fd9654f2ae..c71ae4ce737 100644 --- a/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java +++ b/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java @@ -211,6 +211,16 @@ protected void buildRecipes(RecipeOutput consumer) { // recipes/network // ==================================================== + ShapedRecipeBuilder.shaped(RecipeCategory.MISC, AEBlocks.CRYSTAL_RESONANCE_GENERATOR) + .pattern("cfc") + .pattern("cqc") + .pattern("iii") + .define('i', ConventionTags.IRON_INGOT) + .define('f', AEBlocks.FLUIX_BLOCK) + .define('c', ConventionTags.COPPER_INGOT) + .define('q', AEItems.CERTUS_QUARTZ_CRYSTAL_CHARGED) + .unlockedBy(getHasName(AEBlocks.FLUIX_BLOCK), has(AEBlocks.FLUIX_BLOCK)) + .save(consumer, AppEng.makeId("network/crystal_resonance_generator")); ShapedRecipeBuilder.shaped(RecipeCategory.MISC, AEBlocks.WIRELESS_ACCESS_POINT) .pattern("a") .pattern("b") diff --git a/src/main/java/appeng/integration/modules/igtooltip/TooltipIds.java b/src/main/java/appeng/integration/modules/igtooltip/TooltipIds.java index 315b648939f..83ad7db9938 100644 --- a/src/main/java/appeng/integration/modules/igtooltip/TooltipIds.java +++ b/src/main/java/appeng/integration/modules/igtooltip/TooltipIds.java @@ -14,6 +14,7 @@ private TooltipIds() { public static final ResourceLocation CRAFTING_MONITOR = AppEng.makeId("crafting_monitor"); public static final ResourceLocation PATTERN_PROVIDER = AppEng.makeId("pattern_provider"); public static final ResourceLocation CHARGER = AppEng.makeId("charger"); + public static final ResourceLocation CRYSTAL_RESONANCE_GENERATOR = AppEng.makeId("crystal_resonance_generator"); public static final ResourceLocation PART_NAME = AppEng.makeId("part_name"); public static final ResourceLocation PART_ICON = AppEng.makeId("part_icon"); diff --git a/src/main/java/appeng/integration/modules/igtooltip/TooltipProviders.java b/src/main/java/appeng/integration/modules/igtooltip/TooltipProviders.java index e644aab5f02..ad0f5297708 100644 --- a/src/main/java/appeng/integration/modules/igtooltip/TooltipProviders.java +++ b/src/main/java/appeng/integration/modules/igtooltip/TooltipProviders.java @@ -17,15 +17,18 @@ import appeng.block.crafting.PatternProviderBlock; import appeng.block.misc.ChargerBlock; import appeng.block.networking.CableBusBlock; +import appeng.block.networking.CrystalResonanceGeneratorBlock; import appeng.blockentity.AEBaseBlockEntity; import appeng.blockentity.crafting.CraftingMonitorBlockEntity; import appeng.blockentity.crafting.PatternProviderBlockEntity; import appeng.blockentity.misc.ChargerBlockEntity; import appeng.blockentity.networking.CableBusBlockEntity; +import appeng.blockentity.networking.CrystalResonanceGeneratorBlockEntity; import appeng.core.AppEng; import appeng.helpers.patternprovider.PatternProviderLogicHost; import appeng.integration.modules.igtooltip.blocks.ChargerDataProvider; import appeng.integration.modules.igtooltip.blocks.CraftingMonitorDataProvider; +import appeng.integration.modules.igtooltip.blocks.CrystalResonanceGeneratorProvider; import appeng.integration.modules.igtooltip.blocks.GridNodeStateDataProvider; import appeng.integration.modules.igtooltip.blocks.PatternProviderDataProvider; import appeng.integration.modules.igtooltip.blocks.PowerStorageDataProvider; @@ -154,6 +157,11 @@ public void registerCommon(CommonRegistration registration) { @Override public void registerClient(ClientRegistration registration) { + registration.addBlockEntityBody( + CrystalResonanceGeneratorBlockEntity.class, + CrystalResonanceGeneratorBlock.class, + TooltipIds.CHARGER, + new CrystalResonanceGeneratorProvider()); registration.addBlockEntityBody( ChargerBlockEntity.class, ChargerBlock.class, diff --git a/src/main/java/appeng/integration/modules/igtooltip/blocks/CrystalResonanceGeneratorProvider.java b/src/main/java/appeng/integration/modules/igtooltip/blocks/CrystalResonanceGeneratorProvider.java new file mode 100644 index 00000000000..786fa6db656 --- /dev/null +++ b/src/main/java/appeng/integration/modules/igtooltip/blocks/CrystalResonanceGeneratorProvider.java @@ -0,0 +1,19 @@ +package appeng.integration.modules.igtooltip.blocks; + +import net.minecraft.ChatFormatting; + +import appeng.api.integrations.igtooltip.TooltipBuilder; +import appeng.api.integrations.igtooltip.TooltipContext; +import appeng.api.integrations.igtooltip.providers.BodyProvider; +import appeng.blockentity.networking.CrystalResonanceGeneratorBlockEntity; +import appeng.core.localization.InGameTooltip; + +public final class CrystalResonanceGeneratorProvider implements BodyProvider { + @Override + public void buildTooltip(CrystalResonanceGeneratorBlockEntity generator, TooltipContext context, + TooltipBuilder tooltip) { + if (generator.isSuppressed()) { + tooltip.addLine(InGameTooltip.Suppressed.text().withStyle(ChatFormatting.RED)); + } + } +} diff --git a/src/main/java/appeng/me/Grid.java b/src/main/java/appeng/me/Grid.java index d4cc4f7837d..ad4bed38282 100644 --- a/src/main/java/appeng/me/Grid.java +++ b/src/main/java/appeng/me/Grid.java @@ -201,34 +201,42 @@ void setPivot(GridNode pivot) { } public void onServerStartTick() { + if (this.pivot == null) { + return; + } + for (var gc : this.services.values()) { - if (this.pivot != null) { - gc.onServerStartTick(); - } + gc.onServerStartTick(); } } public void onLevelStartTick(Level level) { + if (this.pivot == null) { + return; + } + for (var gc : this.services.values()) { - if (this.pivot != null) { - gc.onLevelStartTick(level); - } + gc.onLevelStartTick(level); } } public void onLevelEndTick(Level level) { + if (this.pivot == null) { + return; + } + for (var gc : this.services.values()) { - if (this.pivot != null) { - gc.onLevelEndTick(level); - } + gc.onLevelEndTick(level); } } public void onServerEndTick() { + if (this.pivot == null) { + return; + } + for (var gc : this.services.values()) { - if (this.pivot != null) { - gc.onServerEndTick(); - } + gc.onServerEndTick(); } } diff --git a/src/main/java/appeng/me/service/EnergyOverlayGrid.java b/src/main/java/appeng/me/service/EnergyOverlayGrid.java index 6901813ac1b..599470868d4 100644 --- a/src/main/java/appeng/me/service/EnergyOverlayGrid.java +++ b/src/main/java/appeng/me/service/EnergyOverlayGrid.java @@ -4,9 +4,12 @@ import java.util.Comparator; import java.util.List; +import org.jetbrains.annotations.Nullable; + import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import appeng.api.networking.energy.IPassiveEnergyGenerator; import appeng.core.AELog; /** @@ -24,16 +27,32 @@ class EnergyOverlayGrid { final List energyServices; + /** + * Which passive energy generator is currently active. + */ + @Nullable + private IPassiveEnergyGenerator currentPassiveGenerator; + private EnergyOverlayGrid(List energyServices) { this.energyServices = energyServices; } void invalidate() { + currentPassiveGenerator = null; for (var service : energyServices) { service.overlayGrid = null; } } + @Nullable + public IPassiveEnergyGenerator getCurrentPassiveGenerator() { + return currentPassiveGenerator; + } + + public void setCurrentPassiveGenerator(@Nullable IPassiveEnergyGenerator currentPassiveGenerator) { + this.currentPassiveGenerator = currentPassiveGenerator; + } + /** * Build a new overlay energy grid by discovering all accessible {@linkplain EnergyService energy services} starting * with the given grid. diff --git a/src/main/java/appeng/me/service/EnergyService.java b/src/main/java/appeng/me/service/EnergyService.java index d6d81283519..d1921b275b3 100644 --- a/src/main/java/appeng/me/service/EnergyService.java +++ b/src/main/java/appeng/me/service/EnergyService.java @@ -19,11 +19,15 @@ package appeng.me.service; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.NavigableSet; +import java.util.Objects; +import java.util.Set; import java.util.SortedSet; import com.google.common.base.Preconditions; @@ -51,6 +55,7 @@ import appeng.api.networking.energy.IEnergyService; import appeng.api.networking.energy.IEnergyWatcher; import appeng.api.networking.energy.IEnergyWatcherNode; +import appeng.api.networking.energy.IPassiveEnergyGenerator; import appeng.api.networking.events.GridPowerIdleChange; import appeng.api.networking.events.GridPowerStatusChange; import appeng.api.networking.events.GridPowerStorageStateChanged; @@ -137,6 +142,11 @@ public class EnergyService implements IEnergyService, IGridServiceProvider { private final PathingService pgc; private double lastStoredPower = -1; + /** + * Passive generators available on this energy grid. + */ + private final Set passiveGenerators = Collections.newSetFromMap(new IdentityHashMap<>()); + /** * The overlay grid containing all the energy services of grids that may be connected by parts like * {@linkplain appeng.parts.networking.QuartzFiberPart quartz fibers}. @@ -174,8 +184,36 @@ public void storagePowerChangeHandler(GridPowerStorageStateChanged ev) { } } + @Override + public void onServerStartTick() { + // replenish the passive power generation reservoir for the overarching energy service + // we only allow one passive generator for the entire network + for (var passiveGenerator : passiveGenerators) { + var currentGenerator = getOverlayGrid().getCurrentPassiveGenerator(); + if (currentGenerator == passiveGenerator) { + passiveGenerator.setSuppressed(false); + } else if (currentGenerator == null || passiveGenerator.getRate() > currentGenerator.getRate()) { + if (currentGenerator != null) { + currentGenerator.setSuppressed(true); + } + getOverlayGrid().setCurrentPassiveGenerator(passiveGenerator); + passiveGenerator.setSuppressed(false); + } else { + // Strictly worse than the current generator + passiveGenerator.setSuppressed(true); + } + } + } + @Override public void onServerEndTick() { + // Inject the passive energy once per overlay grid and do it at the energy service that actually + // contains the passive generator. + var currentPassiveGenerator = getOverlayGrid().getCurrentPassiveGenerator(); + if (currentPassiveGenerator != null && passiveGenerators.contains(currentPassiveGenerator)) { + injectPower(currentPassiveGenerator.getRate(), Actionable.MODULATE); + } + if (!this.interests.isEmpty()) { final double oldPower = this.lastStoredPower; this.lastStoredPower = this.getStoredPower(); @@ -442,6 +480,17 @@ public void removeNode(IGridNode node) { final GridNode gridNode = (GridNode) node; this.drainPerTick -= gridNode.getPreviousDraw(); + // passive generation + var passiveGenerator = node.getService(IPassiveEnergyGenerator.class); + if (passiveGenerator != null) { + passiveGenerators.remove(passiveGenerator); + + var overlayGrid = getOverlayGrid(); + if (overlayGrid.getCurrentPassiveGenerator() == passiveGenerator) { + overlayGrid.setCurrentPassiveGenerator(null); + } + } + // power storage. var ps = node.getService(IAEPowerStorage.class); if (ps != null) { @@ -505,6 +554,12 @@ public void addNode(IGridNode node, @Nullable CompoundTag storedData) { gridNode.setPreviousDraw(node.getIdlePowerUsage()); this.drainPerTick += gridNode.getPreviousDraw(); + // passive generation + var passiveGenerator = node.getService(IPassiveEnergyGenerator.class); + if (passiveGenerator != null) { + passiveGenerators.add(passiveGenerator); + } + // power storage var ps = node.getService(IAEPowerStorage.class); if (ps != null) { @@ -560,11 +615,14 @@ public void invalidateOverlayEnergyGrid() { } private List getConnectedServices() { + return getOverlayGrid().energyServices; + } + + private EnergyOverlayGrid getOverlayGrid() { if (this.overlayGrid == null) { EnergyOverlayGrid.buildCache(this); } - - return this.overlayGrid.energyServices; + return Objects.requireNonNull(this.overlayGrid); } } diff --git a/src/main/resources/assets/ae2/models/block/crystal_resonance_generator.json b/src/main/resources/assets/ae2/models/block/crystal_resonance_generator.json new file mode 100644 index 00000000000..a854af077da --- /dev/null +++ b/src/main/resources/assets/ae2/models/block/crystal_resonance_generator.json @@ -0,0 +1,276 @@ +{ + "parent": "block/block", + "credit": "Made with Blockbench by Sea_Kerman", + "textures": { + "particle": "ae2:block/rtg_top", + "top": "ae2:block/rtg_top", + "bottom": "ae2:block/rtg_bottom", + "side": "ae2:block/rtg_side", + "core": "ae2:block/rtg_emissive" + }, + "elements": [ + { + "name": "Rod", + "from": [4, 4, 4], + "to": [12, 16, 12], + "faces": { + "north": { "uv": [4, 2, 12, 14], "texture": "#side" }, + "east": { "uv": [4, 2, 12, 14], "texture": "#side" }, + "south": { "uv": [4, 2, 12, 14], "texture": "#side" }, + "west": { "uv": [4, 2, 12, 14], "texture": "#side" }, + "up": { "uv": [2, 2, 10, 10], "texture": "#top", "cullface": "up" } + } + }, + { + "name": "RodInterior", + "from": [4, 4, 4], + "to": [12, 16, 12], + "faces": { + "north": { "uv": [8, 0, 16, 12], "texture": "#core" }, + "east": { "uv": [8, 0, 16, 12], "texture": "#core" }, + "south": { "uv": [8, 0, 16, 12], "texture": "#core" }, + "west": { "uv": [8, 0, 16, 12], "texture": "#core" }, + "up": { "uv": [0, 0, 8, 8], "texture": "#core", "cullface": "up" } + } + }, + { + "name": "Base", + "from": [2, 0, 2], + "to": [14, 4, 14], + "faces": { + "north": { "uv": [0, 12, 12, 16], "texture": "#top" }, + "east": { "uv": [0, 12, 12, 16], "texture": "#top" }, + "south": { "uv": [0, 12, 12, 16], "texture": "#top" }, + "west": { "uv": [0, 12, 12, 16], "texture": "#top" }, + "up": { "uv": [0, 0, 12, 12], "texture": "#top" }, + "down": { + "uv": [0, 0, 12, 12], + "texture": "#bottom", + "cullface": "down" + } + } + }, + { + "name": "Fin", + "from": [4, 1, 12], + "to": [5, 15, 16], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "east": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "south": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "south" + }, + "west": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "rotation": 90, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 90, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [11, 1, 12], + "to": [12, 15, 16], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "east": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "south": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "south" + }, + "west": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "rotation": 90, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 90, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [7.5, 1, 12], + "to": [8.5, 15, 16], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "east": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "south": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "south" + }, + "west": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "rotation": 90, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 90, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [12, 1, 11], + "to": [16, 15, 12], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "east": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "east" + }, + "south": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 180, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [12, 1, 4], + "to": [16, 15, 5], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "east": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "east" + }, + "south": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 180, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [12, 1, 7.5], + "to": [16, 15, 8.5], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "east": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "east" + }, + "south": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 180, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [11, 1, 0], + "to": [12, 15, 4], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "north" + }, + "east": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "west": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "rotation": 270, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 270, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [4, 1, 0], + "to": [5, 15, 4], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "north" + }, + "east": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "west": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "rotation": 270, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 270, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [7.5, 1, 0], + "to": [8.5, 15, 4], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "north" + }, + "east": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "west": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "up": { "uv": [12, 0, 16, 1], "rotation": 270, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "rotation": 270, "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [0, 1, 4], + "to": [4, 15, 5], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "south": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "west": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "west" + }, + "up": { "uv": [12, 0, 16, 1], "rotation": 180, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [0, 1, 11], + "to": [4, 15, 12], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "south": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "west": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "west" + }, + "up": { "uv": [12, 0, 16, 1], "rotation": 180, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "texture": "#top" } + } + }, + { + "name": "Fin", + "from": [0, 1, 7.5], + "to": [4, 15, 8.5], + "rotation": { "angle": 0, "axis": "y", "origin": [8, 0, 8] }, + "faces": { + "north": { "uv": [12, 0, 16, 14], "texture": "#top" }, + "south": { "uv": [16, 0, 12, 14], "texture": "#top" }, + "west": { + "uv": [15, 0, 16, 14], + "texture": "#top", + "cullface": "west" + }, + "up": { "uv": [12, 0, 16, 1], "rotation": 180, "texture": "#top" }, + "down": { "uv": [16, 13, 12, 14], "texture": "#top" } + } + } + ], + "display": { + "thirdperson_righthand": { + "rotation": [75, 45, 0], + "translation": [0, 2.5, 0], + "scale": [0.375, 0.375, 0.375] + } + }, + "groups": [ + 0, + 1, + 2, + { + "name": "Fins", + "origin": [0, 0, 0], + "color": 2, + "children": [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + } + ] +} diff --git a/src/main/resources/assets/ae2/textures/block/rtg_bottom.png b/src/main/resources/assets/ae2/textures/block/rtg_bottom.png new file mode 100644 index 00000000000..36f449a9d88 Binary files /dev/null and b/src/main/resources/assets/ae2/textures/block/rtg_bottom.png differ diff --git a/src/main/resources/assets/ae2/textures/block/rtg_emissive.png b/src/main/resources/assets/ae2/textures/block/rtg_emissive.png new file mode 100644 index 00000000000..460e6042635 Binary files /dev/null and b/src/main/resources/assets/ae2/textures/block/rtg_emissive.png differ diff --git a/src/main/resources/assets/ae2/textures/block/rtg_emissive.png.mcmeta b/src/main/resources/assets/ae2/textures/block/rtg_emissive.png.mcmeta new file mode 100644 index 00000000000..401c4e2bb46 --- /dev/null +++ b/src/main/resources/assets/ae2/textures/block/rtg_emissive.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "frametime": 5, + "interpolate": true + } +} \ No newline at end of file diff --git a/src/main/resources/assets/ae2/textures/block/rtg_side.png b/src/main/resources/assets/ae2/textures/block/rtg_side.png new file mode 100644 index 00000000000..8ad7075a2a5 Binary files /dev/null and b/src/main/resources/assets/ae2/textures/block/rtg_side.png differ diff --git a/src/main/resources/assets/ae2/textures/block/rtg_top.png b/src/main/resources/assets/ae2/textures/block/rtg_top.png new file mode 100644 index 00000000000..d0d2e3cd127 Binary files /dev/null and b/src/main/resources/assets/ae2/textures/block/rtg_top.png differ