diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ba5855 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +*.class +*.log +*.ctxt +.mtj.tmp/ +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar +hs_err_pid* +.gradle +**/build/ +!src/**/build/ +gradle-app.setting +!gradle-wrapper.jar +.gradletasknamecache +.idea/ +run/ +out/ diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7285624 --- /dev/null +++ b/build.gradle @@ -0,0 +1,74 @@ +buildscript { + repositories { + mavenCentral() + maven { + name = "forge" + url = "https://files.minecraftforge.net/maven" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/repositories/snapshots/" + } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:1.2.1' + } +} + +apply plugin: 'forge' + +version = modVersion +group= modGroup +archivesBaseName = modBaseName + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +minecraft { + version = project.forgeVersion + replaceIn "ImpactAPI.java" + replace '${version}', project.version + runDir = "run" +} + +processResources +{ + inputs.property "version", project.version + inputs.property "mcversion", project.minecraft.version + + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + expand 'version': project.version, 'mcversion': project.minecraft.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} + +task deobfJar(type: Jar) { + from sourceSets.main.output + classifier = 'deobf' +} + +task sourceJar(type: Jar) { + from sourceSets.main.allSource + classifier = 'sources' +} + +artifacts { + archives deobfJar + archives sourceJar +} + +install.dependsOn reobf + +idea { + module { + inheritOutputDirs = false + outputDir = new File(compileJava.destinationDir.parentFile.parentFile, "idea") + testOutputDir = new File(compileTestJava.destinationDir.parentFile.parentFile, "ideaTest") + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..4b6b2cf --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +modGroup = space.impact +modVersion = 0.0.1 +modBaseName = ImpactAPI + +forgeVersion = 1.7.10-10.13.4.1614-1.7.10 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..99340b4 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..82b0d2f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 04 01:52:14 CET 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/space/impact/api/ConfigurationHandler.java b/src/main/java/space/impact/api/ConfigurationHandler.java new file mode 100644 index 0000000..635509b --- /dev/null +++ b/src/main/java/space/impact/api/ConfigurationHandler.java @@ -0,0 +1,70 @@ +package space.impact.api; + +import cpw.mods.fml.client.event.ConfigChangedEvent; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +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; + + private Configuration config; + private int maxCoexistingHologram; + private boolean removeCollidingHologram; + + ConfigurationHandler() { + FMLCommonHandler.instance().bus().register(this); + } + + void init(File f) { + config = new Configuration(f, ConfigurationVersion.latest().getVersionMarker()); + ConfigurationVersion.migrateToLatest(config); + loadConfig(); + setLanguageKeys(); + } + + private void setLanguageKeys() { + for (String categoryName : config.getCategoryNames()) { + ConfigCategory category = config.getCategory(categoryName); + category.setLanguageKey("impactapi.config." + categoryName); + for (Map.Entry entry : category.entrySet()) { + entry.getValue().setLanguageKey(String.format("%s.%s", category.getLanguagekey(), entry.getKey())); + } + } + } + + 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."); + if (config.hasChanged()) { + config.save(); + } + } + + @SubscribeEvent + public void onConfigChange(ConfigChangedEvent.PostConfigChangedEvent e) { + if (e.modID.equals(ImpactAPI.MOD_ID)) { + loadConfig(); + } + } + + public int getMaxCoexistingHologram() { + return maxCoexistingHologram; + } + + public boolean isRemoveCollidingHologram() { + return removeCollidingHologram; + } + + Configuration getConfig() { + return config; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/ConfigurationVersion.java b/src/main/java/space/impact/api/ConfigurationVersion.java new file mode 100644 index 0000000..5c96bbc --- /dev/null +++ b/src/main/java/space/impact/api/ConfigurationVersion.java @@ -0,0 +1,44 @@ +package space.impact.api; + +import net.minecraftforge.common.config.Configuration; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Objects; + +enum ConfigurationVersion { + V1 { + @Override + protected void step(Configuration c) { + } + }; + + private static final ConfigurationVersion[] VALUES = values(); + + private final String versionMarker; + + ConfigurationVersion() { + this.versionMarker = name(); + } + + public static void migrateToLatest(Configuration c) { + for (int i = identify(c).ordinal() + 1; i < VALUES.length; i++) { + VALUES[i].step(c); + } + } + + public static ConfigurationVersion latest() { + return VALUES[VALUES.length - 1]; + } + + public static ConfigurationVersion identify(Configuration c) { + return Arrays.stream(VALUES).filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker())).findFirst().orElse(V1); + } + + @Nullable + public String getVersionMarker() { + return versionMarker; + } + + protected abstract void step(Configuration c); +} diff --git a/src/main/java/space/impact/api/ImpactAPI.java b/src/main/java/space/impact/api/ImpactAPI.java new file mode 100644 index 0000000..4e2240d --- /dev/null +++ b/src/main/java/space/impact/api/ImpactAPI.java @@ -0,0 +1,114 @@ +package space.impact.api; + +import cpw.mods.fml.common.network.NetworkRegistry; +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; +import space.impact.api.multiblocks.alignment.IAlignment; +import space.impact.api.multiblocks.alignment.IAlignmentProvider; +import space.impact.api.multiblocks.alignment.constructable.ConstructableUtility; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; +import space.impact.api.net.AlignmentMessage; + +import static space.impact.api.Main.proxy; + +/** + * Стабильный интерфейс к внутренним компонентам Impact API. Обратная совместимость поддерживается в максимально возможной степени. + */ +public class ImpactAPI { + public static final String MOD_ID = "impactapi"; + public static final int WHITE = 0, ORANGE = 1, MAGENTA = 2, L_BLUE = 3, YELLOW = 4, LIME = 5, PINK = 6, GRAY = 7, L_GRAY = 8, CYAN = 9, PURPLE = 10, BLUE = 11, BROWN = 12, GREEN = 13, RED = 14, BLACK = 15; + + /** + * Запуск процесса подсказок. Все частицы подсказок, сгенерированные в течение одного процесса, будут считаться принадлежащими одной голограмме. + *

+ * Вам не нужно вызывать эту функцию, если только ваш триггер инструмент не вызвал функцию {@link ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)} + */ + public static void startHinting(World w) { + proxy.startHinting(w); + } + + /** + * Запуск текущего процесса подсказок. Все частицы подсказок, сгенерированные в течение одного процесса, будут считаться принадлежащими одной голограмме. + *

+ * Вам не нужно вызывать эту функцию, если только ваш триггер инструмент не вызвал функцию {@link ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)} + */ + public static void endHinting(World w) { + proxy.endHinting(w); + } + + public static void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) { + proxy.hintParticleTinted(w, x, y, z, icons, RGBa); + } + + public static void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) { + proxy.hintParticleTinted(w, x, y, z, block, meta, RGBa); + } + + public static void hintParticle(World w, int x, int y, int z, IIcon[] icons) { + proxy.hintParticle(w, x, y, z, icons); + } + + public static void hintParticle(World w, int x, int y, int z, Block block, int meta) { + proxy.hintParticle(w, x, y, z, block, meta); + } + + /** + * Запрос ExtendedFacing данного TileEntity. + * В дальнейшем ExtendedFacing будет установлен для данного TileEntity через {@link IAlignment#setExtendedFacing(ExtendedFacing)} после получения ответа от сервера. + * + * @throws IllegalArgumentException если не является TileEntity или предоставил null. + */ + public static void queryAlignment(IAlignmentProvider provider) { + Main.net.sendToServer(new AlignmentMessage.AlignmentQuery(provider)); + } + + /** + * Отправляет ExtendedFacing TileEntity всем игрокам. Может вызываться только на стороне сервера. + * + * @throws IllegalArgumentException если не является TileEntity или предоставил null. + */ + public static void sendAlignment(IAlignmentProvider provider) { + Main.net.sendToAll(new AlignmentMessage.AlignmentData(provider)); + } + + /** + * Отправляет ExtendedFacing TileEntity игроку. Может вызываться только на стороне сервера. + * + * @throws IllegalArgumentException если не является TileEntity или предоставил null. + */ + public static void sendAlignment(IAlignmentProvider provider, EntityPlayerMP player) { + Main.net.sendTo(new AlignmentMessage.AlignmentData(provider), player); + } + + /** + * Отправляет ExtendedFacing TileEntity всем игрокам вокруг целевой точки. Может вызываться только на стороне сервера. + * + * @throws IllegalArgumentException если не является TileEntity или предоставил null. + */ + public static void sendAlignment(IAlignmentProvider provider, NetworkRegistry.TargetPoint targetPoint) { + Main.net.sendToAllAround(new AlignmentMessage.AlignmentData(provider), targetPoint); + } + + + /** + * Отправляет ExtendedFacing TileEntity всем игрокам в измерении. Может вызываться только на стороне сервера. + * + * @throws IllegalArgumentException если не является TileEntity или предоставил null. + */ + public static void sendAlignment(IAlignmentProvider provider, World dimension) { + Main.net.sendToDimension(new AlignmentMessage.AlignmentData(provider), dimension.provider.dimensionId); + } + + public static Block getBlockHint() { + return Main.blockHint; + } + + public static Item getItemBlockHint() { + return Main.itemBlockHint; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/Main.java b/src/main/java/space/impact/api/Main.java new file mode 100644 index 0000000..b812a1f --- /dev/null +++ b/src/main/java/space/impact/api/Main.java @@ -0,0 +1,65 @@ +package space.impact.api; + +import space.impact.api.net.AlignmentMessage; +import space.impact.api.block.BlockHint; +import space.impact.api.item.ItemBlockHint; +import space.impact.api.item.ItemConstructableTrigger; +import space.impact.api.proxy.CommonProxy; +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.SidedProxy; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.network.NetworkRegistry; +import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.block.Block; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemBlock; + +@Mod(modid = ImpactAPI.MOD_ID, name = "Impact API", version = "0.0.1", dependencies = "required-after:Forge@[10.13.4.1614,);") +public class Main { + + @SidedProxy(serverSide = "space.impact.api.proxy.CommonProxy", clientSide = "space.impact.api.proxy.ClientProxy") + static CommonProxy proxy; + static SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(ImpactAPI.MOD_ID); + + static { + net.registerMessage(AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER); + net.registerMessage(AlignmentMessage.ClientHandler.class, AlignmentMessage.AlignmentData.class, 1, Side.CLIENT); + } + + static Block blockHint; + static Item itemBlockHint; + static Item itemConstructableTrigger; + public static final CreativeTabs creativeTab = new CreativeTabs("impactapi") { + @Override + @SideOnly(Side.CLIENT) + public Item getTabIconItem() { + return ImpactAPI.getItemBlockHint(); + } + }; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent e) { + ConfigurationHandler.INSTANCE.init(e.getSuggestedConfigurationFile()); + GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint"); + itemBlockHint = ItemBlock.getItemFromBlock(ImpactAPI.getBlockHint()); + GameRegistry.registerItem(itemConstructableTrigger = new ItemConstructableTrigger(), itemConstructableTrigger.getUnlocalizedName()); + proxy.preInit(e); + } + + public static void addClientSideChatMessages(String... messages) { + proxy.addClientSideChatMessages(messages); + } + + public static EntityPlayer getCurrentPlayer() { + return proxy.getCurrentPlayer(); + } + + public static boolean isCurrentPlayer(EntityPlayer player) { + return proxy.isCurrentPlayer(player); + } +} diff --git a/src/main/java/space/impact/api/block/BlockHint.java b/src/main/java/space/impact/api/block/BlockHint.java new file mode 100644 index 0000000..a267e83 --- /dev/null +++ b/src/main/java/space/impact/api/block/BlockHint.java @@ -0,0 +1,53 @@ +package space.impact.api.block; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import space.impact.api.ImpactAPI; +import space.impact.api.Main; + +import java.util.List; + +public class BlockHint extends Block { + private static final IIcon[] hint = new IIcon[16]; + + public BlockHint() { + super(Material.iron); + setBlockName("impactapi.blockhint"); + setCreativeTab(Main.creativeTab); + } + + @Override + public void registerBlockIcons(IIconRegister icon) { + for (int i = 1; i <= hint.length; i++) { + hint[i - 1] = icon.registerIcon(ImpactAPI.MOD_ID + ":" + i); + } + } + + @Override + public IIcon getIcon(int side, int meta) { + return hint[meta]; + } + + @Override + @SideOnly(Side.CLIENT) + public IIcon getIcon(IBlockAccess w, int x, int y, int z, int side) { + int meta = w.getBlockMetadata(x, y, z); + return getIcon(side, meta); + } + + @Override + @SuppressWarnings("unchecked") + public void getSubBlocks(Item item, CreativeTabs tabs, List list) { + for (int i = 0; i <= 15; i++) { + list.add(new ItemStack(item, 1, i)); + } + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/entity/fx/EntityFXBlockHint.java b/src/main/java/space/impact/api/entity/fx/EntityFXBlockHint.java new file mode 100644 index 0000000..498a7b8 --- /dev/null +++ b/src/main/java/space/impact/api/entity/fx/EntityFXBlockHint.java @@ -0,0 +1,149 @@ +package space.impact.api.entity.fx; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.block.Block; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.init.Blocks; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; +import space.impact.api.Main; +import space.impact.api.proxy.ClientProxy; + +@SideOnly(Side.CLIENT) +public class EntityFXBlockHint extends EntityFX { + private final IIcon[] icons; + private short[] mRGBa = {255, 255, 255, 0}; + + public EntityFXBlockHint(World world) { + this(world, 0, 0, 0, Blocks.stone, 0); + } + + /** + * @param world - Клиентский мир + * @param x - Координата x + * @param y - Координата y + * @param z - Координата z + * @param icons DOWN, UP, NORTH, SOUTH, WEST, EAST + */ + public EntityFXBlockHint(World world, int x, int y, int z, IIcon[] icons) { + super(world, x + .2, y + .3, z + .2); + particleGravity = 0; + prevPosX = posX; + prevPosY = posY; + prevPosZ = posZ; + noClip = true; + particleMaxAge = 2000/* + Main.RANDOM.nextInt(200)*/; + this.icons = icons; + } + + public EntityFXBlockHint(World world, int x, int y, int z, Block block, int meta) { + super(world, x + .2, y + .3, z + .2); + particleGravity = 0; + prevPosX = posX; + prevPosY = posY; + prevPosZ = posZ; + noClip = true; + particleMaxAge = 2000/* + Main.RANDOM.nextInt(200)*/; + icons = new IIcon[6]; + for (int i = 0; i < 6; i++) { + icons[i] = block.getIcon(i, meta); + } + } + + public EntityFXBlockHint withColorTint(short[] coloure) { + this.mRGBa = coloure; + return this; + } + + @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_) { + float X = (float) (prevPosX + (posX - prevPosX) * (double) subTickTime - EntityFX.interpPosX); + float Y = (float) (prevPosY + (posY - prevPosY) * (double) subTickTime - EntityFX.interpPosY) - .3f / 2; + float Z = (float) (prevPosZ + (posZ - prevPosZ) * (double) subTickTime - EntityFX.interpPosZ); + + GL11.glPushMatrix(); + GL11.glDisable(GL11.GL_CULL_FACE); + GL11.glDepthMask(false); + GL11.glDisable(GL11.GL_DEPTH_TEST); + + tes.setColorRGBA((int) (mRGBa[0] * .9F), (int) (mRGBa[1] * .95F), (int) (mRGBa[2] * 1F), 222); + box(tes, X, Y, Z); + + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glDepthMask(true); + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glPopMatrix(); + } + + private void box(Tessellator tes, float X, float Y, float Z) { + float size = .6f; + for (int i = 0; i < 6; i++) { + if (icons[i] == null) { + continue; + } + + double u = icons[i].getMinU(); + double U = icons[i].getMaxU(); + double v = icons[i].getMinV(); + double V = icons[i].getMaxV(); + 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); + tes.addVertexWithUV(X + size, Y, Z, U, v); + tes.addVertexWithUV(X + size, Y, Z + size, U, V); + break; + case 1: + tes.addVertexWithUV(X, Y + size, Z, u, v); + tes.addVertexWithUV(X, Y + size, Z + size, u, V); + tes.addVertexWithUV(X + size, Y + size, Z + size, U, V); + tes.addVertexWithUV(X + size, Y + size, Z, U, v); + break; + case 2: + tes.addVertexWithUV(X, Y, Z, U, V); + tes.addVertexWithUV(X, Y + size, Z, U, v); + tes.addVertexWithUV(X + size, Y + size, Z, u, v); + tes.addVertexWithUV(X + size, Y, Z, u, V); + break; + case 3: + tes.addVertexWithUV(X + size, Y, Z + size, U, V); + tes.addVertexWithUV(X + size, Y + size, Z + size, U, v); + tes.addVertexWithUV(X, Y + size, Z + size, u, v); + tes.addVertexWithUV(X, Y, Z + size, u, V); + break; + case 4: + tes.addVertexWithUV(X, Y, Z + size, U, V); + tes.addVertexWithUV(X, Y + size, Z + size, U, v); + tes.addVertexWithUV(X, Y + size, Z, u, v); + tes.addVertexWithUV(X, Y, Z, u, V); + break; + case 5: + tes.addVertexWithUV(X + size, Y, Z, U, V); + tes.addVertexWithUV(X + size, Y + size, Z, U, v); + tes.addVertexWithUV(X + size, Y + size, Z + size, u, v); + tes.addVertexWithUV(X + size, Y, Z + size, u, V); + break; + } + + } + } + + @Override + public int getFXLayer() { + return 1; + } + + @Override + public boolean shouldRenderInPass(int pass) { + return pass == 2; + } + + @Override + public void setDead() { + super.setDead(); + ClientProxy.onHintDead(this); + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/entity/fx/WeightlessParticleFX.java b/src/main/java/space/impact/api/entity/fx/WeightlessParticleFX.java new file mode 100644 index 0000000..8acc81c --- /dev/null +++ b/src/main/java/space/impact/api/entity/fx/WeightlessParticleFX.java @@ -0,0 +1,37 @@ +package space.impact.api.entity.fx; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.world.World; + +@SideOnly(Side.CLIENT) +public class WeightlessParticleFX extends EntityFX { + public WeightlessParticleFX(World w, double x, double y, double z, double xx, double yy, double zz) { + super(w, x, y, z, xx, yy, zz); + this.motionX = xx + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F); + this.motionY = yy + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F); + this.motionZ = zz + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F); + this.particleRed = this.particleGreen = this.particleBlue = this.rand.nextFloat() * 0.3F + 0.7F; + this.particleScale = this.rand.nextFloat() * this.rand.nextFloat() * 6.0F + 1.0F; + this.particleMaxAge = (int) (16.0D / ((double) this.rand.nextFloat() * 0.8D + 0.2D)) + 2; + this.noClip = true; + } + + /** + * Вызывается для обновления позиции/логики FX. + */ + public void onUpdate() { + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + if (this.particleAge++ >= this.particleMaxAge) { + this.setDead(); + } + this.setParticleTextureIndex(7 - this.particleAge * 8 / this.particleMaxAge); + this.moveEntity(this.motionX, this.motionY, this.motionZ); + this.motionX *= 0.8999999761581421D; + this.motionY *= 0.8999999761581421D; + this.motionZ *= 0.8999999761581421D; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/item/ItemBlockHint.java b/src/main/java/space/impact/api/item/ItemBlockHint.java new file mode 100644 index 0000000..76393cd --- /dev/null +++ b/src/main/java/space/impact/api/item/ItemBlockHint.java @@ -0,0 +1,42 @@ +package space.impact.api.item; + +import net.minecraft.block.Block; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.player.EntityPlayer; +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 block) { + super(block); + setMaxDamage(0); + setHasSubtypes(true); + setCreativeTab(CreativeTabs.tabBlock); + } + + @Override + @SuppressWarnings("unchecked") + public void addInformation(ItemStack stack, EntityPlayer player, List list, boolean f3) { + // Подсказки + if (f3) { + list.add("F3 Enabled. Its Debug Information"); + list.add("Unlocalized Name: " + stack.getUnlocalizedName()); + list.add("Meta / Damage: " + stack.getItemDamage()); + } + list.add(translateToLocal("impactapi.blockhint.desc.0")); + } + + public int getMetadata(int aMeta) { + return aMeta; + } + + public String getUnlocalizedName(ItemStack aStack) { + return this.field_150939_a.getUnlocalizedName() + "." + getDamage(aStack); + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/item/ItemConstructableTrigger.java b/src/main/java/space/impact/api/item/ItemConstructableTrigger.java new file mode 100644 index 0000000..364cb27 --- /dev/null +++ b/src/main/java/space/impact/api/item/ItemConstructableTrigger.java @@ -0,0 +1,37 @@ +package space.impact.api.item; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import space.impact.api.Main; +import space.impact.api.multiblocks.alignment.constructable.ConstructableUtility; + +import java.util.List; + +import static net.minecraft.util.EnumChatFormatting.RED; +import static net.minecraft.util.StatCollector.translateToLocal; +import static space.impact.api.ImpactAPI.MOD_ID; + +public class ItemConstructableTrigger extends Item { + public ItemConstructableTrigger() { + setUnlocalizedName("impactapi.constructableTrigger"); + setTextureName(MOD_ID + ":itemConstructableTrigger"); + setCreativeTab(Main.creativeTab); + } + + @Override + 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.impactapi.constructableTrigger.desc.0")); // Triggers Constructable Interface + aList.add(RED + translateToLocal("item.impactapi.constructableTrigger.desc.1")); // Shows multiblock construction details, + aList.add(RED + translateToLocal("item.impactapi.constructableTrigger.desc.2")); // just Use on a multiblock controller. + aList.add(RED + translateToLocal("item.impactapi.constructableTrigger.desc.3")); // (Sneak Use in creative to build) + aList.add(RED + translateToLocal("item.impactapi.constructableTrigger.desc.4")); // Quantity affects tier/mode/type + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/AlignmentLimits.java b/src/main/java/space/impact/api/multiblocks/alignment/AlignmentLimits.java new file mode 100644 index 0000000..2781280 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/AlignmentLimits.java @@ -0,0 +1,19 @@ +package space.impact.api.multiblocks.alignment; + +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.multiblocks.alignment.enumerable.Flip; +import space.impact.api.multiblocks.alignment.enumerable.Rotation; + +class AlignmentLimits implements IAlignmentLimits { + + protected final boolean[] validStates; + + AlignmentLimits(boolean[] validStates) { + this.validStates = validStates; + } + + @Override + public boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip) { + return validStates[IAlignment.getAlignmentIndex(direction, rotation, flip)]; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/AlignmentUtility.java b/src/main/java/space/impact/api/multiblocks/alignment/AlignmentUtility.java new file mode 100644 index 0000000..b93a956 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/AlignmentUtility.java @@ -0,0 +1,31 @@ +package space.impact.api.multiblocks.alignment; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraftforge.common.util.FakePlayer; + +public class AlignmentUtility { + private AlignmentUtility() { + } + + public static boolean handle(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ) { + TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ); + if (tTileEntity == null || aPlayer instanceof FakePlayer) { + return aPlayer instanceof EntityPlayerMP; + } + if (aPlayer instanceof EntityPlayerMP && tTileEntity instanceof IAlignmentProvider) { + IAlignment alignment = ((IAlignmentProvider) tTileEntity).getAlignment(); + if (alignment != null) { + if (aPlayer.isSneaking()) { + alignment.toolSetFlip(null); + } else { + alignment.toolSetRotation(null); + } + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/IAlignment.java b/src/main/java/space/impact/api/multiblocks/alignment/IAlignment.java new file mode 100644 index 0000000..7c44a80 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/IAlignment.java @@ -0,0 +1,184 @@ +package space.impact.api.multiblocks.alignment; + +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.multiblocks.alignment.enumerable.Direction; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; +import space.impact.api.multiblocks.alignment.enumerable.Flip; +import space.impact.api.multiblocks.alignment.enumerable.Rotation; + +public interface IAlignment extends IAlignmentLimits, IAlignmentProvider { + int DIRECTIONS_COUNT = Direction.VALUES.length; + int ROTATIONS_COUNT = Rotation.VALUES.length; + int FLIPS_COUNT = Flip.VALUES.length; + int STATES_COUNT = ExtendedFacing.STATES_COUNT; + + static int getAlignmentIndex(ForgeDirection direction, Rotation rotation, Flip flip) { + return (direction.ordinal() * ROTATIONS_COUNT + rotation.getIndex()) * FLIPS_COUNT + flip.getIndex(); + } + + ExtendedFacing getExtendedFacing(); + + void setExtendedFacing(ExtendedFacing alignment); + + IAlignmentLimits getAlignmentLimits(); + + @Override + default IAlignment getAlignment() { + return this; + } + + default ForgeDirection getDirection() { + return getExtendedFacing().getDirection(); + } + + default void setDirection(ForgeDirection direction) { + setExtendedFacing(getExtendedFacing().with(direction)); + } + + default Rotation getRotation() { + return getExtendedFacing().getRotation(); + } + + default void setRotation(Rotation rotation) { + setExtendedFacing(getExtendedFacing().with(rotation)); + } + + default Flip getFlip() { + return getExtendedFacing().getFlip(); + } + + default void setFlip(Flip flip) { + setExtendedFacing(getExtendedFacing().with(flip)); + } + + 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++) { + if (toolSetDirection(Direction.VALUES[(j + i) % valuesLength].getForgeDirection())) { + return true; + } + } + } else { + for (ExtendedFacing extendedFacing : ExtendedFacing.FOR_FACING.get(direction)) { + if (checkedSetExtendedFacing(extendedFacing)) { + return true; + } + } + } + return false; + } + + default boolean checkedSetDirection(ForgeDirection direction) { + if (isNewDirectionValid(direction)) { + setDirection(direction); + return true; + } + return false; + } + + default boolean canSetToDirectionAny(ForgeDirection direction) { + for (ExtendedFacing extendedFacing : ExtendedFacing.FOR_FACING.get(direction)) { + if (isNewExtendedFacingValid(extendedFacing)) { + return true; + } + } + return false; + } + + default boolean toolSetRotation(Rotation rotation) { + if (rotation == null) { + int flips = Flip.VALUES.length; + 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]))) { + return true; + } + } + } + return false; + } else { + return checkedSetRotation(rotation); + } + } + + default boolean checkedSetRotation(Rotation rotation) { + if (isNewRotationValid(rotation)) { + setRotation(rotation); + return true; + } + return false; + } + + default boolean toolSetFlip(Flip flip) { + if (flip == null) { + for (int i = 1, j = getFlip().ordinal(), valuesLength = Flip.VALUES.length; i < valuesLength; i++) { + if (checkedSetFlip(Flip.VALUES[(j + i) % valuesLength])) { + return true; + } + } + return false; + } else { + return checkedSetFlip(flip); + } + } + + default boolean checkedSetFlip(Flip flip) { + if (isNewFlipValid(flip)) { + setFlip(flip); + return true; + } + return false; + } + + default boolean toolSetExtendedFacing(ExtendedFacing extendedFacing) { + if (extendedFacing == null) { + for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length; i < valuesLength; i++) { + if (checkedSetExtendedFacing(ExtendedFacing.VALUES[(j + i) % valuesLength])) { + return true; + } + } + return false; + } else { + return checkedSetExtendedFacing(extendedFacing); + } + } + + default boolean checkedSetExtendedFacing(ExtendedFacing alignment) { + if (isNewExtendedFacingValid(alignment)) { + setExtendedFacing(alignment); + return true; + } + return false; + } + + default boolean isNewDirectionValid(ForgeDirection direction) { + return isNewExtendedFacingValid(direction, getRotation(), getFlip()); + } + + default boolean isNewRotationValid(Rotation rotation) { + return isNewExtendedFacingValid(getDirection(), rotation, getFlip()); + } + + default boolean isNewFlipValid(Flip flip) { + return isNewExtendedFacingValid(getDirection(), getRotation(), flip); + } + + default boolean isExtendedFacingValid() { + return isNewExtendedFacingValid(getDirection(), getRotation(), getFlip()); + } + + @Override + default boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip) { + return getAlignmentLimits().isNewExtendedFacingValid(direction, rotation, flip); + } + + @Override + default boolean isNewExtendedFacingValid(ExtendedFacing alignment) { + return getAlignmentLimits().isNewExtendedFacingValid( + alignment.getDirection(), + alignment.getRotation(), + alignment.getFlip() + ); + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/IAlignmentLimits.java b/src/main/java/space/impact/api/multiblocks/alignment/IAlignmentLimits.java new file mode 100644 index 0000000..68a0216 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/IAlignmentLimits.java @@ -0,0 +1,141 @@ +package space.impact.api.multiblocks.alignment; + +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; +import space.impact.api.multiblocks.alignment.enumerable.Flip; +import space.impact.api.multiblocks.alignment.enumerable.Rotation; + +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Function; + +public interface IAlignmentLimits { + + IAlignmentLimits UNLIMITED = (direction, rotation, flip) -> true; + + static IAlignmentLimits allowOnly(ExtendedFacing... allowedFacings) { + Builder builder = Builder.denyAll(); + for (ExtendedFacing allowedFacing : allowedFacings) { + builder.allow(allowedFacing); + } + return builder.build(); + } + + static IAlignmentLimits denyOnly(ExtendedFacing... allowedFacings) { + Builder builder = Builder.allowAll(); + for (ExtendedFacing allowedFacing : allowedFacings) { + builder.deny(allowedFacing); + } + return builder.build(); + } + + boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip); + + default boolean isNewExtendedFacingValid(ExtendedFacing alignment) { + return isNewExtendedFacingValid( + alignment.getDirection(), + alignment.getRotation(), + alignment.getFlip() + ); + } + + class Builder { + protected final boolean[] validStates = new boolean[IAlignment.STATES_COUNT]; + + private Builder() {} + + public static Builder allowAll() { + Builder b = new Builder(); + Arrays.fill(b.validStates, true); + return b; + } + + public static Builder denyAll() { + Builder b = new Builder(); + Arrays.fill(b.validStates, true); + return b; + } + + public Builder deny(ForgeDirection fd) { + 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); + return this; + } + + public Builder deny(ExtendedFacing o) { + validStates[o.getIndex()] = false; + return this; + } + + public Builder allow(ExtendedFacing o) { + validStates[o.getIndex()] = true; + return this; + } + + public Builder deny(Rotation fd) { + 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); + return this; + } + + public Builder deny(Flip fd) { + 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); + return this; + } + + public Builder filter(Function> predicate) { + for (ExtendedFacing value : ExtendedFacing.VALUES) { + predicate.apply(value).ifPresent(bool -> validStates[value.getIndex()] = bool); + } + return this; + } + + public Builder ensureDuplicates() { + for (ExtendedFacing value : ExtendedFacing.VALUES) { + if (validStates[value.getIndex()]) { + validStates[value.getDuplicate().getIndex()] = true; + } + } + return this; + } + + /** + * Prefers rotation over flip, so both flip will get translated to opposite rotation and no flip + * + * @param flip the preferred flip to be used Horizontal or vertical + * @return this + */ + public Builder ensureNoDuplicates(Flip flip) { + if (flip == Flip.BOTH || flip == Flip.NONE) { + throw new IllegalArgumentException("Preffered Flip must be Horizontal or Vertical"); + } + flip = flip.getOpposite(); + for (ExtendedFacing value : ExtendedFacing.VALUES) { + if (validStates[value.getIndex()]) { + if (value.getFlip() == Flip.BOTH || value.getFlip() == flip) { + validStates[value.getIndex()] = false; + validStates[value.getDuplicate().getIndex()] = true; + } + } + } + return this; + } + + public IAlignmentLimits build() { + return new AlignmentLimits(validStates); + } + } +} diff --git a/src/main/java/space/impact/api/multiblocks/alignment/IAlignmentProvider.java b/src/main/java/space/impact/api/multiblocks/alignment/IAlignmentProvider.java new file mode 100644 index 0000000..f5db176 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/IAlignmentProvider.java @@ -0,0 +1,8 @@ +package space.impact.api.multiblocks.alignment; + +import javax.annotation.Nullable; + +public interface IAlignmentProvider { + @Nullable + IAlignment getAlignment(); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/IntegerAxisSwap.java b/src/main/java/space/impact/api/multiblocks/alignment/IntegerAxisSwap.java new file mode 100644 index 0000000..00bb4a6 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/IntegerAxisSwap.java @@ -0,0 +1,84 @@ +package space.impact.api.multiblocks.alignment; + +import net.minecraft.util.Vec3; +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.multiblocks.alignment.enumerable.Direction; +import space.impact.api.util.Vec3Impl; + +import static java.lang.Math.abs; + +public class IntegerAxisSwap { + private final Vec3Impl forFirstAxis; + private final Vec3Impl forSecondAxis; + private final Vec3Impl forThirdAxis; + + public IntegerAxisSwap(ForgeDirection forFirstAxis, ForgeDirection forSecondAxis, ForgeDirection forThirdAxis) { + 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()); + } + } + + 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() + ); + } + + 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() + ); + } + + 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 + ); + } + + 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 + ); + } + + public void translate(int[] point, int[] out) { + out[0] = forFirstAxis.get0() * point[0] + forFirstAxis.get1() * point[1] + forFirstAxis.get2() * point[2]; + out[1] = forSecondAxis.get0() * point[0] + forSecondAxis.get1() * point[1] + forSecondAxis.get2() * point[2]; + out[2] = forThirdAxis.get0() * point[0] + forThirdAxis.get1() * point[1] + forThirdAxis.get2() * point[2]; + } + + public void inverseTranslate(int[] point, int[] out) { + out[0] = forFirstAxis.get0() * point[0] + forSecondAxis.get0() * point[1] + forThirdAxis.get0() * point[2]; + out[1] = forFirstAxis.get1() * point[0] + forSecondAxis.get1() * point[1] + forThirdAxis.get1() * point[2]; + out[2] = forFirstAxis.get2() * point[0] + forSecondAxis.get2() * point[1] + forThirdAxis.get2() * point[2]; + } + + public void translate(double[] point, double[] out) { + out[0] = forFirstAxis.get0() * point[0] + forFirstAxis.get1() * point[1] + forFirstAxis.get2() * point[2]; + out[1] = forSecondAxis.get0() * point[0] + forSecondAxis.get1() * point[1] + forSecondAxis.get2() * point[2]; + out[2] = forThirdAxis.get0() * point[0] + forThirdAxis.get1() * point[1] + forThirdAxis.get2() * point[2]; + } + + public void inverseTranslate(double[] point, double[] out) { + out[0] = forFirstAxis.get0() * point[0] + forSecondAxis.get0() * point[1] + forThirdAxis.get0() * point[2]; + out[1] = forFirstAxis.get1() * point[0] + forSecondAxis.get1() * point[1] + forThirdAxis.get1() * point[2]; + out[2] = forFirstAxis.get2() * point[0] + forSecondAxis.get2() * point[1] + forThirdAxis.get2() * point[2]; + } +} diff --git a/src/main/java/space/impact/api/multiblocks/alignment/constructable/ConstructableUtility.java b/src/main/java/space/impact/api/multiblocks/alignment/constructable/ConstructableUtility.java new file mode 100644 index 0000000..f3871dc --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/constructable/ConstructableUtility.java @@ -0,0 +1,78 @@ +package space.impact.api.multiblocks.alignment.constructable; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.ImpactAPI; +import space.impact.api.Main; +import space.impact.api.multiblocks.alignment.IAlignment; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; + +public class ConstructableUtility { + + private ConstructableUtility() { + } + + public static boolean handle(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) { + ImpactAPI.startHinting(aWorld); + boolean ret = handle0(aStack, aPlayer, aWorld, aX, aY, aZ, aSide); + ImpactAPI.endHinting(aWorld); + return ret; + } + + 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) { + // Построить по частицам + 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 iMultipleInfoContainer = IMultiBlockInfoContainer.get(tTileEntity.getClass()); + if (tTileEntity instanceof IAlignment) { + iMultipleInfoContainer.construct(aStack, false, tTileEntity, ((IAlignment) tTileEntity).getExtendedFacing()); + } else { + iMultipleInfoContainer.construct(aStack, false, tTileEntity, ExtendedFacing.of(ForgeDirection.getOrientation(aSide))); + } + } + } + return true; + // частицы и текст на стороне клиента + } else if (Main.isCurrentPlayer(aPlayer)) { + if (tTileEntity instanceof IConstructableProvider) { + IConstructable constructable = ((IConstructableProvider) tTileEntity).getConstructable(); + if (constructable != null) { + constructable.construct(aStack, true); + Main.addClientSideChatMessages(constructable.getStructureDescription(aStack)); + } + } else if (tTileEntity instanceof IConstructable) { + IConstructable constructable = (IConstructable) tTileEntity; + constructable.construct(aStack, true); + Main.addClientSideChatMessages(constructable.getStructureDescription(aStack)); + return false; + } else if (IMultiBlockInfoContainer.contains(tTileEntity.getClass())) { + IMultiBlockInfoContainer iMultipleInfoContainer = IMultiBlockInfoContainer.get(tTileEntity.getClass()); + if (tTileEntity instanceof IAlignment) { + iMultipleInfoContainer.construct(aStack, true, tTileEntity, ((IAlignment) tTileEntity).getExtendedFacing()); + } else { + iMultipleInfoContainer.construct(aStack, true, tTileEntity, ExtendedFacing.of(ForgeDirection.getOrientation(aSide))); + } + Main.addClientSideChatMessages(IMultiBlockInfoContainer.get(tTileEntity.getClass()).getDescription(aStack)); + return false; + } + } + return false; + } +} diff --git a/src/main/java/space/impact/api/multiblocks/alignment/constructable/IConstructable.java b/src/main/java/space/impact/api/multiblocks/alignment/constructable/IConstructable.java new file mode 100644 index 0000000..19d45c9 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/constructable/IConstructable.java @@ -0,0 +1,15 @@ +package space.impact.api.multiblocks.alignment.constructable; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.item.ItemStack; + +/** + * @author Tec on 24.03.2017. + */ +public interface IConstructable { + void construct(ItemStack stackSize, boolean hintsOnly); + + @SideOnly(Side.CLIENT) + String[] getStructureDescription(ItemStack stackSize); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/constructable/IConstructableProvider.java b/src/main/java/space/impact/api/multiblocks/alignment/constructable/IConstructableProvider.java new file mode 100644 index 0000000..affe89d --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/constructable/IConstructableProvider.java @@ -0,0 +1,14 @@ +package space.impact.api.multiblocks.alignment.constructable; + +import javax.annotation.Nullable; + +/** + * Реализуйте этот интерфейс, если этот TileEntity МОЖЕТ быть построен + */ +public interface IConstructableProvider { + /** + * @return null, если не может построить, и instance в противном случае. + */ + @Nullable + IConstructable getConstructable(); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/constructable/IMultiBlockInfoContainer.java b/src/main/java/space/impact/api/multiblocks/alignment/constructable/IMultiBlockInfoContainer.java new file mode 100644 index 0000000..39af977 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/constructable/IMultiBlockInfoContainer.java @@ -0,0 +1,47 @@ +package space.impact.api.multiblocks.alignment.constructable; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; + +import java.util.HashMap; + +/** + * Для реализации IConstructable на не собственных TileEntities + *

