commit 46e2dea59fe09bf8187e2b94d99bfc533fe7615c Author: Curle Date: Fri Feb 28 22:27:29 2020 +0000 Initial Commit. Has the startings of a large, extensible multiblock library. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d60b65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle +gradle* + +# other +eclipse +run + +# Files from Forge MDK +forge*changelog.txt + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..67b903c --- /dev/null +++ b/build.gradle @@ -0,0 +1,175 @@ +buildscript { + repositories { + maven { url = 'https://files.minecraftforge.net/maven' } + jcenter() + mavenCentral() + } + dependencies { + classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true + } +} +apply plugin: 'net.minecraftforge.gradle' +// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. +apply plugin: 'eclipse' +apply plugin: 'maven-publish' + +version = '1.15.2-0.1' +group = 'uk.gemwire.engage' // http://maven.apache.org/guides/mini/guide-naming-conventions.html +archivesBaseName = 'engage' + +sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. + +minecraft { + // The mappings can be changed at any time, and must be in the following format. + // snapshot_YYYYMMDD Snapshot are built nightly. + // stable_# Stables are built at the discretion of the MCP team. + // Use non-default mappings at your own risk. they may not always work. + // Simply re-run your setup task after changing the mappings to update your workspace. + mappings channel: 'snapshot', version: '20190719-1.14.3' + // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. + + // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + client { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + + mods { + examplemod { + source sourceSets.main + } + } + } + + server { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + + mods { + examplemod { + source sourceSets.main + } + } + } + + data { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' + + // Recommended logging level for the console + property 'forge.logging.console.level', 'debug' + + args '--mod', 'examplemod', '--all', '--output', file('src/generated/resources/') + + mods { + examplemod { + source sourceSets.main + } + } + } + } +} + +dependencies { + // Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed + // that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied. + // The userdev artifact is a special name and will get all sorts of transformations applied to it. + minecraft 'net.minecraftforge:forge:1.15.2-31.1.12' + + // You may put jars on which you depend on in ./libs or you may define them like so.. + // compile "some.group:artifact:version:classifier" + // compile "some.group:artifact:version" + + // Real examples + // compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env + // compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env + + // The 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime. + // provided 'com.mod-buildcraft:buildcraft:6.0.8:dev' + + // These dependencies get remapped to your current MCP mappings + // deobf 'com.mod-buildcraft:buildcraft:6.0.8:dev' + + // For more info... + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html + +} + +// Example for how to get properties into the manifest for reading by the runtime.. +jar { + manifest { + attributes([ + "Specification-Title": "examplemod", + "Specification-Vendor": "examplemodsareus", + "Specification-Version": "1", // We are version 1 of ourselves + "Implementation-Title": project.name, + "Implementation-Version": "${version}", + "Implementation-Vendor" :"examplemodsareus", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") + ]) + } +} + +// Example configuration to allow publishing using the maven-publish task +// This is the preferred method to reobfuscate your jar file +jar.finalizedBy('reobfJar') +// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing +//publish.dependsOn('reobfJar') + +publishing { + publications { + mavenJava(MavenPublication) { + artifact jar + } + } + repositories { + maven { + url "file:///${project.projectDir}/mcmodsrepo" + } + } +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} +build.dependsOn sourcesJar + +artifacts { + archives sourcesJar +} + +// Process resources on build +processResources { + // This will ensure that this task is redone when the versions change. + inputs.property 'version', project.version + + // Replace stuff in mods.toml, nothing else + from(sourceSets.main.resources.srcDirs) { + include 'META-INF/mods.toml' + + // Replace version + expand 'version':project.version + } + + // Copy everything else except the mods.toml + from(sourceSets.main.resources.srcDirs) { + exclude 'META-INF/mods.toml' + } +} \ No newline at end of file diff --git a/src/main/java/uk/gemwire/engage/Engage.java b/src/main/java/uk/gemwire/engage/Engage.java new file mode 100644 index 0000000..acf9cfd --- /dev/null +++ b/src/main/java/uk/gemwire/engage/Engage.java @@ -0,0 +1,83 @@ +package uk.gemwire.engage; + + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.world.chunk.IChunk; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.world.ChunkEvent; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import uk.gemwire.engage.blocks.Blocks; +import uk.gemwire.engage.blocks.util.WCoreStruct; +import uk.gemwire.engage.items.ItemProperties; +import uk.gemwire.engage.multiblocks.bts.MultiblockRegistry; + + +@Mod("engage") +public class Engage { + + public static final Logger LOGGER = LogManager.getLogger(); + + public static ModSetup SETUP = new ModSetup(); + + + public Engage() { + + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); + } + + private void setup(final FMLCommonSetupEvent e) { + + } + + + @Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD) + public static class RegistryEvents { + + @SubscribeEvent + public static void onBlockRegistry(final RegistryEvent.Register e) { + e.getRegistry().register(new WCoreStruct()); + } + + @SubscribeEvent + public static void onItemRegistry(final RegistryEvent.Register e) { + e.getRegistry().register(new BlockItem(Blocks.WCORESTRUCT, ItemProperties.BlockItemProperties).setRegistryName("wcoreextern")); + + } + + @SubscribeEvent + public void onChunkLoad(final ChunkEvent.Load e) { + IChunk chunk = e.getChunk(); + MultiblockRegistry.INSTANCE.onChunkLoaded(e.getWorld().getWorld(), chunk.getPos().x, chunk.getPos().z); + } + + @SubscribeEvent + public void onWorldUnload(final WorldEvent.Unload e) { + MultiblockRegistry.INSTANCE.onWorldUnloaded(e.getWorld().getWorld()); + } + + @SubscribeEvent + public void onWorldTick(final TickEvent.WorldTickEvent e) { + if(e.phase == TickEvent.Phase.START) + MultiblockRegistry.INSTANCE.tickStart(e.world); + } + + @OnlyIn(Dist.CLIENT) + @SubscribeEvent + public void onClientTick(final TickEvent.ClientTickEvent e) { + if(e.phase == TickEvent.Phase.START) + MultiblockRegistry.INSTANCE.tickStart(Minecraft.getInstance().world); + } + } +} diff --git a/src/main/java/uk/gemwire/engage/ModSetup.java b/src/main/java/uk/gemwire/engage/ModSetup.java new file mode 100644 index 0000000..79b7460 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/ModSetup.java @@ -0,0 +1,19 @@ +package uk.gemwire.engage; + +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import uk.gemwire.engage.blocks.Blocks; + +public class ModSetup { + + public ItemGroup tab = new ItemGroup("engage") { + @Override + public ItemStack createIcon() { + return new ItemStack(Blocks.WCORESTRUCT); + } + }; + + public void init() { + + } +} diff --git a/src/main/java/uk/gemwire/engage/blocks/Blocks.java b/src/main/java/uk/gemwire/engage/blocks/Blocks.java new file mode 100644 index 0000000..13fe7f1 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/blocks/Blocks.java @@ -0,0 +1,11 @@ +package uk.gemwire.engage.blocks; + +import net.minecraftforge.registries.ObjectHolder; +import uk.gemwire.engage.blocks.util.WCoreStruct; + +public class Blocks { + + @ObjectHolder("engage:wcoreextern") + public static WCoreStruct WCORESTRUCT; + +} diff --git a/src/main/java/uk/gemwire/engage/blocks/ModTileEntity.java b/src/main/java/uk/gemwire/engage/blocks/ModTileEntity.java new file mode 100644 index 0000000..b638ffe --- /dev/null +++ b/src/main/java/uk/gemwire/engage/blocks/ModTileEntity.java @@ -0,0 +1,115 @@ +package uk.gemwire.engage.blocks; + +import net.minecraft.block.BlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.play.server.SUpdateTileEntityPacket; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import uk.gemwire.engage.misc.Helper; + +import javax.annotation.Nullable; + +public abstract class ModTileEntity extends TileEntity { + + public ModTileEntity(TileEntityType type) { + super(type); + } + + public boolean isUsableByPlayer(PlayerEntity player) { + BlockPos position = this.getPos(); + if(world.getTileEntity(pos) != this) + return false; + + return player.getDistanceSq((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D) <= 64D; + + } + + public boolean canOpenGui(World world, BlockPos pos, BlockState state) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean showScreen(Screen screen) { + Minecraft.getInstance().displayGuiScreen(screen); + return true; + } + + public enum SyncReason { + FullSync, + NetworkUpdate + } + + + @Override + public void read(CompoundNBT data) { + super.read(data); + this.syncDataFrom(data, SyncReason.FullSync); + } + + @Override + public CompoundNBT write(CompoundNBT data) { + this.syncDataTo(super.write(data), SyncReason.FullSync); + return data; + } + + @Override + public void handleUpdateTag(CompoundNBT tag) { + super.read(tag); + this.syncDataFrom(tag, SyncReason.NetworkUpdate); + } + + @Override + public CompoundNBT getUpdateTag() { + CompoundNBT data = super.getUpdateTag(); + + this.syncDataTo(data, SyncReason.NetworkUpdate); + return data; + } + + @Override + public void onDataPacket(NetworkManager net, SUpdateTileEntityPacket pkt) { + this.syncDataFrom(getUpdatePacket().getNbtCompound(), SyncReason.NetworkUpdate); + } + + @Nullable + @Override + public SUpdateTileEntityPacket getUpdatePacket() { + CompoundNBT data = new CompoundNBT(); + + this.syncDataTo(data, SyncReason.NetworkUpdate); + return new SUpdateTileEntityPacket(this.getPos(), 0, data); + } + + protected abstract void syncDataFrom(CompoundNBT data, SyncReason reason); + + protected abstract void syncDataTo(CompoundNBT data, SyncReason reason); + + public void markChunkDirty() { + this.world.markChunkDirty(this.getPos(), this); + } + + public void callNeighborBlockChange() { + this.world.notifyNeighborsOfStateChange(this.getPos(), this.getBlockState().getBlock()); + } + + public void notifyBlockUpdate() { + Helper.notifyBlockUpdate(this.world, this.getPos(), null, null); + } + + public void notifyBlockUpdate(BlockState oldState, BlockState newState) { + Helper.notifyBlockUpdate(this.world, this.getPos(), oldState, newState); + } + + public void notifyTileEntityUpdate() { + this.markDirty(); + Helper.notifyBlockUpdate(this.world, this.getPos(), null, null); + } +} diff --git a/src/main/java/uk/gemwire/engage/blocks/util/WCoreStruct.java b/src/main/java/uk/gemwire/engage/blocks/util/WCoreStruct.java new file mode 100644 index 0000000..8fe3314 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/blocks/util/WCoreStruct.java @@ -0,0 +1,20 @@ +package uk.gemwire.engage.blocks.util; + +import net.minecraft.block.Block; +import net.minecraft.block.SoundType; +import net.minecraft.block.material.Material; + +public class WCoreStruct extends Block { + + public WCoreStruct() { + + super(Properties + .create(Material.IRON) + .sound(SoundType.STONE) + .hardnessAndResistance(2.0f) + .lightValue(0) + ); + + setRegistryName("wcoreextern"); + } +} diff --git a/src/main/java/uk/gemwire/engage/items/ItemProperties.java b/src/main/java/uk/gemwire/engage/items/ItemProperties.java new file mode 100644 index 0000000..43f3bb1 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/items/ItemProperties.java @@ -0,0 +1,10 @@ +package uk.gemwire.engage.items; + +import net.minecraft.item.Item; +import uk.gemwire.engage.Engage; + +public class ItemProperties { + + public static Item.Properties BlockItemProperties = new Item.Properties() + .group(Engage.SETUP.tab); +} diff --git a/src/main/java/uk/gemwire/engage/misc/Helper.java b/src/main/java/uk/gemwire/engage/misc/Helper.java new file mode 100644 index 0000000..86550ee --- /dev/null +++ b/src/main/java/uk/gemwire/engage/misc/Helper.java @@ -0,0 +1,38 @@ +package uk.gemwire.engage.misc; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; + +public class Helper { + + public static int blockToChunkX(int blockX) { + return blockX >> 4; + } + + public static int blockToChunkX(BlockPos pos) { + return pos.getX() >> 4; + } + public static int blockToChunkZ(int blockZ) { + return blockZ >> 4; + } + public static int blockToChunkZ(BlockPos pos) { + return pos.getZ() >> 4; + } + + public static void notifyBlockUpdate(World world, BlockPos position, BlockState oldState, BlockState newState) { + + if(oldState == null) + oldState = world.getBlockState(position); + + if(newState == null) + newState = oldState; + + world.notifyBlockUpdate(position, oldState, newState, 3); + } + + public static long chunkPosHash(BlockPos pos) { + return ChunkPos.asLong(pos.getX() >> 4, pos.getZ() >> 4); + } +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/bts/BlockFacing.java b/src/main/java/uk/gemwire/engage/multiblocks/bts/BlockFacing.java new file mode 100644 index 0000000..3d86bc0 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/bts/BlockFacing.java @@ -0,0 +1,241 @@ +package uk.gemwire.engage.multiblocks.bts; + +import net.minecraft.block.BlockState; +import net.minecraft.state.BooleanProperty; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; + +import java.util.HashMap; + +public final class BlockFacing { + + private byte value; + private static HashMap cache; + + public static final BlockFacing NONE; + public static final BlockFacing ALL; + public static final BlockFacing DOWN; + public static final BlockFacing UP; + public static final BlockFacing NORTH; + public static final BlockFacing SOUTH; + public static final BlockFacing WEST; + public static final BlockFacing EAST; + + public static final BooleanProperty FACING_DOWN = BooleanProperty.create("downFacing"); + public static final BooleanProperty FACING_UP = BooleanProperty.create("upFacing"); + public static final BooleanProperty FACING_WEST = BooleanProperty.create("westFacing"); + public static final BooleanProperty FACING_EAST = BooleanProperty.create("eastFacing"); + public static final BooleanProperty FACING_NORTH = BooleanProperty.create("northFacing"); + public static final BooleanProperty FACING_SOUTH = BooleanProperty.create("southFacing"); + + static { + Byte hash; + + cache = new HashMap(8); + + hash = BlockFacing.computeHash(false, false, false, false, false, false); + cache.put(hash, NONE = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(true, true, true, true, true, true); + cache.put(hash, ALL = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(true, false, false, false, false, false); + cache.put(hash, DOWN = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(false, true, false, false, false, false); + cache.put(hash, UP = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(false, false, true, false, false, false); + cache.put(hash, NORTH = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(false, false, false, true, false, false); + cache.put(hash, SOUTH = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(false, false, false, false, true, false); + cache.put(hash, WEST = new BlockFacing(hash.byteValue())); + + hash = BlockFacing.computeHash(false, false, false, false, false, true); + cache.put(hash, EAST = new BlockFacing(hash.byteValue())); + + } + + public boolean isSet(Direction dir) { + return (this.value & (1 << dir.getIndex())) != 0; + } + + public boolean none() { + return this.value == 0; + } + + public boolean all() { + return this.value == 0x3f; + } + + public boolean down() { + return this.isSet(Direction.DOWN); + } + + public boolean up() { + return this.isSet(Direction.UP); + } + + public boolean north() { + return this.isSet(Direction.NORTH); + } + + public boolean south() { + return this.isSet(Direction.SOUTH); + } + + public boolean west() { + return this.isSet(Direction.WEST); + } + + public boolean east() { + return this.isSet(Direction.EAST); + } + + public BlockState toBlockState(BlockState state) { + return state.with(FACING_DOWN, this.isSet(Direction.DOWN)) + .with(FACING_UP, this.isSet(Direction.UP)) + .with(FACING_WEST, this.isSet(Direction.WEST)) + .with(FACING_EAST, this.isSet(Direction.EAST)) + .with(FACING_NORTH, this.isSet(Direction.NORTH)) + .with(FACING_SOUTH, this.isSet(Direction.SOUTH)); + } + + public BlockFacing set(Direction dir, boolean value) { + byte newHash = this.value; + if(value) + newHash |= (1 << dir.getIndex()); + else + newHash &= (~1 << dir.getIndex()); + + return BlockFacing.from(Byte.valueOf(newHash)); + } + + public int countFacesIf(boolean areSet) { + int checkFor = areSet ? 1 : 0; + int mask = this.value; + int faces = 0; + + for(int i = 0; i < 6; ++i, mask = mask >>> 1) { + if((mask & 1) == checkFor) + ++faces; + } + + return faces; + } + + public BlockFacingProperty toProperty() { + BlockFacingProperty[] values = BlockFacingProperty.values(); + + for(int i = 0; i < values.length; ++i) { + if (values[i].hash == this.value) + return values[i]; + } + + return BlockFacingProperty.None; + } + + public BlockPos offsetBlockPos(BlockPos origPos) { + int x = 0, y = 0, z = 0; + + for(Direction direction : Direction.values()) { + if(this.isSet(direction)) { + x += direction.getXOffset(); + y += direction.getYOffset(); + z += direction.getZOffset(); + } + } + + return origPos.add(x, y, z); + } + + public Direction firstIf(boolean isSet) { + for(Direction dir : Direction.values()) { + if(this.isSet(dir) == isSet) + return dir; + } + + return null; + } + + public static BlockFacing from(boolean down, boolean up, boolean north, boolean south, boolean west, boolean east) { + return BlockFacing.from(BlockFacing.computeHash(down, up, north, south, west, east)); + } + + public static BlockFacing from(boolean[] facings) { + return BlockFacing.from(BlockFacing.computeHash(facings)); + } + + @Override + public String toString() { + + return String.format("Facings: %s%s%s%s%s%s", + this.isSet(Direction.DOWN) ? "DOWN " : "", + this.isSet(Direction.UP) ? "UP " : "", + this.isSet(Direction.NORTH) ? "NORTH " : "", + this.isSet(Direction.SOUTH) ? "SOUTH " : "", + this.isSet(Direction.WEST) ? "WEST " : "", + this.isSet(Direction.EAST) ? "EAST " : ""); + } + + static BlockFacing from(Byte hash) { + BlockFacing facing = BlockFacing.cache.get(hash); + + if(facing == null) { + facing = new BlockFacing(hash.byteValue()); + BlockFacing.cache.put(hash, facing); + } + + return facing; + } + + private BlockFacing(byte value) { + this.value = value; + } + + static Byte computeHash(boolean down, boolean up, boolean north, boolean south, boolean west, boolean east) { + + byte hash = 0; + + if (down) + hash |= (1 << Direction.DOWN.getIndex()); + + if (up) + hash |= (1 << Direction.UP.getIndex()); + + if (north) + hash |= (1 << Direction.NORTH.getIndex()); + + if (south) + hash |= (1 << Direction.SOUTH.getIndex()); + + if (west) + hash |= (1 << Direction.WEST.getIndex()); + + if (east) + hash |= (1 << Direction.EAST.getIndex()); + + return Byte.valueOf(hash); + } + + static Byte computeHash(boolean[] facings) { + + byte hash = 0; + int len = null == facings ? -1 : facings.length; + + if (len < 0 || len > Direction.values().length) + throw new IllegalArgumentException("Invalid length of facings array"); + + for (int i = 0; i < len; ++i) { + + if (facings[i]) + hash |= (1 << Direction.values()[1].getIndex()); + } + + return Byte.valueOf(hash); + } + +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/bts/BlockFacingProperty.java b/src/main/java/uk/gemwire/engage/multiblocks/bts/BlockFacingProperty.java new file mode 100644 index 0000000..3f043d6 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/bts/BlockFacingProperty.java @@ -0,0 +1,120 @@ +package uk.gemwire.engage.multiblocks.bts; + +import net.minecraft.state.EnumProperty; +import net.minecraft.util.IStringSerializable; + +import java.util.EnumSet; + +public enum BlockFacingProperty implements IStringSerializable { + + None(BlockFacing.computeHash(false, false, false, false, false, false)), + All(BlockFacing.computeHash(true, true, true, true, true, true)), + + Face_D(BlockFacing.computeHash(true, false, false, false, false, false)), + Face_E(BlockFacing.computeHash(false, false, false, false, false, true)), + Face_N(BlockFacing.computeHash(false, false, true, false, false, false)), + Face_S(BlockFacing.computeHash(false, false, false, true, false, false)), + Face_U(BlockFacing.computeHash(false, true, false, false, false, false)), + Face_W(BlockFacing.computeHash(false, false, false, false, true, false)), + + Angle_DE(BlockFacing.computeHash(true, false, false, false, false, true)), + Angle_DN(BlockFacing.computeHash(true, false, true, false, false, false)), + Angle_DS(BlockFacing.computeHash(true, false, false, true, false, false)), + Angle_DW(BlockFacing.computeHash(true, false, false, false, true, false)), + Angle_EN(BlockFacing.computeHash(false, false, true, false, false, true)), + Angle_ES(BlockFacing.computeHash(false, false, false, true, false, true)), + Angle_EU(BlockFacing.computeHash(false, true, false, false, false, true)), + Angle_NU(BlockFacing.computeHash(false, true, true, false, false, false)), + Angle_NW(BlockFacing.computeHash(false, false, true, false, true, false)), + Angle_SU(BlockFacing.computeHash(false, true, false, true, false, false)), + Angle_SW(BlockFacing.computeHash(false, false, false, true, true, false)), + Angle_UW(BlockFacing.computeHash(false, true, false, false, true, false)), + + Opposite_DU(BlockFacing.computeHash(true, true, false, false, false, false)), + Opposite_EW(BlockFacing.computeHash(false, false, false, false, true, true)), + Opposite_NS(BlockFacing.computeHash(false, false, true, true, false, false)), + + CShape_DEU(BlockFacing.computeHash(true, true, false, false, false, true)), + CShape_DEW(BlockFacing.computeHash(true, false, false, false, true, true)), + CShape_DNS(BlockFacing.computeHash(true, false, true, true, false, false)), + CShape_DNU(BlockFacing.computeHash(true, true, true, false, false, false)), + CShape_DSU(BlockFacing.computeHash(true, true, false, true, false, false)), + CShape_DUW(BlockFacing.computeHash(true, true, false, false, true, false)), + CShape_ENS(BlockFacing.computeHash(false, false, true, true, false, true)), + CShape_ENW(BlockFacing.computeHash(false, false, true, false, true, true)), + CShape_ESW(BlockFacing.computeHash(false, false, false, true, true, true)), + CShape_EUW(BlockFacing.computeHash(false, true, false, false, true, true)), + CShape_NSU(BlockFacing.computeHash(false, true, true, true, false, false)), + CShape_NSW(BlockFacing.computeHash(false, false, true, true, true, false)), + + Corner_DEN(BlockFacing.computeHash(true, false, true, false, false, true)), + Corner_DES(BlockFacing.computeHash(true, false, false, true, false, true)), + Corner_DNW(BlockFacing.computeHash(true, false, true, false, true, false)), + Corner_DSW(BlockFacing.computeHash(true, false, false, true, true, false)), + Corner_ENU(BlockFacing.computeHash(false, true, true, false, false, true)), + Corner_ESU(BlockFacing.computeHash(false, true, false, true, false, true)), + Corner_NUW(BlockFacing.computeHash(false, true, true, false, true, false)), + Corner_SUW(BlockFacing.computeHash(false, true, false, true, true, false)), + + Misc_DENS(BlockFacing.computeHash(true, false, true, true, false, true)), + Misc_DENU(BlockFacing.computeHash(true, true, true, false, false, true)), + Misc_DENW(BlockFacing.computeHash(true, false, true, false, true, true)), + Misc_DESU(BlockFacing.computeHash(true, true, false, true, false, true)), + Misc_DESW(BlockFacing.computeHash(true, false, false, true, true, true)), + Misc_DNSW(BlockFacing.computeHash(true, false, true, true, true, false)), + Misc_DNUW(BlockFacing.computeHash(true, true, true, false, true, false)), + Misc_DSUW(BlockFacing.computeHash(true, true, false, true, true, false)), + Misc_ENSU(BlockFacing.computeHash(false, true, true, true, false, true)), + Misc_ENUW(BlockFacing.computeHash(false, true, true, false, true, true)), + Misc_ESUW(BlockFacing.computeHash(false, true, false, true, true, true)), + Misc_NSUW(BlockFacing.computeHash(false, true, true, true, true, false)), + + Pipe_DEUW(BlockFacing.computeHash(true, true, false, false, true, true)), + Pipe_DNSU(BlockFacing.computeHash(true, true, true, true, false, false)), + Pipe_ENSW(BlockFacing.computeHash(false, false, true, true, true, true)), + + PipeEnd_DENSU(BlockFacing.computeHash(true, true, true, true, false, true)), + PipeEnd_DENSW(BlockFacing.computeHash(true, false, true, true, true, true)), + PipeEnd_DENUW(BlockFacing.computeHash(true, true, true, false, true, true)), + PipeEnd_DESUW(BlockFacing.computeHash(true, true, false, true, true, true)), + PipeEnd_DNSUW(BlockFacing.computeHash(true, true, true, true, true, false)), + PipeEnd_ENSUW(BlockFacing.computeHash(false, true, true, true, false, true)); + + public static final EnumProperty FACINGS = EnumProperty.create("facings", BlockFacingProperty.class); + + public static final EnumSet ALL_AND_NONE; + public static final EnumSet FACES; + public static final EnumSet ANGLES; + public static final EnumSet OPPOSITES; + public static final EnumSet CSHAPES; + public static final EnumSet CORNERS; + public static final EnumSet MISCELLANEA; + public static final EnumSet PIPES; + public static final EnumSet PIPEENDS; + + @Override + public String getName() { + return this.name; + } + + BlockFacingProperty(byte hash) { + this.hash = hash; + this.name = this.toString().toLowerCase(); + } + + static { + ALL_AND_NONE = EnumSet.of(All, None); + FACES = EnumSet.of(Face_D, Face_E, Face_N, Face_S, Face_U, Face_W); + ANGLES = EnumSet.of(Angle_DE, Angle_DN, Angle_DS, Angle_DW, Angle_EN, Angle_ES, Angle_EU, Angle_NU, Angle_NW, Angle_SU, Angle_SW, Angle_UW); + OPPOSITES = EnumSet.of(Opposite_DU, Opposite_EW, Opposite_NS); + CSHAPES = EnumSet.of(CShape_DEU, CShape_DEW, CShape_DNS, CShape_DNU, CShape_DSU, CShape_DUW, CShape_ENS, CShape_ENW, CShape_ESW, CShape_EUW, CShape_NSU, CShape_NSW); + CORNERS = EnumSet.of(Corner_DEN, Corner_DES, Corner_DNW, Corner_DSW, Corner_ENU, Corner_ESU, Corner_NUW, Corner_SUW); + MISCELLANEA = EnumSet.of(Misc_DENS, Misc_DENU, Misc_DENW, Misc_DESU, Misc_DESW, Misc_DNSW, Misc_DNUW, Misc_DSUW, Misc_ENSU, Misc_ENUW, Misc_ESUW, Misc_NSUW); + PIPES = EnumSet.of(Pipe_DEUW, Pipe_DNSU, Pipe_ENSW); + PIPEENDS = EnumSet.of(PipeEnd_DENSU, PipeEnd_DENSW, PipeEnd_DENUW, PipeEnd_DESUW, PipeEnd_DNSUW, PipeEnd_ENSUW); + } + + + final byte hash; + private final String name; +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/bts/MultiblockRegistry.java b/src/main/java/uk/gemwire/engage/multiblocks/bts/MultiblockRegistry.java new file mode 100644 index 0000000..713fc7b --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/bts/MultiblockRegistry.java @@ -0,0 +1,79 @@ +package uk.gemwire.engage.multiblocks.bts; + +import net.minecraft.world.World; +import uk.gemwire.engage.Engage; +import uk.gemwire.engage.multiblocks.iface.IMultiblockPart; +import uk.gemwire.engage.multiblocks.iface.IMultiblockRegistry; +import uk.gemwire.engage.multiblocks.te.MultiblockControllerBase; + +import java.util.HashMap; + +public class MultiblockRegistry implements IMultiblockRegistry { + + private HashMap registries; + public static final MultiblockRegistry INSTANCE = new MultiblockRegistry(); + + private MultiblockRegistry() { + this.registries = new HashMap<>(2); + } + + @Override + public void onPartAdded(final World world, final IMultiblockPart part) { + MultiblockWorldRegistry reg; + + if(this.registries.containsKey(world)) { + reg = this.registries.get(world); + } else { + reg = new MultiblockWorldRegistry(world); + this.registries.put(world, reg); + } + + reg.onPartAdded(part); + } + + @Override + public void onPartRemoved(final World world, final IMultiblockPart part) { + if(this.registries.containsKey(world)) + this.registries.get(world).onPartRemoved(part); + } + + @Override + public void addDeadController(final World world, final MultiblockControllerBase controller) { + if(this.registries.containsKey(world)) + this.registries.get(world).addDeadController(controller); + else + Engage.LOGGER.warn("Multiblock controller %d in world %d exists in an untracked world - assuming a glitch.", controller.hashCode(), world); + + } + + @Override + public void addDirtyController(final World world, final MultiblockControllerBase controller) { + if(this.registries.containsKey(world)) + this.registries.get(world).addDirtyController(controller); + else + throw new IllegalArgumentException("Tried to update a controller in a world we don't track"); + } + + public void tickStart(final World world) { + if(this.registries.containsKey(world)) { + final MultiblockWorldRegistry reg = this.registries.get(world); + + reg.processMultiblockChanges(); + reg.tickStart(); + } + } + + public void onChunkLoaded(final World world, final int chunkX, final int chunkZ) { + if(this.registries.containsKey(world)) + this.registries.get(world).onChunkLoaded(chunkX, chunkZ); + } + + public void onWorldUnloaded(final World world) { + if(this.registries.containsKey(world)) { + this.registries.get(world).onWorldUnloaded(); + this.registries.remove(world); + } + } + + +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/bts/MultiblockWorldRegistry.java b/src/main/java/uk/gemwire/engage/multiblocks/bts/MultiblockWorldRegistry.java new file mode 100644 index 0000000..19d822f --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/bts/MultiblockWorldRegistry.java @@ -0,0 +1,293 @@ +package uk.gemwire.engage.multiblocks.bts; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.AbstractChunkProvider; +import uk.gemwire.engage.Engage; +import uk.gemwire.engage.misc.Helper; +import uk.gemwire.engage.multiblocks.iface.IMultiblockPart; +import uk.gemwire.engage.multiblocks.te.MultiblockControllerBase; + +import java.util.*; + +final class MultiblockWorldRegistry { + + private World worldObj; + + private final Set controllers; + private final Set dirtyControllers; + private final Set deadControllers; + + private Set orphanedParts; + private final Set detachedParts; + + private final HashMap> partsAwaitingChunkLoad; + + private final Object partsACLMutex; + private final Object orphanedPartsMutex; + + + public MultiblockWorldRegistry(final World world) { + worldObj = world; + + controllers = new HashSet(); + dirtyControllers = new HashSet(); + deadControllers = new HashSet(); + + detachedParts = new HashSet(); + orphanedParts = new HashSet(); + + partsAwaitingChunkLoad = new HashMap>(); + + partsACLMutex = new Object(); + orphanedPartsMutex = new Object(); + } + + public void tickStart() { + if(controllers.size() > 0) { + for(MultiblockControllerBase controller : controllers) { + if(controller.WORLD == worldObj && controller.WORLD.isRemote == worldObj.isRemote) { + if(controller.isEmpty()) + deadControllers.add(controller); + else + controller.updateMultiblockEntity(); + } + } + } + } + + public void processMultiblockChanges() { + BlockPos coord; + + List> mergePools = null; + + if(orphanedParts.size() > 0) { + Set orphansToProcess = null; + + synchronized (orphanedPartsMutex) { + if(orphanedParts.size() > 0) { + orphansToProcess = orphanedParts; + orphanedParts = new HashSet(); + } + } + + if(orphansToProcess != null && orphansToProcess.size() > 0) { + AbstractChunkProvider chunkProvider = this.worldObj.getChunkProvider(); + Set compatibleControllers; + + for(IMultiblockPart orphan : orphansToProcess) { + coord = orphan.getWorldPosition(); + if(!this.worldObj.isBlockLoaded(coord)) continue; + + if(!orphan.isPartValid()) continue; + + if(this.getMultiblockPartFromWorld(worldObj, coord) != orphan) continue; + + compatibleControllers = orphan.attachToControllers(); + if(compatibleControllers == null) { + MultiblockControllerBase newController = orphan.createNewMultiblock(); + newController.attachBlock(orphan); + this.controllers.add(newController); + } else if(compatibleControllers.size() > 1) { + if(mergePools == null) mergePools = new ArrayList>(); + + boolean addedToPool = false; + List> candidatePools = new ArrayList>(); + + for(Set pool : mergePools) { + if(!Collections.disjoint(pool, compatibleControllers)) + candidatePools.add(pool); + } + + if(candidatePools.size() <= 0) + mergePools.add(compatibleControllers); + else if(candidatePools.size() == 1) + candidatePools.get(0).addAll(compatibleControllers); + else { + Set masterPool = candidatePools.get(0); + Set consumedPool; + + for(int i = 1; i < candidatePools.size(); i++) { + consumedPool = candidatePools.get(i); + masterPool.addAll(consumedPool); + mergePools.remove(consumedPool); + } + + masterPool.addAll(compatibleControllers); + } + } + } + } + } + + if(mergePools != null && mergePools.size() > 0) { + for(Set mergePool : mergePools) { + + MultiblockControllerBase master = null; + for(MultiblockControllerBase controller : mergePool) { + if(master == null || controller.shouldConsume(master)) { + master = controller; + } + } + + if(master == null) + Engage.LOGGER.error("Multiblock system checked a merge candidate with no controller - this is impossible."); + else { + addDirtyController(master); + for(MultiblockControllerBase controller : mergePool) { + if(controller != master) { + master.assimilate(controller); + addDeadController(controller); + addDirtyController(master); + } + } + } + } + } + + if(dirtyControllers.size() > 0) { + Set newlyDetachedParts = null; + for(MultiblockControllerBase controller : dirtyControllers) { + + newlyDetachedParts = controller.checkForDisconnections(); + + if(!controller.isEmpty()) { + controller.recalculateBoundingBox(); + controller.checkAssembled(); + } else + addDeadController(controller); + + if(newlyDetachedParts != null && newlyDetachedParts.size() > 0) + detachedParts.addAll(newlyDetachedParts); + } + + dirtyControllers.clear(); + } + + if(deadControllers.size() > 0) { + for(MultiblockControllerBase controller : deadControllers) { + + if(!controller.isEmpty()) { + Engage.LOGGER.error("Multiblock system killed a controller that still has parts. Forcing it to eat shit and die, because that's impossible."); + detachedParts.addAll(controller.detachAllBlocks()); + } + + this.controllers.remove(controller); + } + deadControllers.clear(); + } + + for(IMultiblockPart part : detachedParts) + part.assertDetached(); + + addAllOrphanedParts(detachedParts); + detachedParts.clear(); + + } + + + public void onPartAdded(final IMultiblockPart part) { + BlockPos worldLocation = part.getWorldPosition(); + + if(!this.worldObj.isBlockLoaded(worldLocation)) { + Set parts; + long chunkHash = Helper.chunkPosHash(worldLocation); + + synchronized(partsACLMutex) { + if(!partsAwaitingChunkLoad.containsKey(chunkHash)) { + parts = new HashSet(); + partsAwaitingChunkLoad.put(chunkHash, parts); + } else + parts = partsAwaitingChunkLoad.get(chunkHash); + + parts.add(part); + } + } else + addOrphanedPart(part); + } + + public void onPartRemoved(final IMultiblockPart part) { + final BlockPos loc = part.getWorldPosition(); + + if(loc != null) { + long chunkHash = Helper.chunkPosHash(loc); + + synchronized (partsACLMutex) { + if(partsAwaitingChunkLoad.containsKey(chunkHash)) { + partsAwaitingChunkLoad.get(chunkHash).remove(part); + if(partsAwaitingChunkLoad.get(chunkHash).size() <= 0) + partsAwaitingChunkLoad.remove(chunkHash); + } + } + } + + detachedParts.remove(part); + synchronized (orphanedPartsMutex) { + orphanedParts.remove(part); + } + + part.assertDetached(); + } + + public void onWorldUnloaded() { + controllers.clear(); + deadControllers.clear(); + dirtyControllers.clear(); + detachedParts.clear(); + + synchronized (partsACLMutex) { + partsAwaitingChunkLoad.clear(); + } + + synchronized (orphanedPartsMutex) { + orphanedParts.clear(); + } + + worldObj = null; + } + + public void onChunkLoaded(final int chunkX, final int chunkZ) { + final long chunkHash = ChunkPos.asLong(chunkX, chunkZ); + + synchronized (partsACLMutex) { + if(partsAwaitingChunkLoad.containsKey(chunkHash)) { + addAllOrphanedParts(partsAwaitingChunkLoad.get(chunkHash)); + partsAwaitingChunkLoad.remove(chunkHash); + } + } + + } + + public void addDeadController(MultiblockControllerBase controller) { + this.deadControllers.add(controller); + } + + public void addDirtyController(MultiblockControllerBase controller) { + this.dirtyControllers.add(controller); + } + + public Set getControllers() { + return Collections.unmodifiableSet(controllers); + } + + private void addOrphanedPart(final IMultiblockPart part) { + synchronized (orphanedPartsMutex) { + orphanedParts.add(part); + } + } + + private void addAllOrphanedParts(final Collection parts) { + synchronized (orphanedPartsMutex) { + orphanedParts.addAll(parts); + } + } + + protected IMultiblockPart getMultiblockPartFromWorld(final World world, final BlockPos pos) { + TileEntity te = world.getTileEntity(pos); + + return te instanceof IMultiblockPart ? (IMultiblockPart) te : null; + } + +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/bts/PartPosition.java b/src/main/java/uk/gemwire/engage/multiblocks/bts/PartPosition.java new file mode 100644 index 0000000..cc19d47 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/bts/PartPosition.java @@ -0,0 +1,61 @@ +package uk.gemwire.engage.multiblocks.bts; + +import net.minecraft.state.EnumProperty; +import net.minecraft.util.Direction; +import net.minecraft.util.IStringSerializable; + +public enum PartPosition implements IStringSerializable { + Unknown(null, Type.Unknown), + Interior(null, Type.Unknown), + FrameCorner(null, Type.Frame), + FrameEastWest(null, Type.Frame), + FrameSouthNorth(null, Type.Frame), + FrameUpDown(null, Type.Frame), + + TopFace(Direction.UP, Type.Face), + BottomFace(Direction.DOWN, Type.Face), + NorthFace(Direction.NORTH, Type.Face), + SouthFace(Direction.SOUTH, Type.Face), + EastFace(Direction.EAST, Type.Face), + WestFace(Direction.WEST, Type.Face); + + public enum Type { + Unknown, + Interior, + Frame, + Face + } + + public boolean isFace() { + return this.type == Type.Face; + } + + public boolean isFrame() { + return this.type == Type.Frame; + } + + public Direction getDir() { + return this.dir; + } + + public Type getType() { + return this.type; + } + + public static EnumProperty createProperty(String name) { + return EnumProperty.create(name, PartPosition.class); + } + + @Override + public String getName() { + return this.toString(); + } + + PartPosition(Direction dir, Type type) { + this.dir = dir; + this.type = type; + } + + private Direction dir; + private Type type; +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/iface/IMultiblockPart.java b/src/main/java/uk/gemwire/engage/multiblocks/iface/IMultiblockPart.java new file mode 100644 index 0000000..b28c90b --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/iface/IMultiblockPart.java @@ -0,0 +1,62 @@ +package uk.gemwire.engage.multiblocks.iface; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.util.math.BlockPos; +import uk.gemwire.engage.multiblocks.te.MultiblockControllerBase; + +import java.util.Set; + +public interface IMultiblockPart { + + boolean isConnected(); + + MultiblockControllerBase getMultiblockController(); + + BlockPos getWorldPosition(); + + boolean isPartValid(); + + void onAttached(MultiblockControllerBase newController); + + void onDetached(MultiblockControllerBase controller); + + void onOrphaned(MultiblockControllerBase controller, int oldMultiblockSize, int newMultiblockSize); + + MultiblockControllerBase createNewMultiblock(); + + Class getMultiblockControllerType(); + + void onAssimilated(MultiblockControllerBase newController); + + void setVisited(); + + void setUnvisited(); + + boolean isVisited(); + + void becomeMultiblockSaveDelegate(); + + void forfeitMultiblockSaveDelegate(); + + boolean isMultiblockSaveDelegate(); + + IMultiblockPart[] getNeighboringParts(); + + void onMachineAssembled(MultiblockControllerBase controller); + + void onMachineBroken(); + + void onMachineActivated(); + + void onMachineDeactivated(); + + Set attachToControllers(); + + void assertDetached(); + + boolean hasMultiblockSaveData(); + + CompoundNBT getMultiblockSaveDate(); + + void onMultiblockDataAssimilated(); +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/iface/IMultiblockRegistry.java b/src/main/java/uk/gemwire/engage/multiblocks/iface/IMultiblockRegistry.java new file mode 100644 index 0000000..c036bf8 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/iface/IMultiblockRegistry.java @@ -0,0 +1,15 @@ +package uk.gemwire.engage.multiblocks.iface; + +import net.minecraft.world.World; +import uk.gemwire.engage.multiblocks.te.MultiblockControllerBase; + +public interface IMultiblockRegistry { + + void onPartAdded(World world, IMultiblockPart part); + + void onPartRemoved(World world, IMultiblockPart part); + + void addDeadController(World world, MultiblockControllerBase controller); + + void addDirtyController(World world, MultiblockControllerBase controller); +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/te/MultiblockControllerBase.java b/src/main/java/uk/gemwire/engage/multiblocks/te/MultiblockControllerBase.java new file mode 100644 index 0000000..af8721f --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/te/MultiblockControllerBase.java @@ -0,0 +1,683 @@ +package uk.gemwire.engage.multiblocks.te; + +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.chunk.AbstractChunkProvider; +import net.minecraft.world.chunk.Chunk; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import uk.gemwire.engage.Engage; +import uk.gemwire.engage.blocks.ModTileEntity; +import uk.gemwire.engage.misc.Helper; +import uk.gemwire.engage.multiblocks.bts.MultiblockRegistry; +import uk.gemwire.engage.multiblocks.iface.IMultiblockPart; +import uk.gemwire.engage.multiblocks.iface.IMultiblockRegistry; +import uk.gemwire.engage.multiblocks.validation.IMultiblockValidator; +import uk.gemwire.engage.multiblocks.validation.ValidationError; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +public abstract class MultiblockControllerBase implements IMultiblockValidator { + + public final World WORLD; + + private static final IMultiblockRegistry REGISTRY; + + static { + REGISTRY = MultiblockRegistry.INSTANCE; + } + + protected enum AssembledState {Disassembled, Assembled, Paused} + protected AssembledState assembledState; + + // Every block that makes up this multiblock + protected HashSet connectedParts; + + // UID of the multiblock + private BlockPos referenceCoord; + + + // Rectangular bounding box + private BlockPos minCoord; + private BlockPos maxCoord; + + + // A flag to keep track of changes + private boolean needsChecked; + + private ValidationError lastValidationError; + + private boolean debugMode; + + protected MultiblockControllerBase(World world) { + + WORLD = world; + connectedParts = new HashSet(); + + referenceCoord = null; + assembledState = AssembledState.Disassembled; + + minCoord = null; + maxCoord = null; + + needsChecked = true; + lastValidationError = null; + + debugMode = false; + } + + public void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + + public boolean isDebugMode() { + return debugMode; + } + + public abstract void onAttachedPartWithMultiblockData(IMultiblockPart part, CompoundNBT data); + + public boolean hasBlock(BlockPos block) { + return connectedParts.contains(block); + } + + public void attachBlock(IMultiblockPart part) { + IMultiblockPart candidate; + + BlockPos coord = part.getWorldPosition(); + + Engage.LOGGER.warn("[%s] Multiblock Controller %s is double-adding part %d @ %s. If issues arise, try breaking and re-placing the block.", + (WORLD.isRemote ? "CLIENT" : "SERVER"), hashCode(), part.hashCode(), coord); + + part.onAttached(this); + this.onBlockAdded(part); + + if(part.hasMultiblockSaveData()) { + CompoundNBT savedData = part.getMultiblockSaveDate(); + onAttachedPartWithMultiblockData(part, savedData); + part.onMultiblockDataAssimilated(); + } + + if(this.referenceCoord == null) { + referenceCoord = coord; + part.becomeMultiblockSaveDelegate(); + } else if(coord.compareTo(referenceCoord) < 0) { + TileEntity te = this.WORLD.getTileEntity(referenceCoord); + + ((IMultiblockPart)te).forfeitMultiblockSaveDelegate(); + referenceCoord = coord; + part.becomeMultiblockSaveDelegate(); + } else { + part.forfeitMultiblockSaveDelegate(); + } + + BlockPos partPos = part.getWorldPosition(); + + int curX, curY, curZ; + int newX, newY, newZ; + int partCoord; + + if(this.minCoord != null) { + + curX = this.minCoord.getX(); + curY = this.minCoord.getY(); + curZ = this.minCoord.getZ(); + + partCoord = partPos.getX(); + newX = partCoord < curX ? partCoord : curX; + + partCoord = partPos.getY(); + newY = partCoord < curY ? partCoord : curY; + + partCoord = partPos.getZ(); + newZ = partCoord < curZ ? partCoord : curZ; + + if((newX != curX) || (newY != curY) || newZ != curZ) + this.minCoord = new BlockPos(newX, newY, newZ); + + } + + if(this.maxCoord != null) { + + curX = this.maxCoord.getX(); + curY = this.maxCoord.getY(); + curZ = this.maxCoord.getZ(); + + partCoord = partPos.getX(); + newX = partCoord < curX ? partCoord : curX; + + partCoord = partPos.getY(); + newY = partCoord < curY ? partCoord : curY; + + partCoord = partPos.getZ(); + newZ = partCoord < curZ ? partCoord : curZ; + + if((newX != curX) || (newY != curY) || newZ != curZ) + this.maxCoord = new BlockPos(newX, newY, newZ); + + } + + REGISTRY.addDirtyController(WORLD, this); + } + + protected abstract void onBlockAdded(IMultiblockPart part); + + protected abstract void onBlockRemoved(IMultiblockPart part); + + protected abstract void onMachineAssembled(); + + protected abstract void onMachineRestored(); + + protected abstract void onMachinePaused(); + + protected abstract void onMachineDisassembled(); + + private void onDetachBlock(IMultiblockPart part) { + part.onDetached(this); + this.onBlockRemoved(part); + + part.forfeitMultiblockSaveDelegate(); + + minCoord = maxCoord = null; + + if(referenceCoord != null && referenceCoord.equals(part.getWorldPosition())) { + referenceCoord = null; + } + + needsChecked = true; + } + + public void detachBlock(IMultiblockPart part, boolean causedByChunkLoading) { + if(causedByChunkLoading && assembledState == AssembledState.Assembled) { + this.assembledState = AssembledState.Paused; + this.onMachinePaused(); + } + + onDetachBlock(part); + + if(!connectedParts.remove(part)) { + BlockPos position = part.getWorldPosition(); + + Engage.LOGGER.warn("[%s] Block (%d) at %d, %d, %d is anomalous - if it causes issues, break it and replace it.", + (WORLD.isRemote ? "CLIENT" : "SERVER"), part.hashCode(), position.getX(), position.getY(), position.getZ()); + + } + + if(connectedParts.isEmpty()) { + REGISTRY.addDeadController(this.WORLD, this); + return; + } + + REGISTRY.addDirtyController(this.WORLD, this); + + if(referenceCoord == null) { + selectNewReferenceCoord(); + } + + + } + + protected abstract int getMinBlocksInFullMachine(); + + protected abstract int getMaxXSize(); + + protected abstract int getMaxZSize(); + + protected abstract int getMaxYSize(); + + + protected int getMinXSize() {return 1;} + + protected int getMinZSize() {return 1;} + + protected int getMinYSize() {return 1;} + + + + @Override + public ValidationError getLastError() { + return this.lastValidationError; + } + + @Override + public void setLastError(ValidationError error) { + if(error == null) + throw new IllegalArgumentException("Multiblock Controller was given an empty error"); + + this.lastValidationError = error; + } + + @Override + public void setLastError(String format, Object... params) { + this.lastValidationError = new ValidationError(format, params); + } + + protected abstract boolean isMachineWhole(IMultiblockValidator validatorCallback); + + public void checkAssembled() { + AssembledState oldState = this.assembledState; + + this.lastValidationError = null; + + if(this.isMachineWhole(this)) { + assembleMachine(oldState); + } else if(oldState == AssembledState.Assembled) { + disassembleMachine(); + } + } + + private void assembleMachine(AssembledState oldState) { + for(IMultiblockPart part : connectedParts) { + part.onMachineAssembled(this); + } + + this.assembledState = AssembledState.Assembled; + + if(oldState == AssembledState.Paused) { + onMachineRestored(); + } else { + onMachineAssembled(); + } + } + + private void disassembleMachine() { + for(IMultiblockPart part : connectedParts) { + part.onMachineBroken(); + } + + this.assembledState = AssembledState.Disassembled; + onMachineDisassembled(); + } + + public void assimilate(MultiblockControllerBase oldController) { + BlockPos oldControllerRefPos = oldController.getReferenceCoord(); + if(oldControllerRefPos != null && getReferenceCoord().compareTo(oldControllerRefPos) >= 0) + throw new IllegalArgumentException("Controller with higher reference tried to assimilate a controller with a lower reference."); + + TileEntity te; + Set partsToAcquire = new HashSet(oldController.connectedParts); + + oldController._onAssimilated(this); + + for(IMultiblockPart part : partsToAcquire) { + if(!part.isPartValid()) continue; + + connectedParts.add(part); + part.onAssimilated(this); + this.onBlockAdded(part); + } + + this.onAssimilate(oldController); + oldController.onAssimilated(this); + } + + private void _onAssimilated(MultiblockControllerBase otherController) { + if(referenceCoord != null) { + if(this.WORLD.isBlockLoaded(this.referenceCoord)) { + TileEntity te = this.WORLD.getTileEntity(this.referenceCoord); + if(te instanceof IMultiblockPart) { + ((IMultiblockPart)te).forfeitMultiblockSaveDelegate(); + } + } + this.referenceCoord = null; + } + + connectedParts.clear(); + } + + protected abstract void onAssimilate(MultiblockControllerBase assimilatedController); + + protected abstract void onAssimilated(MultiblockControllerBase asssimilatorController); + + public final void updateMultiblockEntity() { + if(connectedParts.isEmpty()) { + REGISTRY.addDeadController(this.WORLD, this); + return; + } + + if(this.assembledState != AssembledState.Assembled) { + return; + } + + if(WORLD.isRemote) { + updateClient(); + } else if(updateServer()) { + if(minCoord != null && maxCoord != null && + this.WORLD.isAreaLoaded(this.minCoord, this.maxCoord)) { + + int minChunkX = Helper.blockToChunkX(this.minCoord); + int minChunkZ = Helper.blockToChunkZ(this.minCoord); + int maxChunkX = Helper.blockToChunkX(this.maxCoord); + int maxChunkZ = Helper.blockToChunkZ(this.maxCoord); + + for(int x = minChunkX; x <= maxChunkX; x++) { + for(int z = minChunkZ; z <= maxChunkZ; z++) { + Chunk dirtyChunk = this.WORLD.getChunk(x, z); + dirtyChunk.setModified(true); + } + } + } + } + } + + protected abstract void updateClient(); + + protected abstract boolean updateServer(); + + protected abstract boolean isBlockGoodForFrame(World world, int x, int y, int z, IMultiblockValidator validatorCallback); + protected abstract boolean isBlockGoodForTop(World world, int x, int y, int z, IMultiblockValidator validatorCallback); + protected abstract boolean isBlockGoodForBottom(World world, int x, int y, int z, IMultiblockValidator validatorCallback); + protected abstract boolean isBlockGoodForSides(World world, int x, int y, int z, IMultiblockValidator validatorCallback); + protected abstract boolean isBlockGoodForInterior(World world, int x, int y, int z, IMultiblockValidator validatorCallback); + + + public BlockPos getReferenceCoord() { + if(referenceCoord == null) selectNewReferenceCoord(); + + return referenceCoord; + } + + public int getNumConnectedBlocks() { + return connectedParts.size(); + } + + public abstract void syncDataFrom(CompoundNBT data, ModTileEntity.SyncReason syncReason); + + public abstract void syncDataTo(CompoundNBT data, ModTileEntity.SyncReason syncReason); + + public void recalculateBoundingBox() { + + int minX, minY, minZ, maxX, maxY, maxZ; + int partCoord; + BlockPos partPos; + + minX = minY = minZ = Integer.MAX_VALUE; + maxX = maxY = maxZ = Integer.MIN_VALUE; + + for(IMultiblockPart part : this.connectedParts) { + + partPos = part.getWorldPosition(); + + partCoord = partPos.getX(); + + if(partCoord < minX) minX = partCoord; + if(partCoord > maxX) maxX = partCoord; + + partCoord = partPos.getY(); + if(partCoord < minY) minY = partCoord; + if(partCoord > maxY) maxY = partCoord; + + partCoord = partPos.getZ(); + if(partCoord < minZ) minZ = partCoord; + if(partCoord > maxZ) maxZ = partCoord; + } + + this.minCoord = new BlockPos(minX, minY, minZ); + this.maxCoord = new BlockPos(maxX, maxY, maxZ); + } + + public BlockPos getMinCoord() { + if(minCoord == null) recalculateBoundingBox(); + return minCoord; + } + + public BlockPos getMaxCoord() { + if(maxCoord == null) recalculateBoundingBox(); + return maxCoord; + } + + public boolean isEmpty() { + return connectedParts.isEmpty(); + } + + public boolean shouldConsume(MultiblockControllerBase otherController) { + if (!otherController.getClass().equals(getClass())) { + throw new IllegalArgumentException("Attempted to merge two different multiblocks - what?"); + } + + if (otherController == this) { + return false; + } + + + int res = _shouldConsume(otherController); + if (res < 0) return true; + else if (res > 0) return false; + else { + + Engage.LOGGER.warn("[%s] A multiblock controller tried to consume itself - assuming this means it wants to check for valid parts.", this.WORLD.isRemote ? "CLIENT" : "SERVER"); + + auditParts(); + otherController.auditParts(); + + res = _shouldConsume(otherController); + + if (res < 0) return true; + else if (res > 0) return false; + else { + Engage.LOGGER.error("Controller (%d): size (%d), parts (%d)", hashCode(), connectedParts.size(), this.getPartsListString()); + Engage.LOGGER.error("Other Controller (%d): size (%d), parts (%d)", otherController.hashCode(), otherController.connectedParts.size(), otherController.getPartsListString()); + throw new IllegalArgumentException("[" + (this.WORLD.isRemote ? "CLIENT" : "SERVER") + "] Two valid Multiblock controllers at the same location tried to assimilate each other - Something is wrong!"); + } + + } + + } + + private int _shouldConsume(MultiblockControllerBase otherController) { + BlockPos myCoord = getReferenceCoord(); + BlockPos theirCoord = otherController.getReferenceCoord(); + + if(theirCoord == null) return -1; + else return myCoord.compareTo(theirCoord); + } + + private String getPartsListString() { + StringBuilder builder = new StringBuilder(); + boolean first = true; + BlockPos partPos; + + for(IMultiblockPart part : connectedParts) { + if(!first) { + builder.append(", "); + } + partPos = part.getWorldPosition(); + builder.append(String.format("(%d: %d, %d, %d)", part.hashCode(), partPos.getX(), partPos.getY(), partPos.getZ())); + first = false; + } + return builder.toString(); + } + + private void auditParts() { + HashSet deadParts = new HashSet(); + + for(IMultiblockPart part : connectedParts) { + if(!part.isPartValid() || WORLD.getTileEntity(part.getWorldPosition()) != part) { + onDetachBlock(part); + deadParts.add(part); + } + } + + connectedParts.removeAll(deadParts); + Engage.LOGGER.warn("[%s] Multiblock controller found %d dead parts during an audit, leaving %d parts.", (WORLD.isRemote ? "CLIENT" : "SERVER"), deadParts.size(), connectedParts.size()); + } + + public Set checkForDisconnections() { + + if(!needsChecked) return null; + + if(this.isEmpty()) { + REGISTRY.addDeadController(WORLD, this); + return null; + } + + TileEntity te; + AbstractChunkProvider chunkProvider = WORLD.getChunkProvider(); + + referenceCoord = null; + + Set deadParts = new HashSet(); + + BlockPos position; + IMultiblockPart refPart = null; + + int originalSize = connectedParts.size(); + + for(IMultiblockPart part : connectedParts) { + position = part.getWorldPosition(); + + if ((!this.WORLD.isBlockLoaded(position)) || (!part.isPartValid()) || (WORLD.getTileEntity(position) != part)) { + deadParts.add(part); + onDetachBlock(part); + continue; + } + + part.setUnvisited(); + part.forfeitMultiblockSaveDelegate(); + + if(referenceCoord == null) { + referenceCoord = position; + refPart = part; + } else if(position.compareTo(referenceCoord) < 0) { + referenceCoord = position; + refPart = part; + } + + } + + connectedParts.removeAll(deadParts); + + deadParts.clear(); + + if(refPart == null || isEmpty()) { + needsChecked = false; + REGISTRY.addDeadController(WORLD, this); + return null; + } else { + refPart.becomeMultiblockSaveDelegate(); + } + + IMultiblockPart part; + LinkedList partsToCheck = new LinkedList(); + + IMultiblockPart[] nearbyParts = null; + int visitedParts = 0; + + partsToCheck.add(refPart); + + while(!partsToCheck.isEmpty()) { + part = partsToCheck.removeFirst(); + part.setVisited(); + visitedParts++; + + nearbyParts = part.getNeighboringParts(); + for (IMultiblockPart nearbyPart: nearbyParts) { + + if(nearbyPart.getMultiblockController() != this) { + continue; + } + + if(!nearbyPart.isVisited()) { + nearbyPart.setVisited(); + partsToCheck.add(nearbyPart); + } + } + } + + Set removedParts = new HashSet(); + + for(IMultiblockPart orphanCandidate : connectedParts) { + if(!orphanCandidate.isVisited()) { + deadParts.add(orphanCandidate); + orphanCandidate.onOrphaned(this, originalSize, visitedParts); + onDetachBlock(orphanCandidate); + removedParts.add(orphanCandidate); + } + } + + connectedParts.removeAll(deadParts); + + deadParts.clear(); + + if(referenceCoord == null) { + selectNewReferenceCoord(); + } + + needsChecked = false; + + return removedParts; + + } + + public Set detachAllBlocks() { + if(WORLD == null) return new HashSet(); + + for(IMultiblockPart part : connectedParts) { + if(this.WORLD.isBlockLoaded(part.getWorldPosition())) + onDetachBlock(part); + } + + Set detachedParts = connectedParts; + connectedParts = new HashSet(); + return detachedParts; + } + + public boolean isAssembled() { + return this.assembledState == AssembledState.Assembled; + } + + private void selectNewReferenceCoord() { + IMultiblockPart pickedPart = null; + BlockPos pos; + + referenceCoord = null; + + for(IMultiblockPart part : connectedParts) { + pos = part.getWorldPosition(); + if(!part.isPartValid() || !this.WORLD.isBlockLoaded(pos)) + continue; + + if(referenceCoord == null || referenceCoord.compareTo(pos) > 0) { + referenceCoord = pos; + pickedPart = part; + } + } + + if(pickedPart != null) { + pickedPart.becomeMultiblockSaveDelegate(); + } + } + + protected void markReferenceCoordForUpdate() { + BlockPos pos = this.getReferenceCoord(); + + if((this.WORLD != null) && (pos != null)) + Helper.notifyBlockUpdate(this.WORLD, pos, null, null); + } + + protected void markReferenceCoordDirty() { + + if(WORLD == null || WORLD.isRemote) return; + + BlockPos referenceCoord = this.getReferenceCoord(); + if(referenceCoord == null) return; + + TileEntity te = WORLD.getTileEntity(referenceCoord); + WORLD.markChunkDirty(referenceCoord, te); + } + + + @OnlyIn(Dist.CLIENT) + protected void markMultiblockForRenderUpdate() { + Minecraft.getInstance().worldRenderer.markBlockRangeForRenderUpdate(this.minCoord.getX(), this.minCoord.getY(), this.minCoord.getZ(), this.maxCoord.getX(), this.maxCoord.getY(), this.maxCoord.getZ()); + } + + + +} + + diff --git a/src/main/java/uk/gemwire/engage/multiblocks/te/MultiblockTileEntityBase.java b/src/main/java/uk/gemwire/engage/multiblocks/te/MultiblockTileEntityBase.java new file mode 100644 index 0000000..53a52da --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/te/MultiblockTileEntityBase.java @@ -0,0 +1,255 @@ +package uk.gemwire.engage.multiblocks.te; + +import net.minecraft.nbt.CompoundNBT; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import uk.gemwire.engage.Engage; +import uk.gemwire.engage.blocks.ModTileEntity; +import uk.gemwire.engage.multiblocks.bts.MultiblockRegistry; +import uk.gemwire.engage.multiblocks.iface.IMultiblockPart; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class MultiblockTileEntityBase extends ModTileEntity implements IMultiblockPart { + + private MultiblockControllerBase controller; + private boolean visited; + + private boolean saveMultiblockData; + private CompoundNBT cachedData; + private boolean paused; + + public MultiblockTileEntityBase(TileEntityType type) { + super(type); + controller = null; + visited = false; + saveMultiblockData = false; + paused = false; + cachedData = null; + } + + @Override + protected void syncDataFrom(CompoundNBT data, SyncReason reason) { + if(reason == SyncReason.FullSync) { + if(data.contains("multiblockData")) + this.cachedData = data.getCompound("multiblockData"); + } else { + if(data.contains("multiblockData")) { + CompoundNBT tag = data.getCompound("mutliblockData"); + + if(isConnected()) + getMultiblockController().syncDataFrom(tag, reason); + else + this.cachedData = tag; + } + } + } + + @Override + protected void syncDataTo(CompoundNBT data, SyncReason reason) { + if(reason == SyncReason.FullSync) { + if(isMultiblockSaveDelegate() && isConnected()) { + CompoundNBT multiblockData = new CompoundNBT(); + + this.controller.syncDataTo(multiblockData, reason); + data.put("multiblockData", multiblockData); + } + } else { + if(isMultiblockSaveDelegate() && isConnected()) { + CompoundNBT tag = new CompoundNBT(); + getMultiblockController().syncDataTo(tag, reason); + data.put("multiblockData", tag); + } + } + } + + @Override + public Set attachToControllers() { + Set controllers = null; + MultiblockControllerBase bestController = null; + + IMultiblockPart[] partsToCheck = getNeighboringParts(); + for(IMultiblockPart neighborPart : partsToCheck) { + if(neighborPart.isConnected()) { + MultiblockControllerBase candidate = neighborPart.getMultiblockController(); + if(!candidate.getClass().equals(this.getMultiblockControllerType())) + continue; + + if(controllers == null) { + controllers = new HashSet(); + bestController = candidate; + } else if(!controllers.contains(candidate) && candidate.shouldConsume(bestController)) + bestController = candidate; + } + } + + if(bestController != null) { + this.controller = bestController; + bestController.attachBlock(this); + } + + return controllers; + } + + @Override + public void assertDetached() { + if(this.controller != null) { + BlockPos coord = this.getWorldPosition(); + Engage.LOGGER.info("[assert] Multiblock part at (%d %d %d) thinks it should have detached from the main structure at (%d %d %d), but it hasn't. This will be corrected with the ESAD method.", coord.getX(), coord.getY(), coord.getZ(), controller.getReferenceCoord().getX(), controller.getReferenceCoord().getY(), controller.getReferenceCoord().getZ()); + this.controller = null; + } + } + + + @Override + public void onChunkUnloaded() { + super.onChunkUnloaded(); + detachSelf(true); + } + + @Override + public void validate() { + super.validate(); + MultiblockRegistry.INSTANCE.onPartAdded(this.world, this); + } + + @Override + public boolean hasMultiblockSaveData() { + return this.cachedData != null; + } + + @Override + public CompoundNBT getMultiblockSaveDate() { + return cachedData; + } + + @Override + public void onMultiblockDataAssimilated() { + this.cachedData = null; + } + + @Override + public abstract void onMachineAssembled(MultiblockControllerBase controller); + + @Override + public abstract void onMachineBroken(); + + @Override + public abstract void onMachineActivated(); + + @Override + public abstract void onMachineDeactivated(); + + @Override + public boolean isConnected() { + return (controller != null); + } + + @Override + public MultiblockControllerBase getMultiblockController() { + return controller; + } + + @Override + public void becomeMultiblockSaveDelegate() { + this.saveMultiblockData = true; + } + + @Override + public void forfeitMultiblockSaveDelegate() { + this.saveMultiblockData = false; + } + + @Override + public boolean isMultiblockSaveDelegate() { + return this.saveMultiblockData; + } + + @Override + public void setUnvisited() { + this.visited = false; + } + + @Override + public void setVisited() { + this.visited = true; + } + + @Override + public boolean isVisited() { + return visited; + } + + @Override + public void onAssimilated(MultiblockControllerBase newController) { + assert (this.controller != newController); + this.controller = newController; + } + + @Override + public void onAttached(MultiblockControllerBase newController) { + this.controller = newController; + } + + @Override + public void onDetached(MultiblockControllerBase controller) { + this.controller = null; + } + + @Override + public abstract MultiblockControllerBase createNewMultiblock(); + + @Override + public IMultiblockPart[] getNeighboringParts() { + TileEntity te; + List parts = new ArrayList(); + BlockPos neighborPos, partPos = this.getWorldPosition(); + + + for(Direction dir : Direction.values()) { + neighborPos = partPos.offset(dir); + te = this.world.getTileEntity(neighborPos); + if(te instanceof IMultiblockPart) + parts.add((IMultiblockPart)te); + } + + return parts.toArray(new IMultiblockPart[parts.size()]); + } + + @Override + public void onOrphaned(MultiblockControllerBase controller, int oldMultiblockSize, int newMultiblockSize) { + this.markDirty(); + world.markChunkDirty(this.getWorldPosition(), this); + } + + @Override + public BlockPos getWorldPosition() { + return this.pos; + } + + @Override + public boolean isPartValid() { + return !this.isRemoved(); + } + + public void notifyNeighborsOfBlockChange() { + world.notifyNeighborsOfStateChange(this.getWorldPosition(), this.getBlockState().getBlock()); + } + + protected void detachSelf(boolean chunkUnloading) { + if(this.controller != null) { + this.controller.detachBlock(this, chunkUnloading); + this.controller = null; + } + + MultiblockRegistry.INSTANCE.onPartRemoved(world, this); + } + + + +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/te/rectangular/RectangularMultiblockControllerBase.java b/src/main/java/uk/gemwire/engage/multiblocks/te/rectangular/RectangularMultiblockControllerBase.java new file mode 100644 index 0000000..499e26c --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/te/rectangular/RectangularMultiblockControllerBase.java @@ -0,0 +1,148 @@ +package uk.gemwire.engage.multiblocks.te.rectangular; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import uk.gemwire.engage.multiblocks.te.MultiblockControllerBase; +import uk.gemwire.engage.multiblocks.validation.IMultiblockValidator; +import uk.gemwire.engage.multiblocks.validation.ValidationError; + +public abstract class RectangularMultiblockControllerBase extends MultiblockControllerBase { + + protected RectangularMultiblockControllerBase(World world) { + super(world); + } + + @Override + protected boolean isMachineWhole(IMultiblockValidator validatorCallback) { + if(connectedParts.size() < getMinBlocksInFullMachine()) { + validatorCallback.setLastError(ValidationError.MULTIBLOCK_INCOMPLETE); + return false; + } + + BlockPos maxCoord = this.getMaxCoord(); + BlockPos minCoord = this.getMinCoord(); + + int minX = minCoord.getX(); + int minY = minCoord.getY(); + int minZ = minCoord.getZ(); + int maxX = maxCoord.getX(); + int maxY = maxCoord.getY(); + int maxZ = maxCoord.getZ(); + + int deltaX = maxX - minX + 1; + int deltaY = maxY - minY + 1; + int deltaZ = maxZ - minZ + 1; + + int maxXSize = this.getMaxXSize(); + int maxYSize = this.getMaxYSize(); + int maxZSize = this.getMaxZSize(); + int minXSize = this.getMinXSize(); + int minYSize = this.getMinYSize(); + int minZSize = this.getMinZSize(); + + if(maxXSize > 0 && deltaX > maxXSize) { validatorCallback.setLastError("engage:mblock.error.too_big", maxXSize, "x"); return false; } + if(maxYSize > 0 && deltaY > maxYSize) { validatorCallback.setLastError("engage:mblock.error.too_big", maxYSize, "y"); return false; } + if(maxZSize > 0 && deltaZ > maxZSize) { validatorCallback.setLastError("engage:mblock.error.too_big", maxZSize, "z"); return false; } + + if(deltaX < minXSize) { validatorCallback.setLastError("engage:mblock.error.too_small", minXSize, "x"); return false; } + if(deltaY < minYSize) { validatorCallback.setLastError("engage:mblock.error.too_small", minYSize, "y"); return false; } + if(deltaZ < minZSize) { validatorCallback.setLastError("engage:mblock.error.too_small", minZSize, "z"); return false; } + + TileEntity te; + RectangularMultiblockTileEntityBase part; + Class controllerClass = this.getClass(); + + int extremes; + boolean partValid; + + for(int x = minX; x <= maxX; x++) { + for(int y = minY; y <= maxY; y++) { + for(int z = minZ; z <= maxZ; z++){ + + te = this.WORLD.getTileEntity(new BlockPos(x, y, z)); + + if(te instanceof RectangularMultiblockTileEntityBase) { + part = (RectangularMultiblockTileEntityBase) te; + + if(!controllerClass.equals(part.getMultiblockControllerType())) { + validatorCallback.setLastError("engage:mblock.error.invalid_part", x, y, z); + return false; + } + } else + part = null; + + extremes = 0; + + if(x == minX) + extremes++; + if(y == minY) + extremes++; + if(z == minZ) + extremes++; + + if(x == maxX) + extremes++; + if(y == maxY) + extremes++; + if(z == maxZ) + extremes++; + + if(extremes >= 2) { + + partValid = part != null ? part.isGoodForFrame(validatorCallback) : this.isBlockGoodForFrame(this.WORLD, x, y, z, validatorCallback); + + if(!partValid) { + if(validatorCallback.getLastError() == null) + validatorCallback.setLastError("engage:mblock.error.invalid_frame_part", x, y, z); + + return false; + } + + } else if(extremes == 1) { + if(y == maxY) { + + partValid = part != null ? part.isGoodForTop(validatorCallback) : this.isBlockGoodForTop(this.WORLD, x, y, z, validatorCallback); + + if(!partValid) { + if(validatorCallback.getLastError() == null) + validatorCallback.setLastError("engage:mblock.error.invalid_top_part", x, y, z); + + return false; + } + } else if(y == minY) { + partValid = part != null ? part.isGoodForBottom(validatorCallback) : this.isBlockGoodForBottom(this.WORLD, x, y, z, validatorCallback); + + if(!partValid) { + if(validatorCallback.getLastError() == null) + validatorCallback.setLastError("engage:mblock.error.invalid_bottom_part", x, y, z); + + return false; + } + } else { + partValid = part != null ? part.isGoodForSides(validatorCallback) : this.isBlockGoodForSides(this.WORLD, x, y, z, validatorCallback); + + if(!partValid) { + if(validatorCallback.getLastError() == null) + validatorCallback.setLastError("engage:mblock.error.invalid_side_part", x, y, z); + + return false; + } + } + } else { + partValid = part != null ? part.isGoodForInterior(validatorCallback) : this.isBlockGoodForInterior(this.WORLD, x, y, z, validatorCallback); + + if(!partValid) { + if(validatorCallback.getLastError() == null) + validatorCallback.setLastError("engage:mblock.error.invalid_interior_part", x, y, z); + + return false; + } + } + } + } + } + + return true; + } +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/te/rectangular/RectangularMultiblockTileEntityBase.java b/src/main/java/uk/gemwire/engage/multiblocks/te/rectangular/RectangularMultiblockTileEntityBase.java new file mode 100644 index 0000000..3f0c22a --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/te/rectangular/RectangularMultiblockTileEntityBase.java @@ -0,0 +1,149 @@ +package uk.gemwire.engage.multiblocks.te.rectangular; + +import net.minecraft.tileentity.TileEntityType; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import uk.gemwire.engage.multiblocks.bts.BlockFacing; +import uk.gemwire.engage.multiblocks.bts.PartPosition; +import uk.gemwire.engage.multiblocks.te.MultiblockControllerBase; +import uk.gemwire.engage.multiblocks.te.MultiblockTileEntityBase; +import uk.gemwire.engage.multiblocks.validation.IMultiblockValidator; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class RectangularMultiblockTileEntityBase extends MultiblockTileEntityBase { + + + private PartPosition pos; + private BlockFacing facing; + + public RectangularMultiblockTileEntityBase(TileEntityType type) { + super(type); + // pos = PartPosition.Unknown; + // facing = BlockFacing.NONE; + } + + @Nonnull + public BlockFacing getOutDir() { + return facing; + } + + @Nonnull + public PartPosition getPartPosition() { + return pos; + } + + @Nullable + public Direction getOutwardDirection() { + Direction dir = this.pos != null ? this.pos.getDir() : null; + + if(dir == null) { + BlockFacing out = this.getOutDir(); + + if(!out.none() && out.countFacesIf(true) == 1) + dir = out.firstIf(true); + } + + return dir; + } + + @Nullable + public Direction getOutwardDirectionFromWorldPosition() { + BlockFacing facing = null; + MultiblockControllerBase controller = this.getMultiblockController(); + + if(controller != null) { + BlockPos position = this.getWorldPosition(); + BlockPos min = controller.getMinCoord(); + BlockPos max = controller.getMaxCoord(); + + int x = position.getX(), y = position.getY(), z = position.getZ(); + + facing = BlockFacing.from(min.getY() == y, max.getY() == y, min.getZ() == z, max.getZ() == z, min.getX() == x, max.getX() == x); + } + + return null != facing && !facing.none() && facing.countFacesIf(true) == 1 ? facing.firstIf(true) : null; + } + + + @Override + public void onAttached(MultiblockControllerBase newController) { + super.onAttached(newController); + recalculateOutwardsDirection(newController.getMinCoord(), newController.getMaxCoord()); + } + + @Override + public void onMachineAssembled(MultiblockControllerBase controller) { + recalculateOutwardsDirection(controller.getMinCoord(), controller.getMaxCoord()); + } + + @Override + public void onMachineBroken() { + pos = PartPosition.Unknown; + facing = BlockFacing.NONE; + } + + public void recalculateOutwardsDirection(BlockPos minCoord, BlockPos maxCoord) { + BlockPos myPos = this.getPos(); + + int myX = myPos.getX(); + int myY = myPos.getY(); + int myZ = myPos.getZ(); + + int facesMatching = 0; + + boolean downFacing = myY == minCoord.getY(); + boolean upFacing = myY == maxCoord.getY(); + boolean northFacing = myZ == minCoord.getZ(); + boolean southFacing = myZ == maxCoord.getZ(); + boolean westFacing = myX == minCoord.getX(); + boolean eastFacing = myX == maxCoord.getX(); + + this.facing = BlockFacing.from(downFacing, upFacing, northFacing, southFacing, westFacing, eastFacing); + + if(eastFacing || westFacing) + ++facesMatching; + if(upFacing || westFacing) + ++facesMatching; + if(southFacing || northFacing) + ++facesMatching; + + if(facesMatching < 1) // <= 0 + this.pos = PartPosition.Interior; + else if(facesMatching < 2) { //== 1 + if (eastFacing) + this.pos = PartPosition.EastFace; + else if (westFacing) + this.pos = PartPosition.WestFace; + else if (southFacing) + this.pos = PartPosition.SouthFace; + else if (northFacing) + this.pos = PartPosition.NorthFace; + else if (upFacing) + this.pos = PartPosition.TopFace; + else + this.pos = PartPosition.BottomFace; + } else if(facesMatching < 3) { // == 2 + if (!eastFacing && !westFacing) + this.pos = PartPosition.FrameEastWest; + else if (!southFacing && !northFacing) + this.pos = PartPosition.FrameSouthNorth; + else + this.pos = PartPosition.FrameUpDown; + } else + this.pos = PartPosition.FrameCorner; + + } + + public abstract boolean isGoodForFrame(IMultiblockValidator validatorCallback); + + public abstract boolean isGoodForSides(IMultiblockValidator validatorCallback); + + public abstract boolean isGoodForTop(IMultiblockValidator validatorCallback); + + public abstract boolean isGoodForBottom(IMultiblockValidator validatorCallback); + + public abstract boolean isGoodForInterior(IMultiblockValidator validatorCallback); + +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/validation/IMultiblockValidator.java b/src/main/java/uk/gemwire/engage/multiblocks/validation/IMultiblockValidator.java new file mode 100644 index 0000000..dfc72a6 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/validation/IMultiblockValidator.java @@ -0,0 +1,11 @@ +package uk.gemwire.engage.multiblocks.validation; + + +public interface IMultiblockValidator { + + ValidationError getLastError(); + + void setLastError(ValidationError error); + + void setLastError(String format, Object... params); +} diff --git a/src/main/java/uk/gemwire/engage/multiblocks/validation/ValidationError.java b/src/main/java/uk/gemwire/engage/multiblocks/validation/ValidationError.java new file mode 100644 index 0000000..e1c4b26 --- /dev/null +++ b/src/main/java/uk/gemwire/engage/multiblocks/validation/ValidationError.java @@ -0,0 +1,21 @@ +package uk.gemwire.engage.multiblocks.validation; + +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TranslationTextComponent; + +public class ValidationError { + + protected final String _resourceKey; + protected final Object[] _parameters; + + public static final ValidationError MULTIBLOCK_INCOMPLETE = new ValidationError("engage:multiblock.validation.incomplete"); + + public ValidationError(String format, Object... params) { + this._resourceKey = format; + this._parameters = params; + } + + public ITextComponent getChatMessage() { + return new TranslationTextComponent(this._resourceKey, _parameters); + } +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..40e76e4 --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,56 @@ +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader="javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the forge version +loaderVersion="[31,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +# A URL to refer people to when problems occur with this mod +issueTrackerURL="http://my.issue.tracker/" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId="engage" #mandatory +# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it +version="${version}" #mandatory + # A display name for the mod +displayName="Engage!" #mandatory +# A URL to query for updates for this mod. See the JSON update specification +updateJSONURL="http://myurl.me/" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +displayURL="http://example.com/" #optional +# A file name (in the root of the mod JAR) containing a logo for display +logoFile="engage.png" #optional +# A text field displayed in the mod UI +credits="gwdev" #optional +# A text field displayed in the mod UI +authors="gwdev" #optional +# The description text for the mod (multi line!) (#mandatory) +description=''' +This is a long form description of the mod. You can write whatever you want here + +Have some lorem ipsum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis lacinia magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed sagittis luctus odio eu tempus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque volutpat ligula eget lacus auctor sagittis. In hac habitasse platea dictumst. Nunc gravida elit vitae sem vehicula efficitur. Donec mattis ipsum et arcu lobortis, eleifend sagittis sem rutrum. Cras pharetra quam eget posuere fermentum. Sed id tincidunt justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit. +''' +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.engage]] #optional + # the modid of the dependency + modId="forge" #mandatory + # Does this dependency have to exist - if not, ordering below must be specified + mandatory=true #mandatory + # The version range of the dependency + versionRange="[31,)" #mandatory + # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory + ordering="NONE" + # Side this dependency is applied on - BOTH, CLIENT or SERVER + side="BOTH" +# Here's another dependency +[[dependencies.engage]] + modId="minecraft" + mandatory=true + versionRange="[1.15.2]" + ordering="NONE" + side="BOTH" diff --git a/src/main/resources/assets/engage/blockstates/wcoreextern.json b/src/main/resources/assets/engage/blockstates/wcoreextern.json new file mode 100644 index 0000000..7231fc9 --- /dev/null +++ b/src/main/resources/assets/engage/blockstates/wcoreextern.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "engage:block/wcoreextern" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/engage/lang/en_us.json b/src/main/resources/assets/engage/lang/en_us.json new file mode 100644 index 0000000..1a86267 --- /dev/null +++ b/src/main/resources/assets/engage/lang/en_us.json @@ -0,0 +1,4 @@ +{ + "block.engage.wcoreextern": "Warp Core", + "itemGroup.engage": "Engage!" +} \ No newline at end of file diff --git a/src/main/resources/assets/engage/models/block/wcoreextern.json b/src/main/resources/assets/engage/models/block/wcoreextern.json new file mode 100644 index 0000000..215772e --- /dev/null +++ b/src/main/resources/assets/engage/models/block/wcoreextern.json @@ -0,0 +1,6 @@ +{ + "parent": "block/cube_all", + "textures": { + "all": "engage:block/wcoreextern" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/engage/models/item/wcoreextern.json b/src/main/resources/assets/engage/models/item/wcoreextern.json new file mode 100644 index 0000000..4129f93 --- /dev/null +++ b/src/main/resources/assets/engage/models/item/wcoreextern.json @@ -0,0 +1,3 @@ +{ + "parent": "engage:block/wcoreextern" +} \ No newline at end of file diff --git a/src/main/resources/assets/engage/textures/block/wcoreextern.png b/src/main/resources/assets/engage/textures/block/wcoreextern.png new file mode 100644 index 0000000..87e19ff Binary files /dev/null and b/src/main/resources/assets/engage/textures/block/wcoreextern.png differ diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..40ae1db --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,7 @@ +{ + "pack": { + "description": "engage resources", + "pack_format": 5, + "_comment": "A pack_format of 5 requires json lang files and some texture changes from 1.15. Note: we require v5 pack meta for all mods." + } +}