diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c476faf --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# eclipse + +*.launch + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# macos + +*.DS_Store + +# fabric + +run/ + +# java + +hs_err_*.log +replay_*.log +*.hprof +*.jfr diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..81f9dee --- /dev/null +++ b/build.gradle @@ -0,0 +1,162 @@ +plugins { + id "com.modrinth.minotaur" version "2.+" + id 'fabric-loom' version '1.2-SNAPSHOT' + id 'maven-publish' +} + +version = project.mod_version +group = project.maven_group + +base { + archivesName = project.archives_base_name +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. + maven { + name = 'Ladysnake Mods' + url = 'https://ladysnake.jfrog.io/artifactory/mods' + } + maven { + url = "https://api.modrinth.com/maven" + } + maven { url 'https://jitpack.io' } + maven { url "https://maven.shedaniel.me/" } + maven { url "https://maven.terraformersmc.com/releases/" } + + maven { + url = uri("https://ueaj.dev/maven") + // for 0.4.2 and older + // url uri("https://raw.githubusercontent.com/Devan-Kerman/Devan-Repo/master/") + } + maven { url 'https://maven.siphalor.de/' } +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + // Uncomment the following line to enable the deprecated Fabric API modules. + // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. + + // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}" + + // Dependency of Immersive Portals Core: + modImplementation("com.github.iPortalTeam.ImmersivePortalsMod:imm_ptl_core:${project.immersive_portals_version}") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "net.fabricmc.fabric-loader") + transitive(false) + } + + // Dependency of the Miscellaneous Utility Library from qouteall + modImplementation("com.github.iPortalTeam.ImmersivePortalsMod:q_misc_util:${project.immersive_portals_version}") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "net.fabricmc.fabric-loader") + transitive(false) + } + + modImplementation("com.github.iPortalTeam.ImmersivePortalsMod:build:${project.immersive_portals_version}") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "net.fabricmc.fabric-loader") + transitive(false) + } + + modApi("dev.onyxstudios.cardinal-components-api:cardinal-components-base:${project.cardinal_components_version}") { + exclude(group: "net.fabricmc.fabric-loader") + transitive(false) + } + // Replace modImplementation with modApi if you expose components in your own API + modImplementation("dev.onyxstudios.cardinal-components-api:cardinal-components-level:${project.cardinal_components_version}") { + exclude(group: "net.fabricmc.fabric-loader") + transitive(false) + } + // Includes Cardinal Components API as a Jar-in-Jar dependency (optional) + include("dev.onyxstudios.cardinal-components-api:cardinal-components-level:${project.cardinal_components_version}") { + exclude(group: "net.fabricmc.fabric-loader") + transitive(false) + } + + api("com.github.LlamaLad7:MixinExtras:0.2.0-beta.4") + annotationProcessor("com.github.LlamaLad7:MixinExtras:0.2.0-beta.4") + + include(modApi(platform("de.siphalor.tweed4:tweed4-bom-$project.minecraft_version_major:$project.tweed_version"))) + // Pick any modules you want to use, e.g.: + include(modApi("de.siphalor.tweed4:tweed4-base-$project.minecraft_version_major")) + include(modApi("de.siphalor.tweed4:tweed4-annotated-$project.minecraft_version_major")) + include(modApi("de.siphalor.tweed4:tweed4-data-$project.minecraft_version_major")) + include(modApi("de.siphalor.tweed4:tweed4-data-hjson-$project.minecraft_version_major")) + include(modApi("de.siphalor.tweed4:tweed4-tailor-cloth-$project.minecraft_version_major")) + + + modCompileOnly "maven.modrinth:sodium:mc${project.minecraft_version}-${project.sodium_version}" +} + +configurations.include.transitive = true +configurations.include.dependencies.each { + if (!it.name.contains("bom")) { + it.transitive = false + } +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 17 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}" } + } +} + +import com.modrinth.minotaur.dependencies.ModDependency + +modrinth { + projectId = 'QXA901PE' // The ID of your Modrinth project. Slugs will not work. + uploadFile = remapJar // Tells Minotaur to use the remapped jar + dependencies = [ + new ModDependency('P7dR8mSH', 'required') // Creates a new required dependency on Fabric API + ] +} +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..278c60e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,22 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + +# Fabric Properties +# check these on https://fabricmc.net/develop +minecraft_version=1.19.4 +minecraft_version_major=1.19 +yarn_mappings=1.19.4+build.2 +loader_version=0.14.19 + +# Mod Properties +mod_version=1.0.0 +maven_group=quimufu.colourful-portals +archives_base_name=colourful-portals + +# Dependencies +fabric_version=0.81.1+1.19.4 +cardinal_components_version=5.1.0 +immersive_portals_version=v2.6.9-mc1.19.4 +tweed_version=1.3.0+mc1.19.4 +sodium_version=0.4.10 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c1962a7 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..37aef8d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..aeb74cb --- /dev/null +++ b/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..56266b4 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/src/main/java/quimufu/colourful_portals/ColourfulPortalsMod.java b/src/main/java/quimufu/colourful_portals/ColourfulPortalsMod.java new file mode 100644 index 0000000..add62c8 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/ColourfulPortalsMod.java @@ -0,0 +1,111 @@ +package quimufu.colourful_portals; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; +import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; +import net.fabricmc.fabric.api.event.player.AttackBlockCallback; +import net.fabricmc.fabric.api.item.v1.FabricItemSettings; +import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroupEntries; +import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.minecraft.block.BlockState; +import net.minecraft.block.Material; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.*; +import net.minecraft.registry.Registries; +import net.minecraft.registry.Registry; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.Rarity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import quimufu.colourful_portals.client.PortalFluidRenderHandler; +import quimufu.colourful_portals.config.ColourfulPortalConfig; +import quimufu.colourful_portals.portal.PortalManager; +import quimufu.colourful_portals.portal_fluid.PortalFluid; +import quimufu.colourful_portals.portal_fluid.PortalFluidBlock; +import quimufu.colourful_portals.portal_fluid.PortalFluidBucketItem; + +import java.util.HashSet; + +public class ColourfulPortalsMod implements ModInitializer, ClientModInitializer { + // This logger is used to write text to the console and the log file. + // It is considered best practice to use your mod id as the logger's name. + // That way, it's clear which mod wrote info, warnings, and errors. + public static final Logger LOGGER = LoggerFactory.getLogger("colourful_portals"); + public static final String MOD_ID = "colourful_portals"; + public static final HashSet PORTAL_BLOCKS = new HashSet<>(); + + public static final PortalBlock PORTAL_BLOCK = new PortalBlock(FabricBlockSettings.of(Material.GLASS).strength(-1.0f, 3600000.8f).dropsNothing().nonOpaque().luminance(15).allowsSpawning(ColourfulPortalsMod::never).solidBlock(ColourfulPortalsMod::never).suffocates(ColourfulPortalsMod::never).blockVision(ColourfulPortalsMod::never)); + public static final BlockItem PORTAL_BLOCK_ITEM = new BlockItem(PORTAL_BLOCK, new FabricItemSettings().rarity(Rarity.EPIC)); + public static final Item BLOB_DARK = new Item(new FabricItemSettings()); + public static final Item BLOB_BRIGHT = new Item(new FabricItemSettings()); + public static final PortalFluid PORTAL_FLUID = new PortalFluid(); + public static final PortalFluidBlock PORTAL_FLUID_BLOCk = new PortalFluidBlock(PORTAL_FLUID, FabricBlockSettings.of(Material.WATER).luminance(15).noCollision().strength(100.0f).dropsNothing()); + public static final BucketItem PORTAL_FLUID_BUCKET_ITEM = new PortalFluidBucketItem(PORTAL_FLUID, new FabricItemSettings().recipeRemainder(Items.BUCKET).maxCount(1).rarity(Rarity.RARE)); + + private static boolean never(BlockState blockState, BlockView blockView, BlockPos blockPos, EntityType entityType) { + return false; + } + + private static boolean never(BlockState blockState, BlockView blockView, BlockPos blockPos) { + return false; + } + + + @Override + public void onInitialize() { + // This code runs as soon as Minecraft is in a mod-load-ready state. + // However, some things (like resources) may still be uninitialized. + // Proceed with mild caution. + LOGGER.info("Hello Fabric world!"); + + for (String id : ColourfulPortalConfig.portalBlocks.keySet()) { + PORTAL_BLOCKS.add(Identifier.tryParse(id)); + } + + Identifier identifier = new Identifier(MOD_ID, "portal_block"); + + Registry.register(Registries.BLOCK, identifier, PORTAL_BLOCK); + Registry.register(Registries.ITEM, identifier, PORTAL_BLOCK_ITEM); + + Registry.register(Registries.ITEM, new Identifier(MOD_ID, "portal_fluid_bucket"), PORTAL_FLUID_BUCKET_ITEM); + Registry.register(Registries.FLUID, new Identifier(MOD_ID, "portal_fluid"), PORTAL_FLUID); + Registry.register(Registries.BLOCK, new Identifier(MOD_ID, "portal_fluid_block"), PORTAL_FLUID_BLOCk); + + Registry.register(Registries.ITEM, new Identifier(MOD_ID, "colour_blob_bright"), BLOB_BRIGHT); + Registry.register(Registries.ITEM, new Identifier(MOD_ID, "colour_blob_dark"), BLOB_DARK); + + ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS) + .register(ColourfulPortalsMod::addToIngredients); + + ItemGroupEvents.modifyEntriesEvent(ItemGroups.TOOLS) + .register(ColourfulPortalsMod::addTtTools); + + } + + private static void addTtTools(FabricItemGroupEntries entries) { + entries.add(PORTAL_FLUID_BUCKET_ITEM); + } + + private static void addToIngredients(FabricItemGroupEntries content) { + content.add(BLOB_BRIGHT); + content.add(BLOB_DARK); + } + + public void onInitializeClient() { + BlockRenderLayerMap.INSTANCE.putBlock(PORTAL_BLOCK, RenderLayer.getTranslucent()); + BlockRenderLayerMap.INSTANCE.putFluid(PORTAL_FLUID, RenderLayer.getTranslucent()); + FluidRenderHandlerRegistry.INSTANCE.register(PORTAL_FLUID, new PortalFluidRenderHandler()); + } + +} \ No newline at end of file diff --git a/src/main/java/quimufu/colourful_portals/Components.java b/src/main/java/quimufu/colourful_portals/Components.java new file mode 100644 index 0000000..9b51e2f --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/Components.java @@ -0,0 +1,22 @@ +package quimufu.colourful_portals; + +import dev.onyxstudios.cca.api.v3.component.ComponentKey; +import dev.onyxstudios.cca.api.v3.component.ComponentRegistry; +import dev.onyxstudios.cca.api.v3.level.LevelComponentFactoryRegistry; +import dev.onyxstudios.cca.api.v3.level.LevelComponentInitializer; +import net.minecraft.util.Identifier; +import quimufu.colourful_portals.portal.PortalListComponent; + +public class Components implements LevelComponentInitializer { + public static final ComponentKey PORTAL_LIST = + ComponentRegistry.getOrCreate(new Identifier(ColourfulPortalsMod.MOD_ID, "portal_list"), PortalListComponent.class); + public static final ComponentKey PORTAL_CANDIDATE_LIST = + ComponentRegistry.getOrCreate(new Identifier(ColourfulPortalsMod.MOD_ID, "portal_candidate_list"), PortalListComponent.class); + + @Override + public void registerLevelComponentFactories(LevelComponentFactoryRegistry registry) { + registry.register(PORTAL_LIST, PortalListComponent::new); + registry.register(PORTAL_CANDIDATE_LIST, PortalListComponent::new); + + } +} diff --git a/src/main/java/quimufu/colourful_portals/MixinConfig.java b/src/main/java/quimufu/colourful_portals/MixinConfig.java new file mode 100644 index 0000000..cc697b2 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/MixinConfig.java @@ -0,0 +1,54 @@ +package quimufu.colourful_portals; + +import net.fabricmc.loader.api.FabricLoader; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +public class MixinConfig implements IMixinConfigPlugin { + private static final Supplier TRUE = () -> true; + + private static final Map> CONDITIONS = Map.of( + "quimufu.colourful_portals.mixin.SodiumFluidRendererMixin", () -> FabricLoader.getInstance().isModLoaded("sodium") + ); + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + return CONDITIONS.getOrDefault(mixinClassName, TRUE).get(); + } + @Override + public void onLoad(String mixinPackage) { + + } + + @Override + public String getRefMapperConfig() { + return null; + } + + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + + } +} diff --git a/src/main/java/quimufu/colourful_portals/PortalBlock.java b/src/main/java/quimufu/colourful_portals/PortalBlock.java new file mode 100644 index 0000000..01fb569 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/PortalBlock.java @@ -0,0 +1,115 @@ +package quimufu.colourful_portals; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.Items; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.BlockRotation; +import net.minecraft.util.DyeColor; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; + + +public class PortalBlock extends Block { + public static final EnumProperty AXIS = Properties.AXIS; + public static final EnumProperty DYE_COLOR = EnumProperty.of("colour", DyeColor.class);; + + public static final VoxelShape X_AABB = Block.createCuboidShape( + 7.0D, + 0.0D, + 0.0D, + 9.0D, + 16.0D, + 16.0D + ); + public static final VoxelShape Y_AABB = Block.createCuboidShape( + 0.0D, + 7.0D, + 0.0D, + 16.0D, + 9.0D, + 16.0D + ); + public static final VoxelShape Z_AABB = Block.createCuboidShape( + 0.0D, + 0.0D, + 7.0D, + 16.0D, + 16.0D, + 9.0D + ); + + public PortalBlock(Settings settings) { + super(settings); + this.setDefaultState(this.stateManager.getDefaultState().with(AXIS, Direction.Axis.X).with(DYE_COLOR, DyeColor.BLACK)); + } + + @Override + public VoxelShape getOutlineShape( + BlockState state, BlockView world, BlockPos blockPos, ShapeContext shapeContext + ) { + if(shapeContext.isHolding(Items.DEBUG_STICK) + || shapeContext.isHolding(ColourfulPortalsMod.PORTAL_BLOCK_ITEM)){ + return switch (state.get(AXIS)) { + case Z -> Z_AABB; + case Y -> Y_AABB; + default -> X_AABB; + }; + } + return VoxelShapes.empty(); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return VoxelShapes.empty(); + } + + @Override + public BlockState rotate(BlockState state, BlockRotation rotation) { + return changeRotation(state, rotation); + } + + public static BlockState changeRotation(BlockState state, BlockRotation rotation) { + switch (rotation) { + case COUNTERCLOCKWISE_90, CLOCKWISE_90 -> { + switch (state.get(AXIS)) { + case X -> { + return state.with(AXIS, Direction.Axis.Z); + } + case Z -> { + return state.with(AXIS, Direction.Axis.X); + } + } + } + } + return state; + } + + public BlockState getStateWith(DyeColor color, Direction.Axis axis) { + return this.getDefaultState().with(AXIS, axis).with(DYE_COLOR, color); + + } + + @Override + public BlockState getPlacementState(ItemPlacementContext ctx) { + return this.getDefaultState().with(AXIS, ctx.getPlayerLookDirection().getAxis()); + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(AXIS).add(DYE_COLOR); + } + + @Override + public boolean isTransparent(BlockState state, BlockView world, BlockPos pos) { + return true; + } + +} diff --git a/src/main/java/quimufu/colourful_portals/client/CommonPortalFluidRenderer.java b/src/main/java/quimufu/colourful_portals/client/CommonPortalFluidRenderer.java new file mode 100644 index 0000000..5c01513 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/client/CommonPortalFluidRenderer.java @@ -0,0 +1,251 @@ +package quimufu.colourful_portals.client; + +import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler; +import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; +import net.minecraft.block.BlockState; +import net.minecraft.client.texture.Sprite; +import net.minecraft.fluid.FluidState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockRenderView; +import quimufu.colourful_portals.ColourfulPortalsMod; +import quimufu.colourful_portals.portal_fluid.PortalFluid; + +public class CommonPortalFluidRenderer { + + private Sprite sprite = null; + private final float[] uIu2IvIv2 = new float[4]; + + public boolean render(BlockRenderView world, FluidState fluidState, BlockPos pos, BlockPos offset, VertexEater vertexEater) { + + FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getFluid()); + sprite = handler.getFluidSprites(null, null, fluidState)[0]; + //from + float x = 0; + float y = 0; + float z = 0; + //to + float x2 = 1; + float y2 = 1; + float z2 = 1; + + Direction.Axis axis = fluidState.get(PortalFluid.AXIS).getAxis(); + int amount = fluidState.get(PortalFluid.AMOUNT); + if (amount == 0) + return false; + final float offsetInSquishedDir = (16 - amount) / 32f; + if (axis != null) { + switch (axis) { + case X -> { + x += offsetInSquishedDir; + x2 -= offsetInSquishedDir; + } + case Y -> { + y += offsetInSquishedDir; + y2 -= offsetInSquishedDir; + } + case Z -> { + z += offsetInSquishedDir; + z2 -= offsetInSquishedDir; + } + } + + } + + boolean ret = false; + for (Direction direction : Direction.values()) { + + BlockPos neighbourPos = pos.offset(direction); + BlockState neighbour = world.getBlockState(neighbourPos); + FluidState neighbourFluidState = neighbour.getFluidState(); + if (axis == null || direction.getAxis() != axis) { + if (neighbour.isSideSolidFullSquare(world, neighbourPos, direction.getOpposite()) + && neighbour.isOpaque()) { + continue; + } + if (neighbourFluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID) + && neighbourFluidState.get(PortalFluid.AMOUNT) >= amount) { + continue; + } + } + if (axis != null && direction.getAxis() != axis) { + + int neighbourAmount; + if (neighbourFluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID) + && (neighbourAmount = neighbourFluidState.get(PortalFluid.AMOUNT)) < amount) { + //draw side as two little silvers, adapting our width to neighbour width + float neighbourOffsetInSquishedDir = (16 - neighbourAmount) / 32F; + drawSilvers(vertexEater, direction, axis, offsetInSquishedDir, neighbourOffsetInSquishedDir, offset, (amount & 1) == 1); + ret = true; + continue; + } + calcSquishedUVs(axis, 1.f - offsetInSquishedDir, offsetInSquishedDir, direction, (amount & 1) == 1); + } else { + uIu2IvIv2[0] = sprite.getFrameU(0.); + uIu2IvIv2[1] = sprite.getFrameU(16.); + uIu2IvIv2[2] = sprite.getFrameV(0.); + uIu2IvIv2[3] = sprite.getFrameV(16.); + + } + drawSide(vertexEater, x, y, z, x2, y2, z2, direction, uIu2IvIv2[0], uIu2IvIv2[2], uIu2IvIv2[1], uIu2IvIv2[3], offset); + ret = true; + + } + return ret; + } + + private void calcSquishedUVs(Direction.Axis axis, float squishedTop, float squishedBottom, Direction direction, boolean odd) { + switch (axis) { + case X -> { + if (odd) { + uIu2IvIv2[0] = sprite.getFrameU(0.5 + squishedBottom * 16D); + uIu2IvIv2[1] = sprite.getFrameU(0.5 + squishedTop * 16D); + + } else { + uIu2IvIv2[0] = sprite.getFrameU(0. + squishedBottom * 16D); + uIu2IvIv2[1] = sprite.getFrameU(squishedTop * 16D); + } + uIu2IvIv2[2] = sprite.getFrameV(0.); + uIu2IvIv2[3] = sprite.getFrameV(16.); + } + case Z -> { + if (direction.getAxis() == Direction.Axis.Y) { + if (odd) { + uIu2IvIv2[2] = sprite.getFrameV(0.5 + squishedBottom * 16D); + uIu2IvIv2[3] = sprite.getFrameV(0.5 + squishedTop * 16D); + + } else { + uIu2IvIv2[2] = sprite.getFrameV(squishedBottom * 16D); + uIu2IvIv2[3] = sprite.getFrameV(squishedTop * 16D); + } + uIu2IvIv2[0] = sprite.getFrameU(0.); + uIu2IvIv2[1] = sprite.getFrameU(16.); + } else { + if (odd) { + uIu2IvIv2[0] = sprite.getFrameU(0.5 + squishedBottom * 16D); + uIu2IvIv2[1] = sprite.getFrameU(0.5 + squishedTop * 16D); + + } else { + uIu2IvIv2[0] = sprite.getFrameU(squishedBottom * 16D); + uIu2IvIv2[1] = sprite.getFrameU(squishedTop * 16D); + } + uIu2IvIv2[2] = sprite.getFrameV(0.); + uIu2IvIv2[3] = sprite.getFrameV(16.); + + } + } + case Y -> { + if (odd) { + uIu2IvIv2[2] = sprite.getFrameV(0.5 + squishedBottom * 16D); + uIu2IvIv2[3] = sprite.getFrameV(0.5 + squishedTop * 16D); + + } else { + uIu2IvIv2[2] = sprite.getFrameV(squishedBottom * 16D); + uIu2IvIv2[3] = sprite.getFrameV(squishedTop * 16D); + } + uIu2IvIv2[0] = sprite.getFrameU(0.); + uIu2IvIv2[1] = sprite.getFrameU(16.); + } + default -> throw new IllegalStateException("Unexpected value: " + axis); + } + } + + + private void drawSilvers(VertexEater buffers, Direction direction, Direction.Axis axis, float offsetInSquishedDir, float neighbourOffsetInSquishedDir, BlockPos offset, boolean odd) { + + float x = 0F; + float x2 = 1F; + float y = 0F; + float y2 = 1F; + float z = 0F; + float z2 = 1F; + switch (axis) { + case X -> { + x = offsetInSquishedDir; + x2 = neighbourOffsetInSquishedDir; + } + case Y -> { + y = offsetInSquishedDir; + y2 = neighbourOffsetInSquishedDir; + } + case Z -> { + z = offsetInSquishedDir; + z2 = neighbourOffsetInSquishedDir; + } + } + calcSquishedUVs(axis, offsetInSquishedDir, neighbourOffsetInSquishedDir, direction, odd); + drawSide(buffers, x, y, z, x2, y2, z2, direction, uIu2IvIv2[0], uIu2IvIv2[2], uIu2IvIv2[1], uIu2IvIv2[3], offset); + + switch (axis) { + case X -> { + x = 1F - neighbourOffsetInSquishedDir; + x2 = 1F - offsetInSquishedDir; + } + case Y -> { + y = 1F - neighbourOffsetInSquishedDir; + y2 = 1F - offsetInSquishedDir; + } + case Z -> { + z = 1F - neighbourOffsetInSquishedDir; + z2 = 1F - offsetInSquishedDir; + } + } + calcSquishedUVs(axis, 1.f - neighbourOffsetInSquishedDir,1.f - offsetInSquishedDir , direction, odd); + drawSide(buffers, x, y, z, x2, y2, z2, direction, uIu2IvIv2[0], uIu2IvIv2[2], uIu2IvIv2[1], uIu2IvIv2[3], offset); + } + + private void drawSide(VertexEater vertexConsumer, float x, float y, float z, float x2, float y2, float z2, Direction direction, float frameU, float frameV, float frameU2, float frameV2, BlockPos offset) { + vertexConsumer.setSprite(sprite); + switch (direction) { + case DOWN -> { + vertexConsumer.eatVertex(x, y, z, frameU, frameV2); + vertexConsumer.eatVertex( x2, y, z, frameU2, frameV2); + vertexConsumer.eatVertex( x2, y, z2, frameU2, frameV); + vertexConsumer.eatVertex( x, y, z2, frameU, frameV); + vertexConsumer.drawQuad(offset, direction); + } + case UP -> { + vertexConsumer.eatVertex( x, y2, z2, frameU, frameV2); + vertexConsumer.eatVertex( x2, y2, z2, frameU2, frameV2); + vertexConsumer.eatVertex( x2, y2, z, frameU2, frameV); + vertexConsumer.eatVertex( x, y2, z, frameU, frameV); + vertexConsumer.drawQuad(offset, direction); + } + case NORTH -> { + vertexConsumer.eatVertex( x, y2, z, frameU, frameV); + vertexConsumer.eatVertex( x2, y2, z, frameU2, frameV); + vertexConsumer.eatVertex( x2, y, z, frameU2, frameV2); + vertexConsumer.eatVertex( x, y, z, frameU, frameV2); + vertexConsumer.drawQuad(offset, direction); + } + case SOUTH -> { + vertexConsumer.eatVertex( x, y, z2, frameU, frameV); + vertexConsumer.eatVertex( x2, y, z2, frameU2, frameV); + vertexConsumer.eatVertex( x2, y2, z2, frameU2, frameV2); + vertexConsumer.eatVertex( x, y2, z2, frameU, frameV2); + vertexConsumer.drawQuad(offset, direction); + } + case WEST -> { + vertexConsumer.eatVertex( x, y, z, frameU, frameV); + vertexConsumer.eatVertex( x, y, z2, frameU2, frameV); + vertexConsumer.eatVertex( x, y2, z2, frameU2, frameV2); + vertexConsumer.eatVertex( x, y2, z, frameU, frameV2); + vertexConsumer.drawQuad(offset, direction); + } + case EAST -> { + vertexConsumer.eatVertex( x2, y2, z, frameU, frameV); + vertexConsumer.eatVertex( x2, y2, z2, frameU2, frameV); + vertexConsumer.eatVertex( x2, y, z2, frameU2, frameV2); + vertexConsumer.eatVertex( x2, y, z, frameU, frameV2); + vertexConsumer.drawQuad(offset, direction); + } + } + } + + public interface VertexEater { + + void setSprite(Sprite sprite); + void eatVertex(float x, float y, float z, float frameU, float frameV); + void drawQuad(BlockPos offset, Direction direction); + } +} diff --git a/src/main/java/quimufu/colourful_portals/client/PortalFluidRenderHandler.java b/src/main/java/quimufu/colourful_portals/client/PortalFluidRenderHandler.java new file mode 100644 index 0000000..a7a2e80 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/client/PortalFluidRenderHandler.java @@ -0,0 +1,90 @@ +package quimufu.colourful_portals.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.fluid.FluidState; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockRenderView; +import org.jetbrains.annotations.Nullable; +import quimufu.colourful_portals.ColourfulPortalsMod; + +@Environment(value = EnvType.CLIENT) +public class PortalFluidRenderHandler implements FluidRenderHandler, CommonPortalFluidRenderer.VertexEater { + Identifier PORTAL_FLUID_STILL = new Identifier(ColourfulPortalsMod.MOD_ID, "block/portal_still"); + + private final CommonPortalFluidRenderer commonPortalFluidRenderer = new CommonPortalFluidRenderer(); + private Sprite sprite = null; + private VertexConsumer vertexConsumer; + private int i; + + private final float[][] vertices = new float[4][5]; + + @Override + public Sprite[] getFluidSprites(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) { + return new Sprite[]{sprite}; + } + + @Override + public int getFluidColor(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) { + return 0; + } + + @Override + public void renderFluid(BlockPos pos, BlockRenderView world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) { + this.vertexConsumer = vertexConsumer; + BlockPos offset = new BlockPos(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); + + commonPortalFluidRenderer.render(world, fluidState, pos, offset, this); + + + } + + + @Override + public void reloadTextures(SpriteAtlasTexture textureAtlas) { + sprite = textureAtlas.getSprite(PORTAL_FLUID_STILL); + + } + + @Override + public void setSprite(Sprite sprite) { + + } + + @Override + public void eatVertex(float x, float y, float z, float u, float v) { + vertices[i][0] = x; + vertices[i][1] = y; + vertices[i][2] = z; + vertices[i][3] = u; + vertices[i][4] = v; + i = (i + 1) % 4; + } + + @Override + public void drawQuad(BlockPos offset, Direction direction) { + float offX = offset.getX(); + float offY = offset.getY(); + float offZ = offset.getZ(); + for (float[] v : vertices) { + + vertexConsumer + .vertex(offX + v[0], offY + v[1], offZ + v[2]) + .color(1f, 1f, 1f, 1.0f) + .texture( v[3], v[4]) + .light(16) + .normal(0.0f, 1.0f, 0.0f) + .next(); + + } + + + } +} diff --git a/src/main/java/quimufu/colourful_portals/client/SodiumPortalFluidRenderHandler.java b/src/main/java/quimufu/colourful_portals/client/SodiumPortalFluidRenderHandler.java new file mode 100644 index 0000000..99c0fba --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/client/SodiumPortalFluidRenderHandler.java @@ -0,0 +1,91 @@ +package quimufu.colourful_portals.client; + +import me.jellysquid.mods.sodium.client.model.quad.ModelQuad; +import me.jellysquid.mods.sodium.client.model.quad.ModelQuadView; +import me.jellysquid.mods.sodium.client.model.quad.ModelQuadViewMutable; +import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import me.jellysquid.mods.sodium.client.model.quad.properties.ModelQuadWinding; +import me.jellysquid.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder; +import me.jellysquid.mods.sodium.client.render.vertex.type.ChunkVertexEncoder; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler; +import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; +import net.minecraft.block.BlockState; +import net.minecraft.client.texture.Sprite; +import net.minecraft.fluid.FluidState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockRenderView; +import quimufu.colourful_portals.ColourfulPortalsMod; +import quimufu.colourful_portals.portal_fluid.PortalFluid; + +@Environment(value = EnvType.CLIENT) +public class SodiumPortalFluidRenderHandler implements CommonPortalFluidRenderer.VertexEater { + + private Sprite sprite = null; + private final ModelQuadViewMutable quad = new ModelQuad(); + + private final CommonPortalFluidRenderer commonPortalFluidRenderer = new CommonPortalFluidRenderer(); + private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad(); + private int i; + private ChunkModelBuilder chunkModelBuilder; + + public SodiumPortalFluidRenderHandler() { + } + + public boolean render(BlockRenderView world, FluidState fluidState, BlockPos pos, BlockPos offset, ChunkModelBuilder buffers) { + this.chunkModelBuilder = buffers; + return commonPortalFluidRenderer.render(world,fluidState,pos,offset,this); + } + + + @Override + public void setSprite(Sprite sprite) { + quad.setSprite(sprite); + } + + @Override + public void eatVertex(float x, float y, float z, float u, float v) { + quad.setX(i, x); + quad.setY(i, y); + quad.setZ(i, z); + quad.setTexU(i, u); + quad.setTexV(i, v); + i = (i+1)%4; + + } + + @Override + public void drawQuad(BlockPos offset, Direction direction) { + writeQuad(chunkModelBuilder, offset, quad, ModelQuadFacing.fromDirection(direction), ModelQuadWinding.CLOCKWISE); + } + + private void writeQuad(ChunkModelBuilder builder, BlockPos offset, ModelQuadView quad, ModelQuadFacing facing, ModelQuadWinding winding) { + var vertexBuffer = builder.getVertexBuffer(); + var vertices = this.vertices; + + for (int i = 0; i < 4; i++) { + var out = vertices[i]; + out.x = offset.getX() + quad.getX(i); + out.y = offset.getY() + quad.getY(i); + out.z = offset.getZ() + quad.getZ(i); + out.color = 0xFFFFFFFF; + out.u = quad.getTexU(i); + out.v = quad.getTexV(i); + out.light = 16; + } + + Sprite sprite = quad.getSprite(); + + if (sprite != null) { + builder.addSprite(sprite); + } + + builder.getIndexBuffer(facing) + .add(vertexBuffer.push(vertices), winding); + } + + + +} diff --git a/src/main/java/quimufu/colourful_portals/config/ColourfulPortalConfig.java b/src/main/java/quimufu/colourful_portals/config/ColourfulPortalConfig.java new file mode 100644 index 0000000..5adaa6e --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/config/ColourfulPortalConfig.java @@ -0,0 +1,43 @@ +package quimufu.colourful_portals.config; + +import com.google.common.base.CaseFormat; +import com.google.common.collect.ImmutableMap; +import de.siphalor.tweed4.annotated.AConfigEntry; +import de.siphalor.tweed4.annotated.ATweedConfig; +import de.siphalor.tweed4.config.ConfigEnvironment; +import de.siphalor.tweed4.config.ConfigScope; +import de.siphalor.tweed4.tailor.cloth.ClothData; +import net.minecraft.util.DyeColor; + +import java.util.Map; + +@ATweedConfig(serializer = "tweed4:hjson", scope = ConfigScope.GAME, environment = ConfigEnvironment.SERVER, tailors = {"tweed4:lang_json_descriptions", "tweed4:coat", "tweed4:json_schema"}, casing = CaseFormat.LOWER_HYPHEN) +@ClothData(modid = "tweed4_testmod") +public class ColourfulPortalConfig { + + @AConfigEntry(comment = + """ + Allows you to add additional blocks as portalBlocks, + assigning them the color of the portal plane. + null causes the portals out of that block to have *no* plane. + Changes in the plane color will only apply to new portals, + or on portal update. + """) + public static Map portalBlocks = ImmutableMap.ofEntries( + Map.entry("minecraft:white_wool", DyeColor.WHITE), + Map.entry("minecraft:orange_wool", DyeColor.ORANGE), + Map.entry("minecraft:magenta_wool", DyeColor.MAGENTA), + Map.entry("minecraft:light_blue_wool", DyeColor.LIGHT_BLUE), + Map.entry("minecraft:yellow_wool", DyeColor.YELLOW), + Map.entry("minecraft:lime_wool", DyeColor.LIME), + Map.entry("minecraft:pink_wool", DyeColor.PINK), + Map.entry("minecraft:gray_wool", DyeColor.GRAY), + Map.entry("minecraft:light_gray_wool", DyeColor.LIGHT_GRAY), + Map.entry("minecraft:cyan_wool", DyeColor.CYAN), + Map.entry("minecraft:purple_wool", DyeColor.PURPLE), + Map.entry("minecraft:blue_wool", DyeColor.BLUE), + Map.entry("minecraft:brown_wool", DyeColor.BROWN), + Map.entry("minecraft:green_wool", DyeColor.GREEN), + Map.entry("minecraft:red_wool", DyeColor.RED), + Map.entry("minecraft:black_wool", DyeColor.BLACK)); +} diff --git a/src/main/java/quimufu/colourful_portals/mixin/BlockChangeMixin.java b/src/main/java/quimufu/colourful_portals/mixin/BlockChangeMixin.java new file mode 100644 index 0000000..2585b45 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/mixin/BlockChangeMixin.java @@ -0,0 +1,43 @@ +package quimufu.colourful_portals.mixin; + +import net.minecraft.block.BlockState; +import net.minecraft.registry.Registries; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import quimufu.colourful_portals.ColourfulPortalsMod; +import quimufu.colourful_portals.portal.PortalManager; + +import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER; +import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_BLOCKS; + +@Mixin(ServerWorld.class) +public class BlockChangeMixin { + + @Inject(at = @At("RETURN"), method = "onBlockChanged") + private void init(BlockPos pos, BlockState oldBlock, BlockState newBlock, CallbackInfo info) { + if(((ServerWorld) (Object) this).isDebugWorld()) + return; + // This code is injected into the end of ServerWorld.onBlockChanged()V + if (oldBlock.getBlock() != newBlock.getBlock()) { + if (PORTAL_BLOCKS.contains(Registries.BLOCK.getId(newBlock.getBlock()))) { + LOGGER.info("onBlockNew {} -> {}", oldBlock, newBlock); + Identifier blockId = Registries.BLOCK.getId(newBlock.getBlock()); + ColourfulPortalsMod.LOGGER.info("Block change, new portal block: " + blockId); + PortalManager.onPortalBlockPlaced(((ServerWorld) (Object) this), pos, blockId); + + } + if (PORTAL_BLOCKS.contains(Registries.BLOCK.getId(oldBlock.getBlock()))) { + LOGGER.info("onBlockOld {} -> {}", oldBlock, newBlock); + Identifier blockId = Registries.BLOCK.getId(oldBlock.getBlock()); + ColourfulPortalsMod.LOGGER.info("Block change, removed portal block: " + blockId); + PortalManager.onPortalBlockBroken(((ServerWorld) (Object) this), pos, blockId); + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/quimufu/colourful_portals/mixin/InterpolationMixin.java b/src/main/java/quimufu/colourful_portals/mixin/InterpolationMixin.java new file mode 100644 index 0000000..5675ca7 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/mixin/InterpolationMixin.java @@ -0,0 +1,37 @@ +package quimufu.colourful_portals.mixin; + +import net.minecraft.client.texture.NativeImage; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(targets = "net/minecraft/client/texture/SpriteContents$Interpolation") +public abstract class InterpolationMixin { + + @Shadow + protected abstract int lerp(double delta, int to, int from); + + ThreadLocal colourBefore = new ThreadLocal<>(); + ThreadLocal delta = new ThreadLocal<>(); + + @Redirect(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/NativeImage;setColor(III)V")) + private void injected(NativeImage instance, int x, int y, int color) { + int alpha = lerp(delta.get(), (color >> 24) & 0xFF, (colourBefore.get() >> 24) & 0xFF); + + instance.setColor(x, y, (alpha << 24) | (color & 0xFFFFFF)); + } + + @ModifyVariable(method = "apply", at = @At("STORE"), ordinal = 10) + private int injected(int x) { + colourBefore.set(x); + return x; + } + + @ModifyVariable(method = "apply", at = @At("STORE"), ordinal = 0) + private double injected(double deltaV) { + delta.set(deltaV); + return deltaV; + } +} diff --git a/src/main/java/quimufu/colourful_portals/mixin/SodiumFluidRendererMixin.java b/src/main/java/quimufu/colourful_portals/mixin/SodiumFluidRendererMixin.java new file mode 100644 index 0000000..2e1c52b --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/mixin/SodiumFluidRendererMixin.java @@ -0,0 +1,28 @@ +package quimufu.colourful_portals.mixin; + +import me.jellysquid.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder; +import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.FluidRenderer; +import net.minecraft.fluid.FluidState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockRenderView; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import quimufu.colourful_portals.client.SodiumPortalFluidRenderHandler; + +import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_FLUID; + +@Mixin(FluidRenderer.class) +public class SodiumFluidRendererMixin { + + private final SodiumPortalFluidRenderHandler sodiumPortalFluidRenderHandler = new SodiumPortalFluidRenderHandler(); + + @Inject(at = @At("HEAD"), method = "render", cancellable = true, remap = false) + private void init(BlockRenderView world, FluidState fluidState, BlockPos pos, BlockPos offset, ChunkModelBuilder buffers, CallbackInfoReturnable cir) { + if (fluidState.isOf(PORTAL_FLUID)) { + cir.setReturnValue(sodiumPortalFluidRenderHandler.render(world, fluidState, pos, offset, buffers)); + cir.cancel(); + } + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal/PortalHelper.java b/src/main/java/quimufu/colourful_portals/portal/PortalHelper.java new file mode 100644 index 0000000..1f76a58 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal/PortalHelper.java @@ -0,0 +1,255 @@ +package quimufu.colourful_portals.portal; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.registry.Registries; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3i; + +import java.util.*; + +import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_BLOCK; +import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_FLUID; + +public class PortalHelper { + public static final char[][] PORTAL = new char[][]{ + {'N', 'X', 'X', 'N'}, + {'X', 'O', 'O', 'X'}, + {'X', 'O', 'O', 'X'}, + {'X', 'O', 'O', 'X'}, + {'N', 'X', 'X', 'N'}, + }; + + static Iterator insideOf(BlockBox portal) { + Direction axisW = getAxisW(portal); + Vec3i dir = axisW.getVector(); + BlockPos startPos = new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()); + + return new BlockPosIterator(startPos, dir, 'O'); + + } + + static boolean isValidPortal(ServerWorld world, BlockBox portal, Identifier blockId) { + return isValidPortal(world, portal, blockId, true); + } + + static boolean isValidCandidate(ServerWorld world, BlockBox portal, Identifier blockId) { + return isValidPortal(world, portal, blockId, false); + } + + private static boolean isValidPortal(ServerWorld world, BlockBox portal, Identifier blockId, boolean empty) { + Direction axisW = getAxisW(portal); + return isValidPortal(world, Registries.BLOCK.get(blockId), new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()), empty, axisW); + } + + static Direction getAxisW(BlockBox fromPortalBox) { + if (fromPortalBox.getMaxX() - fromPortalBox.getMinX() > 2) + return Direction.EAST; + return Direction.SOUTH; + } + + static List findPortalCandidates(ServerWorld world, BlockPos pos, Identifier blockId) { + Block block = Registries.BLOCK.get(blockId); + ArrayList portals = new ArrayList<>(); + outer: + for (Direction direction : Direction.values()) { + int size = direction == Direction.UP || direction == Direction.DOWN ? 5 : 4; + //quick checks that fail on most non-portal buildings + for (int i = 1; i < size - 1; i++) { + if (world.getBlockState(pos.add(direction.getVector().multiply(i))).isOf(block)) { + continue outer; + } + } + if (!world.getBlockState(pos.add(direction.getVector().multiply(size - 1))).isOf(block)) { + continue; + } + //just check all possible portal shapes + switch (direction) { + case DOWN -> { + //orientation X wise + //start left + BlockPos lowerCorner = pos.add(-1, -4, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + lowerCorner = pos.add(-2, -4, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + //orientation Y wise + lowerCorner = pos.add(0, -4, -1); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -4, -2); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + } + case UP -> { + //orientation X wise + //start left + BlockPos lowerCorner = pos.add(-1, 0, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + lowerCorner = pos.add(-2, 0, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + //orientation Y wise + lowerCorner = pos.add(0, 0, -1); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + lowerCorner = pos.add(0, 0, -2); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + } + case NORTH -> { + BlockPos lowerCorner = pos.add(0, -1, -3); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -2, -3); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -3, -3); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + } + case SOUTH -> { + BlockPos lowerCorner = pos.add(0, -1, 0); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -2, 0); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -3, 0); + getPortalStartingAt(world, block, lowerCorner, Direction.SOUTH) + .ifPresent(portals::add); + } + case WEST -> { + BlockPos lowerCorner = pos.add(-3, -1, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + lowerCorner = pos.add(-3, -2, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + lowerCorner = pos.add(-3, -3, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + } + case EAST -> { + BlockPos lowerCorner = pos.add(0, -1, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -2, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + lowerCorner = pos.add(0, -3, 0); + + getPortalStartingAt(world, block, lowerCorner, Direction.EAST) + .ifPresent(portals::add); + } + } + + } + return portals; + } + + + private static Optional getPortalStartingAt(ServerWorld world, Block block, BlockPos lowerCorner, Direction direction) { + Vec3i sidewardsOffset = direction.getVector().multiply(3); + Vec3i upwardsOffset = Direction.UP.getVector().multiply(4); + if (isValidPortal(world, block, lowerCorner, false, direction)) { + return Optional.of(BlockBox.create(lowerCorner, lowerCorner.add(sidewardsOffset).add(upwardsOffset))); + } + return Optional.empty(); + } + + private static boolean isValidPortal(ServerWorld world, Block block, BlockPos lowerCorner, boolean hasToBeFilledCorrectly, Direction direction) { + //maybe use BlockPosIterator, but make sure Performance is OK + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 4; j++) { + Vec3i sidewardsOffset = direction.getVector().multiply(j); + Vec3i upwardsOffset = Direction.UP.getVector().multiply(i); + BlockPos curr = lowerCorner.add(sidewardsOffset).add(upwardsOffset); + + switch (PORTAL[i][j]) { + case 'X' -> { + if (!world.getBlockState(curr).isOf(block)) { + return false; + } + } + case 'N' -> { + } + case 'O' -> { + BlockState blockState = world.getBlockState(curr); + if (hasToBeFilledCorrectly && !(blockState.getFluidState().isOf(PORTAL_FLUID) || blockState.getBlock().equals(PORTAL_BLOCK))) { + return false; + } + if (blockState.isOf(block)) { + return false; + } + } + + } + } + } + return true; + } + + private static class BlockPosIterator implements Iterator { + private final BlockPos startPos; + private final Vec3i dir; + private final char filter; + int pos; + int size; + static final Vec3i up = Direction.UP.getVector(); + + public BlockPosIterator(BlockPos startPos, Vec3i dir, char filter) { + this.startPos = startPos; + this.dir = dir; + this.filter = filter; + this.size = PORTAL.length * PORTAL[0].length; + } + + @Override + public boolean hasNext() { + return peekNext() != null; + } + + @Override + public BlockPos next() { + while (pos < size) { + int i = pos / PORTAL[0].length; + int j = pos % PORTAL[0].length; + pos++; + if (PORTAL[i][j] == filter) { + return startPos.add(up.multiply(i)).add(dir.multiply(j)); + } + } + throw new NoSuchElementException(); + } + + private BlockPos peekNext() { + int pos = this.pos; + while (pos < size) { + int i = pos / PORTAL[0].length; + int j = pos % PORTAL[0].length; + pos++; + if (PORTAL[i][j] == filter) { + return startPos.add(up.multiply(i)).add(dir.multiply(j)); + } + } + return null; + } + + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal/PortalListComponent.java b/src/main/java/quimufu/colourful_portals/portal/PortalListComponent.java new file mode 100644 index 0000000..7ded3ad --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal/PortalListComponent.java @@ -0,0 +1,123 @@ +package quimufu.colourful_portals.portal; + +import com.ibm.icu.impl.Pair; +import dev.onyxstudios.cca.api.v3.component.Component; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.nbt.NbtList; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockBox; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldProperties; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER; + +public class PortalListComponent implements Component { + private final WorldProperties worldProperties; + HashMap>> portalsPerPortalBlock = new HashMap<>(); + + public PortalListComponent(WorldProperties worldProperties) { + this.worldProperties = worldProperties; + } + + @Override + public void readFromNbt(NbtCompound tag) { + if (tag != null && !tag.isEmpty()) { + for (String block : tag.getKeys()) { + NbtList portalsWithDim = tag.getList(block, NbtElement.COMPOUND_TYPE); + ArrayList> portals = new ArrayList<>(); + for (int i = 0; i < portalsWithDim.size(); i++) { + NbtCompound portalWithDim = portalsWithDim.getCompound(i); + + NbtCompound portalCompound = portalWithDim.getCompound("portal"); + BlockBox portal = BlockBox.create(NbtHelper.toBlockPos(portalCompound.getCompound("from")), + NbtHelper.toBlockPos(portalCompound.getCompound("to"))); + + Identifier dimension = Identifier.tryParse(portalWithDim.getString("dim")); + + portals.add(Pair.of(portal, dimension)); + } + portalsPerPortalBlock.put(Identifier.tryParse(block), portals); + } + } + } + + @Override + public void writeToNbt(NbtCompound tag) { + for (Identifier portalBlockId : portalsPerPortalBlock.keySet()) { + NbtList portalsWithDimList = new NbtList(); + tag.put(portalBlockId.toString(), portalsWithDimList); + for (Pair portalWithDim : portalsPerPortalBlock.get(portalBlockId)) { + NbtCompound portalWithDimCompound = new NbtCompound(); + portalsWithDimList.add(portalWithDimCompound); + + NbtCompound portalCompound = new NbtCompound(); + portalWithDimCompound.put("portal", portalCompound); + portalWithDimCompound.putString("dim", portalWithDim.second.toString()); + + BlockBox portal = portalWithDim.first; + BlockPos from = new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()); + BlockPos to = new BlockPos(portal.getMaxX(), portal.getMaxY(), portal.getMaxZ()); + + portalCompound.put("from", NbtHelper.fromBlockPos(from)); + portalCompound.put("to", NbtHelper.fromBlockPos(to)); + } + + } + LOGGER.info("portals {}", tag.toString()); + + + } + + public List getContainingPortals(Identifier blockId, BlockPos pos, Identifier dim) { + return portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()).stream() + .filter((portalWithDim) -> (portalWithDim.second.equals(dim) + && portalWithDim.first.contains(pos))) + .map((p) -> p.first) + .toList(); + } + + public void createPortal(Identifier blockId, Pair portalWithDim) { + List> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); + if (!portals.contains(portalWithDim)) { + portals.add(portalWithDim); + } + } + + public void removePortal(Identifier blockId, Pair portalWithDim) { + List> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); + portals.remove(portalWithDim); + } + + public List> getPortals(Identifier blockId) { + return portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); + } + + + public Set getBlockIds() { + return portalsPerPortalBlock.keySet(); + } + + public Pair getNext(Identifier blockId, BlockBox portal, Identifier dim) { + List> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); + Pair portalWithDim = Pair.of(portal, dim); + + int nextIndex = portals.indexOf(portalWithDim) + 1; + if (nextIndex >= portals.size()) { + return portals.get(0); + } + + return portals.get(nextIndex); + } + + public boolean containsPortal(Identifier blockId, Pair portal) { + List> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); + return portals.contains(portal); + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal/PortalManager.java b/src/main/java/quimufu/colourful_portals/portal/PortalManager.java new file mode 100644 index 0000000..acb4cf0 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal/PortalManager.java @@ -0,0 +1,275 @@ +package quimufu.colourful_portals.portal; + +import com.ibm.icu.impl.Pair; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.Entity; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Identifier; +import net.minecraft.util.TypeFilter; +import net.minecraft.util.math.*; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import qouteall.imm_ptl.core.api.PortalAPI; +import qouteall.imm_ptl.core.portal.Portal; +import qouteall.q_misc_util.my_util.DQuaternion; +import quimufu.colourful_portals.config.ColourfulPortalConfig; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static quimufu.colourful_portals.ColourfulPortalsMod.*; +import static quimufu.colourful_portals.Components.PORTAL_CANDIDATE_LIST; +import static quimufu.colourful_portals.Components.PORTAL_LIST; + +public class PortalManager { + + public static void onPortalBlockPlaced(ServerWorld world, BlockPos pos, Identifier blockId) { + world.getProfiler().push("onPortalBlockPlaced"); + LOGGER.debug("onPortalBlockPlaced, {}", blockId); + + PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties()); + //delete portalCandidates obstructed by PortalBlocks for consistency + List portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, world.getDimensionKey().getValue()); + LOGGER.debug("got containing PortalCandidates , {}", portalCandidates); + for (BlockBox portalCandidate : portalCandidates) { + if (!PortalHelper.isValidCandidate(world, portalCandidate, blockId)) { + LOGGER.debug("invalid, {}", portalCandidate); + Pair portalWithDim = + Pair.of(portalCandidate, world.getDimensionKey().getValue()); + + portalCandidateList.removePortal(blockId, portalWithDim); + } + } + //find new portalCandidates created by PortalBlock placement + List portals = PortalHelper.findPortalCandidates(world, pos, blockId); + LOGGER.debug("new portalCandidates found, {}", portals); + + for (BlockBox portal : portals) { + Pair portalWithDim = Pair.of(portal, world.getDimensionKey().getValue()); + portalCandidateList.createPortal(blockId, portalWithDim); + } + world.getProfiler().pop(); + } + + public static void onPortalBlockBroken(ServerWorld world, BlockPos pos, Identifier blockId) { + world.getProfiler().push("onPortalBlockBroken"); + LOGGER.debug("onPortalBlockBroken, {}", blockId); + + PortalListComponent portalList = PORTAL_LIST.get(world.getLevelProperties()); + PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties()); + + //check portalCandidate validity + List portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, world.getDimensionKey().getValue()); + LOGGER.debug("getContainingPortals, {}", portalCandidates); + for (BlockBox portalCandidate : portalCandidates) { + if (!PortalHelper.isValidCandidate(world, portalCandidate, blockId)) { + LOGGER.debug("invalid, {}", portalCandidate); + Pair portalWithDim = + Pair.of(portalCandidate, world.getDimensionKey().getValue()); + portalCandidateList.removePortal(blockId, portalWithDim); + } + } + + //add portalCandidates deobstructed by PortalBlock removal + for (Direction direction : Direction.values()) { + List newPortalCandidates = PortalHelper.findPortalCandidates(world, pos.add(direction.getVector()), blockId); + + for (BlockBox newPortal : newPortalCandidates) { + LOGGER.debug("potentially new candidate after deobstruction , {}", newPortal); + + PortalListComponent portalListComponent = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties()); + Pair portalWithDim = + Pair.of(newPortal, world.getDimensionKey().getValue()); + portalListComponent.createPortal(blockId, portalWithDim); + } + } + + + //check portal validity + List portals = portalList.getContainingPortals(blockId, pos, world.getDimensionKey().getValue()); + boolean changed = false; + for (BlockBox portal : portals) { + if (!PortalHelper.isValidPortal(world, portal, blockId)) { + LOGGER.debug("portal became invalid ,{} {}", world.getDimensionKey().getValue(), portal); + + Pair portalWithDim = + Pair.of(portal, world.getDimensionKey().getValue()); + portalList.removePortal(blockId, portalWithDim); + destroyPortalEntitiesInside(world, Box.from(portal)); + removePortalBlocks(world, portal); + changed = true; + } + + } + if (changed) { + fixPortalGroup(blockId, portalList, world.getServer()); + } + + world.getProfiler().pop(); + } + + private static void removePortalBlocks(ServerWorld world, BlockBox portal) { + BlockPos.stream(portal) + .filter(blockPos -> world.getBlockState(blockPos).isOf(PORTAL_BLOCK)) + .forEach(blockPos -> world.setBlockState(blockPos, Blocks.AIR.getDefaultState())); + } + + private static void fixPortalGroup(Identifier blockId, PortalListComponent portalList, MinecraftServer server) { + List> portalsWithDim = portalList.getPortals(blockId); + if (portalsWithDim.size() == 1) { + Pair portal = portalsWithDim.get(0); + ServerWorld world = getPortalWorld(portal, server); + if (world == null) { + LOGGER.error("error fixing portalGroup, world {} was null", portal.second); + return; + } + Pair portalWithDim = Pair.of(portal.first, portal.second); + portalList.removePortal(blockId, portalWithDim); + destroyPortalEntitiesInside(world, Box.from(portal.first)); + removePortalBlocks(world, portal.first); + return; + } + for (int i = 0; i < portalsWithDim.size(); i++) { + Pair portal = portalsWithDim.get(i); + Pair linkedToPortal = i + 1 < portalsWithDim.size() ? portalsWithDim.get(i + 1) : portalsWithDim.get(0); + assureLinkedTo(portal, linkedToPortal, server); + assurePortalBlocksPlaced(blockId, portal, server); + } + } + + private static void assurePortalBlocksPlaced(Identifier blockId, Pair portal, MinecraftServer server) { + DyeColor color = ColourfulPortalConfig.portalBlocks.get(blockId.toString()); + if (color != null) { + Direction.Axis portalPlaneAxis = PortalHelper.getAxisW(portal.first) + .rotateClockwise(Direction.Axis.Y) + .getAxis(); + ServerWorld portalWorld = getPortalWorld(portal, server); + if (portalWorld == null) { + LOGGER.error("error placing portal planes, world {} was null", portal.second); + return; + } + BlockState portalBlockState = PORTAL_BLOCK.getStateWith(color, portalPlaneAxis); + + PortalHelper.insideOf(portal.first) + .forEachRemaining(blockPos -> { + BlockState blockState = portalWorld.getBlockState(blockPos); + if (blockState.isAir() || blockState.getFluidState().isOf(PORTAL_FLUID)) { + portalWorld.setBlockState(blockPos, portalBlockState); + } + }); + } + + } + + private static void assureLinkedTo(Pair fromPortal, Pair linkedToPortal, MinecraftServer server) { + + ServerWorld fromPortalWorld = getPortalWorld(fromPortal, server); + RegistryKey linkedToPortalWorldRegKey = RegistryKey.of(RegistryKeys.WORLD, linkedToPortal.second); + + BlockBox fromPortalBlockBox = fromPortal.first; + Box fromPortalBox = Box.from(fromPortalBlockBox); + BlockBox linkedToPortalBlockBox = linkedToPortal.first; + Box linkedToPortalBox = Box.from(linkedToPortalBlockBox); + if (fromPortalWorld == null) { + LOGGER.error("error linking portals, world {} was null", fromPortalBlockBox); + return; + } + + List portals = fromPortalWorld.getEntitiesByType(TypeFilter.instanceOf(Portal.class), fromPortalBox, Entity::isAlive); + List outgoingPortals = new ArrayList<>(); + for (Portal portal : portals) { + Box portalBoundingBox = portal.getBoundingBox(); + //is portal actually inside portalBox? + if (fromPortalBox.contains(portalBoundingBox.minX, portalBoundingBox.minY, portalBoundingBox.minZ) + && fromPortalBox.contains(portalBoundingBox.maxX, portalBoundingBox.maxY, portalBoundingBox.maxZ)) { + outgoingPortals.add(portal); + } + } + if (outgoingPortals.size() > 2) { + LOGGER.warn("Found more then 2 portals in {}, cleaning up", fromPortalBox); + for (int i = 2; i < outgoingPortals.size(); i++) { + Portal outgoingPortal = outgoingPortals.get(i); + outgoingPortal.kill(); + } + } + if (outgoingPortals.isEmpty()) { + Portal portal = Portal.entityType.create(fromPortalWorld); + if (portal == null) { + LOGGER.error("could not create Portal entity for {}", fromPortal); + return; + } + + portal.setOriginPos(fromPortalBox.getCenter()); + portal.setDestinationDimension(linkedToPortalWorldRegKey); + portal.setDestination(linkedToPortalBox.getCenter()); + Vec3d axisW = Vec3d.of(PortalHelper.getAxisW(fromPortalBlockBox).getVector()); + portal.setOrientationAndSize( + axisW, // axisW + new Vec3d(0, 1, 0), // axisH + 2, // width + 3 // height + ); + portal.setRotationTransformation(DQuaternion.getRotationBetween(axisW, Vec3d.of(PortalHelper.getAxisW(linkedToPortalBlockBox).getVector()))); + outgoingPortals.add(portal); + PortalAPI.spawnServerEntity(portal); + } + if (outgoingPortals.size() == 1) { + Portal portal = PortalAPI.createFlippedPortal(outgoingPortals.get(0)); + outgoingPortals.add(portal); + PortalAPI.spawnServerEntity(portal); + } + for (Portal outgoingPortal : outgoingPortals) { + outgoingPortal.setDestinationDimension(linkedToPortalWorldRegKey); + outgoingPortal.setDestination(linkedToPortalBox.getCenter()); + Vec3d axisW = Vec3d.of(PortalHelper.getAxisW(fromPortalBlockBox).getVector()); + outgoingPortal.setRotationTransformation(DQuaternion.getRotationBetween(axisW, Vec3d.of(PortalHelper.getAxisW(linkedToPortalBlockBox).getVector()))); + outgoingPortal.reloadAndSyncToClient(); + } + } + + @Nullable + private static ServerWorld getPortalWorld(Pair fromPortal, MinecraftServer server) { + return server.getWorld(RegistryKey.of(RegistryKeys.WORLD, fromPortal.second)); + } + + private static void destroyPortalEntitiesInside(ServerWorld world, Box box) { + List portals = world.getEntitiesByType(TypeFilter.instanceOf(Portal.class), box, Entity::isAlive); + portals.forEach(portal -> PortalAPI.removeGlobalPortal(world, portal)); + } + + public static boolean tryIgnite(ServerWorld world, BlockPos pos) { + world.getProfiler().push("portal ignition"); + PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties()); + PortalListComponent portalList = PORTAL_LIST.get(world.getLevelProperties()); + Set blockIds = portalCandidateList.getBlockIds(); + boolean ret = false; + for (Identifier blockId : blockIds) { + Identifier dim = world.getDimensionKey().getValue(); + List portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, dim); + for (BlockBox portalCandidate : portalCandidates) { + if (PortalHelper.isValidPortal(world, portalCandidate, blockId)) { + Pair next = portalCandidateList.getNext(blockId, portalCandidate, dim); + Pair current = Pair.of(portalCandidate, dim); + if (!next.equals(current) + && (!portalList.containsPortal(blockId, current) || !portalList.containsPortal(blockId, next))) { + portalList.createPortal(blockId, current); + portalList.createPortal(blockId, next); + ret = true; + + } + fixPortalGroup(blockId, portalList, world.getServer()); + } + } + + } + + world.getProfiler().pop(); + return ret; + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal_fluid/NullableAxis.java b/src/main/java/quimufu/colourful_portals/portal_fluid/NullableAxis.java new file mode 100644 index 0000000..fdedce8 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal_fluid/NullableAxis.java @@ -0,0 +1,47 @@ +package quimufu.colourful_portals.portal_fluid; + +import net.minecraft.util.StringIdentifiable; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.Nullable; + +public enum NullableAxis implements StringIdentifiable { + X(Direction.Axis.X), + Y(Direction.Axis.Y), + Z(Direction.Axis.Z), + NULL(null); + + private final Direction.Axis axis; + + NullableAxis(Direction.Axis axis) { + + this.axis = axis; + } + + public static NullableAxis of(Direction.Axis currentAxis) { + if (currentAxis == null){ + return NULL; + } + switch (currentAxis) { + case X -> { + return X; + } + case Y -> { + return Y; + } + case Z -> { + return Z; + } + } + return NULL; + } + + @Nullable + public Direction.Axis getAxis() { + return axis; + } + + @Override + public String asString() { + return this == NULL ? "null" : axis.asString(); + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluid.java b/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluid.java new file mode 100644 index 0000000..bdc8c12 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluid.java @@ -0,0 +1,564 @@ +package quimufu.colourful_portals.portal_fluid; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.IntProperty; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import net.minecraft.world.WorldView; +import org.jetbrains.annotations.NotNull; +import quimufu.colourful_portals.ColourfulPortalsMod; +import quimufu.colourful_portals.portal.PortalManager; + +import java.util.*; + +import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER; + +public class PortalFluid extends Fluid { + public static final EnumProperty AXIS = EnumProperty.of("axis", NullableAxis.class); + public static final IntProperty AMOUNT = IntProperty.of("amount", 0, 16); + + public final FluidState[] fromBlockStateId; + private final Map shapeCache = new IdentityHashMap<>(); + + public PortalFluid() { + super(); + fromBlockStateId = this.getStateManager().getStates().toArray(new FluidState[0]); + setDefaultState(stateManager.getDefaultState().with(AXIS, NullableAxis.NULL).with(AMOUNT, 16)); + } + + public FluidState unpackFluidState(int i) { + return fromBlockStateId[i]; + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(AXIS, AMOUNT); + super.appendProperties(builder); + } + + @Override + protected boolean hasRandomTicks() { + return true; + } + + @Override + protected void onRandomTick(World world, BlockPos pos, FluidState state, Random random) { + if (!world.isClient && !world.getFluidTickScheduler().isQueued(pos, this)) { + //world.scheduleFluidTick(pos, this, this.getTickRate(world)); + } + } + + @Override + public Item getBucketItem() { + return ColourfulPortalsMod.PORTAL_FLUID_BUCKET_ITEM; + } + + @Override + protected boolean canBeReplacedWith(FluidState state, BlockView world, BlockPos pos, Fluid fluid, Direction direction) { + return false; + } + + @Override + protected Vec3d getVelocity(BlockView world, BlockPos pos, FluidState state) { + return calcVelocity(world, pos, state); + } + + private static Vec3d calcVelocity(BlockView world, BlockPos pos, FluidState state) { + Vec3d ret = Vec3d.ZERO; + for (Direction direction : Direction.values()) { + if (direction.getAxis() != state.get(AXIS).getAxis()) { + BlockState neighbour = world.getBlockState(pos.offset(direction)); + if (neighbour.isOf(ColourfulPortalsMod.PORTAL_FLUID_BLOCk) + && neighbour.getFluidState().get(AMOUNT) == 0) { + ret = ret.add(Vec3d.of(direction.getVector())); + } + } + } + return ret.normalize(); + } + + @Override + protected void onScheduledTick(World world, BlockPos pos, FluidState state) { + world.getProfiler().push("portalFluidTick"); + + FluidState newStateMe = state; + + int amount = state.get(AMOUNT); + Direction.Axis currentAxis = state.get(AXIS).getAxis(); + + //copy target dir from neighbours to allow easy extension of planes + if (currentAxis == null) { + for (Direction direction : Direction.values()) { + BlockPos offset = pos.offset(direction); + FluidState fluidState = world.getBlockState(offset).getFluidState(); + if (fluidState.isOf(this) && fluidState.get(AXIS) != NullableAxis.NULL) { + currentAxis = fluidState.get(AXIS).getAxis(); + newStateMe = state.with(AXIS, NullableAxis.of(currentAxis)); + world.scheduleFluidTick(offset, this, getTickRate(world)); + break; + } + } + } + + + Direction[] costToDirArray = calculateCostToDirArray(world, pos, currentAxis); + + Direction targetDir = null; + int cheapestCost = 0; + for (int i = 0; i < costToDirArray.length; i++) { + Direction direction = costToDirArray[i]; + if (direction != null) { + targetDir = direction; + cheapestCost = i; + break; + } + } + if (currentAxis == null && targetDir != null) { + currentAxis = getAxis(world, pos, costToDirArray, targetDir, cheapestCost); + newStateMe = state.with(AXIS, NullableAxis.of(currentAxis)); + } + + if (amount == 0 || (amount <= 14 && targetDir != null)) { + //*might* change my state in world, returned value is what new state should be + newStateMe = steal(world, pos, newStateMe, currentAxis); + } + //my state might have changed. + amount = newStateMe.get(AMOUNT); + + if (amount == 0) { + //we couldn't steal anything to keep ourselves alive. This ?should? not happen. + world.setBlockState(pos, Blocks.AIR.getDefaultState()); + world.updateNeighborsAlways(pos, ColourfulPortalsMod.PORTAL_FLUID_BLOCk); + world.getProfiler().pop(); + return; + } + if (amount == 1) { + //we are alive, but can't spread, we need no updates + //create a portal if applicable + world.getProfiler().pop(); + return; + } + + if (targetDir != null) { + int partnerAmount = amount / 2; + FluidState newStatePartner = state.with(AXIS, NullableAxis.of(currentAxis)).with(AMOUNT, partnerAmount); + newStateMe = newStateMe.with(AMOUNT, amount - partnerAmount); + + BlockPos posPartner = pos.offset(targetDir); + + world.scheduleFluidTick(posPartner, this, getTickRate(world)); + world.setBlockState(posPartner, newStatePartner.getBlockState()); + } else if (!world.isClient) { + for (Direction direction : Direction.values()) { + if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(world.getBlockState(pos.offset(direction)).getBlock())) + && PortalManager.tryIgnite((ServerWorld) world, pos)) { + return; + } + } + + } + + if (world.getBlockState(pos).getFluidState() != newStateMe) { + BlockState blockState = newStateMe.getBlockState(); + world.setBlockState(pos, blockState); + world.updateNeighborsAlways(pos, blockState.getBlock()); + world.scheduleFluidTick(pos, this, getTickRate(world)); + } else if (world.getBlockState(pos).getFluidState() != state) { + //we changed, so we schedule an update for ourselves + world.scheduleFluidTick(pos, this, getTickRate(world)); + } + world.getProfiler().pop(); + } + + private static Direction.Axis getAxis(World world, BlockPos pos, Direction[] costToDirArray, Direction targetDir, int cheapestCost) { + for (int i = cheapestCost + 1; i < 12; i++) { + Direction direction = costToDirArray[i]; + if (direction != null && direction != targetDir.getOpposite()) { + return findPerpendicularAxis(targetDir, direction); + } + } + Direction candidate = null; + for (Direction dir : Direction.values()) { + if (dir == targetDir.getOpposite() || dir == targetDir) { + continue; + } + BlockPos neighborOfInspectedLocation = pos.offset(dir); + BlockState neighbourOfInspected = world.getBlockState(neighborOfInspectedLocation); + + //axis of portal Block takes precedence + if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(neighbourOfInspected.getBlock()))) { + return findPerpendicularAxis(targetDir, dir); + } + //if adjacent to any solid Block, it's a candidate + if (neighbourOfInspected.isSideSolidFullSquare(world, neighborOfInspectedLocation, dir.getOpposite())) { + candidate = dir; + } + if (candidate == null && !neighbourOfInspected.isReplaceable()) { + candidate = dir; + } + if (candidate == null && !neighbourOfInspected.isReplaceable()) { + candidate = dir; + } + + } + return findPerpendicularAxis(targetDir, candidate); + } + + private static Direction.Axis findPerpendicularAxis(Direction one, Direction two) { + Vec3i axisVec = one.getVector().crossProduct(two.getVector()); + for (Direction d : Direction.values()) { + if (d.getVector().equals(axisVec)) { + return d.getAxis(); + } + } + throw new RuntimeException("invalid axis Vector " + axisVec); + } + + @NotNull + private FluidState steal(World world, BlockPos pos, FluidState myState, Direction.Axis currentAxis) { + Integer myAmount = myState.get(AMOUNT); + LinkedHashMap victims = getConnectedNearBy(world, pos, myState, currentAxis, 1); + + //only I + if (victims.size() == 1) { + //can't steal from no one :( + return myState; + } + + int sum = 0; + int count = 0; + for (FluidState s : victims.values()) { + int i = s.get(AMOUNT); + sum += i; + count++; + } + //everyone should have average AMOUNT, + int average = sum / count; + //and reminder of them should have one more + int reminder = sum % count; + + if (average == 0) { + //can't steal from poor people + return myState; + } + + //I want to steal target AMOUNT for myself + int target = average - myAmount; + if (reminder > 0) { + target++; + reminder--; + } + if (target <= 0) { + //how did we even get here?!? + return myState; + } + + int stolen = 0; + HashMap stolenFrom = new HashMap<>(); + + for (Map.Entry victim : victims.entrySet()) { + FluidState victimState = victim.getValue(); + Integer victimAmount = victimState.get(AMOUNT); + if (victimAmount > average) { + if (reminder > 0 && victimAmount == average + 1) { + reminder--; + } else { + //needs to be stolen from! + int toSteal = Math.min(victimAmount - average, target - stolen); + FluidState newVictimState = victimState.with(AMOUNT, victimAmount - toSteal); + stolen += toSteal; + stolenFrom.put(victim.getKey(), newVictimState); + } + } + if (stolen == target) { + //Already done! + break; + } + } + if (stolen != target) { + LOGGER.error("Unsuccessful in Stealing! we somehow stole {}/{}", stolen, target); + } + myState = myState.with(AMOUNT, myAmount + stolen); + world.setBlockState(pos, myState.getBlockState(), Block.NOTIFY_LISTENERS); + for (Map.Entry updated : stolenFrom.entrySet()) { + world.setBlockState(updated.getKey(), updated.getValue().getBlockState(), Block.NOTIFY_LISTENERS); + } + + + return myState; + } + + @NotNull + private LinkedHashMap getConnectedNearBy(WorldAccess world, BlockPos pos, FluidState myState, Direction.Axis currentAxis, int minAmount) { + LinkedHashMap connectedNearBy = new LinkedHashMap<>(16); + HashSet borderCurr = new HashSet<>(); + HashSet borderNext = new HashSet<>(); + connectedNearBy.put(pos, myState); + borderCurr.add(pos); + while (connectedNearBy.size() <= 17 && !borderCurr.isEmpty()) { + for (BlockPos blockPos : borderCurr) { + for (Direction direction : Direction.values()) { + BlockPos pos1 = blockPos.offset(direction); + FluidState fluidState; + if (!connectedNearBy.containsKey(pos1) + && (fluidState = world.getBlockState(pos1).getFluidState()).isOf(this) + && fluidState.get(AXIS).getAxis() == currentAxis + && fluidState.get(AMOUNT) >= minAmount) { + borderNext.add(pos1); + connectedNearBy.put(pos1, fluidState); + } + } + } + borderCurr.clear(); + HashSet tmp = borderCurr; + borderCurr = borderNext; + borderNext = tmp; + } + return connectedNearBy; + } + + @NotNull + private Direction[] calculateCostToDirArray(World world, BlockPos pos, Direction.Axis currentAxis) { + int maxCost = 48; + Direction[] directionCosts = new Direction[maxCost + 1]; + for (Direction direction : Direction.values()) { + //if this is not the first time spreading (=>currentAxis != null), we wanna spread perpendicular to AXIS + if (direction.getAxis() == currentAxis) + continue; + //can't spread into walls or our own fluid + BlockState blockState = world.getBlockState(pos.offset(direction)); + if (!blockState.isReplaceable() || blockState.getFluidState().isOf(this)) + continue; + int dirCost = 0; + int distance = 1; + while (distance <= 15 && dirCost < maxCost) { + BlockPos inspectedLocation = pos.offset(direction, distance); + BlockState inspectedState = world.getBlockState(inspectedLocation); + //we reached a wall. Keeping cost + if (!inspectedState.isReplaceable()) { + //make sure we don't lose equally cheap options + while (dirCost < 16 && directionCosts[dirCost] != null) { + dirCost++; + } + directionCosts[dirCost] = direction; + break; + } + //each distance costs 3 + dirCost += 3; + boolean discount = false; + for (Direction dir : Direction.values()) { + if (dir.getOpposite() == direction) { + continue; + } + BlockPos neighborOfInspectedLocation = inspectedLocation.offset(dir); + BlockState neighbourOfInspected = world.getBlockState(neighborOfInspectedLocation); + + //if adjacent to a Portal Block, 2 less + if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(neighbourOfInspected.getBlock()))) { + dirCost -= 2; + discount = false; + break; + } + //if adjacent to any solid Block, 1 less + if (neighbourOfInspected.isSideSolidFullSquare(world, neighborOfInspectedLocation, dir.getOpposite())) { + discount = true; + //maybe a Portal Block in another direction + continue; + } + } + if (discount) { + dirCost--; + } + if (distance == 15) { + //we did not find a block within 16 blocks. The fluid can't travel further + // punishing with +3 cost, unless we are already at max cost + dirCost = Math.max(dirCost + 3, maxCost); + directionCosts[dirCost] = direction; + } + distance++; + } + } + return directionCosts; + } + + @Override + public int getTickRate(WorldView world) { + return 50; + } + + @Override + protected float getBlastResistance() { + return 100.0f; + } + + @Override + public float getHeight(FluidState state, BlockView world, BlockPos pos) { + return getHeight(state); + } + + @Override + public float getHeight(FluidState state) { + return state.get(AMOUNT) / 16f + 0.1f;//TODO:remove add + } + + @Override + public Optional getBucketFillSound() { + return Optional.of(SoundEvents.ITEM_BUCKET_FILL); + } + + @Override + protected BlockState toBlockState(FluidState state) { + return ColourfulPortalsMod.PORTAL_FLUID_BLOCk.getDefaultState().with(PortalFluidBlock.FLUID_STATE_ID, stateManager.getStates().indexOf(state)); + } + + @Override + public boolean isStill(FluidState state) { + return true; + } + + @Override + public int getLevel(FluidState state) { + return state.get(AMOUNT) / 2; + } + + @Override + public VoxelShape getShape(FluidState state, BlockView world, BlockPos pos) { + return this.shapeCache.computeIfAbsent(state, this::calcShape); + } + + private VoxelShape calcShape(FluidState s) { + float height = getHeight(s); + if (height == 0) { + return VoxelShapes.empty(); + } + double minX = 0.0; + double minY = 0.0; + double minZ = 0.0; + double maxX = 1.0; + double maxY = 1.0; + double maxZ = 1.0; + switch (s.get(AXIS)) { + case X -> { + minX = 0.5 - height / 2F; + maxX = 0.5 + height / 2F; + } + case Y -> { + minY = 0.5 - height / 2F; + maxY = 0.5 + height / 2F; + } + case Z -> { + minZ = 0.5 - height / 2F; + maxZ = 0.5 + height / 2F; + } + } + return VoxelShapes.cuboid(minX, minY, minZ, maxX, maxY, maxZ); + } + + public boolean tryFillWithFluid(WorldAccess world, BlockPos pos, BlockState state, FluidState fluidState) { + + int toDistribute = fluidState.get(AMOUNT); + FluidState myState = state.getFluidState(); + LinkedHashMap connectedNearBy = getConnectedNearBy(world, pos, myState, myState.get(AXIS).getAxis(), 1); + int sum = connectedNearBy.values().stream().mapToInt(f -> 16 - f.get(AMOUNT)).sum(); + if (sum < toDistribute) { + return false; + } + + HashMap updated = new HashMap<>(); + for (Map.Entry stateEntry : connectedNearBy.entrySet()) { + FluidState value = stateEntry.getValue(); + Integer targetAmount = value.get(AMOUNT); + if (targetAmount < 16) { + //needs to be filled up! + int fillAmount = Math.min(16 - targetAmount, toDistribute); + + FluidState newTargetState = value.with(AMOUNT, targetAmount + fillAmount); + toDistribute -= fillAmount; + updated.put(stateEntry.getKey(), newTargetState); + } + if (toDistribute == 0) { + //Already done! + break; + } + } + if (toDistribute != 0) { + LOGGER.error("Unsuccessful in Filling! we somehow have {} left", toDistribute); + return false; + } + if (!world.isClient()) { + for (Map.Entry update : updated.entrySet()) { + world.setBlockState(update.getKey(), update.getValue().getBlockState(), Block.NOTIFY_LISTENERS); + + if (!world.getFluidTickScheduler().isQueued(update.getKey(), this)) { + world.scheduleFluidTick(update.getKey(), this, this.getTickRate(world)); + } + } + } + return true; + } + + public boolean tryDrainFluid(WorldAccess world, BlockPos pos, BlockState state) { + FluidState myState = state.getFluidState(); + int toGeather = this.getDefaultState().get(AMOUNT); + LinkedHashMap connectedNearBy = getConnectedNearBy(world, pos, myState, myState.get(AXIS).getAxis(), 1); + int sum = connectedNearBy.values().stream().mapToInt(f -> f.get(AMOUNT)).sum(); + if (sum < toGeather) { + return false; + } + + HashMap updated = new HashMap<>(); + for (Map.Entry stateEntry : connectedNearBy.entrySet()) { + FluidState value = stateEntry.getValue(); + Integer targetAmount = value.get(AMOUNT); + if (targetAmount > 0) { + //needs to be filled up! + int gatherAmount = Math.min(targetAmount, toGeather); + + FluidState newTargetState = value.with(AMOUNT, targetAmount - gatherAmount); + toGeather -= gatherAmount; + updated.put(stateEntry.getKey(), newTargetState); + } + if (toGeather == 0) { + //Already done! + break; + } + } + if (toGeather != 0) { + LOGGER.error("Unsuccessful in Draining! we somehow got {} to few", toGeather); + return false; + } + + if (!world.isClient()) { + for (Map.Entry update : updated.entrySet()) { + if (update.getValue().get(AMOUNT) != 0) { + world.setBlockState(update.getKey(), update.getValue().getBlockState(), Block.NOTIFY_LISTENERS); + if (!world.getFluidTickScheduler().isQueued(update.getKey(), this)) { + world.scheduleFluidTick(update.getKey(), this, this.getTickRate(world)); + } + } else { + world.setBlockState(update.getKey(), Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL); + } + } + } + return true; + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluidBlock.java b/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluidBlock.java new file mode 100644 index 0000000..08acedd --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluidBlock.java @@ -0,0 +1,134 @@ +package quimufu.colourful_portals.portal_fluid; + +import net.minecraft.block.*; +import net.minecraft.entity.ai.pathing.NavigationType; +import net.minecraft.fluid.Fluid; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.loot.context.LootContext; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvent; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.IntProperty; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class PortalFluidBlock + extends Block + implements FluidDrainable { + public static final IntProperty FLUID_STATE_ID = IntProperty.of("fluid_state_id", 0, 67); + private final PortalFluid fluid; + + public PortalFluidBlock(PortalFluid fluid, Settings settings) { + super(settings); + this.fluid = fluid; + } + + @Override + protected void appendProperties(StateManager.Builder builder) { + builder.add(FLUID_STATE_ID); + super.appendProperties(builder); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + return VoxelShapes.empty(); + } + + @Override + public boolean hasRandomTicks(BlockState state) { + return state.getFluidState().hasRandomTicks(); + } + + @Override + public void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) { + state.getFluidState().onRandomTick(world, pos, random); + } + + @Override + public void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) { + if (!world.getFluidTickScheduler().isQueued(pos, fluid)) { + world.scheduleFluidTick(pos, fluid, this.fluid.getTickRate(world)); + } + } + + @Override + public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos) { + if (!world.getFluidTickScheduler().isQueued(pos, fluid)) { + world.scheduleFluidTick(pos, fluid, this.fluid.getTickRate(world)); + } + return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos); + } + + @Override + public void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { + if (!world.getFluidTickScheduler().isQueued(pos, fluid)) { + world.scheduleFluidTick(pos, fluid, this.fluid.getTickRate(world)); + } + } + + @Override + public boolean isSideInvisible(BlockState state, BlockState stateFrom, Direction direction) { + return stateFrom.getFluidState().getFluid().matchesType(this.fluid); + } + + @Override + public BlockRenderType getRenderType(BlockState state) { + return BlockRenderType.INVISIBLE; + } + + @Override + public List getDroppedStacks(BlockState state, LootContext.Builder builder) { + return Collections.emptyList(); + } + + @Override + public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { + if(context.isHolding(Items.DEBUG_STICK)){ + return VoxelShapes.fullCube(); + } + return VoxelShapes.empty(); + } + + @Override + public boolean isTransparent(BlockState state, BlockView world, BlockPos pos) { + return false; + } + + @Override + public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) { + return true; + } + + @Override + public FluidState getFluidState(BlockState state) { + return fluid.unpackFluidState(state.get(FLUID_STATE_ID)); + } + + @Override + public ItemStack tryDrainFluid(WorldAccess world, BlockPos pos, BlockState state) { + if (state.getFluidState().get(PortalFluid.AMOUNT) == 16) { + world.setBlockState(pos, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL | Block.REDRAW_ON_MAIN_THREAD); + return new ItemStack(this.fluid.getBucketItem()); + } else if (this.fluid.tryDrainFluid(world,pos,state)){ + return new ItemStack(this.fluid.getBucketItem()); + } + return ItemStack.EMPTY; + } + + @Override + public Optional getBucketFillSound() { + return fluid.getBucketFillSound(); + } +} diff --git a/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluidBucketItem.java b/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluidBucketItem.java new file mode 100644 index 0000000..16fcf02 --- /dev/null +++ b/src/main/java/quimufu/colourful_portals/portal_fluid/PortalFluidBucketItem.java @@ -0,0 +1,85 @@ +package quimufu.colourful_portals.portal_fluid; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.FluidFillable; +import net.minecraft.block.Material; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BucketItem; +import net.minecraft.item.ItemStack; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.RaycastContext; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import net.minecraft.world.event.GameEvent; + +public class PortalFluidBucketItem extends BucketItem { + private final PortalFluid fluid; + + public PortalFluidBucketItem(PortalFluid fluid, Settings settings) { + super(fluid, settings); + this.fluid = fluid; + } + + @Override + public TypedActionResult use(World world, PlayerEntity user, Hand hand) { + BlockHitResult blockHitResult = BucketItem.raycast(world, user, RaycastContext.FluidHandling.ANY); + if (blockHitResult.getType() == HitResult.Type.BLOCK) { + BlockPos pos = blockHitResult.getBlockPos(); + if (world.getBlockState(pos).getFluidState().isOf(fluid)) { + placeFluid(user, world, pos, blockHitResult); + } + } + return super.use(world, user, hand); + } + + @Override + public boolean placeFluid(PlayerEntity player, World world, BlockPos pos, BlockHitResult hitResult) { + BlockState blockState = world.getBlockState(pos); + Block block = blockState.getBlock(); + Material material = blockState.getMaterial(); + boolean shouldTryPlace = blockState.isAir() + || blockState.canBucketPlace(this.fluid) + || block instanceof FluidFillable && ((FluidFillable) block).canFillWithFluid(world, pos, blockState, this.fluid); + if (!shouldTryPlace) { + return hitResult != null && this.placeFluid(player, world, hitResult.getBlockPos().offset(hitResult.getSide()), null); + } + if (blockState.getFluidState().isOf(fluid)) { + if (fluid.tryFillWithFluid(world, pos, blockState, this.fluid.getDefaultState())) { + this.playEmptyingSound(player, world, pos); + return true; + } + return false; + } + if (block instanceof FluidFillable) { + ((FluidFillable) block).tryFillWithFluid(world, pos, blockState, this.fluid.getDefaultState()); + this.playEmptyingSound(player, world, pos); + return true; + } + + + if (!world.isClient && blockState.canBucketPlace(this.fluid) && !material.isLiquid()) { + world.breakBlock(pos, true); + } + if (world.setBlockState(pos, this.fluid.getDefaultState().getBlockState(), Block.NOTIFY_ALL | Block.REDRAW_ON_MAIN_THREAD) || blockState.getFluidState().isStill()) { + this.playEmptyingSound(player, world, pos); + return true; + } + return false; + } + + //we need to overwrite this because the fabric API mixin crashes otherwise! + @Override + protected void playEmptyingSound(PlayerEntity player, WorldAccess world, BlockPos pos) { + SoundEvent soundEvent = SoundEvents.ITEM_BUCKET_EMPTY; + world.playSound(player, pos, soundEvent, SoundCategory.BLOCKS, 1.0f, 1.0f); + world.emitGameEvent(player, GameEvent.FLUID_PLACE, pos); + } +} diff --git a/src/main/resources/assets/colourful_portals/blockstates/portal_block.json b/src/main/resources/assets/colourful_portals/blockstates/portal_block.json new file mode 100644 index 0000000..7ecef7d --- /dev/null +++ b/src/main/resources/assets/colourful_portals/blockstates/portal_block.json @@ -0,0 +1,181 @@ +{ + "variants": { + "axis=x,colour=white": { + "model": "colourful_portals:block/portal_block_white", + "y": 90 + }, + "axis=y,colour=white": { + "model": "colourful_portals:block/portal_block_white", + "x": 90 + }, + "axis=z,colour=white": { + "model": "colourful_portals:block/portal_block_white" + }, + "axis=x,colour=orange": { + "model": "colourful_portals:block/portal_block_orange", + "y": 90 + }, + "axis=y,colour=orange": { + "model": "colourful_portals:block/portal_block_orange", + "x": 90 + }, + "axis=z,colour=orange": { + "model": "colourful_portals:block/portal_block_orange" + }, + "axis=x,colour=magenta": { + "model": "colourful_portals:block/portal_block_magenta", + "y": 90 + }, + "axis=y,colour=magenta": { + "model": "colourful_portals:block/portal_block_magenta", + "x": 90 + }, + "axis=z,colour=magenta": { + "model": "colourful_portals:block/portal_block_magenta" + }, + "axis=x,colour=light_blue": { + "model": "colourful_portals:block/portal_block_light_blue", + "y": 90 + }, + "axis=y,colour=light_blue": { + "model": "colourful_portals:block/portal_block_light_blue", + "x": 90 + }, + "axis=z,colour=light_blue": { + "model": "colourful_portals:block/portal_block_light_blue" + }, + "axis=x,colour=yellow": { + "model": "colourful_portals:block/portal_block_yellow", + "y": 90 + }, + "axis=y,colour=yellow": { + "model": "colourful_portals:block/portal_block_yellow", + "x": 90 + }, + "axis=z,colour=yellow": { + "model": "colourful_portals:block/portal_block_yellow" + }, + "axis=x,colour=lime": { + "model": "colourful_portals:block/portal_block_lime", + "y": 90 + }, + "axis=y,colour=lime": { + "model": "colourful_portals:block/portal_block_lime", + "x": 90 + }, + "axis=z,colour=lime": { + "model": "colourful_portals:block/portal_block_lime" + }, + "axis=x,colour=pink": { + "model": "colourful_portals:block/portal_block_pink", + "y": 90 + }, + "axis=y,colour=pink": { + "model": "colourful_portals:block/portal_block_pink", + "x": 90 + }, + "axis=z,colour=pink": { + "model": "colourful_portals:block/portal_block_pink" + }, + "axis=x,colour=gray": { + "model": "colourful_portals:block/portal_block_gray", + "y": 90 + }, + "axis=y,colour=gray": { + "model": "colourful_portals:block/portal_block_gray", + "x": 90 + }, + "axis=z,colour=gray": { + "model": "colourful_portals:block/portal_block_gray" + }, + "axis=x,colour=light_gray": { + "model": "colourful_portals:block/portal_block_light_gray", + "y": 90 + }, + "axis=y,colour=light_gray": { + "model": "colourful_portals:block/portal_block_light_gray", + "x": 90 + }, + "axis=z,colour=light_gray": { + "model": "colourful_portals:block/portal_block_light_gray" + }, + "axis=x,colour=cyan": { + "model": "colourful_portals:block/portal_block_cyan", + "y": 90 + }, + "axis=y,colour=cyan": { + "model": "colourful_portals:block/portal_block_cyan", + "x": 90 + }, + "axis=z,colour=cyan": { + "model": "colourful_portals:block/portal_block_cyan" + }, + "axis=x,colour=purple": { + "model": "colourful_portals:block/portal_block_purple", + "y": 90 + }, + "axis=y,colour=purple": { + "model": "colourful_portals:block/portal_block_purple", + "x": 90 + }, + "axis=z,colour=purple": { + "model": "colourful_portals:block/portal_block_purple" + }, + "axis=x,colour=blue": { + "model": "colourful_portals:block/portal_block_blue", + "y": 90 + }, + "axis=y,colour=blue": { + "model": "colourful_portals:block/portal_block_blue", + "x": 90 + }, + "axis=z,colour=blue": { + "model": "colourful_portals:block/portal_block_blue" + }, + "axis=x,colour=brown": { + "model": "colourful_portals:block/portal_block_brown", + "y": 90 + }, + "axis=y,colour=brown": { + "model": "colourful_portals:block/portal_block_brown", + "x": 90 + }, + "axis=z,colour=brown": { + "model": "colourful_portals:block/portal_block_brown" + }, + "axis=x,colour=green": { + "model": "colourful_portals:block/portal_block_green", + "y": 90 + }, + "axis=y,colour=green": { + "model": "colourful_portals:block/portal_block_green", + "x": 90 + }, + "axis=z,colour=green": { + "model": "colourful_portals:block/portal_block_green" + }, + "axis=x,colour=red": { + "model": "colourful_portals:block/portal_block_red", + "y": 90 + }, + "axis=y,colour=red": { + "model": "colourful_portals:block/portal_block_red", + "x": 90 + }, + "axis=z,colour=red": { + "model": "colourful_portals:block/portal_block_red" + }, + "axis=x,colour=black": { + "model": "colourful_portals:block/portal_block_black", + "y": 90 + }, + "axis=y,colour=black": { + "model": "colourful_portals:block/portal_block_black", + "x": 90 + }, + "axis=z,colour=black": { + "model": "colourful_portals:block/portal_block_black" + } + } +} + diff --git a/src/main/resources/assets/colourful_portals/blockstates/portal_fluid_block.json b/src/main/resources/assets/colourful_portals/blockstates/portal_fluid_block.json new file mode 100644 index 0000000..99fd360 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/blockstates/portal_fluid_block.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "minecraft:block/water" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/icon.png b/src/main/resources/assets/colourful_portals/icon.png new file mode 100644 index 0000000..047b91f Binary files /dev/null and b/src/main/resources/assets/colourful_portals/icon.png differ diff --git a/src/main/resources/assets/colourful_portals/lang/de_de.json b/src/main/resources/assets/colourful_portals/lang/de_de.json new file mode 100644 index 0000000..d856975 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/lang/de_de.json @@ -0,0 +1,7 @@ +{ + "item.colourful_portals.portal_fluid_bucket": "Farbeimer", + "block.colourful_portals.portal_block": "Farbenfrohes Portal", + "item.colourful_portals.colour_blob_bright": "Heller Farbmix", + "item.colourful_portals.colour_blob_dark": "Dunkler Farbmix", + "block.colourful_portals.portal_fluid_block": "Farbenfrohe Flüssigkeit" +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/lang/en_us.json b/src/main/resources/assets/colourful_portals/lang/en_us.json new file mode 100644 index 0000000..d6c8cc5 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/lang/en_us.json @@ -0,0 +1,7 @@ +{ + "item.colourful_portals.portal_fluid_bucket": "Colourful Bucket", + "item.colourful_portals.colour_blob_bright": "Bright Colour Mix", + "item.colourful_portals.colour_blob_dark": "Dark Colour Mix", + "block.colourful_portals.portal_block": "Colourful Portal", + "block.colourful_portals.portal_fluid_block": "Colourful Fluid" +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block.json b/src/main/resources/assets/colourful_portals/models/block/portal_block.json new file mode 100644 index 0000000..f6502ab --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block.json @@ -0,0 +1,17 @@ +{ + "parent": "block/block", + "elements": [ + { + "from": [0, 0, 7], + "to": [16, 16, 9], + "faces": { + "north": { + "texture": "#pane" + }, + "south": { + "texture": "#pane" + } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_black.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_black.json new file mode 100644 index 0000000..cba4efe --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_black.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_black" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_blue.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_blue.json new file mode 100644 index 0000000..0a5c64f --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_blue.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_blue" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_brown.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_brown.json new file mode 100644 index 0000000..9ecb0a9 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_brown.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_brown" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_cyan.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_cyan.json new file mode 100644 index 0000000..dff46de --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_cyan.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_cyan" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_gray.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_gray.json new file mode 100644 index 0000000..8666804 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_gray.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_gray" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_green.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_green.json new file mode 100644 index 0000000..f8745a1 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_green.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_green" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_light_blue.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_light_blue.json new file mode 100644 index 0000000..c8c04ba --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_light_blue.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_light_blue" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_light_gray.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_light_gray.json new file mode 100644 index 0000000..746087e --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_light_gray.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_light_gray" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_lime.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_lime.json new file mode 100644 index 0000000..c1eaeeb --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_lime.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_lime" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_magenta.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_magenta.json new file mode 100644 index 0000000..6a1409f --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_magenta.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_magenta" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_orange.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_orange.json new file mode 100644 index 0000000..9a231d1 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_orange.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_orange" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_pink.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_pink.json new file mode 100644 index 0000000..b50d3a9 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_pink.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_pink" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_purple.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_purple.json new file mode 100644 index 0000000..bebe20c --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_purple.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_purple" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_red.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_red.json new file mode 100644 index 0000000..03d0d7b --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_red.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_red" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_white.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_white.json new file mode 100644 index 0000000..d2036e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_white.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_white" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/block/portal_block_yellow.json b/src/main/resources/assets/colourful_portals/models/block/portal_block_yellow.json new file mode 100644 index 0000000..fc374ce --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/block/portal_block_yellow.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_yellow" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/item/colour_blob_bright.json b/src/main/resources/assets/colourful_portals/models/item/colour_blob_bright.json new file mode 100644 index 0000000..097962f --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/item/colour_blob_bright.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "colourful_portals:item/colour_blob_bright" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/item/colour_blob_dark.json b/src/main/resources/assets/colourful_portals/models/item/colour_blob_dark.json new file mode 100644 index 0000000..f6f2cfb --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/item/colour_blob_dark.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "colourful_portals:item/colour_blob_dark" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/models/item/portal_block.json b/src/main/resources/assets/colourful_portals/models/item/portal_block.json new file mode 100644 index 0000000..22d9251 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/item/portal_block.json @@ -0,0 +1,6 @@ +{ + "parent": "colourful_portals:block/portal_block", + "textures": { + "pane": "colourful_portals:block/portal_block_black" + } +} diff --git a/src/main/resources/assets/colourful_portals/models/item/portal_fluid_bucket.json b/src/main/resources/assets/colourful_portals/models/item/portal_fluid_bucket.json new file mode 100644 index 0000000..1e1c1b7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/models/item/portal_fluid_bucket.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "colourful_portals:item/portal_fluid_bucket" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_black.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_black.png new file mode 100644 index 0000000..3007238 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_black.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_black.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_black.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_black.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_blue.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_blue.png new file mode 100644 index 0000000..f5a61ee Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_blue.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_blue.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_blue.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_blue.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_brown.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_brown.png new file mode 100644 index 0000000..24bffae Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_brown.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_brown.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_brown.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_brown.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_cyan.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_cyan.png new file mode 100644 index 0000000..a6fe03f Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_cyan.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_cyan.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_cyan.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_cyan.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_gray.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_gray.png new file mode 100644 index 0000000..18a6745 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_gray.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_gray.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_gray.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_gray.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_green.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_green.png new file mode 100644 index 0000000..72475b5 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_green.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_green.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_green.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_green.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_blue.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_blue.png new file mode 100644 index 0000000..7e0570a Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_blue.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_blue.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_blue.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_blue.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_gray.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_gray.png new file mode 100644 index 0000000..4bc53ab Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_gray.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_gray.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_gray.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_light_gray.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_lime.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_lime.png new file mode 100644 index 0000000..3da73e6 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_lime.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_lime.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_lime.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_lime.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_magenta.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_magenta.png new file mode 100644 index 0000000..4257a2d Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_magenta.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_magenta.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_magenta.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_magenta.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_orange.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_orange.png new file mode 100644 index 0000000..cc1069e Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_orange.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_orange.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_orange.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_orange.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_pink.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_pink.png new file mode 100644 index 0000000..53ce2f1 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_pink.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_pink.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_pink.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_pink.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_purple.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_purple.png new file mode 100644 index 0000000..29b5f15 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_purple.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_purple.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_purple.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_purple.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_red.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_red.png new file mode 100644 index 0000000..4212dae Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_red.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_red.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_red.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_red.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_white.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_white.png new file mode 100644 index 0000000..2946068 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_white.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_white.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_white.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_white.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_yellow.png b/src/main/resources/assets/colourful_portals/textures/block/portal_block_yellow.png new file mode 100644 index 0000000..84221ba Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_block_yellow.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_block_yellow.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_block_yellow.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_block_yellow.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_still.png b/src/main/resources/assets/colourful_portals/textures/block/portal_still.png new file mode 100644 index 0000000..3007238 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/block/portal_still.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/block/portal_still.png.mcmeta b/src/main/resources/assets/colourful_portals/textures/block/portal_still.png.mcmeta new file mode 100644 index 0000000..ef624e7 --- /dev/null +++ b/src/main/resources/assets/colourful_portals/textures/block/portal_still.png.mcmeta @@ -0,0 +1,6 @@ +{ + "animation": { + "interpolate": true, + "frametime": 12 + } +} \ No newline at end of file diff --git a/src/main/resources/assets/colourful_portals/textures/item/colour_blob_bright.png b/src/main/resources/assets/colourful_portals/textures/item/colour_blob_bright.png new file mode 100644 index 0000000..cf3521f Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/item/colour_blob_bright.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/item/colour_blob_dark.png b/src/main/resources/assets/colourful_portals/textures/item/colour_blob_dark.png new file mode 100644 index 0000000..338a944 Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/item/colour_blob_dark.png differ diff --git a/src/main/resources/assets/colourful_portals/textures/item/portal_fluid_bucket.png b/src/main/resources/assets/colourful_portals/textures/item/portal_fluid_bucket.png new file mode 100644 index 0000000..c2cdf0a Binary files /dev/null and b/src/main/resources/assets/colourful_portals/textures/item/portal_fluid_bucket.png differ diff --git a/src/main/resources/colourful_portals.mixins.json b/src/main/resources/colourful_portals.mixins.json new file mode 100644 index 0000000..ea48216 --- /dev/null +++ b/src/main/resources/colourful_portals.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "package": "quimufu.colourful_portals.mixin", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "BlockChangeMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "plugin": "quimufu.colourful_portals.MixinConfig", + "client": [ + "InterpolationMixin", + "SodiumFluidRendererMixin" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/black_dyes.json b/src/main/resources/data/c/tags/items/black_dyes.json new file mode 100644 index 0000000..c5f93d3 --- /dev/null +++ b/src/main/resources/data/c/tags/items/black_dyes.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "values": + [ + "minecraft:black_dye", + "minecraft:ink_sac" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/blue_dyes.json b/src/main/resources/data/c/tags/items/blue_dyes.json new file mode 100644 index 0000000..dcfe75c --- /dev/null +++ b/src/main/resources/data/c/tags/items/blue_dyes.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "values": + [ + "minecraft:blue_dye", + "minecraft:lapis_lazuli" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/brown_dyes.json b/src/main/resources/data/c/tags/items/brown_dyes.json new file mode 100644 index 0000000..b096e2f --- /dev/null +++ b/src/main/resources/data/c/tags/items/brown_dyes.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "values": + [ + "minecraft:brown_dye", + "minecraft:cocoa_beans" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/cyan_dyes.json b/src/main/resources/data/c/tags/items/cyan_dyes.json new file mode 100644 index 0000000..40d2ea6 --- /dev/null +++ b/src/main/resources/data/c/tags/items/cyan_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:cyan_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/gems.json b/src/main/resources/data/c/tags/items/gems.json new file mode 100644 index 0000000..110b5ec --- /dev/null +++ b/src/main/resources/data/c/tags/items/gems.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": + [ + "minecraft:diamond", + "minecraft:emerald", + "minecraft:amethyst_shard" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/gray_dyes.json b/src/main/resources/data/c/tags/items/gray_dyes.json new file mode 100644 index 0000000..f13796b --- /dev/null +++ b/src/main/resources/data/c/tags/items/gray_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:gray_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/green_dyes.json b/src/main/resources/data/c/tags/items/green_dyes.json new file mode 100644 index 0000000..c8e8d95 --- /dev/null +++ b/src/main/resources/data/c/tags/items/green_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:green_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/light_blue_dyes.json b/src/main/resources/data/c/tags/items/light_blue_dyes.json new file mode 100644 index 0000000..2416f12 --- /dev/null +++ b/src/main/resources/data/c/tags/items/light_blue_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:light_blue_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/light_gray_dyes.json b/src/main/resources/data/c/tags/items/light_gray_dyes.json new file mode 100644 index 0000000..83d7344 --- /dev/null +++ b/src/main/resources/data/c/tags/items/light_gray_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:light_gray_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/lime_dyes.json b/src/main/resources/data/c/tags/items/lime_dyes.json new file mode 100644 index 0000000..e37db9b --- /dev/null +++ b/src/main/resources/data/c/tags/items/lime_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:lime_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/magenta_dyes.json b/src/main/resources/data/c/tags/items/magenta_dyes.json new file mode 100644 index 0000000..dfc6fe8 --- /dev/null +++ b/src/main/resources/data/c/tags/items/magenta_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:magenta_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/orange_dyes.json b/src/main/resources/data/c/tags/items/orange_dyes.json new file mode 100644 index 0000000..7921b2e --- /dev/null +++ b/src/main/resources/data/c/tags/items/orange_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:orange_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/pink_dyes.json b/src/main/resources/data/c/tags/items/pink_dyes.json new file mode 100644 index 0000000..c16d7bf --- /dev/null +++ b/src/main/resources/data/c/tags/items/pink_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:pink_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/purple_dyes.json b/src/main/resources/data/c/tags/items/purple_dyes.json new file mode 100644 index 0000000..883dac5 --- /dev/null +++ b/src/main/resources/data/c/tags/items/purple_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:purple_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/red_dyes.json b/src/main/resources/data/c/tags/items/red_dyes.json new file mode 100644 index 0000000..1497f86 --- /dev/null +++ b/src/main/resources/data/c/tags/items/red_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:red_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/slime_balls.json b/src/main/resources/data/c/tags/items/slime_balls.json new file mode 100644 index 0000000..1ee190b --- /dev/null +++ b/src/main/resources/data/c/tags/items/slime_balls.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:slime_ball" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/white_dyes.json b/src/main/resources/data/c/tags/items/white_dyes.json new file mode 100644 index 0000000..319da3b --- /dev/null +++ b/src/main/resources/data/c/tags/items/white_dyes.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "values": + [ + "minecraft:white_dye", + "minecraft:bone_meal" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/yellow_dyes.json b/src/main/resources/data/c/tags/items/yellow_dyes.json new file mode 100644 index 0000000..2537d30 --- /dev/null +++ b/src/main/resources/data/c/tags/items/yellow_dyes.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": + [ + "minecraft:yellow_dye" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/colourful_portals/recipes/colour_blob_bright.json b/src/main/resources/data/colourful_portals/recipes/colour_blob_bright.json new file mode 100644 index 0000000..f75046b --- /dev/null +++ b/src/main/resources/data/colourful_portals/recipes/colour_blob_bright.json @@ -0,0 +1,37 @@ +{ + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + { + "tag": "c:white_dyes" + }, + { + "tag": "c:yellow_dyes" + }, + { + "tag": "c:pink_dyes" + }, + { + "tag": "c:light_green_dyes" + }, + { + "tag": "c:light_gray_dyes" + }, + { + "tag": "c:orange_dyes" + }, + { + "tag": "c:light_blue_dyes" + }, + { + "tag": "c:purple_dyes" + }, + { + "tag": "c:slime_balls" + } + ], + "result": { + "count": 1, + "item": "colourful_portals:colour_blob_bright" + } +} \ No newline at end of file diff --git a/src/main/resources/data/colourful_portals/recipes/colour_blob_dark.json b/src/main/resources/data/colourful_portals/recipes/colour_blob_dark.json new file mode 100644 index 0000000..60e4d1d --- /dev/null +++ b/src/main/resources/data/colourful_portals/recipes/colour_blob_dark.json @@ -0,0 +1,37 @@ +{ + "type": "minecraft:crafting_shapeless", + "category": "misc", + "ingredients": [ + { + "tag": "c:black_dyes" + }, + { + "tag": "c:gray_dyes" + }, + { + "tag": "c:blue_dyes" + }, + { + "tag": "c:red_dyes" + }, + { + "tag": "c:magenta_dyes" + }, + { + "tag": "c:brown_dyes" + }, + { + "tag": "c:green_dyes" + }, + { + "tag": "c:cyan_dyes" + }, + { + "tag": "c:slime_balls" + } + ], + "result": { + "count": 1, + "item": "colourful_portals:colour_blob_dark" + } +} \ No newline at end of file diff --git a/src/main/resources/data/colourful_portals/recipes/portal_fluid_bucket.json b/src/main/resources/data/colourful_portals/recipes/portal_fluid_bucket.json new file mode 100644 index 0000000..f22a37a --- /dev/null +++ b/src/main/resources/data/colourful_portals/recipes/portal_fluid_bucket.json @@ -0,0 +1,34 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "W": { + "item": "colourful_portals:colour_blob_bright" + }, + "B": { + "item": "colourful_portals:colour_blob_dark" + }, + "U": { + "item": "minecraft:bucket" + }, + "G": { + "item": "minecraft:glowstone_dust" + }, + "E": { + "item": "minecraft:ender_eye" + }, + "D": { + "tag": "c:gems" + } + }, + "pattern": [ + "GEG", + "EDE", + "BUW" + ], + "result": { + "count": 1, + "item": "colourful_portals:portal_fluid_bucket" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..29e0450 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,54 @@ +{ + "schemaVersion": 1, + "id": "colourful_portals", + "version": "${version}", + "name": "Colourful Portals", + "description": "Fabric version of The Colourful Portal mod based on Immersive Portals!", + "authors": [ + "QuImUfu" + ], + "contact": { + "homepage": "https://modrinth.com/mod/colourful-portals-reimagined", + "sources": "https://merl.dnshome.de/git/QuImUfu/colourful_portals" + }, + "license": "MIT", + "icon": "assets/colourful_portals/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "quimufu.colourful_portals.ColourfulPortalsMod" + ], + "client": [ + "quimufu.colourful_portals.ColourfulPortalsMod" + ], + "cardinal-components": [ + "quimufu.colourful_portals.Components" + ], + "tweed4:config": [ + "quimufu.colourful_portals.config.ColourfulPortalConfig" + ] + }, + "custom": { + "cardinal-components": [ + "colourful_portals:portal_list", + "colourful_portals:portal_candidate_list" + ], + "sodium:options": { + "mixin.features.texture_updates": false + } + }, + "mixins": [ + "colourful_portals.mixins.json" + ], + "depends": { + "fabricloader": ">=0.14.19", + "imm_ptl_core": "v2.6.9-mc1.19.4", + "q_misc_util": "v2.6.9-mc1.19.4", + "minecraft": "~1.19.4", + "java": ">=17", + "fabric-api": "*" + }, + "suggests": { + "another-mod": "*" + } +} \ No newline at end of file