diff --git a/README.md b/README.md index 99751236..255cb42e 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,13 @@ Originally created by TecTech authors, taken with permission. It's under MIT lic } ``` Replace `master-SNAPSHOT` with a commit hash or a tag name (if any) of your choice to prevent unexpected upstream changes. -2. Add `required-after:structurelib;` to your `@Mod(dependencies = "..")` tring. +2. Add `required-after:structurelib;` to your `@Mod(dependencies = "..")` tring. 3. Add `structurelib` to your `mcmod.info`. ## Contribution The project is developed using IDEA. Please manually configure the build to not delegate to gradle for textures to show up in dev. + +## Current license +Due to me adding in ItemStackMap, which was derived from code from CodeChicken's LGPL mod NEI. This mod is effectively considered +licensed under LGPL in its entirety since commit ebef584b8c518f8202cfe0984fb28a6d23e52b3d. diff --git a/build.gradle b/build.gradle index 656f439f..ec6cfc96 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,9 @@ -//version: 1644894948 +//version: 1659365110 /* -DO NOT CHANGE THIS FILE! - -Also, you may replace this file at any time if there is an update available. -Please check https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/build.gradle for updates. -*/ + DO NOT CHANGE THIS FILE! + Also, you may replace this file at any time if there is an update available. + Please check https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/build.gradle for updates. + */ import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation @@ -12,10 +11,17 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.internal.logging.text.StyledTextOutput.Style import org.gradle.internal.logging.text.StyledTextOutputFactory +import java.nio.file.Files +import java.nio.file.Paths import java.util.concurrent.TimeUnit +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream buildscript { repositories { + mavenCentral() + maven { name 'forge' url 'https://maven.minecraftforge.net' @@ -37,21 +43,29 @@ buildscript { classpath 'com.github.GTNewHorizons:ForgeGradle:1.2.7' } } - plugins { id 'java-library' id 'idea' id 'eclipse' id 'scala' id 'maven-publish' - id 'org.jetbrains.kotlin.jvm' version '1.5.30' apply false - id 'org.jetbrains.kotlin.kapt' version '1.5.30' apply false + id 'org.jetbrains.kotlin.jvm' version '1.5.30' apply false + id 'org.jetbrains.kotlin.kapt' version '1.5.30' apply false + id 'com.google.devtools.ksp' version '1.5.30-1.0.0' apply false id 'org.ajoberstar.grgit' version '4.1.1' id 'com.github.johnrengelman.shadow' version '4.0.4' - id 'com.palantir.git-version' version '0.13.0' apply false + id 'com.palantir.git-version' version '0.13.0' apply false id 'de.undercouch.download' version '5.0.1' - id 'com.github.gmazzo.buildconfig' version '3.0.3' apply false + id 'com.github.gmazzo.buildconfig' version '3.0.3' apply false + id "com.diffplug.spotless" version "6.7.2" } +verifySettingsGradle() + +dependencies { + implementation 'com.diffplug:blowdryer:1.6.0' +} + +apply plugin: 'com.diffplug.blowdryer' if (project.file('.git/HEAD').isFile()) { apply plugin: 'com.palantir.git-version' @@ -76,6 +90,7 @@ idea { downloadSources = true } } +apply from: Blowdryer.file('spotless.gradle') if(JavaVersion.current() != JavaVersion.VERSION_1_8) { throw new GradleException("This project requires Java 8, but it's running on " + JavaVersion.current()) @@ -102,8 +117,11 @@ checkPropertyExists("containsMixinsAndOrCoreModOnly") checkPropertyExists("usesShadowedDependencies") checkPropertyExists("developmentEnvironmentUserName") -boolean noPublishedSources = project.findProperty("noPublishedSources") ? project.noPublishedSources.toBoolean() : false - +boolean noPublishedSources = project.hasProperty("noPublishedSources") ? project.noPublishedSources.toBoolean() : false +boolean usesMixinDebug = project.hasProperty('usesMixinDebug') ?: project.usesMixins.toBoolean() +boolean forceEnableMixins = project.hasProperty('forceEnableMixins') ? project.forceEnableMixins.toBoolean() : false +String channel = project.hasProperty('channel') ? project.channel : 'stable' +String mappingsVersion = project.hasProperty('mappingsVersion') ? project.mappingsVersion : '12' String javaSourceDir = "src/main/java/" String scalaSourceDir = "src/main/scala/" String kotlinSourceDir = "src/main/kotlin/" @@ -186,10 +204,10 @@ try { } catch (Exception ignored) { out.style(Style.Failure).text( - 'This mod must be version controlled by Git AND the repository must provide at least one tag,\n' + - 'or the VERSION override must be set! ').style(Style.SuccessHeader).text('(Do NOT download from GitHub using the ZIP option, instead\n' + - 'clone the repository, see ').style(Style.Info).text('https://gtnh.miraheze.org/wiki/Development').style(Style.SuccessHeader).println(' for details.)' - ) + 'This mod must be version controlled by Git AND the repository must provide at least one tag,\n' + + 'or the VERSION override must be set! ').style(Style.SuccessHeader).text('(Do NOT download from GitHub using the ZIP option, instead\n' + + 'clone the repository, see ').style(Style.Info).text('https://gtnh.miraheze.org/wiki/Development').style(Style.SuccessHeader).println(' for details.)' + ) versionOverride = 'NO-GIT-TAG-SET' identifiedVersion = versionOverride } @@ -213,11 +231,11 @@ else { def arguments = [] def jvmArguments = [] -if (usesMixins.toBoolean()) { +if (usesMixins.toBoolean() || forceEnableMixins) { arguments += [ "--tweakClass org.spongepowered.asm.launch.MixinTweaker" ] - if (!project.hasProperty(usesMixinDebug) || usesMixinDebug.toBoolean()) { + if (usesMixinDebug.toBoolean()) { jvmArguments += [ "-Dmixin.debug.countInjections=true", "-Dmixin.debug.verbose=true", @@ -278,7 +296,7 @@ repositories { name 'Overmind forge repo mirror' url 'https://gregtech.overminddl1.com/' } - if(usesMixins.toBoolean()) { + if(usesMixins.toBoolean() || forceEnableMixins) { maven { name 'sponge' url 'https://repo.spongepowered.org/repository/maven-public' @@ -295,6 +313,8 @@ dependencies { annotationProcessor('com.google.guava:guava:24.1.1-jre') annotationProcessor('com.google.code.gson:gson:2.8.6') annotationProcessor('org.spongepowered:mixin:0.8-SNAPSHOT') + } + if(usesMixins.toBoolean() || forceEnableMixins) { // using 0.8 to workaround a issue in 0.7 which fails mixin application compile('com.github.GTNewHorizons:SpongePoweredMixin:0.7.12-GTNH') { // Mixin includes a lot of dependencies that are too up-to-date @@ -352,7 +372,10 @@ shadowJar { } minimize() // This will only allow shading for actually used classes - configurations = [project.configurations.shadowImplementation, project.configurations.shadowCompile] + configurations = [ + project.configurations.shadowImplementation, + project.configurations.shadowCompile + ] dependsOn(relocateShadowJar) } @@ -383,12 +406,12 @@ afterEvaluate { if(usesMixins.toBoolean()) { tasks.compileJava { options.compilerArgs += [ - "-AreobfSrgFile=${tasks.reobf.srg}", - "-AoutSrgFile=${mixinSrg}", - "-AoutRefMapFile=${refMap}", - // Elan: from what I understand they are just some linter configs so you get some warning on how to properly code - "-XDenableSunApiLintControl", - "-XDignore.symbol.file" + "-AreobfSrgFile=${tasks.reobf.srg}", + "-AoutSrgFile=${mixinSrg}", + "-AoutRefMapFile=${refMap}", + // Elan: from what I understand they are just some linter configs so you get some warning on how to properly code + "-XDenableSunApiLintControl", + "-XDignore.symbol.file" ] } } @@ -397,8 +420,8 @@ afterEvaluate { runClient { if(developmentEnvironmentUserName) { arguments += [ - "--username", - developmentEnvironmentUserName + "--username", + developmentEnvironmentUserName ] } @@ -416,13 +439,14 @@ tasks.withType(JavaExec).configureEach { javaToolchains.launcherFor { languageVersion = projectJavaVersion } - ) + ) } processResources { // this will ensure that this task is redone when the versions change. inputs.property "version", project.version inputs.property "mcversion", project.minecraft.version + exclude("spotless.gradle") // replace stuff in mcmod.info, nothing else from(sourceSets.main.resources.srcDirs) { @@ -430,9 +454,9 @@ processResources { // replace modVersion and minecraftVersion expand "minecraftVersion": project.minecraft.version, - "modVersion": modVersion, - "modId": modId, - "modName": modName + "modVersion": modVersion, + "modId": modId, + "modName": modName } if(usesMixins.toBoolean()) { @@ -442,6 +466,7 @@ processResources { // copy everything else that's not the mcmod.info from(sourceSets.main.resources.srcDirs) { exclude 'mcmod.info' + exclude 'spotless.gradle' } } @@ -461,9 +486,9 @@ def getManifestAttributes() { if(usesMixins.toBoolean()) { manifestAttributes += [ - "TweakClass" : "org.spongepowered.asm.launch.MixinTweaker", - "MixinConfigs" : "mixins." + modId + ".json", - "ForceLoadAsMod" : !containsMixinsAndOrCoreModOnly.toBoolean() + "TweakClass" : "org.spongepowered.asm.launch.MixinTweaker", + "MixinConfigs" : "mixins." + modId + ".json", + "ForceLoadAsMod" : !containsMixinsAndOrCoreModOnly.toBoolean() ] } return manifestAttributes @@ -490,7 +515,10 @@ task shadowDevJar(type: ShadowJar) { } minimize() // This will only allow shading for actually used classes - configurations = [project.configurations.shadowImplementation, project.configurations.shadowCompile] + configurations = [ + project.configurations.shadowImplementation, + project.configurations.shadowCompile + ] } task relocateShadowDevJar(type: ConfigureShadowRelocation) { @@ -567,7 +595,7 @@ publishing { artifact source: shadowJar, classifier: "" } if(!noPublishedSources) { - artifact source: sourcesJar, classifier: "src" + artifact source: sourcesJar, classifier: "sources" } artifact source: usesShadowedDependencies.toBoolean() ? shadowDevJar : devJar, classifier: "dev" if (apiPackage) { @@ -582,7 +610,10 @@ publishing { // remove extra garbage from minecraft and minecraftDeps configuration pom.withXml { def badArtifacts = [:].withDefault {[] as Set} - for (configuration in [projectConfigs.minecraft, projectConfigs.minecraftDeps]) { + for (configuration in [ + projectConfigs.minecraft, + projectConfigs.minecraftDeps + ]) { for (dependency in configuration.allDependencies) { badArtifacts[dependency.group == null ? "" : dependency.group] += dependency.name } @@ -616,7 +647,7 @@ task updateBuildScript { doLast { if (performBuildScriptUpdate(projectDir.toString())) return - print("Build script already up-to-date!") + print("Build script already up-to-date!") } } @@ -631,12 +662,26 @@ if (isNewBuildScriptVersionAvailable(projectDir.toString())) { static URL availableBuildScriptUrl() { new URL("https://raw.githubusercontent.com/GTNewHorizons/ExampleMod1.7.10/main/build.gradle") } +static URL exampleSettingsGradleUrl() { + new URL("https://raw.githubusercontent.com/GTNewHorizons/ExampleMod1.7.10/main/settings.gradle.example") +} + + +def verifySettingsGradle() { + def settingsFile = getFile("settings.gradle") + if (!settingsFile.exists()) { + println("Downloading default settings.gradle") + exampleSettingsGradleUrl().withInputStream { i -> settingsFile.withOutputStream { it << i } } + throw new GradleException("Settings.gradle has been updated, please re-run task.") + } +} boolean performBuildScriptUpdate(String projectDir) { if (isNewBuildScriptVersionAvailable(projectDir)) { def buildscriptFile = getFile("build.gradle") availableBuildScriptUrl().withInputStream { i -> buildscriptFile.withOutputStream { it << i } } out.style(Style.Success).print("Build script updated. Please REIMPORT the project or RESTART your IDE!") + verifySettingsGradle() return true } return false @@ -667,80 +712,225 @@ configure(updateBuildScript) { description = 'Updates the build script to the latest version' } -// Deobfuscation +// Parameter Deobfuscation + +task deobfParams { + doLast { + + String mcpDir = "$project.gradle.gradleUserHomeDir/caches/minecraft/de/oceanlabs/mcp/mcp_$channel/$mappingsVersion" + String mcpZIP = "$mcpDir/mcp_$channel-$mappingsVersion-${minecraftVersion}.zip" + String paramsCSV = "$mcpDir/params.csv" + + download.run { + src "https://maven.minecraftforge.net/de/oceanlabs/mcp/mcp_$channel/$mappingsVersion-$minecraftVersion/mcp_$channel-$mappingsVersion-${minecraftVersion}.zip" + dest mcpZIP + overwrite false + } + + if(!file(paramsCSV).exists()) { + println("Extracting MCP archive ...") + unzip(mcpZIP, mcpDir) + } + + println("Parsing params.csv ...") + Map params = new HashMap<>() + Files.lines(Paths.get(paramsCSV)).forEach{line -> + String[] cells = line.split(",") + if(cells.length > 2 && cells[0].matches("p_i?\\d+_\\d+_")) { + params.put(cells[0], cells[1]) + } + } + + out.style(Style.Success).println("Modified ${replaceParams(file("$projectDir/src/main/java"), params)} files!") + out.style(Style.Failure).println("Don't forget to verify that the code still works as before!\n It could be broken due to duplicate variables existing now\n or parameters taking priority over other variables.") +} +} + +static int replaceParams(File file, Map params) { +int fileCount = 0 + +if(file.isDirectory()) { + for(File f : file.listFiles()) { + fileCount += replaceParams(f, params) + } + return fileCount +} +println("Visiting ${file.getName()} ...") +try { + String content = new String(Files.readAllBytes(file.toPath())) + int hash = content.hashCode() + params.forEach{key, value -> + content = content.replaceAll(key, value) + } + if(hash != content.hashCode()) { + Files.write(file.toPath(), content.getBytes("UTF-8")) + return 1 + } +} catch(Exception e) { + e.printStackTrace() +} +return 0 +} + +// Credit: bitsnaps (https://gist.github.com/bitsnaps/00947f2dce66f4bbdabc67d7e7b33681) +static unzip(String zipFileName, String outputDir) { +byte[] buffer = new byte[16384] +ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName)) +ZipEntry zipEntry = zis.getNextEntry() +while (zipEntry != null) { + File newFile = new File(outputDir + File.separator, zipEntry.name) + if (zipEntry.isDirectory()) { + if (!newFile.isDirectory() && !newFile.mkdirs()) { + throw new IOException("Failed to create directory $newFile") + } + } else { + // fix for Windows-created archives + File parent = newFile.parentFile + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Failed to create directory $parent") + } + // write file content + FileOutputStream fos = new FileOutputStream(newFile) + int len = 0 + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len) + } + fos.close() + } + zipEntry = zis.getNextEntry() +} +zis.closeEntry() +zis.close() +} + +configure(deobfParams) { +group = 'forgegradle' +description = 'Rename all obfuscated parameter names inherited from Minecraft classes' +} + +// Dependency Deobfuscation def deobf(String sourceURL) { - try { - URL url = new URL(sourceURL) - String fileName = url.getFile() +try { + URL url = new URL(sourceURL) + String fileName = url.getFile() - //get rid of directories: - int lastSlash = fileName.lastIndexOf("/") - if(lastSlash > 0) { - fileName = fileName.substring(lastSlash + 1) - } - //get rid of extension: - if(fileName.endsWith(".jar")) { - fileName = fileName.substring(0, fileName.lastIndexOf(".")) - } - - String hostName = url.getHost() - if(hostName.startsWith("www.")) { - hostName = hostName.substring(4) - } - List parts = Arrays.asList(hostName.split("\\.")) - Collections.reverse(parts) - hostName = String.join(".", parts) - - return deobf(sourceURL, hostName + "/" + fileName) - } catch(Exception e) { - return deobf(sourceURL, "deobf/" + String.valueOf(sourceURL.hashCode())) + //get rid of directories: + int lastSlash = fileName.lastIndexOf("/") + if(lastSlash > 0) { + fileName = fileName.substring(lastSlash + 1) } + //get rid of extension: + if(fileName.endsWith(".jar") || fileName.endsWith(".litemod")) { + fileName = fileName.substring(0, fileName.lastIndexOf(".")) + } + + String hostName = url.getHost() + if(hostName.startsWith("www.")) { + hostName = hostName.substring(4) + } + List parts = Arrays.asList(hostName.split("\\.")) + Collections.reverse(parts) + hostName = String.join(".", parts) + + return deobf(sourceURL, "$hostName/$fileName") +} catch(Exception e) { + return deobf(sourceURL, "deobf/${sourceURL.hashCode()}") +} } // The method above is to be preferred. Use this method if the filename is not at the end of the URL. -def deobf(String sourceURL, String fileName) { - String cacheDir = System.getProperty("user.home") + "/.gradle/caches/" - String bon2Dir = cacheDir + "forge_gradle/deobf" - String bon2File = bon2Dir + "/BON2-2.5.0.jar" - String obfFile = cacheDir + "modules-2/files-2.1/" + fileName + ".jar" - String deobfFile = cacheDir + "modules-2/files-2.1/" + fileName + "-deobf.jar" - - if(file(deobfFile).exists()) { - return files(deobfFile) - } - - download.run { - src 'https://github.com/GTNewHorizons/BON2/releases/download/2.5.0/BON2-2.5.0.CUSTOM-all.jar' - dest bon2File - quiet true - overwrite false - } - - download.run { - src sourceURL - dest obfFile - quiet true - overwrite false - } - - exec { - commandLine 'java', '-jar', bon2File, '--inputJar', obfFile, '--outputJar', deobfFile, '--mcVer', '1.7.10', '--mappingsVer', 'stable_12', '--notch' - workingDir bon2Dir - standardOutput = new ByteArrayOutputStream() - } +def deobf(String sourceURL, String rawFileName) { +String bon2Version = "2.5.1" +String fileName = URLDecoder.decode(rawFileName, "UTF-8") +String cacheDir = "$project.gradle.gradleUserHomeDir/caches" +String bon2Dir = "$cacheDir/forge_gradle/deobf" +String bon2File = "$bon2Dir/BON2-${bon2Version}.jar" +String obfFile = "$cacheDir/modules-2/files-2.1/${fileName}.jar" +String deobfFile = "$cacheDir/modules-2/files-2.1/${fileName}-deobf.jar" +if(file(deobfFile).exists()) { return files(deobfFile) } +String mappingsVer +String remoteMappings = project.hasProperty('remoteMappings') ? project.remoteMappings : 'https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/' +if(remoteMappings) { + String id = "${forgeVersion.split("\\.")[3]}-$minecraftVersion" + String mappingsZIP = "$cacheDir/forge_gradle/maven_downloader/de/oceanlabs/mcp/mcp_snapshot_nodoc/$id/mcp_snapshot_nodoc-${id}.zip" + + zipMappings(mappingsZIP, remoteMappings, bon2Dir) + + mappingsVer = "snapshot_$id" +} else { + mappingsVer = "${channel}_$mappingsVersion" +} + +download.run { + src "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases/com/github/parker8283/BON2/$bon2Version-CUSTOM/BON2-$bon2Version-CUSTOM-all.jar" + dest bon2File + quiet true + overwrite false +} + +download.run { + src sourceURL + dest obfFile + quiet true + overwrite false +} + +exec { + commandLine 'java', '-jar', bon2File, '--inputJar', obfFile, '--outputJar', deobfFile, '--mcVer', minecraftVersion, '--mappingsVer', mappingsVer, '--notch' + workingDir bon2Dir + standardOutput = new FileOutputStream("${deobfFile}.log") +} + +return files(deobfFile) +} + +def zipMappings(String zipPath, String url, String bon2Dir) { +File zipFile = new File(zipPath) +if(zipFile.exists()) { + return +} + +String fieldsCache = "$bon2Dir/data/fields.csv" +String methodsCache = "$bon2Dir/data/methods.csv" + +download.run { + src "${url}fields.csv" + dest fieldsCache + quiet true +} +download.run { + src "${url}methods.csv" + dest methodsCache + quiet true +} + +zipFile.getParentFile().mkdirs() +ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile)) + +zos.putNextEntry(new ZipEntry("fields.csv")) +Files.copy(Paths.get(fieldsCache), zos) +zos.closeEntry() + +zos.putNextEntry(new ZipEntry("methods.csv")) +Files.copy(Paths.get(methodsCache), zos) +zos.closeEntry() + +zos.close() +} + // Helper methods def checkPropertyExists(String propertyName) { - if (!project.hasProperty(propertyName)) { - throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/gradle.properties") - } +if (!project.hasProperty(propertyName)) { + throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/gradle.properties") +} } def getFile(String relativePath) { - return new File(projectDir, relativePath) +return new File(projectDir, relativePath) } diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..97d8f71c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +plugins { + id 'com.diffplug.blowdryerSetup' version '1.6.0' +} + +apply plugin: 'com.diffplug.blowdryerSetup' + +blowdryerSetup { + github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.1.4') + //devLocal '.' // Use this when testing config updates locally +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java b/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java similarity index 80% rename from src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java rename to src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java index 36b77959..351aea87 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/proxy/ClientProxy.java +++ b/src/main/java/com/gtnewhorizon/structurelib/ClientProxy.java @@ -1,31 +1,29 @@ -package com.gtnewhorizon.structurelib.proxy; +package com.gtnewhorizon.structurelib; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; -import com.gtnewhorizon.structurelib.ConfigurationHandler; import com.gtnewhorizon.structurelib.entity.fx.EntityFXBlockHint; -import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import net.minecraft.block.Block; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiNewChat; -import net.minecraft.client.particle.EntityFX; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.util.ChatComponentText; import net.minecraft.util.IIcon; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.world.WorldEvent; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import static com.gtnewhorizon.structurelib.StructureLib.RANDOM; - public class ClientProxy extends CommonProxy { + + private static final short[] NO_TINT = {255, 255, 255, 0}; + @Override public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) { EntityFXBlockHint hint = new EntityFXBlockHint(w, x, y, z, icons).withColorTint(RGBa); @@ -46,10 +44,6 @@ public class ClientProxy extends CommonProxy { allHints.forcePut(hint, info); } currentHints.add(hint); - - EntityFX particle = new WeightlessParticleFX(w, x + RANDOM.nextFloat() * 0.5F, y + RANDOM.nextFloat() * 0.5F, z + RANDOM.nextFloat() * 0.5F, 0, 0, 0); - particle.setRBGColorF(0, 0.6F * RANDOM.nextFloat(), 0.8f); - Minecraft.getMinecraft().effectRenderer.addEffect(particle); } @Override @@ -59,12 +53,25 @@ public class ClientProxy extends CommonProxy { @Override public void hintParticle(World w, int x, int y, int z, IIcon[] icons) { - hintParticleTinted(w, x, y, z, icons, new short[]{255, 255, 255, 0}); + hintParticleTinted(w, x, y, z, icons, NO_TINT); } @Override public void hintParticle(World w, int x, int y, int z, Block block, int meta) { - hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), new short[]{255, 255, 255, 0}); + hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), NO_TINT); + } + + @Override + public boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] rgBa) { + if (player instanceof EntityPlayerMP) return super.updateHintParticleTint(player, w, x, y, z, rgBa); + if (player != getCurrentPlayer()) return false; // how? + HintParticleInfo info = new HintParticleInfo(x, y, z, null); + EntityFXBlockHint existing = allHints.inverse().get(info); + if (existing != null) { + existing.withColorTint(rgBa); + return true; + } + return false; } private static IIcon[] createIIconFromBlock(Block block, int meta) { @@ -98,13 +105,11 @@ public class ClientProxy extends CommonProxy { private static List currentHints; public static void onHintDead(EntityFXBlockHint fx) { - if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) - allHints.remove(fx); + if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) allHints.remove(fx); for (Iterator> iterator = hintOwners.iterator(); iterator.hasNext(); ) { List list = iterator.next(); if (list.remove(fx)) { - if (list.isEmpty()) - iterator.remove(); + if (list.isEmpty()) iterator.remove(); break; } } @@ -112,29 +117,24 @@ public class ClientProxy extends CommonProxy { @Override public void startHinting(World w) { - if (!w.isRemote) - return; - if (currentHints != null) - hintOwners.add(currentHints); + if (!w.isRemote) return; + if (currentHints != null) hintOwners.add(currentHints); currentHints = new LinkedList<>(); } private void ensureHinting() { - if (currentHints == null) - currentHints = new LinkedList<>(); + if (currentHints == null) currentHints = new LinkedList<>(); } @Override public void endHinting(World w) { - if (!w.isRemote) - return; + if (!w.isRemote) return; while (!hintOwners.isEmpty() && hintOwners.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) { List list = hintOwners.remove(0); list.forEach(EntityFXBlockHint::setDead); list.clear(); } - if (!currentHints.isEmpty()) - hintOwners.add(currentHints); + if (!currentHints.isEmpty()) hintOwners.add(currentHints); currentHints = null; } @@ -143,6 +143,12 @@ public class ClientProxy extends CommonProxy { MinecraftForge.EVENT_BUS.register(new ForgeEventHandler()); } + @Override + public long getOverworldTime() { + // there is no overworld, better just hope current world time is ok... + return Minecraft.getMinecraft().theWorld.getTotalWorldTime(); + } + private static class HintParticleInfo { private final int x, y, z; private final List owner; diff --git a/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java b/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java new file mode 100644 index 00000000..0751b4ba --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/CommonProxy.java @@ -0,0 +1,51 @@ +package com.gtnewhorizon.structurelib; + +import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; + +public class CommonProxy { + public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {} + + public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {} + + public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {} + + public void hintParticle(World w, int x, int y, int z, Block block, int meta) {} + + public boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] rgBa) { + if (player instanceof EntityPlayerMP) { // just in case + StructureLib.net.sendTo( + new UpdateHintParticleMessage(x, (short) y, z, rgBa[0], rgBa[1], rgBa[2], rgBa[3]), + (EntityPlayerMP) player); + return true; + } else { + return false; + } + } + + public EntityPlayer getCurrentPlayer() { + return null; + } + + public boolean isCurrentPlayer(EntityPlayer player) { + return false; + } + + public void addClientSideChatMessages(String... messages) {} + + public void startHinting(World w) {} + + public void endHinting(World w) {} + + public void preInit(FMLPreInitializationEvent e) {} + + public long getOverworldTime() { + return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime(); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/ConfigurationHandler.java b/src/main/java/com/gtnewhorizon/structurelib/ConfigurationHandler.java index 3ed09d42..c3ece97b 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/ConfigurationHandler.java +++ b/src/main/java/com/gtnewhorizon/structurelib/ConfigurationHandler.java @@ -3,13 +3,12 @@ package com.gtnewhorizon.structurelib; import cpw.mods.fml.client.event.ConfigChangedEvent; import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import java.io.File; +import java.util.Map; import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Property; -import java.io.File; -import java.util.Map; - public enum ConfigurationHandler { INSTANCE; @@ -39,8 +38,18 @@ public enum ConfigurationHandler { } private void loadConfig() { - maxCoexistingHologram = config.getInt("maxCoexisting", "client.hologram", 1, 1, 100, "An attempt will be made to prune old holograms when a new hologram is about to be projected"); - removeCollidingHologram = config.getBoolean("removeColliding", "client.hologram", true, "An attempt will be made to remove an existing hologram if it collides with a new hologram."); + maxCoexistingHologram = config.getInt( + "maxCoexisting", + "client.hologram", + 1, + 1, + 100, + "An attempt will be made to prune old holograms when a new hologram is about to be projected"); + removeCollidingHologram = config.getBoolean( + "removeColliding", + "client.hologram", + true, + "An attempt will be made to remove an existing hologram if it collides with a new hologram."); if (config.hasChanged()) { config.save(); diff --git a/src/main/java/com/gtnewhorizon/structurelib/ConfigurationVersion.java b/src/main/java/com/gtnewhorizon/structurelib/ConfigurationVersion.java index 4df0964e..3c107bce 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/ConfigurationVersion.java +++ b/src/main/java/com/gtnewhorizon/structurelib/ConfigurationVersion.java @@ -1,16 +1,14 @@ package com.gtnewhorizon.structurelib; -import net.minecraftforge.common.config.Configuration; - -import javax.annotation.Nullable; import java.util.Arrays; import java.util.Objects; +import javax.annotation.Nullable; +import net.minecraftforge.common.config.Configuration; enum ConfigurationVersion { V1 { @Override - protected void step(Configuration c) { - } + protected void step(Configuration c) {} }; private static final ConfigurationVersion[] VALUES = values(); @@ -32,7 +30,10 @@ enum ConfigurationVersion { } public static ConfigurationVersion identify(Configuration c) { - return Arrays.stream(VALUES).filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker())).findFirst().orElse(V1); + return Arrays.stream(VALUES) + .filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker())) + .findFirst() + .orElse(V1); } @Nullable diff --git a/src/main/java/com/gtnewhorizon/structurelib/GuiFactory.java b/src/main/java/com/gtnewhorizon/structurelib/GuiFactory.java index 879a12b6..22dba088 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/GuiFactory.java +++ b/src/main/java/com/gtnewhorizon/structurelib/GuiFactory.java @@ -1,15 +1,13 @@ package com.gtnewhorizon.structurelib; import cpw.mods.fml.client.IModGuiFactory; +import java.util.Set; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; -import java.util.Set; - public class GuiFactory implements IModGuiFactory { @Override - public void initialize(Minecraft minecraftInstance) { - } + public void initialize(Minecraft minecraftInstance) {} @Override public Class mainConfigGuiClass() { @@ -25,4 +23,4 @@ public class GuiFactory implements IModGuiFactory { public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) { return null; } -} \ No newline at end of file +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/GuiModConfig.java b/src/main/java/com/gtnewhorizon/structurelib/GuiModConfig.java index b586039d..db725ff1 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/GuiModConfig.java +++ b/src/main/java/com/gtnewhorizon/structurelib/GuiModConfig.java @@ -2,24 +2,30 @@ package com.gtnewhorizon.structurelib; import cpw.mods.fml.client.config.GuiConfig; import cpw.mods.fml.client.config.IConfigElement; +import java.util.List; +import java.util.stream.Collectors; import net.minecraft.client.gui.GuiScreen; import net.minecraftforge.common.config.ConfigElement; import net.minecraftforge.common.config.Configuration; -import java.util.List; -import java.util.stream.Collectors; - public class GuiModConfig extends GuiConfig { public GuiModConfig(GuiScreen guiScreen) { - super(guiScreen, getConfigElements(), StructureLibAPI.MOD_ID, false, false, GuiConfig.getAbridgedConfigPath(ConfigurationHandler.INSTANCE.getConfig().toString())); + super( + guiScreen, + getConfigElements(), + StructureLibAPI.MOD_ID, + false, + false, + GuiConfig.getAbridgedConfigPath( + ConfigurationHandler.INSTANCE.getConfig().toString())); } @SuppressWarnings("rawtypes") private static List getConfigElements() { final Configuration config = ConfigurationHandler.INSTANCE.getConfig(); return config.getCategoryNames().stream() - .filter(name -> name.indexOf('.') == -1) - .map(name -> new ConfigElement(config.getCategory(name))) - .collect(Collectors.toList()); + .filter(name -> name.indexOf('.') == -1) + .map(name -> new ConfigElement(config.getCategory(name))) + .collect(Collectors.toList()); } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java index 6de73dc0..19875020 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLib.java @@ -1,11 +1,11 @@ package com.gtnewhorizon.structurelib; -import com.gtnewhorizon.structurelib.net.AlignmentMessage; import com.gtnewhorizon.structurelib.block.BlockHint; import com.gtnewhorizon.structurelib.item.ItemBlockHint; import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger; import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool; -import com.gtnewhorizon.structurelib.proxy.CommonProxy; +import com.gtnewhorizon.structurelib.net.AlignmentMessage; +import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage; import com.gtnewhorizon.structurelib.util.XSTR; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.SidedProxy; @@ -26,18 +26,29 @@ import org.apache.logging.log4j.Logger; /** * This class does not contain a stable API. Refrain from using this class. */ -@Mod(modid = StructureLibAPI.MOD_ID, name = "StructureLib", version = "GRADLETOKEN_VERSION", acceptableRemoteVersions = "*", guiFactory = "com.gtnewhorizon.structurelib.GuiFactory") +@Mod( + modid = StructureLibAPI.MOD_ID, + name = "StructureLib", + version = "GRADLETOKEN_VERSION", + acceptableRemoteVersions = "*", + guiFactory = "com.gtnewhorizon.structurelib.GuiFactory") public class StructureLib { public static boolean DEBUG_MODE = Boolean.getBoolean("structurelib.debug"); + public static boolean PANIC_MODE = Boolean.getBoolean("structurelib.panic"); public static Logger LOGGER = LogManager.getLogger("StructureLib"); - @SidedProxy(serverSide = "com.gtnewhorizon.structurelib.proxy.CommonProxy", clientSide = "com.gtnewhorizon.structurelib.proxy.ClientProxy") + @SidedProxy( + serverSide = "com.gtnewhorizon.structurelib.CommonProxy", + clientSide = "com.gtnewhorizon.structurelib.ClientProxy") static CommonProxy proxy; + static SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID); static { - net.registerMessage(AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER); + net.registerMessage( + AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER); net.registerMessage(AlignmentMessage.ClientHandler.class, AlignmentMessage.AlignmentData.class, 1, Side.CLIENT); + net.registerMessage(UpdateHintParticleMessage.Handler.class, UpdateHintParticleMessage.class, 2, Side.CLIENT); } public static final XSTR RANDOM = new XSTR(); @@ -59,8 +70,11 @@ public class StructureLib { ConfigurationHandler.INSTANCE.init(e.getSuggestedConfigurationFile()); GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint"); itemBlockHint = ItemBlock.getItemFromBlock(StructureLibAPI.getBlockHint()); - GameRegistry.registerItem(itemFrontRotationTool = new ItemFrontRotationTool(), itemFrontRotationTool.getUnlocalizedName()); - GameRegistry.registerItem(itemConstructableTrigger = new ItemConstructableTrigger(), itemConstructableTrigger.getUnlocalizedName()); + GameRegistry.registerItem( + itemFrontRotationTool = new ItemFrontRotationTool(), itemFrontRotationTool.getUnlocalizedName()); + GameRegistry.registerItem( + itemConstructableTrigger = new ItemConstructableTrigger(), + itemConstructableTrigger.getUnlocalizedName()); proxy.preInit(e); } @@ -75,4 +89,8 @@ public class StructureLib { public static boolean isCurrentPlayer(EntityPlayer player) { return proxy.isCurrentPlayer(player); } + + public static long getOverworldTime() { + return proxy.getOverworldTime(); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java index 70f89bd7..8e82e88e 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java +++ b/src/main/java/com/gtnewhorizon/structurelib/StructureLibAPI.java @@ -1,8 +1,11 @@ package com.gtnewhorizon.structurelib; -import com.gtnewhorizon.structurelib.net.AlignmentMessage; +import static com.gtnewhorizon.structurelib.StructureLib.proxy; + import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.net.AlignmentMessage; +import com.gtnewhorizon.structurelib.structure.IItemSource; import cpw.mods.fml.common.network.NetworkRegistry; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; @@ -12,8 +15,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.IIcon; import net.minecraft.world.World; -import static com.gtnewhorizon.structurelib.StructureLib.proxy; - /** * A stable interface into the structure lib's internals. Backwards compatibility is maintained to the maximum extend possible. */ @@ -46,7 +47,7 @@ public class StructureLibAPI { } /** - * Start current batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram. + * End current batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram. *

* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)} */ @@ -70,6 +71,14 @@ public class StructureLibAPI { proxy.hintParticle(w, x, y, z, block, meta); } + /** + * Update the tint of given hint particle. Do nothing if particle not found. + * @return false if nothing updated. true if updated. + */ + public static boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] RGBa) { + return proxy.updateHintParticleTint(player, w, x, y, z, RGBa); + } + /** * Query the ExtendedFacing of this tile entity. * The ExtendedFacing will later be set onto given tile entity via {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} @@ -108,7 +117,6 @@ public class StructureLibAPI { StructureLib.net.sendToAllAround(new AlignmentMessage.AlignmentData(provider), targetPoint); } - /** * Send the ExtendedFacing of this Tile Entity to all players in that dimension. Can be called on server side only. * @@ -133,4 +141,15 @@ public class StructureLibAPI { public static void setDebugEnabled(boolean enabled) { StructureLib.DEBUG_MODE = enabled; } + + /** + * Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and + * will be changed, but the general idea will always stay the same. + * + * Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)} + */ + public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayerMP actor) { + // TODO extend this function a bit + return w.getBlock(x, y, z).isReplaceable(w, x, y, z); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/AlignmentUtility.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/AlignmentUtility.java index 20d23484..7a50c04c 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/AlignmentUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/AlignmentUtility.java @@ -7,8 +7,7 @@ import net.minecraft.world.World; import net.minecraftforge.common.util.FakePlayer; public class AlignmentUtility { - private AlignmentUtility() { - } + private AlignmentUtility() {} public static boolean handle(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ) { TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ); diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignment.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignment.java index bbe33927..1f9349b0 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignment.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignment.java @@ -57,7 +57,9 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider { default boolean toolSetDirection(ForgeDirection direction) { if (direction == null || direction == ForgeDirection.UNKNOWN) { - for (int i = 0, j = getDirection().ordinal() + 1, valuesLength = Direction.VALUES.length; i < valuesLength; i++) { + for (int i = 0, j = getDirection().ordinal() + 1, valuesLength = Direction.VALUES.length; + i < valuesLength; + i++) { if (toolSetDirection(Direction.VALUES[(j + i) % valuesLength].getForgeDirection())) { return true; } @@ -95,7 +97,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider { int rotations = Rotation.VALUES.length; for (int ii = 0, jj = getFlip().ordinal(); ii < flips; ii++) { for (int i = 1, j = getRotation().ordinal(); i < rotations; i++) { - if (checkedSetExtendedFacing(ExtendedFacing.of(getDirection(), Rotation.VALUES[(j + i) % rotations], Flip.VALUES[(jj + ii) % flips]))) { + if (checkedSetExtendedFacing(ExtendedFacing.of( + getDirection(), Rotation.VALUES[(j + i) % rotations], Flip.VALUES[(jj + ii) % flips]))) { return true; } } @@ -137,7 +140,9 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider { default boolean toolSetExtendedFacing(ExtendedFacing extendedFacing) { if (extendedFacing == null) { - for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length; i < valuesLength; i++) { + for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length; + i < valuesLength; + i++) { if (checkedSetExtendedFacing(ExtendedFacing.VALUES[(j + i) % valuesLength])) { return true; } @@ -179,9 +184,7 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider { @Override default boolean isNewExtendedFacingValid(ExtendedFacing alignment) { - return getAlignmentLimits().isNewExtendedFacingValid( - alignment.getDirection(), - alignment.getRotation(), - alignment.getFlip()); + return getAlignmentLimits() + .isNewExtendedFacingValid(alignment.getDirection(), alignment.getRotation(), alignment.getFlip()); } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentLimits.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentLimits.java index c368caf1..a5318a45 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentLimits.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentLimits.java @@ -1,15 +1,14 @@ package com.gtnewhorizon.structurelib.alignment; +import static com.gtnewhorizon.structurelib.alignment.IAlignment.STATES_COUNT; + import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; import com.gtnewhorizon.structurelib.alignment.enumerable.Flip; import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation; -import net.minecraftforge.common.util.ForgeDirection; - import java.util.Arrays; import java.util.Optional; import java.util.function.Function; - -import static com.gtnewhorizon.structurelib.alignment.IAlignment.STATES_COUNT; +import net.minecraftforge.common.util.ForgeDirection; public interface IAlignmentLimits { @@ -18,10 +17,7 @@ public interface IAlignmentLimits { boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip); default boolean isNewExtendedFacingValid(ExtendedFacing alignment) { - return isNewExtendedFacingValid( - alignment.getDirection(), - alignment.getRotation(), - alignment.getFlip()); + return isNewExtendedFacingValid(alignment.getDirection(), alignment.getRotation(), alignment.getFlip()); } static IAlignmentLimits allowOnly(ExtendedFacing... allowedFacings) { @@ -43,8 +39,7 @@ public interface IAlignmentLimits { class Builder { protected final boolean[] validStates = new boolean[STATES_COUNT]; - private Builder() { - } + private Builder() {} public static Builder allowAll() { Builder b = new Builder(); @@ -59,12 +54,16 @@ public interface IAlignmentLimits { } public Builder deny(ForgeDirection fd) { - ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = false); + ExtendedFacing.getAllWith(fd).stream() + .mapToInt(ExtendedFacing::getIndex) + .forEach(v -> validStates[v] = false); return this; } public Builder allow(ForgeDirection fd) { - ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = true); + ExtendedFacing.getAllWith(fd).stream() + .mapToInt(ExtendedFacing::getIndex) + .forEach(v -> validStates[v] = true); return this; } @@ -79,22 +78,30 @@ public interface IAlignmentLimits { } public Builder deny(Rotation fd) { - ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = false); + ExtendedFacing.getAllWith(fd).stream() + .mapToInt(ExtendedFacing::getIndex) + .forEach(v -> validStates[v] = false); return this; } public Builder allow(Rotation fd) { - ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = true); + ExtendedFacing.getAllWith(fd).stream() + .mapToInt(ExtendedFacing::getIndex) + .forEach(v -> validStates[v] = true); return this; } public Builder deny(Flip fd) { - ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = false); + ExtendedFacing.getAllWith(fd).stream() + .mapToInt(ExtendedFacing::getIndex) + .forEach(v -> validStates[v] = false); return this; } public Builder allow(Flip fd) { - ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex).forEach(v -> validStates[v] = true); + ExtendedFacing.getAllWith(fd).stream() + .mapToInt(ExtendedFacing::getIndex) + .forEach(v -> validStates[v] = true); return this; } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentProvider.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentProvider.java index 10572dbf..df3d355e 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentProvider.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/IAlignmentProvider.java @@ -5,4 +5,4 @@ import javax.annotation.Nullable; public interface IAlignmentProvider { @Nullable IAlignment getAlignment(); -} \ No newline at end of file +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/IntegerAxisSwap.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/IntegerAxisSwap.java index 4f7da9d1..2619c746 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/IntegerAxisSwap.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/IntegerAxisSwap.java @@ -1,12 +1,12 @@ package com.gtnewhorizon.structurelib.alignment; -import com.gtnewhorizon.structurelib.util.Vec3Impl; +import static java.lang.Math.abs; + import com.gtnewhorizon.structurelib.alignment.enumerable.Direction; +import com.gtnewhorizon.structurelib.util.Vec3Impl; import net.minecraft.util.Vec3; import net.minecraftforge.common.util.ForgeDirection; -import static java.lang.Math.abs; - public class IntegerAxisSwap { private final Vec3Impl forFirstAxis; private final Vec3Impl forSecondAxis; @@ -16,46 +16,66 @@ public class IntegerAxisSwap { this.forFirstAxis = Direction.getAxisVector(forFirstAxis); this.forSecondAxis = Direction.getAxisVector(forSecondAxis); this.forThirdAxis = Direction.getAxisVector(forThirdAxis); - if (abs(this.forFirstAxis.get0()) + abs(this.forSecondAxis.get0()) + abs(this.forThirdAxis.get0()) != 1 || - abs(this.forFirstAxis.get1()) + abs(this.forSecondAxis.get1()) + abs(this.forThirdAxis.get1()) != 1 || - abs(this.forFirstAxis.get2()) + abs(this.forSecondAxis.get2()) + abs(this.forThirdAxis.get2()) != 1) { - throw new IllegalArgumentException("Axis are overlapping/missing! " + - forFirstAxis.name() + " " + - forSecondAxis.name() + " " + - forThirdAxis.name()); + if (abs(this.forFirstAxis.get0()) + abs(this.forSecondAxis.get0()) + abs(this.forThirdAxis.get0()) != 1 + || abs(this.forFirstAxis.get1()) + abs(this.forSecondAxis.get1()) + abs(this.forThirdAxis.get1()) != 1 + || abs(this.forFirstAxis.get2()) + abs(this.forSecondAxis.get2()) + abs(this.forThirdAxis.get2()) + != 1) { + throw new IllegalArgumentException("Axis are overlapping/missing! " + forFirstAxis.name() + + " " + forSecondAxis.name() + + " " + forThirdAxis.name()); } } public Vec3Impl translate(Vec3Impl point) { return new Vec3Impl( - forFirstAxis.get0() * point.get0() + forFirstAxis.get1() * point.get1() + forFirstAxis.get2() * point.get2(), - forSecondAxis.get0() * point.get0() + forSecondAxis.get1() * point.get1() + forSecondAxis.get2() * point.get2(), - forThirdAxis.get0() * point.get0() + forThirdAxis.get1() * point.get1() + forThirdAxis.get2() * point.get2() - ); + forFirstAxis.get0() * point.get0() + + forFirstAxis.get1() * point.get1() + + forFirstAxis.get2() * point.get2(), + forSecondAxis.get0() * point.get0() + + forSecondAxis.get1() * point.get1() + + forSecondAxis.get2() * point.get2(), + forThirdAxis.get0() * point.get0() + + forThirdAxis.get1() * point.get1() + + forThirdAxis.get2() * point.get2()); } public Vec3Impl inverseTranslate(Vec3Impl point) { return new Vec3Impl( - forFirstAxis.get0() * point.get0() + forSecondAxis.get0() * point.get1() + forThirdAxis.get0() * point.get2(), - forFirstAxis.get1() * point.get0() + forSecondAxis.get1() * point.get1() + forThirdAxis.get1() * point.get2(), - forFirstAxis.get2() * point.get0() + forSecondAxis.get2() * point.get1() + forThirdAxis.get2() * point.get2() - ); + forFirstAxis.get0() * point.get0() + + forSecondAxis.get0() * point.get1() + + forThirdAxis.get0() * point.get2(), + forFirstAxis.get1() * point.get0() + + forSecondAxis.get1() * point.get1() + + forThirdAxis.get1() * point.get2(), + forFirstAxis.get2() * point.get0() + + forSecondAxis.get2() * point.get1() + + forThirdAxis.get2() * point.get2()); } public Vec3 translate(Vec3 point) { return Vec3.createVectorHelper( - forFirstAxis.get0() * point.xCoord + forFirstAxis.get1() * point.yCoord + forFirstAxis.get2() * point.zCoord, - forSecondAxis.get0() * point.xCoord + forSecondAxis.get1() * point.yCoord + forSecondAxis.get2() * point.zCoord, - forThirdAxis.get0() * point.xCoord + forThirdAxis.get1() * point.yCoord + forThirdAxis.get2() * point.zCoord - ); + forFirstAxis.get0() * point.xCoord + + forFirstAxis.get1() * point.yCoord + + forFirstAxis.get2() * point.zCoord, + forSecondAxis.get0() * point.xCoord + + forSecondAxis.get1() * point.yCoord + + forSecondAxis.get2() * point.zCoord, + forThirdAxis.get0() * point.xCoord + + forThirdAxis.get1() * point.yCoord + + forThirdAxis.get2() * point.zCoord); } public Vec3 inverseTranslate(Vec3 point) { return Vec3.createVectorHelper( - forFirstAxis.get0() * point.xCoord + forSecondAxis.get0() * point.yCoord + forThirdAxis.get0() * point.zCoord, - forFirstAxis.get1() * point.xCoord + forSecondAxis.get1() * point.yCoord + forThirdAxis.get1() * point.zCoord, - forFirstAxis.get2() * point.xCoord + forSecondAxis.get2() * point.yCoord + forThirdAxis.get2() * point.zCoord - ); + forFirstAxis.get0() * point.xCoord + + forSecondAxis.get0() * point.yCoord + + forThirdAxis.get0() * point.zCoord, + forFirstAxis.get1() * point.xCoord + + forSecondAxis.get1() * point.yCoord + + forThirdAxis.get1() * point.zCoord, + forFirstAxis.get2() * point.xCoord + + forSecondAxis.get2() * point.yCoord + + forThirdAxis.get2() * point.zCoord); } public void translate(int[] point, int[] out) { diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java index f881f88b..994b54c7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ConstructableUtility.java @@ -4,79 +4,107 @@ import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.IAlignment; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IItemSource; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ChatComponentTranslation; import net.minecraft.world.World; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.common.util.ForgeDirection; public class ConstructableUtility { - private ConstructableUtility() { - } + // TODO make these configurable and expose this to external world + public static final int COOLDOWN = 5; + private static final int BUDGET = 25; + public static boolean redBrokenOne = true; // TODO this is horrible - public static boolean handle(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) { + private ConstructableUtility() {} + + public static boolean handle( + ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) { StructureLibAPI.startHinting(aWorld); boolean ret = handle0(aStack, aPlayer, aWorld, aX, aY, aZ, aSide); StructureLibAPI.endHinting(aWorld); return ret; } - private static boolean handle0(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) { + private static boolean handle0( + ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) { TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ); if (tTileEntity == null || aPlayer instanceof FakePlayer) { return aPlayer instanceof EntityPlayerMP; } if (aPlayer instanceof EntityPlayerMP) { - //struct gen - if (aPlayer.isSneaking() && aPlayer.capabilities.isCreativeMode) { - if (tTileEntity instanceof IConstructableProvider) { - IConstructable constructable = ((IConstructableProvider) tTileEntity).getConstructable(); - if (constructable != null) { - constructable.construct(aStack, false); - } - } else if (tTileEntity instanceof IConstructable) { - ((IConstructable) tTileEntity).construct(aStack, false); - } else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) { - IMultiblockInfoContainer iMultiblockInfoContainer = IMultiblockInfoContainer.get(tTileEntity.getClass()); - if (tTileEntity instanceof IAlignment) { - iMultiblockInfoContainer.construct(aStack, false, tTileEntity, - ((IAlignment) tTileEntity).getExtendedFacing()); - } else { - iMultiblockInfoContainer.construct(aStack, false, tTileEntity, - ExtendedFacing.of(ForgeDirection.getOrientation(aSide))); - } + // not sneaking - client side will generate hologram. nothing to do on server side + if (!aPlayer.isSneaking()) return true; + + long timePast = StructureLib.getOverworldTime() - getLastUseTick(aStack); + if (timePast < COOLDOWN) { + aPlayer.addChatComponentMessage(new ChatComponentTranslation( + "item.structurelib.constructableTrigger.too_fast", COOLDOWN - timePast)); + return true; + } + } else if (!StructureLib.isCurrentPlayer(aPlayer)) { + return false; + } + + IConstructable constructable = null; + if (tTileEntity instanceof IConstructableProvider) + constructable = ((IConstructableProvider) tTileEntity).getConstructable(); + else if (tTileEntity instanceof IConstructable) { + constructable = (IConstructable) tTileEntity; + } else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) { + ExtendedFacing facing = tTileEntity instanceof IAlignment + ? ((IAlignment) tTileEntity).getExtendedFacing() + : ExtendedFacing.of(ForgeDirection.getOrientation(aSide)); + constructable = IMultiblockInfoContainer.get(tTileEntity.getClass()) + .toConstructable(tTileEntity, facing); + } + + if (constructable == null) return false; + + if (aPlayer instanceof EntityPlayerMP) { + EntityPlayerMP playerMP = (EntityPlayerMP) aPlayer; + // server side and sneaking (already checked above) + // do construct + if (aPlayer.capabilities.isCreativeMode) { + constructable.construct(aStack, false); + } else if (constructable instanceof ISurvivalConstructable) { + int built = ((ISurvivalConstructable) constructable) + .survivalConstruct(aStack, BUDGET, IItemSource.fromPlayer(playerMP), playerMP); + if (built > 0) + // TODO somehow notify extensions that their inventory might have been modified and need to be + // synced to client or saved + playerMP.inventory.markDirty(); + else if (built == -1) { + playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.complete")); } + setLastUseTickToStackTag(aStack); + } else { + playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.error.not_enabled")); } return true; - } else if (StructureLib.isCurrentPlayer(aPlayer)) {//particles and text client side - //if ((!aPlayer.isSneaking() || !aPlayer.capabilities.isCreativeMode)) { - if (tTileEntity instanceof IConstructableProvider) { - IConstructable constructable = ((IConstructableProvider) tTileEntity).getConstructable(); - if (constructable != null) { - constructable.construct(aStack, true); - StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack)); - } - } else if (tTileEntity instanceof IConstructable) { - IConstructable constructable = (IConstructable) tTileEntity; - constructable.construct(aStack, true); - StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack)); - return false; - } else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) { - IMultiblockInfoContainer iMultiblockInfoContainer = IMultiblockInfoContainer.get(tTileEntity.getClass()); - if (tTileEntity instanceof IAlignment) { - iMultiblockInfoContainer.construct(aStack, true, tTileEntity, - ((IAlignment) tTileEntity).getExtendedFacing()); - } else { - iMultiblockInfoContainer.construct(aStack, true, tTileEntity, - ExtendedFacing.of(ForgeDirection.getOrientation(aSide))); - } - StructureLib.addClientSideChatMessages(IMultiblockInfoContainer.get(tTileEntity.getClass()).getDescription(aStack)); - return false; - } } + // client side + // particles and text + constructable.construct(aStack, true); + if (getLastUseTick(aStack) == 0) + StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack)); return false; } + + private static long getLastUseTick(ItemStack aStack) { + return aStack.hasTagCompound() ? aStack.getTagCompound().getLong("LastUse") : 0; + } + + private static void setLastUseTickToStackTag(ItemStack aStack) { + NBTTagCompound tag = aStack.stackTagCompound; + if (tag == null) tag = aStack.stackTagCompound = new NBTTagCompound(); + // here we force use the overworld tick time, in case the current world is over + tag.setLong("LastUse", StructureLib.getOverworldTime()); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IConstructable.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IConstructable.java index 89f3d288..0d813897 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IConstructable.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IConstructable.java @@ -13,4 +13,3 @@ public interface IConstructable { @SideOnly(Side.CLIENT) String[] getStructureDescription(ItemStack stackSize); } - diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java index 138ed968..d93d2e2e 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/IMultiblockInfoContainer.java @@ -1,12 +1,14 @@ package com.gtnewhorizon.structurelib.alignment.constructable; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IItemSource; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import java.util.HashMap; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.tileentity.TileEntity; - -import java.util.HashMap; +import net.minecraft.world.World; /** * To implement IConstructable on not own TileEntities @@ -33,6 +35,59 @@ public interface IMultiblockInfoContainer { void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide); + /** + * Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)} + * @return -1 if done, a helping pointer + */ + default int survivalConstruct( + ItemStack stackSize, + int elementBudge, + IItemSource source, + EntityPlayerMP actorProfile, + T tileEntity, + ExtendedFacing aSide) { + return -1; + } + @SideOnly(Side.CLIENT) String[] getDescription(ItemStack stackSize); + + /** + * Override this and return {@link #toConstructable(IMultiblockInfoContainer, Object, ExtendedFacing)} if + * {@link #survivalConstruct(ItemStack, int, IItemSource, EntityPlayerMP, Object, ExtendedFacing)} isn't a stub + */ + default IConstructable toConstructable(T tileEntity, ExtendedFacing aSide) { + return new IConstructable() { + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + IMultiblockInfoContainer.this.construct(stackSize, hintsOnly, tileEntity, aSide); + } + + @Override + public String[] getStructureDescription(ItemStack stackSize) { + return IMultiblockInfoContainer.this.getDescription(stackSize); + } + }; + } + + static ISurvivalConstructable toConstructable( + IMultiblockInfoContainer thiz, T tileEntity, ExtendedFacing aSide) { + return new ISurvivalConstructable() { + @Override + public int survivalConstruct( + ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor) { + return thiz.survivalConstruct(stackSize, elementBudget, source, actor, tileEntity, aSide); + } + + @Override + public void construct(ItemStack stackSize, boolean hintsOnly) { + thiz.construct(stackSize, hintsOnly, tileEntity, aSide); + } + + @Override + public String[] getStructureDescription(ItemStack stackSize) { + return thiz.getDescription(stackSize); + } + }; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java new file mode 100644 index 00000000..2d63e2f0 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/constructable/ISurvivalConstructable.java @@ -0,0 +1,17 @@ +package com.gtnewhorizon.structurelib.alignment.constructable; + +import com.gtnewhorizon.structurelib.structure.IItemSource; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +public interface ISurvivalConstructable extends IConstructable { + /** + * Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)} + * + * @param elementBudget The server configured element budget. You can tune this up a bit if the structure is too big, but + * generally should not be a 4 digits number to not overwhelm the server + * @return -1 if done, otherwise number of elements placed this round + */ + int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java index d0cd827b..3b54c274 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/ExtendedFacing.java @@ -1,21 +1,20 @@ package com.gtnewhorizon.structurelib.alignment.enumerable; -import com.google.common.collect.ImmutableSet; -import com.gtnewhorizon.structurelib.alignment.IAlignment; -import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap; -import com.gtnewhorizon.structurelib.util.Vec3Impl; -import net.minecraft.util.StatCollector; -import net.minecraft.util.Vec3; -import net.minecraftforge.common.util.ForgeDirection; - -import java.util.*; - import static com.gtnewhorizon.structurelib.alignment.IAlignment.FLIPS_COUNT; import static com.gtnewhorizon.structurelib.alignment.IAlignment.ROTATIONS_COUNT; import static java.lang.Math.abs; import static java.util.Arrays.stream; import static java.util.stream.Collectors.*; +import com.google.common.collect.ImmutableSet; +import com.gtnewhorizon.structurelib.alignment.IAlignment; +import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap; +import com.gtnewhorizon.structurelib.util.Vec3Impl; +import java.util.*; +import net.minecraft.util.StatCollector; +import net.minecraft.util.Vec3; +import net.minecraftforge.common.util.ForgeDirection; + public enum ExtendedFacing { DOWN_NORMAL_NONE("down normal none"), DOWN_NORMAL_HORIZONTAL("down normal horizontal"), @@ -120,20 +119,34 @@ public enum ExtendedFacing { public static final int STATES_COUNT = VALUES.length; static { - stream(values()).forEach(extendedFacing -> - FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> { - if (extendedFacings == null) { - extendedFacings = new ArrayList<>(); - } - extendedFacings.add(extendedFacing); - return extendedFacings; - }))); + stream(values()) + .forEach(extendedFacing -> + FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> { + if (extendedFacings == null) { + extendedFacings = new ArrayList<>(); + } + extendedFacings.add(extendedFacing); + return extendedFacings; + }))); } - private static final Map NAME_LOOKUP = stream(VALUES).collect(toMap(ExtendedFacing::getName2, (extendedFacing) -> extendedFacing)); - private static final EnumMap> LOOKUP_BY_DIRECTION = stream(VALUES).collect(groupingBy(ExtendedFacing::getDirection, () -> new EnumMap<>(ForgeDirection.class), collectingAndThen(toSet(), ImmutableSet::copyOf))); - private static final EnumMap> LOOKUP_BY_ROTATION = stream(VALUES).collect(groupingBy(ExtendedFacing::getRotation, () -> new EnumMap<>(Rotation.class), collectingAndThen(toSet(), ImmutableSet::copyOf))); - private static final EnumMap> LOOKUP_BY_FLIP = stream(VALUES).collect(groupingBy(ExtendedFacing::getFlip, () -> new EnumMap<>(Flip.class), collectingAndThen(toSet(), ImmutableSet::copyOf))); + private static final Map NAME_LOOKUP = + stream(VALUES).collect(toMap(ExtendedFacing::getName2, (extendedFacing) -> extendedFacing)); + private static final EnumMap> LOOKUP_BY_DIRECTION = stream(VALUES) + .collect(groupingBy( + ExtendedFacing::getDirection, + () -> new EnumMap<>(ForgeDirection.class), + collectingAndThen(toSet(), ImmutableSet::copyOf))); + private static final EnumMap> LOOKUP_BY_ROTATION = stream(VALUES) + .collect(groupingBy( + ExtendedFacing::getRotation, + () -> new EnumMap<>(Rotation.class), + collectingAndThen(toSet(), ImmutableSet::copyOf))); + private static final EnumMap> LOOKUP_BY_FLIP = stream(VALUES) + .collect(groupingBy( + ExtendedFacing::getFlip, + () -> new EnumMap<>(Flip.class), + collectingAndThen(toSet(), ImmutableSet::copyOf))); private final ForgeDirection direction; private final ForgeDirection a, b, c; private final Rotation rotation; @@ -182,7 +195,7 @@ public enum ExtendedFacing { default: throw new RuntimeException("Is impossible..."); } - switch (flip) {//This duplicates some axis swaps since flip boolean would do, but seems more convenient to use + switch (flip) { // This duplicates some axis swaps since flip boolean would do, but seems more convenient to use case HORIZONTAL: a = a.getOpposite(); break; @@ -341,7 +354,6 @@ public enum ExtendedFacing { integerAxisSwap.inverseTranslate(point, out); } - /** * Translates world offset to relative front facing offset * diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Flip.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Flip.java index f4befdfa..dade8c75 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Flip.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Flip.java @@ -1,14 +1,14 @@ package com.gtnewhorizon.structurelib.alignment.enumerable; -import javax.annotation.Nonnull; -import java.util.Locale; -import java.util.Map; -import java.util.Random; - import static java.lang.Math.abs; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toMap; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import javax.annotation.Nonnull; + public enum Flip { NONE(3, "none"), HORIZONTAL(2, "horizontal"), diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Rotation.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Rotation.java index c66f1557..a555f00b 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Rotation.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/enumerable/Rotation.java @@ -1,14 +1,14 @@ package com.gtnewhorizon.structurelib.alignment.enumerable; -import javax.annotation.Nonnull; -import java.util.Locale; -import java.util.Map; -import java.util.Random; - import static java.lang.Math.abs; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toMap; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import javax.annotation.Nonnull; + public enum Rotation { NORMAL(2, "normal"), CLOCKWISE(3, "clockwise"), @@ -19,7 +19,8 @@ public enum Rotation { private final String name; public static final Rotation[] VALUES = values(); - private static final Map NAME_LOOKUP = stream(VALUES).collect(toMap(Rotation::getName2, (rotation) -> rotation)); + private static final Map NAME_LOOKUP = + stream(VALUES).collect(toMap(Rotation::getName2, (rotation) -> rotation)); Rotation(int oppositeIn, String nameIn) { this.opposite = oppositeIn; diff --git a/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java b/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java index b4757ad7..9e031726 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java +++ b/src/main/java/com/gtnewhorizon/structurelib/alignment/package-info.java @@ -2,4 +2,4 @@ * All public stuff in this package and its subpackages are considered part of the public API. * Backwards compatibility is maintained to the maximum extend possible. */ -package com.gtnewhorizon.structurelib.alignment; \ No newline at end of file +package com.gtnewhorizon.structurelib.alignment; diff --git a/src/main/java/com/gtnewhorizon/structurelib/block/BlockHint.java b/src/main/java/com/gtnewhorizon/structurelib/block/BlockHint.java index 5551e098..de6bfbc7 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/block/BlockHint.java +++ b/src/main/java/com/gtnewhorizon/structurelib/block/BlockHint.java @@ -4,6 +4,7 @@ import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import java.util.List; import net.minecraft.block.Block; import net.minecraft.block.material.Material; import net.minecraft.client.renderer.texture.IIconRegister; @@ -13,8 +14,6 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.IIcon; import net.minecraft.world.IBlockAccess; -import java.util.List; - public class BlockHint extends Block { private static final IIcon[] hint = new IIcon[16]; @@ -26,7 +25,7 @@ public class BlockHint extends Block { @Override public void registerBlockIcons(IIconRegister aIconRegister) { - //super.registerBlockIcons(aIconRegister); + // super.registerBlockIcons(aIconRegister); hint[0] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_0"); hint[1] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_1"); hint[2] = aIconRegister.registerIcon(StructureLibAPI.MOD_ID + ":iconsets/HINT_2"); diff --git a/src/main/java/com/gtnewhorizon/structurelib/entity/fx/EntityFXBlockHint.java b/src/main/java/com/gtnewhorizon/structurelib/entity/fx/EntityFXBlockHint.java index fdd9d86e..315b4771 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/entity/fx/EntityFXBlockHint.java +++ b/src/main/java/com/gtnewhorizon/structurelib/entity/fx/EntityFXBlockHint.java @@ -1,7 +1,7 @@ package com.gtnewhorizon.structurelib.entity.fx; +import com.gtnewhorizon.structurelib.ClientProxy; import com.gtnewhorizon.structurelib.StructureLib; -import com.gtnewhorizon.structurelib.proxy.ClientProxy; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import net.minecraft.block.Block; @@ -59,7 +59,14 @@ public class EntityFXBlockHint extends EntityFX { } @Override - public void renderParticle(Tessellator tes, float subTickTime, float p_70539_3_, float p_70539_4_, float p_70539_5_, float p_70539_6_, float p_70539_7_) { + public void renderParticle( + Tessellator tes, + float subTickTime, + float p_70539_3_, + float p_70539_4_, + float p_70539_5_, + float p_70539_6_, + float p_70539_7_) { float size = .5f; float X = (float) (prevPosX + (posX - prevPosX) * (double) subTickTime - EntityFX.interpPosX); float Y = (float) (prevPosY + (posY - prevPosY) * (double) subTickTime - EntityFX.interpPosY) - size / 2; @@ -68,8 +75,8 @@ public class EntityFXBlockHint extends EntityFX { GL11.glDepthMask(false); tes.setColorRGBA((int) (mRGBa[0] * .9F), (int) (mRGBa[1] * .95F), (int) (mRGBa[2] * 1F), 192); - //var8, var9 - X U - //var 10, var 11 - Y V + // var8, var9 - X U + // var 10, var 11 - Y V for (int i = 0; i < 6; i++) { if (icons[i] == null) { continue; @@ -78,7 +85,7 @@ public class EntityFXBlockHint extends EntityFX { double U = icons[i].getMaxU(); double v = icons[i].getMinV(); double V = icons[i].getMaxV(); - switch (i) {//{DOWN, UP, NORTH, SOUTH, WEST, EAST} + switch (i) { // {DOWN, UP, NORTH, SOUTH, WEST, EAST} case 0: tes.addVertexWithUV(X, Y, Z + size, u, V); tes.addVertexWithUV(X, Y, Z, u, v); diff --git a/src/main/java/com/gtnewhorizon/structurelib/entity/fx/WeightlessParticleFX.java b/src/main/java/com/gtnewhorizon/structurelib/entity/fx/WeightlessParticleFX.java index 34be31ac..5f4a7c12 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/entity/fx/WeightlessParticleFX.java +++ b/src/main/java/com/gtnewhorizon/structurelib/entity/fx/WeightlessParticleFX.java @@ -7,7 +7,14 @@ import net.minecraft.world.World; @SideOnly(Side.CLIENT) public class WeightlessParticleFX extends EntityFX { - public WeightlessParticleFX(World p_i1205_1_, double p_i1205_2_, double p_i1205_4_, double p_i1205_6_, double p_i1205_8_, double p_i1205_10_, double p_i1205_12_) { + public WeightlessParticleFX( + World p_i1205_1_, + double p_i1205_2_, + double p_i1205_4_, + double p_i1205_6_, + double p_i1205_8_, + double p_i1205_10_, + double p_i1205_12_) { super(p_i1205_1_, p_i1205_2_, p_i1205_4_, p_i1205_6_, p_i1205_8_, p_i1205_10_, p_i1205_12_); this.motionX = p_i1205_8_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F); this.motionY = p_i1205_10_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F); @@ -31,7 +38,7 @@ public class WeightlessParticleFX extends EntityFX { } this.setParticleTextureIndex(7 - this.particleAge * 8 / this.particleMaxAge); - //this.motionY += 0.004D; + // this.motionY += 0.004D; this.moveEntity(this.motionX, this.motionY, this.motionZ); this.motionX *= 0.8999999761581421D; this.motionY *= 0.8999999761581421D; diff --git a/src/main/java/com/gtnewhorizon/structurelib/item/ItemBlockHint.java b/src/main/java/com/gtnewhorizon/structurelib/item/ItemBlockHint.java index d62ac5c4..c346caac 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/item/ItemBlockHint.java +++ b/src/main/java/com/gtnewhorizon/structurelib/item/ItemBlockHint.java @@ -1,5 +1,8 @@ package com.gtnewhorizon.structurelib.item; +import static net.minecraft.util.StatCollector.translateToLocal; + +import java.util.List; import net.minecraft.block.Block; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.entity.player.EntityPlayer; @@ -7,10 +10,6 @@ import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumChatFormatting; -import java.util.List; - -import static net.minecraft.util.StatCollector.translateToLocal; - public class ItemBlockHint extends ItemBlock { public ItemBlockHint(Block p_i45328_1_) { super(p_i45328_1_); @@ -22,7 +21,7 @@ public class ItemBlockHint extends ItemBlock { @Override @SuppressWarnings("unchecked") public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List aList, boolean aF3_H) { - aList.add(translateToLocal("structurelib.blockhint.desc.0"));//Helps while building + aList.add(translateToLocal("structurelib.blockhint.desc.0")); // Helps while building switch (aStack.getItemDamage()) { case 0: case 1: @@ -36,21 +35,31 @@ public class ItemBlockHint extends ItemBlock { case 9: case 10: case 11: - aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.1"));//Placeholder for a certain group. + aList.add(EnumChatFormatting.AQUA.toString() + + EnumChatFormatting.BOLD + + translateToLocal("structurelib.blockhint.desc.1")); // Placeholder for a certain group. break; case 12: - aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.2"));//General placeholder. + aList.add(EnumChatFormatting.AQUA.toString() + + EnumChatFormatting.BOLD + + translateToLocal("structurelib.blockhint.desc.2")); // General placeholder. break; case 13: - aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.3"));//Make sure it contains Air material. + aList.add(EnumChatFormatting.AQUA.toString() + + EnumChatFormatting.BOLD + + translateToLocal("structurelib.blockhint.desc.3")); // Make sure it contains Air material. break; case 14: - aList.add(EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD + translateToLocal("structurelib.blockhint.desc.4"));//Make sure it does not contain Air material. + aList.add(EnumChatFormatting.AQUA.toString() + + EnumChatFormatting.BOLD + + translateToLocal( + "structurelib.blockhint.desc.4")); // Make sure it does not contain Air material. break; case 15: - aList.add(EnumChatFormatting.BLUE.toString() + translateToLocal("structurelib.blockhint.desc.5"));//ERROR, what did u expect? + aList.add(EnumChatFormatting.BLUE.toString() + + translateToLocal("structurelib.blockhint.desc.5")); // ERROR, what did u expect? break; - default://WTF? + default: // WTF? aList.add("Damn son where did you get that!?"); aList.add(EnumChatFormatting.BLUE.toString() + "From outer space... I guess..."); } diff --git a/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java b/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java index 3ff5e22e..14b2a784 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java +++ b/src/main/java/com/gtnewhorizon/structurelib/item/ItemConstructableTrigger.java @@ -1,18 +1,19 @@ package com.gtnewhorizon.structurelib.item; -import com.gtnewhorizon.structurelib.StructureLib; -import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.world.World; - -import java.util.List; - import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID; import static net.minecraft.util.EnumChatFormatting.BLUE; import static net.minecraft.util.StatCollector.translateToLocal; +import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility; +import java.util.List; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + public class ItemConstructableTrigger extends Item { public ItemConstructableTrigger() { setUnlocalizedName("structurelib.constructableTrigger"); @@ -21,17 +22,51 @@ public class ItemConstructableTrigger extends Item { } @Override - public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ) { + public boolean onItemUseFirst( + ItemStack stack, + EntityPlayer player, + World world, + int x, + int y, + int z, + int side, + float hitX, + float hitY, + float hitZ) { return ConstructableUtility.handle(stack, player, world, x, y, z, side); } @SuppressWarnings("unchecked") @Override public void addInformation(ItemStack aStack, EntityPlayer ep, List aList, boolean boo) { - aList.add(translateToLocal("item.structurelib.constructableTrigger.desc.0"));//Triggers Constructable Interface - aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.1"));//Shows multiblock construction details, - aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.2"));//just Use on a multiblock controller. - aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.3"));//(Sneak Use in creative to build) - aList.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.4"));//Quantity affects tier/mode/type + aList.add( + translateToLocal("item.structurelib.constructableTrigger.desc.0")); // Triggers Constructable Interface + aList.add(BLUE + + translateToLocal( + "item.structurelib.constructableTrigger.desc.1")); // Shows multiblock construction details, + aList.add(BLUE + + translateToLocal( + "item.structurelib.constructableTrigger.desc.2")); // just Use on a multiblock controller. + aList.add(BLUE + + translateToLocal( + "item.structurelib.constructableTrigger.desc.3")); // (Sneak Use in creative to build) + aList.add(BLUE + + translateToLocal("item.structurelib.constructableTrigger.desc.4")); // Quantity affects tier/mode/type + } + + @Override + public void onUpdate( + ItemStack p_77663_1_, World p_77663_2_, Entity p_77663_3_, int p_77663_4_, boolean p_77663_5_) { + // remove LastUse tags if it times out, + // so it can stack with other trigger item stacks + // don't remove it too soon though, to prevent chat spam + // this might be perceived as a hack, but ok this is simple enough to implement + if (p_77663_1_.hasTagCompound() + && p_77663_1_.getTagCompound().getLong("LastUse") + ConstructableUtility.COOLDOWN * 2 + < StructureLib.getOverworldTime()) { + NBTTagCompound tag = p_77663_1_.getTagCompound(); + tag.removeTag("LastUse"); + if (tag.hasNoTags()) p_77663_1_.setTagCompound(null); + } } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java b/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java index 3e000fd1..88538047 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java +++ b/src/main/java/com/gtnewhorizon/structurelib/item/ItemFrontRotationTool.java @@ -1,20 +1,19 @@ package com.gtnewhorizon.structurelib.item; +import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID; +import static net.minecraft.util.StatCollector.translateToLocal; + import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.alignment.AlignmentUtility; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; +import java.util.List; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumChatFormatting; import net.minecraft.world.World; -import java.util.List; - -import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID; -import static net.minecraft.util.StatCollector.translateToLocal; - public class ItemFrontRotationTool extends Item { public ItemFrontRotationTool() { setMaxStackSize(1); @@ -24,15 +23,27 @@ public class ItemFrontRotationTool extends Item { } @Override - public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side, float hitX, float hitY, float hitZ) { + public boolean onItemUseFirst( + ItemStack stack, + EntityPlayer player, + World world, + int x, + int y, + int z, + int side, + float hitX, + float hitY, + float hitZ) { return AlignmentUtility.handle(player, world, x, y, z); } @Override @SideOnly(Side.CLIENT) public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List aList, boolean p_77624_4_) { - aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0"));//Triggers Front Rotation Interface - aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.1"));//Rotates only the front panel, - aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.2"));//which allows structure rotation. + aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0")); // Triggers Front Rotation Interface + aList.add(EnumChatFormatting.BLUE + + translateToLocal("item.structurelib.frontRotationTool.desc.1")); // Rotates only the front panel, + aList.add(EnumChatFormatting.BLUE + + translateToLocal("item.structurelib.frontRotationTool.desc.2")); // which allows structure rotation. } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java b/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java index 1805433b..16e7248d 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java +++ b/src/main/java/com/gtnewhorizon/structurelib/net/AlignmentMessage.java @@ -21,8 +21,7 @@ public abstract class AlignmentMessage implements IMessage { int mPosD; int mAlign; - public AlignmentMessage() { - } + public AlignmentMessage() {} @Override public void fromBytes(ByteBuf pBuffer) { @@ -47,11 +46,9 @@ public abstract class AlignmentMessage implements IMessage { } private AlignmentMessage(IAlignmentProvider provider) { - if (!(provider instanceof TileEntity)) - throw new IllegalArgumentException("Provider must be a TileEntity"); + if (!(provider instanceof TileEntity)) throw new IllegalArgumentException("Provider must be a TileEntity"); IAlignment alignment = provider.getAlignment(); - if (alignment == null) - throw new IllegalArgumentException("Passed in provider does not provide an alignment!"); + if (alignment == null) throw new IllegalArgumentException("Passed in provider does not provide an alignment!"); TileEntity base = (TileEntity) provider; mPosX = base.xCoord; mPosY = base.yCoord; @@ -69,8 +66,7 @@ public abstract class AlignmentMessage implements IMessage { } public static class AlignmentQuery extends AlignmentMessage { - public AlignmentQuery() { - } + public AlignmentQuery() {} public AlignmentQuery(IAlignmentProvider provider) { super(provider); @@ -82,8 +78,7 @@ public abstract class AlignmentMessage implements IMessage { } public static class AlignmentData extends AlignmentMessage { - public AlignmentData() { - } + public AlignmentData() {} private AlignmentData(AlignmentQuery query) { mPosX = query.mPosX; @@ -106,7 +101,9 @@ public abstract class AlignmentMessage implements IMessage { @Override public IMessage onMessage(AlignmentData pMessage, MessageContext pCtx) { if (StructureLib.getCurrentPlayer().worldObj.provider.dimensionId == pMessage.mPosD) { - TileEntity te = StructureLib.getCurrentPlayer().worldObj.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ); + TileEntity te = StructureLib.getCurrentPlayer() + .worldObj + .getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ); if (te instanceof IAlignmentProvider) { IAlignment alignment = ((IAlignmentProvider) te).getAlignment(); if (alignment != null) { @@ -126,8 +123,7 @@ public abstract class AlignmentMessage implements IMessage { TileEntity te = world.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ); if (te instanceof IAlignmentProvider) { IAlignment alignment = ((IAlignmentProvider) te).getAlignment(); - if (alignment == null) - return null; + if (alignment == null) return null; pMessage.mAlign = alignment.getExtendedFacing().getIndex(); return new AlignmentData(pMessage); } @@ -135,4 +131,4 @@ public abstract class AlignmentMessage implements IMessage { return null; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java b/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java new file mode 100644 index 00000000..88fab993 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/net/UpdateHintParticleMessage.java @@ -0,0 +1,77 @@ +package com.gtnewhorizon.structurelib.net; + +import static com.gtnewhorizon.structurelib.StructureLib.LOGGER; + +import com.gtnewhorizon.structurelib.StructureLib; +import com.gtnewhorizon.structurelib.StructureLibAPI; +import cpw.mods.fml.common.network.simpleimpl.IMessage; +import cpw.mods.fml.common.network.simpleimpl.IMessageHandler; +import cpw.mods.fml.common.network.simpleimpl.MessageContext; +import io.netty.buffer.ByteBuf; + +public class UpdateHintParticleMessage implements IMessage { + private int x; + private short y; + private int z; + private short r; + private short g; + private short b; + private short a; + + public UpdateHintParticleMessage() {} + + public UpdateHintParticleMessage(int x, short y, int z, short r, short g, short b, short a) { + this.x = x; + this.y = y; + this.z = z; + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + @Override + public void fromBytes(ByteBuf buf) { + x = buf.readInt(); + y = buf.readShort(); + z = buf.readInt(); + r = buf.readUnsignedByte(); + g = buf.readUnsignedByte(); + b = buf.readUnsignedByte(); + a = buf.readUnsignedByte(); + } + + @Override + public void toBytes(ByteBuf buf) { + buf.writeInt(x); + buf.writeShort(y); + buf.writeInt(z); + buf.writeByte(r); + buf.writeByte(g); + buf.writeByte(b); + buf.writeByte(a); + } + + public static class Handler implements IMessageHandler { + @Override + public IMessage onMessage(UpdateHintParticleMessage msg, MessageContext ctx) { + boolean updateResult = StructureLibAPI.updateHintParticleTint( + StructureLib.getCurrentPlayer(), + StructureLib.getCurrentPlayer().worldObj, + msg.x, + msg.y, + msg.z, + new short[] { + msg.r, msg.g, msg.b, msg.a, + }); + if (StructureLib.DEBUG_MODE) + LOGGER.debug( + "Server instructed to update hint particle at ({}, {}, {}), result {}!", + msg.x, + msg.y, + msg.z, + updateResult); + return null; + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java b/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java deleted file mode 100644 index dae0878e..00000000 --- a/src/main/java/com/gtnewhorizon/structurelib/proxy/CommonProxy.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.gtnewhorizon.structurelib.proxy; - -import cpw.mods.fml.common.event.FMLPreInitializationEvent; -import net.minecraft.block.Block; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.util.IIcon; -import net.minecraft.world.World; - -public class CommonProxy { - public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) { - } - - public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) { - } - - public void hintParticle(World w, int x, int y, int z, IIcon[] icons) { - } - - public void hintParticle(World w, int x, int y, int z, Block block, int meta) { - } - - public EntityPlayer getCurrentPlayer() { - return null; - } - - public boolean isCurrentPlayer(EntityPlayer player) { - return false; - } - - public void addClientSideChatMessages(String... messages) { - } - - public void startHinting(World w) { - } - - public void endHinting(World w) { - } - - public void preInit(FMLPreInitializationEvent e) { - } -} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java new file mode 100644 index 00000000..69c9eae1 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java @@ -0,0 +1,77 @@ +package com.gtnewhorizon.structurelib.structure; + +import com.gtnewhorizon.structurelib.util.InventoryIterable; +import com.gtnewhorizon.structurelib.util.InventoryUtility; +import com.gtnewhorizon.structurelib.util.ItemStackPredicate; +import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode; +import java.util.Map; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public interface IItemSource { + /** + * We are probably not going to need to get a lot of items from this method + * so I don't care about the overhead of storing all fetched items even if downstream code doesn't need it + */ + @Nonnull + Map take(Predicate predicate, boolean simulate, int count); + + default ItemStack takeOne(Predicate predicate, boolean simulate) { + Map take = take(predicate, simulate, 1); + return take.isEmpty() ? null : take.keySet().iterator().next(); + } + + default boolean takeAll(Predicate predicate, boolean simulate, int count) { + // fast path for 1 item take requests + if (count == 1) return takeOne(predicate, simulate) != null; + Map have = take(predicate, true, count); + if (have.values().stream().mapToInt(Integer::intValue).sum() < count) return false; + take(predicate, simulate, count); + return true; + } + + default boolean takeOne(ItemStack stack, boolean simulate) { + if (stack == null || stack.getItem() == null || stack.stackSize != 1) throw new IllegalArgumentException(); + ItemStack took = takeOne(ItemStackPredicate.from(stack, NBTMode.EXACT), simulate); + return took == null || took.stackSize <= 0; + } + + default boolean takeAll(ItemStack stack, boolean simulate) { + // fast path for 1 item take requests + if (stack.stackSize == 1) return takeOne(stack, simulate); + ItemStackPredicate predicate = ItemStackPredicate.from(stack, NBTMode.EXACT); + Map have = take(predicate, true, stack.stackSize); + if (have.values().stream().mapToInt(Integer::intValue).sum() < stack.stackSize) return false; + take(predicate, simulate, stack.stackSize); + return true; + } + + static IItemSource fromPlayer(EntityPlayerMP player) { + return new IItemSource() { + @Nonnull + @Override + public Map take(Predicate p, boolean s, int c) { + return InventoryUtility.takeFromInventory(player, p, s, c); + } + + @Override + public boolean takeOne(ItemStack stack, boolean simulate) { + if (stack == null || stack.getItem() == null || stack.stackSize != 1) + throw new IllegalArgumentException(); + return InventoryUtility.takeFromInventory(player, stack, simulate) == 1; + } + + @Override + public boolean takeAll(ItemStack stack, boolean simulate) { + return InventoryUtility.takeFromInventory(player, stack, simulate) == stack.stackSize; + } + }; + } + + static IItemSource fromInventory(IInventory inv) { + return (p, s, c) -> InventoryUtility.takeFromInventory(new InventoryIterable<>(inv), p, s, c, true); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/ISpecialItemBlock.java b/src/main/java/com/gtnewhorizon/structurelib/structure/ISpecialItemBlock.java new file mode 100644 index 00000000..d7ca064f --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/ISpecialItemBlock.java @@ -0,0 +1,7 @@ +package com.gtnewhorizon.structurelib.structure; + +import net.minecraft.block.Block; + +public interface ISpecialItemBlock { + int getItemMetaFromBlockMeta(Block block, int blockMeta); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java index 6b9082ff..110ee648 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureDefinition.java @@ -1,14 +1,14 @@ package com.gtnewhorizon.structurelib.structure; +import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; + import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import java.util.Arrays; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.world.World; -import java.util.Arrays; - -import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; - public interface IStructureDefinition { /** * Used internally @@ -21,50 +21,187 @@ public interface IStructureDefinition { */ IStructureElement[] getStructureFor(String name); - default boolean check(T object, String piece, World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC, - boolean forceCheckAllBlocks) { - return iterate(object, null, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, false, forceCheckAllBlocks); + default boolean check( + T object, + String piece, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + boolean forceCheckAllBlocks) { + return iterate( + object, + null, + getStructureFor(piece), + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + false, + forceCheckAllBlocks); } - default boolean hints(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC) { - return iterate(object, trigger, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, true, null); + default boolean hints( + T object, + ItemStack trigger, + String piece, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC) { + return iterate( + object, + trigger, + getStructureFor(piece), + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + true, + null); } - default boolean build(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC) { - return iterate(object, trigger, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, false, null); + default boolean build( + T object, + ItemStack trigger, + String piece, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC) { + return iterate( + object, + trigger, + getStructureFor(piece), + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + false, + null); } - default boolean buildOrHints(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC, - boolean hintsOnly) { - return iterate(object, trigger, getStructureFor(piece), world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, hintsOnly, null); + default boolean buildOrHints( + T object, + ItemStack trigger, + String piece, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + boolean hintsOnly) { + return iterate( + object, + trigger, + getStructureFor(piece), + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + hintsOnly, + null); } - static boolean iterate(T object, ItemStack trigger, IStructureElement[] elements, World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC, - boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) { + /** + * @param elementBudget build up to this many elements + * @param source from where to drain resource + * @param actor source of action + * @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if anything + * would be placed. use with caution. + * if in doubt, call {@link #check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)} + * after this call and set this parameter to false + * @return number of elements built, or -1 if structure done + * @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int) + */ + default int survivalBuild( + T object, + ItemStack trigger, + String piece, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + int elementBudget, + IItemSource source, + EntityPlayerMP actor, + boolean check) { + SurvivalBuildStructureWalker walker = + new SurvivalBuildStructureWalker<>(object, trigger, source, actor, elementBudget, check); + StructureUtility.iterateV2( + getStructureFor(piece), + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + walker, + "survivalBuild"); + return walker.getBuilt(); + } + + static boolean iterate( + T object, + ItemStack trigger, + IStructureElement[] elements, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + boolean hintsOnly, + Boolean checkBlocksIfNotNullForceCheckAllIfTrue) { if (world.isRemote ^ hintsOnly) { return false; } - //change base position to base offset + // change base position to base offset basePositionA = -basePositionA; basePositionB = -basePositionB; basePositionC = -basePositionC; - int[] abc = new int[]{basePositionA, basePositionB, basePositionC}; + int[] abc = new int[] {basePositionA, basePositionB, basePositionC}; int[] xyz = new int[3]; if (checkBlocksIfNotNullForceCheckAllIfTrue != null) { @@ -80,18 +217,28 @@ public interface IStructureDefinition { xyz[1] += basePositionY; xyz[2] += basePositionZ; + StructureLib.LOGGER.info( + "Multi [{}, {}, {}] step @ {} {}", + basePositionX, + basePositionY, + basePositionZ, + Arrays.toString(xyz), + Arrays.toString(abc)); + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) { if (DEBUG_MODE) { - StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] failed @ " + - Arrays.toString(xyz) + " " + Arrays.toString(abc)); + StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + + basePositionZ + "] failed @ " + Arrays.toString(xyz) + " " + + Arrays.toString(abc)); } return false; } } else { if (DEBUG_MODE) { - StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] !blockExists @ " + - Arrays.toString(xyz) + " " + Arrays.toString(abc)); + StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + + basePositionZ + "] !blockExists @ " + Arrays.toString(xyz) + " " + + Arrays.toString(abc)); } return false; } @@ -110,18 +257,28 @@ public interface IStructureDefinition { xyz[1] += basePositionY; xyz[2] += basePositionZ; + StructureLib.LOGGER.info( + "Multi [{}, {}, {}] step @ {} {}", + basePositionX, + basePositionY, + basePositionZ, + Arrays.toString(xyz), + Arrays.toString(abc)); + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) { if (DEBUG_MODE) { - StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] failed @ " + - Arrays.toString(xyz) + " " + Arrays.toString(abc)); + StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + + basePositionZ + "] failed @ " + Arrays.toString(xyz) + " " + + Arrays.toString(abc)); } return false; } } else { if (DEBUG_MODE) { - StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] !blockExists @ " + - Arrays.toString(xyz) + " " + Arrays.toString(abc)); + StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + + basePositionZ + "] !blockExists @ " + Arrays.toString(xyz) + " " + + Arrays.toString(abc)); } } abc[0] += 1; @@ -129,7 +286,8 @@ public interface IStructureDefinition { } } if (DEBUG_MODE) { - StructureLib.LOGGER.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass"); + StructureLib.LOGGER.info( + "Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass"); } } else { if (hintsOnly) { diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java index 22764437..53b07978 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElement.java @@ -1,6 +1,14 @@ package com.gtnewhorizon.structurelib.structure; +import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; +import static com.gtnewhorizon.structurelib.StructureLib.LOGGER; +import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE; + +import com.gtnewhorizon.structurelib.StructureLibAPI; +import java.util.function.Consumer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; import net.minecraft.world.World; /** @@ -13,6 +21,50 @@ public interface IStructureElement { boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger); + /** + * Try place the block by taking resource from given IItemSource. + * + * @param s drain resources from this place + * @param actor source of action. + * @param chatter + */ + default PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit"); + if (DEBUG_MODE) + LOGGER.error( + "Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}", + getClass().getName()); + if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT; + return PlaceResult.SKIP; + } + + /** + * Forget the messed up class dependency graph for now. this is just so convenient. + */ + default IStructureElementNoPlacement noPlacement() { + return new IStructureElementNoPlacement() { + + @Override + public boolean check(T t, World world, int x, int y, int z) { + return IStructureElement.this.check(t, world, x, y, z); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + return IStructureElement.this.spawnHint(t, world, x, y, z, trigger); + } + }; + } + default int getStepA() { return 1; } @@ -40,4 +92,31 @@ public interface IStructureElement { default boolean isNavigating() { return false; } + + enum PlaceResult { + /** + * This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or + * some other unforeseen situations. + */ + SKIP, + /** + * This element's space is occupied by other stuff and require player attention, or player missing required resource, + * or the block cannot be placed due to obscure mechanisms, or some other unforeseen situations. + */ + REJECT, + /** + * Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait for next round. + */ + STOP, + /** + * Element placed successfully. + */ + ACCEPT, + /** + * Combination of ACCEPT and STOP. + * + * Element placed successfully. To proceed further would require stopping the placement and wait for next round. + */ + ACCEPT_STOP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java index 52ccc487..82a697af 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementChain.java @@ -1,6 +1,11 @@ package com.gtnewhorizon.structurelib.structure; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; import net.minecraft.world.World; /** @@ -38,4 +43,35 @@ public interface IStructureElementChain extends IStructureElement { } return false; } + + @Override + default PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + boolean haveSkip = false; + List bufferedNoise = new ArrayList<>(); + for (IStructureElement fallback : fallbacks()) { + PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add); + switch (result) { + case REJECT: + break; + case SKIP: + haveSkip = true; + break; + default: + return result; + } + } + // dump all that noise back into upstream + bufferedNoise.forEach(chatter); + // TODO need reconsider to ensure this is the right course of action + return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java index 3ba0c6ca..854f405b 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementCheckOnly.java @@ -1,6 +1,9 @@ package com.gtnewhorizon.structurelib.structure; +import java.util.function.Consumer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; import net.minecraft.world.World; public interface IStructureElementCheckOnly extends IStructureElement { @@ -13,4 +16,18 @@ public interface IStructureElementCheckOnly extends IStructureElement { default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return false; } + + @Override + default PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return PlaceResult.SKIP; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementDeferred.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementDeferred.java index cf5ab2d9..44d02988 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementDeferred.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementDeferred.java @@ -3,5 +3,4 @@ package com.gtnewhorizon.structurelib.structure; /** * Use StructureUtility to instantiate */ -public interface IStructureElementDeferred extends IStructureElement { -} +public interface IStructureElementDeferred extends IStructureElement {} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java index e80d23ed..db861178 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureElementNoPlacement.java @@ -1,6 +1,9 @@ package com.gtnewhorizon.structurelib.structure; +import java.util.function.Consumer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; import net.minecraft.world.World; public interface IStructureElementNoPlacement extends IStructureElement { @@ -8,4 +11,23 @@ public interface IStructureElementNoPlacement extends IStructureElement { default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { return false; } + + @Override + default PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return PlaceResult.REJECT; + } + + @Override + default IStructureElementNoPlacement noPlacement() { + return this; + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java index be0c7b08..00e3a89e 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureNavigate.java @@ -1,6 +1,9 @@ package com.gtnewhorizon.structurelib.structure; +import java.util.function.Consumer; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; import net.minecraft.world.World; /** @@ -22,6 +25,20 @@ interface IStructureNavigate extends IStructureElement { return true; } + @Override + default PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return PlaceResult.SKIP; + } + default boolean isNavigating() { return true; } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java new file mode 100644 index 00000000..a6710169 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IStructureWalker.java @@ -0,0 +1,7 @@ +package com.gtnewhorizon.structurelib.structure; + +import net.minecraft.world.World; + +public interface IStructureWalker { + boolean visit(IStructureElement element, World world, int x, int y, int z); +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java index a1f326d7..6739673e 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/LazyStructureElement.java @@ -1,9 +1,11 @@ package com.gtnewhorizon.structurelib.structure; -import net.minecraft.item.ItemStack; -import net.minecraft.world.World; - +import java.util.function.Consumer; import java.util.function.Function; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IChatComponent; +import net.minecraft.world.World; class LazyStructureElement implements IStructureElementDeferred { private Function> to; @@ -35,4 +37,18 @@ class LazyStructureElement implements IStructureElementDeferred { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return get(t).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java index 97ffc8d4..38edb59c 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureDefinition.java @@ -1,13 +1,12 @@ package com.gtnewhorizon.structurelib.structure; -import com.gtnewhorizon.structurelib.util.Vec3Impl; - -import java.util.*; -import java.util.stream.Collectors; - import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; import static com.gtnewhorizon.structurelib.structure.StructureUtility.*; +import com.gtnewhorizon.structurelib.util.Vec3Impl; +import java.util.*; +import java.util.stream.Collectors; + public class StructureDefinition implements IStructureDefinition { private final Map> elements; private final Map shapes; @@ -18,9 +17,9 @@ public class StructureDefinition implements IStructureDefinition { } private StructureDefinition( - Map> elements, - Map shapes, - Map[]> structures) { + Map> elements, + Map shapes, + Map[]> structures) { this.elements = elements; this.shapes = shapes; this.structures = structures; @@ -142,17 +141,15 @@ public class StructureDefinition implements IStructureDefinition { private Map[]> compileElementSetMap() { Set missing = findMissing(); if (missing.isEmpty()) { - return shapes.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, - e -> e.getValue().chars() - .mapToObj(c -> (char) c) - .distinct() - .map(elements::get) - .toArray(IStructureElement[]::new) - )); + return shapes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue() + .chars() + .mapToObj(c -> (char) c) + .distinct() + .map(elements::get) + .toArray(IStructureElement[]::new))); } else { - throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " + - Arrays.toString(missing.toArray())); + throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " + + Arrays.toString(missing.toArray())); } } @@ -160,22 +157,22 @@ public class StructureDefinition implements IStructureDefinition { private Map[]> compileStructureMap() { Set missing = findMissing(); if (missing.isEmpty()) { - return shapes.entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, - e -> e.getValue().chars().mapToObj(c -> elements.get((char) c)).toArray(IStructureElement[]::new) - )); + return shapes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue() + .chars() + .mapToObj(c -> elements.get((char) c)) + .toArray(IStructureElement[]::new))); } else { - throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " + - Arrays.toString(missing.toArray())); + throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " + + Arrays.toString(missing.toArray())); } } private Set findMissing() { return shapes.values().stream() - .flatMapToInt(CharSequence::chars) - .filter(i -> !elements.containsKey((char) i)) - .boxed() - .collect(Collectors.toSet()); + .flatMapToInt(CharSequence::chars) + .filter(i -> !elements.containsKey((char) i)) + .boxed() + .collect(Collectors.toSet()); } } @@ -194,8 +191,7 @@ public class StructureDefinition implements IStructureDefinition { @Override public IStructureElement[] getStructureFor(String name) { IStructureElement[] elements = structures.get(name); - if (elements == null) - throw new NoSuchElementException(name); + if (elements == null) throw new NoSuchElementException(name); return elements; } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 2f7d7615..58cab5fa 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -1,45 +1,48 @@ package com.gtnewhorizon.structurelib.structure; +import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE; +import static java.lang.Integer.MIN_VALUE; + +import com.gtnewhorizon.structurelib.StructureLib; import com.gtnewhorizon.structurelib.StructureLibAPI; import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing; +import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult; import com.gtnewhorizon.structurelib.structure.adders.IBlockAdder; import com.gtnewhorizon.structurelib.structure.adders.ITileAdder; +import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode; import com.gtnewhorizon.structurelib.util.Vec3Impl; import cpw.mods.fml.common.registry.GameRegistry; +import java.util.*; +import java.util.function.*; +import javax.annotation.Nullable; import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; +import net.minecraft.item.Item; +import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.IChatComponent; import net.minecraft.util.IIcon; import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.function.*; - -import static java.lang.Integer.MIN_VALUE; - /** * Fluent API for structure checking! *

* (Just import static this class to have a nice fluent syntax while defining structure definitions) */ public class StructureUtility { - private static final String NICE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz=|!@#$%&()[]{};:<>/?_,.*^'`"; + private static final String NICE_CHARS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz=|!@#$%&()[]{};:<>/?_,.*^'`"; + @SuppressWarnings("rawtypes") private static final Map STEP = new HashMap<>(); + @SuppressWarnings("rawtypes") private static final IStructureElement AIR = new IStructureElement() { @Override @@ -58,7 +61,25 @@ public class StructureUtility { world.setBlock(x, y, z, Blocks.air, 0, 2); return false; } + + @Override + public PlaceResult survivalPlaceBlock( + Object o, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(o, world, x, y, z)) return PlaceResult.SKIP; + if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT; + world.setBlock(x, y, z, Blocks.air, 0, 2); + return PlaceResult.ACCEPT; + } }; + @SuppressWarnings("rawtypes") private static final IStructureElement NOT_AIR = new IStructureElement() { @Override @@ -77,7 +98,28 @@ public class StructureUtility { world.setBlock(x, y, z, StructureLibAPI.getBlockHint(), 14, 2); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + Object o, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(o, world, x, y, z)) return PlaceResult.SKIP; + // user should place anything here. + // maybe make this configurable, but for now we try to take some cobble from user + if (s.takeOne(new ItemStack(Blocks.cobblestone), false)) { + world.setBlock(x, y, z, Blocks.cobblestone, 0, 2); + } + return PlaceResult.REJECT; + } }; + @SuppressWarnings("rawtypes") private static final IStructureElement ERROR = new IStructureElement() { @Override @@ -95,10 +137,120 @@ public class StructureUtility { public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) { return true; } + + @Override + public PlaceResult survivalPlaceBlock( + Object o, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return PlaceResult.REJECT; + } }; - private StructureUtility() { + private StructureUtility() {} + /** + * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + */ + public static PlaceResult survivalPlaceBlock( + Block block, int meta, World world, int x, int y, int z, IItemSource s, EntityPlayerMP actor) { + return survivalPlaceBlock(block, meta, world, x, y, z, s, actor, null); + } + + /** + * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + */ + public static PlaceResult survivalPlaceBlock( + Block block, + int meta, + World world, + int x, + int y, + int z, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (block == null) throw new NullPointerException(); + if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT; + Item itemBlock = Item.getItemFromBlock(block); + int itemMeta = itemBlock instanceof ISpecialItemBlock + ? ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta) + : meta; + if (!s.takeOne(new ItemStack(itemBlock, 1, itemMeta), false)) { + if (chatter != null) + chatter.accept(new ChatComponentTranslation( + "structurelib.autoplace.error.no_simple_block", + new ItemStack(itemBlock, 1, itemMeta).func_151000_E())); + return PlaceResult.REJECT; + } + if (block instanceof ICustomBlockSetting) { + ICustomBlockSetting block2 = (ICustomBlockSetting) block; + block2.setBlock(world, x, y, z, meta); + } else { + world.setBlock(x, y, z, block, meta, 2); + } + return PlaceResult.ACCEPT; + } + + /** + * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + * + * @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock! + */ + public static PlaceResult survivalPlaceBlock( + ItemStack stack, + NBTMode nbtMode, + NBTTagCompound tag, + boolean assumeStackPresent, + World world, + int x, + int y, + int z, + IItemSource s, + EntityPlayerMP actor) { + return survivalPlaceBlock(stack, nbtMode, tag, assumeStackPresent, world, x, y, z, s, actor, null); + } + + /** + * This method assume block at given coord is NOT acceptable, but may or may not be trivially replaceable + * + * @param stack a valid stack with stack size of exactly 1. Must be of an ItemBlock! + * @param chatter pass in null to suppress error output, or just use the other overload + */ + public static PlaceResult survivalPlaceBlock( + ItemStack stack, + NBTMode nbtMode, + NBTTagCompound tag, + boolean assumeStackPresent, + World world, + int x, + int y, + int z, + IItemSource s, + EntityPlayerMP actor, + @Nullable Consumer chatter) { + if (stack == null) throw new NullPointerException(); + if (stack.stackSize != 1) throw new IllegalArgumentException(); + if (!(stack.getItem() instanceof ItemBlock)) throw new IllegalArgumentException(); + if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT; + if (!assumeStackPresent && !s.takeOne(stack, true)) { + if (chatter != null) + chatter.accept(new ChatComponentTranslation( + "structurelib.autoplace.error.no_item_stack", stack.func_151000_E())); + return PlaceResult.REJECT; + } + if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f)) + return PlaceResult.REJECT; + if (!s.takeOne(stack, false)) + // this is bad! probably an exploit somehow. Let's nullify the block we just placed instead + world.setBlockToAir(x, y, z); + return PlaceResult.ACCEPT; } @SuppressWarnings("unchecked") @@ -121,7 +273,7 @@ public class StructureUtility { return ERROR; } - //region hint only + // region hint only /** * Check always returns: true. @@ -178,9 +330,9 @@ public class StructureUtility { }; } - //endregion + // endregion - //region block + // region block /** * A more primitive variant of {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)} @@ -188,7 +340,11 @@ public class StructureUtility { * * @see #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function) */ - public static IStructureElementCheckOnly ofBlocksTiered(ITierConverter tierExtractor, @Nullable TIER notSet, BiConsumer setter, Function getter) { + public static IStructureElementCheckOnly ofBlocksTiered( + ITierConverter tierExtractor, + @Nullable TIER notSet, + BiConsumer setter, + Function getter) { if (tierExtractor == null) throw new IllegalArgumentException(); if (setter == null) throw new IllegalArgumentException(); if (getter == null) throw new IllegalArgumentException(); @@ -220,7 +376,12 @@ public class StructureUtility { * @param setter a function to set the current tier into T * @param tierExtractor a function to extract tier info from a block. */ - public static IStructureElement ofBlocksTiered(ITierConverter tierExtractor, @Nullable List> allKnownTiers, @Nullable TIER notSet, BiConsumer setter, Function getter) { + public static IStructureElement ofBlocksTiered( + ITierConverter tierExtractor, + @Nullable List> allKnownTiers, + @Nullable TIER notSet, + BiConsumer setter, + Function getter) { List> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers; if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException(); IStructureElementCheckOnly check = ofBlocksTiered(tierExtractor, notSet, setter, getter); @@ -237,8 +398,7 @@ public class StructureUtility { @Override public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { Pair hint = getHint(trigger); - if (hint == null) - return false; + if (hint == null) return false; StructureLibAPI.hintParticle(world, x, y, z, hint.getKey(), hint.getValue()); return true; } @@ -246,8 +406,7 @@ public class StructureUtility { @Override public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { Pair hint = getHint(trigger); - if (hint == null) - return false; + if (hint == null) return false; if (hint.getKey() instanceof ICustomBlockSetting) { ICustomBlockSetting block = (ICustomBlockSetting) hint.getKey(); block.setBlock(world, x, y, z, hint.getValue()); @@ -256,6 +415,24 @@ public class StructureUtility { } return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + Pair hint = getHint(trigger); + if (hint == null) return PlaceResult.REJECT; // TODO or SKIP? + return StructureUtility.survivalPlaceBlock( + hint.getKey(), hint.getValue(), world, x, y, z, s, actor, chatter); + } }; } @@ -266,6 +443,19 @@ public class StructureUtility { * said mod is present, otherwise bad things will happen later! */ public static IStructureElement ofBlockUnlocalizedName(String modid, String unlocalizedName, int meta) { + return ofBlockUnlocalizedName(modid, unlocalizedName, meta, false); + } + + /** + * Denote a block using unlocalized names. This can be useful to get around mod loading order issues. + *

+ * While no immediate error will be thrown, client code should ensure said mod is loaded and + * said mod is present, otherwise bad things will happen later! + *

+ * Will place block or hint using the given meta if wildcard is true. + */ + public static IStructureElement ofBlockUnlocalizedName( + String modid, String unlocalizedName, int meta, boolean wildcard) { if (StringUtils.isBlank(unlocalizedName)) throw new IllegalArgumentException(); if (meta < 0) throw new IllegalArgumentException(); if (meta > 15) throw new IllegalArgumentException(); @@ -274,27 +464,44 @@ public class StructureUtility { private Block block; private Block getBlock() { - if (block == null) - block = GameRegistry.findBlock(modid, unlocalizedName); + if (block == null) block = GameRegistry.findBlock(modid, unlocalizedName); return block; } @Override public boolean check(T t, World world, int x, int y, int z) { - return world.getBlock(x, y, z) == getBlock() && world.getBlockMetadata(x, y, z) == meta; + return world.getBlock(x, y, z) == getBlock() && (wildcard || world.getBlockMetadata(x, y, z) == meta); } @Override public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + if (getBlock() == null) return error().spawnHint(t, world, x, y, z, trigger); StructureLibAPI.hintParticle(world, x, y, z, getBlock(), meta); return true; } @Override public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + if (getBlock() == null) return error().placeBlock(t, world, x, y, z, trigger); world.setBlock(x, y, z, getBlock(), meta, 2); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + if (getBlock() == null) return PlaceResult.REJECT; + return StructureUtility.survivalPlaceBlock(getBlock(), meta, world, x, y, z, s, actor, chatter); + } }; } @@ -305,7 +512,8 @@ public class StructureUtility { * This is slightly different to using the other overload and another element in a {@link #ofChain(IStructureElement[])}, * as that combination would cause crash, where this won't. */ - public static IStructureElement ofBlockUnlocalizedName(String modid, String unlocalizedName, int meta, IStructureElement fallback) { + public static IStructureElement ofBlockUnlocalizedName( + String modid, String unlocalizedName, int meta, IStructureElement fallback) { if (StringUtils.isBlank(unlocalizedName)) throw new IllegalArgumentException(); if (meta < 0) throw new IllegalArgumentException(); if (meta > 15) throw new IllegalArgumentException(); @@ -325,10 +533,8 @@ public class StructureUtility { @Override public boolean check(T t, World world, int x, int y, int z) { - if (init()) - return world.getBlock(x, y, z) != block && world.getBlockMetadata(x, y, z) == meta; - else - return fallback.check(t, world, x, y, z); + if (init()) return world.getBlock(x, y, z) != block && world.getBlockMetadata(x, y, z) == meta; + else return fallback.check(t, world, x, y, z); } @Override @@ -336,8 +542,7 @@ public class StructureUtility { if (init()) { StructureLibAPI.hintParticle(world, x, y, z, block, meta); return true; - } else - return fallback.spawnHint(t, world, x, y, z, trigger); + } else return fallback.spawnHint(t, world, x, y, z, trigger); } @Override @@ -345,8 +550,23 @@ public class StructureUtility { if (init()) { world.setBlock(x, y, z, block, meta, 2); return true; - } else - return fallback.placeBlock(t, world, x, y, z, trigger); + } else return fallback.placeBlock(t, world, x, y, z, trigger); + } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + if (init()) return StructureUtility.survivalPlaceBlock(block, meta, world, x, y, z, s, actor, chatter); + return fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); } }; } @@ -354,7 +574,8 @@ public class StructureUtility { /** * Does not allow Block duplicates (with different meta) */ - public static IStructureElementNoPlacement ofBlocksFlatHint(Map blocsMap, Block hintBlock, int hintMeta) { + public static IStructureElementNoPlacement ofBlocksFlatHint( + Map blocsMap, Block hintBlock, int hintMeta) { if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) { throw new IllegalArgumentException(); } @@ -376,7 +597,8 @@ public class StructureUtility { /** * Allows block duplicates (with different meta) */ - public static IStructureElementNoPlacement ofBlocksMapHint(Map> blocsMap, Block hintBlock, int hintMeta) { + public static IStructureElementNoPlacement ofBlocksMapHint( + Map> blocsMap, Block hintBlock, int hintMeta) { if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) { throw new IllegalArgumentException(); } @@ -389,7 +611,8 @@ public class StructureUtility { @Override public boolean check(T t, World world, int x, int y, int z) { Block worldBlock = world.getBlock(x, y, z); - return blocsMap.getOrDefault(worldBlock, Collections.emptySet()).contains(worldBlock.getDamageValue(world, x, y, z)); + return blocsMap.getOrDefault(worldBlock, Collections.emptySet()) + .contains(worldBlock.getDamageValue(world, x, y, z)); } @Override @@ -400,7 +623,8 @@ public class StructureUtility { }; } - public static IStructureElementNoPlacement ofBlockHint(Block block, int meta, Block hintBlock, int hintMeta) { + public static IStructureElementNoPlacement ofBlockHint( + Block block, int meta, Block hintBlock, int hintMeta) { if (block == null || hintBlock == null) { throw new IllegalArgumentException(); } @@ -423,7 +647,8 @@ public class StructureUtility { return ofBlockHint(block, meta, block, meta); } - public static IStructureElementNoPlacement ofBlockAdderHint(IBlockAdder iBlockAdder, Block hintBlock, int hintMeta) { + public static IStructureElementNoPlacement ofBlockAdderHint( + IBlockAdder iBlockAdder, Block hintBlock, int hintMeta) { if (iBlockAdder == null || hintBlock == null) { throw new IllegalArgumentException(); } @@ -445,7 +670,8 @@ public class StructureUtility { /** * Does not allow Block duplicates (with different meta) */ - public static IStructureElement ofBlocksFlat(Map blocsMap, Block defaultBlock, int defaultMeta) { + public static IStructureElement ofBlocksFlat( + Map blocsMap, Block defaultBlock, int defaultMeta) { if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) { throw new IllegalArgumentException(); } @@ -468,6 +694,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } else { return new IStructureElement() { @@ -488,6 +730,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } } @@ -495,7 +753,8 @@ public class StructureUtility { /** * Allows block duplicates (with different meta) */ - public static IStructureElement ofBlocksMap(Map> blocsMap, Block defaultBlock, int defaultMeta) { + public static IStructureElement ofBlocksMap( + Map> blocsMap, Block defaultBlock, int defaultMeta) { if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) { throw new IllegalArgumentException(); } @@ -509,7 +768,8 @@ public class StructureUtility { @Override public boolean check(T t, World world, int x, int y, int z) { Block worldBlock = world.getBlock(x, y, z); - return blocsMap.getOrDefault(worldBlock, Collections.emptySet()).contains(worldBlock.getDamageValue(world, x, y, z)); + return blocsMap.getOrDefault(worldBlock, Collections.emptySet()) + .contains(worldBlock.getDamageValue(world, x, y, z)); } @Override @@ -523,13 +783,30 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } else { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { Block worldBlock = world.getBlock(x, y, z); - return blocsMap.getOrDefault(worldBlock, Collections.emptySet()).contains(worldBlock.getDamageValue(world, x, y, z)); + return blocsMap.getOrDefault(worldBlock, Collections.emptySet()) + .contains(worldBlock.getDamageValue(world, x, y, z)); } @Override @@ -543,6 +820,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } } @@ -570,6 +863,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } else { return new IStructureElement() { @@ -590,6 +899,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } } @@ -619,6 +944,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } else { return new IStructureElement() { @@ -638,6 +979,22 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (check(t, world, x, y, z)) return PlaceResult.SKIP; + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } } @@ -660,11 +1017,12 @@ public class StructureUtility { return ofBlockAnyMeta(block, block, defaultMeta); } - //endregion + // endregion - //region adders + // region adders - public static IStructureElement ofBlockAdder(IBlockAdder iBlockAdder, Block defaultBlock, int defaultMeta) { + public static IStructureElement ofBlockAdder( + IBlockAdder iBlockAdder, Block defaultBlock, int defaultMeta) { if (iBlockAdder == null || defaultBlock == null) { throw new IllegalArgumentException(); } @@ -687,6 +1045,21 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } else { return new IStructureElement() { @@ -707,6 +1080,21 @@ public class StructureUtility { StructureLibAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); return true; } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return StructureUtility.survivalPlaceBlock( + defaultBlock, defaultMeta, world, x, y, z, s, actor, chatter); + } }; } } @@ -715,7 +1103,8 @@ public class StructureUtility { return ofBlockAdder(iBlockAdder, StructureLibAPI.getBlockHint(), dots - 1); } - public static IStructureElementNoPlacement ofTileAdder(ITileAdder iTileAdder, Block hintBlock, int hintMeta) { + public static IStructureElementNoPlacement ofTileAdder( + ITileAdder iTileAdder, Block hintBlock, int hintMeta) { if (iTileAdder == null || hintBlock == null) { throw new IllegalArgumentException(); } @@ -735,7 +1124,8 @@ public class StructureUtility { }; } - public static IStructureElementNoPlacement ofSpecificTileAdder(BiPredicate iTileAdder, Class tileClass, Block hintBlock, int hintMeta) { + public static IStructureElementNoPlacement ofSpecificTileAdder( + BiPredicate iTileAdder, Class tileClass, Block hintBlock, int hintMeta) { if (iTileAdder == null || hintBlock == null || tileClass == null) { throw new IllegalArgumentException(); } @@ -754,13 +1144,15 @@ public class StructureUtility { } }; } - // No more hatch adder. Implement it via tile adder. We could of course add a wrapper around it in gregtech, but not any more in this standalone mod. + // No more hatch adder. Implement it via tile adder. We could of course add a wrapper around it in gregtech, but not + // any more in this standalone mod. - //endregion + // endregion - //region side effects + // region side effects - public static , T> IStructureElement onElementPass(Consumer onCheckPass, B element) { + public static , T> IStructureElement onElementPass( + Consumer onCheckPass, B element) { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { @@ -780,10 +1172,25 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return element.spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static , T> IStructureElement onElementFail(Consumer onFail, B element) { + public static , T> IStructureElement onElementFail( + Consumer onFail, B element) { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { @@ -803,15 +1210,40 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return element.spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - //endregion + // endregion /** * Enable this structure element only if given predicate returns true. */ - public static IStructureElement onlyIf(Predicate predicate, IStructureElement downstream) { + public static IStructureElement onlyIf( + Predicate predicate, IStructureElement downstream) { + return onlyIf(predicate, downstream, PlaceResult.SKIP); + } + + /** + * Enable this structure element only if given predicate returns true. + */ + public static IStructureElement onlyIf( + Predicate predicate, + IStructureElement downstream, + PlaceResult placeResultWhenDisabled) { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { @@ -827,6 +1259,22 @@ public class StructureUtility { public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { return predicate.test(t) && downstream.placeBlock(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + if (predicate.test(t)) + return downstream.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + return placeResultWhenDisabled; + } }; } @@ -857,7 +1305,8 @@ public class StructureUtility { } // region context - public static , E extends IStructureElement> IStructureElement withContext(E elem) { + public static , E extends IStructureElement> + IStructureElement withContext(E elem) { return new IStructureElement() { @Override public boolean check(T t, World world, int x, int y, int z) { @@ -873,11 +1322,25 @@ public class StructureUtility { public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { return elem.placeBlock(t.getCurrentContext(), world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, s, actor, chatter); + } }; } - //endregion + // endregion - //region defer + // region defer /** * Similar to defer, but caches the first returned element returned and won't call it again. @@ -920,6 +1383,20 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return to.get().spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return to.get().survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } @@ -942,10 +1419,25 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return to.apply(t).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return to.apply(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static IStructureElementDeferred defer(Function keyExtractor, Map> map) { + public static IStructureElementDeferred defer( + Function keyExtractor, Map> map) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -964,10 +1456,25 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.get(keyExtractor.apply(t)).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return map.get(keyExtractor.apply(t)).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static IStructureElementDeferred defer(Function keyExtractor, Map> map, IStructureElement defaultElem) { + public static IStructureElementDeferred defer( + Function keyExtractor, Map> map, IStructureElement defaultElem) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -986,11 +1493,27 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.getOrDefault(keyExtractor.apply(t), defaultElem).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return map.getOrDefault(keyExtractor.apply(t), defaultElem) + .survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } @SafeVarargs - public static IStructureElementDeferred defer(Function keyExtractor, IStructureElement... array) { + public static IStructureElementDeferred defer( + Function keyExtractor, IStructureElement... array) { if (keyExtractor == null || array == null) { throw new IllegalArgumentException(); } @@ -1009,11 +1532,26 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return array[keyExtractor.apply(t)].spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return array[keyExtractor.apply(t)].survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } @SuppressWarnings("unchecked") - public static IStructureElementDeferred defer(Function keyExtractor, List> array) { + public static IStructureElementDeferred defer( + Function keyExtractor, List> array) { return defer(keyExtractor, array.toArray(new IStructureElement[0])); } @@ -1036,10 +1574,25 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return to.apply(t, trigger).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static IStructureElementDeferred defer(BiFunction keyExtractor, Map> map) { + public static IStructureElementDeferred defer( + BiFunction keyExtractor, Map> map) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1058,33 +1611,70 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.get(keyExtractor.apply(t, trigger)).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return map.get(keyExtractor.apply(t, trigger)) + .survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static IStructureElementDeferred defer(BiFunction keyExtractor, Map> map, IStructureElement defaultElem) { + public static IStructureElementDeferred defer( + BiFunction keyExtractor, + Map> map, + IStructureElement defaultElem) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } return new IStructureElementDeferred() { @Override public boolean check(T t, World world, int x, int y, int z) { - return map.getOrDefault(keyExtractor.apply(t, null), defaultElem).check(t, world, x, y, z); + return map.getOrDefault(keyExtractor.apply(t, null), defaultElem) + .check(t, world, x, y, z); } @Override public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { - return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).placeBlock(t, world, x, y, z, trigger); + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem) + .placeBlock(t, world, x, y, z, trigger); } @Override public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { - return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).spawnHint(t, world, x, y, z, trigger); + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem) + .spawnHint(t, world, x, y, z, trigger); + } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem) + .survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); } }; } @SafeVarargs - public static IStructureElementDeferred defer(BiFunction keyExtractor, IStructureElement... array) { + public static IStructureElementDeferred defer( + BiFunction keyExtractor, IStructureElement... array) { if (keyExtractor == null || array == null) { throw new IllegalArgumentException(); } @@ -1103,15 +1693,32 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return array[keyExtractor.apply(t, trigger)].spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return array[keyExtractor.apply(t, trigger)].survivalPlaceBlock( + t, world, x, y, z, trigger, s, actor, chatter); + } }; } @SuppressWarnings("unchecked") - public static IStructureElementDeferred defer(BiFunction keyExtractor, List> array) { + public static IStructureElementDeferred defer( + BiFunction keyExtractor, List> array) { return defer(keyExtractor, array.toArray(new IStructureElement[0])); } - public static IStructureElementDeferred defer(Function> toCheck, BiFunction> to) { + public static IStructureElementDeferred defer( + Function> toCheck, BiFunction> to) { if (to == null) { throw new IllegalArgumentException(); } @@ -1130,10 +1737,27 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return to.apply(t, trigger).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return to.apply(t, trigger).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, Map> map) { + public static IStructureElementDeferred defer( + Function keyExtractorCheck, + BiFunction keyExtractor, + Map> map) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1152,10 +1776,29 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return map.get(keyExtractor.apply(t, trigger)).spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return map.get(keyExtractor.apply(t, trigger)) + .survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); + } }; } - public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, Map> map, IStructureElement defaultElem) { + public static IStructureElementDeferred defer( + Function keyExtractorCheck, + BiFunction keyExtractor, + Map> map, + IStructureElement defaultElem) { if (keyExtractor == null || map == null) { throw new IllegalArgumentException(); } @@ -1167,18 +1810,38 @@ public class StructureUtility { @Override public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { - return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).placeBlock(t, world, x, y, z, trigger); + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem) + .placeBlock(t, world, x, y, z, trigger); } @Override public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { - return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem).spawnHint(t, world, x, y, z, trigger); + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem) + .spawnHint(t, world, x, y, z, trigger); + } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return map.getOrDefault(keyExtractor.apply(t, trigger), defaultElem) + .survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter); } }; } @SafeVarargs - public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, IStructureElement... array) { + public static IStructureElementDeferred defer( + Function keyExtractorCheck, + BiFunction keyExtractor, + IStructureElement... array) { if (keyExtractor == null || array == null) { throw new IllegalArgumentException(); } @@ -1197,15 +1860,33 @@ public class StructureUtility { public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { return array[keyExtractor.apply(t, trigger)].spawnHint(t, world, x, y, z, trigger); } + + @Override + public PlaceResult survivalPlaceBlock( + T t, + World world, + int x, + int y, + int z, + ItemStack trigger, + IItemSource s, + EntityPlayerMP actor, + Consumer chatter) { + return array[keyExtractor.apply(t, trigger)].survivalPlaceBlock( + t, world, x, y, z, trigger, s, actor, chatter); + } }; } @SuppressWarnings("unchecked") - public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, List> array) { + public static IStructureElementDeferred defer( + Function keyExtractorCheck, + BiFunction keyExtractor, + List> array) { return defer(keyExtractorCheck, keyExtractor, array.toArray(new IStructureElement[0])); } - //endregion + // endregion /** * Used internally, to generate skips for structure definitions @@ -1312,44 +1993,61 @@ public class StructureUtility { * * @param tileEntityClassifier return a string that denote the type of a tile entity, or null if it's nothing special. useful if the tile entity cannot be simply distinguished via getClass. */ - public static String getPseudoJavaCode(World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC, - Function tileEntityClassifier, - int sizeA, int sizeB, int sizeC, boolean transpose) { + public static String getPseudoJavaCode( + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + Function tileEntityClassifier, + int sizeA, + int sizeB, + int sizeC, + boolean transpose) { Map> blocks = new TreeMap<>(Comparator.comparing(Block::getUnlocalizedName)); Set> tiles = new HashSet<>(); Set specialTiles = new HashSet<>(); - iterate(world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, - sizeA, sizeB, sizeC, ((w, x, y, z) -> { - TileEntity tileEntity = w.getTileEntity(x, y, z); - if (tileEntity == null) { - Block block = w.getBlock(x, y, z); - if (block != null && block != Blocks.air) { - blocks.compute(block, (b, set) -> { - if (set == null) { - set = new TreeSet<>(); - } - set.add(block.getDamageValue(world, x, y, z)); - return set; - }); + iterate( + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + sizeA, + sizeB, + sizeC, + ((w, x, y, z) -> { + TileEntity tileEntity = w.getTileEntity(x, y, z); + if (tileEntity == null) { + Block block = w.getBlock(x, y, z); + if (block != null && block != Blocks.air) { + blocks.compute(block, (b, set) -> { + if (set == null) { + set = new TreeSet<>(); + } + set.add(block.getDamageValue(world, x, y, z)); + return set; + }); + } + } else { + String classification = tileEntityClassifier.apply(tileEntity); + if (classification == null) { + tiles.add(tileEntity.getClass()); + } else specialTiles.add(classification); } - } else { - String classification = tileEntityClassifier.apply(tileEntity); - if (classification == null) { - tiles.add(tileEntity.getClass()); - } else - specialTiles.add(classification); - } - })); + })); Map map = new HashMap<>(); StringBuilder builder = new StringBuilder(); { int i = 0; char c; - builder.append("\n\nStructure:\n") - .append("\nBlocks:\n"); + builder.append("\n\nStructure:\n").append("\nBlocks:\n"); for (Map.Entry> entry : blocks.entrySet()) { Block block = entry.getKey(); Set set = entry.getValue(); @@ -1359,8 +2057,12 @@ public class StructureUtility { return "Too complicated for nice chars"; } map.put(block.getUnlocalizedName() + '\0' + meta, c); - builder.append(c).append(" -> ofBlock...(") - .append(block.getUnlocalizedName()).append(", ").append(meta).append(", ...);\n"); + builder.append(c) + .append(" -> ofBlock...(") + .append(block.getUnlocalizedName()) + .append(", ") + .append(meta) + .append(", ...);\n"); } } builder.append("\nTiles:\n"); @@ -1370,8 +2072,7 @@ public class StructureUtility { return "Too complicated for nice chars"; } map.put(tile.getCanonicalName(), c); - builder.append(c).append(" -> ofTileAdder(") - .append(tile).append(", ...);\n"); + builder.append(c).append(" -> ofTileAdder(").append(tile).append(", ...);\n"); } builder.append("\nSpecial Tiles:\n"); for (String tile : specialTiles) { @@ -1380,81 +2081,189 @@ public class StructureUtility { return "Too complicated for nice chars"; } map.put(tile, c); - builder.append(c).append(" -> ofSpecialTileAdder(") - .append(tile).append(", ...); // You will probably want to change it to something else\n"); + builder.append(c) + .append(" -> ofSpecialTileAdder(") + .append(tile) + .append(", ...); // You will probably want to change it to something else\n"); } } builder.append("\nOffsets:\n") - .append(basePositionA).append(' ').append(basePositionB).append(' ').append(basePositionC).append('\n'); + .append(basePositionA) + .append(' ') + .append(basePositionB) + .append(' ') + .append(basePositionC) + .append('\n'); if (transpose) { - builder.append("\nTransposed Scan:\n") - .append("new String[][]{\n") - .append(" {\""); - iterate(world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, true, - sizeA, sizeB, sizeC, ((w, x, y, z) -> { - TileEntity tileEntity = w.getTileEntity(x, y, z); - if (tileEntity == null) { - Block block = w.getBlock(x, y, z); - if (block != null && block != Blocks.air) { - builder.append(map.get(block.getUnlocalizedName() + '\0' + block.getDamageValue(world, x, y, z))); + builder.append("\nTransposed Scan:\n").append("new String[][]{\n").append(" {\""); + iterate( + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + true, + sizeA, + sizeB, + sizeC, + ((w, x, y, z) -> { + TileEntity tileEntity = w.getTileEntity(x, y, z); + if (tileEntity == null) { + Block block = w.getBlock(x, y, z); + if (block != null && block != Blocks.air) { + builder.append(map.get( + block.getUnlocalizedName() + '\0' + block.getDamageValue(world, x, y, z))); + } else { + builder.append(' '); + } } else { - builder.append(' '); + String classification = tileEntityClassifier.apply(tileEntity); + if (classification == null) { + classification = tileEntity.getClass().getCanonicalName(); + } + builder.append(map.get(classification)); } - } else { - String classification = tileEntityClassifier.apply(tileEntity); - if (classification == null) { - classification = tileEntity.getClass().getCanonicalName(); - } - builder.append(map.get(classification)); - } - }), - () -> builder.append("\",\""), - () -> { - builder.setLength(builder.length() - 2); - builder.append("},\n {\""); - }); + }), + () -> builder.append("\",\""), + () -> { + builder.setLength(builder.length() - 2); + builder.append("},\n {\""); + }); builder.setLength(builder.length() - 8); builder.append("\n}\n\n"); } else { - builder.append("\nNormal Scan:\n") - .append("new String[][]{{\n") - .append(" \""); - iterate(world, extendedFacing, basePositionX, basePositionY, basePositionZ, - basePositionA, basePositionB, basePositionC, false, - sizeA, sizeB, sizeC, ((w, x, y, z) -> { - TileEntity tileEntity = w.getTileEntity(x, y, z); - if (tileEntity == null) { - Block block = w.getBlock(x, y, z); - if (block != null && block != Blocks.air) { - builder.append(map.get(block.getUnlocalizedName() + '\0' + block.getDamageValue(world, x, y, z))); + builder.append("\nNormal Scan:\n").append("new String[][]{{\n").append(" \""); + iterate( + world, + extendedFacing, + basePositionX, + basePositionY, + basePositionZ, + basePositionA, + basePositionB, + basePositionC, + false, + sizeA, + sizeB, + sizeC, + ((w, x, y, z) -> { + TileEntity tileEntity = w.getTileEntity(x, y, z); + if (tileEntity == null) { + Block block = w.getBlock(x, y, z); + if (block != null && block != Blocks.air) { + builder.append(map.get( + block.getUnlocalizedName() + '\0' + block.getDamageValue(world, x, y, z))); + } else { + builder.append(' '); + } } else { - builder.append(' '); + String classification = tileEntityClassifier.apply(tileEntity); + if (classification == null) { + classification = tileEntity.getClass().getCanonicalName(); + } + builder.append(map.get(classification)); } - } else { - String classification = tileEntityClassifier.apply(tileEntity); - if (classification == null) { - classification = tileEntity.getClass().getCanonicalName(); - } - builder.append(map.get(classification)); - } - }), - () -> builder.append("\",\n").append(" \""), - () -> { - builder.setLength(builder.length() - 7); - builder.append("\n").append("},{\n").append(" \""); - }); + }), + () -> builder.append("\",\n").append(" \""), + () -> { + builder.setLength(builder.length() - 7); + builder.append("\n").append("},{\n").append(" \""); + }); builder.setLength(builder.length() - 8); builder.append("}\n\n"); } return (builder.toString().replaceAll("\"\"", "E")); } - public static void iterate(World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC, - int sizeA, int sizeB, int sizeC, - IBlockPosConsumer iBlockPosConsumer) { + static boolean iterateV2( + IStructureElement[] elements, + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + IStructureWalker predicate, + String iterateType) { + // change base position to base offset + basePositionA = -basePositionA; + basePositionB = -basePositionB; + basePositionC = -basePositionC; + + int[] abc = new int[] {basePositionA, basePositionB, basePositionC}; + int[] xyz = new int[3]; + + for (IStructureElement element : elements) { + if (element.isNavigating()) { + abc[0] = (element.resetA() ? basePositionA : abc[0]) + element.getStepA(); + abc[1] = (element.resetB() ? basePositionB : abc[1]) + element.getStepB(); + abc[2] = (element.resetC() ? basePositionC : abc[2]) + element.getStepC(); + } else { + extendedFacing.getWorldOffset(abc, xyz); + xyz[0] += basePositionX; + xyz[1] += basePositionY; + xyz[2] += basePositionZ; + + StructureLib.LOGGER.info( + "Multi [{}, {}, {}] {} step @ {} {}", + basePositionX, + basePositionY, + basePositionZ, + iterateType, + Arrays.toString(xyz), + Arrays.toString(abc)); + + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { + if (!predicate.visit(element, world, xyz[0], xyz[1], xyz[2])) { + if (DEBUG_MODE) { + StructureLib.LOGGER.info( + "Multi [{}, {}, {}] {} stop @ {} {}", + basePositionX, + basePositionY, + basePositionZ, + iterateType, + Arrays.toString(xyz), + Arrays.toString(abc)); + } + return false; + } + } else { + if (DEBUG_MODE) { + StructureLib.LOGGER.info( + "Multi [{}, {}, {}] {} !blockExists @ {} {}", + basePositionX, + basePositionY, + basePositionZ, + iterateType, + Arrays.toString(xyz), + Arrays.toString(abc)); + } + return false; + } + abc[0] += 1; + } + } + return true; + } + + public static void iterate( + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + int sizeA, + int sizeB, + int sizeC, + IBlockPosConsumer iBlockPosConsumer) { sizeA -= basePositionA; sizeB -= basePositionB; sizeC -= basePositionC; @@ -1466,20 +2275,29 @@ public class StructureUtility { for (abc[1] = -basePositionB; abc[1] < sizeB; abc[1]++) { for (abc[0] = -basePositionA; abc[0] < sizeA; abc[0]++) { extendedFacing.getWorldOffset(abc, xyz); - iBlockPosConsumer.consume(world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ); + iBlockPosConsumer.consume( + world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ); } - } } } - public static void iterate(World world, ExtendedFacing extendedFacing, - int basePositionX, int basePositionY, int basePositionZ, - int basePositionA, int basePositionB, int basePositionC, - boolean transpose, int sizeA, int sizeB, int sizeC, - IBlockPosConsumer iBlockPosConsumer, - Runnable nextB, - Runnable nextC) { + public static void iterate( + World world, + ExtendedFacing extendedFacing, + int basePositionX, + int basePositionY, + int basePositionZ, + int basePositionA, + int basePositionB, + int basePositionC, + boolean transpose, + int sizeA, + int sizeB, + int sizeC, + IBlockPosConsumer iBlockPosConsumer, + Runnable nextB, + Runnable nextC) { sizeA -= basePositionA; sizeB -= basePositionB; sizeC -= basePositionC; @@ -1491,7 +2309,8 @@ public class StructureUtility { for (abc[2] = -basePositionC; abc[2] < sizeC; abc[2]++) { for (abc[0] = -basePositionA; abc[0] < sizeA; abc[0]++) { extendedFacing.getWorldOffset(abc, xyz); - iBlockPosConsumer.consume(world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ); + iBlockPosConsumer.consume( + world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ); } nextB.run(); } @@ -1502,7 +2321,8 @@ public class StructureUtility { for (abc[1] = -basePositionB; abc[1] < sizeB; abc[1]++) { for (abc[0] = -basePositionA; abc[0] < sizeA; abc[0]++) { extendedFacing.getWorldOffset(abc, xyz); - iBlockPosConsumer.consume(world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ); + iBlockPosConsumer.consume( + world, xyz[0] + basePositionX, xyz[1] + basePositionY, xyz[2] + basePositionZ); } nextB.run(); } @@ -1527,5 +2347,4 @@ public class StructureUtility { } return shape; } - } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java b/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java new file mode 100644 index 00000000..46813a27 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/SurvivalBuildStructureWalker.java @@ -0,0 +1,58 @@ +package com.gtnewhorizon.structurelib.structure; + +import com.gtnewhorizon.structurelib.StructureLibAPI; +import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility; +import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +class SurvivalBuildStructureWalker implements IStructureWalker { + private final T object; + private final ItemStack trigger; + private final IItemSource source; + private final EntityPlayerMP actor; + private final int elementBudget; + private final boolean check; + private int built = -1; + + public SurvivalBuildStructureWalker( + T object, ItemStack trigger, IItemSource source, EntityPlayerMP actor, int elementBudget, boolean check) { + this.object = object; + this.trigger = trigger; + this.source = source; + this.actor = actor; + this.elementBudget = elementBudget; + this.check = check; + } + + @Override + public boolean visit(IStructureElement element, World world, int x, int y, int z) { + PlaceResult placeResult = element.survivalPlaceBlock( + object, world, x, y, z, trigger, source, actor, actor::addChatComponentMessage); + if (placeResult != PlaceResult.SKIP && built == -1) built = 0; + switch (placeResult) { + case SKIP: + return true; + case ACCEPT: + if (check) element.check(object, world, x, y, z); + return ++built < elementBudget; + case REJECT: + if (ConstructableUtility.redBrokenOne) + StructureLibAPI.updateHintParticleTint(actor, world, x, y, z, new short[] {255, 128, 128, 0}); + return false; + case ACCEPT_STOP: + if (check) element.check(object, world, x, y, z); + built += 1; + // intentional fallthrough + case STOP: + return false; + default: + throw new NullPointerException(); + } + } + + public int getBuilt() { + return built; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/adders/IBlockAdder.java b/src/main/java/com/gtnewhorizon/structurelib/structure/adders/IBlockAdder.java index c80693a3..1fa0840f 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/adders/IBlockAdder.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/adders/IBlockAdder.java @@ -1,6 +1,5 @@ package com.gtnewhorizon.structurelib.structure.adders; - import net.minecraft.block.Block; public interface IBlockAdder { diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java b/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java index 1460b167..dd43e8c1 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/package-info.java @@ -2,4 +2,4 @@ * All public stuff in this package and its subpackages are considered part of the public API. * Backwards compatibility is maintained to the maximum extend possible. */ -package com.gtnewhorizon.structurelib.structure; \ No newline at end of file +package com.gtnewhorizon.structurelib.structure; diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/InventoryIterable.java b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryIterable.java new file mode 100644 index 00000000..1b27dc18 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryIterable.java @@ -0,0 +1,47 @@ +package com.gtnewhorizon.structurelib.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class InventoryIterable implements Iterable { + private final Inv inv; + private final int maxSlot; + + public InventoryIterable(Inv inv) { + this(inv, -1); + } + + InventoryIterable(Inv inv, int maxSlot) { + this.inv = inv; + this.maxSlot = maxSlot; + } + + public Inv getInventory() { + return inv; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int ptr = 0; + + @Override + public boolean hasNext() { + return ptr < inv.getSizeInventory() && (maxSlot == -1 || ptr < maxSlot); + } + + @Override + public ItemStack next() { + if (!hasNext()) throw new NoSuchElementException(); + return inv.getStackInSlot(ptr++); + } + + @Override + public void remove() { + inv.setInventorySlotContents(ptr - 1, null); + } + }; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java new file mode 100644 index 00000000..e572e8f5 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java @@ -0,0 +1,390 @@ +/** + * Copyright (c) 2022, glee8e + * This file is part of StructureLib. + *

+ * StructureLib is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + *

+ * StructureLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with Foobar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +package com.gtnewhorizon.structurelib.util; + +import com.gtnewhorizon.structurelib.util.InventoryUtility.ItemStackExtractor.APIType; +import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Function; +import java.util.function.Predicate; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.init.Items; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryEnderChest; +import net.minecraft.item.ItemStack; + +/** + * This class is part of API, but is not stable. Use at your own risk. + */ +public class InventoryUtility { + private static final SortedRegistry stackExtractors = new SortedRegistry<>(); + private static final List> enableEnder = new CopyOnWriteArrayList<>(); + /** + * The remove() of the Iterable returned must be implemented! + */ + private static final SortedRegistry> inventoryProviders = new SortedRegistry<>(); + + static { + inventoryProviders.register("5000-main-inventory", new InventoryProvider>() { + @Override + public InventoryIterable getInventory(EntityPlayerMP player) { + return new InventoryIterable<>(player.inventory, player.inventory.mainInventory.length); + } + + @Override + public void markDirty(InventoryIterable inv) { + // player save its content using means other than inv.markDirty() + // here we only need to sync it to client + inv.getInventory().player.inventoryContainer.detectAndSendChanges(); + } + }); + inventoryProviders.register( + "7000-ender-inventory", new InventoryProvider>() { + @Override + public InventoryIterable getInventory(EntityPlayerMP player) { + if (enableEnder.stream().anyMatch(p -> p.test(player))) + return new InventoryIterable<>(player.getInventoryEnderChest()); + return null; + } + + @Override + public void markDirty(InventoryIterable inv) { + // inv.getInventory().markDirty(); + // TODO this seems to be a noop + } + }); + } + + public static void registerEnableEnderCondition(Predicate predicate) { + enableEnder.add(predicate); + } + + public static void registerStackExtractor(String key, ItemStackExtractor val) { + if (Arrays.stream(APIType.values()).noneMatch(val::isAPIImplemented)) + throw new IllegalArgumentException("Must implement at least one API"); + stackExtractors.register(key, val); + } + + public static void registerStackExtractor( + String key, Function extractor) { + registerStackExtractor(key, newItemStackProvider(extractor)); + } + + public static void registerInventoryProvider(String key, InventoryProvider val) { + inventoryProviders.register(key, val); + } + + public static void registerInventoryProvider( + String key, Function extractor) { + registerInventoryProvider(key, newInventoryProvider(extractor)); + } + + public static Iterator getStackExtractors() { + return stackExtractors.iterator(); + } + + public static InventoryProvider> newInventoryProvider( + Function extractor) { + return new InventoryProvider>() { + @Override + public InventoryIterable getInventory(EntityPlayerMP player) { + Inv inv = extractor.apply(player); + return inv != null ? new InventoryIterable<>(inv) : null; + } + + @Override + public void markDirty(InventoryIterable inv) { + inv.getInventory().markDirty(); + } + }; + } + + public static ItemStackExtractor newItemStackProvider(Function extractor) { + return new ItemStackExtractor() { + @Override + public boolean isAPIImplemented(APIType type) { + return type == APIType.MAIN; + } + + @Override + public int takeFromStack( + Predicate predicate, + boolean simulate, + int count, + ItemStackCounter store, + ItemStack stack, + ItemStack filter, + EntityPlayerMP player) { + IInventory inv = extractor.apply(stack); + if (inv == null) return 0; + int found = takeFromInventory( + new InventoryIterable<>(inv), predicate, simulate, count, store, filter, player, false); + if (found > 0) inv.markDirty(); + return found; + } + }; + } + + /** + * take count amount of stacks that matches given filter. might take partial amount of stuff, so simulation is suggested + * if you ever need to take more than one. + * + * @param player source of stacks + * @param predicate item stack filter + * @param simulate whether to do removal + * @param count let's hope int size is enough... + * @return amount taken. never negative nor bigger than count... + */ + public static Map takeFromInventory( + EntityPlayerMP player, Predicate predicate, boolean simulate, int count) { + ItemStackCounterImpl store = new ItemStackCounterImpl(); + int sum = 0; + for (InventoryProvider provider : inventoryProviders) { + sum += takeFromPlayer(player, predicate, simulate, count - sum, store, provider, null); + if (sum >= count) return store.getStore(); + } + return store.getStore(); + } + + /** + * take count amount of stacks that matches given filter. might take partial amount of stuff, so simulation is suggested + * if you ever need to take more than one. + * + * @param player source of stacks + * @param filter the precise type of item to extract. stackSize matters + * @param simulate whether to do removal + * @return amount taken. never negative nor bigger than count... + */ + public static int takeFromInventory(EntityPlayerMP player, ItemStack filter, boolean simulate) { + int sum = 0; + int count = filter.stackSize; + ItemStackPredicate predicate = ItemStackPredicate.from(filter, NBTMode.EXACT); + ItemStackCounterImpl store = new ItemStackCounterImpl(); + for (InventoryProvider provider : inventoryProviders) { + sum += takeFromPlayer(player, predicate, simulate, count - sum, store, provider, filter); + if (sum >= count) return sum; + } + return sum; + } + + // workaround java generics issue + private static > int takeFromPlayer( + EntityPlayerMP player, + Predicate predicate, + boolean simulate, + int count, + ItemStackCounterImpl store, + InventoryProvider provider, + ItemStack filter) { + R inv = provider.getInventory(player); + if (inv == null) return 0; + int taken = takeFromInventory(inv, predicate, simulate, count, store, filter, player, true); + if (taken > 0) provider.markDirty(inv); + return taken; + } + + /** + * take count amount of stacks that matches given filter. Will do 1 level of recursion to try find more stacks.... + * e.g. from backpacks... + * + * @param inv source of stacks + * @param predicate item stack filter + * @param simulate whether to do removal + * @param count let's hope int size is enough... + * @param recursive do recursive lookup using {@link #getStackExtractors() stack extractors} + * @return amount taken. never negative nor bigger than count... + */ + public static Map takeFromInventory( + Iterable inv, Predicate predicate, boolean simulate, int count, boolean recursive) { + ItemStackCounterImpl store = new ItemStackCounterImpl(); + takeFromInventory(inv, predicate, simulate, count, store, null, null, recursive); + return store.getStore(); + } + + private static int takeFromInventory( + @Nonnull Iterable inv, + @Nonnull Predicate predicate, + boolean simulate, + int count, + @Nonnull ItemStackCounter store, + @Nullable ItemStack filter, + @Nullable EntityPlayerMP player, + boolean recursive) { + int found = 0; + ItemStack copiedFilter = null; + if (filter != null) { + copiedFilter = new ItemStack(filter.getItem(), filter.stackSize, Items.feather.getDamage(filter)); + copiedFilter.setTagCompound(filter.stackTagCompound); + } + for (Iterator iterator = inv.iterator(); iterator.hasNext(); ) { + ItemStack stack = iterator.next(); + // invalid stack + if (stack == null || stack.getItem() == null || stack.stackSize <= 0) continue; + + if (predicate.test(stack)) { + found += stack.stackSize; + if (found > count) { + int surplus = found - count; + store.add(stack, stack.stackSize - surplus); + if (!simulate) { + // leave the surplus in its place + stack.stackSize = surplus; + } + return count; + } + store.add(stack, stack.stackSize); + if (!simulate) iterator.remove(); + if (found == count) return count; + } + if (!recursive) continue; + for (ItemStackExtractor f : stackExtractors) { + if (filter != null && f.isAPIImplemented(APIType.EXTRACT_ONE_STACK)) { + copiedFilter.stackSize = count - found; + found += f.getItem(stack, copiedFilter, simulate, player); + } else { + found += f.takeFromStack(predicate, simulate, count - found, store, stack, filter, player); + } + if (found >= count) return found; + } + } + return found; + } + + public interface OptimizedExtractor { + /** + * Extract a particular type of item. The extractor can choose to not return all items contained within this item + * as long as it makes sense, but the author should inform the player of this. + *

+ * Whether optimized extractor do recursive extraction is at the discretion of implementor. + * + * @param source from where an extraction should be attempted + * @param toExtract stack to extract. should not be mutated! match the NBT tag using EXACT mode. + * @param simulate true if only query but does not actually remove + * @param player executor of extraction, or null if from a machine + * @return amount extracted + */ + int extract(ItemStack source, ItemStack toExtract, boolean simulate, EntityPlayerMP player); + } + + public interface InventoryProvider> { + R getInventory(EntityPlayerMP player); + + void markDirty(R inv); + } + + public interface ItemStackExtractor { + enum APIType { + MAIN, + EXTRACT_ONE_STACK, + } + + boolean isAPIImplemented(APIType type); + + /** + * Extract a particular type of item. The extractor can choose to not return all items contained within this item + * as long as it makes sense, but the author should inform the player of this. + *

+ * Whether this method does recursive extraction is at the discretion of implementor. + * + * @param predicate the main filtering predicate. never null. It's assumed predicate always returns true on filter, + * if that is not null + * @param store where to store extracted items. Should increment the + * @param source from where an extraction should be attempted + * @param filter stack to extract. should not be mutated! match the NBT tag using EXACT mode. might be null. + * @param simulate true if only query but does not actually remove + * @param player executor of extraction, or null if from a machine + * @return amount extracted, or -1 if this is not implemented + */ + int takeFromStack( + Predicate predicate, + boolean simulate, + int count, + ItemStackCounter store, + ItemStack source, + ItemStack filter, + EntityPlayerMP player); + + /** + * Extract a particular type of item. The extractor can choose to not return all items contained within this item + * as long as it makes sense, but the author should inform the player of this. + *

+ * Whether this method does recursive extraction is at the discretion of implementor. + * + * @param source from where an extraction should be attempted + * @param toExtract stack to extract. should not be mutated! match the NBT tag using EXACT mode. + * @param simulate true if only query but does not actually remove + * @param player executor of extraction, or null if from a machine + * @return amount extracted, or -1 if this is not implemented + */ + default int getItem(ItemStack source, ItemStack toExtract, boolean simulate, EntityPlayerMP player) { + return -1; + } + + static ItemStackExtractor createOnlyOptimized(@Nonnull OptimizedExtractor optimizedExtractor) { + return new ItemStackExtractor() { + @Override + public boolean isAPIImplemented(APIType type) { + return type == APIType.EXTRACT_ONE_STACK; + } + + @Override + public int takeFromStack( + Predicate predicate, + boolean simulate, + int count, + ItemStackCounter store, + ItemStack stack, + ItemStack filter, + EntityPlayerMP player) { + return 0; + } + + @Override + public int getItem(ItemStack source, ItemStack toExtract, boolean simulate, EntityPlayerMP player) { + return optimizedExtractor.extract(source, toExtract, simulate, player); + } + }; + } + } + + public interface ItemStackCounter { + /** + * Add some amount of stack. Note stackSize is used instead of stack.stackSize. + */ + void add(ItemStack stack, int stackSize); + } + + private static class ItemStackCounterImpl implements ItemStackCounter { + private final Map store = new ItemStackMap<>(); + + @Override + public void add(ItemStack stack, int stackSize) { + if (stack == null || stack.getItem() == null || stackSize <= 0) throw new IllegalArgumentException(); + store.merge(stack, stackSize, Integer::sum); + } + + public Map getStore() { + return store; + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackArrayIterable.java b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackArrayIterable.java new file mode 100644 index 00000000..ae49f49c --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackArrayIterable.java @@ -0,0 +1,38 @@ +package com.gtnewhorizon.structurelib.util; + +import java.util.Iterator; +import net.minecraft.item.ItemStack; + +public class ItemStackArrayIterable implements Iterable { + private final ItemStack[] stacks; + + public ItemStackArrayIterable(ItemStack[] stacks) { + this.stacks = stacks; + } + + public ItemStack[] getStacks() { + return stacks; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private int ptr = 0; + + @Override + public boolean hasNext() { + return ptr < stacks.length; + } + + @Override + public ItemStack next() { + return stacks[ptr++]; + } + + @Override + public void remove() { + stacks[ptr - 1] = null; + } + }; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackMap.java b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackMap.java new file mode 100644 index 00000000..9f729e69 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackMap.java @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2021, glee8e, Code Chicken. + * + * This file is originally part of NotEnoughtItem by Code Chicken. + * It is adapted to implement the standard Map interface by glease + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.gtnewhorizon.structurelib.util; + +import static net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE; + +import com.google.common.base.Objects; +import com.google.common.collect.Iterators; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +/** + * A map class for ItemStack keys with wildcard damage/NBT. Optimised for lookup. + *

+ * This map does NOT support null values or null keys! null will be silently ignored! + *

+ * Originally created by CodeChicken for NotEnoughItems. Adapted to {@code Map} interface by glee8e + * @author CodeChiken + * @author glee8e + */ +public final class ItemStackMap extends AbstractMap { + public static final NBTTagCompound WILDCARD_TAG; + + static { + WILDCARD_TAG = new NBTTagCompound(); + WILDCARD_TAG.setBoolean("*", true); + } + + private final HashMap itemMap = new HashMap<>(); + private int size; + + static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) { + ItemStack stack = new ItemStack(item, size, damage); + stack.stackTagCompound = tag; + return stack; + } + + static int actualDamage(ItemStack stack) { + return Items.diamond.getDamage(stack); + } + + static int getKeyType(int damage, NBTTagCompound tag) { + int i = 0; + if (isWildcard(damage)) i = 1; + if (isWildcard(tag)) i |= 2; + return i; + } + + static ItemStack wildcard(Item item) { + return newItemStack(item, 1, WILDCARD_VALUE, WILDCARD_TAG); + } + + static boolean isWildcard(int damage) { + return damage == WILDCARD_VALUE; + } + + static boolean isWildcard(NBTTagCompound tag) { + return tag != null && tag.getBoolean("*"); + } + + @Override + public T get(Object key) { + if (!(key instanceof ItemStack)) return null; + ItemStack stack = ((ItemStack) key); + if (stack.getItem() == null) return null; + DetailMap map = itemMap.get(stack.getItem()); + return map == null ? null : map.get(stack); + } + + @Override + public T put(ItemStack key, T value) { + if (key == null || key.getItem() == null || value == null) return null; + return itemMap.computeIfAbsent(key.getItem(), k -> new DetailMap()).put(key, value); + } + + @Override + public T remove(Object key) { + if (!(key instanceof ItemStack)) return null; + ItemStack stack = ((ItemStack) key); + if (stack.getItem() == null) return null; + DetailMap map = itemMap.get(stack.getItem()); + return map == null ? null : map.remove(stack); + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof ItemStack)) return false; + ItemStack stack = ((ItemStack) key); + if (stack.getItem() == null) return false; + DetailMap map = itemMap.get(stack.getItem()); + return map != null && map.get(stack) != null; + } + + @Override + public void clear() { + itemMap.clear(); + size = 0; + } + + @Override + public Set> entrySet() { + return new SetView(); + } + + private static class StackMetaKey { + public final int damage; + public final NBTTagCompound tag; + + public StackMetaKey(int damage, NBTTagCompound tag) { + this.damage = damage; + this.tag = tag; + } + + public StackMetaKey(ItemStack key) { + this(actualDamage(key), key.stackTagCompound); + } + + public int hashCode() { + return Objects.hashCode(damage, tag); + } + + public boolean equals(Object o) { + if (!(o instanceof StackMetaKey)) return false; + StackMetaKey t = (StackMetaKey) o; + return damage == t.damage && Objects.equal(tag, t.tag); + } + } + + private static class DetailIter implements Iterator> { + private final Item owner; + private final ItemStackMap.DetailMap backing; + + @Nullable + private final Iterator> damageIter; + + @Nullable + private final Iterator> tagIter; + + @Nullable + private final Iterator> metaIter; + + private DetailIterState state = DetailIterState.NOT_STARTED; + private boolean removed = false; + + private DetailIter(Map.Entry.DetailMap> input) { + this.owner = input.getKey(); + this.backing = input.getValue(); + damageIter = + backing.damageMap != null ? backing.damageMap.entrySet().iterator() : null; + tagIter = backing.tagMap != null ? backing.tagMap.entrySet().iterator() : null; + metaIter = backing.metaMap != null ? backing.metaMap.entrySet().iterator() : null; + } + + private DetailIterState nextState() { + switch (state) { + case NOT_STARTED: + if (backing.hasWildcard) return DetailIterState.WILDCARD; + case WILDCARD: + case DAMAGE: + if (damageIter != null && damageIter.hasNext()) return DetailIterState.DAMAGE; + case TAG: + if (tagIter != null && tagIter.hasNext()) return DetailIterState.TAG; + case META: + if (metaIter != null && metaIter.hasNext()) return DetailIterState.META; + case DONE: + return DetailIterState.DONE; + default: + throw new IllegalStateException("Unexpected value: " + state); + } + } + + @Override + public void remove() { + if (removed) throw new IllegalStateException("remove() called twice"); + state.remove(this); + removed = true; + } + + @Override + public boolean hasNext() { + return nextState() != DetailIterState.DONE; + } + + @Override + public Map.Entry next() { + DetailIterState nextState = nextState(); + if (nextState == DetailIterState.DONE) throw new NoSuchElementException(); + state = nextState; + removed = false; + return nextState.get(this); + } + + private enum DetailIterState { + NOT_STARTED { + @Override + Map.Entry get(DetailIter iter) { + throw new AssertionError("Should not call get on NOT_STARTED"); + } + + @Override + void remove(DetailIter iter) { + throw new IllegalStateException("next() never called"); + } + }, + WILDCARD { + @Override + Map.Entry get(DetailIter iter) { + return new Map.Entry() { + private final ItemStack key = wildcard(iter.owner); + + @Override + public ItemStack getKey() { + return key; + } + + @Override + public T getValue() { + return iter.backing.wildcard; + } + + @Override + public T setValue(T value) { + return iter.backing.wildcard = value; + } + }; + } + + @Override + void remove(DetailIter iter) { + assert iter.backing.hasWildcard; + iter.backing.wildcard = null; + iter.backing.hasWildcard = false; + } + }, + DAMAGE { + @Override + Map.Entry get(DetailIter iter) { + assert iter.damageIter != null; + Map.Entry entry = iter.damageIter.next(); + return new ItemStackEntry<>(newItemStack(iter.owner, 1, entry.getKey(), WILDCARD_TAG), entry); + } + + @Override + void remove(DetailIter iter) { + assert iter.damageIter != null; + iter.damageIter.remove(); + } + }, + TAG { + @Override + Map.Entry get(DetailIter iter) { + assert iter.tagIter != null; + Map.Entry entry = iter.tagIter.next(); + return new ItemStackEntry<>(newItemStack(iter.owner, 1, WILDCARD_VALUE, entry.getKey()), entry); + } + + @Override + void remove(DetailIter iter) { + assert iter.tagIter != null; + iter.tagIter.remove(); + } + }, + META { + @Override + Map.Entry get(DetailIter iter) { + assert iter.metaIter != null; + Map.Entry entry = iter.metaIter.next(); + return new ItemStackEntry<>( + newItemStack(iter.owner, 1, entry.getKey().damage, entry.getKey().tag), entry); + } + + @Override + void remove(DetailIter iter) { + assert iter.metaIter != null; + iter.metaIter.remove(); + } + }, + DONE { + @Override + Map.Entry get(DetailIter iter) { + throw new AssertionError("Should not call get on DONE"); + } + + @Override + void remove(DetailIter iter) { + throw new AssertionError("Should not call remove on DONE"); + } + }; + + abstract Map.Entry get(DetailIter iter); + + abstract void remove(DetailIter iter); + + private static class ItemStackEntry implements Map.Entry { + private final ItemStack key; + private final Map.Entry entry; + + public ItemStackEntry(ItemStack key, Map.Entry entry) { + this.key = key; + this.entry = entry; + } + + @Override + public ItemStack getKey() { + return key; + } + + @Override + public T getValue() { + return entry.getValue(); + } + + @Override + public T setValue(T value) { + return entry.setValue(value); + } + } + } + } + + private class DetailMap { + private boolean hasWildcard; + private T wildcard; + private HashMap damageMap; + private HashMap tagMap; + private HashMap metaMap; + private int size; + + public T get(ItemStack key) { + if (wildcard != null) return wildcard; + + if (damageMap != null) { + final T ret = damageMap.get(actualDamage(key)); + if (ret != null) return ret; + } + if (tagMap != null) { + final T ret = tagMap.get(key.stackTagCompound); + if (ret != null) return ret; + } + if (metaMap != null) return metaMap.get(new StackMetaKey(key)); + + return null; + } + + public T put(ItemStack key, T value) { + try { + switch (getKeyType(actualDamage(key), key.stackTagCompound)) { + case 0: + if (metaMap == null) metaMap = new HashMap<>(); + return metaMap.put(new StackMetaKey(key), value); + case 1: + if (tagMap == null) tagMap = new HashMap<>(); + return tagMap.put(key.stackTagCompound, value); + case 2: + if (damageMap == null) damageMap = new HashMap<>(); + return damageMap.put(actualDamage(key), value); + case 3: + T ret = wildcard; + wildcard = value; + hasWildcard = true; + return ret; + } + } finally { + updateSize(); + } + return null; + } + + public T remove(ItemStack key) { + try { + switch (getKeyType(actualDamage(key), key.stackTagCompound)) { + case 0: + return metaMap != null ? metaMap.remove(new StackMetaKey(key)) : null; + case 1: + return tagMap != null ? tagMap.remove(key.stackTagCompound) : null; + case 2: + return damageMap != null ? damageMap.remove(actualDamage(key)) : null; + case 3: + T ret = wildcard; + wildcard = null; + hasWildcard = false; + return ret; + } + } finally { + updateSize(); + } + return null; + } + + private void updateSize() { + int newSize = (hasWildcard ? 1 : 0) + + (metaMap != null ? metaMap.size() : 0) + + (tagMap != null ? tagMap.size() : 0) + + (damageMap != null ? damageMap.size() : 0); + + if (newSize != size) { + ItemStackMap.this.size += newSize - size; + size = newSize; + } + } + } + + private class SetView extends AbstractSet> { + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) return false; + Map.Entry entry = (Map.Entry) o; + return java.util.Objects.equals(get(entry.getKey()), entry.getValue()); + } + + @Override + public boolean add(Map.Entry itemStackTEntry) { + return itemStackTEntry.getKey() != null + && itemStackTEntry.getValue() != null + && put(itemStackTEntry.getKey(), itemStackTEntry.getValue()) == null; + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) return false; + Map.Entry entry = (Map.Entry) o; + return ItemStackMap.this.remove(entry.getKey(), entry.getValue()); + } + + @Nonnull + @Override + public Iterator> iterator() { + return Iterators.concat(Iterators.transform(itemMap.entrySet().iterator(), DetailIter::new)); + } + + @Override + public int size() { + return size; + } + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackPredicate.java b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackPredicate.java new file mode 100644 index 00000000..a366681c --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackPredicate.java @@ -0,0 +1,112 @@ +package com.gtnewhorizon.structurelib.util; + +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import net.minecraft.block.Block; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; + +public final class ItemStackPredicate implements Predicate { + public static ItemStackPredicate from(Item item) { + return new ItemStackPredicate(item); + } + + public static ItemStackPredicate from(ItemStack itemStack) { + return new ItemStackPredicate(itemStack.getItem()).setMeta(Items.feather.getDamage(itemStack)); + } + + public static ItemStackPredicate from(ItemStack itemStack, NBTMode mode) { + return new ItemStackPredicate(itemStack.getItem()) + .setMeta(Items.feather.getDamage(itemStack)) + .setTag(mode, itemStack.stackTagCompound); + } + + public static ItemStackPredicate from(Block block) { + return new ItemStackPredicate(Item.getItemFromBlock(block)); + } + + private final Item item; + private int meta = -1; + + private NBTTagCompound tag; + private NBTMode mode = NBTMode.IGNORE; + + private ItemStackPredicate(Item item) { + this.item = item; + } + + public ItemStackPredicate ignoreMeta() { + this.meta = -1; + return this; + } + + public ItemStackPredicate setMeta(int meta) { + this.meta = meta; + return this; + } + + public ItemStackPredicate setTag(NBTMode mode, NBTTagCompound tag) { + this.mode = mode; + this.tag = tag; + return this; + } + + @Override + public boolean test(ItemStack itemStack) { + if (item != null) if (itemStack.getItem() != item) return false; + if (meta != -1) if (Items.feather.getDamage(itemStack) != meta) return false; + return mode.test(tag, itemStack.stackTagCompound); + } + + public enum NBTMode implements BiPredicate { + IGNORE { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + return true; + } + }, + IN { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + if (lhs == null || lhs.hasNoTags()) return true; + if (rhs == null || rhs.hasNoTags()) return false; + for (String key : MiscUtils.getTagKeys(lhs)) { + if (!rhs.hasKey(key, lhs.func_150299_b(key))) return false; + NBTBase tag = lhs.getTag(key); + if (tag instanceof NBTTagCompound) { + if (!test((NBTTagCompound) tag, rhs.getCompoundTag(key))) return false; + } else { + if (!tag.equals(rhs.getTag(key))) return false; + } + } + return true; + } + }, + EXACT { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + if (lhs != null && lhs.hasNoTags()) lhs = null; + if (rhs != null && rhs.hasNoTags()) rhs = null; + return Objects.equals(lhs, rhs); + } + }, + IGNORE_KNOWN_INSIGNIFICANT_TAGS { + @Override + public boolean test(NBTTagCompound lhs, NBTTagCompound rhs) { + // fast path for empty tags + if (rhs == null || rhs.hasNoTags()) return lhs == null || lhs.hasNoTags(); + // TODO make an implementation without copying a potentially huge tag + rhs = (NBTTagCompound) rhs.copy(); + for (String s : KNOWN_INSIGNIFICANT_TAGS) rhs.removeTag(s); + return EXACT.test(lhs, rhs); + } + }; + private static final String[] KNOWN_INSIGNIFICANT_TAGS = { + "display", // TODO expand/refine this + }; + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/MiscUtils.java b/src/main/java/com/gtnewhorizon/structurelib/util/MiscUtils.java new file mode 100644 index 00000000..a794f031 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/MiscUtils.java @@ -0,0 +1,16 @@ +package com.gtnewhorizon.structurelib.util; + +import java.util.Set; +import net.minecraft.nbt.NBTTagCompound; + +/** + * not related to a certain mod! + */ +public class MiscUtils { + private MiscUtils() {} + + @SuppressWarnings("unchecked") + public static Set getTagKeys(NBTTagCompound tag) { + return tag.func_150296_c(); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/SortedRegistry.java b/src/main/java/com/gtnewhorizon/structurelib/util/SortedRegistry.java new file mode 100644 index 00000000..d9bb5cb7 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/SortedRegistry.java @@ -0,0 +1,57 @@ +package com.gtnewhorizon.structurelib.util; + +import java.util.*; +import java.util.function.Consumer; + +public class SortedRegistry implements Iterable { + private final NavigableMap store = new TreeMap<>(); + /** + * Store stuff in an array for faster iteration access than a red black tree + */ + private List baked = Collections.emptyList(); + + public void register(String key, V val) { + if (key == null || val == null) throw new NullPointerException(); + V old = store.putIfAbsent(key, val); + if (old != null) { + throw new IllegalArgumentException("Duplicate key: " + key); + } + // TODO allow a user to edit the order with a configuration file + baked = new ArrayList<>(store.values()); + } + + public int size() { + return store.size(); + } + + public boolean isEmpty() { + return store.isEmpty(); + } + + public boolean containsKey(String key) { + return store.containsKey(key); + } + + public boolean containsValue(V value) { + return store.containsValue(value); + } + + public V get(String key) { + return store.get(key); + } + + @Override + public Iterator iterator() { + return baked.iterator(); + } + + @Override + public void forEach(Consumer action) { + baked.forEach(action); + } + + @Override + public Spliterator spliterator() { + return baked.spliterator(); + } +} diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/Vec3Impl.java b/src/main/java/com/gtnewhorizon/structurelib/util/Vec3Impl.java index db2abb07..e52b47d0 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/util/Vec3Impl.java +++ b/src/main/java/com/gtnewhorizon/structurelib/util/Vec3Impl.java @@ -57,7 +57,9 @@ public class Vec3Impl implements Comparable { } public Vec3Impl offset(ForgeDirection facing, int n) { - return n == 0 ? this : new Vec3Impl(val0 + facing.offsetX * n, val1 + facing.offsetY * n, val2 + facing.offsetZ * n); + return n == 0 + ? this + : new Vec3Impl(val0 + facing.offsetX * n, val1 + facing.offsetY * n, val2 + facing.offsetZ * n); } public Vec3Impl add(Vec3Impl pos) { @@ -77,8 +79,10 @@ public class Vec3Impl implements Comparable { } public Vec3Impl crossProduct(Vec3Impl vec) { - return new Vec3Impl(val1 * vec.val2 - val2 * vec.val1, val2 * vec.val0 - val0 * vec.val2, - val0 * vec.val1 - val1 * vec.val0); + return new Vec3Impl( + val1 * vec.val2 - val2 * vec.val1, + val2 * vec.val0 - val0 * vec.val2, + val0 * vec.val1 - val1 * vec.val0); } public boolean withinDistance(Vec3Impl to, double distance) { diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/XSTR.java b/src/main/java/com/gtnewhorizon/structurelib/util/XSTR.java index e3b8198b..a079097c 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/util/XSTR.java +++ b/src/main/java/com/gtnewhorizon/structurelib/util/XSTR.java @@ -1,28 +1,28 @@ package com.gtnewhorizon.structurelib.util; /* - A subclass of java.util.random that implements the Xorshift random number - generator -

- - it is 30% faster than the generator from Java's library - it produces - random sequences of higher quality than java.util.Random - this class also - provides a clone() function -

- Usage: XSRandom rand = new XSRandom(); //Instantiation x = rand.nextInt(); - //pull a random number -

- To use the class in legacy code, you may also instantiate an XSRandom object - and assign it to a java.util.Random object: java.util.Random rand = new - XSRandom(); -

- for an explanation of the algorithm, see - http://demesos.blogspot.com/2011/09/pseudo-random-number-generators.html + A subclass of java.util.random that implements the Xorshift random number + generator +

+ - it is 30% faster than the generator from Java's library - it produces + random sequences of higher quality than java.util.Random - this class also + provides a clone() function +

+ Usage: XSRandom rand = new XSRandom(); //Instantiation x = rand.nextInt(); + //pull a random number +

+ To use the class in legacy code, you may also instantiate an XSRandom object + and assign it to a java.util.Random object: java.util.Random rand = new + XSRandom(); +

+ for an explanation of the algorithm, see + http://demesos.blogspot.com/2011/09/pseudo-random-number-generators.html - @author Wilfried Elmenreich University of Klagenfurt/Lakeside Labs - * http://www.elmenreich.tk - *

- * This code is released under the GNU Lesser General Public License Version 3 - * http://www.gnu.org/licenses/lgpl-3.0.txt - */ + @author Wilfried Elmenreich University of Klagenfurt/Lakeside Labs +* http://www.elmenreich.tk +*

+* This code is released under the GNU Lesser General Public License Version 3 +* http://www.gnu.org/licenses/lgpl-3.0.txt +*/ import java.util.Random; import java.util.concurrent.atomic.AtomicLong; @@ -40,7 +40,7 @@ public class XSTR extends Random { private static final long GAMMA = 0x9e3779b97f4a7c15L; private static final int PROBE_INCREMENT = 0x9e3779b9; private static final long SEEDER_INCREMENT = 0xbb67ae8584caa73bL; - private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) + private static final double DOUBLE_UNIT = 0x1.0p-53; // 1.0 / (1L << 53) private static final float FLOAT_UNIT = 0x1.0p-24f; // 1.0f / (1 << 24) private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L); public static final XSTR XSTR_INSTANCE = new XSTR() { @@ -53,9 +53,9 @@ public class XSTR extends Random { }; /* - MODIFIED BY: Robotia - Modification: Implemented Random class seed generator - */ + MODIFIED BY: Robotia + Modification: Implemented Random class seed generator + */ /** * Creates a new pseudo random number generator. The seed is initialized to @@ -226,9 +226,9 @@ public class XSTR extends Random { */ @Override public int nextInt(int bound) { - //if (bound <= 0) { - //throw new RuntimeException("BadBound"); - //} + // if (bound <= 0) { + // throw new RuntimeException("BadBound"); + // } /*int r = next(31); int m = bound - 1; @@ -242,7 +242,7 @@ public class XSTR extends Random { ; } return r;*/ - //speedup, new nextInt ~+40% + // speedup, new nextInt ~+40% long last = seed ^ seed << 21; last ^= last >>> 35; last ^= last << 4; @@ -270,7 +270,9 @@ public class XSTR extends Random { @Override public void nextBytes(byte[] bytes_arr) { for (int iba = 0, lenba = bytes_arr.length; iba < lenba; ) { - for (int rndba = nextInt(), nba = Math.min(lenba - iba, Integer.SIZE / Byte.SIZE); nba-- > 0; rndba >>= Byte.SIZE) { + for (int rndba = nextInt(), nba = Math.min(lenba - iba, Integer.SIZE / Byte.SIZE); + nba-- > 0; + rndba >>= Byte.SIZE) { bytes_arr[iba++] = (byte) rndba; } } diff --git a/src/main/resources/assets/structurelib/lang/en_US.lang b/src/main/resources/assets/structurelib/lang/en_US.lang index 3d303adc..435002da 100644 --- a/src/main/resources/assets/structurelib/lang/en_US.lang +++ b/src/main/resources/assets/structurelib/lang/en_US.lang @@ -24,8 +24,14 @@ item.structurelib.constructableTrigger.name=Multiblock Structure Hologram Projec item.structurelib.constructableTrigger.desc.0=Triggers Constructable Interface item.structurelib.constructableTrigger.desc.1=Shows multiblock construction schematic, item.structurelib.constructableTrigger.desc.2=use on the multiblock controller. -item.structurelib.constructableTrigger.desc.3=(Sneak use on controller in creative to build) +item.structurelib.constructableTrigger.desc.3=(Sneak use on controller to build) item.structurelib.constructableTrigger.desc.4=Quantity of schematics in item stack affects tier/mode/type +item.structurelib.constructableTrigger.too_fast=Please wait %d ticks before another autoplace... + +structurelib.autoplace.complete=§aThis multiblock has completed! +structurelib.autoplace.error.not_enabled=§cThis multiblock does not support autoplace in survival! +structurelib.autoplace.error.no_simple_block=§cSuggested to place §r%s§c but none was found +structurelib.autoplace.error.no_item_stack=§cSuggested to place block by using §r%s§c but none was found structurelib.blockhint.desc.0=Helps while building structurelib.blockhint.0.name=Hint 1 dot diff --git a/src/main/resources/assets/structurelib/lang/zh_CN.lang b/src/main/resources/assets/structurelib/lang/zh_CN.lang index 3637adc2..75dde0be 100644 --- a/src/main/resources/assets/structurelib/lang/zh_CN.lang +++ b/src/main/resources/assets/structurelib/lang/zh_CN.lang @@ -26,6 +26,7 @@ item.structurelib.constructableTrigger.desc.1=只能在多方块主机上使用 item.structurelib.constructableTrigger.desc.2=结构的示意图。 (译者注:请搭高使用) item.structurelib.constructableTrigger.desc.3= (创造模式下潜行右键可以直接完成搭建) item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显示的机器等级/模式/类型 +item.structurelib.constructableTrigger.too_fast=Please wait %d ticks before another autoplace... structurelib.blockhint.desc.0=帮助你搭建机器 structurelib.blockhint.0.name=提示方块:1号