+ * Создает мета класс + */ +public interface IMultiBlockInfoContainer { + + HashMap> MULTIBLOCK_MAP = new HashMap<>(); + + /** + * Конкретного ограничения на фазу загрузки нет, но обычно ее не следует + * вызывать до того, как TileEntities будет должным образом зарегистрирован. + *

+ * Рекомендация: загружать вместе с TileEntities, при регистрации. + */ + static void registerTileClass(Class clazz, IMultiBlockInfoContainer info) { + MULTIBLOCK_MAP.put(clazz.getCanonicalName(), info); + } + + static void registerTileClass(String CanonicalNameClass, IMultiBlockInfoContainer info) { + MULTIBLOCK_MAP.put(CanonicalNameClass, info); + } + + @SuppressWarnings("unchecked") + static IMultiBlockInfoContainer get(Class tClass) { + return (IMultiBlockInfoContainer) MULTIBLOCK_MAP.get(tClass.getCanonicalName()); + } + + static boolean contains(Class tClass) { + return MULTIBLOCK_MAP.containsKey(tClass.getCanonicalName()); + } + + void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide); + + @SideOnly(Side.CLIENT) + String[] getDescription(ItemStack stackSize); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Direction.java b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Direction.java new file mode 100644 index 0000000..410a3a8 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Direction.java @@ -0,0 +1,35 @@ +package space.impact.api.multiblocks.alignment.enumerable; + +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.util.Vec3Impl; + +public enum Direction { + + DOWN(ForgeDirection.DOWN), + UP(ForgeDirection.UP), + NORTH(ForgeDirection.NORTH), + SOUTH(ForgeDirection.SOUTH), + WEST(ForgeDirection.WEST), + EAST(ForgeDirection.EAST); + + public static final Direction[] VALUES = values(); + private final ForgeDirection forgeDirection; + private final Vec3Impl axisVector; + + Direction(ForgeDirection forgeDirection) { + this.forgeDirection = forgeDirection; + axisVector = new Vec3Impl(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ); + } + + public static Vec3Impl getAxisVector(ForgeDirection forgeDirection) { + return VALUES[forgeDirection.ordinal()].axisVector; + } + + public ForgeDirection getForgeDirection() { + return forgeDirection; + } + + public Vec3Impl getAxisVector() { + return axisVector; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/enumerable/ExtendedFacing.java b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/ExtendedFacing.java new file mode 100644 index 0000000..9d16baf --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/ExtendedFacing.java @@ -0,0 +1,381 @@ +package space.impact.api.multiblocks.alignment.enumerable; + +import com.google.common.collect.ImmutableSet; +import net.minecraft.util.Vec3; +import net.minecraftforge.common.util.ForgeDirection; +import space.impact.api.multiblocks.alignment.IAlignment; +import space.impact.api.multiblocks.alignment.IntegerAxisSwap; +import space.impact.api.util.Vec3Impl; + +import java.util.*; + +import static java.lang.Math.abs; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.*; + +public enum ExtendedFacing { + DOWN_NORMAL_NONE("down normal none"), + DOWN_NORMAL_HORIZONTAL("down normal horizontal"), + DOWN_NORMAL_VERTICAL("down normal vertical"), + DOWN_NORMAL_BOTH("down normal both"), + DOWN_CLOCKWISE_NONE("down clockwise none"), + DOWN_CLOCKWISE_HORIZONTAL("down clockwise horizontal"), + DOWN_CLOCKWISE_VERTICAL("down clockwise vertical"), + DOWN_CLOCKWISE_BOTH("down clockwise both"), + DOWN_UPSIDE_DOWN_NONE("down upside down none"), + DOWN_UPSIDE_DOWN_HORIZONTAL("down upside down horizontal"), + DOWN_UPSIDE_DOWN_VERTICAL("down upside down vertical"), + DOWN_UPSIDE_DOWN_BOTH("down upside down both"), + DOWN_COUNTER_CLOCKWISE_NONE("down counter clockwise none"), + DOWN_COUNTER_CLOCKWISE_HORIZONTAL("down counter clockwise horizontal"), + DOWN_COUNTER_CLOCKWISE_VERTICAL("down counter clockwise vertical"), + DOWN_COUNTER_CLOCKWISE_BOTH("down counter clockwise both"), + UP_NORMAL_NONE("up normal none"), + UP_NORMAL_HORIZONTAL("up normal horizontal"), + UP_NORMAL_VERTICAL("up normal vertical"), + UP_NORMAL_BOTH("up normal both"), + UP_CLOCKWISE_NONE("up clockwise none"), + UP_CLOCKWISE_HORIZONTAL("up clockwise horizontal"), + UP_CLOCKWISE_VERTICAL("up clockwise vertical"), + UP_CLOCKWISE_BOTH("up clockwise both"), + UP_UPSIDE_DOWN_NONE("up upside down none"), + UP_UPSIDE_DOWN_HORIZONTAL("up upside down horizontal"), + UP_UPSIDE_DOWN_VERTICAL("up upside down vertical"), + UP_UPSIDE_DOWN_BOTH("up upside down both"), + UP_COUNTER_CLOCKWISE_NONE("up counter clockwise none"), + UP_COUNTER_CLOCKWISE_HORIZONTAL("up counter clockwise horizontal"), + UP_COUNTER_CLOCKWISE_VERTICAL("up counter clockwise vertical"), + UP_COUNTER_CLOCKWISE_BOTH("up counter clockwise both"), + NORTH_NORMAL_NONE("north normal none"), + NORTH_NORMAL_HORIZONTAL("north normal horizontal"), + NORTH_NORMAL_VERTICAL("north normal vertical"), + NORTH_NORMAL_BOTH("north normal both"), + NORTH_CLOCKWISE_NONE("north clockwise none"), + NORTH_CLOCKWISE_HORIZONTAL("north clockwise horizontal"), + NORTH_CLOCKWISE_VERTICAL("north clockwise vertical"), + NORTH_CLOCKWISE_BOTH("north clockwise both"), + NORTH_UPSIDE_DOWN_NONE("north upside down none"), + NORTH_UPSIDE_DOWN_HORIZONTAL("north upside down horizontal"), + NORTH_UPSIDE_DOWN_VERTICAL("north upside down vertical"), + NORTH_UPSIDE_DOWN_BOTH("north upside down both"), + NORTH_COUNTER_CLOCKWISE_NONE("north counter clockwise none"), + NORTH_COUNTER_CLOCKWISE_HORIZONTAL("north counter clockwise horizontal"), + NORTH_COUNTER_CLOCKWISE_VERTICAL("north counter clockwise vertical"), + NORTH_COUNTER_CLOCKWISE_BOTH("north counter clockwise both"), + SOUTH_NORMAL_NONE("south normal none"), + SOUTH_NORMAL_HORIZONTAL("south normal horizontal"), + SOUTH_NORMAL_VERTICAL("south normal vertical"), + SOUTH_NORMAL_BOTH("south normal both"), + SOUTH_CLOCKWISE_NONE("south clockwise none"), + SOUTH_CLOCKWISE_HORIZONTAL("south clockwise horizontal"), + SOUTH_CLOCKWISE_VERTICAL("south clockwise vertical"), + SOUTH_CLOCKWISE_BOTH("south clockwise both"), + SOUTH_UPSIDE_DOWN_NONE("south upside down none"), + SOUTH_UPSIDE_DOWN_HORIZONTAL("south upside down horizontal"), + SOUTH_UPSIDE_DOWN_VERTICAL("south upside down vertical"), + SOUTH_UPSIDE_DOWN_BOTH("south upside down both"), + SOUTH_COUNTER_CLOCKWISE_NONE("south counter clockwise none"), + SOUTH_COUNTER_CLOCKWISE_HORIZONTAL("south counter clockwise horizontal"), + SOUTH_COUNTER_CLOCKWISE_VERTICAL("south counter clockwise vertical"), + SOUTH_COUNTER_CLOCKWISE_BOTH("south counter clockwise both"), + WEST_NORMAL_NONE("west normal none"), + WEST_NORMAL_HORIZONTAL("west normal horizontal"), + WEST_NORMAL_VERTICAL("west normal vertical"), + WEST_NORMAL_BOTH("west normal both"), + WEST_CLOCKWISE_NONE("west clockwise none"), + WEST_CLOCKWISE_HORIZONTAL("west clockwise horizontal"), + WEST_CLOCKWISE_VERTICAL("west clockwise vertical"), + WEST_CLOCKWISE_BOTH("west clockwise both"), + WEST_UPSIDE_DOWN_NONE("west upside down none"), + WEST_UPSIDE_DOWN_HORIZONTAL("west upside down horizontal"), + WEST_UPSIDE_DOWN_VERTICAL("west upside down vertical"), + WEST_UPSIDE_DOWN_BOTH("west upside down both"), + WEST_COUNTER_CLOCKWISE_NONE("west counter clockwise none"), + WEST_COUNTER_CLOCKWISE_HORIZONTAL("west counter clockwise horizontal"), + WEST_COUNTER_CLOCKWISE_VERTICAL("west counter clockwise vertical"), + WEST_COUNTER_CLOCKWISE_BOTH("west counter clockwise both"), + EAST_NORMAL_NONE("east normal none"), + EAST_NORMAL_HORIZONTAL("east normal horizontal"), + EAST_NORMAL_VERTICAL("east normal vertical"), + EAST_NORMAL_BOTH("east normal both"), + EAST_CLOCKWISE_NONE("east clockwise none"), + EAST_CLOCKWISE_HORIZONTAL("east clockwise horizontal"), + EAST_CLOCKWISE_VERTICAL("east clockwise vertical"), + EAST_CLOCKWISE_BOTH("east clockwise both"), + EAST_UPSIDE_DOWN_NONE("east upside down none"), + EAST_UPSIDE_DOWN_HORIZONTAL("east upside down horizontal"), + EAST_UPSIDE_DOWN_VERTICAL("east upside down vertical"), + EAST_UPSIDE_DOWN_BOTH("east upside down both"), + EAST_COUNTER_CLOCKWISE_NONE("east counter clockwise none"), + EAST_COUNTER_CLOCKWISE_HORIZONTAL("east counter clockwise horizontal"), + EAST_COUNTER_CLOCKWISE_VERTICAL("east counter clockwise vertical"), + EAST_COUNTER_CLOCKWISE_BOTH("east counter clockwise both"); + + public static final ExtendedFacing DEFAULT = NORTH_NORMAL_NONE; + public static final ExtendedFacing[] VALUES = values(); + public static final Map> FOR_FACING = new HashMap<>(); + public static final int STATES_COUNT = VALUES.length; + 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))); + + static { + stream(values()).forEach(extendedFacing -> + FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> { + if (extendedFacings == null) { + extendedFacings = new ArrayList<>(); + } + extendedFacings.add(extendedFacing); + return extendedFacings; + }))); + } + + private final ForgeDirection direction; + private final ForgeDirection a, b, c; + private final Rotation rotation; + private final Flip flip; + + private final String name; + private final IntegerAxisSwap integerAxisSwap; + + ExtendedFacing(String name) { + this.name = name; + direction = Direction.VALUES[ordinal() / (IAlignment.ROTATIONS_COUNT * IAlignment.FLIPS_COUNT)].getForgeDirection(); + rotation = Rotation.VALUES[ordinal() / IAlignment.FLIPS_COUNT - direction.ordinal() * IAlignment.ROTATIONS_COUNT]; + flip = Flip.VALUES[ordinal() % IAlignment.FLIPS_COUNT]; + ForgeDirection a, b, c; + switch (direction) { + case DOWN: + a = ForgeDirection.WEST; + b = ForgeDirection.SOUTH; + c = ForgeDirection.UP; + break; + case UP: + a = ForgeDirection.EAST; + b = ForgeDirection.SOUTH; + c = ForgeDirection.DOWN; + break; + case NORTH: + a = ForgeDirection.WEST; + b = ForgeDirection.DOWN; + c = ForgeDirection.SOUTH; + break; + case SOUTH: + a = ForgeDirection.EAST; + b = ForgeDirection.DOWN; + c = ForgeDirection.NORTH; + break; + case WEST: + a = ForgeDirection.SOUTH; + b = ForgeDirection.DOWN; + c = ForgeDirection.EAST; + break; + case EAST: + a = ForgeDirection.NORTH; + b = ForgeDirection.DOWN; + c = ForgeDirection.WEST; + break; + default: + throw new RuntimeException("Is impossible..."); + } + switch (flip) {//This duplicates some axis swaps since flip boolean would do, but seems more convenient to use + case HORIZONTAL: + a = a.getOpposite(); + break; + case BOTH: + a = a.getOpposite(); + case VERTICAL: + b = b.getOpposite(); + break; + case NONE: + break; + default: + throw new RuntimeException("Even more impossible..."); + } + switch (rotation) { + case CLOCKWISE: { + ForgeDirection _a = a; + a = b; + b = _a.getOpposite(); + break; + } + case UPSIDE_DOWN: + a = a.getOpposite(); + b = b.getOpposite(); + break; + case COUNTER_CLOCKWISE: { + ForgeDirection _a = a; + a = b.getOpposite(); + b = _a; + break; + } + case NORMAL: + break; + default: + throw new RuntimeException("More impossible..."); + } + this.a = a; + this.b = b; + this.c = c; + integerAxisSwap = new IntegerAxisSwap(a, b, c); + } + + public static ExtendedFacing of(ForgeDirection direction, Rotation rotation, Flip flip) { + if (direction == ForgeDirection.UNKNOWN) { + return VALUES[IAlignment.getAlignmentIndex(ForgeDirection.NORTH, rotation, flip)]; + } + return VALUES[IAlignment.getAlignmentIndex(direction, rotation, flip)]; + } + + public static ExtendedFacing of(ForgeDirection direction) { + if (direction == ForgeDirection.UNKNOWN) { + return DEFAULT; + } + return VALUES[IAlignment.getAlignmentIndex(direction, Rotation.NORMAL, Flip.NONE)]; + } + + public static ImmutableSet getAllWith(ForgeDirection direction) {return LOOKUP_BY_DIRECTION.get(direction);} + + public static ImmutableSet getAllWith(Rotation rotation) {return LOOKUP_BY_ROTATION.get(rotation);} + + public static ImmutableSet getAllWith(Flip flip) {return LOOKUP_BY_FLIP.get(flip);} + + public static ExtendedFacing byName(String name) { + return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT)); + } + + public static ExtendedFacing byIndex(int index) { + return VALUES[abs(index % VALUES.length)]; + } + + public static ExtendedFacing random(Random rand) { + return VALUES[rand.nextInt(VALUES.length)]; + } + + public ExtendedFacing with(ForgeDirection direction) { + return of(direction, rotation, flip); + } + + public ExtendedFacing with(Rotation rotation) { + return of(direction, rotation, flip); + } + + public ExtendedFacing with(Flip flip) { + return of(direction, rotation, flip); + } + + public ExtendedFacing getOppositeDirection() { + return of(direction.getOpposite(), rotation, flip); + } + + public ExtendedFacing getOppositeRotation() { + return of(direction, rotation.getOpposite(), flip); + } + + public ExtendedFacing getOppositeFlip() { + return of(direction, rotation, flip.getOpposite()); + } + + /** + * Gets the same effective facing achieved by different rot/flip combo + * + * @return same effective facing, but different enum value + */ + public ExtendedFacing getDuplicate() { + return of(direction, rotation.getOpposite(), flip.getOpposite()); + } + + public int getIndex() { + return ordinal(); + } + + public String getName2() { + return this.name; + } + + public ForgeDirection getDirection() { + return direction; + } + + public Rotation getRotation() { + return rotation; + } + + public Flip getFlip() { + return flip; + } + + /** + * Translates relative to front facing offset to world offset + * + * @param abcOffset A,B,C offset (facing relative L--R,U--D,F--B) + * @return X, Y, Z offset in world + */ + public Vec3 getWorldOffset(Vec3 abcOffset) { + return integerAxisSwap.inverseTranslate(abcOffset); + } + + public Vec3Impl getWorldOffset(Vec3Impl abcOffset) { + return integerAxisSwap.inverseTranslate(abcOffset); + } + + public void getWorldOffset(int[] point, int[] out) { + integerAxisSwap.inverseTranslate(point, out); + } + + public void getWorldOffset(double[] point, double[] out) { + integerAxisSwap.inverseTranslate(point, out); + } + + + /** + * Translates world offset to relative front facing offset + * + * @param xyzOffset X,Y,Z offset in world + * @return Vec3 - A, B, C offset (facing relative L--R, U--D, F--B) + */ + public Vec3 getOffsetABC(Vec3 xyzOffset) { + return integerAxisSwap.translate(xyzOffset); + } + + public Vec3Impl getOffsetABC(Vec3Impl xyzOffset) { + return integerAxisSwap.translate(xyzOffset); + } + + public void getOffsetABC(int[] point, int[] out) { + integerAxisSwap.translate(point, out); + } + + public void getOffsetABC(double[] point, double[] out) { + integerAxisSwap.translate(point, out); + } + + public IntegerAxisSwap getIntegerAxisSwap() { + return integerAxisSwap; + } + + public ForgeDirection getRelativeLeftInWorld() { + return a; + } + + public ForgeDirection getRelativeRightInWorld() { + return a.getOpposite(); + } + + public ForgeDirection getRelativeDownInWorld() { + return b; + } + + public ForgeDirection getRelativeUpInWorld() { + return b.getOpposite(); + } + + public ForgeDirection getRelativeBackInWorld() { + return c; + } + + public ForgeDirection getRelativeForwardInWorld() { + return c.getOpposite(); + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Flip.java b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Flip.java new file mode 100644 index 0000000..85dfad6 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Flip.java @@ -0,0 +1,75 @@ +package space.impact.api.multiblocks.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; + +public enum Flip { + NONE(3, "none"), + HORIZONTAL(2, "horizontal"), + VERTICAL(1, "vertical"), + BOTH(0, "both"); + + public static final Flip[] VALUES = values(); + private static final Map NAME_LOOKUP = stream(VALUES).collect(toMap(Flip::getName2, (flip) -> flip)); + private final int opposite; + private final String name; + + Flip(int oppositeIn, String nameIn) { + this.opposite = oppositeIn; + this.name = nameIn; + } + + public static Flip byName(String name) { + return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT)); + } + + public static Flip byIndex(int index) { + return VALUES[abs(index % VALUES.length)]; + } + + public static Flip random(@Nonnull Random rand) { + return VALUES[rand.nextInt(VALUES.length)]; + } + + public int getIndex() { + return ordinal(); + } + + public Flip getOpposite() { + return VALUES[opposite]; + } + + public String getName2() { + return this.name; + } + + public String toString() { + return this.name; + } + + public String getName() { + return this.name; + } + + public boolean isNotFlipped() { + return this == NONE; + } + + public boolean isBothFlipped() { + return this == BOTH; + } + + public boolean isHorizontallyFlipped() { + return this == HORIZONTAL || isBothFlipped(); + } + + public boolean isVerticallyFliped() { + return this == VERTICAL || isBothFlipped(); + } +} diff --git a/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Rotation.java b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Rotation.java new file mode 100644 index 0000000..eb997f3 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/alignment/enumerable/Rotation.java @@ -0,0 +1,75 @@ +package space.impact.api.multiblocks.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; + +public enum Rotation { + NORMAL(2, "normal"), + CLOCKWISE(3, "clockwise"), + UPSIDE_DOWN(0, "upside down"), + COUNTER_CLOCKWISE(1, "counter clockwise"); + + public static final Rotation[] VALUES = values(); + private static final Map NAME_LOOKUP = stream(VALUES).collect(toMap(Rotation::getName2, (rotation) -> rotation)); + private final int opposite; + private final String name; + + Rotation(int oppositeIn, String nameIn) { + this.opposite = oppositeIn; + this.name = nameIn; + } + + public static Rotation byName(String name) { + return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT)); + } + + public static Rotation byIndex(int index) { + return VALUES[abs(index % VALUES.length)]; + } + + public static Rotation random(@Nonnull Random rand) { + return VALUES[rand.nextInt(VALUES.length)]; + } + + public int getIndex() { + return ordinal(); + } + + public Rotation getOpposite() { + return VALUES[opposite]; + } + + public String getName2() { + return this.name; + } + + public String toString() { + return this.name; + } + + public String getName() { + return this.name; + } + + public boolean isNotRotated() { + return this == NORMAL; + } + + public boolean isClockwise() { + return this == CLOCKWISE; + } + + public boolean isCounterClockwise() { + return this == COUNTER_CLOCKWISE; + } + + public boolean isUpsideDown() { + return this == UPSIDE_DOWN; + } +} diff --git a/src/main/java/space/impact/api/multiblocks/structure/IBlockPosConsumer.java b/src/main/java/space/impact/api/multiblocks/structure/IBlockPosConsumer.java new file mode 100644 index 0000000..e950d3b --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IBlockPosConsumer.java @@ -0,0 +1,7 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.world.World; + +public interface IBlockPosConsumer { + void consume(World world, int x, int y, int z); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/ICustomBlockSetting.java b/src/main/java/space/impact/api/multiblocks/structure/ICustomBlockSetting.java new file mode 100644 index 0000000..ab27879 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/ICustomBlockSetting.java @@ -0,0 +1,18 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.world.World; + +public interface ICustomBlockSetting { + /** + * Установка блока по умолчанию вызывает World#setBlock(int x, int y, int z, Block block block, int meta, int updateType) + *

+ * {@code world.setBlock(x,y,z,this/block,meta,2)}, где updateType 2 означает обновление освещения и прочего. + * + * @param world - текущий мир + * @param x - координата x + * @param y - координата y + * @param z - координата z + * @param meta - мета блока + */ + void setBlock(World world, int x, int y, int z, int meta); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureDefinition.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureDefinition.java new file mode 100644 index 0000000..7b247ce --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureDefinition.java @@ -0,0 +1,150 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +import space.impact.api.Main; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; + +import java.util.Arrays; + +public interface IStructureDefinition { + 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 + basePositionA = -basePositionA; + basePositionB = -basePositionB; + basePositionC = -basePositionC; + + int[] abc = new int[]{basePositionA, basePositionB, basePositionC}; + int[] xyz = new int[3]; + + if (checkBlocksIfNotNullForceCheckAllIfTrue != null) { + if (checkBlocksIfNotNullForceCheckAllIfTrue) { + 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; + + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { + if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) { + return false; + } + } else { + return false; + } + abc[0] += 1; + } + } + } else { + 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; + + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { + if (!element.check(object, world, xyz[0], xyz[1], xyz[2])) { + return false; + } + } + abc[0] += 1; + } + } + } + } else { + if (hintsOnly) { + 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; + + element.spawnHint(object, world, xyz[0], xyz[1], xyz[2], trigger); + + abc[0] += 1; + } + } + } else { + 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; + + if (world.blockExists(xyz[0], xyz[1], xyz[2])) { + element.placeBlock(object, world, xyz[0], xyz[1], xyz[2], trigger); + } + abc[0] += 1; + } + } + } + } + return true; + } + + static StructureDefinition.Builder builder() { + return StructureDefinition.builder(); + } + + /** + * Может, но не обязан, вызывать {@link java.util.NoSuchElementException}, если данный фрагмент структуры не найден. + * + * @param name - то же название, что и для других методов здесь + * @return массив элементов для обработки + * @throws java.util.NoSuchElementException если данный фрагмент структуры не найден, а фаза луны полностью совпадает + */ + 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 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 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 + ); + } +} diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureElement.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureElement.java new file mode 100644 index 0000000..4ab97e1 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureElement.java @@ -0,0 +1,43 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * Используйте {@link StructureUtility} для создания instance + */ +public interface IStructureElement { + boolean check(T t, World world, int x, int y, int z); + + boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger); + + boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger); + + default int getStepA() { + return 1; + } + + default int getStepB() { + return 0; + } + + default int getStepC() { + return 0; + } + + default boolean resetA() { + return false; + } + + default boolean resetB() { + return false; + } + + default boolean resetC() { + return false; + } + + default boolean isNavigating() { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureElementChain.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementChain.java new file mode 100644 index 0000000..75e9ac2 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementChain.java @@ -0,0 +1,41 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * Используйте {@link StructureUtility} для создания instance + */ +public interface IStructureElementChain extends IStructureElement { + IStructureElement[] fallbacks(); + + @Override + default boolean check(T t, World world, int x, int y, int z) { + for (IStructureElement fallback : fallbacks()) { + if (fallback.check(t, world, x, y, z)) { + return true; + } + } + return false; + } + + @Override + default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + for (IStructureElement fallback : fallbacks()) { + if (fallback.spawnHint(t, world, x, y, z, trigger)) { + return true; + } + } + return false; + } + + @Override + default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + for (IStructureElement fallback : fallbacks()) { + if (fallback.placeBlock(t, world, x, y, z, trigger)) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureElementCheckOnly.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementCheckOnly.java new file mode 100644 index 0000000..f3f9d4d --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementCheckOnly.java @@ -0,0 +1,20 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * Используйте {@link StructureUtility} для создания instance + */ + +public interface IStructureElementCheckOnly extends IStructureElement { + @Override + default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return false; + } + + @Override + default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureElementDeferred.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementDeferred.java new file mode 100644 index 0000000..a557be7 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementDeferred.java @@ -0,0 +1,7 @@ +package space.impact.api.multiblocks.structure; + +/** + * Используйте {@link StructureUtility} для создания instance + */ +public interface IStructureElementDeferred extends IStructureElement { +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureElementNoPlacement.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementNoPlacement.java new file mode 100644 index 0000000..5b18378 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureElementNoPlacement.java @@ -0,0 +1,15 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * Используйте {@link StructureUtility} для создания instance + */ + +public interface IStructureElementNoPlacement extends IStructureElement { + @Override + default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IStructureNavigate.java b/src/main/java/space/impact/api/multiblocks/structure/IStructureNavigate.java new file mode 100644 index 0000000..61ec2fc --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IStructureNavigate.java @@ -0,0 +1,28 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +/** + * Используйте {@link StructureUtility} для создания instance + */ +interface IStructureNavigate extends IStructureElement { + @Override + default boolean check(T t, World world, int x, int y, int z) { + return true; + } + + @Override + default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + return true; + } + + @Override + default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return true; + } + + default boolean isNavigating() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/ITierConverter.java b/src/main/java/space/impact/api/multiblocks/structure/ITierConverter.java new file mode 100644 index 0000000..d869a55 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/ITierConverter.java @@ -0,0 +1,11 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.block.Block; + +/** + * Функция для извлечения информации тире из блока. + */ +@FunctionalInterface +public interface ITierConverter { + T convert(Block block, int meta); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/IWithExtendedContext.java b/src/main/java/space/impact/api/multiblocks/structure/IWithExtendedContext.java new file mode 100644 index 0000000..d2d2776 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/IWithExtendedContext.java @@ -0,0 +1,19 @@ +package space.impact.api.multiblocks.structure; + +/** + * Реализуйте это и используйте {@link StructureUtility#withContext(IStructureElement)} + * в случае, если вам нужно записать некоторые временные состояния, которые не должны жить + * в вашем основном контексте, и которые обычно живут в течение всего времени жизни структуры, + * а не только во время проверки структуры. + *

+ * Обычно это просто экономит несколько байт, но становится нетривиальным, + * если вам нужно записать слишком много всего. + *

+ * Однако вам придется сбросить контекст самостоятельно. + * API не может угадать, когда старый контекст станет недействительным. + * + * @param контекст временного состояния + */ +public interface IWithExtendedContext { + T getCurrentContext(); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/LazyStructureElement.java b/src/main/java/space/impact/api/multiblocks/structure/LazyStructureElement.java new file mode 100644 index 0000000..8d80920 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/LazyStructureElement.java @@ -0,0 +1,38 @@ +package space.impact.api.multiblocks.structure; + +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; + +import java.util.function.Function; + +class LazyStructureElement implements IStructureElementDeferred { + private Function> to; + private IStructureElement elem; + + public LazyStructureElement(Function> to) { + this.to = to; + } + + private IStructureElement get(T t) { + if (to != null) { + elem = to.apply(t); + to = null; + } + return elem; + } + + @Override + public boolean check(T t, World world, int x, int y, int z) { + return get(t).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return get(t).placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/StructureDefinition.java b/src/main/java/space/impact/api/multiblocks/structure/StructureDefinition.java new file mode 100644 index 0000000..6caee2c --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/StructureDefinition.java @@ -0,0 +1,264 @@ +package space.impact.api.multiblocks.structure; + +import space.impact.api.util.Vec3Impl; + +import java.util.*; +import java.util.stream.Collectors; + +public class StructureDefinition implements IStructureDefinition { + private final Map> elements; + private final Map shapes; + private final Map[]> structures; + + private StructureDefinition(Map> elements, Map shapes, Map[]> structures) { + this.elements = elements; + this.shapes = shapes; + this.structures = structures; + } + + public static Builder builder() { + return new Builder<>(); + } + + public Map> getElements() { + return elements; + } + + public Map getShapes() { + return shapes; + } + + public Map[]> getStructures() { + return structures; + } + + @Override + public IStructureElement[] getStructureFor(String name) { + IStructureElement[] elements = structures.get(name); + if (elements == null) + throw new NoSuchElementException(name); + return elements; + } + + public static class Builder { + private static final char A = '\uA000'; + private static final char B = '\uB000'; + private static final char C = '\uC000'; + public static boolean debug = false; + private final Map navigates; + private final Map> elements; + private final Map shapes; + private char d = '\uD000'; + + private Builder() { + navigates = new HashMap<>(); + elements = new HashMap<>(); + shapes = new HashMap<>(); + } + + public Map> getElements() { + return elements; + } + + public Map getShapes() { + return shapes; + } + + /** + * Casings go: 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + *
+ * HatchAdders go: space ! " # $ % & ' ( ) * + * + * @param name нелокализованное/кодовое название + * @param structurePiece сгенерированная или записанная структура - НЕ СОХРАНЯЙТЕ ЕЕ НИГДЕ, или, по крайней мере, установите их в null после этого + * @return текущий Builder + */ + @Deprecated + public Builder addShapeOldApi(String name, String[][] structurePiece) { + StringBuilder builder = new StringBuilder(); + if (structurePiece.length > 0) { + for (String[] strings : structurePiece) { + if (strings.length > 0) { + for (String string : strings) { + for (int i = 0; i < string.length(); i++) { + char ch = string.charAt(i); + if (ch < ' ') { + for (int b = 0; b < ch; b++) { + builder.append(B); + } + } else if (ch > '@') { + for (int a = '@'; a < ch; a++) { + builder.append(A); + } + } else if (ch == '.') { + builder.append(A); + } else { + builder.append(ch); + } + } + builder.append(B); + } + builder.setLength(builder.length() - 1); + } + builder.append(C); + } + builder.setLength(builder.length() - 1); + } + int a = 0, b = 0, c = 0; + for (int i = 0; i < builder.length(); i++) { + char ch = builder.charAt(i); + if (ch == A) { + a++; + } else if (ch == B) { + a = 0; + b++; + } else if (ch == C) { + a = 0; + b = 0; + c++; + } else if (a != 0 || b != 0 || c != 0) { + Vec3Impl vec3 = new Vec3Impl(a, b, c); + Character navigate = navigates.get(vec3); + if (navigate == null) { + navigate = d++; + navigates.put(vec3, navigate); + addElement(navigate, StructureUtility.step(vec3)); + } + builder.setCharAt(i - 1, navigate); + a = 0; + b = 0; + c = 0; + } + } + + String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", ""); + + if (built.contains("+")) { + addElement('+', StructureUtility.notAir()); + } + if (built.contains("-")) { + addElement('-', StructureUtility.isAir()); + } + shapes.put(name, built); + return this; + } + + /** + * Добавляет форму + * + является чем угодно, только не воздухом + * - проверяет воздух + * пробел - пропуск + * ~ также пропускается (но обозначает позицию контроллера, необязателен и логически является пробелом...) + * остальное необходимо определить + *

+ * следующий {@link Character} - следующий блок (A) + * следующая {@link String} - следующая строка (B) + * следующая {@link String[]} - следующий фрагмент(C) + *

+ * диапазон {@link Character} A000-FFFF зарезервирован для генерируемых пропусков + * + * @param name нелокализованное/кодовое название + * @param structurePiece сгенерированная или записанная структура - НЕ СОХРАНЯЙТЕ ЕЕ НИГДЕ, или, по крайней мере, установите их в null после этого + * @return текущий Builder + */ + public Builder addShape(String name, String[][] structurePiece) { + StringBuilder builder = new StringBuilder(); + if (structurePiece.length > 0) { + for (String[] strings : structurePiece) { + if (strings.length > 0) { + for (String string : strings) { + builder.append(string).append(B); + } + builder.setLength(builder.length() - 1); + } + builder.append(C); + } + builder.setLength(builder.length() - 1); + } + int a = 0, b = 0, c = 0; + for (int i = 0; i < builder.length(); i++) { + char ch = builder.charAt(i); + if (ch == ' ' || ch == '~' || ch == '.') { + builder.setCharAt(i, A); + ch = A; + } + if (ch == A) { + a++; + } else if (ch == B) { + a = 0; + b++; + } else if (ch == C) { + a = 0; + b = 0; + c++; + } else if (a != 0 || b != 0 || c != 0) { + Vec3Impl vec3 = new Vec3Impl(a, b, c); + Character navigate = navigates.get(vec3); + if (navigate == null) { + navigate = d++; + navigates.put(vec3, navigate); + addElement(navigate, StructureUtility.step(vec3)); + } + builder.setCharAt(i - 1, navigate); + a = 0; + b = 0; + c = 0; + } + } + + String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", ""); + + if (built.contains("+")) { + addElement('+', StructureUtility.notAir()); + } + if (built.contains("-")) { + addElement('-', StructureUtility.isAir()); + } + shapes.put(name, built); + return this; + } + + public Builder addElement(Character name, IStructureElement structurePiece) { + elements.putIfAbsent(name, structurePiece); + return this; + } + + public IStructureDefinition build() { + Map[]> structures = compileStructureMap(); + if (debug) { + return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures); + } else { + return structures::get; + } + } + + @SuppressWarnings("unchecked") + 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) + )); + } else { + throw new RuntimeException("Missing Structure Element bindings for (chars as integers): " + Arrays.toString(missing.toArray())); + } + } + + @SuppressWarnings("unchecked") + 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) + )); + } else { + 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()); + } + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/StructureUtility.java b/src/main/java/space/impact/api/multiblocks/structure/StructureUtility.java new file mode 100644 index 0000000..dbe5e21 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/StructureUtility.java @@ -0,0 +1,1500 @@ +package space.impact.api.multiblocks.structure; + +import cpw.mods.fml.common.registry.GameRegistry; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import space.impact.api.ImpactAPI; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; +import space.impact.api.multiblocks.structure.adders.IBlockAdder; +import space.impact.api.multiblocks.structure.adders.ITileAdder; +import space.impact.api.util.Vec3Impl; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.*; + +import static java.lang.Integer.MIN_VALUE; + +/** + * API для проверки структуры! + *

+ * (Просто импортируйте статически этот класс, чтобы иметь хороший беглый синтаксис при определении определений структуры) + */ +public class StructureUtility { + 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 + public boolean check(Object t, World world, int x, int y, int z) { + return world.isAirBlock(x, y, z); + } + + @Override + public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, ImpactAPI.getBlockHint(), 13); + return true; + } + + @Override + public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, Blocks.air, 0, 2); + return false; + } + }; + @SuppressWarnings("rawtypes") + private static final IStructureElement NOT_AIR = new IStructureElement() { + @Override + public boolean check(Object t, World world, int x, int y, int z) { + return !world.isAirBlock(x, y, z); + } + + @Override + public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, ImpactAPI.getBlockHint(), 14); + return true; + } + + @Override + public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, ImpactAPI.getBlockHint(), 14, 2); + return true; + } + }; + @SuppressWarnings("rawtypes") + private static final IStructureElement ERROR = new IStructureElement() { + @Override + public boolean check(Object t, World world, int x, int y, int z) { + return false; + } + + @Override + public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, ImpactAPI.getBlockHint(), 15); + return true; + } + + @Override + public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) { + return true; + } + }; + + private StructureUtility() { + + } + + @SuppressWarnings("unchecked") + public static IStructureElement isAir() { + return AIR; + } + + @SuppressWarnings("unchecked") + public static IStructureElement notAir() { + return NOT_AIR; + } + + /** + * Проверка возвращает false. + * Размещение всегда обрабатывается этим и ничего не делает. + * Практически не используется в цепочке fallback. + */ + @SuppressWarnings("unchecked") + public static IStructureElement error() { + return ERROR; + } + + //region hint only + + /** + * Проверка всегда возвращает: true. + */ + public static IStructureElementNoPlacement ofHint(int dots) { + int meta = dots - 1; + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, ImpactAPI.getBlockHint(), meta); + return false; + } + }; + } + + /** + * Проверка всегда возвращает: true. + */ + public static IStructureElementNoPlacement ofHintDeferred(Supplier icons) { + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, icons.get()); + return false; + } + }; + } + + /** + * Проверка всегда возвращает: true. + */ + public static IStructureElementNoPlacement ofHintDeferred(Supplier icons, short[] RGBa) { + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticleTinted(world, x, y, z, icons.get(), RGBa); + return false; + } + }; + } + //endregion + + //region block + + /** + * Более примитивный вариант {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)}. + * Основное отличие в том, что этот вариант только для проверки. + * + * @see #ofBlocksTiered(ITierConverter, Object, BiConsumer, Function) + */ + 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(); + + return (t, world, x, y, z) -> { + Block block = world.getBlock(x, y, z); + int meta = world.getBlockMetadata(x, y, z); + TIER tier = tierExtractor.convert(block, meta); + TIER current = getter.apply(t); + if (Objects.equals(notSet, current)) { + setter.accept(t, tier); + return true; + } + return Objects.equals(current, tier); + }; + } + + /** + * Элемент, представляющий компонент с различными уровнями. + * Игрок может использовать больше чертежей, чтобы получить подсказки, обозначающие более продвинутые компоненты. + *

