Compare commits

..

16 Commits

Author SHA1 Message Date
a9ce715750 fix sodium compat 2024-10-11 20:57:23 +02:00
7aefd13755 finish update and fix immersive portals integration 2024-10-11 20:17:22 +02:00
562a16c4f8 WIP: colourful pearls 2024-10-11 16:06:40 +02:00
b4582640b7 add colourful pearl and air 2024-08-13 08:20:13 +02:00
01ba9ca366 add alternative portalRendering system 2024-07-29 21:25:00 +02:00
4ee1a066ac fix recipes 2024-07-25 21:07:35 +02:00
9545a46171 fixes... lots of fixes 2024-07-23 22:49:14 +02:00
a654eec852 fix config 2024-07-11 22:54:38 +02:00
0d1e607206 fix deps 2024-06-29 17:41:34 +02:00
4aa2643a76 update for 1.21, drop hard dependency on immersive portals 2024-06-25 22:40:27 +02:00
b1cd170c60 fix portal fluid look 2024-03-31 14:20:43 +02:00
ec041f9269 update dependencies, more sane alpha interpolation implementation 2024-03-31 11:11:10 +02:00
265d71ae67 fix imports 2023-06-27 23:48:33 +02:00
82007f627f test and fix multiplayer 2023-06-27 23:47:05 +02:00
83ada2096e bump version 2023-06-27 22:38:19 +02:00
6a02d60f6a update minecraft and dependency versions 2023-06-27 22:09:07 +02:00
273 changed files with 4435 additions and 804 deletions

View File

@@ -1,6 +1,6 @@
plugins { plugins {
id "com.modrinth.minotaur" version "2.+" id "com.modrinth.minotaur" version "2.+"
id 'fabric-loom' version '1.2-SNAPSHOT' id 'fabric-loom' version '1.7-SNAPSHOT'
id 'maven-publish' id 'maven-publish'
} }
@@ -18,8 +18,8 @@ repositories {
// See https://docs.gradle.org/current/userguide/declaring_repositories.html // See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories. // for more information about repositories.
maven { maven {
name = 'Ladysnake Mods' name = "Ladysnake Mods"
url = 'https://ladysnake.jfrog.io/artifactory/mods' url = 'https://maven.ladysnake.org/releases'
} }
maven { maven {
url = "https://api.modrinth.com/maven" url = "https://api.modrinth.com/maven"
@@ -50,48 +50,27 @@ dependencies {
// modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}" // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
// Dependency of Immersive Portals Core: modImplementation ("com.github.iPortalTeam:ImmersivePortalsMod:${project.immersive_portals_version}")
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)
}
include(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 // Replace modImplementation with modApi if you expose components in your own API
include(modImplementation("dev.onyxstudios.cardinal-components-api:cardinal-components-level:${project.cardinal_components_version}")) { modImplementation "org.ladysnake.cardinal-components-api:cardinal-components-base:${project.cca_version}"
exclude(group: "net.fabricmc.fabric-loader") // Includes Cardinal Components API as a Jar-in-Jar dependency (optional but recommended)
transitive(false) include "org.ladysnake.cardinal-components-api:cardinal-components-base:${project.cca_version}"
}
api("com.github.LlamaLad7:MixinExtras:0.2.0-beta.4") // Replace modImplementation with modApi if you expose components in your own API
annotationProcessor("com.github.LlamaLad7:MixinExtras:0.2.0-beta.4") modImplementation "org.ladysnake.cardinal-components-api:cardinal-components-level:${project.cca_version}"
// Includes Cardinal Components API as a Jar-in-Jar dependency (optional but recommended)
include "org.ladysnake.cardinal-components-api:cardinal-components-level:${project.cca_version}"
include(modApi(platform("de.siphalor.tweed4:tweed4-bom-$project.minecraft_version_major:$project.tweed_version"))) //Config lib
// Pick any modules you want to use, e.g.: modImplementation "maven.modrinth:midnightlib:${project.midnightlib_version}"
include(modApi("de.siphalor.tweed4:tweed4-base-$project.minecraft_version_major")) include "maven.modrinth:midnightlib:${project.midnightlib_version}"
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"))
include(modApi("de.siphalor.tweed4:tweed4-tailor-screen-$project.minecraft_version_major"))
modImplementation("maven.modrinth:sodium:${project.sodium_version}")
}
modImplementation("me.shedaniel.cloth:cloth-config-fabric:$project.cloth_config_version") { loom {
exclude(group: "net.fabricmc.fabric-api") accessWidenerPath = file("src/main/resources/colourful_portals.accesswidener")
}
modImplementation "maven.modrinth:sodium:mc${project.minecraft_version}-${project.sodium_version}"
} }
configurations.include.transitive = true configurations.include.transitive = true
@@ -109,7 +88,7 @@ processResources {
} }
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
it.options.release = 17 it.options.release = 21
} }
java { java {
@@ -118,8 +97,8 @@ java {
// If you remove this line, sources will not be generated. // If you remove this line, sources will not be generated.
withSourcesJar() withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_21
} }
jar { jar {
@@ -136,7 +115,7 @@ modrinth {
versionType = "beta" versionType = "beta"
dependencies = [ dependencies = [
new ModDependency('P7dR8mSH', 'required'), //required dependency on Fabric API new ModDependency('P7dR8mSH', 'required'), //required dependency on Fabric API
new ModDependency('zJpHMkdD', 'required') //required dependency on Immersive Portals new ModDependency('AANobbMI', 'optional'), //compatible with Sodium
] ]
} }
// configure the maven publication // configure the maven publication

View File

@@ -4,21 +4,20 @@ org.gradle.parallel=true
# Fabric Properties # Fabric Properties
# check these on https://fabricmc.net/develop # check these on https://fabricmc.net/develop
minecraft_version=1.20 minecraft_version=1.21.1
yarn_mappings=1.20+build.1 minecraft_version_major=1.21
loader_version=0.14.21 yarn_mappings=1.21.1+build.3
minecraft_version_major=1.20 loader_version=0.16.7
# Mod Properties # Mod Properties
mod_version=0.9.2 mod_version=0.9.7
maven_group=quimufu.colourful-portals maven_group=quimufu.colourful-portals
archives_base_name=colourful-portals archives_base_name=colourful-portals
# Dependencies # Dependencies
fabric_version=0.83.0+1.20 fabric_version=0.105.0+1.21.1
cardinal_components_version=5.2.1 cca_version = 6.1.1
immersive_portals_version_short=3.0.1 immersive_portals_version_short=6.0.3
immersive_portals_version=v3.0.1-mc1.20 immersive_portals_version=v6.0.3-mc1.21.1
tweed_version=1.3.0+mc1.20-pre1 sodium_version=mc1.21-0.5.11
sodium_version=0.4.10 midnightlib_version=1.6.3-fabric
cloth_config_version=11.0.99

Binary file not shown.

View File

@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

24
gradlew vendored
View File

@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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 # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,7 +83,8 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -130,10 +131,13 @@ location of your Java installation."
fi fi
else else
JAVACMD=java 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. if ! command -v java >/dev/null 2>&1
then
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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@@ -198,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command:
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# shell script including quotes and variable substitutions, so put them in # and any embedded shellness will be escaped.
# double quotes to make sure that they get re-expanded; and # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# * put everything else in single quotes, so that it's not re-expanded. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View File

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@@ -0,0 +1,153 @@
package quimufu.colourful_portals;
import net.minecraft.block.*;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.item.*;
import net.minecraft.item.tooltip.TooltipType;
import net.minecraft.network.packet.s2c.play.ParticleS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.state.StateManager;
import net.minecraft.state.property.EnumProperty;
import net.minecraft.text.Text;
import net.minecraft.util.DyeColor;
import net.minecraft.util.Hand;
import net.minecraft.util.ItemActionResult;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
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.Nullable;
import java.util.List;
public class ColourfulAirBlock extends Block implements FluidFillable {
public static final EnumProperty<DyeColor> DYE_COLOR = EnumProperty.of("colour", DyeColor.class);
public ColourfulAirBlock(Settings settings) {
super(settings);
this.setDefaultState(this.stateManager.getDefaultState().with(DYE_COLOR, DyeColor.BLACK));
}
@Override
protected ItemActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) {
if(stack.getItem() == Items.GLASS_BOTTLE){
ItemStack filledBottle = getPickStack(world, pos, state);
player.setStackInHand(hand, ItemUsage.exchangeStack(stack, player, filledBottle));
if(!world.isClient){
world.setBlockState(pos, Blocks.AIR.getDefaultState());
}
return ItemActionResult.success(world.isClient);
}
return ItemActionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION;
}
@Override
protected void onBlockAdded(BlockState state, World world, BlockPos pos, BlockState oldState, boolean notify) {
scheduleTick(world, pos, true);
}
@Override
public void appendTooltip(ItemStack stack,
Item.TooltipContext context,
List<Text> tooltip, TooltipType options) {
DyeColor color = stack.getOrDefault(DataComponentTypes.BASE_COLOR, DyeColor.BLACK);
tooltip.add(Text.translatable("color.minecraft." + color.getName())
.withColor(color.getEntityColor()));
}
@Override
public ItemStack getPickStack(WorldView world, BlockPos pos, BlockState state) {
ItemStack stack = ColourfulPortalsMod.COLOURFUL_AIR_BOTTLE_ITEM.getDefaultStack();
stack.set(DataComponentTypes.BASE_COLOR, state.get(DYE_COLOR));
return stack;
}
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos blockPos, ShapeContext shapeContext) {
if (shapeContext.isHolding(Items.DEBUG_STICK)
|| shapeContext.isHolding(ColourfulPortalsMod.COLOURFUL_AIR_BOTTLE_ITEM)
|| shapeContext.isHolding(Items.GLASS_BOTTLE)) {
return VoxelShapes.fullCube();
}
return VoxelShapes.empty();
}
@Override
protected BlockRenderType getRenderType(BlockState state) {
return BlockRenderType.INVISIBLE;
}
@Override
protected void randomTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
scheduleTick(world, pos, false);
}
@Override
protected void scheduledTick(BlockState state, ServerWorld world, BlockPos pos, Random random) {
DyeColor dyeColor = state.get(DYE_COLOR);
for (ServerPlayerEntity player : world.getPlayers()) {
double x = pos.getX() + .5D;
double y = pos.getY() + .5D;
double z = pos.getZ() + .5D;
ParticleS2CPacket particleS2CPacket = new ParticleS2CPacket(
ColourfulPortalsMod.COLOURFUL_AIR_PARTICLE_BY_COLOUR.get(dyeColor),
true,
x, y, z,
0.25F, 0.25F, 0.25F,
0,
1);
world.sendToPlayerIfNearby(player, true, x, y, z, particleS2CPacket);
}
scheduleTick(world, pos, false);
}
private void scheduleTick(WorldAccess world, BlockPos pos, boolean asap) {
if (!world.isClient() && !world.getBlockTickScheduler().isQueued(pos, this)) {
if(asap){
world.scheduleBlockTick(pos, this, 2);
} else if (world.getClosestPlayer(pos.getX(),pos.getY(),pos.getZ(),512,false) != null) {
world.scheduleBlockTick(pos, this, 20);
}
}
}
@Override
public BlockState getPlacementState(ItemPlacementContext ctx) {
DyeColor color = ctx.getStack().getOrDefault(DataComponentTypes.BASE_COLOR, DyeColor.BLACK);
return this.getDefaultState().with(DYE_COLOR, color);
}
public BlockState getRandomState(org.joml.Random random) {
DyeColor color = DyeColor.values()[random.nextInt(DyeColor.values().length)];
return this.getDefaultState().with(DYE_COLOR, color);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(DYE_COLOR);
}
@Override
public boolean canFillWithFluid(@Nullable PlayerEntity player, BlockView world, BlockPos pos, BlockState state, Fluid fluid) {
return false;
}
@Override
public boolean tryFillWithFluid(WorldAccess world, BlockPos pos, BlockState state, FluidState fluidState) {
return false;
}
}

View File

@@ -1,48 +1,93 @@
package quimufu.colourful_portals; package quimufu.colourful_portals;
import net.fabricmc.api.ClientModInitializer; import eu.midnightdust.lib.config.MidnightConfig;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroupEntries; import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroupEntries;
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.particle.v1.FabricParticleTypes;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.enums.Instrument; import net.minecraft.block.enums.NoteBlockInstrument;
import net.minecraft.client.render.RenderLayer; import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.entity.SpawnGroup;
import net.minecraft.item.*; import net.minecraft.item.*;
import net.minecraft.particle.SimpleParticleType;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry; import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.BlockSoundGroup;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.DyeColor;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.Rarity; import net.minecraft.util.Rarity;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import quimufu.colourful_portals.client.PortalFluidRenderHandler;
import quimufu.colourful_portals.config.ColourfulPortalConfig; import quimufu.colourful_portals.config.ColourfulPortalConfig;
import quimufu.colourful_portals.entity.ColourfulPearlEntity;
import quimufu.colourful_portals.general_util.WeightedSelector;
import quimufu.colourful_portals.item.ColourfulAirBottleItem;
import quimufu.colourful_portals.item.ColourfulPearlItem;
import quimufu.colourful_portals.portal.*;
import quimufu.colourful_portals.portal_fluid.PortalFluid; import quimufu.colourful_portals.portal_fluid.PortalFluid;
import quimufu.colourful_portals.portal_fluid.PortalFluidBlock; import quimufu.colourful_portals.portal_fluid.PortalFluidBlock;
import quimufu.colourful_portals.portal_fluid.PortalFluidBucketItem; import quimufu.colourful_portals.portal_fluid.PortalFluidBucketItem;
import java.util.HashSet; import java.util.*;
public class ColourfulPortalsMod implements ModInitializer, ClientModInitializer { import static quimufu.colourful_portals.Components.PORTAL_CANDIDATE_LIST;
import static quimufu.colourful_portals.Components.PORTAL_LIST;
public class ColourfulPortalsMod implements ModInitializer {
public static final String MOD_ID = "colourful_portals"; public static final String MOD_ID = "colourful_portals";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
public static final HashSet<Identifier> PORTAL_BLOCKS = new HashSet<>(); public static final HashSet<Identifier> PORTAL_BLOCKS = new HashSet<>();
public static final PortalBlock PORTAL_BLOCK = new PortalBlock(FabricBlockSettings.create().instrument(Instrument.HAT).sounds(BlockSoundGroup.GLASS).strength(-1.0f, 3600000.8f).dropsNothing().nonOpaque().luminance(15).allowsSpawning(ColourfulPortalsMod::never).solidBlock(ColourfulPortalsMod::never).suffocates(ColourfulPortalsMod::never).blockVision(ColourfulPortalsMod::never).ticksRandomly()); public static final ColourfulAirBlock COLOURFUL_AIR = new ColourfulAirBlock(AbstractBlock.Settings.create().instrument(NoteBlockInstrument.HAT).sounds(BlockSoundGroup.GLASS).strength(-1.0f, 3600000.8f).dropsNothing().noCollision().luminance((bs) -> 7).allowsSpawning(ColourfulPortalsMod::never).solidBlock(ColourfulPortalsMod::never).suffocates(ColourfulPortalsMod::never).blockVision(ColourfulPortalsMod::never).ticksRandomly());
public static final BlockItem PORTAL_BLOCK_ITEM = new BlockItem(PORTAL_BLOCK, new FabricItemSettings().rarity(Rarity.EPIC)); public static final ColourfulAirBottleItem COLOURFUL_AIR_BOTTLE_ITEM = new ColourfulAirBottleItem(COLOURFUL_AIR, new Item.Settings().rarity(Rarity.RARE).component(DataComponentTypes.BASE_COLOR, DyeColor.BLACK));
public static final Item BLOB_DARK = new Item(new FabricItemSettings());
public static final Item BLOB_BRIGHT = new Item(new FabricItemSettings()); public static final EnumMap<DyeColor, SimpleParticleType> COLOURFUL_AIR_PARTICLE_BY_COLOUR;
static {
COLOURFUL_AIR_PARTICLE_BY_COLOUR = new EnumMap<>(DyeColor.class);
for (DyeColor dyeColor : DyeColor.values()) {
COLOURFUL_AIR_PARTICLE_BY_COLOUR.put(dyeColor, FabricParticleTypes.simple(true));
}
}
public static final PortalBlock PORTAL_BLOCK = new PortalBlock(AbstractBlock.Settings.create().instrument(NoteBlockInstrument.HAT).sounds(BlockSoundGroup.GLASS).strength(-1.0f, 3600000.8f).dropsNothing().noCollision().luminance((bs) -> 15).allowsSpawning(ColourfulPortalsMod::never).solidBlock(ColourfulPortalsMod::never).suffocates(ColourfulPortalsMod::never).blockVision(ColourfulPortalsMod::never).ticksRandomly());
public static final BlockItem PORTAL_BLOCK_ITEM = new BlockItem(PORTAL_BLOCK, new Item.Settings().rarity(Rarity.EPIC));
public static final Item BLOB_DARK = new Item(new Item.Settings());
public static final Item BLOB_BRIGHT = new Item(new Item.Settings());
public static final Identifier COLOURFUL_PEARL_ID = Identifier.of(MOD_ID, "colourful_pearl");
public static final Item COLOURFUL_PEARL_ITEM = new ColourfulPearlItem(new Item.Settings().maxCount(8));
public static final TagKey<Block> COLOURFUL_PEARL_REPLACEABLE_BLOCK_TAG = TagKey.of(RegistryKeys.BLOCK, Identifier.of(MOD_ID, "colourful_pearl_replaceable"));
public static final WeightedSelector<Identifier> DIMENSION_WEIGHTS_COLOURFUL_PEARL = new WeightedSelector<>();
public static final EntityType<ColourfulPearlEntity> COLOURFUL_PEARL_ENTITY_TYPE = EntityType.Builder.<ColourfulPearlEntity>create(ColourfulPearlEntity::new, SpawnGroup.MISC).dimensions(0.25f, 0.25f).maxTrackingRange(4).trackingTickInterval(10).build(COLOURFUL_PEARL_ID.toString());
public static final TagKey<EntityType<?>> COLOURFUL_PEARL_NOT_TELEPORTABLE = TagKey.of(RegistryKeys.ENTITY_TYPE, Identifier.of(MOD_ID, "pearl_not_teleportable"));
public static final SoundEvent TELEPORT_AWAY_SOUND = SoundEvent.of(Identifier.of(MOD_ID, "entity.colourful_pearl.teleport_away"));
public static final PortalFluid PORTAL_FLUID = new PortalFluid(); public static final PortalFluid PORTAL_FLUID = new PortalFluid();
public static final PortalFluidBlock PORTAL_FLUID_BLOCk = new PortalFluidBlock(PORTAL_FLUID, FabricBlockSettings.create().sounds(BlockSoundGroup.field_44608).luminance(15).noCollision().strength(100.0f).dropsNothing()); public static final PortalFluidBlock PORTAL_FLUID_BLOCk = new PortalFluidBlock(PORTAL_FLUID, AbstractBlock.Settings.create().sounds(BlockSoundGroup.INTENTIONALLY_EMPTY).luminance((bs) -> 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)); public static final BucketItem PORTAL_FLUID_BUCKET_ITEM = new PortalFluidBucketItem(PORTAL_FLUID, new Item.Settings().recipeRemainder(Items.BUCKET).maxCount(1).rarity(Rarity.RARE));
public static PortalManager PORTAL_MANAGER;
public static final RegistryKey<Registry<PrioritizedPortalLinkingSystemBuilder>> PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY_KEY = RegistryKey.ofRegistry(Identifier.of(MOD_ID, "portal_linking_system"));
public static final Registry<PrioritizedPortalLinkingSystemBuilder> PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY = FabricRegistryBuilder.createSimple(PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY_KEY).buildAndRegister();
private static boolean never(BlockState blockState, BlockView blockView, BlockPos blockPos, EntityType<?> entityType) { private static boolean never(BlockState blockState, BlockView blockView, BlockPos blockPos, EntityType<?> entityType) {
return false; return false;
@@ -55,23 +100,39 @@ public class ColourfulPortalsMod implements ModInitializer, ClientModInitializer
@Override @Override
public void onInitialize() { public void onInitialize() {
LOGGER.info("Colourizing Portals..."); LOGGER.info("Colouring Portals...");
MidnightConfig.init(MOD_ID, ColourfulPortalConfig.class);
ColourfulPortalConfig.registerListener(this::onConfigUpdate);
for (String id : ColourfulPortalConfig.portalBlocks.keySet()) { onConfigUpdate();
PORTAL_BLOCKS.add(Identifier.tryParse(id)); if (hasImmPtl()) {
Registry.register(PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY, ImmersivePortalsLinkingSystem.IMMERSIVE_PORTALS_LINKING_SYSTEM, new PrioritizedPortalLinkingSystemBuilder(ImmersivePortalsLinkingSystem::new, () -> ColourfulPortalConfig.disableImmersivePortals ? -1 : 100));
}
for (Map.Entry<DyeColor, SimpleParticleType> entry : COLOURFUL_AIR_PARTICLE_BY_COLOUR.entrySet()) {
Registry.register(Registries.PARTICLE_TYPE, Identifier.of(MOD_ID, entry.getKey().getName() + "_colourful_air_sparkle"), entry.getValue());
} }
Identifier identifier = new Identifier(MOD_ID, "portal_block"); Registry.register(PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY, DefaultLinkingSystem.DEFAULT_LINKING_SYSTEM, new PrioritizedPortalLinkingSystemBuilder(DefaultLinkingSystem::new, () -> 90));
Identifier identifier = Identifier.of(MOD_ID, "portal_block");
Registry.register(Registries.BLOCK, identifier, PORTAL_BLOCK); Registry.register(Registries.BLOCK, identifier, PORTAL_BLOCK);
Registry.register(Registries.ITEM, identifier, PORTAL_BLOCK_ITEM); 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.BLOCK, Identifier.of(MOD_ID, "colourful_air"), COLOURFUL_AIR);
Registry.register(Registries.FLUID, new Identifier(MOD_ID, "portal_fluid"), PORTAL_FLUID); Registry.register(Registries.ITEM, Identifier.of(MOD_ID, "colourful_air_bottle"), COLOURFUL_AIR_BOTTLE_ITEM);
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); Registry.register(Registries.ENTITY_TYPE, COLOURFUL_PEARL_ID, COLOURFUL_PEARL_ENTITY_TYPE);
Registry.register(Registries.ITEM, COLOURFUL_PEARL_ID, COLOURFUL_PEARL_ITEM);
Registry.register(Registries.SOUND_EVENT, TELEPORT_AWAY_SOUND.getId(), TELEPORT_AWAY_SOUND);
Registry.register(Registries.ITEM, Identifier.of(MOD_ID, "portal_fluid_bucket"), PORTAL_FLUID_BUCKET_ITEM);
Registry.register(Registries.FLUID, Identifier.of(MOD_ID, "portal_fluid"), PORTAL_FLUID);
Registry.register(Registries.BLOCK, Identifier.of(MOD_ID, "portal_fluid_block"), PORTAL_FLUID_BLOCk);
Registry.register(Registries.ITEM, Identifier.of(MOD_ID, "colour_blob_bright"), BLOB_BRIGHT);
Registry.register(Registries.ITEM, Identifier.of(MOD_ID, "colour_blob_dark"), BLOB_DARK);
ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS) ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS)
.register(ColourfulPortalsMod::addToIngredients); .register(ColourfulPortalsMod::addToIngredients);
@@ -79,9 +140,65 @@ public class ColourfulPortalsMod implements ModInitializer, ClientModInitializer
ItemGroupEvents.modifyEntriesEvent(ItemGroups.TOOLS) ItemGroupEvents.modifyEntriesEvent(ItemGroups.TOOLS)
.register(ColourfulPortalsMod::addTtTools); .register(ColourfulPortalsMod::addTtTools);
ServerLifecycleEvents.SERVER_STARTED.register(this::onServerStarted);
LOGGER.info("Portals Colourful!"); LOGGER.info("Portals Colourful!");
} }
private void onConfigUpdate() {
PORTAL_BLOCKS.clear();
for (String id : ColourfulPortalConfig.getAllPortalBlocks()) {
LOGGER.info(id);
PORTAL_BLOCKS.add(Identifier.tryParse(id));
}
DIMENSION_WEIGHTS_COLOURFUL_PEARL.clear();
for (Map.Entry<String, Integer> weight : ColourfulPortalConfig.pearlDimensionWeights.entrySet()) {
DIMENSION_WEIGHTS_COLOURFUL_PEARL.add(Identifier.of(weight.getKey()), weight.getValue());
}
}
private boolean hasImmPtl() {
try {
Class.forName("qouteall.imm_ptl.core.IPModMain", false, this.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
return false;
}
return true;
}
private void onServerStarted(MinecraftServer minecraftServer) {
PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(minecraftServer.getSaveProperties().getMainWorldProperties());
PortalListComponent portalList = PORTAL_LIST.get(minecraftServer.getSaveProperties().getMainWorldProperties());
PortalLinkingSystem currentLinkingSystem = getCurrentlyConfiguredLinkingSystem(minecraftServer);
if (!Objects.equals(currentLinkingSystem.getLinkingSystemId(), portalList.lastPortalLinkingSystemId()) || currentLinkingSystem.needsReInit()) {
PrioritizedPortalLinkingSystemBuilder prevSystem;
if ((prevSystem = PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY.get(portalList.lastPortalLinkingSystemId())) != null) {
PortalLinkingSystem prevLS = prevSystem.portalLinkingSystemBuilder().build(minecraftServer);
for (Identifier blockId : portalList.getBlockIds()) {
for (PortalRepresentation portal : portalList.getPortals(blockId)) {
prevLS.unLinkPortal(portal);
}
}
}
for (Identifier blockId : portalList.getBlockIds()) {
currentLinkingSystem.linkPortals(portalList.getPortals(blockId));
}
}
PORTAL_MANAGER = new PortalManager(currentLinkingSystem, portalCandidateList, portalList);
PORTAL_MANAGER.onLoad(minecraftServer);
}
private PortalLinkingSystem getCurrentlyConfiguredLinkingSystem(MinecraftServer minecraftServer) {
return PORTAL_LINKING_SYSTEM_BUILDER_REGISTRY
.stream()
.filter(prio -> prio.priority().getAsInt() >= 0)
.max(Comparator.comparing(prio -> prio.priority().getAsInt()))
.map(PrioritizedPortalLinkingSystemBuilder::portalLinkingSystemBuilder)
.orElse(DefaultLinkingSystem::new)
.build(minecraftServer);
}
private static void addTtTools(FabricItemGroupEntries entries) { private static void addTtTools(FabricItemGroupEntries entries) {
entries.add(PORTAL_FLUID_BUCKET_ITEM); entries.add(PORTAL_FLUID_BUCKET_ITEM);
} }
@@ -91,10 +208,4 @@ public class ColourfulPortalsMod implements ModInitializer, ClientModInitializer
content.add(BLOB_DARK); 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());
}
} }

