Initial Commit.

Has the startings of a large, extensible multiblock library.
This commit is contained in:
Curle 2020-02-28 22:27:29 +00:00
commit 46e2dea59f
29 changed files with 2719 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@ -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

175
build.gradle Normal file
View File

@ -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'
}
}

View File

@ -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<Block> e) {
e.getRegistry().register(new WCoreStruct());
}
@SubscribeEvent
public static void onItemRegistry(final RegistryEvent.Register<Item> 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);
}
}
}

View File

@ -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() {
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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<Byte, BlockFacing> 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<Byte, BlockFacing>(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);
}
}

View File

@ -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<BlockFacingProperty> ALL_AND_NONE;
public static final EnumSet<BlockFacingProperty> FACES;
public static final EnumSet<BlockFacingProperty> ANGLES;
public static final EnumSet<BlockFacingProperty> OPPOSITES;
public static final EnumSet<BlockFacingProperty> CSHAPES;
public static final EnumSet<BlockFacingProperty> CORNERS;
public static final EnumSet<BlockFacingProperty> MISCELLANEA;
public static final EnumSet<BlockFacingProperty> PIPES;
public static final EnumSet<BlockFacingProperty> 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;
}

View File

@ -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<World, MultiblockWorldRegistry> 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);
}
}
}

View File

@ -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<MultiblockControllerBase> controllers;
private final Set<MultiblockControllerBase> dirtyControllers;
private final Set<MultiblockControllerBase> deadControllers;
private Set<IMultiblockPart> orphanedParts;
private final Set<IMultiblockPart> detachedParts;
private final HashMap<Long, Set<IMultiblockPart>> partsAwaitingChunkLoad;
private final Object partsACLMutex;
private final Object orphanedPartsMutex;
public MultiblockWorldRegistry(final World world) {
worldObj = world;
controllers = new HashSet<MultiblockControllerBase>();
dirtyControllers = new HashSet<MultiblockControllerBase>();
deadControllers = new HashSet<MultiblockControllerBase>();
detachedParts = new HashSet<IMultiblockPart>();
orphanedParts = new HashSet<IMultiblockPart>();
partsAwaitingChunkLoad = new HashMap<Long, Set<IMultiblockPart>>();
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<Set<MultiblockControllerBase>> mergePools = null;
if(orphanedParts.size() > 0) {
Set<IMultiblockPart> orphansToProcess = null;
synchronized (orphanedPartsMutex) {
if(orphanedParts.size() > 0) {
orphansToProcess = orphanedParts;
orphanedParts = new HashSet<IMultiblockPart>();
}
}
if(orphansToProcess != null && orphansToProcess.size() > 0) {
AbstractChunkProvider chunkProvider = this.worldObj.getChunkProvider();
Set<MultiblockControllerBase> 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<Set<MultiblockControllerBase>>();
boolean addedToPool = false;
List<Set<MultiblockControllerBase>> candidatePools = new ArrayList<Set<MultiblockControllerBase>>();
for(Set<MultiblockControllerBase> 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<MultiblockControllerBase> masterPool = candidatePools.get(0);
Set<MultiblockControllerBase> 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<MultiblockControllerBase> 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<IMultiblockPart> 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<IMultiblockPart> parts;
long chunkHash = Helper.chunkPosHash(worldLocation);
synchronized(partsACLMutex) {
if(!partsAwaitingChunkLoad.containsKey(chunkHash)) {
parts = new HashSet<IMultiblockPart>();
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<MultiblockControllerBase> getControllers() {
return Collections.unmodifiableSet(controllers);
}
private void addOrphanedPart(final IMultiblockPart part) {
synchronized (orphanedPartsMutex) {
orphanedParts.add(part);
}
}
private void addAllOrphanedParts(final Collection<? extends IMultiblockPart> 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;
}
}

View File

@ -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;
}

View File

@ -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<? extends MultiblockControllerBase> 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<MultiblockControllerBase> attachToControllers();
void assertDetached();
boolean hasMultiblockSaveData();
CompoundNBT getMultiblockSaveDate();
void onMultiblockDataAssimilated();
}

View File

@ -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);
}

View File

@ -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<IMultiblockPart> 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<IMultiblockPart>();
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<IMultiblockPart> partsToAcquire = new HashSet<IMultiblockPart>(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<IMultiblockPart> deadParts = new HashSet<IMultiblockPart>();
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<IMultiblockPart> checkForDisconnections() {
if(!needsChecked) return null;
if(this.isEmpty()) {
REGISTRY.addDeadController(WORLD, this);
return null;
}
TileEntity te;
AbstractChunkProvider chunkProvider = WORLD.getChunkProvider();
referenceCoord = null;
Set<IMultiblockPart> deadParts = new HashSet<IMultiblockPart>();
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<IMultiblockPart> partsToCheck = new LinkedList<IMultiblockPart>();
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<IMultiblockPart> removedParts = new HashSet<IMultiblockPart>();
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<IMultiblockPart> detachAllBlocks() {
if(WORLD == null) return new HashSet<IMultiblockPart>();
for(IMultiblockPart part : connectedParts) {
if(this.WORLD.isBlockLoaded(part.getWorldPosition()))
onDetachBlock(part);
}
Set<IMultiblockPart> detachedParts = connectedParts;
connectedParts = new HashSet<IMultiblockPart>();
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());
}
}

View File

@ -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<MultiblockControllerBase> attachToControllers() {
Set<MultiblockControllerBase> 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<MultiblockControllerBase>();
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<IMultiblockPart> parts = new ArrayList<IMultiblockPart>();
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);
}
}

View File

@ -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<? extends RectangularMultiblockControllerBase> 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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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 <here>
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"

View File

@ -0,0 +1,7 @@
{
"variants": {
"": {
"model": "engage:block/wcoreextern"
}
}
}

View File

@ -0,0 +1,4 @@
{
"block.engage.wcoreextern": "Warp Core",
"itemGroup.engage": "Engage!"
}

View File

@ -0,0 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "engage:block/wcoreextern"
}
}

View File

@ -0,0 +1,3 @@
{
"parent": "engage:block/wcoreextern"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

View File

@ -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."
}
}