+ * TODO В этой утилите пока нет контр. части TileEntity. + * + * @param allKnownTiers - Все известные уровни на момент вызова. Может быть пустым или нулевым. + * При пустом или нулевом значении подсказка не будет порождена. Не может иметь нулевых элементов. + * Первый элемент обозначает самый примитивный уровень. Последний элемент обозначает самый продвинутый уровень. + * Если не все уровни доступны во время построения определения используйте {@link #lazy(Supplier)} или его перегрузки для небольшой задержки. + * @param notSet - Значение, возвращаемое из {@code getter}, когда в T еще не найдена информация об уровне. Может быть равно null. + * @param getter - функция для получения текущего уровня из T + * @param setter - функция для установки текущего уровня в T + * @param tierExtractor - функция для извлечения информации о тире из блока. + */ + 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); + return new IStructureElement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return check.check(t, world, x, y, z); + } + + private Pair getHint(ItemStack trigger) { + return hints.get(Math.min(Math.max(trigger.stackSize, 1), hints.size())); + } + + @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; + ImpactAPI.hintParticle(world, x, y, z, hint.getKey(), hint.getValue()); + return true; + } + + @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.getKey() instanceof ICustomBlockSetting) { + ICustomBlockSetting block = (ICustomBlockSetting) hint.getKey(); + block.setBlock(world, x, y, z, hint.getValue()); + } else { + world.setBlock(x, y, z, hint.getKey(), hint.getValue(), 2); + } + return true; + } + }; + } + + /** + * Обозначить блок, используя нелокализованные имена. Это может быть полезно для обхода проблем с порядком загрузки мода. + *

+ * Хотя немедленной ошибки не произойдет, клиентский код должен убедиться, что указанный мод + * загружен и что указанный мод присутствует, иначе в дальнейшем произойдут плохие вещи! + */ + public static IStructureElement ofBlockUnlocalizedName(String modid, String unlocalizedName, int meta) { + if (StringUtils.isBlank(unlocalizedName)) throw new IllegalArgumentException(); + if (meta < 0) throw new IllegalArgumentException(); + if (meta > 15) throw new IllegalArgumentException(); + if (StringUtils.isBlank(modid)) throw new IllegalArgumentException(); + return new IStructureElement() { + private Block block; + + private Block getBlock() { + 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; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.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) { + world.setBlock(x, y, z, getBlock(), meta, 2); + return true; + } + }; + } + + /** + * Аналогична другой перегрузке, но позволяет клиентскому коду указать обратный ход + * в случае, если указанный блок не был найден впоследствии. при вызове элемента. + *

+ * Это несколько отличается от использования другой перегрузки и другого + * элемента в {@link #ofChain(IStructureElement[])}, поскольку такая комбинация приведет к ошибке, а эта - нет. + */ + 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(); + if (StringUtils.isBlank(modid)) throw new IllegalArgumentException(); + if (fallback == null) throw new IllegalArgumentException(); + return new IStructureElement() { + private Block block; + private boolean initialized; + + private boolean init() { + if (!initialized) { + block = GameRegistry.findBlock(modid, unlocalizedName); + initialized = true; + } + return block != null; + } + + @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); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + if (init()) { + ImpactAPI.hintParticle(world, x, y, z, block, meta); + return true; + } else + return fallback.spawnHint(t, world, x, y, z, trigger); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + if (init()) { + world.setBlock(x, y, z, block, meta, 2); + return true; + } else + return fallback.placeBlock(t, world, x, y, z, trigger); + } + }; + } + + /** + * Не допускает дублирования блоков (с разными метами) + */ + public static IStructureElementNoPlacement ofBlocksFlatHint(Map blocsMap, Block hintBlock, int hintMeta) { + if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementNoPlacement() { + @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, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, hintBlock, hintMeta); + return true; + } + }; + } + + /** + * Позволяет дублировать блоки (с разными метами) + */ + public static IStructureElementNoPlacement ofBlocksMapHint(Map> blocsMap, Block hintBlock, int hintMeta) { + if (blocsMap == null || blocsMap.isEmpty() || hintBlock == null) { + throw new IllegalArgumentException(); + } + for (Collection value : blocsMap.values()) { + if (value.isEmpty()) { + throw new IllegalArgumentException(); + } + } + return new IStructureElementNoPlacement() { + @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)); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, hintBlock, hintMeta); + return true; + } + }; + } + + public static IStructureElementNoPlacement ofBlockHint(Block block, int meta, Block hintBlock, int hintMeta) { + if (block == null || hintBlock == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + Block worldBlock = world.getBlock(x, y, z); + return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, hintBlock, hintMeta); + return true; + } + }; + } + + public static IStructureElementNoPlacement ofBlockHint(Block block, int meta) { + return ofBlockHint(block, meta, block, meta); + } + + public static IStructureElementNoPlacement ofBlockAdderHint(IBlockAdder iBlockAdder, Block hintBlock, int hintMeta) { + if (iBlockAdder == null || hintBlock == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + Block worldBlock = world.getBlock(x, y, z); + return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z)); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, hintBlock, hintMeta); + return true; + } + }; + } + + /** + * Не допускает дублирования блоков (с разными метами) + */ + public static IStructureElement ofBlocksFlat(Map blocsMap, Block defaultBlock, int defaultMeta) { + if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) { + throw new IllegalArgumentException(); + } + if (defaultBlock instanceof ICustomBlockSetting) { + 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, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + ((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } 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, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, defaultBlock, defaultMeta, 2); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } + } + + /** + * Позволяет дублировать блоки (с разными метами) + */ + public static IStructureElement ofBlocksMap(Map> blocsMap, Block defaultBlock, int defaultMeta) { + if (blocsMap == null || blocsMap.isEmpty() || defaultBlock == null) { + throw new IllegalArgumentException(); + } + for (Collection value : blocsMap.values()) { + if (value.isEmpty()) { + throw new IllegalArgumentException(); + } + } + if (defaultBlock instanceof ICustomBlockSetting) { + 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)); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + ((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } 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)); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, defaultBlock, defaultMeta, 2); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } + } + + public static IStructureElement ofBlock(Block block, int meta, Block defaultBlock, int defaultMeta) { + if (block == null || defaultBlock == null) { + throw new IllegalArgumentException(); + } + if (block instanceof ICustomBlockSetting) { + 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 block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + ((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } 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 block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, defaultBlock, defaultMeta, 2); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } + } + + /** + * То же, что и выше, но игнорируется мета-идентификатор цели + */ + public static IStructureElement ofBlockAnyMeta(Block block, Block defaultBlock, int defaultMeta) { + if (block == null || defaultBlock == null) { + throw new IllegalArgumentException(); + } + if (block instanceof ICustomBlockSetting) { + return new IStructureElement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return block == world.getBlock(x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + ((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } else { + return new IStructureElement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return block == world.getBlock(x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, defaultBlock, defaultMeta, 2); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } + } + + public static IStructureElement ofBlock(Block block, int meta) { + return ofBlock(block, meta, block, meta); + } + + /** + * То же, что и выше, но игнорируется мета-идентификатор цели + */ + public static IStructureElement ofBlockAnyMeta(Block block) { + return ofBlockAnyMeta(block, block, 0); + } + + /** + * То же, что и выше, но позволяет задать рендеринг частиц с подсказкой + */ + public static IStructureElement ofBlockAnyMeta(Block block, int defaultMeta) { + return ofBlockAnyMeta(block, block, defaultMeta); + } + + //endregion + + //region adders + + public static IStructureElement ofBlockAdder(IBlockAdder iBlockAdder, Block defaultBlock, int defaultMeta) { + if (iBlockAdder == null || defaultBlock == null) { + throw new IllegalArgumentException(); + } + if (defaultBlock instanceof ICustomBlockSetting) { + 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 iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z)); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + ((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } 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 iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z)); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + world.setBlock(x, y, z, defaultBlock, defaultMeta, 2); + return true; + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, defaultBlock, defaultMeta); + return true; + } + }; + } + } + + public static IStructureElement ofBlockAdder(IBlockAdder iBlockAdder, int dots) { + return ofBlockAdder(iBlockAdder, ImpactAPI.getBlockHint(), dots - 1); + } + + public static IStructureElementNoPlacement ofTileAdder(ITileAdder iTileAdder, Block hintBlock, int hintMeta) { + if (iTileAdder == null || hintBlock == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + TileEntity tileEntity = world.getTileEntity(x, y, z); + // This used to check if it's a GT tile. Since this is now an standalone mod we no longer do this + return iTileAdder.apply(t, tileEntity); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, hintBlock, hintMeta); + return true; + } + }; + } + + public static IStructureElementNoPlacement ofSpecificTileAdder(BiPredicate iTileAdder, Class tileClass, Block hintBlock, int hintMeta) { + if (iTileAdder == null || hintBlock == null || tileClass == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementNoPlacement() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + TileEntity tileEntity = world.getTileEntity(x, y, z); + // This used to check if it's a GT tile. Since this is now an standalone mod we no longer do this + return tileClass.isInstance(tileEntity) && iTileAdder.test(t, tileClass.cast(tileEntity)); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + ImpactAPI.hintParticle(world, x, y, z, hintBlock, hintMeta); + return true; + } + }; + } + // Больше не нужно добавлять штриховку. Реализуйте его через тайловый аддер. Мы, конечно, можем добавить обертку вокруг него в gregtech, но больше не в этом отдельном моде. + + //endregion + + //region side effects + + 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) { + boolean check = element.check(t, world, x, y, z); + if (check) { + onCheckPass.accept(t); + } + return check; + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return element.placeBlock(t, world, x, y, z, trigger); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + return element.spawnHint(t, world, x, y, z, trigger); + } + }; + } + + 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) { + boolean check = element.check(t, world, x, y, z); + if (!check) { + onFail.accept(t); + } + return check; + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return element.placeBlock(t, world, x, y, z, trigger); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + return element.spawnHint(t, world, x, y, z, trigger); + } + }; + } + + //endregion + + /** + * Будьте осторожны при построении цепочки, так как она будет пытаться вызвать каждый элемент структуры, пока не вернет true. + * Если нет, то возвращается false. + */ + @SafeVarargs + public static IStructureElementChain ofChain(IStructureElement... elementChain) { + if (elementChain == null || elementChain.length == 0) { + throw new IllegalArgumentException(); + } + for (IStructureElement iStructureElement : elementChain) { + if (iStructureElement == null) { + throw new IllegalArgumentException(); + } + } + return () -> elementChain; + } + + /** + * Будьте осторожны при построении цепочки, так как она будет пытаться вызвать каждый элемент структуры, пока не вернет true. + * Если нет, то возвращается false. + */ + @SuppressWarnings("unchecked") + public static IStructureElementChain ofChain(List> elementChain) { + return ofChain(elementChain.toArray(new IStructureElement[0])); + } + + // region context + 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) { + return elem.check(t.getCurrentContext(), world, x, y, z); + } + + @Override + public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) { + return elem.spawnHint(t.getCurrentContext(), world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + //endregion + + //region defer + + /** + * Аналогично defer, но кэширует первый возвращенный элемент и не будет вызывать его снова. + * Инициализация не является потокобезопасной. + */ + public static IStructureElementDeferred lazy(Supplier> to) { + if (to == null) { + throw new IllegalArgumentException(); + } + return new LazyStructureElement<>(t -> to.get()); + } + + /** + * Аналогично defer, но кэширует первый возвращенный элемент и не будет вызывать его снова. + * Инициализация не является потокобезопасной. + */ + public static IStructureElementDeferred lazy(Function> to) { + if (to == null) { + throw new IllegalArgumentException(); + } + return new LazyStructureElement<>(to); + } + + public static IStructureElementDeferred defer(Supplier> to) { + if (to == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return to.get().check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return to.get().placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + public static IStructureElementDeferred defer(Function> to) { + if (to == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return to.apply(t).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return to.apply(t).placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + public static IStructureElementDeferred defer(Function keyExtractor, Map> map) { + 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.get(keyExtractor.apply(t)).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return map.get(keyExtractor.apply(t)).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.get(keyExtractor.apply(t)).spawnHint(t, world, x, y, z, trigger); + } + }; + } + + public static IStructureElementDeferred defer(Function 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), 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), 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), defaultElem).spawnHint(t, world, x, y, z, trigger); + } + }; + } + + @SafeVarargs + public static IStructureElementDeferred defer(Function keyExtractor, IStructureElement... array) { + if (keyExtractor == null || array == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return array[keyExtractor.apply(t)].check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return array[keyExtractor.apply(t)].placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + @SuppressWarnings("unchecked") + public static IStructureElementDeferred defer(Function keyExtractor, List> array) { + return defer(keyExtractor, array.toArray(new IStructureElement[0])); + } + + public static IStructureElementDeferred defer(BiFunction> to) { + if (to == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return to.apply(t, null).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return to.apply(t, trigger).placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + public static IStructureElementDeferred defer(BiFunction keyExtractor, Map> map) { + 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.get(keyExtractor.apply(t, null)).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return map.get(keyExtractor.apply(t, trigger)).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.get(keyExtractor.apply(t, trigger)).spawnHint(t, world, x, y, z, trigger); + } + }; + } + + 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); + } + + @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); + } + + @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); + } + }; + } + + @SafeVarargs + public static IStructureElementDeferred defer(BiFunction keyExtractor, IStructureElement... array) { + if (keyExtractor == null || array == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return array[keyExtractor.apply(t, null)].check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return array[keyExtractor.apply(t, trigger)].placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + @SuppressWarnings("unchecked") + public static IStructureElementDeferred defer(BiFunction keyExtractor, List> array) { + return defer(keyExtractor, array.toArray(new IStructureElement[0])); + } + + public static IStructureElementDeferred defer(Function> toCheck, BiFunction> to) { + if (to == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return toCheck.apply(t).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return to.apply(t, trigger).placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, Map> map) { + 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.get(keyExtractorCheck.apply(t)).check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return map.get(keyExtractor.apply(t, trigger)).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.get(keyExtractor.apply(t, trigger)).spawnHint(t, world, x, y, z, trigger); + } + }; + } + + public static IStructureElementDeferred defer(Function keyExtractorCheck, 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(keyExtractorCheck.apply(t), 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); + } + + @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); + } + }; + } + + @SafeVarargs + public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, IStructureElement... array) { + if (keyExtractor == null || array == null) { + throw new IllegalArgumentException(); + } + return new IStructureElementDeferred() { + @Override + public boolean check(T t, World world, int x, int y, int z) { + return array[keyExtractorCheck.apply(t)].check(t, world, x, y, z); + } + + @Override + public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) { + return array[keyExtractor.apply(t, trigger)].placeBlock(t, world, x, y, z, trigger); + } + + @Override + 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); + } + }; + } + + @SuppressWarnings("unchecked") + public static IStructureElementDeferred defer(Function keyExtractorCheck, BiFunction keyExtractor, List> array) { + return defer(keyExtractorCheck, keyExtractor, array.toArray(new IStructureElement[0])); + } + + //endregion + + /** + * Используется внутренне, для создания пропусков при определении структуры + */ + public static IStructureNavigate step(int a, int b, int c) { + return step(new Vec3Impl(a, b, c)); + } + + /** + * Используется внутренне, для создания пропусков при определении структуры + */ + @SuppressWarnings("unchecked") + public static IStructureNavigate step(Vec3Impl step) { + if (step == null || step.get0() < 0 || step.get1() < 0 || step.get2() < 0) { + throw new IllegalArgumentException(); + } + return STEP.computeIfAbsent(step, vec3 -> { + if (vec3.get2() > 0) { + return stepC(vec3.get0(), vec3.get1(), vec3.get2()); + } else if (vec3.get1() > 0) { + return stepB(vec3.get0(), vec3.get1(), vec3.get2()); + } else { + return stepA(vec3.get0(), vec3.get1(), vec3.get2()); + } + }); + } + + private static IStructureNavigate stepA(int a, int b, int c) { + return new IStructureNavigate() { + @Override + public int getStepA() { + return a; + } + + @Override + public int getStepB() { + return b; + } + + @Override + public int getStepC() { + return c; + } + }; + } + + private static IStructureNavigate stepB(int a, int b, int c) { + return new IStructureNavigate() { + @Override + public int getStepA() { + return a; + } + + @Override + public int getStepB() { + return b; + } + + @Override + public int getStepC() { + return c; + } + + @Override + public boolean resetA() { + return true; + } + }; + } + + private static IStructureNavigate stepC(int a, int b, int c) { + return new IStructureNavigate() { + @Override + public int getStepA() { + return a; + } + + @Override + public int getStepB() { + return b; + } + + @Override + public int getStepC() { + return c; + } + + @Override + public boolean resetA() { + return true; + } + + @Override + public boolean resetB() { + return true; + } + }; + } + + /** + * Используется только для получения псевдокода в программе написания структур... + *

+ * NOTE: Код, специфичный для GT, был удален. TODO добавить среднее значение + * + * @param tileEntityClassifier - строку, обозначающую тип TileEntity или null, если в ней нет ничего особенного. + * Полезно, если сущность плитки не может быть просто определена с помощью 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) { + 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; + }); + } + } 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"); + for (Map.Entry> entry : blocks.entrySet()) { + Block block = entry.getKey(); + Set set = entry.getValue(); + for (Integer meta : set) { + c = NICE_CHARS.charAt(i++); + if (i > NICE_CHARS.length()) { + 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("\nTiles:\n"); + for (Class tile : tiles) { + c = NICE_CHARS.charAt(i++); + if (i > NICE_CHARS.length()) { + return "Too complicated for nice chars"; + } + map.put(tile.getCanonicalName(), c); + builder.append(c).append(" -> ofTileAdder(") + .append(tile).append(", ...);\n"); + } + builder.append("\nSpecial Tiles:\n"); + for (String tile : specialTiles) { + c = NICE_CHARS.charAt(i++); + if (i > NICE_CHARS.length()) { + 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("\nOffsets:\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))); + } else { + builder.append(' '); + } + } 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.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))); + } else { + builder.append(' '); + } + } 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.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) { + sizeA -= basePositionA; + sizeB -= basePositionB; + sizeC -= basePositionC; + + int[] abc = new int[3]; + int[] xyz = new int[3]; + + for (abc[2] = -basePositionC; abc[2] < sizeC; abc[2]++) { + 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); + } + + } + } + } + + 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; + + int[] abc = new int[3]; + int[] xyz = new int[3]; + if (transpose) { + for (abc[1] = -basePositionB; abc[1] < sizeB; abc[1]++) { + 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); + } + nextB.run(); + } + nextC.run(); + } + } else { + for (abc[2] = -basePositionC; abc[2] < sizeC; abc[2]++) { + 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); + } + nextB.run(); + } + nextC.run(); + } + } + } + + /** + * Транспонирует фигуру (меняет местами оси B и C, может использоваться для отмены транспонирования транспонированной фигуры) + * ВНИМАНИЕ! Не используйте на старых api... + * + * @param structurePiece форма (транспонированная форма) + * @return транспонированная форма (не транспонированная форма) + */ + public static String[][] transpose(String[][] structurePiece) { + String[][] shape = new String[structurePiece[0].length][structurePiece.length]; + for (int i = 0; i < structurePiece.length; i++) { + for (int j = 0; j < structurePiece[i].length; j++) { + shape[j][i] = structurePiece[i][j]; + } + } + return shape; + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/multiblocks/structure/adders/IBlockAdder.java b/src/main/java/space/impact/api/multiblocks/structure/adders/IBlockAdder.java new file mode 100644 index 0000000..e039080 --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/adders/IBlockAdder.java @@ -0,0 +1,15 @@ +package space.impact.api.multiblocks.structure.adders; + + +import net.minecraft.block.Block; + +public interface IBlockAdder { + /** + * Обратный вызов при добавлении блока, необходимо проверить, действителен ли блок (и добавить его) + * + * @param block - добавить блок + * @param meta - добавить мету блока + * @return удалось ли установить блок + */ + boolean apply(T t, Block block, int meta); +} diff --git a/src/main/java/space/impact/api/multiblocks/structure/adders/ITileAdder.java b/src/main/java/space/impact/api/multiblocks/structure/adders/ITileAdder.java new file mode 100644 index 0000000..0b2bd1a --- /dev/null +++ b/src/main/java/space/impact/api/multiblocks/structure/adders/ITileAdder.java @@ -0,0 +1,13 @@ +package space.impact.api.multiblocks.structure.adders; + +import net.minecraft.tileentity.TileEntity; + +public interface ITileAdder { + /** + * Обратный вызов для добавления TileEntity, необходимо проверить, является ли TileEntity действительной (и добавить его) + * + * @param te TileEntity + * @return удалось kb добавить TileEntity (структура все еще актуальна) + */ + boolean apply(T t, TileEntity te); +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/net/AlignmentMessage.java b/src/main/java/space/impact/api/net/AlignmentMessage.java new file mode 100644 index 0000000..e362bde --- /dev/null +++ b/src/main/java/space/impact/api/net/AlignmentMessage.java @@ -0,0 +1,139 @@ +package space.impact.api.net; + +import cpw.mods.fml.common.network.ByteBufUtils; +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; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; +import net.minecraftforge.common.DimensionManager; +import space.impact.api.Main; +import space.impact.api.multiblocks.alignment.IAlignment; +import space.impact.api.multiblocks.alignment.IAlignmentProvider; +import space.impact.api.multiblocks.alignment.enumerable.ExtendedFacing; + +public abstract class AlignmentMessage implements IMessage { + + int mPosX, mPosY, mPosZ, mPosD, mAlign; + + public AlignmentMessage() { + } + + private AlignmentMessage(IAlignmentProvider provider) { + 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!"); + } + TileEntity base = (TileEntity) provider; + mPosX = base.xCoord; + mPosY = base.yCoord; + mPosZ = base.zCoord; + mPosD = base.getWorldObj().provider.dimensionId; + mAlign = alignment.getExtendedFacing().getIndex(); + } + + private AlignmentMessage(World world, int x, int y, int z, IAlignment front) { + mPosX = x; + mPosY = y; + mPosZ = z; + mPosD = world.provider.dimensionId; + mAlign = front.getExtendedFacing().getIndex(); + } + + @Override + public void fromBytes(ByteBuf pBuffer) { + NBTTagCompound tTag = ByteBufUtils.readTag(pBuffer); + mPosX = tTag.getInteger("posx"); + mPosY = tTag.getInteger("posy"); + mPosZ = tTag.getInteger("posz"); + mPosD = tTag.getInteger("posd"); + mAlign = tTag.getInteger("rotf"); + } + + @Override + public void toBytes(ByteBuf pBuffer) { + NBTTagCompound tFXTag = new NBTTagCompound(); + tFXTag.setInteger("posx", mPosX); + tFXTag.setInteger("posy", mPosY); + tFXTag.setInteger("posz", mPosZ); + tFXTag.setInteger("posd", mPosD); + tFXTag.setInteger("rotf", mAlign); + + ByteBufUtils.writeTag(pBuffer, tFXTag); + } + + public static class AlignmentQuery extends AlignmentMessage { + public AlignmentQuery() { + } + + public AlignmentQuery(IAlignmentProvider provider) { + super(provider); + } + + public AlignmentQuery(World world, int x, int y, int z, IAlignment front) { + super(world, x, y, z, front); + } + } + + public static class AlignmentData extends AlignmentMessage { + public AlignmentData() { + } + + private AlignmentData(AlignmentQuery query) { + mPosX = query.mPosX; + mPosY = query.mPosY; + mPosZ = query.mPosZ; + mPosD = query.mPosD; + mAlign = query.mAlign; + } + + public AlignmentData(IAlignmentProvider provider) { + super(provider); + } + + public AlignmentData(World world, int x, int y, int z, IAlignment front) { + super(world, x, y, z, front); + } + } + + public static class ClientHandler implements IMessageHandler { + + @Override + public IMessage onMessage(AlignmentData pMessage, MessageContext pCtx) { + if (Main.getCurrentPlayer().worldObj.provider.dimensionId == pMessage.mPosD) { + TileEntity te = Main.getCurrentPlayer().worldObj.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ); + if (te instanceof IAlignmentProvider) { + IAlignment alignment = ((IAlignmentProvider) te).getAlignment(); + if (alignment != null) { + alignment.setExtendedFacing(ExtendedFacing.byIndex(pMessage.mAlign)); + } + } + } + return null; + } + } + + public static class ServerHandler implements IMessageHandler { + + @Override + public AlignmentData onMessage(AlignmentQuery pMessage, MessageContext pCtx) { + World world = DimensionManager.getWorld(pMessage.mPosD); + if (world != null) { + TileEntity te = world.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ); + if (te instanceof IAlignmentProvider) { + IAlignment alignment = ((IAlignmentProvider) te).getAlignment(); + if (alignment == null) + return null; + pMessage.mAlign = alignment.getExtendedFacing().getIndex(); + return new AlignmentData(pMessage); + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/proxy/ClientProxy.java b/src/main/java/space/impact/api/proxy/ClientProxy.java new file mode 100644 index 0000000..1598534 --- /dev/null +++ b/src/main/java/space/impact/api/proxy/ClientProxy.java @@ -0,0 +1,182 @@ +package space.impact.api.proxy; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +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.util.ChatComponentText; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.WorldEvent; +import space.impact.api.ConfigurationHandler; +import space.impact.api.entity.fx.EntityFXBlockHint; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + + +public class ClientProxy extends CommonProxy { + + private static final BiMap allHints = HashBiMap.create(); + private static final List> hintOwners = new ArrayList<>(); + private static List currentHints; + + private static IIcon[] createIIconFromBlock(Block block, int meta) { + IIcon[] ret = new IIcon[6]; + for (int i = 0; i < 6; i++) { + ret[i] = block.getIcon(i, meta); + } + return ret; + } + + public static void onHintDead(EntityFXBlockHint 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(); + break; + } + } + } + + @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); + Minecraft.getMinecraft().effectRenderer.addEffect(hint); + ensureHinting(); + if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) { + HintParticleInfo info = new HintParticleInfo(x, y, z, currentHints); + EntityFXBlockHint dupe = allHints.inverse().get(info); + if (dupe != null) { + List owner = allHints.get(dupe).owner; + // на случай, если кто-то забудет вызвать endHinting + if (owner != currentHints) { + hintOwners.remove(owner); + owner.forEach(EntityFXBlockHint::setDead); + owner.clear(); + } + } + allHints.forcePut(hint, info); + } + currentHints.add(hint); + } + + @Override + public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) { + hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), RGBa); + } + + @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}); + } + + @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}); + } + + @Override + public void addClientSideChatMessages(String... messages) { + GuiNewChat chat = Minecraft.getMinecraft().ingameGUI.getChatGUI(); + for (String s : messages) { + chat.printChatMessage(new ChatComponentText(s)); + } + } + + @Override + public EntityPlayer getCurrentPlayer() { + return Minecraft.getMinecraft().thePlayer; + } + + @Override + public boolean isCurrentPlayer(EntityPlayer player) { + return player == Minecraft.getMinecraft().thePlayer; + } + + @Override + public void startHinting(World w) { + if (!w.isRemote) + return; + if (currentHints != null) + hintOwners.add(currentHints); + currentHints = new LinkedList<>(); + } + + private void ensureHinting() { + if (currentHints == null) + currentHints = new LinkedList<>(); + } + + @Override + public void endHinting(World w) { + 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); + currentHints = null; + } + + @Override + public void preInit(FMLPreInitializationEvent e) { + MinecraftForge.EVENT_BUS.register(new ForgeEventHandler()); + } + + private static class HintParticleInfo { + private final int x, y, z; + private final List owner; + + public HintParticleInfo(int x, int y, int z, List owner) { + this.x = x; + this.y = y; + this.z = z; + this.owner = owner; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HintParticleInfo that = (HintParticleInfo) o; + + if (x != that.x) return false; + if (y != that.y) return false; + return z == that.z; + } + + @Override + public int hashCode() { + int result = x; + result = 31 * result + y; + result = 31 * result + z; + return result; + } + } + + public static class ForgeEventHandler { + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load e) { + if (e.world.isRemote) { + allHints.clear(); + hintOwners.clear(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/proxy/CommonProxy.java b/src/main/java/space/impact/api/proxy/CommonProxy.java new file mode 100644 index 0000000..0b36feb --- /dev/null +++ b/src/main/java/space/impact/api/proxy/CommonProxy.java @@ -0,0 +1,35 @@ +package space.impact.api.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) {} +} \ No newline at end of file diff --git a/src/main/java/space/impact/api/util/Vec3Impl.java b/src/main/java/space/impact/api/util/Vec3Impl.java new file mode 100644 index 0000000..17dcaf4 --- /dev/null +++ b/src/main/java/space/impact/api/util/Vec3Impl.java @@ -0,0 +1,139 @@ +package space.impact.api.util; + +import net.minecraft.dispenser.IPosition; +import net.minecraftforge.common.util.ForgeDirection; + +public class Vec3Impl implements Comparable { + public static final Vec3Impl NULL_VECTOR = new Vec3Impl(0, 0, 0); + private final int val0; + private final int val1; + private final int val2; + + public Vec3Impl(int in0, int in1, int in2) { + this.val0 = in0; + this.val1 = in1; + this.val2 = in2; + } + + public int compareTo(Vec3Impl o) { + return val1 == o.val1 ? val2 == o.val2 ? val0 - o.val0 : val2 - o.val2 : val1 - o.val1; + } + + /** + * Получает координату. + * @param index - x,y,z + */ + public int get(int index) { + switch (index) { + case 0: + return val0; + case 1: + return val1; + case 2: + return val2; + default: + return 0; + } + } + + /** + * Получает координату X. + */ + public int get0() { + return this.val0; + } + + /** + * Получает координату Y. + */ + public int get1() { + return this.val1; + } + + /** + * Получает координату Z. + */ + public int get2() { + return this.val2; + } + + 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); + } + + public Vec3Impl add(Vec3Impl pos) { + return new Vec3Impl(val0 + pos.val0, val1 + pos.val1, val2 + pos.val2); + } + + public Vec3Impl sub(Vec3Impl pos) { + return new Vec3Impl(val0 - pos.val0, val1 - pos.val1, val2 - pos.val2); + } + + public Vec3Impl add(int pos0, int pos1, int pos2) { + return new Vec3Impl(val0 + pos0, val1 + pos1, val2 + pos2); + } + + public Vec3Impl sub(int pos0, int pos1, int pos2) { + return new Vec3Impl(val0 - pos0, val1 - pos1, val2 - pos2); + } + + 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 + ); + } + + public boolean withinDistance(Vec3Impl to, double distance) { + return this.distanceSq(to.val0, to.val1, to.val2, false) < distance * distance; + } + + public boolean withinDistance(IPosition to, double distance) { + return this.distanceSq(to.getX(), to.getY(), to.getZ(), true) < distance * distance; + } + + public double distanceSq(Vec3Impl to) { + return this.distanceSq(to.val0, to.val1, to.val2, true); + } + + public double distanceSq(IPosition to, boolean useCenter) { + return this.distanceSq(to.getX(), to.getY(), to.getZ(), useCenter); + } + + public double distanceSq(double x, double y, double z, boolean useCenter) { + double d0 = useCenter ? 0.5D : 0.0D; + double d1 = (double) val0 + d0 - x; + double d2 = (double) val1 + d0 - y; + double d3 = (double) val2 + d0 - z; + return d1 * d1 + d2 * d2 + d3 * d3; + } + + public int manhattanDistance(Vec3Impl to) { + float f = (float) Math.abs(to.val0 - val0); + float f1 = (float) Math.abs(to.val1 - val1); + float f2 = (float) Math.abs(to.val2 - val2); + return (int) (f + f1 + f2); + } + + @Override + public String toString() { + return "Vec3[" + val0 + ", " + val1 + ", " + val2 + "]"; + } + + public Vec3Impl abs() { + return new Vec3Impl(Math.abs(val0), Math.abs(val1), Math.abs(val2)); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (o instanceof Vec3Impl) { + Vec3Impl vec3i = (Vec3Impl) o; + return val0 == vec3i.val0 && val1 == vec3i.val1 && val2 == vec3i.val2; + } + return false; + } + + public int hashCode() { + return (val1 + val2 * 31) * 31 + val0; + } +} \ No newline at end of file diff --git a/src/main/resources/assets/impactapi/lang/en_US.lang b/src/main/resources/assets/impactapi/lang/en_US.lang new file mode 100644 index 0000000..84ad83c --- /dev/null +++ b/src/main/resources/assets/impactapi/lang/en_US.lang @@ -0,0 +1,61 @@ +tile.impactapi.blockhint.0.name= White Hint +tile.impactapi.blockhint.1.name= Orange Hint +tile.impactapi.blockhint.2.name= Magenta Hint +tile.impactapi.blockhint.3.name= Light Blue Hint +tile.impactapi.blockhint.4.name= Yellow Hint +tile.impactapi.blockhint.5.name= Lime Hint +tile.impactapi.blockhint.6.name= Pink Hint +tile.impactapi.blockhint.7.name= Gray Hint +tile.impactapi.blockhint.8.name= Light Gray Hint +tile.impactapi.blockhint.9.name= Cyan Hint +tile.impactapi.blockhint.10.name=Purple Hint +tile.impactapi.blockhint.11.name=Blue Hint +tile.impactapi.blockhint.12.name=Brown Hint +tile.impactapi.blockhint.13.name=Green Hint +tile.impactapi.blockhint.14.name=Red Hint +tile.impactapi.blockhint.15.name=Black Hint + +item.impactapi.frontRotationTool.name=Front Rotation Tool (Standalone) +item.impactapi.frontRotationTool.desc.0=Triggers Front Rotation Interface +item.impactapi.frontRotationTool.desc.1=Rotates only the front panel, +item.impactapi.frontRotationTool.desc.2=which allows structure rotation. + +item.impactapi.constructableTrigger.name=Multiblock Structure Hologram Projector +item.impactapi.constructableTrigger.desc.0=Triggers Constructable Interface +item.impactapi.constructableTrigger.desc.1=Shows multiblock construction details, +item.impactapi.constructableTrigger.desc.2=just Use on a multiblock controller. +item.impactapi.constructableTrigger.desc.3=(Sneak Use in creative to build) +item.impactapi.constructableTrigger.desc.4=Quantity affects tier/mode/type + +impactapi.blockhint.desc.0=Helps while building +impactapi.blockhint.0.name=White Hint +impactapi.blockhint.1.name=Orange Hint +impactapi.blockhint.2.name=Magenta Hint +impactapi.blockhint.3.name=Light Blue Hint +impactapi.blockhint.4.name=Yellow Hint +impactapi.blockhint.5.name=Lime Hint +impactapi.blockhint.6.name=Pink Hint +impactapi.blockhint.7.name=Gray Hint +impactapi.blockhint.8.name=Light Gray Hint +impactapi.blockhint.9.name=Cyan Hint +impactapi.blockhint.10.name=Purple Hint +impactapi.blockhint.11.name=Blue Hint +impactapi.blockhint.desc.1=Placeholder for a certain group. +impactapi.blockhint.12.name=Brown Hint general +impactapi.blockhint.desc.2=General placeholder. +impactapi.blockhint.13.name=GreenHint air +impactapi.blockhint.desc.3=Make sure it contains Air material. +impactapi.blockhint.14.name=Red Hint no air +impactapi.blockhint.desc.4=Make sure it does not contain Air material. +impactapi.blockhint.15.name=Black Hint error +impactapi.blockhint.desc.5=ERROR, what did u expect? +itemGroup.impactapi=ImpactAPI + +impactapi.config.client=Client Side Configurations +impactapi.config.client.tooltip=Configurations that only changes how the game looks like, but not how it functions. +impactapi.config.client.hologram=Hologram Configurations +impactapi.config.client.hologram.tooltip=Configurations that changes how the hologram got projected into the world by default. +impactapi.config.client.hologram.maxCoexisting=Max Coexisting Holograms +impactapi.config.client.hologram.maxCoexisting.tooltip=Number of holograms that can coexist. Usually all hints generated by one right click belongs to one hologram. An attempt will be made to prune old holograms when a new hologram is about to be projected and this limit would be exceeded. +impactapi.config.client.hologram.removeColliding=Remove Colliding Holograms +impactapi.config.client.hologram.removeColliding.tooltip=Whether an attempt will be made to remove any existing hologram that collides with a new hologram. diff --git a/src/main/resources/assets/impactapi/textures/blocks/1.png b/src/main/resources/assets/impactapi/textures/blocks/1.png new file mode 100644 index 0000000..e2f7f41 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/1.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/10.png b/src/main/resources/assets/impactapi/textures/blocks/10.png new file mode 100644 index 0000000..ec3936c Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/10.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/11.png b/src/main/resources/assets/impactapi/textures/blocks/11.png new file mode 100644 index 0000000..bf90f74 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/11.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/12.png b/src/main/resources/assets/impactapi/textures/blocks/12.png new file mode 100644 index 0000000..e1f68fc Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/12.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/13.png b/src/main/resources/assets/impactapi/textures/blocks/13.png new file mode 100644 index 0000000..1ee6716 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/13.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/14.png b/src/main/resources/assets/impactapi/textures/blocks/14.png new file mode 100644 index 0000000..8718898 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/14.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/15.png b/src/main/resources/assets/impactapi/textures/blocks/15.png new file mode 100644 index 0000000..7f97884 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/15.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/16.png b/src/main/resources/assets/impactapi/textures/blocks/16.png new file mode 100644 index 0000000..8413b9c Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/16.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/2.png b/src/main/resources/assets/impactapi/textures/blocks/2.png new file mode 100644 index 0000000..243bbc8 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/2.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/3.png b/src/main/resources/assets/impactapi/textures/blocks/3.png new file mode 100644 index 0000000..3532620 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/3.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/4.png b/src/main/resources/assets/impactapi/textures/blocks/4.png new file mode 100644 index 0000000..d5a46fe Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/4.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/5.png b/src/main/resources/assets/impactapi/textures/blocks/5.png new file mode 100644 index 0000000..8bc8343 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/5.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/6.png b/src/main/resources/assets/impactapi/textures/blocks/6.png new file mode 100644 index 0000000..c4df61e Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/6.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/7.png b/src/main/resources/assets/impactapi/textures/blocks/7.png new file mode 100644 index 0000000..dd1950f Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/7.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/8.png b/src/main/resources/assets/impactapi/textures/blocks/8.png new file mode 100644 index 0000000..ee95a15 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/8.png differ diff --git a/src/main/resources/assets/impactapi/textures/blocks/9.png b/src/main/resources/assets/impactapi/textures/blocks/9.png new file mode 100644 index 0000000..21d57aa Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/blocks/9.png differ diff --git a/src/main/resources/assets/impactapi/textures/items/itemConstructableTrigger.png b/src/main/resources/assets/impactapi/textures/items/itemConstructableTrigger.png new file mode 100644 index 0000000..7c62038 Binary files /dev/null and b/src/main/resources/assets/impactapi/textures/items/itemConstructableTrigger.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..07b9955 --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,16 @@ +[ +{ + "modid": "impactapi", + "name": "ImpactAPI", + "description": "Impact!", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "", + "updateUrl": "", + "authorList": ["4gname"], + "credits": "Technus, Glease", + "logoFile": "", + "screenshots": [], + "dependencies": [] +} +] \ No newline at end of file