View File

@@ -1,22 +1,22 @@
package quimufu.colourful_portals; package quimufu.colourful_portals;
import dev.onyxstudios.cca.api.v3.component.ComponentKey; import org.ladysnake.cca.api.v3.component.ComponentKey;
import dev.onyxstudios.cca.api.v3.component.ComponentRegistry; import org.ladysnake.cca.api.v3.component.ComponentRegistry;
import dev.onyxstudios.cca.api.v3.level.LevelComponentFactoryRegistry; import org.ladysnake.cca.api.v3.level.LevelComponentFactoryRegistry;
import dev.onyxstudios.cca.api.v3.level.LevelComponentInitializer; import org.ladysnake.cca.api.v3.level.LevelComponentInitializer;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import quimufu.colourful_portals.portal.PortalListComponent; import quimufu.colourful_portals.portal.PortalListComponent;
public class Components implements LevelComponentInitializer { public class Components implements LevelComponentInitializer {
public static final ComponentKey<PortalListComponent> PORTAL_LIST = public static final ComponentKey<PortalListComponent> PORTAL_LIST =
ComponentRegistry.getOrCreate(new Identifier(ColourfulPortalsMod.MOD_ID, "portal_list"), PortalListComponent.class); ComponentRegistry.getOrCreate(Identifier.of(ColourfulPortalsMod.MOD_ID, "portal_list"), PortalListComponent.class);
public static final ComponentKey<PortalListComponent> PORTAL_CANDIDATE_LIST = public static final ComponentKey<PortalListComponent> PORTAL_CANDIDATE_LIST =
ComponentRegistry.getOrCreate(new Identifier(ColourfulPortalsMod.MOD_ID, "portal_candidate_list"), PortalListComponent.class); ComponentRegistry.getOrCreate(Identifier.of(ColourfulPortalsMod.MOD_ID, "portal_candidate_list"), PortalListComponent.class);
@Override @Override
public void registerLevelComponentFactories(LevelComponentFactoryRegistry registry) { public void registerLevelComponentFactories(LevelComponentFactoryRegistry registry) {
registry.register(PORTAL_LIST, PortalListComponent::new); registry.register(PORTAL_LIST, worldProperties1 -> new PortalListComponent());
registry.register(PORTAL_CANDIDATE_LIST, PortalListComponent::new); registry.register(PORTAL_CANDIDATE_LIST, worldProperties -> new PortalListComponent());
} }
} }

View File

@@ -14,7 +14,7 @@ public class MixinConfig implements IMixinConfigPlugin {
private static final Supplier<Boolean> TRUE = () -> true; private static final Supplier<Boolean> TRUE = () -> true;
private static final Map<String, Supplier<Boolean>> CONDITIONS = Map.of( private static final Map<String, Supplier<Boolean>> CONDITIONS = Map.of(
"quimufu.colourful_portals.mixin.SodiumFluidRendererMixin", () -> FabricLoader.getInstance().isModLoaded("sodium") "quimufu.colourful_portals.client.mixin.SodiumFluidRendererMixin", () -> FabricLoader.getInstance().isModLoaded("sodium")
); );
@Override @Override

View File

@@ -4,6 +4,7 @@ import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.FluidFillable; import net.minecraft.block.FluidFillable;
import net.minecraft.block.ShapeContext; import net.minecraft.block.ShapeContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid; import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState; import net.minecraft.fluid.FluidState;
import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemPlacementContext;
@@ -14,14 +15,15 @@ import net.minecraft.state.property.EnumProperty;
import net.minecraft.state.property.Properties; import net.minecraft.state.property.Properties;
import net.minecraft.util.BlockRotation; import net.minecraft.util.BlockRotation;
import net.minecraft.util.DyeColor; import net.minecraft.util.DyeColor;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.*;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShape;
import net.minecraft.util.shape.VoxelShapes; import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldAccess; import net.minecraft.world.WorldAccess;
import quimufu.colourful_portals.portal.PortalManager; import org.jetbrains.annotations.Nullable;
import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_MANAGER;
public class PortalBlock extends Block implements FluidFillable { public class PortalBlock extends Block implements FluidFillable {
@@ -63,19 +65,18 @@ public class PortalBlock extends Block implements FluidFillable {
if (shapeContext.isHolding(Items.DEBUG_STICK) if (shapeContext.isHolding(Items.DEBUG_STICK)
|| shapeContext.isHolding(ColourfulPortalsMod.PORTAL_BLOCK_ITEM) || shapeContext.isHolding(ColourfulPortalsMod.PORTAL_BLOCK_ITEM)
|| shapeContext.isHolding(ColourfulPortalsMod.PORTAL_FLUID_BUCKET_ITEM)) { || shapeContext.isHolding(ColourfulPortalsMod.PORTAL_FLUID_BUCKET_ITEM)) {
return getShape(state);
}
return VoxelShapes.empty();
}
public VoxelShape getShape(BlockState state) {
return switch (state.get(AXIS)) { return switch (state.get(AXIS)) {
case Z -> Z_AABB; case Z -> Z_AABB;
case Y -> Y_AABB; case Y -> Y_AABB;
default -> X_AABB; default -> X_AABB;
}; };
} }
return VoxelShapes.empty();
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return VoxelShapes.empty();
}
@Override @Override
public BlockState rotate(BlockState state, BlockRotation rotation) { public BlockState rotate(BlockState state, BlockRotation rotation) {
@@ -100,7 +101,6 @@ public class PortalBlock extends Block implements FluidFillable {
public BlockState getStateWith(DyeColor color, Direction.Axis axis) { public BlockState getStateWith(DyeColor color, Direction.Axis axis) {
return this.getDefaultState().with(AXIS, axis).with(DYE_COLOR, color); return this.getDefaultState().with(AXIS, axis).with(DYE_COLOR, color);
} }
@Override @Override
@@ -119,15 +119,16 @@ public class PortalBlock extends Block implements FluidFillable {
} }
@Override @Override
public boolean canFillWithFluid(BlockView world, BlockPos pos, BlockState state, Fluid fluid) { public boolean canFillWithFluid(@Nullable PlayerEntity player, BlockView world, BlockPos pos, BlockState state, Fluid fluid) {
if (((World) world).isClient() && fluid == ColourfulPortalsMod.PORTAL_FLUID) { if (((World) world).isClient() && fluid == ColourfulPortalsMod.PORTAL_FLUID) {
return true; return true;
} }
return fluid == ColourfulPortalsMod.PORTAL_FLUID return fluid == ColourfulPortalsMod.PORTAL_FLUID
&& (world instanceof ServerWorld) && (world instanceof ServerWorld)
&& PortalManager.canExtend((ServerWorld) world, pos); && PORTAL_MANAGER.canExtend((ServerWorld) world, pos);
} }
@Override @Override
public boolean tryFillWithFluid(WorldAccess world, BlockPos pos, BlockState state, FluidState fluidState) { public boolean tryFillWithFluid(WorldAccess world, BlockPos pos, BlockState state, FluidState fluidState) {
if (world.isClient() && fluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID)) { if (world.isClient() && fluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID)) {
@@ -135,6 +136,6 @@ public class PortalBlock extends Block implements FluidFillable {
} }
return fluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID) return fluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID)
&& (world instanceof ServerWorld) && (world instanceof ServerWorld)
&& PortalManager.extend((ServerWorld) world, pos); && PORTAL_MANAGER.extend((ServerWorld) world, pos);
} }
} }

View File

@@ -0,0 +1,57 @@
package quimufu.colourful_portals.client;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry;
import net.minecraft.client.item.ModelPredicateProviderRegistry;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.entity.FlyingItemEntityRenderer;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.particle.SimpleParticleType;
import net.minecraft.util.DyeColor;
import net.minecraft.util.Identifier;
import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.client.particle.ColourfulAirSparkleParticle;
import quimufu.colourful_portals.client.rendering.fluid.CommonPortalFluidRenderer;
import quimufu.colourful_portals.client.rendering.fluid.CommonPortalFluidRendererV1;
import quimufu.colourful_portals.client.rendering.fluid.CommonPortalFluidRendererV2;
import quimufu.colourful_portals.client.rendering.fluid.PortalFluidRenderHandler;
import quimufu.colourful_portals.config.ColourfulPortalConfig;
import java.util.Map;
public class ColourfulPortalsModClient implements ClientModInitializer {
public static ThreadLocal<CommonPortalFluidRenderer> FLUID_RENDERER = ThreadLocal.withInitial(CommonPortalFluidRendererV2::new);
public void onInitializeClient() {
BlockRenderLayerMap.INSTANCE.putBlock(ColourfulPortalsMod.PORTAL_BLOCK, RenderLayer.getTranslucent());
BlockRenderLayerMap.INSTANCE.putBlock(ColourfulPortalsMod.COLOURFUL_AIR, RenderLayer.getTranslucent());
BlockRenderLayerMap.INSTANCE.putFluid(ColourfulPortalsMod.PORTAL_FLUID, RenderLayer.getTranslucent());
FluidRenderHandlerRegistry.INSTANCE.register(ColourfulPortalsMod.PORTAL_FLUID, new PortalFluidRenderHandler());
EntityRendererRegistry.register(ColourfulPortalsMod.COLOURFUL_PEARL_ENTITY_TYPE, FlyingItemEntityRenderer::new);
ColourfulPortalConfig.registerListener(this::onConfigUpdate);
onConfigUpdate();
for (Map.Entry<DyeColor, SimpleParticleType> entry : ColourfulPortalsMod.COLOURFUL_AIR_PARTICLE_BY_COLOUR.entrySet()) {
ParticleFactoryRegistry.getInstance()
.register(entry.getValue(), ColourfulAirSparkleParticle::create);
}
ModelPredicateProviderRegistry.register(ColourfulPortalsMod.COLOURFUL_AIR_BOTTLE_ITEM, Identifier.of(ColourfulPortalsMod.MOD_ID,"color_id"),((stack, world, entity, seed) -> stack.getOrDefault(DataComponentTypes.BASE_COLOR, DyeColor.BLACK).getId()));
}
private void onConfigUpdate() {
if (ColourfulPortalConfig.blockyPortalFluid) {
FLUID_RENDERER = ThreadLocal.withInitial(CommonPortalFluidRendererV1::new);
} else {
FLUID_RENDERER = ThreadLocal.withInitial(CommonPortalFluidRendererV2::new);
}
}
}

View File

@@ -1,91 +0,0 @@
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);
}
}

View File

@@ -0,0 +1,50 @@
package quimufu.colourful_portals.client.mixin;
import net.minecraft.client.texture.Animator;
import net.minecraft.client.texture.SpriteContents;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import quimufu.colourful_portals.client.rendering.AlphaBlendingAnimator;
import quimufu.colourful_portals.client.rendering.AlphaInterpolationHolder;
import java.util.List;
@Mixin(SpriteContents.Animation.class)
public class AnimationMixin implements AlphaInterpolationHolder {
@Shadow
@Final
private boolean interpolation;
@Unique
private SpriteContents parent;
@Unique
private boolean interpolateAlpha = false;
@Inject(method = "<init>", at = @At("RETURN"))
public void assignParent(SpriteContents parent, List frames, int frameCount, boolean interpolation, CallbackInfo ci) {
this.parent = parent;
}
public void colourful_portals$setInterpolateAlpha(boolean interpolateAlpha) {
this.interpolateAlpha = interpolateAlpha;
}
public boolean colourful_portals$isInterpolateAlpha() {
return interpolateAlpha;
}
@Inject(at = @At("RETURN"), method = "createAnimator()Lnet/minecraft/client/texture/Animator;", cancellable = true)
public void createAnimator(CallbackInfoReturnable<Animator> cir) {
if (this.interpolateAlpha && this.interpolation) {
cir.setReturnValue(new AlphaBlendingAnimator(parent, (SpriteContents.Animation) (Object) this));
cir.cancel();
}
}
}

View File

@@ -0,0 +1,20 @@
package quimufu.colourful_portals.client.mixin;
import net.minecraft.client.resource.metadata.AnimationResourceMetadata;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import quimufu.colourful_portals.client.rendering.AlphaInterpolationHolder;
@Mixin(AnimationResourceMetadata.class)
public class AnimationResourceMetadataMixin implements AlphaInterpolationHolder {
@Unique
private boolean interpolateAlpha = false;
public void colourful_portals$setInterpolateAlpha(boolean interpolateAlpha) {
this.interpolateAlpha = interpolateAlpha;
}
public boolean colourful_portals$isInterpolateAlpha() {
return interpolateAlpha;
}
}

View File

@@ -0,0 +1,24 @@
package quimufu.colourful_portals.client.mixin;
import com.google.gson.JsonObject;
import net.minecraft.client.resource.metadata.AnimationResourceMetadata;
import net.minecraft.client.resource.metadata.AnimationResourceMetadataReader;
import net.minecraft.util.JsonHelper;
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.rendering.AlphaInterpolationHolder;
@Mixin(AnimationResourceMetadataReader.class)
public class AnimationResourceMetadataReaderMixin {
@Inject(at = @At("RETURN"), method = "fromJson(Lcom/google/gson/JsonObject;)Lnet/minecraft/client/resource/metadata/AnimationResourceMetadata;", cancellable = true)
void extractInterpolateAlpha(JsonObject jsonObject, CallbackInfoReturnable<AnimationResourceMetadata> cir) {
boolean interpolateAlpha = JsonHelper.getBoolean(jsonObject, "interpolateAlpha", false);
AnimationResourceMetadata returnValue = cir.getReturnValue();
((AlphaInterpolationHolder)returnValue).colourful_portals$setInterpolateAlpha(interpolateAlpha);
cir.setReturnValue(returnValue);
cir.cancel();
}
}

View File

@@ -0,0 +1,11 @@
package quimufu.colourful_portals.client.mixin;
import net.minecraft.client.MinecraftClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(MinecraftClient.class)
public interface MinecraftClientAccessor {
@Accessor
void setItemUseCooldown(int itemUseCooldown);
}

View File

@@ -0,0 +1,30 @@
package quimufu.colourful_portals.client.mixin;
import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers;
import me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.FluidRenderer;
import me.jellysquid.mods.sodium.client.world.WorldSlice;
import net.minecraft.fluid.FluidState;
import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
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.client.rendering.fluid.SodiumPortalFluidRenderHandler;
import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_FLUID;
@Mixin(FluidRenderer.class)
public class SodiumFluidRendererMixin {
@Unique
private final ThreadLocal<SodiumPortalFluidRenderHandler> sodiumPortalFluidRenderHandler = ThreadLocal.withInitial(SodiumPortalFluidRenderHandler::new);
@Inject(at = @At("HEAD"), method = "render", cancellable = true, remap = false)
private void init(WorldSlice world, FluidState fluidState, BlockPos blockPos, BlockPos offset, ChunkBuildBuffers buffers, CallbackInfo ci) {
if (fluidState.isOf(PORTAL_FLUID)) {
sodiumPortalFluidRenderHandler.get().render(world, fluidState, blockPos, offset, buffers);
ci.cancel();
}
}
}

View File

@@ -0,0 +1,50 @@
package quimufu.colourful_portals.client.mixin;
import net.minecraft.client.resource.metadata.AnimationResourceMetadata;
import net.minecraft.client.texture.SpriteContents;
import net.minecraft.client.texture.SpriteDimensions;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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.rendering.AlphaInterpolationHolder;
@Mixin(SpriteContents.class)
public abstract class SpriteContentsMixin {
@Unique
private final ThreadLocal<Boolean> redirect = ThreadLocal.withInitial(() -> true);
@Shadow
@Final
@Nullable
private SpriteContents.@Nullable Animation animation;
@Shadow
public abstract boolean isPixelTransparent(int frame, int x, int y);
@Inject(at = @At("RETURN"), method = "createAnimation(Lnet/minecraft/client/texture/SpriteDimensions;IILnet/minecraft/client/resource/metadata/AnimationResourceMetadata;)Lnet/minecraft/client/texture/SpriteContents$Animation;", cancellable = true)
void passInterpolateAlpha(SpriteDimensions dimensions, int imageWidth, int imageHeight, AnimationResourceMetadata metadata, CallbackInfoReturnable<AlphaInterpolationHolder> cir) {
AlphaInterpolationHolder returnValue = cir.getReturnValue();
if (returnValue != null) {
returnValue.colourful_portals$setInterpolateAlpha(((AlphaInterpolationHolder) metadata).colourful_portals$isInterpolateAlpha());
cir.setReturnValue(returnValue);
cir.cancel();
}
}
@Inject(at = @At("RETURN"), method = "isPixelTransparent(III)Z", cancellable = true)
void correctIsPixelTransparent(int frame, int x, int y, CallbackInfoReturnable<Boolean> cir) {
if (redirect.get() && animation != null && ((AlphaInterpolationHolder) animation).colourful_portals$isInterpolateAlpha()) {
redirect.set(false);
cir.setReturnValue(this.isPixelTransparent(frame, x, y) || this.isPixelTransparent(frame + 1, x, y));
redirect.set(true);
cir.cancel();
}
}
}

View File

@@ -0,0 +1,84 @@
package quimufu.colourful_portals.client.particle;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.particle.v1.FabricSpriteProvider;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.option.ParticlesMode;
import net.minecraft.client.particle.ParticleFactory;
import net.minecraft.client.particle.ParticleTextureSheet;
import net.minecraft.client.particle.SpriteBillboardParticle;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.particle.ParticleGroup;
import net.minecraft.particle.SimpleParticleType;
import org.joml.Random;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import static net.minecraft.client.option.ParticlesMode.*;
@Environment(value = EnvType.CLIENT)
public class ColourfulAirSparkleParticle extends SpriteBillboardParticle {
public static ParticleGroup COLOURFUL_AIR_GROUP = new ParticleGroup(0) {
private static final Random rnd = new Random(747906141);
private static final EnumMap<ParticlesMode, Integer> COUNTS = new EnumMap<>(
Map.of(
ALL, 1 << 12,
DECREASED, 1 << 11,
MINIMAL, 1 << 8
)
);
@Override
public int getMaxCount() {
Integer count = COUNTS
.getOrDefault(MinecraftClient.getInstance().options.getParticles().getValue(), 0);
return count / 4 + rnd.nextInt((count * 3) / 4);
}
};
private final FabricSpriteProvider spriteProvider;
private final int variant;
protected ColourfulAirSparkleParticle(ClientWorld clientWorld,
double posX, double posY, double posZ,
FabricSpriteProvider spriteProvider
) {
super(clientWorld, posX, posY, posZ);
this.spriteProvider = spriteProvider;
int variants = spriteProvider.getSprites().size() / 2;
variant = random.nextInt(variants);
setSprite(spriteProvider.getSprites().get(variant * 2));
setMaxAge(20 + random.nextInt(20) + random.nextInt(20));
gravityStrength = 0;
}
@Override
public void tick() {
super.tick();
if (age == maxAge / 2) {
setSprite(spriteProvider.getSprites().get(variant * 2 + 1));
}
}
@Override
public Optional<ParticleGroup> getGroup() {
return Optional.ofNullable(COLOURFUL_AIR_GROUP);
}
@Override
public ParticleTextureSheet getType() {
return ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT;
}
public static ParticleFactory<SimpleParticleType> create(FabricSpriteProvider provider) {
return (parameters, world, x, y, z, velocityX, velocityY, velocityZ) ->
new ColourfulAirSparkleParticle(world, x, y, z, provider);
}
}

View File

@@ -0,0 +1,112 @@
package quimufu.colourful_portals.client.rendering;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.texture.Animator;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.texture.SpriteContents;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* This code is mostly copies from minecraft source. It is owned by mojang.
*/
public class AlphaBlendingAnimator implements Animator {
int frame;
int currentTime;
final SpriteContents spriteContents;
final SpriteContents.Animation animation;
@Nullable
private final AlphaBlendingInterpolation interpolation;
public AlphaBlendingAnimator(SpriteContents spriteContents, SpriteContents.Animation animation) {
this.spriteContents = spriteContents;
this.animation = animation;
this.interpolation = new AlphaBlendingInterpolation();
}
@Override
public void tick(int x, int y) {
++this.currentTime;
SpriteContents.AnimationFrame animationFrame = this.animation.frames.get(this.frame);
if (this.currentTime >= animationFrame.time) {
int i = animationFrame.index;
this.frame = (this.frame + 1) % this.animation.frames.size();
this.currentTime = 0;
int j = this.animation.frames.get((int)this.frame).index;
if (i != j) {
this.animation.upload(x, y, j);
}
} else if (this.interpolation != null) {
if (!RenderSystem.isOnRenderThread()) {
RenderSystem.recordRenderCall(() -> this.interpolation.apply(x, y, this));
} else {
this.interpolation.apply(x, y, this);
}
}
}
@Override
public void close() {
if (this.interpolation != null) {
this.interpolation.close();
}
}
public class AlphaBlendingInterpolation
implements AutoCloseable {
private final NativeImage[] images;
AlphaBlendingInterpolation() {
this.images = new NativeImage[spriteContents.mipmapLevelsImages.length];
for (int i = 0; i < this.images.length; ++i) {
int j = spriteContents.getWidth() >> i;
int k = spriteContents.getHeight() >> i;
this.images[i] = new NativeImage(j, k, false);
}
}
void apply(int x, int y, AlphaBlendingAnimator animator) {
SpriteContents.Animation animation = animator.animation;
List<SpriteContents.AnimationFrame> list = animation.frames;
SpriteContents.AnimationFrame animationFrame = list.get(animator.frame);
double d = 1.0 - (double)animator.currentTime / (double)animationFrame.time;
int i = animationFrame.index;
int j = list.get((animator.frame + 1) % list.size()).index;
if (i != j) {
for (int k = 0; k < this.images.length; ++k) {
int l = spriteContents.getWidth() >> k;
int m = spriteContents.getHeight() >> k;
for (int n = 0; n < m; ++n) {
for (int o = 0; o < l; ++o) {
int p = this.getPixelColor(animation, i, k, o, n);
int q = this.getPixelColor(animation, j, k, o, n);
int a = this.lerp(d, p >> 24 & 0xFF, q >> 24 & 0xFF);
int r = this.lerp(d, p >> 16 & 0xFF, q >> 16 & 0xFF);
int s = this.lerp(d, p >> 8 & 0xFF, q >> 8 & 0xFF);
int t = this.lerp(d, p & 0xFF, q & 0xFF);
this.images[k].setColor(o, n, a << 24 | r << 16 | s << 8 | t);
}
}
}
spriteContents.upload(x, y, 0, 0, this.images);
}
}
private int getPixelColor(SpriteContents.Animation animation, int frameIndex, int layer, int x, int y) {
return spriteContents.mipmapLevelsImages[layer].getColor(x + (animation.getFrameX(frameIndex) * spriteContents.getWidth() >> layer), y + (animation.getFrameY(frameIndex) * spriteContents.getHeight() >> layer));
}
private int lerp(double delta, int to, int from) {
return (int)(delta * (double)to + (1.0 - delta) * (double)from);
}
@Override
public void close() {
for (NativeImage nativeImage : this.images) {
nativeImage.close();
}
}
}
}

View File

@@ -0,0 +1,8 @@
package quimufu.colourful_portals.client.rendering;
public interface AlphaInterpolationHolder {
public void colourful_portals$setInterpolateAlpha(boolean interpolateAlpha);
public boolean colourful_portals$isInterpolateAlpha();
}

View File

@@ -0,0 +1,14 @@
package quimufu.colourful_portals.client.rendering.fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockRenderView;
import org.joml.Vector3f;
public interface CommonPortalFluidRenderer {
boolean render(BlockRenderView world,
FluidState fluidState,
BlockPos pos,
Vector3f offset,
VertexEater vertexEater);
}

View File

@@ -1,4 +1,4 @@
package quimufu.colourful_portals.client; package quimufu.colourful_portals.client.rendering.fluid;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler; import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandler;
import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry;
@@ -8,15 +8,25 @@ import net.minecraft.fluid.FluidState;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import quimufu.colourful_portals.ColourfulPortalsMod; import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.portal_fluid.PortalFluid; import quimufu.colourful_portals.portal_fluid.PortalFluid;
public class CommonPortalFluidRenderer { public class CommonPortalFluidRendererV1 implements CommonPortalFluidRenderer {
public static final Quaternionf IDENTITY = new Quaternionf();
private Sprite sprite = null; private Sprite sprite = null;
private final float[] uIu2IvIv2 = new float[4]; private final float[] uIu2IvIv2 = new float[4];
public boolean render(BlockRenderView world, FluidState fluidState, BlockPos pos, BlockPos offset, VertexEater vertexEater) { @Override
public boolean render(BlockRenderView world,
FluidState fluidState,
BlockPos pos,
Vector3f offset,
VertexEater vertexEater) {
offset.sub(0.5f,0.5f,0.5f);
FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getFluid()); FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getFluid());
sprite = handler.getFluidSprites(null, null, fluidState)[0]; sprite = handler.getFluidSprites(null, null, fluidState)[0];
@@ -81,10 +91,10 @@ public class CommonPortalFluidRenderer {
} }
calcSquishedUVs(axis, 1.f - offsetInSquishedDir, offsetInSquishedDir, direction, (amount & 1) == 1); calcSquishedUVs(axis, 1.f - offsetInSquishedDir, offsetInSquishedDir, direction, (amount & 1) == 1);
} else { } else {
uIu2IvIv2[0] = sprite.getFrameU(0.); uIu2IvIv2[0] = sprite.getFrameU(0.F);
uIu2IvIv2[1] = sprite.getFrameU(16.); uIu2IvIv2[1] = sprite.getFrameU(1.F);
uIu2IvIv2[2] = sprite.getFrameV(0.); uIu2IvIv2[2] = sprite.getFrameV(0.F);
uIu2IvIv2[3] = sprite.getFrameV(16.); uIu2IvIv2[3] = sprite.getFrameV(1.F);
} }
drawSide(vertexEater, x, y, z, x2, y2, z2, direction, uIu2IvIv2[0], uIu2IvIv2[2], uIu2IvIv2[1], uIu2IvIv2[3], offset); drawSide(vertexEater, x, y, z, x2, y2, z2, direction, uIu2IvIv2[0], uIu2IvIv2[2], uIu2IvIv2[1], uIu2IvIv2[3], offset);
@@ -98,60 +108,60 @@ public class CommonPortalFluidRenderer {
switch (axis) { switch (axis) {
case X -> { case X -> {
if (odd) { if (odd) {
uIu2IvIv2[0] = sprite.getFrameU(0.5 + squishedBottom * 16D); uIu2IvIv2[0] = sprite.getFrameU(1.F / 32.F + squishedBottom);
uIu2IvIv2[1] = sprite.getFrameU(0.5 + squishedTop * 16D); uIu2IvIv2[1] = sprite.getFrameU(1.F / 32.F + squishedTop);
} else { } else {
uIu2IvIv2[0] = sprite.getFrameU(0. + squishedBottom * 16D); uIu2IvIv2[0] = sprite.getFrameU(0.F + squishedBottom);
uIu2IvIv2[1] = sprite.getFrameU(squishedTop * 16D); uIu2IvIv2[1] = sprite.getFrameU(squishedTop);
} }
uIu2IvIv2[2] = sprite.getFrameV(0.); uIu2IvIv2[2] = sprite.getFrameV(0.F);
uIu2IvIv2[3] = sprite.getFrameV(16.); uIu2IvIv2[3] = sprite.getFrameV(1.F);
} }
case Z -> { case Z -> {
if (direction.getAxis() == Direction.Axis.Y) { if (direction.getAxis() == Direction.Axis.Y) {
if (odd) { if (odd) {
uIu2IvIv2[2] = sprite.getFrameV(0.5 + squishedBottom * 16D); uIu2IvIv2[2] = sprite.getFrameV(1.F / 32.F + squishedBottom);
uIu2IvIv2[3] = sprite.getFrameV(0.5 + squishedTop * 16D); uIu2IvIv2[3] = sprite.getFrameV(1.F / 32.F + squishedTop);
} else { } else {
uIu2IvIv2[2] = sprite.getFrameV(squishedBottom * 16D); uIu2IvIv2[2] = sprite.getFrameV(squishedBottom);
uIu2IvIv2[3] = sprite.getFrameV(squishedTop * 16D); uIu2IvIv2[3] = sprite.getFrameV(squishedTop);
} }
uIu2IvIv2[0] = sprite.getFrameU(0.); uIu2IvIv2[0] = sprite.getFrameU(0.F);
uIu2IvIv2[1] = sprite.getFrameU(16.); uIu2IvIv2[1] = sprite.getFrameU(1.F);
} else { } else {
if (odd) { if (odd) {
uIu2IvIv2[0] = sprite.getFrameU(0.5 + squishedBottom * 16D); uIu2IvIv2[0] = sprite.getFrameU(1.F / 32.F + squishedBottom);
uIu2IvIv2[1] = sprite.getFrameU(0.5 + squishedTop * 16D); uIu2IvIv2[1] = sprite.getFrameU(1.F / 32.F + squishedTop);
} else { } else {
uIu2IvIv2[0] = sprite.getFrameU(squishedBottom * 16D); uIu2IvIv2[0] = sprite.getFrameU(squishedBottom);
uIu2IvIv2[1] = sprite.getFrameU(squishedTop * 16D); uIu2IvIv2[1] = sprite.getFrameU(squishedTop);
} }
uIu2IvIv2[2] = sprite.getFrameV(0.); uIu2IvIv2[2] = sprite.getFrameV(0.F);
uIu2IvIv2[3] = sprite.getFrameV(16.); uIu2IvIv2[3] = sprite.getFrameV(1.F);
} }
} }
case Y -> { case Y -> {
if (odd) { if (odd) {
uIu2IvIv2[2] = sprite.getFrameV(0.5 + squishedBottom * 16D); uIu2IvIv2[2] = sprite.getFrameV(1.F / 32.F + squishedBottom);
uIu2IvIv2[3] = sprite.getFrameV(0.5 + squishedTop * 16D); uIu2IvIv2[3] = sprite.getFrameV(1.F / 32.F + squishedTop);
} else { } else {
uIu2IvIv2[2] = sprite.getFrameV(squishedBottom * 16D); uIu2IvIv2[2] = sprite.getFrameV(squishedBottom);
uIu2IvIv2[3] = sprite.getFrameV(squishedTop * 16D); uIu2IvIv2[3] = sprite.getFrameV(squishedTop);
} }
uIu2IvIv2[0] = sprite.getFrameU(0.); uIu2IvIv2[0] = sprite.getFrameU(0.F);
uIu2IvIv2[1] = sprite.getFrameU(16.); uIu2IvIv2[1] = sprite.getFrameU(1.F);
} }
default -> throw new IllegalStateException("Unexpected value: " + axis); 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) { private void drawSilvers(VertexEater buffers, Direction direction, Direction.Axis axis, float offsetInSquishedDir, float neighbourOffsetInSquishedDir, Vector3f offset, boolean odd) {
float x = 0F; float x = 0F;
float x2 = 1F; float x2 = 1F;
@@ -190,62 +200,56 @@ public class CommonPortalFluidRenderer {
z2 = 1F - offsetInSquishedDir; z2 = 1F - offsetInSquishedDir;
} }
} }
calcSquishedUVs(axis, 1.f - neighbourOffsetInSquishedDir,1.f - offsetInSquishedDir , direction, odd); 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); 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) { 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, Vector3f offset) {
vertexConsumer.setSprite(sprite); vertexConsumer.setSprite(sprite);
switch (direction) { switch (direction) {
case DOWN -> { case DOWN -> {
vertexConsumer.eatVertex(x, y, z, frameU, frameV2); vertexConsumer.eatVertex(x, y, z, frameU, frameV2);
vertexConsumer.eatVertex( x2, y, z, frameU2, frameV2); vertexConsumer.eatVertex(x2, y, z, frameU2, frameV2);
vertexConsumer.eatVertex( x2, y, z2, frameU2, frameV); vertexConsumer.eatVertex(x2, y, z2, frameU2, frameV);
vertexConsumer.eatVertex( x, y, z2, frameU, frameV); vertexConsumer.eatVertex(x, y, z2, frameU, frameV);
vertexConsumer.drawQuad(offset, direction); vertexConsumer.drawQuad(offset, IDENTITY, direction);
} }
case UP -> { case UP -> {
vertexConsumer.eatVertex( x, y2, z2, frameU, frameV2); vertexConsumer.eatVertex(x, y2, z2, frameU, frameV2);
vertexConsumer.eatVertex( x2, y2, z2, frameU2, frameV2); vertexConsumer.eatVertex(x2, y2, z2, frameU2, frameV2);
vertexConsumer.eatVertex( x2, y2, z, frameU2, frameV); vertexConsumer.eatVertex(x2, y2, z, frameU2, frameV);
vertexConsumer.eatVertex( x, y2, z, frameU, frameV); vertexConsumer.eatVertex(x, y2, z, frameU, frameV);
vertexConsumer.drawQuad(offset, direction); vertexConsumer.drawQuad(offset, IDENTITY, direction);
} }
case NORTH -> { case NORTH -> {
vertexConsumer.eatVertex( x, y2, z, frameU, frameV); vertexConsumer.eatVertex(x, y2, z, frameU, frameV);
vertexConsumer.eatVertex( x2, y2, z, frameU2, frameV); vertexConsumer.eatVertex(x2, y2, z, frameU2, frameV);
vertexConsumer.eatVertex( x2, y, z, frameU2, frameV2); vertexConsumer.eatVertex(x2, y, z, frameU2, frameV2);
vertexConsumer.eatVertex( x, y, z, frameU, frameV2); vertexConsumer.eatVertex(x, y, z, frameU, frameV2);
vertexConsumer.drawQuad(offset, direction); vertexConsumer.drawQuad(offset, IDENTITY, direction);
} }
case SOUTH -> { case SOUTH -> {
vertexConsumer.eatVertex( x, y, z2, frameU, frameV); vertexConsumer.eatVertex(x, y, z2, frameU, frameV);
vertexConsumer.eatVertex( x2, y, z2, frameU2, frameV); vertexConsumer.eatVertex(x2, y, z2, frameU2, frameV);
vertexConsumer.eatVertex( x2, y2, z2, frameU2, frameV2); vertexConsumer.eatVertex(x2, y2, z2, frameU2, frameV2);
vertexConsumer.eatVertex( x, y2, z2, frameU, frameV2); vertexConsumer.eatVertex(x, y2, z2, frameU, frameV2);
vertexConsumer.drawQuad(offset, direction); vertexConsumer.drawQuad(offset, IDENTITY, direction);
} }
case WEST -> { case WEST -> {
vertexConsumer.eatVertex( x, y, z, frameU, frameV); vertexConsumer.eatVertex(x, y, z, frameU, frameV);
vertexConsumer.eatVertex( x, y, z2, frameU2, frameV); vertexConsumer.eatVertex(x, y, z2, frameU2, frameV);
vertexConsumer.eatVertex( x, y2, z2, frameU2, frameV2); vertexConsumer.eatVertex(x, y2, z2, frameU2, frameV2);
vertexConsumer.eatVertex( x, y2, z, frameU, frameV2); vertexConsumer.eatVertex(x, y2, z, frameU, frameV2);
vertexConsumer.drawQuad(offset, direction); vertexConsumer.drawQuad(offset, IDENTITY, direction);
} }
case EAST -> { case EAST -> {
vertexConsumer.eatVertex( x2, y2, z, frameU, frameV); vertexConsumer.eatVertex(x2, y2, z, frameU, frameV);
vertexConsumer.eatVertex( x2, y2, z2, frameU2, frameV); vertexConsumer.eatVertex(x2, y2, z2, frameU2, frameV);
vertexConsumer.eatVertex( x2, y, z2, frameU2, frameV2); vertexConsumer.eatVertex(x2, y, z2, frameU2, frameV2);
vertexConsumer.eatVertex( x2, y, z, frameU, frameV2); vertexConsumer.eatVertex(x2, y, z, frameU, frameV2);
vertexConsumer.drawQuad(offset, direction); vertexConsumer.drawQuad(offset, IDENTITY, 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);
}
} }

View File

@@ -0,0 +1,349 @@
package quimufu.colourful_portals.client.rendering.fluid;
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 org.joml.Quaternionf;
import org.joml.Vector3f;
import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.portal_fluid.NullableAxis;
import quimufu.colourful_portals.portal_fluid.PortalFluid;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.Map;
import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER;
public class CommonPortalFluidRendererV2 implements CommonPortalFluidRenderer {
private Sprite sprite = null;
private final float[] heights = new float[9];
private final int[] heightsC = new int[9];
private static final EnumMap<NullableAxis, Quaternionf> quaternions =
new EnumMap<>(Map.of(
NullableAxis.X, new Quaternionf()
.rotationTo(0,1,0,-1,0,0),
NullableAxis.Y, new Quaternionf(),//identity
NullableAxis.Z, new Quaternionf()
.rotationTo(0,1,0,0,0,-1),
NullableAxis.NULL, new Quaternionf()//identity
));
private final int[] amounts = new int[9];
private final float[] quads = new float[]{
//Up
-0.5f, 0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
0.0f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.0f, 0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
0.5f, 0.5f, -0.5f,
0.0f, 0.5f, -0.5f,
-0.5f, 0.5f, 0.5f,
0.0f, 0.5f, 0.5f,
0.0f, 0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
0.0f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.0f,
0.0f, 0.5f, 0.0f,
//Down
-0.5f, -0.5f, -0.5f,
0.0f, -0.5f, -0.5f,
0.0f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.0f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.0f,
0.0f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.0f, -0.5f, 0.0f,
0.0f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
0.0f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.5f,
0.0f, -0.5f, 0.5f,
//NORTH
-0.5f, 0.5f, -0.5f,
0.0f, 0.5f, -0.5f,
0.0f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
0.0f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.0f, -0.5f, -0.5f,
//SOUTH
-0.5f, -0.5f, 0.5f,
0.0f, -0.5f, 0.5f,
0.0f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.0f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.0f, 0.5f, 0.5f,
//WEST
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.0f,
-0.5f, 0.5f, 0.0f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, 0.0f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.0f,
//EAST
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, 0.0f,
0.5f, 0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.0f,
};
public boolean render(BlockRenderView world, FluidState fluidState, BlockPos pos, Vector3f offset, VertexEater vertexEater) {
FluidRenderHandler handler = FluidRenderHandlerRegistry.INSTANCE.get(fluidState.getFluid());
sprite = handler.getFluidSprites(null, null, fluidState)[0];
NullableAxis axis = fluidState.get(PortalFluid.AXIS);
int amount = fluidState.get(PortalFluid.AMOUNT);
if (amount == 0)
return false;
//get neighbours heights:
saveAmounts(world, pos, axis);
//offsets of corners:
Arrays.fill(heights, 0);
Arrays.fill(heightsC, 0);
for (int a = 0; a < 3; a++) {
for (int b = 0; b < 3; b++) {
if (amounts[a * 3 + b] <= -1) {
if ((a == 1) ^ (b == 1)) {
heights[a * 3 + b] += amounts[4] / 32f;
heightsC[a * 3 + b]++;
}
continue;
}
if (a < 2 && b < 2) {
heights[0] += amounts[a * 3 + b] / 32f;
heightsC[0]++;
}
if (a < 2 && b > 0) {
heights[2] += amounts[a * 3 + b] / 32f;
heightsC[2]++;
}
if (a > 0 && b < 2) {
heights[6] += amounts[a * 3 + b] / 32f;
heightsC[6]++;
}
if (a > 0 && b > 0) {
heights[8] += amounts[a * 3 + b] / 32f;
heightsC[8]++;
}
if ((a == 1) ^ (b == 1)) {
heights[a * 3 + b] += amounts[a * 3 + b] / 32f;
heights[a * 3 + b] += amounts[4] / 32f; //always != -1
heightsC[a * 3 + b] += 2;
}
if ((a == 1) && (b == 1)) {
heights[4] = amounts[4] / 32f;//always != -1
heightsC[4]++;
}
}
}
for (int a = 0; a < 9; a++) {
heights[a] = heights[a] / heightsC[a];
}
for (int i = 0; i < quads.length / 3; i++) {
int x = (int) ((quads[i * 3] + 0.5f) * 2);
int z = (int) ((quads[i * 3 + 2] + 0.5f) * 2);
if (quads[i * 3 + 1] > 0f) {
quads[i * 3 + 1] = 0f + heights[x * 3 + z];
} else {
quads[i * 3 + 1] = 0f - heights[x * 3 + z];
}
}
vertexEater.setSprite(sprite);
int endUp = 4;
int endDown = 8;
int endNorth = 10;
int endSouth = 12;
int endWest = 14;
int endEast = 16;
for (Direction value : Direction.values()) {
switch (value) {
case UP -> {
for (int i = 0; i < endUp; i++) {
for (int j = 0; j < 4; j++) {
vertexEater.eatVertex(
quads[i * 12 + j * 3],
quads[i * 12 + j * 3 + 1],
quads[i * 12 + j * 3 + 2],
sprite.getFrameU(quads[i * 12 + j * 3] + 0.5f),
sprite.getFrameV(quads[i * 12 + j * 3 + 2] + 0.5f));
}
vertexEater.drawQuad(offset, quaternions.get(axis), Direction.UP);
}
}
case DOWN -> {
for (int i = endUp; i < endDown; i++) {
for (int j = 0; j < 4; j++) {
vertexEater.eatVertex(
quads[i * 12 + j * 3],
quads[i * 12 + j * 3 + 1],
quads[i * 12 + j * 3 + 2],
sprite.getFrameU(quads[i * 12 + j * 3] + 0.5f),
sprite.getFrameV(quads[i * 12 + j * 3 + 2] + 0.5f));
}
vertexEater.drawQuad(offset, quaternions.get(axis), Direction.DOWN);
}
}
case NORTH -> {
if (amounts[3] >= -1) {
continue;
}
for (int i = endDown; i < endNorth; i++) {
for (int j = 0; j < 4; j++) {
vertexEater.eatVertex(
quads[i * 12 + j * 3],
quads[i * 12 + j * 3 + 1],
quads[i * 12 + j * 3 + 2],
sprite.getFrameU(quads[i * 12 + j * 3] + 0.5f),
sprite.getFrameV(quads[i * 12 + j * 3 + 1] + 0.5f));
}
vertexEater.drawQuad(offset, quaternions.get(axis), Direction.NORTH);
}
}
case SOUTH -> {
if (amounts[5] >= -1) {
continue;
}
for (int i = endNorth; i < endSouth; i++) {
for (int j = 0; j < 4; j++) {
vertexEater.eatVertex(
quads[i * 12 + j * 3],
quads[i * 12 + j * 3 + 1],
quads[i * 12 + j * 3 + 2],
sprite.getFrameU(quads[i * 12 + j * 3] + 0.5f),
sprite.getFrameV(quads[i * 12 + j * 3 + 1] + 0.5f));
}
vertexEater.drawQuad(offset, quaternions.get(axis), Direction.SOUTH);
}
}
case WEST -> {
if (amounts[1] >= -1) {
continue;
}
for (int i = endSouth; i < endWest; i++) {
for (int j = 0; j < 4; j++) {
vertexEater.eatVertex(
quads[i * 12 + j * 3],
quads[i * 12 + j * 3 + 1],
quads[i * 12 + j * 3 + 2],
sprite.getFrameU(quads[i * 12 + j * 3 + 1] + 0.5f),
sprite.getFrameV(quads[i * 12 + j * 3 + 2] + 0.5f));
}
vertexEater.drawQuad(offset, quaternions.get(axis), Direction.WEST);
}
}
case EAST -> {
if (amounts[7] >= -1) {
continue;
}
for (int i = endWest; i < endEast; i++) {
for (int j = 0; j < 4; j++) {
vertexEater.eatVertex(
quads[i * 12 + j * 3],
quads[i * 12 + j * 3 + 1],
quads[i * 12 + j * 3 + 2],
sprite.getFrameU(quads[i * 12 + j * 3 + 1] + 0.5f),
sprite.getFrameV(quads[i * 12 + j * 3 + 2] + 0.5f));
}
vertexEater.drawQuad(offset, quaternions.get(axis), Direction.EAST);
}
}
}
}
return true;
}
private void saveAmounts(BlockRenderView world, BlockPos pos, NullableAxis axis) {
BlockPos.Mutable curr = new BlockPos.Mutable();
switch (axis) {
case NULL -> {
Arrays.fill(amounts, -2);
amounts[4] = 16;
}
case X -> {
for (int y = 0; y < 3; y++) {
for (int z = 0; z < 3; z++) {
saveAmount(world, pos, curr, 0, y - 1, z - 1, y, z);
}
}
}
case Y -> {
for (int x = 0; x < 3; x++) {
for (int z = 0; z < 3; z++) {
saveAmount(world, pos, curr, x - 1, 0, z - 1, x, z);
}
}
}
case Z -> {
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
saveAmount(world, pos, curr, x - 1, y - 1, 0, x, y);
}
}
}
}
}
private void saveAmount(BlockRenderView world, BlockPos pos, BlockPos.Mutable curr, int dx, int dy, int dz, int major, int minor) {
curr.set(pos);
curr.move(dx, dy, dz);
BlockState blockState = world.getBlockState(curr);
FluidState fluidState = blockState
.getFluidState();
if (fluidState.isOf(ColourfulPortalsMod.PORTAL_FLUID)) {
amounts[major * 3 + minor] = fluidState.get(PortalFluid.AMOUNT);
} else {
Direction direction = Direction.fromVector(dx, dy, dz);
if (direction != null && blockState.isSideSolidFullSquare(world, curr, direction.getOpposite())) {
amounts[major * 3 + minor] = -1;
} else {
amounts[major * 3 + minor] = -2;
}
}
}
}

View File

@@ -1,4 +1,4 @@
package quimufu.colourful_portals.client; package quimufu.colourful_portals.client.rendering.fluid;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
@@ -13,18 +13,21 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.world.BlockRenderView; import net.minecraft.world.BlockRenderView;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import quimufu.colourful_portals.ColourfulPortalsMod; import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.client.ColourfulPortalsModClient;
@Environment(value = EnvType.CLIENT) @Environment(value = EnvType.CLIENT)
public class PortalFluidRenderHandler implements FluidRenderHandler, CommonPortalFluidRenderer.VertexEater { public class PortalFluidRenderHandler implements FluidRenderHandler, VertexEater {
Identifier PORTAL_FLUID_STILL = new Identifier(ColourfulPortalsMod.MOD_ID, "block/portal_still"); Identifier PORTAL_FLUID_STILL = Identifier.of(ColourfulPortalsMod.MOD_ID, "block/portal_still");
private final CommonPortalFluidRenderer commonPortalFluidRenderer = new CommonPortalFluidRenderer();
private Sprite sprite = null; private Sprite sprite = null;
private final ThreadLocal<VertexConsumer> vertexConsumer = new ThreadLocal<>(); private final ThreadLocal<VertexConsumer> vertexConsumer = new ThreadLocal<>();
private final ThreadLocal<Integer> i = new ThreadLocal<>(); private final ThreadLocal<Integer> i = new ThreadLocal<>();
private final ThreadLocal<float[][]> vertices = new ThreadLocal<>(); private final ThreadLocal<float[][]> vertices = new ThreadLocal<>();
private final ThreadLocal<Vector3f> tmpVector = ThreadLocal.withInitial(Vector3f::new);
@Override @Override
public Sprite[] getFluidSprites(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) { public Sprite[] getFluidSprites(@Nullable BlockRenderView view, @Nullable BlockPos pos, FluidState state) {
@@ -39,11 +42,13 @@ public class PortalFluidRenderHandler implements FluidRenderHandler, CommonPorta
@Override @Override
public void renderFluid(BlockPos pos, BlockRenderView world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) { public void renderFluid(BlockPos pos, BlockRenderView world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
this.vertexConsumer.set(vertexConsumer); this.vertexConsumer.set(vertexConsumer);
BlockPos offset = new BlockPos(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); Vector3f offset = new Vector3f(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF);
offset.add(0.5f,0.5f,0.5f);
if (vertices.get() == null) { if (vertices.get() == null) {
vertices.set(new float[4][5]); vertices.set(new float[4][5]);
} }
commonPortalFluidRenderer.render(world, fluidState, pos, offset, this); ColourfulPortalsModClient.FLUID_RENDERER.get()
.render(world, fluidState, pos, offset, this);
} }
@@ -71,19 +76,20 @@ public class PortalFluidRenderHandler implements FluidRenderHandler, CommonPorta
} }
@Override @Override
public void drawQuad(BlockPos offset, Direction direction) { public void drawQuad(Vector3f offset, Quaternionf rotation, Direction direction) {
float offX = offset.getX(); float offX = offset.x;
float offY = offset.getY(); float offY = offset.y;
float offZ = offset.getZ(); float offZ = offset.z;
Vector3f f = tmpVector.get();
for (float[] v : vertices.get()) { for (float[] v : vertices.get()) {
f.set(v);
rotation.transform(f);
vertexConsumer.get() vertexConsumer.get()
.vertex(offX + v[0], offY + v[1], offZ + v[2]) .vertex(offX + f.x, offY + f.y, offZ + f.z)
.color(1f, 1f, 1f, 1.0f) .color(1f, 1f, 1f, 1.0f)
.texture(v[3], v[4]) .texture(v[3], v[4])
.light(16) .light(16)
.normal(0.0f, 1.0f, 0.0f) .normal(0.0f, 1.0f, 0.0f);
.next();
} }

View File

@@ -0,0 +1,95 @@
package quimufu.colourful_portals.client.rendering.fluid;
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.render.chunk.compile.ChunkBuildBuffers;
import me.jellysquid.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder;
import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials;
import me.jellysquid.mods.sodium.client.render.chunk.terrain.material.Material;
import me.jellysquid.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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 org.joml.Quaternionf;
import org.joml.Vector3f;
import quimufu.colourful_portals.client.ColourfulPortalsModClient;
@Environment(value = EnvType.CLIENT)
public class SodiumPortalFluidRenderHandler implements VertexEater {
private Sprite sprite = null;
private final ModelQuadViewMutable quad = new ModelQuad();
private final CommonPortalFluidRenderer commonPortalFluidRenderer = ColourfulPortalsModClient.FLUID_RENDERER.get();
private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad();
private int i;
private ChunkModelBuilder chunkModelBuilder;
private Material material;
private Vector3f rotBuffer = new Vector3f();
public SodiumPortalFluidRenderHandler() {
}
public boolean render(BlockRenderView world, FluidState fluidState, BlockPos pos, BlockPos offset, ChunkBuildBuffers buffers) {
material = DefaultMaterials.forFluidState(fluidState);
this.chunkModelBuilder = buffers.get(material);
Vector3f vOffset = new Vector3f(offset.getX(), offset.getY(), offset.getZ());
vOffset.add(0.5f, 0.5f, 0.5f);
return commonPortalFluidRenderer.render(world, fluidState, pos, vOffset, 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(Vector3f offset, Quaternionf rot, Direction direction) {
writeQuad(chunkModelBuilder, material, offset, quad, rot, ModelQuadFacing.fromDirection(direction), false);
}
private void writeQuad(ChunkModelBuilder builder, Material material, Vector3f offset, ModelQuadView quad, Quaternionf rot, ModelQuadFacing facing, boolean flip) {
var vertices = this.vertices;
for (int i = 0; i < 4; i++) {
var out = vertices[flip ? 3 - i : i];
rot.transform(quad.getX(i), quad.getY(i), quad.getZ(i), rotBuffer);
out.x = offset.x + rotBuffer.x;
out.y = offset.y + rotBuffer.y;
out.z = offset.z + rotBuffer.z;
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);
}
var vertexBuffer = builder.getVertexBuffer(facing);
vertexBuffer.push(vertices, material);
}
}

View File

@@ -0,0 +1,15 @@
package quimufu.colourful_portals.client.rendering.fluid;
import net.minecraft.client.texture.Sprite;
import net.minecraft.util.math.Direction;
import org.joml.Quaternionf;
import org.joml.Vector3f;
public interface VertexEater {
void setSprite(Sprite sprite);
void eatVertex(float x, float y, float z, float frameU, float frameV);
void drawQuad(Vector3f offset, Quaternionf quaternionf, Direction direction);
}

View File

@@ -1,44 +1,192 @@
package quimufu.colourful_portals.config; package quimufu.colourful_portals.config;
import com.google.common.base.CaseFormat; import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableMap; import eu.midnightdust.lib.config.MidnightConfig;
import de.siphalor.tweed4.annotated.AConfigEntry; import net.minecraft.registry.RegistryKey;
import de.siphalor.tweed4.annotated.ATweedConfig; import net.minecraft.server.MinecraftServer;
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 net.minecraft.util.DyeColor;
import net.minecraft.world.World;
import quimufu.colourful_portals.ColourfulPortalsMod; import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.util.Procedure;
import java.util.Map; import java.util.*;
@ATweedConfig(serializer = "tweed4:hjson", scope = ConfigScope.GAME, environment = ConfigEnvironment.SERVER, tailors = {"tweed4:lang_json_descriptions", "tweed4:coat", "tweed4:json_schema"}, casing = CaseFormat.LOWER_HYPHEN) public class ColourfulPortalConfig extends MidnightConfig {
//@ClothData(modid = ColourfulPortalsMod.MOD_ID)
public class ColourfulPortalConfig {
@AConfigEntry(comment = @Entry(category = "integrations", name = "Disable Immersive Portals integration")
""" public static boolean disableImmersivePortals = false;
Allows you to add additional blocks as portalBlocks,
assigning them the color of the portal plane. @Comment(category = "portal_colours")
null causes the portals out of that block to have *no* plane. public static Comment explanation1;
Changes in the plane color will only apply to new portals, @Comment(category = "portal_colours")
or on portal update. public static Comment explanation2;
""")
public static Map<String, DyeColor> portalBlocks = ImmutableMap.ofEntries( @Entry(category = "portal_colours")
Map.entry("minecraft:white_wool", DyeColor.WHITE), public static List<String> white = Lists.newArrayList("minecraft:white_wool");
Map.entry("minecraft:orange_wool", DyeColor.ORANGE),
Map.entry("minecraft:magenta_wool", DyeColor.MAGENTA), @Entry(category = "portal_colours")
Map.entry("minecraft:light_blue_wool", DyeColor.LIGHT_BLUE), public static List<String> orange = Lists.newArrayList("minecraft:orange_wool");
Map.entry("minecraft:yellow_wool", DyeColor.YELLOW),
Map.entry("minecraft:lime_wool", DyeColor.LIME), @Entry(category = "portal_colours")
Map.entry("minecraft:pink_wool", DyeColor.PINK), public static List<String> magenta = Lists.newArrayList("minecraft:magenta_wool");
Map.entry("minecraft:gray_wool", DyeColor.GRAY),
Map.entry("minecraft:light_gray_wool", DyeColor.LIGHT_GRAY), @Entry(category = "portal_colours")
Map.entry("minecraft:cyan_wool", DyeColor.CYAN), public static List<String> light_blue = Lists.newArrayList("minecraft:light_blue_wool");
Map.entry("minecraft:purple_wool", DyeColor.PURPLE),
Map.entry("minecraft:blue_wool", DyeColor.BLUE), @Entry(category = "portal_colours")
Map.entry("minecraft:brown_wool", DyeColor.BROWN), public static List<String> yellow = Lists.newArrayList("minecraft:yellow_wool");
Map.entry("minecraft:green_wool", DyeColor.GREEN),
Map.entry("minecraft:red_wool", DyeColor.RED), @Entry(category = "portal_colours")
Map.entry("minecraft:black_wool", DyeColor.BLACK)); public static List<String> lime = Lists.newArrayList("minecraft:lime_wool");
@Entry(category = "portal_colours")
public static List<String> pink = Lists.newArrayList("minecraft:pink_wool");
@Entry(category = "portal_colours")
public static List<String> gray = Lists.newArrayList("minecraft:gray_wool");
@Entry(category = "portal_colours")
public static List<String> light_gray = Lists.newArrayList("minecraft:light_gray_wool");
@Entry(category = "portal_colours")
public static List<String> cyan = Lists.newArrayList("minecraft:cyan_wool");
@Entry(category = "portal_colours")
public static List<String> purple = Lists.newArrayList("minecraft:purple_wool");
@Entry(category = "portal_colours")
public static List<String> blue = Lists.newArrayList("minecraft:blue_wool");
@Entry(category = "portal_colours")
public static List<String> brown = Lists.newArrayList("minecraft:brown_wool");
@Entry(category = "portal_colours")
public static List<String> green = Lists.newArrayList("minecraft:green_wool");
@Entry(category = "portal_colours")
public static List<String> red = Lists.newArrayList("minecraft:red_wool");
@Entry(category = "portal_colours")
public static List<String> black = Lists.newArrayList("minecraft:black_wool");
@Entry(category = "portal_colours")
public static List<String> none = Lists.newArrayList();
@Entry(category = "visuals")
public static boolean blockyPortalFluid = false;
@Entry(category = "colourful_pearl")
public static Map<String, Integer> pearlDimensionWeights = new HashMap<>(
Map.of("minecraft:overworld", 79,
"minecraft:the_nether", 20,
"minecraft:the_end", 1)
);
@Entry(category = "colourful_pearl", isSlider = true, min = 0f, max = 1f, precision = 1000)
public static double pearlSameDimensionLikelihood = 0.9D;
@Entry(category = "colourful_pearl", isSlider = true, min = 1 << 4, max = 1 << 16, precision = 10)
public static double maxPearlDistance = 1 << 14;
@Entry(category = "colourful_pearl", isSlider = true, min = 1 << 4, max = 1 << 16, precision = 10)
public static double minPearlDistance = 1 << 12;
private static final ArrayList<Procedure> onWrite = new ArrayList<>(2);
public static Set<String> getAllPortalBlocks() {
Set<String> allBlocks = new HashSet<>();
allBlocks.addAll(white);
allBlocks.addAll(orange);
allBlocks.addAll(magenta);
allBlocks.addAll(light_blue);
allBlocks.addAll(yellow);
allBlocks.addAll(lime);
allBlocks.addAll(pink);
allBlocks.addAll(gray);
allBlocks.addAll(light_gray);
allBlocks.addAll(cyan);
allBlocks.addAll(purple);
allBlocks.addAll(blue);
allBlocks.addAll(brown);
allBlocks.addAll(green);
allBlocks.addAll(red);
allBlocks.addAll(black);
allBlocks.addAll(none);
return allBlocks;
}
public static DyeColor colorOf(String block) {
if (white.contains(block)) {
return DyeColor.WHITE;
} else if (orange.contains(block)) {
return DyeColor.ORANGE;
} else if (magenta.contains(block)) {
return DyeColor.MAGENTA;
} else if (light_blue.contains(block)) {
return DyeColor.LIGHT_BLUE;
} else if (yellow.contains(block)) {
return DyeColor.YELLOW;
} else if (lime.contains(block)) {
return DyeColor.LIME;
} else if (pink.contains(block)) {
return DyeColor.PINK;
} else if (gray.contains(block)) {
return DyeColor.GRAY;
} else if (light_gray.contains(block)) {
return DyeColor.LIGHT_GRAY;
} else if (cyan.contains(block)) {
return DyeColor.CYAN;
} else if (purple.contains(block)) {
return DyeColor.PURPLE;
} else if (blue.contains(block)) {
return DyeColor.BLUE;
} else if (brown.contains(block)) {
return DyeColor.BROWN;
} else if (green.contains(block)) {
return DyeColor.GREEN;
} else if (red.contains(block)) {
return DyeColor.RED;
} else if (black.contains(block)) {
return DyeColor.BLACK;
} else if (none.contains(block)) {
return null; // Fully transparent portal
} else {
throw new IllegalArgumentException("Invalid portal block: " + block);
}
}
@Override
public void writeChanges(String modid) {
super.writeChanges(modid);
for (Procedure procedure : onWrite) {
procedure.run();
}
}
public static void registerListener(Procedure p) {
onWrite.add(p);
}
public static void addMissingDimensionsToConfig(MinecraftServer server) {
Set<RegistryKey<World>> worlds = server.getWorldRegistryKeys();
boolean changed = false;
for (RegistryKey<World> world : worlds) {
if (!pearlDimensionWeights.containsKey(world.getValue().toString())) {
pearlDimensionWeights.put(world.getValue().toString(), 1);
changed = true;
}
}
if (maxPearlDistance < minPearlDistance) {
double min = maxPearlDistance;
maxPearlDistance = minPearlDistance;
minPearlDistance = min;
changed = true;
}
if (changed) {
write(ColourfulPortalsMod.MOD_ID);
}
}
} }

View File

@@ -0,0 +1,81 @@
package quimufu.colourful_portals.entity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.projectile.thrown.ThrownItemEntity;
import net.minecraft.item.Item;
import net.minecraft.predicate.entity.EntityPredicates;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.util.TeleportParticleHelper;
import quimufu.colourful_portals.util.TeleportHelper;
import java.util.List;
public class ColourfulPearlEntity
extends ThrownItemEntity {
public ColourfulPearlEntity(EntityType<? extends ColourfulPearlEntity> entityType, World world) {
super(entityType, world);
}
public ColourfulPearlEntity(World world, LivingEntity owner) {
super(ColourfulPortalsMod.COLOURFUL_PEARL_ENTITY_TYPE, owner, world);
}
@Override
protected Item getDefaultItem() {
return ColourfulPortalsMod.COLOURFUL_PEARL_ITEM;
}
@Override
public void onRemoved() {
if (this.getRemovalReason() == RemovalReason.DISCARDED) {
if (getWorld() instanceof ClientWorld clientWorld) {
clientWorld.playSound(
this.getX(), this.getY(), this.getZ(),
ColourfulPortalsMod.TELEPORT_AWAY_SOUND,
SoundCategory.BLOCKS,
100F, 1.25F,
false);
}
List<Entity> entities = getWorld()
.getOtherEntities(this, this.getBoundingBox().expand(5));
entities.stream()
.filter(e -> e.squaredDistanceTo(this) < 25)
.forEach(entity -> TeleportParticleHelper.spawnTeleportationParticles(entity, getWorld()));
}
}
@Override
protected void onCollision(HitResult hitResult) {
if (!(getWorld() instanceof ServerWorld serverWorld)) {
return;
}
if (isRemoved()) {
return;
}
List<Entity> entities = getWorld()
.getOtherEntities(this, this.getBoundingBox().expand(5), EntityPredicates.EXCEPT_SPECTATOR
.and(entity -> entity instanceof LivingEntity)
.and(entity -> !entity.getType().isIn(ColourfulPortalsMod.COLOURFUL_PEARL_NOT_TELEPORTABLE)));
entities.stream()
.filter(e -> e.squaredDistanceTo(this) < 25)
.forEach(entity -> {
Vec3d pos = this.getPos();
TeleportHelper.markForTeleport(entity,pos.toVector3f(), serverWorld);
});
this.discard();
}
}

View File

@@ -0,0 +1,457 @@
package quimufu.colourful_portals.general_util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER;
public class LinkedList<E> implements List<E> {
Node<E> headNode = null;
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public int size() {
Node<E> node = headNode;
int size = 0;
while (node != null) {
size++;
node = node.getNext();
}
return size;
}
@Override
public boolean isEmpty() {
return headNode == null;
}
@NotNull
@Override
public Iterator<E> iterator() {
return new LLIterator<>(this);
}
@NotNull
@Override
public Object[] toArray() {
Object[] objects = new Object[size()];
Node<E> node = headNode;
int index = 0;
while (node != null) {
objects[index] = node.getValue();
node = node.getNext();
index++;
}
return objects;
}
@NotNull
@Override
public <T> T @NotNull [] toArray(T[] a) {
int size = this.size();
if (size > a.length) {
a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
}
Object[] result = a;
Node<E> node = headNode;
int index = 0;
while (node != null) {
result[index] = node.getValue();
node = node.getNext();
index++;
}
if (size != a.length) {
a[index] = null;
}
return a;
}
@Override
public boolean add(E e) {
if (headNode == null) {
headNode = new Node<>(e, this);
return true;
}
Node<E> node = getLastNode(headNode);
node.insertAfter(e);
return true;
}
private @NotNull Node<E> getLastNode(@NotNull Node<E> start) {
Node<E> node = start;
while (node.getNext() != null) {
node = node.getNext();
}
return node;
}
@Override
public boolean containsAll(@NotNull Collection<?> c) {
for (Object o : c) {
if (!contains(o)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(@NotNull Collection<? extends E> c) {
if (headNode == null) {
headNode = new Node<>(null, this);
Node<E> startNode = new Node<>(null, this);
Node<E> currNode = startNode;
for (E o : c) {
currNode.insertAfter(o);
currNode = currNode.getNext();
}
startNode.remove();
return true;
}
Node<E> currNode = getLastNode(headNode);
for (E o : c) {
currNode.insertAfter(o);
currNode = currNode.getNext();
}
return true;
}
@Override
public boolean addAll(int index, @NotNull Collection<? extends E> c) {
if (c.isEmpty()) {
return false;
}
Node<E> node = getNode(index);
for (E e : c) {
node.insertBefore(e);
}
return true;
}
@Override
public boolean removeAll(@NotNull Collection<?> c) {
if (headNode == null) {
return false;
}
boolean ret = false;
Node<E> currNode = headNode;
while (currNode != null) {
if (c.contains(currNode.getValue())) {
currNode.remove();
ret = true;
}
currNode = currNode.getNext();
}
return ret;
}
@Override
public boolean retainAll(@NotNull Collection<?> c) {
if (headNode == null) {
return false;
}
boolean ret = false;
Node<E> currNode = headNode;
while (currNode != null) {
if (!c.contains(currNode.getValue())) {
currNode.remove();
ret = true;
}
currNode = currNode.getNext();
}
return ret;
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
Node<E> node = headNode;
while (node != null) {
if (Objects.equals(node.getValue(), o)) {
node.remove();
return true;
}
node = node.getNext();
}
return false;
}
public E get(int index) {
return getNode(index).getValue();
}
public Node<E> getNode(int index) {
Node<E> node = headNode;
for (int i = 0; i < index; i++) {
if (node == null)
throw new IndexOutOfBoundsException(index);
node = node.getNext();
}
return node;
}
public E set(int index, E element) {
getNode(index).setValue(element);
return element;
}
public void add(int index, E element) {
getNode(index).insertBefore(element);
}
public E remove(int index) {
Node<E> node = getNode(index);
node.remove();
return node.getValue();
}
public int indexOf(Object o) {
int index = 0;
Node<E> node = headNode;
while (node != null) {
if (Objects.equals(node.getValue(), o)) {
return index;
}
node = node.getNext();
index++;
}
return -1;
}
public int lastIndexOf(Object o) {
int ret = -1;
int index = 0;
Node<E> node = headNode;
while (node != null) {
if (Objects.equals(node.getValue(), o)) {
ret = index;
}
node = node.getNext();
index++;
}
return ret;
}
@NotNull
@Override
public ListIterator<E> listIterator() {
return new LLLIterator<>(this);
}
@NotNull
@Override
public ListIterator<E> listIterator(int index) {
LLLIterator<E> iterator = new LLLIterator<>(this);
iterator.nextNode = getNode(index);
iterator.previousNode = iterator.nextNode.getPrevious();
return iterator;
}
@NotNull
@Override
public List<E> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
@Nullable
public Node<E> getNodeOf(E content) {
Node<E> node = headNode;
while (node != null) {
if (Objects.equals(node.getValue(), content)) {
return node;
}
node = node.getNext();
}
return null;
}
private static class LLIterator<E> implements Iterator<E> {
private Node<E> nextNode;
public LLIterator(LinkedList<E> linkedList) {
nextNode = linkedList.headNode;
}
@Override
public boolean hasNext() {
return nextNode != null;
}
@Override
public E next() {
if (nextNode == null)
throw new IndexOutOfBoundsException();
E value = nextNode.getValue();
nextNode = nextNode.getNext();
return value;
}
}
private static class LLLIterator<E> implements ListIterator<E> {
@Nullable
private Node<E> nextNode;
@Nullable
private Node<E> previousNode;
private boolean lastNext = true;
private final LinkedList<E> linkedList;
public LLLIterator(LinkedList<E> linkedList) {
nextNode = linkedList.headNode;
previousNode = null;
this.linkedList = linkedList;
}
@Override
public boolean hasNext() {
return nextNode != null;
}
@Override
public E next() {
tryFixOrphans();
if (nextNode == null)
throw new IndexOutOfBoundsException();
E value = nextNode.getValue();
previousNode = nextNode;
nextNode = nextNode.getNext();
lastNext = true;
return value;
}
@Override
public boolean hasPrevious() {
return previousNode != null;
}
@Override
public E previous() {
tryFixOrphans();
if (previousNode == null)
throw new IndexOutOfBoundsException();
E value = previousNode.getValue();
nextNode = previousNode;
previousNode = previousNode.getPrevious();
lastNext = false;
return value;
}
@Override
public int nextIndex() {
tryFixOrphans();
if(nextNode == null)
return linkedList.size();
return linkedList.indexOfNode(nextNode);
}
@Override
public int previousIndex() {
tryFixOrphans();
if(previousNode == null) {
return -1;
}
return linkedList.indexOfNode(previousNode);
}
@Override
public void remove() {
tryFixOrphans();
if(lastNext) {
if(previousNode == null)
throw new IllegalStateException();
previousNode.remove();
previousNode = previousNode.getPrevious();
} else {
if(nextNode == null)
throw new IllegalStateException();
nextNode.remove();
nextNode = nextNode.getNext();
}
}
@Override
public void set(E e) {
tryFixOrphans();
if(lastNext) {
if(previousNode == null)
throw new IllegalStateException();
previousNode.setValue(e);
} else {
if(nextNode == null)
throw new IllegalStateException();
nextNode.setValue(e);
}
}
@Override
public void add(E e) {
tryFixOrphans();
if(nextNode != null) {
nextNode.insertBefore(e);
} else if(previousNode != null) {
previousNode.insertAfter(e);
} else {
// empty!
linkedList.add(e);
nextNode = linkedList.headNode;
}
}
private void tryFixOrphans() {
if (previousNode != null && previousNode.orphaned()){
if(nextNode != null && !nextNode.orphaned()){
previousNode = nextNode.getPrevious();
} else {
throw new ConcurrentModificationException("Node was concurrently deleted!");
}
}
if (nextNode != null && nextNode.orphaned()){
if(previousNode != null && !previousNode.orphaned()){
nextNode = previousNode.getNext();
} else {
throw new ConcurrentModificationException("Node was concurrently deleted!");
}
}
}
}
private int indexOfNode(Node<E> needle) {
int index = 0;
Node<E> node = headNode;
while (node != null) {
if (node == needle) {
return index;
}
node = node.getNext();
index++;
}
return -1;
}
}

View File

@@ -0,0 +1,89 @@
package quimufu.colourful_portals.general_util;
import org.jetbrains.annotations.Nullable;
public class Node<E> {
private Node<E> next = null;
private Node<E> previous = null;
private E value;
public LinkedList<E> getPartOf() {
return partOf;
}
private final LinkedList<E> partOf;
private boolean orphaned = false;
protected Node(E value, LinkedList<E> partOf) {
this.value = value;
this.partOf = partOf;
}
public Node<E> getNext() {
return next;
}
public void setNext(@Nullable Node<E> next) {
if (next != null) {
next.previous = this;
}
if(next == this){
throw new IllegalArgumentException("cycles are not allowed");
}
this.next = next;
}
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
public Node<E> getPrevious() {
return previous;
}
public void setPrevious(@Nullable Node<E> previous) {
if (previous != null) {
previous.next = this;
}
this.previous = previous;
}
public void remove() {
if (partOf.headNode == this) {
if (next != null) {
partOf.headNode = next;
} else {
partOf.headNode = previous;
}
}
if (previous != null) {
previous.setNext(next);
}
if (next != null) {
next.setPrevious(previous);
}
orphaned = true;
}
public void insertBefore(E element) {
Node<E> newNode = new Node<>(element, partOf);
newNode.setPrevious(this.previous);
newNode.setNext(this);
}
public void insertAfter(E element) {
Node<E> newNode = new Node<>(element, partOf);
newNode.setNext(this.next);
newNode.setPrevious(this);
}
public boolean orphaned() {
return orphaned;
}
}

View File

@@ -0,0 +1,34 @@
package quimufu.colourful_portals.general_util;
import org.joml.Random;
import java.util.TreeMap;
public class WeightedSelector <E> {
private final TreeMap<Integer, E> backingMap = new TreeMap<>();
private int total = 0;
public void add(E object, int weight){
if(weight <= 0){
return;
}
backingMap.put(total, object);
total += weight;
}
public void clear() {
total = 0;
backingMap.clear();
}
public E getWeighted(Random random) {
if(total == 0){
throw new IllegalStateException("tried to receive Weighted Result from empty Selector");
}
return backingMap.floorEntry(random.nextInt(total)).getValue();
}
}

View File

@@ -0,0 +1,98 @@
package quimufu.colourful_portals.item;
import net.minecraft.advancement.criterion.Criteria;
import net.minecraft.block.Block;
import net.minecraft.client.MinecraftClient;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.*;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.stat.Stats;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import quimufu.colourful_portals.ColourfulAirBlock;
import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.client.mixin.MinecraftClientAccessor;
public class ColourfulAirBottleItem
extends AliasedBlockItem {
private static final int MAX_USE_TIME = 40;
public ColourfulAirBottleItem(Block block, Settings settings) {
super(block, settings);
}
@Override
public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) {
if (!tryPlaceAtHead(stack, world, user)) {
return stack;
}
user.emitGameEvent(GameEvent.DRINK);
if (user instanceof PlayerEntity playerEntity) {
stack = ItemUsage.exchangeStack(stack, playerEntity, new ItemStack(Items.GLASS_BOTTLE));
if(world.isClient){
((MinecraftClientAccessor)MinecraftClient.getInstance())
.setItemUseCooldown(16);
}
}
if (user instanceof ServerPlayerEntity serverPlayerEntity) {
Criteria.CONSUME_ITEM.trigger(serverPlayerEntity, stack);
serverPlayerEntity.incrementStat(Stats.USED.getOrCreateStat(this));
}
return stack;
}
private boolean tryPlaceAtHead(ItemStack stack, World world, LivingEntity user) {
BlockPos headPos = BlockPos.ofFloored(user.getEyePos());
if (world.getBlockState(headPos).isIn(ColourfulPortalsMod.COLOURFUL_PEARL_REPLACEABLE_BLOCK_TAG)) {
DyeColor color = stack.getOrDefault(DataComponentTypes.BASE_COLOR, DyeColor.BLACK);
return world.setBlockState(headPos, getBlock().getDefaultState()
.withIfExists(ColourfulAirBlock.DYE_COLOR, color),
Block.NOTIFY_ALL_AND_REDRAW);
}
return false;
}
@Override
public ActionResult useOnBlock(ItemUsageContext context) {
if (context.getPlayer() != null && context.getPlayer().isSneaking()) {
return ActionResult.PASS;
}
return super.useOnBlock(context);
}
@Override
public int getMaxUseTime(ItemStack stack, LivingEntity user) {
return MAX_USE_TIME;
}
@Override
public UseAction getUseAction(ItemStack stack) {
return UseAction.DRINK;
}
@Override
public SoundEvent getDrinkSound() {
//todo
return SoundEvents.ITEM_HONEY_BOTTLE_DRINK;
}
@Override
public SoundEvent getEatSound() {
//todo
return SoundEvents.ITEM_HONEY_BOTTLE_DRINK;
}
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
return ItemUsage.consumeHeldItem(world, user, hand);
}
}

View File

@@ -0,0 +1,85 @@
package quimufu.colourful_portals.item;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsage;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvent;
import net.minecraft.sound.SoundEvents;
import net.minecraft.stat.Stats;
import net.minecraft.util.Hand;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.UseAction;
import net.minecraft.world.World;
import net.minecraft.world.event.GameEvent;
import quimufu.colourful_portals.entity.ColourfulPearlEntity;
import quimufu.colourful_portals.util.TeleportParticleHelper;
import quimufu.colourful_portals.util.TeleportHelper;
public class ColourfulPearlItem
extends Item {
private static final int MAX_USE_TIME = 5;
public ColourfulPearlItem(Item.Settings settings) {
super(settings);
}
@Override
public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) {
if(user instanceof PlayerEntity player){
player.getItemCooldownManager().set(this, 50);
}
if (world instanceof ServerWorld serverWorld) {
TeleportHelper.markForTeleport(user, user.getPos().toVector3f(), serverWorld);
}
stack.decrementUnlessCreative(1, user);
user.emitGameEvent(GameEvent.EAT);
return stack;
}
@Override
public UseAction getUseAction(ItemStack stack) {
return UseAction.DRINK;
}
@Override
public SoundEvent getDrinkSound() {
//todo
return SoundEvents.ITEM_HONEY_BOTTLE_DRINK;
}
@Override
public SoundEvent getEatSound() {
//todo
return SoundEvents.ITEM_HONEY_BOTTLE_DRINK;
}
@Override
public int getMaxUseTime(ItemStack stack, LivingEntity user) {
return MAX_USE_TIME;
}
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
if (user.isSneaking()) {
TeleportParticleHelper.spawnTeleportationParticles(user, world);
return ItemUsage.consumeHeldItem(world, user, hand);
}
ItemStack stackInHand = user.getStackInHand(hand);
world.playSound(null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENTITY_ENDER_PEARL_THROW, SoundCategory.NEUTRAL, 0.5f, 0.4f / (world.getRandom().nextFloat() * 0.4f + 0.8f));
user.getItemCooldownManager().set(this, 50);
if (!world.isClient) {
ColourfulPearlEntity colourfulPearlEntity = new ColourfulPearlEntity(world, user);
colourfulPearlEntity.setItem(stackInHand);
colourfulPearlEntity.setVelocity(user, user.getPitch(), user.getYaw(), 0.0f, 1.5f, 1.0f);
world.spawnEntity(colourfulPearlEntity);
}
user.incrementStat(Stats.USED.getOrCreateStat(this));
stackInHand.decrementUnlessCreative(1, user);
return TypedActionResult.success(stackInHand, world.isClient());
}
}

View File

@@ -0,0 +1,102 @@
package quimufu.colourful_portals.mixin;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.registry.Registries;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
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.util.RaycastHelper;
import java.util.List;
import static quimufu.colourful_portals.ColourfulPortalsMod.*;
@Mixin(ServerWorld.class)
public abstract class BlockChangeAndEntityMovementMixin {
@Unique
private static final List<Direction> HORIZONTAL = Direction.stream().filter(d -> d.getAxis().isHorizontal()).toList();
// @Unique
// private final ThreadLocal<Map<EntityType<?>, Entity>> entities =
// ThreadLocal.withInitial(() -> HashMap.newHashMap(Registries.ENTITY_TYPE.size()));
@Shadow
public abstract String toString();
// @Inject(at = @At(value = "HEAD"), method = "tickEntity")
// private void cpm_beforeEntityTick(Entity entity, CallbackInfo ci) {
// if(PORTAL_MANAGER.getLinkingSystem().needsMovementCallback()){
// Entity copy = entities.get().computeIfAbsent(entity.getType(), t -> t.create(null));
// copy.copyFrom(entity);
// }
// }
@Shadow
public abstract boolean isChunkLoaded(long chunkPos);
@Inject(at = @At("RETURN"), method = "tickEntity")
private void cpm_afterEntityTick(Entity entity, CallbackInfo ci) {
if (PORTAL_MANAGER.getLinkingSystem().needsMovementCallback()) {
Vec3d prevEntityPos;
if (entity instanceof ServerPlayerEntity serverPlayerEntity) {
ServerPlayNetworkHandlerAccessor spnha = (ServerPlayNetworkHandlerAccessor) serverPlayerEntity.networkHandler;
prevEntityPos = new Vec3d(spnha.getLastTickX(), spnha.getLastTickY(), spnha.getLastTickZ());
} else {
prevEntityPos = new Vec3d(entity.prevX, entity.prevY, entity.prevZ);
}
Vec3d entityPos = entity.getPos();
ServerWorld world = (ServerWorld) (Object) this;
List<BlockPos> passedBlocks = RaycastHelper.passedBlocks(prevEntityPos, entityPos);
for (BlockPos passedBlock : passedBlocks) {
if (PORTAL_MANAGER.onMovementThroughBlock(entity, passedBlock, world)) {
return;
}
}
}
}
@Inject(at = @At("RETURN"), method = "onBlockChanged")
private void cpm_afterOnBlockChanged(BlockPos pos, BlockState oldBlock, BlockState newBlock, CallbackInfo info) {
ServerWorld world = (ServerWorld) (Object) this;
if (world.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.debug("onBlockNew {} -> {}", oldBlock, newBlock);
Identifier blockId = Registries.BLOCK.getId(newBlock.getBlock());
synchronized (PORTAL_MANAGER.pendingUpdates){
PORTAL_MANAGER.pendingUpdates.add(() ->
PORTAL_MANAGER.onPortalBlockPlaced(world, pos, blockId));
}
if(!PORTAL_MANAGER.blockUpdatesPending.getAndSet(true)){
world.getServer().execute(PORTAL_MANAGER::runPendingUpdates);
}
}
if (PORTAL_BLOCKS.contains(Registries.BLOCK.getId(oldBlock.getBlock()))) {
LOGGER.debug("onBlockOld {} -> {}", oldBlock, newBlock);
Identifier blockId = Registries.BLOCK.getId(oldBlock.getBlock());
synchronized (PORTAL_MANAGER.pendingUpdates){
PORTAL_MANAGER.pendingUpdates.add(() ->
PORTAL_MANAGER.onPortalBlockBroken(world, pos, blockId));
}
if(!PORTAL_MANAGER.blockUpdatesPending.getAndSet(true)){
world.getServer().execute(PORTAL_MANAGER::runPendingUpdates);
}
}
}
}
}

View File

@@ -1,42 +0,0 @@
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) {
ServerWorld world = (ServerWorld) (Object) this;
if(world.isDebugWorld()|| !world.isChunkLoaded(pos))
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.debug("onBlockNew {} -> {}", oldBlock, newBlock);
Identifier blockId = Registries.BLOCK.getId(newBlock.getBlock());
PortalManager.onPortalBlockPlaced(world, pos, blockId);
}
if (PORTAL_BLOCKS.contains(Registries.BLOCK.getId(oldBlock.getBlock()))) {
LOGGER.debug("onBlockOld {} -> {}", oldBlock, newBlock);
Identifier blockId = Registries.BLOCK.getId(oldBlock.getBlock());
PortalManager.onPortalBlockBroken(world, pos, blockId);
}
}
}
}

View File

@@ -1,37 +0,0 @@
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<Integer> colourBefore = new ThreadLocal<>();
ThreadLocal<Double> 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;
}
}

View File

@@ -0,0 +1,26 @@
package quimufu.colourful_portals.mixin;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ServerPlayNetworkHandler.class)
public interface ServerPlayNetworkHandlerAccessor {
@Accessor
double getLastTickX();
@Accessor
double getLastTickY();
@Accessor
double getLastTickZ();
@Accessor
double getLastTickRiddenX();
@Accessor
double getLastTickRiddenY();
@Accessor
double getLastTickRiddenZ();
}

View File

@@ -0,0 +1,12 @@
package quimufu.colourful_portals.mixin;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ServerPlayerEntity.class)
public interface ServerPlayerEntityAccessor {
@Accessor
void setInTeleportationState(boolean inTeleportationState);
}

View File

@@ -0,0 +1,14 @@
package quimufu.colourful_portals.mixin;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerEntityManager;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.EntityList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ServerWorld.class)
public interface ServerWorldAccessor {
@Accessor
EntityList getEntityList();
}

View File

@@ -1,28 +0,0 @@
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<Boolean> cir) {
if (fluidState.isOf(PORTAL_FLUID)) {
cir.setReturnValue(sodiumPortalFluidRenderHandler.render(world, fluidState, pos, offset, buffers));
cir.cancel();
}
}
}

View File

@@ -0,0 +1,179 @@
package quimufu.colourful_portals.portal;
import net.minecraft.entity.Entity;
import net.minecraft.entity.projectile.ArrowEntity;
import net.minecraft.entity.projectile.PersistentProjectileEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
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.Vec3d;
import net.minecraft.world.TeleportTarget;
import quimufu.colourful_portals.general_util.LinkedList;
import quimufu.colourful_portals.general_util.Node;
import quimufu.colourful_portals.mixin.ServerPlayNetworkHandlerAccessor;
import quimufu.colourful_portals.mixin.ServerPlayerEntityAccessor;
import quimufu.colourful_portals.util.RaycastHelper;
import java.util.*;
import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER;
import static quimufu.colourful_portals.ColourfulPortalsMod.MOD_ID;
import static quimufu.colourful_portals.portal.PortalHelper.*;
public class DefaultLinkingSystem implements PortalLinkingSystem {
public static final Identifier DEFAULT_LINKING_SYSTEM = Identifier.of(MOD_ID, "default_linking_system");
private final Map<BlockPos, Set<Node<PortalRepresentation>>> positionalLookup = new HashMap<>();
private final MinecraftServer server;
public DefaultLinkingSystem(MinecraftServer minecraftServer) {
this.server = minecraftServer;
}
@Override
public Identifier getLinkingSystemId() {
return DEFAULT_LINKING_SYSTEM;
}
@Override
public void linkPortals(LinkedList<PortalRepresentation> portalRepresentations) {
LOGGER.debug("start linkPortals");
for (Node<PortalRepresentation> node = portalRepresentations.getNode(0); node != null; node = node.getNext()) {
PortalRepresentation portalRepresentation = node.getValue();
Node<PortalRepresentation> finalNode = node;
insideOf(portalRepresentation)
.forEachRemaining(blockPos -> {
Set<Node<PortalRepresentation>> portalsAtPosition = positionalLookup.computeIfAbsent(blockPos, (k) -> new HashSet<>(1));
portalsAtPosition.add(finalNode);
});
}
LOGGER.debug("end linkPortals");
}
@Override
public void unLinkPortal(PortalRepresentation portalRepresentation) {
//no-op
}
@Override
public boolean movementCallback(Entity entity, BlockPos pos, ServerWorld world) {
for (Direction.Axis axis : Direction.Axis.values()) {
Vec3d prevEntityPos;
if (entity instanceof ServerPlayerEntity serverPlayerEntity) {
ServerPlayNetworkHandlerAccessor spnha = (ServerPlayNetworkHandlerAccessor) serverPlayerEntity.networkHandler;
prevEntityPos = new Vec3d(spnha.getLastTickX(), spnha.getLastTickY(), spnha.getLastTickZ());
} else {
prevEntityPos = new Vec3d(entity.prevX, entity.prevY, entity.prevZ);
}
if (RaycastHelper.passedOnAxis(prevEntityPos, entity.getPos(), pos, axis)) {
if (onPortalPassed(entity, pos, world, axis)) {
return true;
}
}
}
return false;
}
@Override
public boolean onPortalPassed(Entity entity, BlockPos pos, ServerWorld world, Direction.Axis a) {
Set<Node<PortalRepresentation>> portalRepresentationCandidates = positionalLookup.get(pos);
if (portalRepresentationCandidates == null) {
return false;
}
Optional<Node<PortalRepresentation>> portalOpt = portalRepresentationCandidates.stream()
.filter(n -> !n.orphaned())
.filter(n -> n.getValue() != null)
.filter(n -> getAxisW(n.getValue()).rotateYClockwise().getAxis() == a)
.filter(n -> Objects.equals(n.getValue().dimensionId(), getDimId(world)))
.findAny();
if (portalOpt.isPresent()) {
Node<PortalRepresentation> fromNode = portalOpt.get();
Node<PortalRepresentation> toNode = fromNode.getNext() != null ? fromNode.getNext() : fromNode.getPartOf().getNode(0);
if (toNode == null || toNode.getValue() == null) {
return false;
}
PortalRepresentation fromPortal = fromNode.getValue();
PortalRepresentation toPortal = toNode.getValue();
BlockBox fromBox = fromPortal.location();
Vec3d fromCenter = new Vec3d((fromBox.getMaxX() + 1 + fromBox.getMinX()) / 2D, (fromBox.getMaxY() + 1 + fromBox.getMinY()) / 2D, (fromBox.getMaxZ() + 1 + fromBox.getMinZ()) / 2D);
BlockBox toBox = toPortal.location();
Vec3d toCenter = new Vec3d((toBox.getMaxX() + 1 + toBox.getMinX()) / 2D, (toBox.getMaxY() + 1 + toBox.getMinY()) / 2D, (toBox.getMaxZ() + 1 + toBox.getMinZ()) / 2D);
Vec3d relPos = entity.getPos().subtract(fromCenter);
Vec3d targetPos;
float targetYaw;
Vec3d targetVelocity;
ServerWorld toWorld = getPortalWorld(server, toPortal);
if (getAxisW(fromPortal) == getAxisW(toPortal)) {
targetPos = toCenter.add(relPos);
targetYaw = entity.getYaw();
targetVelocity = entity.getVelocity();
} else {
targetPos = toCenter.add(new Vec3d(-relPos.z, relPos.y, relPos.x));
if (entity instanceof PersistentProjectileEntity) {
// //un-fuck up arrow Yaw
// //rotate by 90°
// targetYaw = (entity.getYaw() + 90.F) % 360.F;
// //mirror
// targetYaw = (-targetYaw) % 360.F;
// //rotate by 90°
// targetYaw = (targetYaw - 90.F) % 360.F;
//
// //now it's normal!
// //Let's rotate it!
// targetYaw = (targetYaw + 90.F) % 360.F;
//
// //fuck it up again
// //rotate by 90°
// targetYaw = (targetYaw + 90.F) % 360.F;
// //mirror
// targetYaw = (-targetYaw) % 360.F;
// //rotate by 90°
// targetYaw = (targetYaw - 90.F) % 360.F;
//short:
targetYaw = (entity.getYaw() - 90.F) % 360.F;
} else {
targetYaw = (entity.getYaw() + 90.F) % 360.F;
}
Vec3d currVelocity = entity.getVelocity();
targetVelocity = new Vec3d(-currVelocity.z, currVelocity.y, currVelocity.x);
}
//todo: maybe fancy continoous movement math!
TeleportTarget teleportTarget = new TeleportTarget(toWorld, targetPos, targetVelocity, targetYaw, entity.getPitch(), TeleportTarget.ADD_PORTAL_CHUNK_TICKET);
if (entity instanceof ServerPlayerEntity) {
((ServerPlayerEntityAccessor) entity).setInTeleportationState(true);
}
Entity target;
if ((target = entity.teleportTo(teleportTarget)) != null) {
target.velocityDirty = true;
return true;
}
}
return false;
}
@Override
public boolean needsReInit() {
return true;
}
@Override
public boolean needsMovementCallback() {
return true;
}
private static ServerWorld getPortalWorld(MinecraftServer server, PortalRepresentation fromPortalRepresentation) {
ServerWorld serverWorld = PortalHelper.getPortalWorld(server, fromPortalRepresentation);
if (serverWorld == null) {
LOGGER.warn("couldn't get portal dimensionId for portal {}. Don't sue me!", fromPortalRepresentation);
throw new RuntimeException();
}
return serverWorld;
}
}

View File

@@ -0,0 +1,150 @@
package quimufu.colourful_portals.portal;
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.Identifier;
import net.minecraft.util.TypeFilter;
import net.minecraft.util.math.*;
import net.minecraft.world.World;
import qouteall.imm_ptl.core.api.PortalAPI;
import qouteall.imm_ptl.core.portal.Portal;
import qouteall.imm_ptl.core.portal.global_portals.GlobalPortalStorage;
import qouteall.q_misc_util.my_util.DQuaternion;
import quimufu.colourful_portals.general_util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER;
import static quimufu.colourful_portals.ColourfulPortalsMod.MOD_ID;
public class ImmersivePortalsLinkingSystem implements PortalLinkingSystem {
public static final Identifier IMMERSIVE_PORTALS_LINKING_SYSTEM = Identifier.of(MOD_ID, "immersive_portals_linking_system");
private final MinecraftServer server;
public ImmersivePortalsLinkingSystem(MinecraftServer server) {
this.server = server;
}
@Override
public Identifier getLinkingSystemId() {
return IMMERSIVE_PORTALS_LINKING_SYSTEM;
}
public void assureLinked(PortalRepresentation fromPortalRepresentation, PortalRepresentation linkedToPortalRepresentation) {
ServerWorld fromPortalWorld = getPortalWorld(fromPortalRepresentation);
RegistryKey<World> linkedToPortalWorldRegKey = RegistryKey.of(RegistryKeys.WORLD, linkedToPortalRepresentation.dimensionId());
BlockBox fromPortalBlockBox = fromPortalRepresentation.location();
Box fromPortalBox = Box.from(fromPortalBlockBox);
BlockBox linkedToPortalBlockBox = linkedToPortalRepresentation.location();
Box linkedToPortalBox = Box.from(linkedToPortalBlockBox);
List<Portal> outgoingPortals = new ArrayList<>(getPortalList(fromPortalRepresentation));
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);
PortalAPI.removeGlobalPortal(getPortalWorld(fromPortalRepresentation), outgoingPortal);
}
}
if (outgoingPortals.isEmpty()) {
Portal portal = Portal.ENTITY_TYPE.create(fromPortalWorld);
if (portal == null) {
LOGGER.error("could not create PortalRepresentation entity for {}", fromPortalRepresentation);
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.addGlobalPortal(fromPortalWorld, portal);
}
if (outgoingPortals.size() == 1) {
Portal portal = PortalAPI.createFlippedPortal(outgoingPortals.getFirst());
outgoingPortals.add(portal);
PortalAPI.addGlobalPortal(fromPortalWorld, 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())));
GlobalPortalStorage.get(getPortalWorld(fromPortalRepresentation)).onDataChanged();
}
}
@Override
public void linkPortals(LinkedList<PortalRepresentation> portalRepresentations) {
for (int i = 0; i < portalRepresentations.size(); i++) {
PortalRepresentation portalRepresentation = portalRepresentations.get(i);
PortalRepresentation linkedToPortalRepresentation = i + 1 < portalRepresentations.size() ? portalRepresentations.get(i + 1) : portalRepresentations.getFirst();
assureLinked(portalRepresentation, linkedToPortalRepresentation);
}
}
@Override
public void unLinkPortal(PortalRepresentation portalRepresentation) {
List<Portal> portals = getPortalList(portalRepresentation);
portals.forEach(portalEntity -> PortalAPI.removeGlobalPortal(getPortalWorld(portalRepresentation), portalEntity));
}
@Override
public boolean onPortalPassed(Entity entity, BlockPos pos, ServerWorld world, Direction.Axis a) {
//no-op, handled by immptl
return false;
}
@Override
public boolean needsReInit() {
return false;
}
@Override
public boolean needsMovementCallback() {
return false;
}
@Override
public boolean movementCallback(Entity entity, BlockPos p, ServerWorld world) {
return false;
}
private List<Portal> getPortalList(PortalRepresentation portalRepresentation) {
Box portalBox = Box.from(portalRepresentation.location());
getPortalWorld(portalRepresentation)
.getEntitiesByType(TypeFilter.instanceOf(Portal.class), portalBox, e -> e.isAlive() && contains(portalBox, e.getBoundingBox()))
.forEach(GlobalPortalStorage::convertNormalPortalIntoGlobalPortal);
return GlobalPortalStorage.get(getPortalWorld(portalRepresentation)).data.stream().filter(portal -> contains(portalBox, portal.getThinBoundingBox())).toList();
}
private boolean contains(Box outside, Box inside) {
return outside.contains(inside.getMaxPos()) && outside.contains(inside.getMinPos());
}
private ServerWorld getPortalWorld(PortalRepresentation fromPortalRepresentation) {
ServerWorld serverWorld = PortalHelper.getPortalWorld(server, fromPortalRepresentation);
if (serverWorld == null) {
LOGGER.warn("couldn't get portal dimensionId for portal {}. Don't sue me!", fromPortalRepresentation);
throw new RuntimeException();
}
return serverWorld;
}
}

View File

@@ -3,12 +3,14 @@ package quimufu.colourful_portals.portal;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockBox; import net.minecraft.util.math.*;
import net.minecraft.util.math.BlockPos; import org.jetbrains.annotations.NotNull;
import net.minecraft.util.math.Direction; import org.jetbrains.annotations.Nullable;
import net.minecraft.util.math.Vec3i;
import java.util.*; import java.util.*;
@@ -24,21 +26,21 @@ public class PortalHelper {
{'N', 'X', 'X', 'N'}, {'N', 'X', 'X', 'N'},
}; };
static Iterator<BlockPos> insideOf(BlockBox portal) { static Iterator<BlockPos> insideOf(PortalRepresentation portal) {
Direction axisW = getAxisW(portal); BlockBox portal1 = portal.location();
Direction axisW = getAxisW(portal1);
Vec3i dir = axisW.getVector(); Vec3i dir = axisW.getVector();
BlockPos startPos = new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()); BlockPos startPos = new BlockPos(portal1.getMinX(), portal1.getMinY(), portal1.getMinZ());
return new BlockPosIterator(startPos, dir, 'O'); return new BlockPosIterator(startPos, dir, 'O');
} }
static boolean isValidPortal(ServerWorld world, PortalRepresentation portal, Identifier blockId) {
static boolean isValidPortal(ServerWorld world, BlockBox portal, Identifier blockId) { return isValidPortal(world, portal.location(), blockId, true, false);
return isValidPortal(world, portal, blockId, true, false);
} }
static boolean isValidCandidate(ServerWorld world, BlockBox portal, Identifier blockId) { static boolean isValidCandidate(ServerWorld world, PortalRepresentation portal, Identifier blockId) {
return isValidPortal(world, portal, blockId, false, false); return isValidPortal(world, portal.location(), blockId, false, false);
} }
private static boolean isValidPortal(ServerWorld world, BlockBox portal, Identifier blockId, boolean empty, boolean placementCheck) { private static boolean isValidPortal(ServerWorld world, BlockBox portal, Identifier blockId, boolean empty, boolean placementCheck) {
@@ -46,15 +48,19 @@ public class PortalHelper {
return isValidPortal(world, Registries.BLOCK.get(blockId), new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()), empty, axisW, placementCheck); return isValidPortal(world, Registries.BLOCK.get(blockId), new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()), empty, axisW, placementCheck);
} }
static Direction getAxisW(PortalRepresentation portalRepresentation) {
return getAxisW(portalRepresentation.location());
}
static Direction getAxisW(BlockBox fromPortalBox) { static Direction getAxisW(BlockBox fromPortalBox) {
if (fromPortalBox.getMaxX() - fromPortalBox.getMinX() > 2) if (fromPortalBox.getMaxX() - fromPortalBox.getMinX() > 2)
return Direction.EAST; return Direction.EAST;
return Direction.SOUTH; return Direction.SOUTH;
} }
static List<BlockBox> findPortalCandidates(ServerWorld world, BlockPos pos, Identifier blockId) { static List<PortalRepresentation> findPortalCandidates(ServerWorld world, BlockPos pos, Identifier blockId) {
Block block = Registries.BLOCK.get(blockId); Block block = Registries.BLOCK.get(blockId);
ArrayList<BlockBox> portals = new ArrayList<>(); ArrayList<PortalRepresentation> portals = new ArrayList<>();
outer: outer:
for (Direction direction : Direction.values()) { for (Direction direction : Direction.values()) {
int size = direction == Direction.UP || direction == Direction.DOWN ? 5 : 4; int size = direction == Direction.UP || direction == Direction.DOWN ? 5 : 4;
@@ -164,17 +170,18 @@ public class PortalHelper {
} }
private static Optional<BlockBox> getPortalStartingAt(ServerWorld world, Block block, BlockPos lowerCorner, Direction direction) { private static Optional<PortalRepresentation> getPortalStartingAt(ServerWorld world, Block block, BlockPos lowerCorner, Direction direction) {
Vec3i sidewardsOffset = direction.getVector().multiply(3); Vec3i sidewardsOffset = direction.getVector().multiply(3);
Vec3i upwardsOffset = Direction.UP.getVector().multiply(4); Vec3i upwardsOffset = Direction.UP.getVector().multiply(4);
if (isValidPortal(world, block, lowerCorner, false, direction, false)) { if (isValidPortal(world, block, lowerCorner, false, direction, false)) {
return Optional.of(BlockBox.create(lowerCorner, lowerCorner.add(sidewardsOffset).add(upwardsOffset))); BlockBox location = BlockBox.create(lowerCorner, lowerCorner.add(sidewardsOffset).add(upwardsOffset));
return Optional.of(new PortalRepresentation(location, getDimId(world)));
} }
return Optional.empty(); return Optional.empty();
} }
public static boolean isPortalPlaceable(ServerWorld world, BlockBox portal, Identifier blockId) { public static boolean isPortalPlaceable(ServerWorld world, PortalRepresentation portal, Identifier blockId) {
return isValidPortal(world, portal, blockId, false, true); return isValidPortal(world, portal.location(), blockId, false, true);
} }
private static boolean isValidPortal(ServerWorld world, Block block, BlockPos lowerCorner, boolean hasToBeFilledCorrectly, Direction direction, boolean placementCheck) { private static boolean isValidPortal(ServerWorld world, Block block, BlockPos lowerCorner, boolean hasToBeFilledCorrectly, Direction direction, boolean placementCheck) {
@@ -212,6 +219,19 @@ public class PortalHelper {
return true; return true;
} }
@Nullable
public static ServerWorld getPortalWorld(MinecraftServer server, PortalRepresentation fromPortalRepresentation) {
return server.getWorld(RegistryKey.of(RegistryKeys.WORLD, fromPortalRepresentation.dimensionId()));
}
public static Identifier getDimId(ServerWorld world) {
return world.getDimensionEntry().getKey().orElseThrow().getValue();
}
public static @NotNull Iterable<BlockPos> blockPosInBox(Box boundinBox) {
return BlockPos.iterate(MathHelper.floor(boundinBox.minX), MathHelper.floor(boundinBox.minY), MathHelper.floor(boundinBox.minZ), MathHelper.floor(boundinBox.maxX), MathHelper.floor(boundinBox.maxY), MathHelper.floor(boundinBox.maxZ));
}
private static class BlockPosIterator implements Iterator<BlockPos> { private static class BlockPosIterator implements Iterator<BlockPos> {
private final BlockPos startPos; private final BlockPos startPos;

View File

@@ -0,0 +1,24 @@
package quimufu.colourful_portals.portal;
import net.minecraft.entity.Entity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import quimufu.colourful_portals.general_util.LinkedList;
public interface PortalLinkingSystem {
Identifier getLinkingSystemId();
void linkPortals(LinkedList<PortalRepresentation> portalRepresentations);
void unLinkPortal(PortalRepresentation portalRepresentation);
boolean onPortalPassed(Entity entity, BlockPos pos, ServerWorld world, Direction.Axis a);
boolean needsReInit();
boolean needsMovementCallback();
boolean movementCallback(Entity entity, BlockPos p, ServerWorld world);
}

View File

@@ -0,0 +1,8 @@
package quimufu.colourful_portals.portal;
import net.minecraft.server.MinecraftServer;
@FunctionalInterface
public interface PortalLinkingSystemBuilder {
PortalLinkingSystem build(MinecraftServer minecraftServer);
}

View File

@@ -1,67 +1,110 @@
package quimufu.colourful_portals.portal; 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.NbtCompound;
import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtHelper; import net.minecraft.nbt.NbtHelper;
import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtList;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockBox; import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.WorldProperties; import org.ladysnake.cca.api.v3.component.Component;
import quimufu.colourful_portals.general_util.LinkedList;
import quimufu.colourful_portals.general_util.Node;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER; import static quimufu.colourful_portals.ColourfulPortalsMod.*;
public class PortalListComponent implements Component { public class PortalListComponent implements Component {
private final WorldProperties worldProperties; public static final int CURRENT_VERSION = 1;
HashMap<Identifier, List<Pair<BlockBox, Identifier>>> portalsPerPortalBlock = new HashMap<>(); HashMap<Identifier, LinkedList<PortalRepresentation>> portalsPerPortalBlock = new HashMap<>();
public PortalListComponent(WorldProperties worldProperties) { Identifier lastPortalLinkingSystem = Identifier.of(MOD_ID, "immersive_portals_linking_system");
this.worldProperties = worldProperties;
public PortalListComponent() {
} }
@Override @Override
public void readFromNbt(NbtCompound tag) { public void readFromNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
if (tag != null && !tag.isEmpty()) { if (tag != null && !tag.isEmpty()) {
for (String block : tag.getKeys()) { if (!tag.contains("version")) {
NbtList portalsWithDim = tag.getList(block, NbtElement.COMPOUND_TYPE); readFromOldNbt(tag);
ArrayList<Pair<BlockBox, Identifier>> portals = new ArrayList<>(); }
if (tag.getInt("version") > CURRENT_VERSION) {
LOGGER.warn("portals component comes from a newer version! Hopefully backwards compatible. Proceeding");
}
if (tag.contains("linking_system")) {
lastPortalLinkingSystem = Identifier.tryParse(tag.getString("linking_system"));
}
NbtCompound blocks = tag.getCompound("blocks");
for (String block : blocks.getKeys()) {
NbtList portalsWithDim = blocks.getList(block, NbtElement.COMPOUND_TYPE);
LinkedList<PortalRepresentation> portalRepresentations = new LinkedList<>();
for (int i = 0; i < portalsWithDim.size(); i++) { for (int i = 0; i < portalsWithDim.size(); i++) {
NbtCompound portalWithDim = portalsWithDim.getCompound(i); NbtCompound portalWithDim = portalsWithDim.getCompound(i);
NbtCompound portalCompound = portalWithDim.getCompound("portal"); NbtCompound portalCompound = portalWithDim.getCompound("portal");
BlockBox portal = BlockBox.create(NbtHelper.toBlockPos(portalCompound.getCompound("from")), BlockBox portal = BlockBox.create(NbtHelper.toBlockPos(portalCompound, "from").orElseThrow(),
NbtHelper.toBlockPos(portalCompound.getCompound("to"))); NbtHelper.toBlockPos(portalCompound, "to").orElseThrow());
Identifier dimension = Identifier.tryParse(portalWithDim.getString("dim")); Identifier dimension = Identifier.tryParse(portalWithDim.getString("dim"));
portals.add(Pair.of(portal, dimension)); portalRepresentations.add(new PortalRepresentation(portal, dimension));
} }
portalsPerPortalBlock.put(Identifier.tryParse(block), portals); portalsPerPortalBlock.put(Identifier.tryParse(block), portalRepresentations);
} }
} }
} }
private void readFromOldNbt(NbtCompound tag) {
for (String block : tag.getKeys()) {
NbtList portalsWithDim = tag.getList(block, NbtElement.COMPOUND_TYPE);
LinkedList<PortalRepresentation> portalRepresentations = new LinkedList<>();
for (int i = 0; i < portalsWithDim.size(); i++) {
NbtCompound portalWithDim = portalsWithDim.getCompound(i);
NbtCompound portalCompound = portalWithDim.getCompound("portal");
BlockBox portal = BlockBox.create(toBlockPosOld(portalCompound.getCompound("from")),
toBlockPosOld(portalCompound.getCompound("to")));
Identifier dimension = Identifier.tryParse(portalWithDim.getString("dim"));
portalRepresentations.add(new PortalRepresentation(portal, dimension));
}
portalsPerPortalBlock.put(Identifier.tryParse(block), portalRepresentations);
}
}
public static BlockPos toBlockPosOld(NbtCompound nbt) {
return new BlockPos(nbt.getInt("X"), nbt.getInt("Y"), nbt.getInt("Z"));
}
@Override @Override
public void writeToNbt(NbtCompound tag) { public void writeToNbt(NbtCompound tag, RegistryWrapper.WrapperLookup registryLookup) {
LOGGER.debug("save");
if(PORTAL_MANAGER == null) {
return;
}
tag.putInt("version", CURRENT_VERSION);
tag.putString("linking_system", PORTAL_MANAGER.getLinkingSystem().getLinkingSystemId().toString());
NbtCompound blocks = new NbtCompound();
tag.put("blocks", blocks);
for (Identifier portalBlockId : portalsPerPortalBlock.keySet()) { for (Identifier portalBlockId : portalsPerPortalBlock.keySet()) {
NbtList portalsWithDimList = new NbtList(); NbtList portalsWithDimList = new NbtList();
tag.put(portalBlockId.toString(), portalsWithDimList); blocks.put(portalBlockId.toString(), portalsWithDimList);
for (Pair<BlockBox, Identifier> portalWithDim : portalsPerPortalBlock.get(portalBlockId)) { for (PortalRepresentation portalRepresentationWithDim : portalsPerPortalBlock.get(portalBlockId)) {
NbtCompound portalWithDimCompound = new NbtCompound(); NbtCompound portalWithDimCompound = new NbtCompound();
portalsWithDimList.add(portalWithDimCompound); portalsWithDimList.add(portalWithDimCompound);
NbtCompound portalCompound = new NbtCompound(); NbtCompound portalCompound = new NbtCompound();
portalWithDimCompound.put("portal", portalCompound); portalWithDimCompound.put("portal", portalCompound);
portalWithDimCompound.putString("dim", portalWithDim.second.toString()); portalWithDimCompound.putString("dim", portalRepresentationWithDim.dimensionId().toString());
BlockBox portal = portalWithDim.first; BlockBox portal = portalRepresentationWithDim.location();
BlockPos from = new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ()); BlockPos from = new BlockPos(portal.getMinX(), portal.getMinY(), portal.getMinZ());
BlockPos to = new BlockPos(portal.getMaxX(), portal.getMaxY(), portal.getMaxZ()); BlockPos to = new BlockPos(portal.getMaxX(), portal.getMaxY(), portal.getMaxZ());
@@ -70,59 +113,54 @@ public class PortalListComponent implements Component {
} }
} }
LOGGER.debug("portals {}", tag.toString()); LOGGER.debug("portals {}", tag);
} }
public List<BlockBox> getContainingPortals(Identifier blockId, BlockPos pos, Identifier dim) { public List<PortalRepresentation> getContainingPortals(Identifier blockId, BlockPos pos, Identifier dim) {
return portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()).stream() return getPortals(blockId).stream()
.filter((portalWithDim) -> (portalWithDim.second.equals(dim) .filter((portal) -> (portal.dimensionId().equals(dim) && portal.location().contains(pos)))
&& portalWithDim.first.contains(pos)))
.map((p) -> p.first)
.toList(); .toList();
} }
public void createPortal(Identifier blockId, Pair<BlockBox, Identifier> portalWithDim) { public void createPortal(Identifier blockId, PortalRepresentation portal) {
List<Pair<BlockBox, Identifier>> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); LinkedList<PortalRepresentation> portalRepresentations = getPortals(blockId);
if (!portals.contains(portalWithDim)) { if (!portalRepresentations.contains(portal)) {
portals.add(portalWithDim); portalRepresentations.add(portal);
} }
} }
public void removePortal(Identifier blockId, Pair<BlockBox, Identifier> portalWithDim) { public void removePortal(Identifier blockId, PortalRepresentation portal) {
List<Pair<BlockBox, Identifier>> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); getPortals(blockId).remove(portal);
portals.remove(portalWithDim);
} }
public List<Pair<BlockBox, Identifier>> getPortals(Identifier blockId) { public LinkedList<PortalRepresentation> getPortals(Identifier blockId) {
return portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); return portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new LinkedList<>());
} }
public Set<Identifier> getBlockIds() { public Set<Identifier> getBlockIds() {
return portalsPerPortalBlock.keySet(); return portalsPerPortalBlock.keySet();
} }
public Pair<BlockBox, Identifier> getNext(Identifier blockId, BlockBox portal, Identifier dim) { public PortalRepresentation getNext(Identifier blockId, PortalRepresentation portalRepresentationWithDim) {
List<Pair<BlockBox, Identifier>> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); LinkedList<PortalRepresentation> portals = getPortals(blockId);
Pair<BlockBox, Identifier> portalWithDim = Pair.of(portal, dim); Node<PortalRepresentation> node;
if ((node = portals.getNodeOf(portalRepresentationWithDim)) == null) {
int nextIndex = portals.indexOf(portalWithDim) + 1; return portals.getFirst();
if (nextIndex >= portals.size()) { }
return portals.get(0); return node.getNext() == null ? portals.getFirst() : node.getNext().getValue();
} }
return portals.get(nextIndex); public boolean containsPortal(Identifier blockId, PortalRepresentation portalRepresentation) {
return getPortals(blockId).contains(portalRepresentation);
} }
public boolean containsPortal(Identifier blockId, Pair<BlockBox,Identifier> portal) { public PortalRepresentation getLast(Identifier blockId) {
List<Pair<BlockBox, Identifier>> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); return getPortals(blockId).getLast();
return portals.contains(portal);
} }
public Pair<BlockBox, Identifier> getLast(Identifier blockId) { public Identifier lastPortalLinkingSystemId() {
List<Pair<BlockBox, Identifier>> portals = portalsPerPortalBlock.computeIfAbsent(blockId, (i) -> new ArrayList<>()); return lastPortalLinkingSystem;
return portals.get(portals.size()-1);
} }
} }

View File

@@ -1,108 +1,108 @@
package quimufu.colourful_portals.portal; package quimufu.colourful_portals.portal;
import com.ibm.icu.impl.Pair;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks; import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.DyeColor; import net.minecraft.util.DyeColor;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.TypeFilter; import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.*; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.util.math.Direction;
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 quimufu.colourful_portals.config.ColourfulPortalConfig;
import quimufu.colourful_portals.general_util.LinkedList;
import java.util.ArrayList; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static quimufu.colourful_portals.ColourfulPortalsMod.*; 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 class PortalManager {
public static void onPortalBlockPlaced(ServerWorld world, BlockPos pos, Identifier blockId) { private final PortalListComponent portalCandidateList;
private final PortalListComponent portalList;
public final AtomicBoolean blockUpdatesPending = new AtomicBoolean(false);
public final Queue<Runnable> pendingUpdates = new java.util.LinkedList<>();
public PortalLinkingSystem getLinkingSystem() {
return linkingSystem;
}
private final PortalLinkingSystem linkingSystem;
public PortalManager(PortalLinkingSystem portalLinkingSystem, PortalListComponent portalCandidateList, PortalListComponent portalList) {
this.portalCandidateList = portalCandidateList;
this.portalList = portalList;
this.linkingSystem = portalLinkingSystem;
}
public void onPortalBlockPlaced(ServerWorld world, BlockPos pos, Identifier blockId) {
world.getProfiler().push("onPortalBlockPlaced"); world.getProfiler().push("onPortalBlockPlaced");
LOGGER.debug("onPortalBlockPlaced, {}", blockId); LOGGER.debug("onPortalBlockPlaced, {}", blockId);
PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties()); Identifier dimIdentifier = PortalHelper.getDimId(world);
//delete portalCandidates obstructed by PortalBlocks for consistency //delete portalCandidates obstructed by PortalBlocks for consistency
List<BlockBox> portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, world.getDimensionKey().getValue()); List<PortalRepresentation> portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, dimIdentifier);
LOGGER.debug("got containing PortalCandidates , {}", portalCandidates); LOGGER.debug("got containing PortalCandidates , {}", portalCandidates);
for (BlockBox portalCandidate : portalCandidates) { for (PortalRepresentation portalCandidate : portalCandidates) {
if (!PortalHelper.isValidCandidate(world, portalCandidate, blockId)) { if (!PortalHelper.isValidCandidate(world, portalCandidate, blockId)) {
LOGGER.debug("invalid, {}", portalCandidate); LOGGER.debug("invalid, {}", portalCandidate);
Pair<BlockBox, Identifier> portalWithDim =
Pair.of(portalCandidate, world.getDimensionKey().getValue());
portalCandidateList.removePortal(blockId, portalWithDim); portalCandidateList.removePortal(blockId, portalCandidate);
} }
} }
//find new portalCandidates created by PortalBlock placement //find new portalCandidates created by PortalBlock placement
List<BlockBox> portals = PortalHelper.findPortalCandidates(world, pos, blockId); List<PortalRepresentation> portals = PortalHelper.findPortalCandidates(world, pos, blockId);
LOGGER.debug("new portalCandidates found, {}", portals); LOGGER.debug("new portalCandidates found, {}", portals);
for (BlockBox portal : portals) { for (PortalRepresentation portal : portals) {
Pair<BlockBox, Identifier> portalWithDim = Pair.of(portal, world.getDimensionKey().getValue()); portalCandidateList.createPortal(blockId, portal);
portalCandidateList.createPortal(blockId, portalWithDim);
} }
world.getProfiler().pop(); world.getProfiler().pop();
} }
public static void onPortalBlockBroken(ServerWorld world, BlockPos pos, Identifier blockId) { public void onPortalBlockBroken(ServerWorld world, BlockPos pos, Identifier blockId) {
world.getProfiler().push("onPortalBlockBroken"); world.getProfiler().push("onPortalBlockBroken");
LOGGER.debug("onPortalBlockBroken, {}", blockId); LOGGER.debug("onPortalBlockBroken, {}", blockId);
PortalListComponent portalList = PORTAL_LIST.get(world.getLevelProperties());
PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties());
//check portalCandidate validity //check portalCandidate validity
List<BlockBox> portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, world.getDimensionKey().getValue()); Identifier dimensionIdentifier = PortalHelper.getDimId(world);
List<PortalRepresentation> portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, dimensionIdentifier);
LOGGER.debug("getContainingPortals, {}", portalCandidates); LOGGER.debug("getContainingPortals, {}", portalCandidates);
for (BlockBox portalCandidate : portalCandidates) { for (PortalRepresentation portalCandidate : portalCandidates) {
if (!PortalHelper.isValidCandidate(world, portalCandidate, blockId)) { if (!PortalHelper.isValidCandidate(world, portalCandidate, blockId)) {
LOGGER.debug("invalid, {}", portalCandidate); LOGGER.debug("invalid, {}", portalCandidate);
Pair<BlockBox, Identifier> portalWithDim = portalCandidateList.removePortal(blockId, portalCandidate);
Pair.of(portalCandidate, world.getDimensionKey().getValue());
portalCandidateList.removePortal(blockId, portalWithDim);
} }
} }
//add portalCandidates deobstructed by PortalBlock removal //add portalCandidates deobstructed by PortalBlock removal
for (Direction direction : Direction.values()) { for (Direction direction : Direction.values()) {
List<BlockBox> newPortalCandidates = PortalHelper.findPortalCandidates(world, pos.add(direction.getVector()), blockId); List<PortalRepresentation> newPortalCandidates = PortalHelper.findPortalCandidates(world, pos.add(direction.getVector()), blockId);
for (BlockBox newPortal : newPortalCandidates) { for (PortalRepresentation newPortal : newPortalCandidates) {
LOGGER.debug("potentially new candidate after deobstruction , {}", newPortal); LOGGER.debug("potentially new candidate after deobstruction , {}", newPortal);
portalCandidateList.createPortal(blockId, newPortal);
PortalListComponent portalListComponent = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties());
Pair<BlockBox, Identifier> portalWithDim =
Pair.of(newPortal, world.getDimensionKey().getValue());
portalListComponent.createPortal(blockId, portalWithDim);
} }
} }
//check portal validity //check portal validity
List<BlockBox> portals = portalList.getContainingPortals(blockId, pos, world.getDimensionKey().getValue()); List<PortalRepresentation> portals = portalList.getContainingPortals(blockId, pos, dimensionIdentifier);
boolean changed = false; boolean changed = false;
for (BlockBox portal : portals) { for (PortalRepresentation portal : portals) {
if (!PortalHelper.isValidPortal(world, portal, blockId)) { if (!PortalHelper.isValidPortal(world, portal, blockId)) {
LOGGER.debug("portal became invalid ,{} {}", world.getDimensionKey().getValue(), portal); LOGGER.debug("portal became invalid ,{} {}", dimensionIdentifier, portal);
Pair<BlockBox, Identifier> portalWithDim = portalList.removePortal(blockId, portal);
Pair.of(portal, world.getDimensionKey().getValue()); linkingSystem.unLinkPortal(portal);
portalList.removePortal(blockId, portalWithDim); removePortalBlocks(world, portal.location());
destroyPortalEntitiesInside(world, Box.from(portal));
removePortalBlocks(world, portal);
changed = true; changed = true;
} }
@@ -114,55 +114,53 @@ public class PortalManager {
world.getProfiler().pop(); world.getProfiler().pop();
} }
private static void removePortalBlocks(ServerWorld world, BlockBox portal) { private void removePortalBlocks(ServerWorld world, BlockBox portal) {
BlockPos.stream(portal) BlockPos.stream(portal)
.filter(blockPos -> world.getBlockState(blockPos).isOf(PORTAL_BLOCK)) .filter(blockPos -> world.getBlockState(blockPos).isOf(PORTAL_BLOCK))
.forEach(blockPos -> world.setBlockState(blockPos, Blocks.AIR.getDefaultState())); .forEach(blockPos -> world.setBlockState(blockPos, Blocks.AIR.getDefaultState()));
} }
private static void fixPortalGroup(Identifier blockId, PortalListComponent portalList, MinecraftServer server) { private void fixPortalGroup(Identifier blockId, PortalListComponent portalList, MinecraftServer server) {
List<Pair<BlockBox, Identifier>> portalsWithDim = portalList.getPortals(blockId); LinkedList<PortalRepresentation> portalsWithDim = portalList.getPortals(blockId);
if (portalsWithDim.size() == 1) { if (portalsWithDim.size() == 1) {
Pair<BlockBox, Identifier> portal = portalsWithDim.get(0); PortalRepresentation portalRepresentation = portalsWithDim.getFirst();
ServerWorld world = getPortalWorld(portal, server); ServerWorld world = PortalHelper.getPortalWorld(server, portalRepresentation);
if (world == null) { if (world == null) {
LOGGER.error("error fixing portalGroup, world {} was null", portal.second); LOGGER.error("error fixing portalGroup, dimensionId {} was null", portalRepresentation.dimensionId());
return; return;
} }
Pair<BlockBox, Identifier> portalWithDim = Pair.of(portal.first, portal.second); portalList.removePortal(blockId, portalRepresentation);
portalList.removePortal(blockId, portalWithDim); linkingSystem.unLinkPortal(portalRepresentation);
destroyPortalEntitiesInside(world, Box.from(portal.first)); removePortalBlocks(world, portalRepresentation.location());
removePortalBlocks(world, portal.first);
return; return;
} }
for (int i = 0; i < portalsWithDim.size(); i++) { for (PortalRepresentation portalRepresentation : portalsWithDim) {
Pair<BlockBox, Identifier> portal = portalsWithDim.get(i); assurePortalBlocksPlaced(blockId, portalRepresentation, server);
Pair<BlockBox, Identifier> linkedToPortal = i + 1 < portalsWithDim.size() ? portalsWithDim.get(i + 1) : portalsWithDim.get(0);
assureLinkedTo(portal, linkedToPortal, server);
assurePortalBlocksPlaced(blockId, portal, server);
} }
linkingSystem.linkPortals(portalsWithDim);
} }
private static void assurePortalBlocksPlaced(Identifier blockId, Pair<BlockBox, Identifier> portal, MinecraftServer server) { private void assurePortalBlocksPlaced(Identifier blockId, PortalRepresentation portalRepresentation, MinecraftServer server) {
DyeColor color = ColourfulPortalConfig.portalBlocks.get(blockId.toString()); DyeColor color = ColourfulPortalConfig.colorOf(blockId.toString());
if (color != null) { if (color != null) {
Direction.Axis portalPlaneAxis = PortalHelper.getAxisW(portal.first) Direction.Axis portalPlaneAxis = PortalHelper.getAxisW(portalRepresentation)
.rotateClockwise(Direction.Axis.Y) .rotateClockwise(Direction.Axis.Y)
.getAxis(); .getAxis();
ServerWorld portalWorld = getPortalWorld(portal, server); ServerWorld portalWorld = PortalHelper.getPortalWorld(server, portalRepresentation);
if (portalWorld == null) { if (portalWorld == null) {
LOGGER.error("error placing portal planes, world {} was null", portal.second); LOGGER.error("error placing portalRepresentation planes, dimensionId {} was null", portalRepresentation.dimensionId());
return; return;
} }
BlockState portalBlockState = PORTAL_BLOCK.getStateWith(color, portalPlaneAxis); BlockState portalBlockState = PORTAL_BLOCK.getStateWith(color, portalPlaneAxis);
PortalHelper.insideOf(portal.first) PortalHelper.insideOf(portalRepresentation)
.forEachRemaining(blockPos -> { .forEachRemaining(blockPos -> {
BlockState blockState = portalWorld.getBlockState(blockPos); BlockState blockState = portalWorld.getBlockState(blockPos);
if (blockState.isAir() || blockState.getFluidState().isOf(PORTAL_FLUID)) { if (blockState.isAir() || blockState.getFluidState().isOf(PORTAL_FLUID)) {
portalWorld.setBlockState(blockPos, portalBlockState); portalWorld.setBlockState(blockPos, portalBlockState);
} else if (blockState.isReplaceable()) { } else if (blockState.isReplaceable()) {
portalWorld.breakBlock(blockPos,true); portalWorld.breakBlock(blockPos, true);
portalWorld.setBlockState(blockPos, portalBlockState); portalWorld.setBlockState(blockPos, portalBlockState);
} }
}); });
@@ -170,99 +168,22 @@ public class PortalManager {
} }
private static void assureLinkedTo(Pair<BlockBox, Identifier> fromPortal, Pair<BlockBox, Identifier> linkedToPortal, MinecraftServer server) {
ServerWorld fromPortalWorld = getPortalWorld(fromPortal, server); public boolean tryIgnite(ServerWorld world, BlockPos pos) {
RegistryKey<World> 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<Portal> portals = fromPortalWorld.getEntitiesByType(TypeFilter.instanceOf(Portal.class), fromPortalBox, Entity::isAlive);
List<Portal> 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<BlockBox, Identifier> fromPortal, MinecraftServer server) {
return server.getWorld(RegistryKey.of(RegistryKeys.WORLD, fromPortal.second));
}
private static void destroyPortalEntitiesInside(ServerWorld world, Box box) {
List<Portal> 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"); world.getProfiler().push("portal ignition");
PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties()); LOGGER.info("portal ignition");
PortalListComponent portalList = PORTAL_LIST.get(world.getLevelProperties());
Set<Identifier> blockIds = portalCandidateList.getBlockIds(); Set<Identifier> blockIds = portalCandidateList.getBlockIds();
boolean ret = false; boolean ret = false;
for (Identifier blockId : blockIds) { for (Identifier blockId : blockIds) {
Identifier dim = world.getDimensionKey().getValue(); Identifier dim = PortalHelper.getDimId(world);
List<BlockBox> portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, dim); List<PortalRepresentation> portalCandidates = portalCandidateList.getContainingPortals(blockId, pos, dim);
for (BlockBox portalCandidate : portalCandidates) { for (PortalRepresentation current : portalCandidates) {
if (PortalHelper.isValidPortal(world, portalCandidate, blockId)) { if (PortalHelper.isValidPortal(world, current, blockId)) {
Pair<BlockBox, Identifier> current = Pair.of(portalCandidate, dim); PortalRepresentation next = portalCandidateList.getNext(blockId, current);
Pair<BlockBox, Identifier> next = portalCandidateList.getNext(blockId, portalCandidate, dim); ServerWorld nextWorld = PortalHelper.getPortalWorld(world.getServer(), next);
ServerWorld nextWorld = world.getServer().getWorld(RegistryKey.of(RegistryKeys.WORLD, next.second));
if (!next.equals(current) if (!next.equals(current)
&& (!portalList.containsPortal(blockId, current) || !portalList.containsPortal(blockId, next)) && (!portalList.containsPortal(blockId, current) || !portalList.containsPortal(blockId, next))
&& PortalHelper.isPortalPlaceable(nextWorld, next.first, blockId)) { && PortalHelper.isPortalPlaceable(nextWorld, next, blockId)) {
portalList.createPortal(blockId, current); portalList.createPortal(blockId, current);
portalList.createPortal(blockId, next); portalList.createPortal(blockId, next);
ret = true; ret = true;
@@ -275,24 +196,21 @@ public class PortalManager {
return ret; return ret;
} }
public static boolean canExtend(ServerWorld world, BlockPos pos) { public boolean canExtend(ServerWorld world, BlockPos pos) {
world.getProfiler().push("portal extension check"); world.getProfiler().push("portal extension check");
PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties());
PortalListComponent portalList = PORTAL_LIST.get(world.getLevelProperties());
Set<Identifier> blockIds = portalList.getBlockIds(); Set<Identifier> blockIds = portalList.getBlockIds();
Identifier dim = world.getDimensionKey().getValue(); Identifier dim = PortalHelper.getDimId(world);
for (Identifier blockId : blockIds) { for (Identifier blockId : blockIds) {
List<BlockBox> portals = portalList.getContainingPortals(blockId, pos, dim); List<PortalRepresentation> portals = portalList.getContainingPortals(blockId, pos, dim);
for (BlockBox portal : portals) { for (PortalRepresentation portal : portals) {
if (PortalHelper.isValidPortal(world, portal, blockId)) { if (PortalHelper.isValidPortal(world, portal, blockId)) {
Pair<BlockBox, Identifier> last = portalList.getLast(blockId); PortalRepresentation last = portalList.getLast(blockId);
Pair<BlockBox, Identifier> next = portalCandidateList.getNext(blockId, last.first, last.second); PortalRepresentation next = portalCandidateList.getNext(blockId, last);
ServerWorld nextWorld = world.getServer().getWorld(RegistryKey.of(RegistryKeys.WORLD, next.second)); ServerWorld nextWorld = PortalHelper.getPortalWorld(world.getServer(), next);
if(!portalList.containsPortal(blockId, next) if (!portalList.containsPortal(blockId, next)
&& PortalHelper.isValidPortal(world, portal, blockId) && PortalHelper.isPortalPlaceable(nextWorld, next, blockId)) {
&& PortalHelper.isPortalPlaceable(nextWorld, next.first, blockId)){
world.getProfiler().pop(); world.getProfiler().pop();
return true; return true;
} }
@@ -305,25 +223,22 @@ public class PortalManager {
return false; return false;
} }
public static boolean extend(ServerWorld world, BlockPos pos) { public boolean extend(ServerWorld world, BlockPos pos) {
world.getProfiler().push("portal extension"); world.getProfiler().push("portal extension");
PortalListComponent portalCandidateList = PORTAL_CANDIDATE_LIST.get(world.getLevelProperties());
PortalListComponent portalList = PORTAL_LIST.get(world.getLevelProperties());
Set<Identifier> blockIds = portalList.getBlockIds(); Set<Identifier> blockIds = portalList.getBlockIds();
Identifier dim = world.getDimensionKey().getValue(); Identifier dim = PortalHelper.getDimId(world);
boolean ret = false; boolean ret = false;
for (Identifier blockId : blockIds) { for (Identifier blockId : blockIds) {
List<BlockBox> portals = portalList.getContainingPortals(blockId, pos, dim); List<PortalRepresentation> portals = portalList.getContainingPortals(blockId, pos, dim);
for (BlockBox portal : portals) { for (PortalRepresentation portal : portals) {
if (PortalHelper.isValidPortal(world, portal, blockId)) { if (PortalHelper.isValidPortal(world, portal, blockId)) {
Pair<BlockBox, Identifier> last = portalList.getLast(blockId); PortalRepresentation last = portalList.getLast(blockId);
Pair<BlockBox, Identifier> next = portalCandidateList.getNext(blockId, last.first, last.second); PortalRepresentation next = portalCandidateList.getNext(blockId, last);
ServerWorld nextWorld = world.getServer().getWorld(RegistryKey.of(RegistryKeys.WORLD, next.second)); ServerWorld nextWorld = PortalHelper.getPortalWorld(world.getServer(), next);
if(!portalList.containsPortal(blockId, next) if (!portalList.containsPortal(blockId, next)
&& PortalHelper.isValidPortal(world, portal, blockId) && PortalHelper.isPortalPlaceable(nextWorld, next, blockId)) {
&& PortalHelper.isPortalPlaceable(nextWorld, next.first, blockId)){
portalList.createPortal(blockId, next); portalList.createPortal(blockId, next);
ret = true; ret = true;
} }
@@ -336,4 +251,49 @@ public class PortalManager {
world.getProfiler().pop(); world.getProfiler().pop();
return ret; return ret;
} }
public void onPortalPassed(Entity entity, BlockPos pos, ServerWorld world, Direction.Axis a) {
linkingSystem.onPortalPassed(entity, pos, world, a);
}
public boolean onMovementThroughBlock(Entity entity, BlockPos pos, ServerWorld world) {
return linkingSystem.movementCallback(entity, pos, world);
}
public void onLoad(MinecraftServer minecraftServer) {
HashSet<Identifier> blockIdsToDelete = new HashSet<>(portalCandidateList.getBlockIds());
blockIdsToDelete.addAll(portalList.getBlockIds());
blockIdsToDelete.removeAll(PORTAL_BLOCKS);
for (Identifier blockId : blockIdsToDelete) {
for (PortalRepresentation portal : portalCandidateList.getPortals(blockId)) {
portalCandidateList.removePortal(blockId, portal);
}
for (PortalRepresentation portal : portalList.getPortals(blockId)) {
portalList.removePortal(blockId, portal);
linkingSystem.unLinkPortal(portal);
ServerWorld portalWorld = PortalHelper.getPortalWorld(minecraftServer, portal);
if (portalWorld != null) {
removePortalBlocks(portalWorld, portal.location());
}
}
}
}
public void runPendingUpdates() {
if(pendingUpdates.size()<20){
blockUpdatesPending.set(false);
}
for (int i = 0; i < Math.min(20, pendingUpdates.size()); i++) {
Runnable runnable;
synchronized (pendingUpdates) {
runnable = pendingUpdates.poll();
}
if(runnable == null){
return;
}
runnable.run();
}
}
} }

View File

@@ -0,0 +1,9 @@
package quimufu.colourful_portals.portal;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockBox;
public record PortalRepresentation(BlockBox location, Identifier dimensionId) {
}

View File

@@ -0,0 +1,6 @@
package quimufu.colourful_portals.portal;
import java.util.function.IntSupplier;
public record PrioritizedPortalLinkingSystemBuilder(PortalLinkingSystemBuilder portalLinkingSystemBuilder,
IntSupplier priority) {}

View File

@@ -13,7 +13,6 @@ public enum NullableAxis implements StringIdentifiable {
private final Direction.Axis axis; private final Direction.Axis axis;
NullableAxis(Direction.Axis axis) { NullableAxis(Direction.Axis axis) {
this.axis = axis; this.axis = axis;
} }

View File

@@ -31,6 +31,7 @@ import quimufu.colourful_portals.portal.PortalManager;
import java.util.*; import java.util.*;
import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER; import static quimufu.colourful_portals.ColourfulPortalsMod.LOGGER;
import static quimufu.colourful_portals.ColourfulPortalsMod.PORTAL_MANAGER;
public class PortalFluid extends Fluid { public class PortalFluid extends Fluid {
public static final EnumProperty<NullableAxis> AXIS = EnumProperty.of("axis", NullableAxis.class); public static final EnumProperty<NullableAxis> AXIS = EnumProperty.of("axis", NullableAxis.class);
@@ -138,7 +139,7 @@ public class PortalFluid extends Fluid {
} }
if (amount <= 14) { if (amount <= 14) {
//*might* change my state in world, returned value is what new state should be //*might* change my state in dimensionId, returned value is what new state should be
newStateMe = steal(world, pos, newStateMe, currentAxis); newStateMe = steal(world, pos, newStateMe, currentAxis);
} }
//my state might have changed. //my state might have changed.
@@ -169,7 +170,7 @@ public class PortalFluid extends Fluid {
} else if (!world.isClient) { } else if (!world.isClient) {
for (Direction direction : Direction.values()) { for (Direction direction : Direction.values()) {
if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(world.getBlockState(pos.offset(direction)).getBlock())) if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(world.getBlockState(pos.offset(direction)).getBlock()))
&& PortalManager.tryIgnite((ServerWorld) world, pos)) { && PORTAL_MANAGER.tryIgnite((ServerWorld) world, pos)) {
return; return;
} }
} }
@@ -197,6 +198,8 @@ public class PortalFluid extends Fluid {
} }
} }
Direction candidate = null; Direction candidate = null;
BlockState candidateBlockState = null;
for (Direction dir : Direction.values()) { for (Direction dir : Direction.values()) {
if (dir == targetDir.getOpposite() || dir == targetDir) { if (dir == targetDir.getOpposite() || dir == targetDir) {
continue; continue;
@@ -211,12 +214,16 @@ public class PortalFluid extends Fluid {
//if adjacent to any solid Block, it's a candidate //if adjacent to any solid Block, it's a candidate
if (neighbourOfInspected.isSideSolidFullSquare(world, neighborOfInspectedLocation, dir.getOpposite())) { if (neighbourOfInspected.isSideSolidFullSquare(world, neighborOfInspectedLocation, dir.getOpposite())) {
candidate = dir; candidate = dir;
candidateBlockState = world.getBlockState(pos.offset(dir));
} }
if (candidate == null && !neighbourOfInspected.isReplaceable()) { if ((candidate == null || candidateBlockState.isReplaceable()) && !neighbourOfInspected.isReplaceable()) {
candidate = dir; candidate = dir;
candidateBlockState = world.getBlockState(pos.offset(dir));
} }
if (candidate == null && !neighbourOfInspected.isReplaceable()) { if (candidate == null) {
candidate = dir; candidate = dir;
candidateBlockState = world.getBlockState(pos.offset(dir));
} }
} }
@@ -372,7 +379,7 @@ public class PortalFluid extends Fluid {
BlockPos neighborOfInspectedLocation = inspectedLocation.offset(dir); BlockPos neighborOfInspectedLocation = inspectedLocation.offset(dir);
BlockState neighbourOfInspected = world.getBlockState(neighborOfInspectedLocation); BlockState neighbourOfInspected = world.getBlockState(neighborOfInspectedLocation);
//if adjacent to a Portal Block, 2 less //if adjacent to a PortalRepresentation Block, 2 less
if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(neighbourOfInspected.getBlock()))) { if (ColourfulPortalsMod.PORTAL_BLOCKS.contains(Registries.BLOCK.getId(neighbourOfInspected.getBlock()))) {
dirCost -= 2; dirCost -= 2;
discount = false; discount = false;
@@ -381,7 +388,7 @@ public class PortalFluid extends Fluid {
//if adjacent to any solid Block, 1 less //if adjacent to any solid Block, 1 less
if (neighbourOfInspected.isSideSolidFullSquare(world, neighborOfInspectedLocation, dir.getOpposite())) { if (neighbourOfInspected.isSideSolidFullSquare(world, neighborOfInspectedLocation, dir.getOpposite())) {
discount = true; discount = true;
//maybe a Portal Block in another direction //maybe a PortalRepresentation Block in another direction
continue; continue;
} }
} }

View File

@@ -2,11 +2,10 @@ package quimufu.colourful_portals.portal_fluid;
import net.minecraft.block.*; import net.minecraft.block.*;
import net.minecraft.entity.ai.pathing.NavigationType; import net.minecraft.entity.ai.pathing.NavigationType;
import net.minecraft.fluid.Fluid; import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.FluidState; import net.minecraft.fluid.FluidState;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.loot.context.LootContext;
import net.minecraft.loot.context.LootContextParameterSet; import net.minecraft.loot.context.LootContextParameterSet;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvent;
@@ -20,7 +19,7 @@ import net.minecraft.util.shape.VoxelShapes;
import net.minecraft.world.BlockView; import net.minecraft.world.BlockView;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.WorldAccess; import net.minecraft.world.WorldAccess;
import quimufu.colourful_portals.ColourfulPortalsMod; import org.jetbrains.annotations.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -109,7 +108,7 @@ public class PortalFluidBlock
} }
@Override @Override
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type) { protected boolean canPathfindThrough(BlockState state, NavigationType type) {
return true; return true;
} }
@@ -119,7 +118,7 @@ public class PortalFluidBlock
} }
@Override @Override
public ItemStack tryDrainFluid(WorldAccess world, BlockPos pos, BlockState state) { public ItemStack tryDrainFluid(PlayerEntity player, WorldAccess world, BlockPos pos, BlockState state) {
if (state.getFluidState().get(PortalFluid.AMOUNT) == 16) { if (state.getFluidState().get(PortalFluid.AMOUNT) == 16) {
world.setBlockState(pos, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL | Block.REDRAW_ON_MAIN_THREAD); world.setBlockState(pos, Blocks.AIR.getDefaultState(), Block.NOTIFY_ALL | Block.REDRAW_ON_MAIN_THREAD);
return new ItemStack(this.fluid.getBucketItem()); return new ItemStack(this.fluid.getBucketItem());
@@ -129,6 +128,7 @@ public class PortalFluidBlock
return ItemStack.EMPTY; return ItemStack.EMPTY;
} }
@Override @Override
public Optional<SoundEvent> getBucketFillSound() { public Optional<SoundEvent> getBucketFillSound() {
return fluid.getBucketFillSound(); return fluid.getBucketFillSound();

View File

@@ -35,7 +35,7 @@ public class PortalFluidBucketItem extends BucketItem {
BlockPos pos = blockHitResult.getBlockPos(); BlockPos pos = blockHitResult.getBlockPos();
BlockState blockState = world.getBlockState(pos); BlockState blockState = world.getBlockState(pos);
Block block = blockState.getBlock(); Block block = blockState.getBlock();
if (block instanceof FluidFillable && ((FluidFillable) block).canFillWithFluid(world, pos, blockState, fluid)) { if (block instanceof FluidFillable && ((FluidFillable) block).canFillWithFluid(user, world, pos, blockState, fluid)) {
if (placeFluid(user, world, pos, blockHitResult)) { if (placeFluid(user, world, pos, blockHitResult)) {
return TypedActionResult.success(BucketItem.getEmptiedStack(itemStack, user), world.isClient()); return TypedActionResult.success(BucketItem.getEmptiedStack(itemStack, user), world.isClient());
} }
@@ -63,7 +63,7 @@ public class PortalFluidBucketItem extends BucketItem {
Block block = blockState.getBlock(); Block block = blockState.getBlock();
boolean shouldTryPlace = blockState.isAir() boolean shouldTryPlace = blockState.isAir()
|| blockState.canBucketPlace(this.fluid) || blockState.canBucketPlace(this.fluid)
|| block instanceof FluidFillable && ((FluidFillable) block).canFillWithFluid(world, pos, blockState, this.fluid); || block instanceof FluidFillable && ((FluidFillable) block).canFillWithFluid(player, world, pos, blockState, this.fluid);
if (!shouldTryPlace) { if (!shouldTryPlace) {
return hitResult != null && this.placeFluid(player, world, hitResult.getBlockPos().offset(hitResult.getSide()), null); return hitResult != null && this.placeFluid(player, world, hitResult.getBlockPos().offset(hitResult.getSide()), null);
} }

View File

@@ -0,0 +1,17 @@
package quimufu.colourful_portals.util;
import static java.lang.Float.NaN;
public class AdditionalMath {
public static float root(float x, int nThRoot){
if(nThRoot<=0){
return NaN;
}
if(x<0 && nThRoot%2 == 1){
return -(float) Math.pow(-x, 1D / nThRoot);
}
return (float) Math.pow(x, 1D / nThRoot);
}
}

View File

@@ -0,0 +1,20 @@
package quimufu.colourful_portals.util;
@FunctionalInterface
public interface Procedure {
void run();
default Procedure andThen(Procedure after){
return () -> {
this.run();
after.run();
};
}
default Procedure compose(Procedure before){
return () -> {
before.run();
this.run();
};
}
}

View File

@@ -0,0 +1,77 @@
package quimufu.colourful_portals.util;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import java.util.LinkedList;
import java.util.List;
public class RaycastHelper {
public static List<BlockPos> passedBlocks(Vec3d from, Vec3d to) {
LinkedList<BlockPos> psitions = new LinkedList<>();
Vec3d difference = to.subtract(from);
if(to.equals(from)){
return List.of();
}
double[] diff = new double[]{difference.getX(), difference.getY(), difference.getZ()};
double[] start = new double[]{from.getX(), from.getY(), from.getZ()};
double[] pos = new double[]{from.getX(), from.getY(), from.getZ()};
BlockPos currentBlockPos = BlockPos.ofFloored(pos[0], pos[1], pos[2]);
double currDelta = 0;
while (currDelta < 1) {
psitions.add(currentBlockPos);
double minMissing = 1;
for (int i = 0; i < 3; i++) {
if (diff[i] == 0) {
continue;
}
double curr;
if ((curr = ((round(pos[i], Math.signum(diff[i])) - pos[i]) / diff[i])) < minMissing) {
minMissing = curr;
}
}
currDelta += minMissing;
for (int i = 0; i < 3; i++) {
pos[i] = start[i] + currDelta * diff[i];
}
currentBlockPos = BlockPos.ofFloored(pos[0], pos[1], pos[2]);
}
BlockPos last = BlockPos.ofFloored(to);
if(!psitions.getLast().equals(last)){
psitions.add(last);
}
return psitions;
}
private static double round(double val, double signum) {
if (signum > 0) {
return Math.floor(val + signum);
}
return Math.ceil(val + signum);
}
public static boolean passedOnAxis(Vec3d from, Vec3d to, BlockPos blockPos, Direction.Axis axis) {
double startAA = from.getComponentAlongAxis(axis);
double centerAA = blockPos.toCenterPos().getComponentAlongAxis(axis);
Vec3d diff = to.subtract(from);
double differenceAA = diff.getComponentAlongAxis(axis);
double hitAfter = (centerAA - startAA) / differenceAA;
if (differenceAA == 0 || hitAfter > 1 || hitAfter < 0) {
return false;
}
return BlockPos.ofFloored(from.add(diff.multiply(hitAfter)))
.equals(blockPos);
}
}

View File

@@ -0,0 +1,358 @@
package quimufu.colourful_portals.util;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.registry.Registries;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTask;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ChunkTicketType;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.*;
import net.minecraft.world.Heightmap;
import net.minecraft.world.TeleportTarget;
import net.minecraft.world.World;
import net.minecraft.world.border.WorldBorder;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.dimension.DimensionType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.joml.Random;
import org.joml.Vector3f;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import quimufu.colourful_portals.ColourfulPortalsMod;
import quimufu.colourful_portals.config.ColourfulPortalConfig;
import quimufu.colourful_portals.mixin.ServerWorldAccessor;
import quimufu.colourful_portals.portal.PortalHelper;
import java.util.Comparator;
import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class TeleportHelper extends ServerTask {
private static final Logger log = LoggerFactory.getLogger(TeleportHelper.class);
public static final int TICK_DELAY = 2;
private final Random random;
private final MinecraftServer minecraftServer;
private final ConcurrentSkipListSet<TeleportRequest> toTeleport = new ConcurrentSkipListSet<>();
private static TeleportHelper INSTANCE;
private final AtomicBoolean tickScheduled = new AtomicBoolean(false);
private int lastExecTick;
private final ScheduledExecutorService sleeperExecutor = Executors.newSingleThreadScheduledExecutor();
private static TeleportHelper getInstance(MinecraftServer server) {
if (INSTANCE == null) {
INSTANCE = new TeleportHelper(server);
}
return INSTANCE;
}
private TeleportHelper(MinecraftServer server) {
super(server.getTicks(), null);
this.minecraftServer = server;
this.random = new Random();
}
private boolean targetValid(Entity entity, ServerWorld world, Vector3f targetPos, boolean force) {
Box boundingBox = entity.getDimensions(entity.getPose())
.getBoxAt(new Vec3d(targetPos))
.stretch(0, -1, 0);
boolean foundAir = false;
boolean foundNonAir = false;
for (BlockPos pos : PortalHelper.blockPosInBox(boundingBox)) {
BlockState blockState = world.getBlockState(pos);
if (!blockState.isIn(ColourfulPortalsMod.COLOURFUL_PEARL_REPLACEABLE_BLOCK_TAG)) {
ColourfulPortalsMod.LOGGER.info("invalid location at {}, block {}", pos, Registries.BLOCK.getId(blockState.getBlock()));
return false;
}
if (blockState.isAir()) {
foundAir = true;
} else {
foundNonAir = true;
}
}
return (foundNonAir) && (foundAir || force);
}
private @Nullable TeleportTarget getTarget(Entity entity, ServerWorld serverWorld, Vector3f from, ServerWorld targetWorld, BlockPos targetBlockPos) {
minecraftServer.getProfiler().push("getChunk");
Chunk chunk = targetWorld.getChunk(targetBlockPos);
minecraftServer.getProfiler().pop();
if (isEmptyPosition(targetWorld, targetBlockPos, chunk)) {
log.info("empty at {}, retrying", targetBlockPos);
TeleportHelper.markForTeleport(entity, from, serverWorld);
return null;
}
Vector3f targetPos = Vec3d.of(targetBlockPos).toVector3f();
int width = MathHelper.ceil(entity.getBoundingBox().getLengthX());
int depth = MathHelper.ceil(entity.getBoundingBox().getLengthZ());
targetPos.add((width % 2) * 0.5F, 0, (depth % 2) * 0.5F);
minecraftServer.getProfiler().push("valid check");
int tries = 0;
while (!targetValid(entity, targetWorld, targetPos, tries > targetWorld.getLogicalHeight() * 2)) {
if (tries > targetWorld.getLogicalHeight() * 4) {
break;
}
targetPos.y = getTargetY(targetWorld);
tries++;
}
minecraftServer.getProfiler().pop();
return new TeleportTarget(targetWorld, new Vec3d(targetPos), entity.getVelocity(), entity.getYaw(), entity.getPitch(), TeleportTarget.NO_OP);
}
private boolean isEmptyPosition(ServerWorld world, BlockPos target, Chunk chunk) {
minecraftServer.getProfiler().push("sampleHeightmap");
int height = chunk.sampleHeightmap(Heightmap.Type.WORLD_SURFACE, target.getX() & 0xF, target.getZ() & 0xF) + 1;
minecraftServer.getProfiler().pop();
return height == world.getBottomY();
}
private @Nullable ServerWorld getTargetWorld(ServerWorld serverWorld) {
if (random.nextFloat() < ColourfulPortalConfig.pearlSameDimensionLikelihood) {
return serverWorld;
}
MinecraftServer server = serverWorld.getServer();
ColourfulPortalConfig.addMissingDimensionsToConfig(server);
Identifier targetWorldId = ColourfulPortalsMod.DIMENSION_WEIGHTS_COLOURFUL_PEARL.getWeighted(random);
return server.getWorld(RegistryKey.of(RegistryKeys.WORLD, targetWorldId));
}
private BlockPos getTargetPos(ServerWorld fromWorld, ServerWorld toWorld, Vector3f pos) {
double distance = ColourfulPortalConfig.minPearlDistance +
random.nextFloat() *
(ColourfulPortalConfig.maxPearlDistance - ColourfulPortalConfig.minPearlDistance);
double angle = Math.PI * 2 * random.nextFloat();
int targetY = getTargetY(toWorld);
Vec3d target = new Vec3d(pos.x + Math.cos(angle) * distance, targetY, pos.z + Math.sin(angle) * distance);
WorldBorder worldBorder = toWorld.getWorldBorder();
double d = DimensionType.getCoordinateScaleFactor(fromWorld.getDimension(), toWorld.getDimension());
return worldBorder.clamp(target.getX() * d, target.getY(), target.getZ() * d);
}
private int getTargetY(ServerWorld toWorld) {
return toWorld.getBottomY() + 4 + (random.nextInt(toWorld.getLogicalHeight() - 8));
}
public void tryTeleport(Entity entity, Vector3f from, ServerWorld serverWorld, BlockPos to, ServerWorld toWorld) {
if (entity == null) {
return;
}
if (entity.hasVehicle()) {
entity.detach();
}
minecraftServer.getProfiler().push("getTarget");
TeleportTarget target = getTarget(entity, serverWorld, from, toWorld, to);
minecraftServer.getProfiler().pop();
if (target == null) {
return;
}
prepareTarget(target, entity);
((ServerWorldAccessor) serverWorld).getEntityList()
.add(entity);
minecraftServer.getProfiler().push("teleportTo");
if (entity instanceof ServerPlayerEntity serverPlayerEntity) {
if (serverPlayerEntity.networkHandler.isConnectionOpen()) {
Entity entityAfterTeleport = entity.teleportTo(target);
if (entityAfterTeleport == null) {
minecraftServer.getProfiler().pop();
return;
}
entityAfterTeleport.onLanding();
serverPlayerEntity.clearCurrentExplosion();
entityAfterTeleport.damage(entityAfterTeleport.getDamageSources().fall(), 5.0f);
serverPlayerEntity.playSoundToPlayer(SoundEvents.ENTITY_PLAYER_TELEPORT, SoundCategory.PLAYERS, 1F, 0.75F);
}
} else {
Entity entityAfterTeleport = entity.teleportTo(target);
if (entityAfterTeleport == null) {
minecraftServer.getProfiler().pop();
return;
}
entityAfterTeleport.onLanding();
serverWorld.playSound(entityAfterTeleport, entityAfterTeleport.getBlockPos(),
SoundEvents.ENTITY_PLAYER_TELEPORT, SoundCategory.NEUTRAL,
1F, 0.75F);
}
minecraftServer.getProfiler().pop();
}
private void prepareTarget(TeleportTarget target, Entity entity) {
Box entityBody = entity.getDimensions(entity.getPose()).getBoxAt(target.pos());
double minY = entityBody.getMinPos().getY();
Box floor = entityBody.withMinY(minY - 1).withMaxY(minY - 0.5);
for (BlockPos pos : PortalHelper.blockPosInBox(entityBody)) {
target.world().setBlockState(pos, ColourfulPortalsMod.COLOURFUL_AIR.getRandomState(random));
}
for (BlockPos pos : PortalHelper.blockPosInBox(floor)) {
target.world().setBlockState(pos, Blocks.STONE.getDefaultState());
}
}
private static boolean canTeleportEntityTo(Entity entity, World world) {
if (entity.getWorld().getRegistryKey() == world.getRegistryKey()) {
if (entity instanceof LivingEntity livingEntity) {
return livingEntity.isAlive() && !livingEntity.isSleeping();
}
return entity.isAlive();
}
return entity.canUsePortals(true);
}
private void addTeleportRequest(Entity entity, Vector3f from,
ServerWorld fromWorld,
boolean originalInvulnerability) {
ServerWorld targetWorld = getTargetWorld(fromWorld);
if (targetWorld == null) {
log.info("couldn't find target world");
return;
}
BlockPos targetBlockPos = getTargetPos(fromWorld, targetWorld, from);
if (entity instanceof MobEntity mobEntity
&& !mobEntity.isPersistent()
&& !mobEntity.cannotDespawn()
&& mobEntity.canImmediatelyDespawn(targetBlockPos.getSquaredDistance(new Vec3d(from)))
&& !targetWorld.isChunkLoaded(targetBlockPos)) {
log.info("despawning {} instead of teleporting it OOB", mobEntity.getName().getString());
((ServerWorldAccessor) fromWorld).getEntityList().add(entity);
mobEntity.discard();
return;
}
if (!canTeleportEntityTo(entity, targetWorld)) {
return;
}
targetWorld.getChunkManager()
.addTicket(ChunkTicketType.PORTAL, new ChunkPos(targetBlockPos), 2, targetBlockPos);
toTeleport.add(new TeleportRequest(entity.getUuid(),
from,
fromWorld.getRegistryKey().getValue(),
originalInvulnerability,
targetBlockPos,
targetWorld.getRegistryKey().getValue()
));
if (!tickScheduled.getAndSet(true)) {
long nanosPerTick = minecraftServer.getTickManager().getNanosPerTick();
sleeperExecutor.schedule(() -> minecraftServer.send(this), nanosPerTick * 16, TimeUnit.NANOSECONDS);
}
}
@Override
public void run() {
log.info("{}", minecraftServer.getTicks());
int nextExecIn = (lastExecTick + TICK_DELAY) - minecraftServer.getTicks();
long nanosPerTick = minecraftServer.getTickManager().getNanosPerTick();
if (nextExecIn > 0) {
minecraftServer.send(this);
return;
}
this.lastExecTick = minecraftServer.getTicks();
minecraftServer.getProfiler().push("teleportLoaded");
teleportLoaded();
minecraftServer.getProfiler().pop();
log.info("there are currently {} entities awaiting teleportation", toTeleport.size());
if (!toTeleport.isEmpty()) {
minecraftServer.send(this);
} else {
tickScheduled.set(false);
}
}
private void teleportLoaded() {
Iterator<TeleportRequest> iterator = toTeleport.iterator();
while (iterator.hasNext()) {
TeleportRequest teleportRequest = iterator.next();
if (teleportRequest == null) {
//empty for some reason!
iterator.remove();
log.error("null teleportRequest");
return;
}
ServerWorld fromWorld = minecraftServer.getWorld(RegistryKey.of(RegistryKeys.WORLD, teleportRequest.fromWorldId));
if (fromWorld == null) {
iterator.remove();
log.error("lost from world");
return;
}
ServerWorld toWorld = minecraftServer.getWorld(RegistryKey.of(RegistryKeys.WORLD, teleportRequest.toWorldId));
if (toWorld == null) {
iterator.remove();
log.error("lost to world");
return;
}
Entity entity = fromWorld.getEntity(teleportRequest.entity);
if (entity == null) {
iterator.remove();
log.error("lost entity");
return;
}
int x = teleportRequest.to.getX();
int z = teleportRequest.to.getZ();
if (toWorld.getChunkManager().isChunkLoaded(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(z))) {
log.info("chunkLoaded {}", teleportRequest.to);
minecraftServer.getProfiler().push("tryTeleport");
tryTeleport(entity, teleportRequest.from, fromWorld, teleportRequest.to, toWorld);
minecraftServer.getProfiler().pop();
iterator.remove();
return;
} else {
toWorld.getChunkManager()
.addTicket(ChunkTicketType.PORTAL, new ChunkPos(teleportRequest.to), 2, teleportRequest.to);
}
}
}
public static void markForTeleport(Entity entity, Vector3f from, ServerWorld serverWorld) {
((ServerWorldAccessor) serverWorld).getEntityList()
.remove(entity);
boolean originalInvulnerability = entity.isInvulnerable();
entity.setInvulnerable(true);
getInstance(serverWorld.getServer())
.addTeleportRequest(entity, from, serverWorld, originalInvulnerability);
}
private record TeleportRequest(UUID entity, Vector3f from,
Identifier fromWorldId,
boolean originalInvulnerability,
BlockPos to,
Identifier toWorldId) implements Comparable<TeleportRequest> {
@Override
public int compareTo(@NotNull TeleportHelper.TeleportRequest other) {
Comparator<TeleportRequest> comparator = Comparator
.comparing((TeleportRequest teleportRequest) -> teleportRequest.entity);
return comparator.compare(this, other);
}
}
}

View File

@@ -0,0 +1,36 @@
package quimufu.colourful_portals.util;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.util.math.Box;
import net.minecraft.world.World;
import org.joml.Random;
import org.joml.Vector3f;
public class TeleportParticleHelper {
private static final Random random = new Random();
public static void spawnTeleportationParticles(Entity entity, World world) {
for (int i = 0; i < 64; ++i) {
Box box = entity.getBoundingBox();
float xOffset = (float) ((AdditionalMath.root(random.nextFloat() * 2 - 1, 3) / 2) * box.getLengthX() * 2);
float yOffset = (float) (AdditionalMath.root(random.nextFloat(), 3) * box.getLengthY() * 2);
float zOffset = (float) ((AdditionalMath.root(random.nextFloat() * 2 - 1, 3) / 2) * box.getLengthZ() * 2);
Vector3f offset = new Vector3f(xOffset, yOffset, zOffset);
EntityDimensions dimensions = EntityDimensions.fixed(0.25f, 0.25f);
Vector3f endPos = randomInside(dimensions.getBoxAt(entity.getPos())).add(offset);
world.addParticle(ParticleTypes.PORTAL,
endPos.x, endPos.y, endPos.z,
-offset.x, -offset.y, -offset.z);
}
}
private static Vector3f randomInside(Box box) {
return box.getMinPos().toVector3f()
.add(((float) box.getLengthX()) * random.nextFloat(),
((float) box.getLengthY()) * random.nextFloat(),
((float) box.getLengthZ()) * random.nextFloat());
}
}

View File

@@ -1,7 +1,39 @@
{ {
"item.colourful_portals.portal_fluid_bucket": "Farbeimer",
"block.colourful_portals.portal_block": "Farbenfrohes Portal", "block.colourful_portals.portal_block": "Farbenfrohes Portal",
"block.colourful_portals.portal_fluid_block": "Farbenfrohe Flüssigkeit",
"colourful_portals.midnightconfig.disableImmersivePortals": "Immersive Portals integration deaktivieren",
"colourful_portals.midnightconfig.black": "Blöcke, die schwarze Portale erzeugen",
"colourful_portals.midnightconfig.blue": "Blöcke, die blaue Portale erzeugen",
"colourful_portals.midnightconfig.brown": "Blöcke, die braune Portale erzeugen",
"colourful_portals.midnightconfig.cyan": "Blöcke, die cyanfarbene Portale erzeugen",
"colourful_portals.midnightconfig.explanation1": "Diese Konfiguration erlaubt es, zusätzliche/andere Portalblocke zu definieren.\nPortale aus diesen verbinden sich mit anderen aus dem selben Block.",
"colourful_portals.midnightconfig.explanation2": "Die Liste, der der Block hinzugefügt wird, bestimmt dabei nur die Portalfarbe.\nÄnderungen treten für neue Portale oder bei Aktualisierung eines Portals in Kraft.",
"colourful_portals.midnightconfig.gray": "Blöcke, die graue Portale erzeugen",
"colourful_portals.midnightconfig.green": "Blöcke, die grüne Portale erzeugen",
"colourful_portals.midnightconfig.light_blue": "Blöcke, die hellblaue Portale erzeugen",
"colourful_portals.midnightconfig.light_gray": "Blöcke, die hellgraue Portale erzeugen",
"colourful_portals.midnightconfig.lime": "Blöcke, die limonenfarbene Portale erzeugen",
"colourful_portals.midnightconfig.magenta": "Blöcke, die magentafarbene Portale erzeugen",
"colourful_portals.midnightconfig.none": "Blöcke, die vollständig transparente Portale erzeugen",
"colourful_portals.midnightconfig.orange": "Blöcke, die orangefarbene Portale erzeugen",
"colourful_portals.midnightconfig.pink": "Blöcke, die rosa Portale erzeugen",
"colourful_portals.midnightconfig.purple": "Blöcke, die violette Portale erzeugen",
"colourful_portals.midnightconfig.red": "Blöcke, die rote Portale erzeugen",
"colourful_portals.midnightconfig.white": "Blöcke, die weiße Portale erzeugen",
"colourful_portals.midnightconfig.yellow": "Blöcke, die gelbe Portale erzeugen",
"colourful_portals.midnightconfig.blockyPortalFluid": "Das alte Aussehen von der farbenfrohen Flüssigkeit wiederherstellen",
"colourful_portals.midnightconfig.pearlDimensionWeights": "Die Gewichtungen für verschiedene Dimensionen bei Dimensionswechsel (bearbeitbar nur über die Konfigurationsdatei)",
"colourful_portals.midnightconfig.pearlSameDimensionLikelihood": "Die Wahrscheinlichkeit bei einer Teleportation mit einer Farbenfrohen Perle in der selben Dimension zu verbleiben",
"colourful_portals.midnightconfig.maxPearlDistance": "Der Mindestabstand, um den der Spieler von einer farbenfrohen Perle teleportiert werden",
"colourful_portals.midnightconfig.minPearlDistance": "Der Maximalabstand, um den der Spieler von einer farbenfrohen Perle teleportiert werden",
"colourful_portals.midnightconfig.category.portal_colours": "Portalblöcke",
"colourful_portals.midnightconfig.category.visuals": "Visuell",
"colourful_portals.midnightconfig.category.colourful_pearl": "Farbenfrohe Perle",
"colourful_portals.midnightconfig.category.integrations": "Mod Integrationen",
"subtitles.colourful_portals.entity.colourful_pearl.teleport_away": "Farbenfrohe Perle teleportiert weg",
"item.colourful_portals.colour_blob_bright": "Heller Farbmix", "item.colourful_portals.colour_blob_bright": "Heller Farbmix",
"item.colourful_portals.colour_blob_dark": "Dunkler Farbmix", "item.colourful_portals.colour_blob_dark": "Dunkler Farbmix",
"block.colourful_portals.portal_fluid_block": "Farbenfrohe Flüssigkeit" "item.colourful_portals.portal_fluid_bucket": "Farbeimer",
"tag.block.c.slime_balls": "Schleimbälle",
"tag.block.c.crystals": "Kristalle"
} }

View File

@@ -1,7 +1,39 @@
{ {
"item.colourful_portals.portal_fluid_bucket": "Colourful Bucket", "block.colourful_portals.portal_block": "Colourful Portal",
"block.colourful_portals.portal_fluid_block": "Colourful Fluid",
"colourful_portals.midnightconfig.disableImmersivePortals": "Disable Immersive Portals integration",
"colourful_portals.midnightconfig.black": "Blocks that create black portals",
"colourful_portals.midnightconfig.blue": "Blocks that create blue portals",
"colourful_portals.midnightconfig.brown": "Blocks that create brown portals",
"colourful_portals.midnightconfig.cyan": "Blocks that create cyan portals",
"colourful_portals.midnightconfig.explanation1": "This configuration allows you to define additional/other portal blocks.\nPortals from these connect to others from the same block.",
"colourful_portals.midnightconfig.explanation2": "The list to which the block is added only determines the portal color.\nChanges take effect for new portals or when a portal is updated.",
"colourful_portals.midnightconfig.gray": "Blocks that create gray portals",
"colourful_portals.midnightconfig.green": "Blocks that create green portals",
"colourful_portals.midnightconfig.light_blue": "Blocks that create light blue portals",
"colourful_portals.midnightconfig.light_gray": "Blocks that create light gray portals",
"colourful_portals.midnightconfig.lime": "Blocks that create lime portals",
"colourful_portals.midnightconfig.magenta": "Blocks that create magenta portals",
"colourful_portals.midnightconfig.none": "Blocks that create fully transparent portals",
"colourful_portals.midnightconfig.orange": "Blocks that create orange portals",
"colourful_portals.midnightconfig.pink": "Blocks that create pink portals",
"colourful_portals.midnightconfig.purple": "Blocks that create purple portals",
"colourful_portals.midnightconfig.red": "Blocks that create red portals",
"colourful_portals.midnightconfig.white": "Blocks that create white portals",
"colourful_portals.midnightconfig.yellow": "Blocks that create yellow portals",
"colourful_portals.midnightconfig.blockyPortalFluid": "Use the old Portal Fluid look",
"colourful_portals.midnightconfig.pearlDimensionWeights": "The weight for the different dimensions on dimension switch (only editable in config file)",
"colourful_portals.midnightconfig.pearlSameDimensionLikelihood": "Likelihood for a colourful pearl to teleport within the same dimension",
"colourful_portals.midnightconfig.maxPearlDistance": "The maximal distance a colourful pearl will teleport the player",
"colourful_portals.midnightconfig.minPearlDistance": "The minimal distance a colourful pearl will teleport the player",
"colourful_portals.midnightconfig.category.portal_colours": "Portal Blocks",
"colourful_portals.midnightconfig.category.visuals": "Visual",
"colourful_portals.midnightconfig.category.colourful_pearl": "Colourful Pearl",
"colourful_portals.midnightconfig.category.integrations": "Mod Integrations",
"subtitles.colourful_portals.entity.colourful_pearl.teleport_away": "Colourful pearl teleporting away",
"item.colourful_portals.colour_blob_bright": "Bright Colour Mix", "item.colourful_portals.colour_blob_bright": "Bright Colour Mix",
"item.colourful_portals.colour_blob_dark": "Dark Colour Mix", "item.colourful_portals.colour_blob_dark": "Dark Colour Mix",
"block.colourful_portals.portal_block": "Colourful Portal", "item.colourful_portals.portal_fluid_bucket": "Colourful Bucket",
"block.colourful_portals.portal_fluid_block": "Colourful Fluid" "tag.block.c.slime_balls": "Slime Balls",
"tag.block.c.crystals": "Crystals"
} }

View File

@@ -0,0 +1,24 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "minecraft:item/honey_bottle"
},
"overrides": [
{ "predicate": { "color_id": 0 }, "model": "colourful_portals:item/colourful_air_bottle/white" },
{ "predicate": { "color_id": 1 }, "model": "colourful_portals:item/colourful_air_bottle/orange" },
{ "predicate": { "color_id": 2 }, "model": "colourful_portals:item/colourful_air_bottle/magenta" },
{ "predicate": { "color_id": 3 }, "model": "colourful_portals:item/colourful_air_bottle/light_blue" },
{ "predicate": { "color_id": 4 }, "model": "colourful_portals:item/colourful_air_bottle/yellow" },
{ "predicate": { "color_id": 5 }, "model": "colourful_portals:item/colourful_air_bottle/lime" },
{ "predicate": { "color_id": 6 }, "model": "colourful_portals:item/colourful_air_bottle/pink" },
{ "predicate": { "color_id": 7 }, "model": "colourful_portals:item/colourful_air_bottle/gray" },
{ "predicate": { "color_id": 8 }, "model": "colourful_portals:item/colourful_air_bottle/light_gray" },
{ "predicate": { "color_id": 9 }, "model": "colourful_portals:item/colourful_air_bottle/cyan" },
{ "predicate": { "color_id": 10 }, "model": "colourful_portals:item/colourful_air_bottle/purple" },
{ "predicate": { "color_id": 11 }, "model": "colourful_portals:item/colourful_air_bottle/blue" },
{ "predicate": { "color_id": 12 }, "model": "colourful_portals:item/colourful_air_bottle/brown" },
{ "predicate": { "color_id": 13 }, "model": "colourful_portals:item/colourful_air_bottle/green" },
{ "predicate": { "color_id": 14 }, "model": "colourful_portals:item/colourful_air_bottle/red" },
{ "predicate": { "color_id": 15 }, "model": "colourful_portals:item/colourful_air_bottle/black" }
]
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/black"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/blue"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/brown"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/cyan"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/gray"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/green"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/light_blue"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/light_gray"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/lime"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/magenta"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/orange"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/pink"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/purple"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/red"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/white"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_air_bottle/yellow"
}
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "colourful_portals:item/colourful_pearl"
}
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/black_colourful_air_0_0",
"colourful_portals:colourful_air_particle/black_colourful_air_0_1",
"colourful_portals:colourful_air_particle/black_colourful_air_1_0",
"colourful_portals:colourful_air_particle/black_colourful_air_1_1",
"colourful_portals:colourful_air_particle/black_colourful_air_2_0",
"colourful_portals:colourful_air_particle/black_colourful_air_2_1",
"colourful_portals:colourful_air_particle/black_colourful_air_3_0",
"colourful_portals:colourful_air_particle/black_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/blue_colourful_air_0_0",
"colourful_portals:colourful_air_particle/blue_colourful_air_0_1",
"colourful_portals:colourful_air_particle/blue_colourful_air_1_0",
"colourful_portals:colourful_air_particle/blue_colourful_air_1_1",
"colourful_portals:colourful_air_particle/blue_colourful_air_2_0",
"colourful_portals:colourful_air_particle/blue_colourful_air_2_1",
"colourful_portals:colourful_air_particle/blue_colourful_air_3_0",
"colourful_portals:colourful_air_particle/blue_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/brown_colourful_air_0_0",
"colourful_portals:colourful_air_particle/brown_colourful_air_0_1",
"colourful_portals:colourful_air_particle/brown_colourful_air_1_0",
"colourful_portals:colourful_air_particle/brown_colourful_air_1_1",
"colourful_portals:colourful_air_particle/brown_colourful_air_2_0",
"colourful_portals:colourful_air_particle/brown_colourful_air_2_1",
"colourful_portals:colourful_air_particle/brown_colourful_air_3_0",
"colourful_portals:colourful_air_particle/brown_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,132 @@
{
"textures": [
"black_colourful_air_0_0",
"black_colourful_air_0_1",
"black_colourful_air_1_0",
"black_colourful_air_1_1",
"black_colourful_air_2_0",
"black_colourful_air_2_1",
"black_colourful_air_3_0",
"black_colourful_air_3_1",
"blue_colourful_air_0_0",
"blue_colourful_air_0_1",
"blue_colourful_air_1_0",
"blue_colourful_air_1_1",
"blue_colourful_air_2_0",
"blue_colourful_air_2_1",
"blue_colourful_air_3_0",
"blue_colourful_air_3_1",
"brown_colourful_air_0_0",
"brown_colourful_air_0_1",
"brown_colourful_air_1_0",
"brown_colourful_air_1_1",
"brown_colourful_air_2_0",
"brown_colourful_air_2_1",
"brown_colourful_air_3_0",
"brown_colourful_air_3_1",
"cyan_colourful_air_0_0",
"cyan_colourful_air_0_1",
"cyan_colourful_air_1_0",
"cyan_colourful_air_1_1",
"cyan_colourful_air_2_0",
"cyan_colourful_air_2_1",
"cyan_colourful_air_3_0",
"cyan_colourful_air_3_1",
"green_colourful_air_0_0",
"green_colourful_air_0_1",
"green_colourful_air_1_0",
"green_colourful_air_1_1",
"green_colourful_air_2_0",
"green_colourful_air_2_1",
"green_colourful_air_3_0",
"green_colourful_air_3_1",
"grey_colourful_air_0_0",
"grey_colourful_air_0_1",
"grey_colourful_air_1_0",
"grey_colourful_air_1_1",
"grey_colourful_air_2_0",
"grey_colourful_air_2_1",
"grey_colourful_air_3_0",
"grey_colourful_air_3_1",
"light_blue_colourful_air_0_0",
"light_blue_colourful_air_0_1",
"light_blue_colourful_air_1_0",
"light_blue_colourful_air_1_1",
"light_blue_colourful_air_2_0",
"light_blue_colourful_air_2_1",
"light_blue_colourful_air_3_0",
"light_blue_colourful_air_3_1",
"light_grey_colourful_air_0_0",
"light_grey_colourful_air_0_1",
"light_grey_colourful_air_1_0",
"light_grey_colourful_air_1_1",
"light_grey_colourful_air_2_0",
"light_grey_colourful_air_2_1",
"light_grey_colourful_air_3_0",
"light_grey_colourful_air_3_1",
"lime_colourful_air_0_0",
"lime_colourful_air_0_1",
"lime_colourful_air_1_0",
"lime_colourful_air_1_1",
"lime_colourful_air_2_0",
"lime_colourful_air_2_1",
"lime_colourful_air_3_0",
"lime_colourful_air_3_1",
"magenta_colourful_air_0_0",
"magenta_colourful_air_0_1",
"magenta_colourful_air_1_0",
"magenta_colourful_air_1_1",
"magenta_colourful_air_2_0",
"magenta_colourful_air_2_1",
"magenta_colourful_air_3_0",
"magenta_colourful_air_3_1",
"orange_colourful_air_0_0",
"orange_colourful_air_0_1",
"orange_colourful_air_1_0",
"orange_colourful_air_1_1",
"orange_colourful_air_2_0",
"orange_colourful_air_2_1",
"orange_colourful_air_3_0",
"orange_colourful_air_3_1",
"pink_colourful_air_0_0",
"pink_colourful_air_0_1",
"pink_colourful_air_1_0",
"pink_colourful_air_1_1",
"pink_colourful_air_2_0",
"pink_colourful_air_2_1",
"pink_colourful_air_3_0",
"pink_colourful_air_3_1",
"purple_colourful_air_0_0",
"purple_colourful_air_0_1",
"purple_colourful_air_1_0",
"purple_colourful_air_1_1",
"purple_colourful_air_2_0",
"purple_colourful_air_2_1",
"purple_colourful_air_3_0",
"purple_colourful_air_3_1",
"red_colourful_air_0_0",
"red_colourful_air_0_1",
"red_colourful_air_1_0",
"red_colourful_air_1_1",
"red_colourful_air_2_0",
"red_colourful_air_2_1",
"red_colourful_air_3_0",
"red_colourful_air_3_1",
"white_colourful_air_0_0",
"white_colourful_air_0_1",
"white_colourful_air_1_0",
"white_colourful_air_1_1",
"white_colourful_air_2_0",
"white_colourful_air_2_1",
"white_colourful_air_3_0",
"white_colourful_air_3_1",
"yellow_colourful_air_0_0",
"yellow_colourful_air_0_1",
"yellow_colourful_air_1_0",
"yellow_colourful_air_1_1",
"yellow_colourful_air_2_0",
"yellow_colourful_air_2_1",
"yellow_colourful_air_3_0",
"yellow_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/cyan_colourful_air_0_0",
"colourful_portals:colourful_air_particle/cyan_colourful_air_0_1",
"colourful_portals:colourful_air_particle/cyan_colourful_air_1_0",
"colourful_portals:colourful_air_particle/cyan_colourful_air_1_1",
"colourful_portals:colourful_air_particle/cyan_colourful_air_2_0",
"colourful_portals:colourful_air_particle/cyan_colourful_air_2_1",
"colourful_portals:colourful_air_particle/cyan_colourful_air_3_0",
"colourful_portals:colourful_air_particle/cyan_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/grey_colourful_air_0_0",
"colourful_portals:colourful_air_particle/grey_colourful_air_0_1",
"colourful_portals:colourful_air_particle/grey_colourful_air_1_0",
"colourful_portals:colourful_air_particle/grey_colourful_air_1_1",
"colourful_portals:colourful_air_particle/grey_colourful_air_2_0",
"colourful_portals:colourful_air_particle/grey_colourful_air_2_1",
"colourful_portals:colourful_air_particle/grey_colourful_air_3_0",
"colourful_portals:colourful_air_particle/grey_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/green_colourful_air_0_0",
"colourful_portals:colourful_air_particle/green_colourful_air_0_1",
"colourful_portals:colourful_air_particle/green_colourful_air_1_0",
"colourful_portals:colourful_air_particle/green_colourful_air_1_1",
"colourful_portals:colourful_air_particle/green_colourful_air_2_0",
"colourful_portals:colourful_air_particle/green_colourful_air_2_1",
"colourful_portals:colourful_air_particle/green_colourful_air_3_0",
"colourful_portals:colourful_air_particle/green_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/light_blue_colourful_air_0_0",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_0_1",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_1_0",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_1_1",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_2_0",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_2_1",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_3_0",
"colourful_portals:colourful_air_particle/light_blue_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/light_grey_colourful_air_0_0",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_0_1",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_1_0",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_1_1",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_2_0",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_2_1",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_3_0",
"colourful_portals:colourful_air_particle/light_grey_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/lime_colourful_air_0_0",
"colourful_portals:colourful_air_particle/lime_colourful_air_0_1",
"colourful_portals:colourful_air_particle/lime_colourful_air_1_0",
"colourful_portals:colourful_air_particle/lime_colourful_air_1_1",
"colourful_portals:colourful_air_particle/lime_colourful_air_2_0",
"colourful_portals:colourful_air_particle/lime_colourful_air_2_1",
"colourful_portals:colourful_air_particle/lime_colourful_air_3_0",
"colourful_portals:colourful_air_particle/lime_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/magenta_colourful_air_0_0",
"colourful_portals:colourful_air_particle/magenta_colourful_air_0_1",
"colourful_portals:colourful_air_particle/magenta_colourful_air_1_0",
"colourful_portals:colourful_air_particle/magenta_colourful_air_1_1",
"colourful_portals:colourful_air_particle/magenta_colourful_air_2_0",
"colourful_portals:colourful_air_particle/magenta_colourful_air_2_1",
"colourful_portals:colourful_air_particle/magenta_colourful_air_3_0",
"colourful_portals:colourful_air_particle/magenta_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/orange_colourful_air_0_0",
"colourful_portals:colourful_air_particle/orange_colourful_air_0_1",
"colourful_portals:colourful_air_particle/orange_colourful_air_1_0",
"colourful_portals:colourful_air_particle/orange_colourful_air_1_1",
"colourful_portals:colourful_air_particle/orange_colourful_air_2_0",
"colourful_portals:colourful_air_particle/orange_colourful_air_2_1",
"colourful_portals:colourful_air_particle/orange_colourful_air_3_0",
"colourful_portals:colourful_air_particle/orange_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/pink_colourful_air_0_0",
"colourful_portals:colourful_air_particle/pink_colourful_air_0_1",
"colourful_portals:colourful_air_particle/pink_colourful_air_1_0",
"colourful_portals:colourful_air_particle/pink_colourful_air_1_1",
"colourful_portals:colourful_air_particle/pink_colourful_air_2_0",
"colourful_portals:colourful_air_particle/pink_colourful_air_2_1",
"colourful_portals:colourful_air_particle/pink_colourful_air_3_0",
"colourful_portals:colourful_air_particle/pink_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/purple_colourful_air_0_0",
"colourful_portals:colourful_air_particle/purple_colourful_air_0_1",
"colourful_portals:colourful_air_particle/purple_colourful_air_1_0",
"colourful_portals:colourful_air_particle/purple_colourful_air_1_1",
"colourful_portals:colourful_air_particle/purple_colourful_air_2_0",
"colourful_portals:colourful_air_particle/purple_colourful_air_2_1",
"colourful_portals:colourful_air_particle/purple_colourful_air_3_0",
"colourful_portals:colourful_air_particle/purple_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/red_colourful_air_0_0",
"colourful_portals:colourful_air_particle/red_colourful_air_0_1",
"colourful_portals:colourful_air_particle/red_colourful_air_1_0",
"colourful_portals:colourful_air_particle/red_colourful_air_1_1",
"colourful_portals:colourful_air_particle/red_colourful_air_2_0",
"colourful_portals:colourful_air_particle/red_colourful_air_2_1",
"colourful_portals:colourful_air_particle/red_colourful_air_3_0",
"colourful_portals:colourful_air_particle/red_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/white_colourful_air_0_0",
"colourful_portals:colourful_air_particle/white_colourful_air_0_1",
"colourful_portals:colourful_air_particle/white_colourful_air_1_0",
"colourful_portals:colourful_air_particle/white_colourful_air_1_1",
"colourful_portals:colourful_air_particle/white_colourful_air_2_0",
"colourful_portals:colourful_air_particle/white_colourful_air_2_1",
"colourful_portals:colourful_air_particle/white_colourful_air_3_0",
"colourful_portals:colourful_air_particle/white_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,12 @@
{
"textures": [
"colourful_portals:colourful_air_particle/yellow_colourful_air_0_0",
"colourful_portals:colourful_air_particle/yellow_colourful_air_0_1",
"colourful_portals:colourful_air_particle/yellow_colourful_air_1_0",
"colourful_portals:colourful_air_particle/yellow_colourful_air_1_1",
"colourful_portals:colourful_air_particle/yellow_colourful_air_2_0",
"colourful_portals:colourful_air_particle/yellow_colourful_air_2_1",
"colourful_portals:colourful_air_particle/yellow_colourful_air_3_0",
"colourful_portals:colourful_air_particle/yellow_colourful_air_3_1"
]
}

View File

@@ -0,0 +1,11 @@
{
"entity.colourful_pearl.teleport_away": {
"subtitle": "subtitles.colourful_portals.entity.colourful_pearl.teleport_away",
"sounds": [
{
"name": "colourful_portals:teleport_away",
"attenuation_distance": 64
}
]
}
}

View File

@@ -1,6 +1,7 @@
{ {
"animation": { "animation": {
"interpolate": true, "interpolate": true,
"frametime": 12 "frametime": 12,
"interpolateAlpha": true
} }
} }

Some files were not shown because too many files have changed in this diff Show More