Initial 1.17 port.

This commit is contained in:
stfwi 2021-08-08 09:10:14 +02:00
parent 011edac67f
commit 3621b15989
242 changed files with 21371 additions and 22684 deletions

View file

@ -1,20 +1,18 @@
// @file build.gradle
// Engineer's decor mod gradle build.
import net.minecraftforge.gradle.common.task.SignJar
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
jcenter()
mavenCentral()
maven { url = 'https://maven.minecraftforge.net' }
mavenCentral()
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true
}
}
apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8'
java.toolchain.languageVersion = JavaLanguageVersion.of(16)
//----------------------------------------------------------------------------------------------------------------------
version = "${version_engineersdecor}"
group = "wile.engineersdecor"
@ -23,51 +21,40 @@ archivesBaseName = "engineersdecor-${version_minecraft}"
repositories {
maven { name = "Progwml6 maven"; url = "https://dvs1.progwml6.com/files/maven/" } // JEI files
maven { name = "ModMaven"; url = "https://modmaven.k-4u.nl" } // JEI files, fallback
flatDir { dir 'libs' } // Mod jar dependencies in ./libs
}
minecraft {
// mappings channel: 'snapshot', version: "${version_fml_mappings}"
mappings channel: "official", version: "1.16.5"
mappings channel: "official", version: "1.17.1"
// accessTransformer = file('build/resources/main/META-INF/accesstransformer.cfg')
runs {
client {
workingDirectory project.file('run')
property 'forge.logging.markers', '' // SCAN,REGISTRIES,REGISTRYDUMP
property 'forge.logging.console.level', 'debug'
mods {
engineersdecor {
source sourceSets.main
}
}
property 'forge.logging.console.level', 'info'
mods { engineersdecor { source sourceSets.main } }
}
server {
workingDirectory project.file('run')
property 'forge.logging.markers', '' // SCAN,REGISTRIES,REGISTRYDUMP
property 'forge.logging.console.level', 'debug'
mods {
engineersdecor {
source sourceSets.main
}
}
}
data {
workingDirectory project.file('run')
property 'forge.logging.markers', '' // SCAN,REGISTRIES,REGISTRYDUMP
property 'forge.logging.console.level', 'debug'
args '--mod', 'engineersdecor', '--all', '--output', file('src/generated/resources/')
mods {
engineersdecor {
source sourceSets.main
}
}
property 'forge.logging.console.level', 'info'
mods { engineersdecor { source sourceSets.main } }
}
// data {
// workingDirectory project.file('run')
// property 'forge.logging.markers', '' // SCAN,REGISTRIES,REGISTRYDUMP
// property 'forge.logging.console.level', 'info'
// args '--mod', 'engineersdecor', '--all', '--output', file('src/generated/resources/')
// mods { engineersdecor { source sourceSets.main } }
// }
}
}
dependencies {
minecraft "net.minecraftforge:forge:${version_forge_minecraft}"
compileOnly fg.deobf("mezz.jei:jei-${version_jei}:api")
runtimeOnly fg.deobf("mezz.jei:jei-${version_jei}")
//compileOnly fg.deobf("mezz.jei:jei-${version_jei}:api")
//runtimeOnly fg.deobf("mezz.jei:jei-${version_jei}")
}
processResources {
@ -89,25 +76,29 @@ jar {
}
}
def reobfFile = file("$buildDir/reobfJar/output.jar")
def reobfArtifact = artifacts.add('default', reobfFile) { type 'jar'; builtBy 'reobfJar' }
sourceSets.main.resources { srcDir 'src/generated/resources' } // Include datagen resources
jar.finalizedBy('reobfJar')
task signJar(type: SignJar, dependsOn: jar) {
onlyIf { project.hasProperty("keystore_file") }
if(project.hasProperty("keystore_file")) {
keyStore = project.getProperty("keystore_file")
alias = project.getProperty("keystore_alias")
storePass = project.getProperty("keystore_pass")
keyPass = project.getProperty("keystore_keypass")
inputFile = jar.archivePath
outputFile = jar.archivePath
} else {
logger.warn("[WARNING] Signing skipped.")
}
}
build.dependsOn signJar
// import net.minecraftforge.gradle.common.task.SignJar
// def reobfFile = file("$buildDir/reobfJar/output.jar")
// def reobfArtifact = artifacts.add('default', reobfFile) { type 'jar'; builtBy 'reobfJar' }
// task signJar(type: SignJar, dependsOn: jar) {
// onlyIf { project.hasProperty("keystore_file") }
// if(project.hasProperty("keystore_file")) {
// keyStore = project.getProperty("keystore_file")
// alias = project.getProperty("keystore_alias")
// storePass = project.getProperty("keystore_pass")
// keyPass = project.getProperty("keystore_keypass")
// inputFile = jar.archivePath
// outputFile = jar.archivePath
// } else {
// logger.warn("[WARNING] Signing skipped.")
// }
// }
// build.dependsOn signJar
publishing {
publications { mavenJava(MavenPublication) { artifact reobfArtifact } }
publications { mavenJava(MavenPublication) { artifact jar } }
repositories { maven { url "file:///${project.projectDir}/mcmodsrepo" } }
}

View file

@ -1,8 +1,7 @@
# @file gradle.properties
org.gradle.daemon=false
org.gradle.jvmargs=-Xmx8G
version_minecraft=1.16.4
version_forge_minecraft=1.16.4-35.1.10
version_fml_mappings=20201028-1.16.3
version_minecraft=1.17.1
version_forge_minecraft=1.17.1-37.0.17
version_jei=1.16.4:7.6.1.63
version_engineersdecor=1.1.15-b1
version_engineersdecor=1.1.17-b1

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-7.2-20210702220150+0000-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-bin.zip

55
gradlew vendored
View file

@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -56,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
MINGW* )
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

43
gradlew.bat vendored
View file

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell

View file

@ -1,6 +1,6 @@
{
"homepage": "https://www.curseforge.com/minecraft/mc-mods/engineers-decor/",
"1.16.4": {
"1.17.1": {
"1.1.14": "[R] Release build v1.1.14.",
"1.1.14-b3": "[A] Spanish language support added (PR#180, thx FrannDzs).",
"1.1.14-b2": "[F] Block Placer: Attempt circumventing external placement prevention.",
@ -39,7 +39,7 @@
"1.1.2-b1": "[U] Ported to MC1.16.2."
},
"promos": {
"1.16.4-recommended": "1.1.14",
"1.16.4-latest": "1.1.14"
"1.17.1-recommended": "1.1.14",
"1.17.1-latest": "1.1.14"
}
}

View file

@ -11,7 +11,11 @@ Mod sources for Minecraft version 1.16.x.
## Version history
~ v1.1.15-b1 [F]
~ v1.1.17-b1 [U] Initial MC 1.17.1 port.
[M] Clinker Brick textures adapted to contemporary MC textures.
[D] Gas Concrete dropped (Rebar Concrete suffices for texture variation).
[D] Treated Wood Side Table dropped.
[M] Updated zh_cn (PR#183, Lyaiya).
- v1.1.14 [R] Release build v1.1.14.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,136 +1,131 @@
package wile.engineersdecor;
import wile.engineersdecor.blocks.*;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.OptionalRecipeCondition;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.container.ContainerType;
import net.minecraft.item.ItemGroup;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.*;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod("engineersdecor")
public class ModEngineersDecor
{
public static final String MODID = "engineersdecor";
public static final String MODNAME = "Engineer's Decor";
public static final int VERSION_DATAFIXER = 0;
private static final Logger LOGGER = LogManager.getLogger();
public ModEngineersDecor()
{
Auxiliaries.init(MODID, LOGGER, ModConfig::getServerConfig);
Auxiliaries.logGitVersion(MODNAME);
OptionalRecipeCondition.init(MODID, LOGGER);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigLoad);
FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigReload);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.SERVER, ModConfig.SERVER_CONFIG_SPEC);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.COMMON, ModConfig.COMMON_CONFIG_SPEC);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.CLIENT, ModConfig.CLIENT_CONFIG_SPEC);
MinecraftForge.EVENT_BUS.register(this);
}
public static final Logger logger() { return LOGGER; }
//
// Events
//
private void onSetup(final FMLCommonSetupEvent event)
{
LOGGER.info("Registering recipe condition processor ...");
CraftingHelper.register(OptionalRecipeCondition.Serializer.INSTANCE);
wile.engineersdecor.libmc.detail.Networking.init(MODID);
}
private void onClientSetup(final FMLClientSetupEvent event)
{
ModContent.registerContainerGuis(event);
ModContent.registerTileEntityRenderers(event);
ModContent.processContentClientSide(event);
wile.engineersdecor.libmc.detail.Overlay.register();
}
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
public static class ForgeEvents
{
@SubscribeEvent
public static void onBlocksRegistry(final RegistryEvent.Register<Block> event)
{ ModContent.registerBlocks(event); }
@SubscribeEvent
public static void onItemRegistry(final RegistryEvent.Register<Item> event)
{ ModContent.registerItems(event); ModContent.registerBlockItems(event); }
@SubscribeEvent
public static void onTileEntityRegistry(final RegistryEvent.Register<TileEntityType<?>> event)
{ ModContent.registerTileEntities(event); }
@SubscribeEvent
public static void onRegisterEntityTypes(final RegistryEvent.Register<EntityType<?>> event)
{ ModContent.registerEntities(event); }
@SubscribeEvent
public static void onRegisterContainerTypes(final RegistryEvent.Register<ContainerType<?>> event)
{ ModContent.registerContainers(event); }
public static void onConfigLoad(net.minecraftforge.fml.config.ModConfig.Loading configEvent)
{ ModConfig.apply(); }
public static void onConfigReload(net.minecraftforge.fml.config.ModConfig.Reloading configEvent)
{
try {
ModEngineersDecor.logger().info("Config file changed {}", configEvent.getConfig().getFileName());
ModConfig.apply();
} catch(Throwable e) {
ModEngineersDecor.logger().error("Failed to load changed config: " + e.getMessage());
}
}
@SubscribeEvent
public static void onDataGeneration(GatherDataEvent event)
{
event.getGenerator().addProvider(new wile.engineersdecor.libmc.datagen.LootTableGen(event.getGenerator(), ModContent::allBlocks));
}
}
//
// Item group / creative tab
//
public static final ItemGroup ITEMGROUP = (new ItemGroup("tab" + MODID) {
@OnlyIn(Dist.CLIENT)
public ItemStack makeIcon()
{ return new ItemStack(ModContent.SIGN_MODLOGO); }
});
//
// Player update event
//
@SubscribeEvent
public void onPlayerEvent(final LivingEvent.LivingUpdateEvent event)
{
if((event.getEntity().level == null) || (!(event.getEntity() instanceof PlayerEntity))) return;
final PlayerEntity player = (PlayerEntity)event.getEntity();
if(player.onClimbable()) EdLadderBlock.onPlayerUpdateEvent(player);
}
}
package wile.engineersdecor;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import wile.engineersdecor.blocks.EdLadderBlock;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.OptionalRecipeCondition;
@Mod("engineersdecor")
public class ModEngineersDecor
{
public static final String MODID = "engineersdecor";
public static final String MODNAME = "Engineer's Decor";
public static final int VERSION_DATAFIXER = 0;
private static final Logger LOGGER = LogManager.getLogger();
public ModEngineersDecor()
{
Auxiliaries.init(MODID, LOGGER, ModConfig::getServerConfig);
Auxiliaries.logGitVersion(MODNAME);
OptionalRecipeCondition.init(MODID, LOGGER);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.SERVER, ModConfig.SERVER_CONFIG_SPEC);
ModLoadingContext.get().registerConfig(net.minecraftforge.fml.config.ModConfig.Type.COMMON, ModConfig.COMMON_CONFIG_SPEC);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onSetup);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientSetup);
// FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigLoad);
// FMLJavaModLoadingContext.get().getModEventBus().addListener(ForgeEvents::onConfigReload);
MinecraftForge.EVENT_BUS.register(this);
}
public static Logger logger() { return LOGGER; }
//
// Events
//
private void onSetup(final FMLCommonSetupEvent event)
{
LOGGER.info("Registering recipe condition processor ...");
CraftingHelper.register(OptionalRecipeCondition.Serializer.INSTANCE);
wile.engineersdecor.libmc.detail.Networking.init(MODID);
}
private void onClientSetup(final FMLClientSetupEvent event)
{
ModContent.registerContainerGuis(event);
ModContent.registerTileEntityRenderers(event);
ModContent.processContentClientSide(event);
wile.engineersdecor.libmc.detail.Overlay.register();
}
@Mod.EventBusSubscriber(bus=Mod.EventBusSubscriber.Bus.MOD)
public static class ForgeEvents
{
@SubscribeEvent
public static void onBlocksRegistry(final RegistryEvent.Register<Block> event)
{ ModContent.registerBlocks(event); }
@SubscribeEvent
public static void onItemRegistry(final RegistryEvent.Register<Item> event)
{ ModContent.registerItems(event); ModContent.registerBlockItems(event); }
@SubscribeEvent
public static void onTileEntityRegistry(final RegistryEvent.Register<BlockEntityType<?>> event)
{ ModContent.registerTileEntities(event); }
@SubscribeEvent
public static void onRegisterEntityTypes(final RegistryEvent.Register<EntityType<?>> event)
{ ModContent.registerEntities(event); }
@SubscribeEvent
public static void onRegisterContainerTypes(final RegistryEvent.Register<MenuType<?>> event)
{ ModContent.registerContainers(event); }
/*
public static void onConfigLoad(net.minecraftforge.fml.config.ModConfig.Loading configEvent)
{ ModConfig.apply(); }
public static void onConfigReload(net.minecraftforge.fml.config.ModConfig.Reloading configEvent)
{
try {
ModEngineersDecor.logger().info("Config file changed {}", configEvent.getConfig().getFileName());
ModConfig.apply();
} catch(Throwable e) {
ModEngineersDecor.logger().error("Failed to load changed config: " + e.getMessage());
}
}
*/
}
//
// Item group / creative tab
//
public static final CreativeModeTab ITEMGROUP = (new CreativeModeTab("tab" + MODID) {
@OnlyIn(Dist.CLIENT)
public ItemStack makeIcon()
{ return new ItemStack(ModContent.SIGN_MODLOGO); }
});
//
// Player update event
//
@SubscribeEvent
public void onPlayerEvent(final LivingEvent.LivingUpdateEvent event)
{
if((event.getEntity().level == null) || (!(event.getEntity() instanceof final Player player))) return;
if(player.onClimbable()) EdLadderBlock.onPlayerUpdateEvent(player);
}
}

View file

@ -1,135 +1,34 @@
/*
* @file DecorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Common functionality class for decor blocks.
* Mainly needed for:
* - MC block defaults.
* - Tooltip functionality
* - Model initialisation
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardBlocks.IStandardBlock;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.block.IWaterLoggable;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.shapes.VoxelShape;
import java.util.ArrayList;
import java.util.function.Supplier;
public class DecorBlock
{
public static final long CFG_DEFAULT = StandardBlocks.CFG_DEFAULT;
public static final long CFG_CUTOUT = StandardBlocks.CFG_CUTOUT;
public static final long CFG_MIPPED = StandardBlocks.CFG_MIPPED;
public static final long CFG_TRANSLUCENT = StandardBlocks.CFG_TRANSLUCENT;
public static final long CFG_WATERLOGGABLE = StandardBlocks.CFG_WATERLOGGABLE;
public static final long CFG_HORIZIONTAL = StandardBlocks.CFG_HORIZIONTAL;
public static final long CFG_LOOK_PLACEMENT = StandardBlocks.CFG_LOOK_PLACEMENT;
public static final long CFG_FACING_PLACEMENT = StandardBlocks.CFG_FACING_PLACEMENT;
public static final long CFG_OPPOSITE_PLACEMENT = StandardBlocks.CFG_OPPOSITE_PLACEMENT;
public static final long CFG_FLIP_PLACEMENT_IF_SAME = StandardBlocks.CFG_FLIP_PLACEMENT_IF_SAME;
public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = StandardBlocks.CFG_FLIP_PLACEMENT_SHIFTCLICK;
public static final long CFG_STRICT_CONNECTIONS = StandardBlocks.CFG_STRICT_CONNECTIONS;
public static final long CFG_AI_PASSABLE = StandardBlocks.CFG_AI_PASSABLE;
public static final long CFG_HARD_IE_DEPENDENT = 0x8000000000000000L;
@Deprecated public static final long CFG_EXPERIMENTAL = 0x4000000000000000L;
public static class Normal extends StandardBlocks.BaseBlock implements IDecorBlock
{
public Normal(long conf, AbstractBlock.Properties properties)
{ super(conf, properties); }
}
public static class Cutout extends StandardBlocks.Cutout implements IDecorBlock
{
public Cutout(long conf, AbstractBlock.Properties properties)
{ super(conf, properties, Auxiliaries.getPixeledAABB(0, 0, 0, 16, 16,16 )); }
public Cutout(long conf, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(conf, properties, aabb);}
public Cutout(long conf, AbstractBlock.Properties properties, VoxelShape voxel_shape)
{ super(conf, properties, voxel_shape); }
public Cutout(long conf, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(conf, properties, aabbs); }
}
public static class WaterLoggable extends StandardBlocks.WaterLoggable implements IStandardBlock, IWaterLoggable
{
public WaterLoggable(long config, AbstractBlock.Properties properties)
{ super(config, properties); }
public WaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public WaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(config, properties, aabbs); }
public WaterLoggable(long config, AbstractBlock.Properties properties, VoxelShape voxel_shape)
{ super(config, properties, voxel_shape); }
}
public static class Directed extends StandardBlocks.Directed implements IDecorBlock
{
public Directed(long config, AbstractBlock.Properties properties, final AxisAlignedBB unrotatedAABB)
{ super(config, properties, unrotatedAABB); }
public Directed(long config, AbstractBlock.Properties properties, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, properties, unrotatedAABBs); }
public Directed(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class DirectedWaterLoggable extends StandardBlocks.DirectedWaterLoggable implements IDecorBlock,IWaterLoggable
{
public DirectedWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public DirectedWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(config, properties, aabbs); }
public DirectedWaterLoggable(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class Horizontal extends StandardBlocks.Horizontal implements IDecorBlock
{
public Horizontal(long config, AbstractBlock.Properties properties, final AxisAlignedBB unrotatedAABB)
{ super(config, properties, unrotatedAABB); }
public Horizontal(long config, AbstractBlock.Properties properties, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, properties, unrotatedAABBs); }
public Horizontal(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class HorizontalWaterLoggable extends StandardBlocks.HorizontalWaterLoggable implements IWaterLoggable
{
public HorizontalWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public HorizontalWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{ super(config, properties, aabbs); }
public HorizontalWaterLoggable(long config, AbstractBlock.Properties properties, final Supplier<ArrayList<VoxelShape>> shape_supplier)
{ super(config, properties, shape_supplier); }
}
public static class HorizontalFourWayWaterLoggable extends StandardBlocks.HorizontalFourWayWaterLoggable implements IWaterLoggable
{
public HorizontalFourWayWaterLoggable(long config, AbstractBlock.Properties properties, AxisAlignedBB base_aabb, AxisAlignedBB side_aabb, int railing_height_extension)
{ super(config, properties, base_aabb, side_aabb, railing_height_extension); }
}
}
/*
* @file DecorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Common functionality class for decor blocks.
* Mainly needed for:
* - MC block defaults.
* - Tooltip functionality
* - Model initialisation
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
public class DecorBlock
{
public static final long CFG_DEFAULT = StandardBlocks.CFG_DEFAULT;
public static final long CFG_CUTOUT = StandardBlocks.CFG_CUTOUT;
public static final long CFG_MIPPED = StandardBlocks.CFG_MIPPED;
public static final long CFG_TRANSLUCENT = StandardBlocks.CFG_TRANSLUCENT;
public static final long CFG_WATERLOGGABLE = StandardBlocks.CFG_WATERLOGGABLE;
public static final long CFG_HORIZIONTAL = StandardBlocks.CFG_HORIZIONTAL;
public static final long CFG_LOOK_PLACEMENT = StandardBlocks.CFG_LOOK_PLACEMENT;
public static final long CFG_FACING_PLACEMENT = StandardBlocks.CFG_FACING_PLACEMENT;
public static final long CFG_OPPOSITE_PLACEMENT = StandardBlocks.CFG_OPPOSITE_PLACEMENT;
public static final long CFG_FLIP_PLACEMENT_IF_SAME = StandardBlocks.CFG_FLIP_PLACEMENT_IF_SAME;
public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = StandardBlocks.CFG_FLIP_PLACEMENT_SHIFTCLICK;
public static final long CFG_STRICT_CONNECTIONS = StandardBlocks.CFG_STRICT_CONNECTIONS;
public static final long CFG_AI_PASSABLE = StandardBlocks.CFG_AI_PASSABLE;
public static final long CFG_HARD_IE_DEPENDENT = 0x8000000000000000L;
@Deprecated public static final long CFG_EXPERIMENTAL = 0x4000000000000000L;
}

View file

@ -1,356 +1,349 @@
/*
* @file EdBreaker.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Block Breaker
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import wile.engineersdecor.libmc.detail.Overlay;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.GameRules;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Blocks;
import net.minecraft.block.SoundType;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.libmc.detail.RfEnergy;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class EdBreaker
{
public static void on_config(int boost_energy_per_tick, int breaking_time_per_hardness, int min_breaking_time_ticks, boolean power_required)
{ BreakerTileEntity.on_config(boost_energy_per_tick, breaking_time_per_hardness, min_breaking_time_ticks, power_required); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public BreakerBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, builder, unrotatedAABBs); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new BreakerTileEntity(); }
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
final double rv = rnd.nextDouble();
if(rv > 0.8) return;
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch(state.getValue(HORIZONTAL_FACING)) {
case WEST: world.addParticle(ParticleTypes.SMOKE, x-xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case EAST: world.addParticle(ParticleTypes.SMOKE, x+xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case NORTH: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z-xc, 0.0, 0.0, 0.0); break;
default: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z+xc, 0.0, 0.0, 0.0); break;
}
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
if(!(world instanceof World) || (((World) world).isClientSide)) return;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof BreakerTileEntity)) return;
((BreakerTileEntity)te).block_updated();
}
@Override
@SuppressWarnings("deprecation")
public boolean isSignalSource(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(te instanceof BreakerTileEntity) ((BreakerTileEntity)te).state_message(player);
return ActionResultType.CONSUME;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerTileEntity extends TileEntity implements ITickableTileEntity
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
public static final int BOOST_FACTOR = 8;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_BREAKING_RELUCTANCE = 17;
public static final int DEFAULT_MIN_BREAKING_TIME = 15;
public static final int MAX_BREAKING_TIME = 800;
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = 32000;
private static int breaking_reluctance = DEFAULT_BREAKING_RELUCTANCE;
private static int min_breaking_time = DEFAULT_MIN_BREAKING_TIME;
private static boolean requires_power = false;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int time_needed_;
private final RfEnergy.Battery battery_;
private final LazyOptional<IEnergyStorage> energy_handler_;
public static void on_config(int boost_energy_per_tick, int breaking_time_per_hardness, int min_breaking_time_ticks, boolean power_required)
{
boost_energy_consumption = TICK_INTERVAL * MathHelper.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 100000);
breaking_reluctance = MathHelper.clamp(breaking_time_per_hardness, 5, 50);
min_breaking_time = MathHelper.clamp(min_breaking_time_ticks, 10, 100);
requires_power = power_required;
ModConfig.log("Config block breaker: Boost energy consumption:" + (boost_energy_consumption/TICK_INTERVAL) + "rf/t, reluctance=" + breaking_reluctance + "t/hrdn, break time offset=" + min_breaking_time + "t.");
}
public BreakerTileEntity()
{ this(ModContent.TET_SMALL_BLOCK_BREAKER); }
public BreakerTileEntity(TileEntityType<?> te_type)
{
super(te_type);
battery_ = new RfEnergy.Battery(energy_max, boost_energy_consumption, 0);
energy_handler_ = battery_.createEnergyHandler();
}
public void block_updated()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
public void readnbt(CompoundNBT nbt)
{ battery_.load(nbt); }
private void writenbt(CompoundNBT nbt)
{ battery_.save(nbt); }
public void state_message(PlayerEntity player)
{
String progress = "0";
if((proc_time_elapsed_ > 0) && (time_needed_ > 0)) {
progress = Integer.toString((int)MathHelper.clamp((((double)proc_time_elapsed_) / ((double)time_needed_) * 100), 0, 100));
}
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_block_breaker.status", new Object[]{battery_.getSOC(), energy_max, progress }));
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
private static final HashSet<Block> blacklist = new HashSet<>();
static {
blacklist.add(Blocks.AIR);
blacklist.add(Blocks.BEDROCK);
blacklist.add(Blocks.FIRE);
blacklist.add(Blocks.END_PORTAL);
blacklist.add(Blocks.END_GATEWAY);
blacklist.add(Blocks.END_PORTAL_FRAME);
blacklist.add(Blocks.NETHER_PORTAL);
blacklist.add(Blocks.BARRIER);
}
private static boolean isBreakable(BlockState state, BlockPos pos, World world)
{
final Block block = state.getBlock();
if(blacklist.contains(block)) return false;
if(state.getMaterial().isLiquid()) return false;
if(block.isAir(state, world, pos)) return false;
float bh = state.getDestroySpeed(world, pos);
if((bh<0) || (bh>55)) return false;
return true;
}
private static void spawnBlockAsEntity(World world, BlockPos pos, ItemStack stack) {
if(world.isClientSide || stack.isEmpty() || (!world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) || world.restoringBlockSnapshots) return;
ItemEntity e = new ItemEntity(world,
((world.random.nextFloat()*0.1)+0.5) + pos.getX(),
((world.random.nextFloat()*0.1)+0.5) + pos.getY(),
((world.random.nextFloat()*0.1)+0.5) + pos.getZ(),
stack
);
e.setDefaultPickUpDelay();
e.setDeltaMovement((world.random.nextFloat()*0.1-0.05), (world.random.nextFloat()*0.1-0.03), (world.random.nextFloat()*0.1-0.05));
world.addFreshEntity(e);
}
private static boolean canInsertInto(World world, BlockPos pos)
{
// Maybe make a tag for that. The question is if it is actually worth it, or if that would be only
// tag spamming the game. So for now only FH and VH.
final BlockState state = world.getBlockState(pos);
return (state.getBlock() == ModContent.FACTORY_HOPPER) || (state.getBlock() == Blocks.HOPPER);
}
private boolean breakBlock(BlockState state, BlockPos pos, World world)
{
if(world.isClientSide || (!(world instanceof ServerWorld)) || world.restoringBlockSnapshots) return false; // retry next cycle
List<ItemStack> drops;
final Block block = state.getBlock();
final boolean insert = canInsertInto(world, getBlockPos().below());
drops = Block.getDrops(state, (ServerWorld)world, pos, world.getBlockEntity(pos));
world.removeBlock(pos, false);
for(ItemStack drop:drops) {
if(!insert) {
spawnBlockAsEntity(world, pos, drop);
} else {
final ItemStack remaining = Inventories.insert(world, getBlockPos().below(), Direction.UP, drop, false);
if(!remaining.isEmpty()) spawnBlockAsEntity(world, pos, remaining);
}
}
SoundType stype = state.getBlock().getSoundType(state, world, pos, null);
if(stype != null) world.playSound(null, pos, stype.getPlaceSound(), SoundCategory.BLOCKS, stype.getVolume()*0.6f, stype.getPitch());
return true;
}
@Override
@SuppressWarnings("deprecation")
public void tick()
{
if(--tick_timer_ > 0) return;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof BreakerBlock)) return;
if(level.isClientSide) {
if(!device_state.getValue(BreakerBlock.ACTIVE)) {
tick_timer_ = TICK_INTERVAL;
} else {
tick_timer_ = 1;
// not sure if is so cool to do this each tick ... may be simplified/removed again.
SoundEvent sound = SoundEvents.WOOD_HIT;
BlockState target_state = level.getBlockState(worldPosition.relative(device_state.getValue(BreakerBlock.HORIZONTAL_FACING)));
SoundType stype = target_state.getBlock().getSoundType(target_state);
if((stype == SoundType.WOOL) || (stype == SoundType.GRASS) || (stype == SoundType.SNOW)) {
sound = SoundEvents.WOOL_HIT;
} else if((stype == SoundType.GRAVEL) || (stype == SoundType.SAND)) {
sound = SoundEvents.GRAVEL_HIT;
}
level.playLocalSound(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), sound, SoundCategory.BLOCKS, 0.1f, 1.2f, false);
}
} else {
tick_timer_ = TICK_INTERVAL;
final BlockPos target_pos = worldPosition.relative(device_state.getValue(BreakerBlock.HORIZONTAL_FACING));
final BlockState target_state = level.getBlockState(target_pos);
if((level.hasNeighborSignal(worldPosition)) || (!isBreakable(target_state, target_pos, level))) {
if(device_state.getValue(BreakerBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
time_needed_ = MathHelper.clamp((int)(target_state.getDestroySpeed(level, worldPosition) * breaking_reluctance) + min_breaking_time, min_breaking_time, MAX_BREAKING_TIME);
if(battery_.draw(boost_energy_consumption)) {
proc_time_elapsed_ += TICK_INTERVAL * (1+BOOST_FACTOR);
time_needed_ += min_breaking_time * (3*BOOST_FACTOR/5);
active_timer_ = 2;
} else if(!requires_power) {
proc_time_elapsed_ += TICK_INTERVAL;
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= time_needed_) {
proc_time_elapsed_ = 0;
breakBlock(target_state, target_pos, level);
active = false;
}
if(device_state.getValue(BreakerBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, active), 1|2);
}
}
}
}
}
/*
* @file EdBreaker.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Block Breaker
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import wile.engineersdecor.libmc.detail.Overlay;
import wile.engineersdecor.libmc.detail.RfEnergy;
import javax.annotation.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class EdBreaker
{
public static final int BOOST_FACTOR = 8;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_BREAKING_RELUCTANCE = 17;
public static final int DEFAULT_MIN_BREAKING_TIME = 15;
public static final int MAX_BREAKING_TIME = 800;
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = 32000;
private static int breaking_reluctance = DEFAULT_BREAKING_RELUCTANCE;
private static int min_breaking_time = DEFAULT_MIN_BREAKING_TIME;
private static boolean requires_power = false;
public static void on_config(int boost_energy_per_tick, int breaking_time_per_hardness, int min_breaking_time_ticks, boolean power_required)
{
final int interval = BreakerTileEntity.TICK_INTERVAL;
boost_energy_consumption = interval * Mth.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 100000);
breaking_reluctance = Mth.clamp(breaking_time_per_hardness, 5, 50);
min_breaking_time = Mth.clamp(min_breaking_time_ticks, 10, 100);
requires_power = power_required;
ModConfig.log("Config block breaker: Boost energy consumption:" + (boost_energy_consumption/interval) + "rf/t, reluctance=" + breaking_reluctance + "t/hrdn, break time offset=" + min_breaking_time + "t.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerBlock extends StandardBlocks.HorizontalWaterLoggable implements StandardEntityBlocks.IStandardEntityBlock<BreakerTileEntity>
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public BreakerBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABBs)
{ super(config, builder, unrotatedAABBs); }
@Nullable
@Override
public BlockEntityType<BreakerTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_BLOCK_BREAKER; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
// Sound
if(true || (world.getGameTime() & 0x1) == 0) {
SoundEvent sound = SoundEvents.WOOD_HIT;
BlockState target_state = world.getBlockState(pos.relative(state.getValue(BreakerBlock.HORIZONTAL_FACING)));
SoundType stype = target_state.getBlock().getSoundType(target_state);
if((stype == SoundType.WOOL) || (stype == SoundType.GRASS) || (stype == SoundType.SNOW)) {
sound = SoundEvents.WOOL_HIT;
} else if((stype == SoundType.GRAVEL) || (stype == SoundType.SAND)) {
sound = SoundEvents.GRAVEL_HIT;
}
world.playLocalSound(pos.getX(), pos.getY(), pos.getZ(), sound, SoundSource.BLOCKS, 0.1f, 1.2f, false);
}
// Particles
{
final double rv = rnd.nextDouble();
if(rv < 0.8) {
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch (state.getValue(HORIZONTAL_FACING)) {
case WEST -> world.addParticle(ParticleTypes.SMOKE, x - xc, yr, z + xr, 0.0, 0.0, 0.0);
case EAST -> world.addParticle(ParticleTypes.SMOKE, x + xc, yr, z + xr, 0.0, 0.0, 0.0);
case NORTH -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z - xc, 0.0, 0.0, 0.0);
default -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z + xc, 0.0, 0.0, 0.0);
}
}
}
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
if(!(world instanceof Level) || (world.isClientSide)) return;
BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof BreakerTileEntity)) return;
((BreakerTileEntity)te).block_updated();
}
@Override
@SuppressWarnings("deprecation")
public boolean isSignalSource(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(te instanceof BreakerTileEntity) ((BreakerTileEntity)te).state_message(player);
return InteractionResult.CONSUME;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class BreakerTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int time_needed_;
private final RfEnergy.Battery battery_ = new RfEnergy.Battery(energy_max, boost_energy_consumption, 0);
private final LazyOptional<IEnergyStorage> energy_handler_ = battery_.createEnergyHandler();
public BreakerTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_BLOCK_BREAKER, pos, state); }
public void block_updated()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
public void readnbt(CompoundTag nbt)
{ battery_.load(nbt); }
private void writenbt(CompoundTag nbt)
{ battery_.save(nbt); }
public void state_message(Player player)
{
String progress = "0";
if((proc_time_elapsed_ > 0) && (time_needed_ > 0)) {
progress = Integer.toString((int)Mth.clamp((((double)proc_time_elapsed_) / ((double)time_needed_) * 100), 0, 100));
}
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_block_breaker.status", battery_.getSOC(), energy_max, progress));
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
private static final HashSet<Block> blacklist = new HashSet<>();
static {
blacklist.add(Blocks.AIR);
blacklist.add(Blocks.BEDROCK);
blacklist.add(Blocks.FIRE);
blacklist.add(Blocks.END_PORTAL);
blacklist.add(Blocks.END_GATEWAY);
blacklist.add(Blocks.END_PORTAL_FRAME);
blacklist.add(Blocks.NETHER_PORTAL);
blacklist.add(Blocks.BARRIER);
}
private static boolean isBreakable(BlockState state, BlockPos pos, Level world)
{
final Block block = state.getBlock();
if(blacklist.contains(block)) return false;
if(state.isAir()) return false;
if(state.getMaterial().isLiquid()) return false;
float bh = state.getDestroySpeed(world, pos);
return !((bh<0) || (bh>55));
}
private static void spawnBlockAsEntity(Level world, BlockPos pos, ItemStack stack) {
if(world.isClientSide || stack.isEmpty() || (!world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) || world.restoringBlockSnapshots) return;
ItemEntity e = new ItemEntity(world,
((world.random.nextFloat()*0.1)+0.5) + pos.getX(),
((world.random.nextFloat()*0.1)+0.5) + pos.getY(),
((world.random.nextFloat()*0.1)+0.5) + pos.getZ(),
stack
);
e.setDefaultPickUpDelay();
e.setDeltaMovement((world.random.nextFloat()*0.1-0.05), (world.random.nextFloat()*0.1-0.03), (world.random.nextFloat()*0.1-0.05));
world.addFreshEntity(e);
}
private static boolean canInsertInto(Level world, BlockPos pos)
{
// Maybe make a tag for that. The question is if it is actually worth it, or if that would be only
// tag spamming the game. So for now only FH and VH.
final BlockState state = world.getBlockState(pos);
return (state.getBlock() == ModContent.FACTORY_HOPPER) || (state.getBlock() == Blocks.HOPPER);
}
private boolean breakBlock(BlockState state, BlockPos pos, Level world)
{
if(world.isClientSide || (!(world instanceof ServerLevel)) || world.restoringBlockSnapshots) return false; // retry next cycle
List<ItemStack> drops;
final Block block = state.getBlock();
final boolean insert = canInsertInto(world, getBlockPos().below());
drops = Block.getDrops(state, (ServerLevel)world, pos, world.getBlockEntity(pos));
world.removeBlock(pos, false);
for(ItemStack drop:drops) {
if(!insert) {
spawnBlockAsEntity(world, pos, drop);
} else {
final ItemStack remaining = Inventories.insert(world, getBlockPos().below(), Direction.UP, drop, false);
if(!remaining.isEmpty()) spawnBlockAsEntity(world, pos, remaining);
}
}
SoundType stype = state.getBlock().getSoundType(state, world, pos, null);
if(stype != null) world.playSound(null, pos, stype.getPlaceSound(), SoundSource.BLOCKS, stype.getVolume()*0.6f, stype.getPitch());
return true;
}
@Override
@SuppressWarnings("deprecation")
public void tick()
{
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof BreakerBlock)) return;
final BlockPos target_pos = worldPosition.relative(device_state.getValue(BreakerBlock.HORIZONTAL_FACING));
final BlockState target_state = level.getBlockState(target_pos);
if((level.hasNeighborSignal(worldPosition)) || (!isBreakable(target_state, target_pos, level))) {
if(device_state.getValue(BreakerBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
time_needed_ = Mth.clamp((int)(target_state.getDestroySpeed(level, worldPosition) * breaking_reluctance) + min_breaking_time, min_breaking_time, MAX_BREAKING_TIME);
if(battery_.draw(boost_energy_consumption)) {
proc_time_elapsed_ += TICK_INTERVAL * (1+BOOST_FACTOR);
time_needed_ += min_breaking_time * (3*BOOST_FACTOR/5);
active_timer_ = 2;
} else if(!requires_power) {
proc_time_elapsed_ += TICK_INTERVAL;
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= time_needed_) {
proc_time_elapsed_ = 0;
breakBlock(target_state, target_pos, level);
active = false;
}
if(device_state.getValue(BreakerBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(BreakerBlock.ACTIVE, active), 1|2);
}
}
}
}

View file

@ -1,131 +1,138 @@
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.BooleanProperty;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class EdCatwalkBlock extends DecorBlock.HorizontalFourWayWaterLoggable implements IDecorBlock
{
final Block railing_block;
final AxisAlignedBB base_aabb;
public EdCatwalkBlock(long config, AbstractBlock.Properties properties, final AxisAlignedBB base_aabb, final AxisAlignedBB railing_aabb, final Block railing_block)
{ super(config, properties, base_aabb, railing_aabb, 0); this.railing_block = railing_block; this.base_aabb=base_aabb; }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); }
public static boolean place_consume(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, int shrink)
{
if(!world.setBlock(pos, state, 1|2)) return false;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
if((!player.isCreative()) && (!world.isClientSide())) {
ItemStack stack = player.getItemInHand(hand);
if(shrink >= 0) {
stack.shrink(shrink);
} else if(stack.getCount() < stack.getMaxStackSize()) {
stack.grow(Math.abs(shrink));
} else {
Inventories.give(player, new ItemStack(stack.getItem(), Math.abs(shrink)));
}
Inventories.setItemInPlayerHand(player, hand, stack);
}
return true;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return ActionResultType.PASS;
final Block block = ((BlockItem)item).getBlock();
if(block == this) {
if(hit.getDirection().getAxis().isHorizontal()) return ActionResultType.PASS; // place new block on the clicked side.
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
place_consume(place_state, world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
if(block == railing_block) {
Direction face = hit.getDirection();
final Vector3d rhv = hit.getLocation().subtract(Vector3d.atCenterOf(hit.getBlockPos()));
if(face.getAxis().isHorizontal()) {
// Side or railing clicked
if(rhv.multiply(Vector3d.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
} else if(player.distanceToSqr(Vector3d.atCenterOf(pos)) < 3) {
// near accurate placement
face = Direction.getNearest(rhv.x, 0, rhv.z);
} else {
// far automatic placement
face = Direction.getNearest(player.getLookAngle().x, 0, player.getLookAngle().z);
List<Direction> free_sides = Arrays.stream(Direction.values()).filter(d->d.getAxis().isHorizontal() && (world.getBlockState(pos.relative(d)).getBlock()!=this)).collect(Collectors.toList());
if(free_sides.isEmpty()) return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
if(!free_sides.contains(face)) face = free_sides.get(0);
}
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
place_consume(state.setValue(railing, add), world, pos, player, hand, add ? 1 : -1);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
return ActionResultType.PASS;
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, @Nullable TileEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
public class EdCatwalkBlock extends StandardBlocks.HorizontalFourWayWaterLoggable
{
final Block railing_block;
final AABB base_aabb;
public EdCatwalkBlock(long config, BlockBehaviour.Properties properties, final AABB base_aabb, final AABB railing_aabb, final Block railing_block)
{ super(config, properties, base_aabb, railing_aabb, 0); this.railing_block = railing_block; this.base_aabb=base_aabb; }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); }
public static boolean place_consume(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, int shrink)
{
if(!world.setBlock(pos, state, 1|2)) return false;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
if((!player.isCreative()) && (!world.isClientSide())) {
ItemStack stack = player.getItemInHand(hand);
if(shrink >= 0) {
stack.shrink(shrink);
} else if(stack.getCount() < stack.getMaxStackSize()) {
stack.grow(Math.abs(shrink));
} else {
Inventories.give(player, new ItemStack(stack.getItem(), Math.abs(shrink)));
}
Inventories.setItemInPlayerHand(player, hand, stack);
}
return true;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return InteractionResult.PASS;
final Block block = ((BlockItem)item).getBlock();
if(block == this) {
if(hit.getDirection().getAxis().isHorizontal()) return InteractionResult.PASS; // place new block on the clicked side.
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
place_consume(place_state, world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
if(block == railing_block) {
Direction face = hit.getDirection();
final Vec3 rhv = hit.getLocation().subtract(Vec3.atCenterOf(hit.getBlockPos()));
if(face.getAxis().isHorizontal()) {
// Side or railing clicked
if(rhv.multiply(Vec3.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
} else if(player.distanceToSqr(Vec3.atCenterOf(pos)) < 3) {
// near accurate placement
face = Direction.getNearest(rhv.x, 0, rhv.z);
} else {
// far automatic placement
face = Direction.getNearest(player.getLookAngle().x, 0, player.getLookAngle().z);
List<Direction> free_sides = Arrays.stream(Direction.values()).filter(d->d.getAxis().isHorizontal() && (world.getBlockState(pos.relative(d)).getBlock()!=this)).collect(Collectors.toList());
if(free_sides.isEmpty()) return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
if(!free_sides.contains(face)) face = free_sides.get(0);
}
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
place_consume(state.setValue(railing, add), world, pos, player, hand, add ? 1 : -1);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
return InteractionResult.PASS;
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}

View file

@ -1,183 +1,186 @@
/*
* @file EdCatwalkStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stair version of the catwalk block, optional left/right railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.*;
public class EdCatwalkStairsBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final BooleanProperty RIGHT_RAILING = BooleanProperty.create("right_railing");
public static final BooleanProperty LEFT_RAILING = BooleanProperty.create("left_railing");
protected final Map<BlockState, VoxelShape> shapes;
protected final Map<BlockState, VoxelShape> collision_shapes;
protected final Map<Direction, Integer> y_rotations;
public EdCatwalkStairsBlock(long config, AbstractBlock.Properties properties, final AxisAlignedBB[] base_aabb, final AxisAlignedBB[] railing_aabbs)
{
super(config, properties, base_aabb);
Map<BlockState, VoxelShape> sh = new HashMap<>();
Map<BlockState, VoxelShape> csh = new HashMap<>();
getStateDefinition().getPossibleStates().forEach(state->{
Direction facing = state.getValue(HORIZONTAL_FACING);
VoxelShape base_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(base_aabb, facing, true));
if(state.getValue(RIGHT_RAILING)) {
VoxelShape right_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(Auxiliaries.getMirroredAABB(railing_aabbs, Axis.X), facing, true));
base_shape = VoxelShapes.joinUnoptimized(base_shape, right_shape, IBooleanFunction.OR);
}
if(state.getValue(LEFT_RAILING)) {
VoxelShape left_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(railing_aabbs, facing, true));
base_shape = VoxelShapes.joinUnoptimized(base_shape, left_shape, IBooleanFunction.OR);
}
sh.put(state, base_shape);
csh.put(state, base_shape);
});
shapes = sh;
collision_shapes = csh;
y_rotations = new HashMap<>();
y_rotations.put(Direction.NORTH, 0);
y_rotations.put(Direction.EAST, 1);
y_rotations.put(Direction.SOUTH, 2);
y_rotations.put(Direction.WEST, 3);
y_rotations.put(Direction.UP, 0);
y_rotations.put(Direction.DOWN, 0);
registerDefaultState(super.defaultBlockState().setValue(LEFT_RAILING, false).setValue(RIGHT_RAILING, false));
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context)
{ return shapes.getOrDefault(state, VoxelShapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader worldIn, BlockPos pos, ISelectionContext context)
{ return collision_shapes.getOrDefault(state, VoxelShapes.block()); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RIGHT_RAILING, LEFT_RAILING); }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return ActionResultType.PASS;
final Block block = ((BlockItem)item).getBlock();
final Direction facing = state.getValue(HORIZONTAL_FACING);
if(block == this) {
final Direction hlv = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis().isHorizontal()).findFirst().orElse(Direction.NORTH);
BlockPos adjacent_pos;
if(hlv == facing) {
adjacent_pos = pos.above().relative(hlv);
} else if(hlv == facing.getOpposite()) {
adjacent_pos = pos.below().relative(hlv);
} else {
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return ActionResultType.CONSUME;
BlockState place_state = defaultBlockState().setValue(HORIZONTAL_FACING, facing);
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
} else if((block == ModContent.STEEL_CATWALK) || (block == ModContent.STEEL_CATWALK_TOP_ALIGNED)) {
BlockPos adjacent_pos;
adjacent_pos = pos.relative(facing);
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return ActionResultType.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return ActionResultType.CONSUME;
BlockState place_state = ModContent.STEEL_CATWALK_TOP_ALIGNED.defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
} else if(block == ModContent.STEEL_RAILING) {
Direction face = hit.getDirection();
int shrink = 0;
BlockState place_state = state;
if(face == Direction.UP) {
Vector3d rhv = hit.getLocation().subtract(Vector3d.atCenterOf(hit.getBlockPos())).multiply(new Vector3d(1,0,1)).cross(Vector3d.atLowerCornerOf(facing.getNormal()));
face = (rhv.y > 0) ? (facing.getClockWise()) : (facing.getCounterClockWise());
}
if(face == facing.getClockWise()) {
if(state.getValue(RIGHT_RAILING)) {
place_state = state.setValue(RIGHT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(RIGHT_RAILING, true);
shrink = 1;
}
} else if(face == facing.getCounterClockWise()) {
if(state.getValue(LEFT_RAILING)) {
place_state = state.setValue(LEFT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(LEFT_RAILING, true);
shrink = 1;
}
}
if(shrink != 0) EdCatwalkBlock.place_consume(place_state, world, pos, player, hand, shrink);
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
return ActionResultType.PASS;
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, @Nullable TileEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(LEFT_RAILING)?1:0)+(state.getValue(RIGHT_RAILING)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}
/*
* @file EdCatwalkStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stair version of the catwalk block, optional left/right railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.*;
public class EdCatwalkStairsBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final BooleanProperty RIGHT_RAILING = BooleanProperty.create("right_railing");
public static final BooleanProperty LEFT_RAILING = BooleanProperty.create("left_railing");
protected final Map<BlockState, VoxelShape> shapes;
protected final Map<BlockState, VoxelShape> collision_shapes;
protected final Map<Direction, Integer> y_rotations;
public EdCatwalkStairsBlock(long config, BlockBehaviour.Properties properties, final AABB[] base_aabb, final AABB[] railing_aabbs)
{
super(config, properties, base_aabb);
Map<BlockState, VoxelShape> sh = new HashMap<>();
Map<BlockState, VoxelShape> csh = new HashMap<>();
getStateDefinition().getPossibleStates().forEach(state->{
Direction facing = state.getValue(HORIZONTAL_FACING);
VoxelShape base_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(base_aabb, facing, true));
if(state.getValue(RIGHT_RAILING)) {
VoxelShape right_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(Auxiliaries.getMirroredAABB(railing_aabbs, Direction.Axis.X), facing, true));
base_shape = Shapes.joinUnoptimized(base_shape, right_shape, BooleanOp.OR);
}
if(state.getValue(LEFT_RAILING)) {
VoxelShape left_shape = Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(railing_aabbs, facing, true));
base_shape = Shapes.joinUnoptimized(base_shape, left_shape, BooleanOp.OR);
}
sh.put(state, base_shape);
csh.put(state, base_shape);
});
shapes = sh;
collision_shapes = csh;
y_rotations = new HashMap<>();
y_rotations.put(Direction.NORTH, 0);
y_rotations.put(Direction.EAST, 1);
y_rotations.put(Direction.SOUTH, 2);
y_rotations.put(Direction.WEST, 3);
y_rotations.put(Direction.UP, 0);
y_rotations.put(Direction.DOWN, 0);
registerDefaultState(super.defaultBlockState().setValue(LEFT_RAILING, false).setValue(RIGHT_RAILING, false));
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context)
{ return shapes.getOrDefault(state, Shapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context)
{ return collision_shapes.getOrDefault(state, Shapes.block()); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RIGHT_RAILING, LEFT_RAILING); }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if((!(item instanceof BlockItem))) return InteractionResult.PASS;
final Block block = ((BlockItem)item).getBlock();
final Direction facing = state.getValue(HORIZONTAL_FACING);
if(block == this) {
final Direction hlv = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis().isHorizontal()).findFirst().orElse(Direction.NORTH);
BlockPos adjacent_pos;
if(hlv == facing) {
adjacent_pos = pos.above().relative(hlv);
} else if(hlv == facing.getOpposite()) {
adjacent_pos = pos.below().relative(hlv);
} else {
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return InteractionResult.CONSUME;
BlockState place_state = defaultBlockState().setValue(HORIZONTAL_FACING, facing);
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
} else if((block == ModContent.STEEL_CATWALK) || (block == ModContent.STEEL_CATWALK_TOP_ALIGNED)) {
BlockPos adjacent_pos;
adjacent_pos = pos.relative(facing);
final BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state == null) return InteractionResult.CONSUME;
if(!adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) return InteractionResult.CONSUME;
BlockState place_state = ModContent.STEEL_CATWALK_TOP_ALIGNED.defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(place_state, world, adjacent_pos, player, hand, 1);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
} else if(block == ModContent.STEEL_RAILING) {
Direction face = hit.getDirection();
int shrink = 0;
BlockState place_state = state;
if(face == Direction.UP) {
Vec3 rhv = hit.getLocation().subtract(Vec3.atCenterOf(hit.getBlockPos())).multiply(new Vec3(1,0,1)).cross(Vec3.atLowerCornerOf(facing.getNormal()));
face = (rhv.y > 0) ? (facing.getClockWise()) : (facing.getCounterClockWise());
}
if(face == facing.getClockWise()) {
if(state.getValue(RIGHT_RAILING)) {
place_state = state.setValue(RIGHT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(RIGHT_RAILING, true);
shrink = 1;
}
} else if(face == facing.getCounterClockWise()) {
if(state.getValue(LEFT_RAILING)) {
place_state = state.setValue(LEFT_RAILING, false);
shrink = -1;
} else {
place_state = state.setValue(LEFT_RAILING, true);
shrink = 1;
}
}
if(shrink != 0) EdCatwalkBlock.place_consume(place_state, world, pos, player, hand, shrink);
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
return InteractionResult.PASS;
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
drops.add(new ItemStack(state.getBlock().asItem()));
int n = (state.getValue(LEFT_RAILING)?1:0)+(state.getValue(RIGHT_RAILING)?1:0);
if(n > 0) drops.add(new ItemStack(ModContent.STEEL_RAILING, n));
return drops;
}
}

View file

@ -1,106 +1,111 @@
/*
* @file EdCatwalkTopAlignedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Top aligned platforms, down-connection to poles.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import wile.engineersdecor.ModContent;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
public class EdCatwalkTopAlignedBlock extends DecorBlock.WaterLoggable implements IDecorBlock
{
public static final IntegerProperty VARIANT = IntegerProperty.create("variant", 0, 3);
protected final List<VoxelShape> variant_shapes;
public EdCatwalkTopAlignedBlock(long config, AbstractBlock.Properties properties, final VoxelShape[] variant_shapes)
{
super(config, properties, variant_shapes[0]);
registerDefaultState(super.defaultBlockState().setValue(VARIANT, 0));
this.variant_shapes = VARIANT.getPossibleValues().stream().map(i->(i<variant_shapes.length) ? (variant_shapes[i]) : (VoxelShapes.block())).collect(Collectors.toList());
}
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return variant_shapes.get(state.getValue(VARIANT)); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = adapted_state(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos());
if(context.getClickedFace() != Direction.UP) return state;
BlockState below = context.getLevel().getBlockState(context.getClickedPos().below());
if((state.getValue(VARIANT)==0) && (below.isFaceSturdy(context.getLevel(), context.getClickedPos().below(), Direction.UP))) return state.setValue(VARIANT, 3);
return state;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if(item != this.asItem()) return ActionResultType.PASS;
if(hit.getDirection().getAxis().isHorizontal()) return ActionResultType.PASS;
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(adapted_state(place_state, world, adjacent_pos), world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? ActionResultType.SUCCESS : ActionResultType.CONSUME;
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return adapted_state(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
// ---
private BlockState adapted_state(BlockState state, IWorld world, BlockPos pos)
{
BlockState below = world.getBlockState(pos.below());
if((below == null) || (state == null)) return state;
if((below.getBlock() == ModContent.THICK_STEEL_POLE) || (below.getBlock() == ModContent.THICK_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 1);
if((below.getBlock() == ModContent.THIN_STEEL_POLE) || (below.getBlock() == ModContent.THIN_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 2);
return state;
}
}
/*
* @file EdCatwalkTopAlignedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Top aligned platforms, down-connection to poles.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.List;
import java.util.stream.Collectors;
public class EdCatwalkTopAlignedBlock extends StandardBlocks.WaterLoggable
{
public static final IntegerProperty VARIANT = IntegerProperty.create("variant", 0, 3);
protected final List<VoxelShape> variant_shapes;
public EdCatwalkTopAlignedBlock(long config, BlockBehaviour.Properties properties, final VoxelShape[] variant_shapes)
{
super(config, properties, variant_shapes[0]);
registerDefaultState(super.defaultBlockState().setValue(VARIANT, 0));
this.variant_shapes = VARIANT.getPossibleValues().stream().map(i->(i<variant_shapes.length) ? (variant_shapes[i]) : (Shapes.block())).collect(Collectors.toList());
}
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return variant_shapes.get(state.getValue(VARIANT)); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = adapted_state(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos());
if(context.getClickedFace() != Direction.UP) return state;
BlockState below = context.getLevel().getBlockState(context.getClickedPos().below());
if((state.getValue(VARIANT)==0) && (below.isFaceSturdy(context.getLevel(), context.getClickedPos().below(), Direction.UP))) return state.setValue(VARIANT, 3);
return state;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
final Item item = player.getItemInHand(hand).getItem();
if(item != this.asItem()) return InteractionResult.PASS;
if(hit.getDirection().getAxis().isHorizontal()) return InteractionResult.PASS;
BlockPos adjacent_pos = pos.relative(player.getDirection());
BlockState adjacent_state = world.getBlockState(adjacent_pos);
if(adjacent_state.canBeReplaced(new DirectionalPlaceContext(world, adjacent_pos, hit.getDirection().getOpposite(), player.getItemInHand(hand), hit.getDirection()))) {
BlockState place_state = defaultBlockState();
place_state = place_state.setValue(WATERLOGGED,adjacent_state.getFluidState().getType()==Fluids.WATER);
EdCatwalkBlock.place_consume(adapted_state(place_state, world, adjacent_pos), world, adjacent_pos, player, hand, 1);
}
return world.isClientSide() ? InteractionResult.SUCCESS : InteractionResult.CONSUME;
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return adapted_state(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
// ---
private BlockState adapted_state(BlockState state, LevelAccessor world, BlockPos pos)
{
BlockState below = world.getBlockState(pos.below());
if((below == null) || (state == null)) return state;
if((below.getBlock() == ModContent.THICK_STEEL_POLE) || (below.getBlock() == ModContent.THICK_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 1);
if((below.getBlock() == ModContent.THIN_STEEL_POLE) || (below.getBlock() == ModContent.THIN_STEEL_POLE_HEAD)) return state.setValue(VARIANT, 2);
return state;
}
}

View file

@ -1,195 +1,204 @@
/*
* @file EdChair.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class.
*/
package wile.engineersdecor.blocks;
import net.minecraft.entity.monster.piglin.PiglinEntity;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.block.AbstractBlock;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.World;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.*;
import net.minecraft.entity.monster.*;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.IPacket;
import net.minecraft.util.math.*;
import net.minecraft.util.*;
import net.minecraftforge.fml.network.FMLPlayMessages;
import net.minecraftforge.fml.network.NetworkHooks;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import java.util.List;
import java.util.Random;
public class EdChair
{
private static boolean sitting_enabled = true;
private static double sitting_probability = 0.1;
private static double standup_probability = 0.01;
public static void on_config(boolean without_sitting, boolean without_mob_sitting, double sitting_probability_percent, double standup_probability_percent)
{
sitting_enabled = (!without_sitting);
sitting_probability = (without_sitting||without_mob_sitting) ? 0.0 : MathHelper.clamp(sitting_probability_percent/100, 0, 0.9);
standup_probability = (without_sitting||without_mob_sitting) ? 1.0 : MathHelper.clamp(standup_probability_percent/100, 1e-6, 1e-2);
ModConfig.log("Config chairs: sit:" + sitting_enabled + ", mob-sit: " + (sitting_probability*100) + "%, standup: " + (standup_probability) + "%.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class ChairBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public ChairBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABBs)
{ super(config, builder.randomTicks(), unrotatedAABBs); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(!sitting_enabled) return ActionResultType.PASS;
if(world.isClientSide()) return ActionResultType.SUCCESS;
EntityChair.sit(world, player, pos);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, World world, BlockPos pos, Entity entity)
{
if(sitting_enabled && (Math.random() < sitting_probability) && (entity instanceof MobEntity)) EntityChair.sit(world, (LivingEntity)entity, pos);
}
@Override
@SuppressWarnings("deprecation")
public void tick(BlockState state, ServerWorld world, BlockPos pos, Random rnd)
{
if((!sitting_enabled) || (sitting_probability < 1e-6)) return;
final List<LivingEntity> entities = world.getEntitiesOfClass(MobEntity.class, new AxisAlignedBB(pos).inflate(2,1,2).expandTowards(0,1,0), e->true);
if(entities.isEmpty()) return;
int index = rnd.nextInt(entities.size());
if((index < 0) || (index >= entities.size())) return;
EntityChair.sit(world, entities.get(index), pos);
}
}
//--------------------------------------------------------------------------------------------------------------------
// Entity
//--------------------------------------------------------------------------------------------------------------------
public static class EntityChair extends Entity
{
public static final double x_offset = 0.5d;
public static final double y_offset = 0.4d;
public static final double z_offset = 0.5d;
private int t_sit = 0;
public BlockPos chair_pos = new BlockPos(0,0,0);
public EntityChair(EntityType<? extends Entity> entityType, World world)
{
super(entityType, world);
blocksBuilding=true;
setDeltaMovement(Vector3d.ZERO);
canUpdate(true);
noPhysics=true;
}
public EntityChair(World world)
{ this(ModContent.ET_CHAIR, world); }
public static EntityChair customClientFactory(FMLPlayMessages.SpawnEntity spkt, World world)
{ return new EntityChair(world); }
public IPacket<?> getAddEntityPacket()
{ return NetworkHooks.getEntitySpawningPacket(this); }
public static boolean accepts_mob(LivingEntity entity)
{
if(!(entity instanceof net.minecraft.entity.monster.MonsterEntity)) return false;
if((entity.getType().getDimensions().height > 2.5) || (entity.getType().getDimensions().height > 2.0)) return false;
if(entity instanceof ZombieEntity) return true;
if(entity instanceof ZombieVillagerEntity) return true;
if(entity instanceof ZombifiedPiglinEntity) return true;
if(entity instanceof PiglinEntity) return true;
if(entity instanceof HuskEntity) return true;
if(entity instanceof StrayEntity) return true;
if(entity instanceof SkeletonEntity) return true;
if(entity instanceof WitherSkeletonEntity) return true;
return false;
}
public static void sit(World world, LivingEntity sitter, BlockPos pos)
{
if(!sitting_enabled) return;
if((world==null) || (world.isClientSide) || (sitter==null) || (pos==null)) return;
if((!(sitter instanceof PlayerEntity)) && (!accepts_mob(sitter))) return;
if(!world.getEntitiesOfClass(EntityChair.class, new AxisAlignedBB(pos)).isEmpty()) return;
if(sitter.isVehicle() || (!sitter.isAlive()) || (sitter.isPassenger()) ) return;
if((!world.isEmptyBlock(pos.above())) || (!world.isEmptyBlock(pos.above(2)))) return;
boolean on_top_of_block_position = true;
boolean use_next_negative_y_position = false;
EntityChair chair = new EntityChair(world);
BlockPos chair_pos = chair.blockPosition();
chair.chair_pos = pos;
chair.t_sit = 5;
chair.xo = chair_pos.getX();
chair.yo = chair_pos.getY();
chair.zo = chair_pos.getZ();
chair.setPos(pos.getX()+x_offset,pos.getY()+y_offset,pos.getZ()+z_offset);
world.addFreshEntity(chair);
sitter.startRiding(chair, true);
}
@Override
protected void defineSynchedData() {}
@Override
protected void readAdditionalSaveData(CompoundNBT compound) {}
@Override
protected void addAdditionalSaveData(CompoundNBT compound) {}
@Override
public boolean isPushable()
{ return false; }
@Override
public double getPassengersRidingOffset()
{ return 0.0; }
@Override
public void tick()
{
if(level.isClientSide) return;
super.tick();
if(--t_sit > 0) return;
Entity sitter = getPassengers().isEmpty() ? null : getPassengers().get(0);
if((sitter==null) || (!sitter.isAlive())) {
this.remove();
return;
}
boolean abort = (!sitting_enabled);
final BlockState state = level.getBlockState(chair_pos);
if((state==null) || (!(state.getBlock() instanceof ChairBlock))) abort = true;
if(!level.isEmptyBlock(chair_pos.above())) abort = true;
if((!(sitter instanceof PlayerEntity)) && (Math.random() < standup_probability)) abort = true;
if(abort) {
for(Entity e:getPassengers()) {
if(e.isAlive()) e.stopRiding();
}
this.remove();
}
}
}
}
/*
* @file EdChair.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.monster.*;
import net.minecraft.world.entity.monster.piglin.Piglin;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fmllegacy.network.FMLPlayMessages;
import net.minecraftforge.fmllegacy.network.NetworkHooks;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import java.util.List;
import java.util.Random;
public class EdChair
{
private static boolean sitting_enabled = true;
private static double sitting_probability = 0.1;
private static double standup_probability = 0.01;
public static void on_config(boolean without_sitting, boolean without_mob_sitting, double sitting_probability_percent, double standup_probability_percent)
{
sitting_enabled = (!without_sitting);
sitting_probability = (without_sitting||without_mob_sitting) ? 0.0 : Mth.clamp(sitting_probability_percent/100, 0, 0.9);
standup_probability = (without_sitting||without_mob_sitting) ? 1.0 : Mth.clamp(standup_probability_percent/100, 1e-6, 1e-2);
ModConfig.log("Config chairs: sit:" + sitting_enabled + ", mob-sit: " + (sitting_probability*100) + "%, standup: " + (standup_probability) + "%.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class ChairBlock extends StandardBlocks.HorizontalWaterLoggable
{
public ChairBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABBs)
{ super(config, builder.randomTicks(), unrotatedAABBs); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(!sitting_enabled) return InteractionResult.PASS;
if(world.isClientSide()) return InteractionResult.SUCCESS;
EntityChair.sit(world, player, pos);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity)
{
if(sitting_enabled && (Math.random() < sitting_probability) && (entity instanceof Mob)) EntityChair.sit(world, (LivingEntity)entity, pos);
}
@Override
@SuppressWarnings("deprecation")
public void tick(BlockState state, ServerLevel world, BlockPos pos, Random rnd)
{
if((!sitting_enabled) || (sitting_probability < 1e-6)) return;
final List<Mob> entities = world.getEntitiesOfClass(Mob.class, new AABB(pos).inflate(2,1,2).expandTowards(0,1,0), e->true);
if(entities.isEmpty()) return;
int index = rnd.nextInt(entities.size());
if((index < 0) || (index >= entities.size())) return;
EntityChair.sit(world, entities.get(index), pos);
}
}
//--------------------------------------------------------------------------------------------------------------------
// Entity
//--------------------------------------------------------------------------------------------------------------------
public static class EntityChair extends Entity
{
public static final double x_offset = 0.5d;
public static final double y_offset = 0.4d;
public static final double z_offset = 0.5d;
private int t_sit = 0;
public BlockPos chair_pos = new BlockPos(0,0,0);
public EntityChair(EntityType<? extends Entity> entityType, Level world)
{
super(entityType, world);
blocksBuilding=true;
setDeltaMovement(Vec3.ZERO);
canUpdate(true);
noPhysics=true;
}
public EntityChair(Level world)
{ this(ModContent.ET_CHAIR, world); }
public static EntityChair customClientFactory(FMLPlayMessages.SpawnEntity spkt, Level world)
{ return new EntityChair(world); }
public Packet<?> getAddEntityPacket()
{ return NetworkHooks.getEntitySpawningPacket(this); }
public static boolean accepts_mob(LivingEntity entity)
{
if(!(entity instanceof Monster)) return false;
if((entity.getType().getDimensions().height > 2.5) || (entity.getType().getDimensions().height > 2.0)) return false;
if(entity instanceof Zombie) return true;
if(entity instanceof ZombieVillager) return true;
if(entity instanceof ZombifiedPiglin) return true;
if(entity instanceof Piglin) return true;
if(entity instanceof Husk) return true;
if(entity instanceof Stray) return true;
if(entity instanceof Skeleton) return true;
if(entity instanceof WitherSkeleton) return true;
return false;
}
public static void sit(Level world, LivingEntity sitter, BlockPos pos)
{
if(!sitting_enabled) return;
if((world==null) || (world.isClientSide) || (sitter==null) || (pos==null)) return;
if((!(sitter instanceof Player)) && (!accepts_mob(sitter))) return;
if(!world.getEntitiesOfClass(EntityChair.class, new AABB(pos)).isEmpty()) return;
if(sitter.isVehicle() || (!sitter.isAlive()) || (sitter.isPassenger()) ) return;
if((!world.isEmptyBlock(pos.above())) || (!world.isEmptyBlock(pos.above(2)))) return;
boolean on_top_of_block_position = true;
boolean use_next_negative_y_position = false;
EntityChair chair = new EntityChair(world);
BlockPos chair_pos = chair.blockPosition();
chair.chair_pos = pos;
chair.t_sit = 5;
chair.xo = chair_pos.getX();
chair.yo = chair_pos.getY();
chair.zo = chair_pos.getZ();
chair.setPos(pos.getX()+x_offset,pos.getY()+y_offset,pos.getZ()+z_offset);
world.addFreshEntity(chair);
sitter.startRiding(chair, true);
}
@Override
protected void defineSynchedData() {}
@Override
protected void readAdditionalSaveData(CompoundTag compound) {}
@Override
protected void addAdditionalSaveData(CompoundTag compound) {}
@Override
public boolean isPushable()
{ return false; }
@Override
public double getPassengersRidingOffset()
{ return 0.0; }
@Override
public void tick()
{
if(level.isClientSide) return;
super.tick();
if(--t_sit > 0) return;
Entity sitter = getPassengers().isEmpty() ? null : getPassengers().get(0);
if((sitter==null) || (!sitter.isAlive())) {
this.remove(RemovalReason.DISCARDED);
return;
}
boolean abort = (!sitting_enabled);
final BlockState state = level.getBlockState(chair_pos);
if((state==null) || (!(state.getBlock() instanceof ChairBlock))) abort = true;
if(!level.isEmptyBlock(chair_pos.above())) abort = true;
if((!(sitter instanceof Player)) && (Math.random() < standup_probability)) abort = true;
if(abort) {
for(Entity e:getPassengers()) {
if(e.isAlive()) e.stopRiding();
}
this.remove(RemovalReason.DISCARDED);
}
}
}
}

View file

@ -1,98 +1,101 @@
/*
* @file EdChimneyBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block type for smoking chimneys.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.Random;
public class EdChimneyBlock extends DecorBlock.Cutout implements IDecorBlock
{
public static final IntegerProperty POWER = BlockStateProperties.POWER;
public EdChimneyBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ super(config, properties, aabb); }
public EdChimneyBlock(long config, AbstractBlock.Properties builder)
{
this(config, builder, new AxisAlignedBB(0,0,0,1,1,1));
registerDefaultState(super.defaultBlockState().setValue(POWER, 0)); // no smoke in JEI
}
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(POWER); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = super.getStateForPlacement(context);
if(state==null) return state;
int p = context.getLevel().getBestNeighborSignal(context.getClickedPos());
return state.setValue(POWER, p==0 ? 5 : p);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{ world.setBlock(pos, state.setValue(POWER, (state.getValue(POWER)+1) & 0xf), 1|2); return ActionResultType.sidedSuccess(world.isClientSide()); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
int p = world.getBestNeighborSignal(pos);
if(p != state.getValue(POWER)) world.setBlock(pos, state.setValue(POWER, p), 2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{
if(state.getBlock() != this) return;
final int p = state.getValue(POWER);
if(p==0) return;
int end = 1+rnd.nextInt(10) * p / 15;
for(int i=0; i<end; ++i) {
double rv = rnd.nextDouble() * p / 5;
world.addParticle(
(rv > 0.7 ? ParticleTypes.LARGE_SMOKE : (rv>0.4 ? ParticleTypes.SMOKE : ParticleTypes.CAMPFIRE_COSY_SMOKE)),
0.5+pos.getX()+(rnd.nextDouble()*0.2),
0.9+pos.getY()+(rnd.nextDouble()*0.1),
0.5+pos.getZ()+(rnd.nextDouble()*0.2),
-0.02 + rnd.nextDouble()*0.04,
+0.05 + rnd.nextDouble()*0.1,
-0.02 + rnd.nextDouble()*0.04
);
}
}
}
/*
* @file EdChimneyBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block type for smoking chimneys.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.Random;
public class EdChimneyBlock extends StandardBlocks.Cutout
{
public static final IntegerProperty POWER = BlockStateProperties.POWER;
public EdChimneyBlock(long config, BlockBehaviour.Properties properties, AABB aabb)
{ super(config, properties, aabb); }
public EdChimneyBlock(long config, BlockBehaviour.Properties builder)
{
this(config, builder, new AABB(0,0,0,1,1,1));
registerDefaultState(super.defaultBlockState().setValue(POWER, 0)); // no smoke in JEI
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(POWER); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = super.getStateForPlacement(context);
if(state==null) return state;
int p = context.getLevel().getBestNeighborSignal(context.getClickedPos());
return state.setValue(POWER, p==0 ? 5 : p);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{ world.setBlock(pos, state.setValue(POWER, (state.getValue(POWER)+1) & 0xf), 1|2); return InteractionResult.sidedSuccess(world.isClientSide()); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{
int p = world.getBestNeighborSignal(pos);
if(p != state.getValue(POWER)) world.setBlock(pos, state.setValue(POWER, p), 2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{
if(state.getBlock() != this) return;
final int p = state.getValue(POWER);
if(p==0) return;
int end = 1+rnd.nextInt(10) * p / 15;
for(int i=0; i<end; ++i) {
double rv = rnd.nextDouble() * p / 5;
world.addParticle(
(rv > 0.7 ? ParticleTypes.LARGE_SMOKE : (rv>0.4 ? ParticleTypes.SMOKE : ParticleTypes.CAMPFIRE_COSY_SMOKE)),
0.5+pos.getX()+(rnd.nextDouble()*0.2),
0.9+pos.getY()+(rnd.nextDouble()*0.1),
0.5+pos.getZ()+(rnd.nextDouble()*0.2),
-0.02 + rnd.nextDouble()*0.04,
+0.05 + rnd.nextDouble()*0.1,
-0.02 + rnd.nextDouble()*0.04
);
}
}
}

View file

@ -1,36 +1,37 @@
/*
* @file EdChimneyTrunkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof block with chimney trunk, only straight.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.state.properties.Half;
import net.minecraft.state.properties.StairsShape;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import javax.annotation.Nullable;
public class EdChimneyTrunkBlock extends EdRoofBlock implements IDecorBlock
{
public EdChimneyTrunkBlock(long config, AbstractBlock.Properties properties)
{ super(config, properties.dynamicShape(), VoxelShapes.empty(), VoxelShapes.empty()); }
public EdChimneyTrunkBlock(long config, AbstractBlock.Properties properties, VoxelShape add, VoxelShape cut)
{ super(config, properties, add, cut); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = super.getStateForPlacement(context);
return (state==null) ? (state) : (state.setValue(EdRoofBlock.SHAPE, StairsShape.STRAIGHT).setValue(EdRoofBlock.HALF, Half.BOTTOM));
}
}
/*
* @file EdChimneyTrunkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof block with chimney trunk, only straight.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.block.state.properties.StairsShape;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import javax.annotation.Nullable;
public class EdChimneyTrunkBlock extends EdRoofBlock
{
public EdChimneyTrunkBlock(long config, BlockBehaviour.Properties properties)
{ super(config, properties.dynamicShape(), Shapes.empty(), Shapes.empty()); }
public EdChimneyTrunkBlock(long config, BlockBehaviour.Properties properties, VoxelShape add, VoxelShape cut)
{ super(config, properties, add, cut); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = super.getStateForPlacement(context);
return (state==null) ? (state) : (state.setValue(EdRoofBlock.SHAPE, StairsShape.STRAIGHT).setValue(EdRoofBlock.HALF, Half.BOTTOM));
}
}

View file

@ -1,71 +1,72 @@
/*
* @file EdCornerOrnamentedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block for corner/quoin ornamentation.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector2f;
import net.minecraft.world.World;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.*;
public class EdCornerOrnamentedBlock extends DecorBlock.Directed
{
protected final HashSet<Block> compatible_blocks;
public EdCornerOrnamentedBlock(long config, AbstractBlock.Properties properties, Block[] assigned_wall_blocks)
{
super(config, properties, Auxiliaries.getPixeledAABB(0,0,0,16,16,16));
compatible_blocks = new HashSet<Block>(Arrays.asList(assigned_wall_blocks));
}
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
final World world = context.getLevel();
final BlockPos pos = context.getClickedPos();
// 1. Placement as below/above for corners, or placement adjacent horizontally if up/down facing.
for(Direction adj: Direction.values()) {
BlockState state = world.getBlockState(pos.relative(adj));
if(state.getBlock() != this) continue;
Direction facing = state.getValue(FACING);
if(facing.getAxis().isHorizontal() == (adj.getAxis().isVertical())) {
return super.getStateForPlacement(context).setValue(FACING, state.getValue(FACING));
}
}
// 2. By Player look angles with minimum horizontal diagonal deviation.
{
Direction facing = Direction.WEST;
final Vector2f look = context.getPlayer().getRotationVector();
final Direction hit_face = context.getClickedFace();
if((context.getClickedFace()==Direction.DOWN) && (look.x <= -60)) {
facing = Direction.DOWN;
} else if((context.getClickedFace()==Direction.UP) && (look.x >= 60)) {
facing = Direction.UP;
} else if(MathHelper.degreesDifferenceAbs(look.y, 45) <= 45) {
facing = Direction.NORTH;
} else if(MathHelper.degreesDifferenceAbs(look.y, 45+90) <= 45) {
facing = Direction.EAST;
} else if(MathHelper.degreesDifferenceAbs(look.y, 45+180) <= 45) {
facing = Direction.SOUTH;
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
}
}
/*
* @file EdCornerOrnamentedBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block for corner/quoin ornamentation.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec2;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashSet;
public class EdCornerOrnamentedBlock extends StandardBlocks.Directed
{
protected final HashSet<Block> compatible_blocks;
public EdCornerOrnamentedBlock(long config, BlockBehaviour.Properties properties, Block[] assigned_wall_blocks)
{
super(config, properties, Auxiliaries.getPixeledAABB(0,0,0,16,16,16));
compatible_blocks = new HashSet<>(Arrays.asList(assigned_wall_blocks));
}
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
final Level world = context.getLevel();
final BlockPos pos = context.getClickedPos();
// 1. Placement as below/above for corners, or placement adjacent horizontally if up/down facing.
for(Direction adj: Direction.values()) {
BlockState state = world.getBlockState(pos.relative(adj));
if(state.getBlock() != this) continue;
Direction facing = state.getValue(FACING);
if(facing.getAxis().isHorizontal() == (adj.getAxis().isVertical())) {
return super.getStateForPlacement(context).setValue(FACING, state.getValue(FACING));
}
}
// 2. By Player look angles with minimum horizontal diagonal deviation.
{
Direction facing = Direction.WEST;
final Vec2 look = context.getPlayer().getRotationVector();
final Direction hit_face = context.getClickedFace();
if((context.getClickedFace()==Direction.DOWN) && (look.x <= -60)) {
facing = Direction.DOWN;
} else if((context.getClickedFace()==Direction.UP) && (look.x >= 60)) {
facing = Direction.UP;
} else if(Mth.degreesDifferenceAbs(look.y, 45) <= 45) {
facing = Direction.NORTH;
} else if(Mth.degreesDifferenceAbs(look.y, 45+90) <= 45) {
facing = Direction.EAST;
} else if(Mth.degreesDifferenceAbs(look.y, 45+180) <= 45) {
facing = Direction.SOUTH;
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,31 +0,0 @@
/*
* @file EdDoorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Blocks representing centered doors opening by sliding
* to the sides.
*/
package wile.engineersdecor.blocks;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.block.*;
import wile.engineersdecor.libmc.blocks.StandardDoorBlock;
public class EdDoorBlock extends StandardDoorBlock implements IDecorBlock
{
public EdDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB[] open_aabbs_top, AxisAlignedBB[] open_aabbs_bottom, AxisAlignedBB[] closed_aabbs_top, AxisAlignedBB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound)
{ super(config, properties, open_aabbs_top, open_aabbs_bottom, closed_aabbs_top, closed_aabbs_bottom, open_sound, close_sound); }
public EdDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB open_aabb, AxisAlignedBB closed_aabb, SoundEvent open_sound, SoundEvent close_sound)
{ super(config, properties, open_aabb, closed_aabb, open_sound, close_sound); }
public EdDoorBlock(long config, AbstractBlock.Properties properties, SoundEvent open_sound, SoundEvent close_sound)
{ super(config, properties, open_sound, close_sound); }
public EdDoorBlock(long config, AbstractBlock.Properties properties)
{ super(config, properties); }
}

View file

@ -1,156 +1,165 @@
/*
* @file EdDoubleGateBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Gate blocks that can be one or two segments high.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.*;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.pathfinding.PathType;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
public class EdDoubleGateBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final IntegerProperty SEGMENT = IntegerProperty.create("segment", 0, 1);
public static final BooleanProperty OPEN = FenceGateBlock.OPEN;
public static final int SEGMENT_LOWER = 0;
public static final int SEGMENT_UPPER = 1;
protected final ArrayList<VoxelShape> collision_shapes_;
public EdDoubleGateBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB aabb)
{ this(config, properties, new AxisAlignedBB[]{aabb}); }
public EdDoubleGateBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB[] aabbs)
{
super(config, properties, aabbs);
AxisAlignedBB[] caabbs = new AxisAlignedBB[aabbs.length];
for(int i=0; i<caabbs.length; ++i) caabbs[i] = aabbs[i].expandTowards(0, 0.5, 0);
collision_shapes_ = new ArrayList<VoxelShape>(Arrays.asList(
VoxelShapes.block(),
VoxelShapes.block(),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.NORTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.SOUTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.WEST, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.EAST, true)),
VoxelShapes.block(),
VoxelShapes.block()
));
}
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return state.getValue(OPEN) ? VoxelShapes.empty() : collision_shapes_.get(state.getValue(HORIZONTAL_FACING).get3DDataValue() & 0x7); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SEGMENT).add(OPEN); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return getInitialState(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos()); }
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return getInitialState(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if((rayTraceResult.getDirection()==Direction.UP) || (rayTraceResult.getDirection()==Direction.DOWN) && (player.getItemInHand(hand).getItem()==this.asItem())) return ActionResultType.PASS;
if(world.isClientSide()) return ActionResultType.SUCCESS;
final boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open),2|8|16);
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
final BlockState adjacent = world.getBlockState(pos.below());
if(adjacent.getBlock()==this) world.setBlock(pos.below(), adjacent.setValue(OPEN, open), 2|8|16);
} else {
final BlockState adjacent = world.getBlockState(pos.above());
if(adjacent.getBlock()==this) world.setBlock(pos.above(), adjacent.setValue(OPEN, open), 2|8|16);
}
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, IBlockReader world, BlockPos pos, PathType type)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if(world.isClientSide) return;
boolean powered = false;
BlockState adjacent;
BlockPos adjacent_pos;
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
adjacent_pos = pos.below();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.getSignal(pos.above(), Direction.UP) > 0) {
powered = true;
} else if((adjacent!=null) && (world.hasNeighborSignal(pos.below(2)))) {
powered = true;
}
} else {
adjacent_pos = pos.above();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.hasNeighborSignal(pos)) {
powered = true;
} else if((adjacent!=null) && (world.getSignal(pos.above(2), Direction.UP) > 0)) {
powered = true;
}
}
boolean sound = false;
if(powered != state.getValue(OPEN)) {
world.setBlock(pos, state.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if((adjacent != null) && (powered != adjacent.getValue(OPEN))) {
world.setBlock(adjacent_pos, adjacent.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if(sound) {
world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
}
}
// -------------------------------------------------------------------------------------------------------------------
private BlockState getInitialState(BlockState state, IWorld world, BlockPos pos)
{
final BlockState down = world.getBlockState(pos.below());
if(down.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_UPPER).setValue(OPEN, down.getValue(OPEN)).setValue(HORIZONTAL_FACING, down.getValue(HORIZONTAL_FACING));
final BlockState up = world.getBlockState(pos.above());
if(up.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, up.getValue(OPEN)).setValue(HORIZONTAL_FACING, up.getValue(HORIZONTAL_FACING));
return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, false);
}
}
/*
* @file EdDoubleGateBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Gate blocks that can be one or two segments high.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
public class EdDoubleGateBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final IntegerProperty SEGMENT = IntegerProperty.create("segment", 0, 1);
public static final BooleanProperty OPEN = FenceGateBlock.OPEN;
public static final int SEGMENT_LOWER = 0;
public static final int SEGMENT_UPPER = 1;
protected final ArrayList<VoxelShape> collision_shapes_;
public EdDoubleGateBlock(long config, BlockBehaviour.Properties properties, AABB aabb)
{ this(config, properties, new AABB[]{aabb}); }
public EdDoubleGateBlock(long config, BlockBehaviour.Properties properties, AABB[] aabbs)
{
super(config, properties, aabbs);
AABB[] caabbs = new AABB[aabbs.length];
for(int i=0; i<caabbs.length; ++i) caabbs[i] = aabbs[i].expandTowards(0, 0.5, 0);
collision_shapes_ = new ArrayList<>(Arrays.asList(
Shapes.block(),
Shapes.block(),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.NORTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.SOUTH, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.WEST, true)),
Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(caabbs, Direction.EAST, true)),
Shapes.block(),
Shapes.block()
));
}
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return state.getValue(OPEN) ? Shapes.empty() : collision_shapes_.get(state.getValue(HORIZONTAL_FACING).get3DDataValue() & 0x7); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SEGMENT).add(OPEN); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return getInitialState(super.getStateForPlacement(context), context.getLevel(), context.getClickedPos()); }
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return getInitialState(super.updateShape(state, facing, facingState, world, pos, facingPos), world, pos); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if((rayTraceResult.getDirection()==Direction.UP) || (rayTraceResult.getDirection()==Direction.DOWN) && (player.getItemInHand(hand).getItem()==this.asItem())) return InteractionResult.PASS;
if(world.isClientSide()) return InteractionResult.SUCCESS;
final boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open),2|8|16);
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
final BlockState adjacent = world.getBlockState(pos.below());
if(adjacent.getBlock()==this) world.setBlock(pos.below(), adjacent.setValue(OPEN, open), 2|8|16);
} else {
final BlockState adjacent = world.getBlockState(pos.above());
if(adjacent.getBlock()==this) world.setBlock(pos.above(), adjacent.setValue(OPEN, open), 2|8|16);
}
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if(world.isClientSide) return;
boolean powered = false;
BlockState adjacent;
BlockPos adjacent_pos;
if(state.getValue(SEGMENT) == SEGMENT_UPPER) {
adjacent_pos = pos.below();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.getSignal(pos.above(), Direction.UP) > 0) {
powered = true;
} else if((adjacent!=null) && (world.hasNeighborSignal(pos.below(2)))) {
powered = true;
}
} else {
adjacent_pos = pos.above();
adjacent = world.getBlockState(adjacent_pos);
if(adjacent.getBlock()!=this) adjacent = null;
if(world.hasNeighborSignal(pos)) {
powered = true;
} else if((adjacent!=null) && (world.getSignal(pos.above(2), Direction.UP) > 0)) {
powered = true;
}
}
boolean sound = false;
if(powered != state.getValue(OPEN)) {
world.setBlock(pos, state.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if((adjacent != null) && (powered != adjacent.getValue(OPEN))) {
world.setBlock(adjacent_pos, adjacent.setValue(OPEN, powered), 2|8|16);
sound = true;
}
if(sound) {
world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
}
}
// -------------------------------------------------------------------------------------------------------------------
private BlockState getInitialState(BlockState state, LevelAccessor world, BlockPos pos)
{
final BlockState down = world.getBlockState(pos.below());
if(down.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_UPPER).setValue(OPEN, down.getValue(OPEN)).setValue(HORIZONTAL_FACING, down.getValue(HORIZONTAL_FACING));
final BlockState up = world.getBlockState(pos.above());
if(up.getBlock() == this) return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, up.getValue(OPEN)).setValue(HORIZONTAL_FACING, up.getValue(HORIZONTAL_FACING));
return state.setValue(SEGMENT, SEGMENT_LOWER).setValue(OPEN, false);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,30 @@
/*
* @file EdFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardFenceBlock;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorldReader;
import net.minecraft.block.*;
public class EdFenceBlock extends StandardFenceBlock implements IDecorBlock
{
public EdFenceBlock(long config, AbstractBlock.Properties properties)
{ super(config, properties); }
public EdFenceBlock(long config, AbstractBlock.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{ super(config, properties, pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); }
@Override
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{ return ((facingState.getBlock()) instanceof EdDoubleGateBlock) || super.attachesTo(facingState, world, facingPos, side); }
}
/*
* @file EdFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import wile.engineersdecor.libmc.blocks.StandardFenceBlock;
public class EdFenceBlock extends StandardFenceBlock
{
public EdFenceBlock(long config, BlockBehaviour.Properties properties)
{ super(config, properties); }
public EdFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{ super(config, properties, pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); }
@Override
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{ return ((facingState.getBlock()) instanceof EdDoubleGateBlock) || super.attachesTo(facingState, world, facingPos, side); }
}

View file

@ -1,72 +1,73 @@
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
public class EdFloorGratingBlock extends DecorBlock.WaterLoggable implements IDecorBlock
{
public EdFloorGratingBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, World world, BlockPos pos, Entity entity)
{
if(!(entity instanceof ItemEntity)) return;
final boolean colliding = ((entity.position().y-pos.getY()) > 0.7);
if(colliding || (entity.getDeltaMovement().y() > 0)) {
double x = pos.getX() + 0.5;
double y = MathHelper.clamp(entity.position().y-0.3, pos.getY(), pos.getY()+0.6);
double z = pos.getZ() + 0.5;
double vx = entity.getDeltaMovement().x();
double vy = entity.getDeltaMovement().y();
double vz = entity.getDeltaMovement().z();
if(colliding) {
vx = 0;
vy = -0.3;
vz = 0;
if((entity.position().y-pos.getY()) > 0.8) y = pos.getY() + 0.6;
entity.xo = x+0.1;
entity.yo = y+0.1;
entity.zo = z+0.1;
}
vy = MathHelper.clamp(vy, -0.3, 0);
entity.setDeltaMovement(vx, vy, vz);
entity.fallDistance = 0;
entity.teleportTo(x,y,z);
}
}
}
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
public class EdFloorGratingBlock extends StandardBlocks.WaterLoggable
{
public EdFloorGratingBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity)
{
if(!(entity instanceof ItemEntity)) return;
final boolean colliding = ((entity.position().y-pos.getY()) > 0.7);
if(colliding || (entity.getDeltaMovement().y() > 0)) {
double x = pos.getX() + 0.5;
double y = Mth.clamp(entity.position().y-0.3, pos.getY(), pos.getY()+0.6);
double z = pos.getZ() + 0.5;
double vx = entity.getDeltaMovement().x();
double vy = entity.getDeltaMovement().y();
double vz = entity.getDeltaMovement().z();
if(colliding) {
vx = 0;
vy = -0.3;
vz = 0;
if((entity.position().y-pos.getY()) > 0.8) y = pos.getY() + 0.6;
entity.xo = x+0.1;
entity.yo = y+0.1;
entity.zo = z+0.1;
}
vy = Mth.clamp(vy, -0.3, 0);
entity.setDeltaMovement(vx, vy, vz);
entity.fallDistance = 0;
entity.teleportTo(x,y,z);
}
}
}

View file

@ -1,428 +1,399 @@
/*
* @file EdFluidBarrel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple fluid tank with a built-in gauge.
*/
package wile.engineersdecor.blocks;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.util.*;
import net.minecraft.world.IWorldReader;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Fluidics;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EdFluidBarrel
{
//--------------------------------------------------------------------------------------------------------------------
// Config
//--------------------------------------------------------------------------------------------------------------------
private static int capacity_ = 12000;
private static int item_fluid_handler_transfer_rate_ = 1000;
private static int tile_fluid_handler_transfer_rate_ = 1000;
public static void on_config(int tank_capacity, int transfer_rate)
{
capacity_ = MathHelper.clamp(tank_capacity, 2000, 64000);
tile_fluid_handler_transfer_rate_ = MathHelper.clamp(tank_capacity, 50, 4096);
item_fluid_handler_transfer_rate_ = tile_fluid_handler_transfer_rate_;
ModConfig.log("Config fluid barrel: capacity:" + capacity_ + "mb, transfer-rate:" + tile_fluid_handler_transfer_rate_ + "mb/t.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock, StandardBlocks.IBlockItemFactory
{
public static final int FILL_LEVEL_MAX = 4;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidBarrelBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP).setValue(FILL_LEVEL, 0));
}
// IBlockItemFactory ----------------------------------------------------------------------------
@Override
public BlockItem getBlockItem(Block block, Item.Properties builder)
{ return new FluidBarrelItem(block, builder); }
// IStandardBlock --------------------------------------------------------------------------------
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, final TileEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidBarrelTileEntity)) return stacks;
ItemStack stack = new ItemStack(this, 1);
CompoundNBT te_nbt = ((FluidBarrelTileEntity) te).clear_getnbt();
if(!te_nbt.isEmpty()) {
CompoundNBT nbt = new CompoundNBT();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
return stacks;
}
// Block/IForgeBlock -----------------------------------------------------------------------------
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(final ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{
if(
(!(stack.getItem() instanceof FluidBarrelItem)) ||
(Auxiliaries.Tooltip.helpCondition())
) {
super.appendHoverText(stack, world, tooltip, flag); return;
}
FluidStack fs = FluidBarrelItem.getFluid(stack);
if(!fs.isEmpty()) {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip", new Object[] {
Integer.toString(fs.getAmount()),
Integer.toString(capacity_),
new TranslationTextComponent(fs.getTranslationKey())
}));
} else {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip.empty", new Object[] {
"0",
Integer.toString(capacity_),
}));
}
if(!Auxiliaries.Tooltip.extendedTipCondition()) {
super.appendHoverText(stack, world, tooltip, flag);
}
}
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new EdFluidBarrel.FluidBarrelTileEntity(); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockState state = super.getStateForPlacement(context);
if(!context.getPlayer().isShiftKeyDown()) state = state.setValue(FACING, Direction.UP);
return state;
}
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundNBT te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return;
((FluidBarrelTileEntity)te).readnbt(te_nbt);
((FluidBarrelTileEntity)te).setChanged();
world.getBlockTicks().scheduleTick(pos, this, 4);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(player.getItemInHand(hand).getItem() == asItem()) return ActionResultType.PASS; // Pass that to block placement.
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return ActionResultType.FAIL;
if(!((FluidBarrelTileEntity)te).handlePlayerInteraction(state, world, pos, player, hand)) return ActionResultType.PASS;
world.getBlockTicks().scheduleTick(pos, this, 4);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, World world, BlockPos pos)
{
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return 0;
return (int)MathHelper.clamp(((FluidBarrelTileEntity)te).getNormalizedFillLevel() * 15, 0, 15);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelTileEntity extends TileEntity implements ICapabilityProvider, ITickableTileEntity
{
private final int TICK_INTERVAL = 10;
private int tick_timer_ = 0;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
public FluidBarrelTileEntity()
{ this(ModContent.TET_FLUID_BARREL); }
public FluidBarrelTileEntity(TileEntityType<?> te_type)
{
super(te_type);
tank_ = new Fluidics.Tank(capacity_);
tank_.setInteractionNotifier((t,d)->on_tank_changed());
fluid_handler_ = tank_.createFluidHandler();
}
public void readnbt(CompoundNBT nbt)
{ tank_.load(nbt); }
public CompoundNBT writenbt(CompoundNBT nbt)
{ tank_.save(nbt); return nbt; }
public boolean handlePlayerInteraction(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand)
{
if(world.isClientSide()) return false;
{
Tuple<Fluid,Integer> transferred = Fluidics.manualTrackedFluidHandlerInteraction(world, pos, null, player, hand);
if(transferred==null) {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundCategory.BLOCKS, 0.2f, 0.02f);
} else if(transferred.getB() > 0) {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_EMPTY_LAVA: SoundEvents.BUCKET_EMPTY;
world.playSound(null, pos, se, SoundCategory.BLOCKS, 1f, 1f);
} else {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_FILL_LAVA : SoundEvents.BUCKET_FILL;
world.playSound(null, pos, se, SoundCategory.BLOCKS, 1f, 1f);
}
}
{
int vol = tank_.getFluidAmount();
int cap = tank_.getCapacity();
String name = (new TranslationTextComponent(tank_.getFluid().getTranslationKey())).getString();
if((vol>0) && (cap>0)) {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status", new Object[]{
Integer.toString(vol), Integer.toString(cap), name
}));
} else {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status.empty", new Object[]{
Integer.toString(vol), Integer.toString(cap)
}));
}
}
return true;
}
public double getNormalizedFillLevel()
{ return (tank_.isEmpty()) ? (0) : ((double)tank_.getFluidAmount()/(double)tank_.getCapacity()); }
protected void on_tank_changed()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); return writenbt(nbt); }
@Override
public void setRemoved()
{ super.setRemoved(); fluid_handler_.invalidate(); }
public CompoundNBT clear_getnbt()
{ return tank_.save(new CompoundNBT()); }
// ICapabilityProvider --------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickableTileEntity --------------------------------------------------------------------
private boolean transfer_down()
{
if(tank_.isEmpty()) return false;
final IFluidHandler fh = FluidUtil.getFluidHandler(level, worldPosition.below(), Direction.UP).orElse(null);
if(fh==null) return false;
final FluidStack fs = tank_.getFluid().copy();
if(fs.getAmount() > tile_fluid_handler_transfer_rate_) fs.setAmount(tile_fluid_handler_transfer_rate_);
final int nfilled = fh.fill(fs, FluidAction.EXECUTE);
if(nfilled <= 0) return false;
tank_.drain(nfilled, FluidAction.EXECUTE);
return true;
}
public void tick()
{
if((level.isClientSide()) || (--tick_timer_>=0)) return;
tick_timer_ = TICK_INTERVAL;
final BlockState state = getBlockState();
final Block block = state.getBlock();
if(!(block instanceof FluidBarrelBlock)) return;
if(state.getValue(FluidBarrelBlock.FACING).getAxis().isVertical()) transfer_down(); // tick_timer_ ==> 1 if something was transferred, otherwise no need to waste CPU
double norm_level = getNormalizedFillLevel();
int fill_level = (norm_level <= 0) ? 0 : ((int)MathHelper.clamp((norm_level * FluidBarrelBlock.FILL_LEVEL_MAX)+0.5, 1, FluidBarrelBlock.FILL_LEVEL_MAX));
if(fill_level != state.getValue(FluidBarrelBlock.FILL_LEVEL)) {
level.setBlock(worldPosition, state.setValue(FluidBarrelBlock.FILL_LEVEL, fill_level), 2);
level.updateNeighborsAt(worldPosition, block);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Block item
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelItem extends BlockItem
{
public FluidBarrelItem(Block block, Item.Properties builder)
{ super(block, builder); }
private static CompoundNBT read_fluid_nbt(ItemStack stack)
{
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return new CompoundNBT();
final CompoundNBT nbt = stack.getTag().getCompound("tedata");
if(!nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) return new CompoundNBT();
return nbt.getCompound("tank");
}
private static void write_fluid_nbt(ItemStack stack, CompoundNBT fluid_nbt)
{
if((fluid_nbt==null) || (fluid_nbt.isEmpty())) {
if((!stack.hasTag()) || (!stack.getTag().contains("tedata", Constants.NBT.TAG_COMPOUND))) return;
final CompoundNBT tag = stack.getTag();
final CompoundNBT tedata = tag.getCompound("tedata");
if(tedata.contains("tank")) tedata.remove("tank");
if(tedata.isEmpty()) tag.remove("tedata");
stack.setTag(tag.isEmpty() ? null : tag);
} else {
CompoundNBT tag = stack.getTag();
if(tag==null) tag = new CompoundNBT();
CompoundNBT tedata = tag.getCompound("tedata");
if(tedata==null) tedata = new CompoundNBT();
tedata.put("tank", fluid_nbt);
tag.put("tedata", tedata);
stack.setTag(tag);
}
}
public static FluidStack getFluid(ItemStack stack)
{
final CompoundNBT nbt = read_fluid_nbt(stack);
return (nbt.isEmpty()) ? (FluidStack.EMPTY) : (FluidStack.loadFluidStackFromNBT(nbt));
}
public static ItemStack setFluid(ItemStack stack, FluidStack fs)
{ write_fluid_nbt(stack, fs.writeToNBT(new CompoundNBT())); return stack; }
@Override
public int getItemStackLimit(ItemStack stack)
{ return (!getFluid(stack).isEmpty()) ? 1 : super.getItemStackLimit(stack); }
@Override
public boolean showDurabilityBar(ItemStack stack)
{ return (!getFluid(stack).isEmpty()); }
@Override
public double getDurabilityForDisplay(ItemStack stack)
{ return 1.0 - MathHelper.clamp(((double)(getFluid(stack).getAmount()))/((double)capacity_), 0.0, 1.0); }
@Override
public int getRGBDurabilityForDisplay(ItemStack stack)
{ return 0x336633; }
@Override
public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundNBT nbt)
{ return new Fluidics.FluidContainerItemCapabilityWrapper(stack, capacity_, item_fluid_handler_transfer_rate_, (s)->read_fluid_nbt(s), (s,n)->write_fluid_nbt(s,n), e->true); }
@Override
public boolean hasContainerItem(ItemStack stack)
{ return (stack.getCount()==1) && (!getFluid(stack).isEmpty()); }
@Override
public ItemStack getContainerItem(ItemStack stack)
{
if(stack.getCount()!=1) return ItemStack.EMPTY;
FluidStack fs = getFluid(stack);
if(fs.getAmount() > 1000) fs.shrink(1000); else fs = FluidStack.EMPTY;
return setFluid(stack, fs);
}
}
}
/*
* @file EdFluidBarrel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple fluid tank with a built-in gauge.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Fluidics;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EdFluidBarrel
{
//--------------------------------------------------------------------------------------------------------------------
// Config
//--------------------------------------------------------------------------------------------------------------------
private static int capacity_ = 12000;
private static int item_fluid_handler_transfer_rate_ = 1000;
private static int tile_fluid_handler_transfer_rate_ = 1000;
public static void on_config(int tank_capacity, int transfer_rate)
{
capacity_ = Mth.clamp(tank_capacity, 2000, 64000);
tile_fluid_handler_transfer_rate_ = Mth.clamp(tank_capacity, 50, 4096);
item_fluid_handler_transfer_rate_ = tile_fluid_handler_transfer_rate_;
ModConfig.log("Config fluid barrel: capacity:" + capacity_ + "mb, transfer-rate:" + tile_fluid_handler_transfer_rate_ + "mb/t.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelBlock extends StandardBlocks.DirectedWaterLoggable implements StandardEntityBlocks.IStandardEntityBlock<FluidBarrelTileEntity>, StandardBlocks.IBlockItemFactory
{
public static final int FILL_LEVEL_MAX = 4;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidBarrelBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP).setValue(FILL_LEVEL, 0));
}
@Nullable
@Override
public BlockEntityType<EdFluidBarrel.FluidBarrelTileEntity> getBlockEntityType()
{ return ModContent.TET_FLUID_BARREL; }
@Override
public BlockItem getBlockItem(Block block, Item.Properties builder)
{ return new FluidBarrelItem(block, builder); }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, final BlockEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidBarrelTileEntity)) return stacks;
ItemStack stack = new ItemStack(this, 1);
CompoundTag te_nbt = ((FluidBarrelTileEntity) te).clear_getnbt();
if(!te_nbt.isEmpty()) {
CompoundTag nbt = new CompoundTag();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
return stacks;
}
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(final ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{
if((!(stack.getItem() instanceof FluidBarrelItem)) || (Auxiliaries.Tooltip.helpCondition())) {
super.appendHoverText(stack, world, tooltip, flag); return;
}
FluidStack fs = FluidBarrelItem.getFluid(stack);
if(!fs.isEmpty()) {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip", Integer.toString(fs.getAmount()), Integer.toString(capacity_), new TranslatableComponent(fs.getTranslationKey())));
} else {
tooltip.add(Auxiliaries.localizable(getDescriptionId()+".status.tip.empty", "0", Integer.toString(capacity_)));
}
if(!Auxiliaries.Tooltip.extendedTipCondition()) {
super.appendHoverText(stack, world, tooltip, flag);
}
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockState state = super.getStateForPlacement(context);
if(!context.getPlayer().isShiftKeyDown()) state = state.setValue(FACING, Direction.UP);
return state;
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundTag te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return;
((FluidBarrelTileEntity)te).readnbt(te_nbt);
te.setChanged();
world.getBlockTicks().scheduleTick(pos, this, 4);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(player.getItemInHand(hand).getItem() == asItem()) return InteractionResult.PASS; // Pass that to block placement.
if(world.isClientSide()) return InteractionResult.SUCCESS;
if(!(world.getBlockEntity(pos) instanceof final FluidBarrelTileEntity te)) return InteractionResult.FAIL;
if(!te.handlePlayerInteraction(state, world, pos, player, hand)) return InteractionResult.PASS;
world.getBlockTicks().scheduleTick(pos, this, 4);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos)
{
BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidBarrelTileEntity)) return 0;
return (int)Mth.clamp(((FluidBarrelTileEntity)te).getNormalizedFillLevel() * 15, 0, 15);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelTileEntity extends StandardEntityBlocks.StandardBlockEntity implements ICapabilityProvider
{
private final int TICK_INTERVAL = 10;
private int tick_timer_ = 0;
private final Fluidics.Tank tank_ = (new Fluidics.Tank(capacity_)).setInteractionNotifier((t,d)->on_tank_changed());
private final LazyOptional<IFluidHandler> fluid_handler_ = tank_.createFluidHandler();
public FluidBarrelTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_FLUID_BARREL, pos, state); }
public void readnbt(CompoundTag nbt)
{ tank_.load(nbt); }
public CompoundTag writenbt(CompoundTag nbt)
{ tank_.save(nbt); return nbt; }
public boolean handlePlayerInteraction(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand)
{
if(world.isClientSide()) return false;
{
Tuple<Fluid,Integer> transferred = Fluidics.manualTrackedFluidHandlerInteraction(world, pos, null, player, hand);
if(transferred==null) {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundSource.BLOCKS, 0.2f, 0.02f);
} else if(transferred.getB() > 0) {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_EMPTY_LAVA: SoundEvents.BUCKET_EMPTY;
world.playSound(null, pos, se, SoundSource.BLOCKS, 1f, 1f);
} else {
SoundEvent se = (transferred.getA()==Fluids.LAVA) ? SoundEvents.BUCKET_FILL_LAVA : SoundEvents.BUCKET_FILL;
world.playSound(null, pos, se, SoundSource.BLOCKS, 1f, 1f);
}
}
{
int vol = tank_.getFluidAmount();
int cap = tank_.getCapacity();
String name = (new TranslatableComponent(tank_.getFluid().getTranslationKey())).getString();
if((vol>0) && (cap>0)) {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status", Integer.toString(vol), Integer.toString(cap), name));
} else {
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.fluid_barrel.status.empty", Integer.toString(vol), Integer.toString(cap)));
}
}
return true;
}
public double getNormalizedFillLevel()
{ return (tank_.isEmpty()) ? (0) : ((double)tank_.getFluidAmount()/(double)tank_.getCapacity()); }
protected void on_tank_changed()
{ if(tick_timer_ > 2) tick_timer_ = 2; }
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); return writenbt(nbt); }
@Override
public void setRemoved()
{ super.setRemoved(); fluid_handler_.invalidate(); }
public CompoundTag clear_getnbt()
{ return tank_.save(new CompoundTag()); }
// ICapabilityProvider --------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// Tick --------------------------------------------------------------------
private boolean transfer_down()
{
if(tank_.isEmpty()) return false;
final IFluidHandler fh = Fluidics.handler(level, worldPosition.below(), Direction.UP);
if(fh==null) return false;
final FluidStack fs = tank_.getFluid().copy();
if(fs.getAmount() > tile_fluid_handler_transfer_rate_) fs.setAmount(tile_fluid_handler_transfer_rate_);
final int nfilled = fh.fill(fs, IFluidHandler.FluidAction.EXECUTE);
if(nfilled <= 0) return false;
tank_.drain(nfilled, IFluidHandler.FluidAction.EXECUTE);
return true;
}
public void tick()
{
if((level.isClientSide()) || (--tick_timer_>=0)) return;
tick_timer_ = TICK_INTERVAL;
final BlockState state = getBlockState();
final Block block = state.getBlock();
if(!(block instanceof FluidBarrelBlock)) return;
if(state.getValue(FluidBarrelBlock.FACING).getAxis().isVertical()) transfer_down(); // tick_timer_ ==> 1 if something was transferred, otherwise no need to waste CPU
double norm_level = getNormalizedFillLevel();
int fill_level = (norm_level <= 0) ? 0 : ((int)Mth.clamp((norm_level * FluidBarrelBlock.FILL_LEVEL_MAX)+0.5, 1, FluidBarrelBlock.FILL_LEVEL_MAX));
if(fill_level != state.getValue(FluidBarrelBlock.FILL_LEVEL)) {
level.setBlock(worldPosition, state.setValue(FluidBarrelBlock.FILL_LEVEL, fill_level), 2);
level.updateNeighborsAt(worldPosition, block);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Block item
//--------------------------------------------------------------------------------------------------------------------
public static class FluidBarrelItem extends BlockItem
{
public FluidBarrelItem(Block block, Item.Properties builder)
{ super(block, builder); }
private static CompoundTag read_fluid_nbt(ItemStack stack)
{
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return new CompoundTag();
final CompoundTag nbt = stack.getTag().getCompound("tedata");
if(!nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) return new CompoundTag();
return nbt.getCompound("tank");
}
private static void write_fluid_nbt(ItemStack stack, CompoundTag fluid_nbt)
{
if((fluid_nbt==null) || (fluid_nbt.isEmpty())) {
if((!stack.hasTag()) || (!stack.getTag().contains("tedata", Constants.NBT.TAG_COMPOUND))) return;
final CompoundTag tag = stack.getTag();
final CompoundTag tedata = tag.getCompound("tedata");
if(tedata.contains("tank")) tedata.remove("tank");
if(tedata.isEmpty()) tag.remove("tedata");
stack.setTag(tag.isEmpty() ? null : tag);
} else {
CompoundTag tag = stack.getTag();
if(tag==null) tag = new CompoundTag();
CompoundTag tedata = tag.getCompound("tedata");
if(tedata==null) tedata = new CompoundTag();
tedata.put("tank", fluid_nbt);
tag.put("tedata", tedata);
stack.setTag(tag);
}
}
public static FluidStack getFluid(ItemStack stack)
{
final CompoundTag nbt = read_fluid_nbt(stack);
return (nbt.isEmpty()) ? (FluidStack.EMPTY) : (FluidStack.loadFluidStackFromNBT(nbt));
}
public static ItemStack setFluid(ItemStack stack, FluidStack fs)
{ write_fluid_nbt(stack, fs.writeToNBT(new CompoundTag())); return stack; }
@Override
public int getItemStackLimit(ItemStack stack)
{ return (!getFluid(stack).isEmpty()) ? 1 : super.getItemStackLimit(stack); }
@Override
public boolean showDurabilityBar(ItemStack stack)
{ return (!getFluid(stack).isEmpty()); }
@Override
public double getDurabilityForDisplay(ItemStack stack)
{ return 1.0 - Mth.clamp(((double)(getFluid(stack).getAmount()))/((double)capacity_), 0.0, 1.0); }
@Override
public int getRGBDurabilityForDisplay(ItemStack stack)
{ return 0x336633; }
@Override
public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundTag nbt)
{ return new Fluidics.FluidContainerItemCapabilityWrapper(stack, capacity_, item_fluid_handler_transfer_rate_, FluidBarrelItem::read_fluid_nbt, FluidBarrelItem::write_fluid_nbt, e->true); }
@Override
public boolean hasContainerItem(ItemStack stack)
{ return (stack.getCount()==1) && (!getFluid(stack).isEmpty()); }
@Override
public ItemStack getContainerItem(ItemStack stack)
{
if(stack.getCount()!=1) return ItemStack.EMPTY;
FluidStack fs = getFluid(stack);
if(fs.getAmount() > 1000) fs.shrink(1000); else fs = FluidStack.EMPTY;
return setFluid(stack, fs);
}
}
}

View file

@ -1,416 +1,400 @@
/*
* @file EdFluidFunnel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* A device that collects and stores fluid blocks above it.
* Tracks flowing fluid to their source blocks. Compatible
* with vanilla infinite water source.
*/
package wile.engineersdecor.blocks;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.world.IWorldReader;
import net.minecraft.block.*;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.FluidState;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.*;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.*;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Fluidics;
import javax.annotation.Nullable;
import java.util.*;
public class EdFluidFunnel
{
private static boolean with_device_fluid_handler_collection = false;
public static void on_config(boolean with_tank_fluid_collection)
{
with_device_fluid_handler_collection = with_tank_fluid_collection;
ModConfig.log("Config fluid funnel: tank-fluid-collection:" + with_device_fluid_handler_collection + ".");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelBlock extends DecorBlock.Cutout implements IDecorBlock
{
public static final int FILL_LEVEL_MAX = 3;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidFunnelBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(FILL_LEVEL, 0); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new FluidFunnelTileEntity(); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, World world, BlockPos pos)
{ return MathHelper.clamp((state.getValue(FILL_LEVEL)*5), 0, 15); }
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundNBT te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidFunnelTileEntity)) return;
((FluidFunnelTileEntity)te).readnbt(te_nbt);
((FluidFunnelTileEntity)te).setChanged();
world.setBlockAndUpdate(pos, state.setValue(FILL_LEVEL, 0));
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, final TileEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidFunnelTileEntity)) return stacks;
if(!explosion) {
ItemStack stack = new ItemStack(this, 1);
CompoundNBT te_nbt = new CompoundNBT();
((FluidFunnelTileEntity)te).writenbt(te_nbt);
if(!te_nbt.isEmpty()) {
CompoundNBT nbt = new CompoundNBT();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
} else {
stacks.add(new ItemStack(this, 1));
}
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(world.isClientSide) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidFunnelTileEntity)) return ActionResultType.FAIL;
return FluidUtil.interactWithFluidHandler(player, hand, world, pos, rayTraceResult.getDirection()) ? ActionResultType.CONSUME : ActionResultType.FAIL;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{ TileEntity te = world.getBlockEntity(pos); if(te instanceof FluidFunnelTileEntity) ((FluidFunnelTileEntity)te).block_changed(); }
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelTileEntity extends TileEntity implements ITickableTileEntity, ICapabilityProvider
{
public static final int TANK_CAPACITY = 3000;
public static final int TICK_INTERVAL = 10; // ca 500ms
public static final int COLLECTION_INTERVAL = 40; // ca 2000ms, simulates suction delay and saves CPU when not drained.
public static final int MAX_TRACK_RADIUS = 16;
public static final int MAX_TRACKING_STEPS_PER_CYCLE = 72;
public static final int MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE = 1024;
public static final int MAX_TRACK_RADIUS_SQ = MAX_TRACK_RADIUS*MAX_TRACK_RADIUS;
public static final int INTENSIVE_SEARCH_TRIGGER_THRESHOLD = 16;
private int tick_timer_ = 0;
private int collection_timer_ = 0;
private int no_fluid_found_counter_ = 0;
private int intensive_search_counter_ = 0;
private int total_pick_counter_ = 0;
private BlockPos last_pick_pos_ = BlockPos.ZERO;
private ArrayList<Vector3i> search_offsets_ = null;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
public FluidFunnelTileEntity()
{ this(ModContent.TET_SMALL_FLUID_FUNNEL); }
public FluidFunnelTileEntity(TileEntityType<?> te_type)
{
super(te_type);
tank_ = new Fluidics.Tank(TANK_CAPACITY, 0, TANK_CAPACITY);
fluid_handler_ = tank_.createOutputFluidHandler();
}
public void readnbt(CompoundNBT nbt)
{
tank_.load(nbt);
}
public void writenbt(CompoundNBT nbt)
{
tank_.save(nbt);
}
public void block_changed()
{ tick_timer_ = TICK_INTERVAL; }
// TileEntity -----------------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
fluid_handler_.invalidate();
}
// ICapabilityProvider / Output flow handler ----------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickableTileEntity --------------------------------------------------------------------------------
private FluidState get_fluidstate(BlockPos pos)
{
final Block collection_block = level.getBlockState(pos).getBlock();
if((!(collection_block instanceof IFluidBlock)) && (!(collection_block instanceof FlowingFluidBlock)) && (!(collection_block instanceof IWaterLoggable))) {
return Fluids.EMPTY.defaultFluidState();
}
return level.getFluidState(pos);
}
private boolean try_pick(BlockPos pos, FluidState fluidstate)
{
if(!fluidstate.isSource()) return false;
IFluidHandler hnd = FluidUtil.getFluidHandler(level, pos, null).orElse(null);
FluidStack fs;
if(hnd != null) {
fs = hnd.drain(TANK_CAPACITY, FluidAction.EXECUTE); // IFluidBlock
} else {
fs = new FluidStack(fluidstate.getType(), 1000);
BlockState state = level.getBlockState(pos);
if(state instanceof IBucketPickupHandler) {
((IBucketPickupHandler)state).takeLiquid(level, pos, state);
} else if((state.getBlock() instanceof IWaterLoggable) && (state.hasProperty(BlockStateProperties.WATERLOGGED))) {
level.setBlock(pos, state.setValue(BlockStateProperties.WATERLOGGED, false), 1|2);
} else {
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 1|2); // ok we can't leave the block, that would be an infinite source of an unknown fluid.
}
}
if((fs==null) || (fs.isEmpty())) return false; // it's marked nonnull but I don't trust every modder - including meself ...
if(tank_.isEmpty()) {
tank_.setFluid(fs.copy());
} else if(tank_.isFluidEqual(fs)) {
tank_.fill(fs, FluidAction.EXECUTE);
} else {
return false;
}
return true;
}
private boolean can_pick(BlockPos pos, FluidState fluidstate)
{
if(fluidstate.isSource()) return true;
IFluidHandler hnd = FluidUtil.getFluidHandler(level, pos, null).orElse(null);
if(hnd == null) return false;
FluidStack fs = hnd.drain(TANK_CAPACITY, FluidAction.SIMULATE); // don't trust that everyone returns nonnull
return ((fs!=null) && (!fs.isEmpty())) && (fluidstate.getType().isSame(fs.getFluid()));
}
private void rebuild_search_offsets(boolean intensive)
{
search_offsets_ = new ArrayList<>(9);
search_offsets_.add(new Vector3i(0, 1, 0)); // up first
{
ArrayList<Vector3i> ofs = new ArrayList<Vector3i>(Arrays.asList(new Vector3i(-1, 0, 0), new Vector3i( 1, 0, 0), new Vector3i( 0, 0,-1), new Vector3i( 0, 0, 1)));
if(intensive || (total_pick_counter_ > 50)) Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
if(intensive) {
ArrayList<Vector3i> ofs = new ArrayList<Vector3i>(Arrays.asList(new Vector3i(-1, 1, 0), new Vector3i( 1, 1, 0), new Vector3i( 0, 1,-1), new Vector3i( 0, 1, 1)));
Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
}
private boolean try_collect(final BlockPos collection_pos)
{
FluidState collection_fluidstate = get_fluidstate(collection_pos);
if(collection_fluidstate.isEmpty()) return false;
Fluid fluid_to_collect = collection_fluidstate.getType();
if((!tank_.isEmpty()) && (!tank_.getFluid().getFluid().isSame(fluid_to_collect))) return false;
if(try_pick(collection_pos, collection_fluidstate)) { last_pick_pos_ = collection_pos; return true; } // Blocks directly always first. Allows water source blocks to recover/reflow to source blocks.
if((last_pick_pos_==null) || (last_pick_pos_.distSqr(collection_pos) > MAX_TRACK_RADIUS_SQ)) { last_pick_pos_ = collection_pos; search_offsets_ = null; }
BlockPos pos = last_pick_pos_;
HashSet<BlockPos> checked = new HashSet<>();
Stack<BlockPos> trail = new Stack<BlockPos>();
trail.add(pos);
checked.add(pos);
int steps=0;
boolean intensive = (no_fluid_found_counter_ >= INTENSIVE_SEARCH_TRIGGER_THRESHOLD);
if(intensive) { no_fluid_found_counter_ = 0; ++intensive_search_counter_; }
if(search_offsets_ == null) rebuild_search_offsets(intensive);
int max = intensive ? MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE : MAX_TRACKING_STEPS_PER_CYCLE;
while(++steps <= max) {
int num_adjacent = 0;
for(int i=0; i<search_offsets_.size(); ++i) {
BlockPos p = pos.offset(search_offsets_.get(i));
if(checked.contains(p)) continue;
checked.add(p);
++steps;
FluidState fluidstate = get_fluidstate(p);
if(fluidstate.getType().isSame(fluid_to_collect)) {
++num_adjacent;
pos = p;
trail.push(pos);
if(steps < MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2) {
// check for same fluid above (only source blocks)
final int max_surface_search = (MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2)-steps;
for(int k=0; k<max_surface_search; ++k) {
FluidState fs = get_fluidstate(pos.above());
if(!can_pick(pos.above(), fs)) break;
fluidstate = fs;
pos = pos.above();
trail.push(pos);
}
}
if(try_pick(pos, fluidstate)) {
last_pick_pos_ = pos;
no_fluid_found_counter_ = 0;
search_offsets_ = null;
// probability reset, so it's not turteling too far away, mainly for large nether lava seas, not desert lakes.
if((++total_pick_counter_ > 50) && level.random.nextInt(10)==0) last_pick_pos_ = collection_pos;
//println("PASS " + steps + " - " + (pos.subtract(collection_pos)));
return true;
}
}
}
if(trail.isEmpty()) break; // reset search
if(num_adjacent==0) pos = trail.pop();
}
//println("FAIL=" + steps + " - " + (pos.subtract(collection_pos)));
//String s = new String(); for(BlockPos p:checked) s += "\n" + p; println(s);
if(intensive_search_counter_ > 2) level.removeBlock(pos, false);
last_pick_pos_ = collection_pos;
search_offsets_ = null; // try other search order
++no_fluid_found_counter_;
return false;
}
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
collection_timer_ += TICK_INTERVAL;
final BlockState funnel_state = level.getBlockState(worldPosition);
if(!(funnel_state.getBlock() instanceof FluidFunnelBlock)) return;
boolean dirty = false;
// Collection
if((collection_timer_ >= COLLECTION_INTERVAL) && ((tank_==null) || (tank_.getFluidAmount() <= (TANK_CAPACITY-1000)))) {
collection_timer_ = 0;
if(!level.hasNeighborSignal(worldPosition)) { // redstone disable feature
if(last_pick_pos_==null) last_pick_pos_ = worldPosition.above();
TileEntity te = with_device_fluid_handler_collection ? (level.getBlockEntity(worldPosition.above())) : (null);
if(te != null) {
IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, Direction.DOWN).orElse(null);
if(fh == null) {
te = null;
} else if(tank_.isEmpty()) {
FluidStack fs = fh.drain(1000, FluidAction.EXECUTE);
if((fs!=null) && (!fs.isEmpty())) tank_.setFluid(fs.copy());
dirty = true;
} else if (!tank_.isFull()) {
FluidStack todrain = new FluidStack(tank_.getFluid(), Math.min(tank_.getCapacity()-tank_.getFluidAmount(), 1000));
tank_.fill(fh.drain(todrain, FluidAction.EXECUTE), FluidAction.EXECUTE);
dirty = true;
}
}
if(te==null) {
if(try_collect(worldPosition.above())) dirty = true;
}
}
}
// Gravity fluid transfer
if((tank_.getFluidAmount() >= 1000)) {
IFluidHandler fh = FluidUtil.getFluidHandler(level, worldPosition.below(), Direction.UP).orElse(null);
if(fh != null) {
FluidStack fs = new FluidStack(tank_.getFluid().getFluid(), 1000);
int nfilled = MathHelper.clamp(fh.fill(fs, FluidAction.EXECUTE), 0, 1000);
tank_.drain(nfilled);
dirty = true;
}
}
// Block state
int fill_level = (tank_==null) ? 0 : (MathHelper.clamp(tank_.getFluidAmount()/1000,0, FluidFunnelBlock.FILL_LEVEL_MAX));
if(funnel_state.getValue(FluidFunnelBlock.FILL_LEVEL) != fill_level) level.setBlock(worldPosition, funnel_state.setValue(FluidFunnelBlock.FILL_LEVEL, fill_level), 2|16);
if(dirty) setChanged();
}
}
}
/*
* @file EdFluidFunnel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* A device that collects and stores fluid blocks above it.
* Tracks flowing fluid to their source blocks. Compatible
* with vanilla infinite water source.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Fluidics;
import javax.annotation.Nullable;
import java.util.*;
public class EdFluidFunnel
{
private static boolean with_device_fluid_handler_collection = false;
public static void on_config(boolean with_tank_fluid_collection)
{
with_device_fluid_handler_collection = with_tank_fluid_collection;
ModConfig.log("Config fluid funnel: tank-fluid-collection:" + with_device_fluid_handler_collection + ".");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelBlock extends StandardBlocks.Cutout implements StandardEntityBlocks.IStandardEntityBlock<FluidFunnelTileEntity>
{
public static final int FILL_LEVEL_MAX = 3;
public static final IntegerProperty FILL_LEVEL = IntegerProperty.create("level", 0, FILL_LEVEL_MAX);
public FluidFunnelBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Nullable
@Override
public BlockEntityType<EdFluidFunnel.FluidFunnelTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_FLUID_FUNNEL; }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(FILL_LEVEL); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(FILL_LEVEL, 0); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos)
{ return Mth.clamp((state.getValue(FILL_LEVEL)*5), 0, 15); }
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{
if(world.isClientSide) return;
if((!stack.hasTag()) || (!stack.getTag().contains("tedata"))) return;
CompoundTag te_nbt = stack.getTag().getCompound("tedata");
if(te_nbt.isEmpty()) return;
final BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof FluidFunnelTileEntity)) return;
((FluidFunnelTileEntity)te).readnbt(te_nbt);
te.setChanged();
world.setBlockAndUpdate(pos, state.setValue(FILL_LEVEL, 0));
}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, final BlockEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<>();
if(world.isClientSide) return stacks;
if(!(te instanceof FluidFunnelTileEntity)) return stacks;
if(!explosion) {
ItemStack stack = new ItemStack(this, 1);
CompoundTag te_nbt = new CompoundTag();
((FluidFunnelTileEntity)te).writenbt(te_nbt);
if(!te_nbt.isEmpty()) {
CompoundTag nbt = new CompoundTag();
nbt.put("tedata", te_nbt);
stack.setTag(nbt);
}
stacks.add(stack);
} else {
stacks.add(new ItemStack(this, 1));
}
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(world.isClientSide) return InteractionResult.SUCCESS;
return (world.getBlockEntity(pos) instanceof FluidFunnelTileEntity) && Fluidics.manualFluidHandlerInteraction(player, hand, world, pos, rayTraceResult.getDirection()) ? InteractionResult.CONSUME : InteractionResult.FAIL;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean unused)
{ BlockEntity te = world.getBlockEntity(pos); if(te instanceof FluidFunnelTileEntity) ((FluidFunnelTileEntity)te).block_changed(); }
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FluidFunnelTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
public static final int TANK_CAPACITY = 3000;
public static final int TICK_INTERVAL = 10; // ca 500ms
public static final int COLLECTION_INTERVAL = 40; // ca 2000ms, simulates suction delay and saves CPU when not drained.
public static final int MAX_TRACK_RADIUS = 16;
public static final int MAX_TRACKING_STEPS_PER_CYCLE = 72;
public static final int MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE = 1024;
public static final int MAX_TRACK_RADIUS_SQ = MAX_TRACK_RADIUS*MAX_TRACK_RADIUS;
public static final int INTENSIVE_SEARCH_TRIGGER_THRESHOLD = 16;
private int tick_timer_ = 0;
private int collection_timer_ = 0;
private int no_fluid_found_counter_ = 0;
private int intensive_search_counter_ = 0;
private int total_pick_counter_ = 0;
private BlockPos last_pick_pos_ = BlockPos.ZERO;
private ArrayList<Vec3i> search_offsets_ = null;
private final Fluidics.Tank tank_ = new Fluidics.Tank(TANK_CAPACITY, 0, TANK_CAPACITY);
private final LazyOptional<IFluidHandler> fluid_handler_ = tank_.createOutputFluidHandler();
public FluidFunnelTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_FLUID_FUNNEL, pos, state); }
public void readnbt(CompoundTag nbt)
{
tank_.load(nbt);
}
public void writenbt(CompoundTag nbt)
{
tank_.save(nbt);
}
public void block_changed()
{ tick_timer_ = TICK_INTERVAL; }
// BlockEntity -----------------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
fluid_handler_.invalidate();
}
// ICapabilityProvider / Output flow handler ----------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
return super.getCapability(capability, facing);
}
// -----------------------------------------------------------------------------------------------------------------
private FluidState get_fluidstate(BlockPos pos)
{
return level.getFluidState(pos);
}
private boolean try_pick(BlockPos pos, FluidState fluidstate)
{
if(!fluidstate.isSource()) return false;
IFluidHandler hnd = Fluidics.handler(level, pos, null);
FluidStack fs;
if(hnd != null) {
fs = hnd.drain(TANK_CAPACITY, IFluidHandler.FluidAction.EXECUTE); // IFluidBlock
} else {
fs = new FluidStack(fluidstate.getType(), 1000);
BlockState state = level.getBlockState(pos);
if(state.hasProperty(BlockStateProperties.WATERLOGGED)) {
level.setBlock(pos, state.setValue(BlockStateProperties.WATERLOGGED, false), 1|2);
} else {
level.setBlock(pos, Blocks.AIR.defaultBlockState(), 1|2); // ok we can't leave the block, that would be an infinite source of an unknown fluid.
}
}
if((fs==null) || (fs.isEmpty())) return false; // it's marked nonnull but I don't trust every modder - including meself ...
if(tank_.isEmpty()) {
tank_.setFluid(fs.copy());
} else if(tank_.isFluidEqual(fs)) {
tank_.fill(fs, IFluidHandler.FluidAction.EXECUTE);
} else {
return false;
}
return true;
}
private boolean can_pick(BlockPos pos, FluidState fluidstate)
{
if(fluidstate.isSource()) return true;
final IFluidHandler hnd = Fluidics.handler(level, pos, null);
if(hnd == null) return false;
final FluidStack fs = hnd.drain(TANK_CAPACITY, IFluidHandler.FluidAction.SIMULATE); // don't trust that everyone returns nonnull
return ((fs!=null) && (!fs.isEmpty())) && (fluidstate.getType().isSame(fs.getFluid()));
}
private void rebuild_search_offsets(boolean intensive)
{
search_offsets_ = new ArrayList<>(9);
search_offsets_.add(new Vec3i(0, 1, 0)); // up first
{
ArrayList<Vec3i> ofs = new ArrayList<>(Arrays.asList(new Vec3i(-1, 0, 0), new Vec3i( 1, 0, 0), new Vec3i( 0, 0,-1), new Vec3i( 0, 0, 1)));
if(intensive || (total_pick_counter_ > 50)) Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
if(intensive) {
ArrayList<Vec3i> ofs = new ArrayList<>(Arrays.asList(new Vec3i(-1, 1, 0), new Vec3i( 1, 1, 0), new Vec3i( 0, 1,-1), new Vec3i( 0, 1, 1)));
Collections.shuffle(ofs);
search_offsets_.addAll(ofs);
}
}
private boolean try_collect(final BlockPos collection_pos)
{
FluidState collection_fluidstate = get_fluidstate(collection_pos);
if(collection_fluidstate.isEmpty()) return false;
Fluid fluid_to_collect = collection_fluidstate.getType();
if((!tank_.isEmpty()) && (!tank_.getFluid().getFluid().isSame(fluid_to_collect))) return false;
if(try_pick(collection_pos, collection_fluidstate)) { last_pick_pos_ = collection_pos; return true; } // Blocks directly always first. Allows water source blocks to recover/reflow to source blocks.
if((last_pick_pos_==null) || (last_pick_pos_.distSqr(collection_pos) > MAX_TRACK_RADIUS_SQ)) { last_pick_pos_ = collection_pos; search_offsets_ = null; }
BlockPos pos = last_pick_pos_;
HashSet<BlockPos> checked = new HashSet<>();
Stack<BlockPos> trail = new Stack<>();
trail.add(pos);
checked.add(pos);
int steps=0;
boolean intensive = (no_fluid_found_counter_ >= INTENSIVE_SEARCH_TRIGGER_THRESHOLD);
if(intensive) { no_fluid_found_counter_ = 0; ++intensive_search_counter_; }
if(search_offsets_ == null) rebuild_search_offsets(intensive);
int max = intensive ? MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE : MAX_TRACKING_STEPS_PER_CYCLE;
while(++steps <= max) {
int num_adjacent = 0;
for(int i=0; i<search_offsets_.size(); ++i) {
BlockPos p = pos.offset(search_offsets_.get(i));
if(checked.contains(p)) continue;
checked.add(p);
++steps;
FluidState fluidstate = get_fluidstate(p);
if(fluidstate.getType().isSame(fluid_to_collect)) {
++num_adjacent;
pos = p;
trail.push(pos);
if(steps < MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2) {
// check for same fluid above (only source blocks)
final int max_surface_search = (MAX_TRACKING_STEPS_PER_CYCLE_INTENSIVE/2)-steps;
for(int k=0; k<max_surface_search; ++k) {
FluidState fs = get_fluidstate(pos.above());
if(!can_pick(pos.above(), fs)) break;
fluidstate = fs;
pos = pos.above();
trail.push(pos);
}
}
if(try_pick(pos, fluidstate)) {
last_pick_pos_ = pos;
no_fluid_found_counter_ = 0;
search_offsets_ = null;
// probability reset, so it's not turteling too far away, mainly for large nether lava seas, not desert lakes.
if((++total_pick_counter_ > 50) && level.random.nextInt(10)==0) last_pick_pos_ = collection_pos;
//println("PASS " + steps + " - " + (pos.subtract(collection_pos)));
return true;
}
}
}
if(trail.isEmpty()) break; // reset search
if(num_adjacent==0) pos = trail.pop();
}
//println("FAIL=" + steps + " - " + (pos.subtract(collection_pos)));
//String s = new String(); for(BlockPos p:checked) s += "\n" + p; println(s);
if(intensive_search_counter_ > 2) level.removeBlock(pos, false);
last_pick_pos_ = collection_pos;
search_offsets_ = null; // try other search order
++no_fluid_found_counter_;
return false;
}
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
collection_timer_ += TICK_INTERVAL;
final BlockState funnel_state = level.getBlockState(worldPosition);
if(!(funnel_state.getBlock() instanceof FluidFunnelBlock)) return;
boolean dirty = false;
// Collection
if((collection_timer_ >= COLLECTION_INTERVAL) && ((tank_==null) || (tank_.getFluidAmount() <= (TANK_CAPACITY-1000)))) {
collection_timer_ = 0;
if(!level.hasNeighborSignal(worldPosition)) { // redstone disable feature
if(last_pick_pos_==null) last_pick_pos_ = worldPosition.above();
BlockEntity te = with_device_fluid_handler_collection ? (level.getBlockEntity(worldPosition.above())) : (null);
if(te != null) {
IFluidHandler fh = te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, Direction.DOWN).orElse(null);
if(fh == null) {
te = null;
} else if(tank_.isEmpty()) {
FluidStack fs = fh.drain(1000, IFluidHandler.FluidAction.EXECUTE);
if((fs!=null) && (!fs.isEmpty())) tank_.setFluid(fs.copy());
dirty = true;
} else if (!tank_.isFull()) {
FluidStack todrain = new FluidStack(tank_.getFluid(), Math.min(tank_.getCapacity()-tank_.getFluidAmount(), 1000));
tank_.fill(fh.drain(todrain, IFluidHandler.FluidAction.EXECUTE), IFluidHandler.FluidAction.EXECUTE);
dirty = true;
}
}
if(te==null) {
if(try_collect(worldPosition.above())) dirty = true;
}
}
}
// Gravity fluid transfer
if((tank_.getFluidAmount() >= 1000)) {
final IFluidHandler fh = Fluidics.handler(level, worldPosition.below(), Direction.UP);
if(fh != null) {
FluidStack fs = new FluidStack(tank_.getFluid().getFluid(), 1000);
int nfilled = Mth.clamp(fh.fill(fs, IFluidHandler.FluidAction.EXECUTE), 0, 1000);
tank_.drain(nfilled);
dirty = true;
}
}
// Block state
int fill_level = (tank_==null) ? 0 : (Mth.clamp(tank_.getFluidAmount()/1000,0, FluidFunnelBlock.FILL_LEVEL_MAX));
if(funnel_state.getValue(FluidFunnelBlock.FILL_LEVEL) != fill_level) level.setBlock(worldPosition, funnel_state.setValue(FluidFunnelBlock.FILL_LEVEL, fill_level), 2|16);
if(dirty) setChanged();
}
}
}

View file

@ -1,394 +1,397 @@
/*
* @file EdFreezer.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small highly insulated stone liquification furnace
* (magmatic phase).
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraft.block.*;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.item.Items;
import net.minecraft.item.ItemStack;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.*;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.fluid.Fluids;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Fluidics;
import wile.engineersdecor.ModContent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
public class EdFreezer
{
public static void on_config(int consumption, int cooldown_per_second)
{ FreezerTileEntity.on_config(consumption, cooldown_per_second); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerBlock extends DecorBlock.Horizontal implements IDecorBlock
{
public static final int PHASE_MAX = 4;
public static final IntegerProperty PHASE = IntegerProperty.create("phase", 0, PHASE_MAX);
public FreezerBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PHASE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(PHASE, 0); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, World world, BlockPos pos)
{ return MathHelper.clamp((state.getValue(PHASE)*4), 0, 15); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new EdFreezer.FreezerTileEntity(); }
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<ItemStack>();
if(world.isClientSide) return stacks;
if(!(te instanceof FreezerTileEntity)) return stacks;
((FreezerTileEntity)te).reset_process();
stacks.add(new ItemStack(this, 1));
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(player.isShiftKeyDown()) return ActionResultType.PASS;
if(world.isClientSide()) return ActionResultType.SUCCESS;
FreezerTileEntity te = getTe(world, pos);
if(te==null) return ActionResultType.FAIL;
final ItemStack stack = player.getItemInHand(hand);
boolean dirty = false;
if(Fluidics.manualFluidHandlerInteraction(world, pos, null, player, hand)) {
world.playSound(null, pos, SoundEvents.BUCKET_EMPTY, SoundCategory.BLOCKS, 0.5f, 1.4f);
return ActionResultType.CONSUME;
}
if(stack.getItem()==Items.WATER_BUCKET) {
return ActionResultType.CONSUME; // would be already handled
} else if(stack.isEmpty()) {
ItemStack ice = te.getIceItem(true);
if(!ice.isEmpty()) {
player.addItem(ice);
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundCategory.BLOCKS, 0.3f, 1.1f);
} else {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundCategory.BLOCKS, 0.2f, 0.02f);
}
return ActionResultType.CONSUME;
} else {
return ActionResultType.PASS;
}
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{}
@Nullable
private FreezerTileEntity getTe(World world, BlockPos pos)
{ final TileEntity te=world.getBlockEntity(pos); return (!(te instanceof FreezerTileEntity)) ? (null) : ((FreezerTileEntity)te); }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerTileEntity extends TileEntity implements ITickableTileEntity, IEnergyStorage, ICapabilityProvider
{
public static final int TICK_INTERVAL = 20;
public static final int MAX_FLUID_LEVEL = 2000;
public static final int MAX_ENERGY_BUFFER = 32000;
public static final int MAX_ENERGY_TRANSFER = 8192;
public static final int TANK_CAPACITY = 2000;
public static final int DEFAULT_ENERGY_CONSUMPTION = 92;
public static final int DEFAULT_COOLDOWN_RATE = 2;
public static final int PHASE_EMPTY = 0;
public static final int PHASE_WATER = 1;
public static final int PHASE_ICE = 2;
public static final int PHASE_PACKEDICE = 3;
public static final int PHASE_BLUEICE = 4;
private static int energy_consumption = DEFAULT_ENERGY_CONSUMPTION;
private static int cooldown_rate = DEFAULT_COOLDOWN_RATE;
private static int reheat_rate = 1;
private final Fluidics.Tank tank_ = new Fluidics.Tank(TANK_CAPACITY, TANK_CAPACITY, TANK_CAPACITY, fs->fs.getFluid()==Fluids.WATER);
private int tick_timer_;
private int energy_stored_;
private int progress_;
private boolean force_block_update_;
public static void on_config(int consumption, int cooldown_per_second)
{
energy_consumption = MathHelper.clamp(consumption, 8, 4096);
cooldown_rate = MathHelper.clamp(cooldown_per_second, 1, 5);
reheat_rate = MathHelper.clamp(cooldown_per_second/2, 1, 5);
ModConfig.log("Config freezer energy consumption:" + energy_consumption + "rf/t, cooldown-rate: " + cooldown_rate + "%/s.");
}
public FreezerTileEntity()
{ this(ModContent.TET_FREEZER); }
public FreezerTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public int progress()
{ return progress_; }
public int phase()
{
if(tank_.getFluidAmount() < 1000) return PHASE_EMPTY;
if(progress_ >= 100) return PHASE_BLUEICE;
if(progress_ >= 70) return PHASE_PACKEDICE;
if(progress_ >= 30) return PHASE_ICE;
return PHASE_WATER;
}
public ItemStack getIceItem(boolean extract)
{
ItemStack stack;
switch(phase()) {
case PHASE_ICE: stack = new ItemStack(Items.ICE); break;
case PHASE_PACKEDICE: stack = new ItemStack(Items.PACKED_ICE); break;
case PHASE_BLUEICE: stack = new ItemStack(Items.BLUE_ICE); break;
default: return ItemStack.EMPTY;
}
if(extract) reset_process();
return stack;
}
public int comparator_signal()
{ return phase() * 4; }
protected void reset_process()
{
force_block_update_ = true;
tank_.drain(1000);
tick_timer_ = 0;
progress_ = 0;
}
public void readnbt(CompoundNBT nbt)
{
energy_stored_ = nbt.getInt("energy");
progress_ = nbt.getInt("progress");
tank_.load(nbt);
}
protected void writenbt(CompoundNBT nbt)
{
nbt.putInt("energy", MathHelper.clamp(energy_stored_,0 , MAX_ENERGY_BUFFER));
nbt.putInt("progress", MathHelper.clamp(progress_,0 , 100));
tank_.save(nbt);
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
// IItemHandler --------------------------------------------------------------------------------
private final LazyOptional<IItemHandler> item_handler_ = LazyOptional.of(() -> (IItemHandler)new FreezerItemHandler(this));
protected static class FreezerItemHandler implements IItemHandler
{
private final FreezerTileEntity te;
FreezerItemHandler(FreezerTileEntity te)
{ this.te = te; }
@Override
public int getSlots()
{ return 1; }
@Override
public int getSlotLimit(int index)
{ return 1; }
@Override
public boolean isItemValid(int slot, @Nonnull ItemStack stack)
{ return false; }
@Override
@Nonnull
public ItemStack getStackInSlot(int index)
{ return (index!=0) ? ItemStack.EMPTY : te.getIceItem(false); }
@Override
@Nonnull
public ItemStack insertItem(int index, @Nonnull ItemStack stack, boolean simulate)
{ return ItemStack.EMPTY; }
@Override
@Nonnull
public ItemStack extractItem(int index, int amount, boolean simulate)
{ return te.getIceItem(!simulate); }
}
// IFluidHandler --------------------------------------------------------------------------------
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(tank_));
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> (IEnergyStorage)this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return MAX_ENERGY_BUFFER; }
@Override
public int getEnergyStored()
{ return energy_stored_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
if(energy_stored_ >= MAX_ENERGY_BUFFER) return 0;
int n = Math.min(maxReceive, (MAX_ENERGY_BUFFER - energy_stored_));
if(n > MAX_ENERGY_TRANSFER) n = MAX_ENERGY_TRANSFER;
if(!simulate) {energy_stored_ += n; setChanged(); }
return n;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(level.isClientSide) return;
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof FreezerBlock)) return;
boolean dirty = false;
final int last_phase = phase();
if(tank_.getFluidAmount() < 1000) {
progress_ = 0;
} else if((energy_stored_ <= 0) || (level.hasNeighborSignal(worldPosition))) {
progress_ = MathHelper.clamp(progress_-reheat_rate, 0,100);
} else if(progress_ >= 100) {
progress_ = 100;
energy_stored_ = MathHelper.clamp(energy_stored_-((energy_consumption*TICK_INTERVAL)/20), 0, MAX_ENERGY_BUFFER);
} else {
energy_stored_ = MathHelper.clamp(energy_stored_-(energy_consumption*TICK_INTERVAL), 0, MAX_ENERGY_BUFFER);
progress_ = MathHelper.clamp(progress_+cooldown_rate, 0, 100);
}
int new_phase = phase();
if(new_phase > last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundCategory.BLOCKS, 0.2f, 0.7f);
} else if(new_phase < last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundCategory.BLOCKS, 0.2f, 0.7f);
}
// Block state
if((force_block_update_ || (state.getValue(FreezerBlock.PHASE) != new_phase))) {
state = state.setValue(FreezerBlock.PHASE, new_phase);
level.setBlock(worldPosition, state,3|16);
level.updateNeighborsAt(getBlockPos(), state.getBlock());
force_block_update_ = false;
}
if(dirty) setChanged();
}
}
}
/*
* @file EdFreezer.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small highly insulated stone liquification furnace
* (magmatic phase).
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Fluidics;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class EdFreezer
{
public static void on_config(int consumption, int cooldown_per_second)
{ FreezerTileEntity.on_config(consumption, cooldown_per_second); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerBlock extends StandardBlocks.Horizontal implements StandardEntityBlocks.IStandardEntityBlock<FreezerTileEntity>
{
public static final int PHASE_MAX = 4;
public static final IntegerProperty PHASE = IntegerProperty.create("phase", 0, PHASE_MAX);
public FreezerBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Nullable
@Override
public BlockEntityType<EdFreezer.FreezerTileEntity> getBlockEntityType()
{ return ModContent.TET_FREEZER; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PHASE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(PHASE, 0); }
@Override
@SuppressWarnings("deprecation")
public boolean hasAnalogOutputSignal(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos)
{ return Mth.clamp((state.getValue(PHASE)*4), 0, 15); }
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{}
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{
final List<ItemStack> stacks = new ArrayList<>();
if(world.isClientSide) return stacks;
if(!(te instanceof FreezerTileEntity)) return stacks;
((FreezerTileEntity)te).reset_process();
stacks.add(new ItemStack(this, 1));
return stacks;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(player.isShiftKeyDown()) return InteractionResult.PASS;
if(world.isClientSide()) return InteractionResult.SUCCESS;
FreezerTileEntity te = getTe(world, pos);
if(te==null) return InteractionResult.FAIL;
final ItemStack stack = player.getItemInHand(hand);
boolean dirty = false;
if(Fluidics.manualFluidHandlerInteraction(world, pos, null, player, hand)) {
world.playSound(null, pos, SoundEvents.BUCKET_EMPTY, SoundSource.BLOCKS, 0.5f, 1.4f);
return InteractionResult.CONSUME;
}
if(stack.getItem()==Items.WATER_BUCKET) {
return InteractionResult.CONSUME; // would be already handled
} else if(stack.isEmpty()) {
ItemStack ice = te.getIceItem(true);
if(!ice.isEmpty()) {
player.addItem(ice);
world.playSound(null, pos, SoundEvents.ITEM_PICKUP, SoundSource.BLOCKS, 0.3f, 1.1f);
} else {
world.playSound(null, pos, SoundEvents.IRON_TRAPDOOR_OPEN, SoundSource.BLOCKS, 0.2f, 0.02f);
}
return InteractionResult.CONSUME;
} else {
return InteractionResult.PASS;
}
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{}
@Nullable
private FreezerTileEntity getTe(Level world, BlockPos pos)
{ final BlockEntity te=world.getBlockEntity(pos); return (!(te instanceof FreezerTileEntity)) ? (null) : ((FreezerTileEntity)te); }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class FreezerTileEntity extends StandardEntityBlocks.StandardBlockEntity implements IEnergyStorage
{
public static final int TICK_INTERVAL = 20;
public static final int MAX_FLUID_LEVEL = 2000;
public static final int MAX_ENERGY_BUFFER = 32000;
public static final int MAX_ENERGY_TRANSFER = 8192;
public static final int TANK_CAPACITY = 2000;
public static final int DEFAULT_ENERGY_CONSUMPTION = 92;
public static final int DEFAULT_COOLDOWN_RATE = 2;
public static final int PHASE_EMPTY = 0;
public static final int PHASE_WATER = 1;
public static final int PHASE_ICE = 2;
public static final int PHASE_PACKEDICE = 3;
public static final int PHASE_BLUEICE = 4;
private static int energy_consumption = DEFAULT_ENERGY_CONSUMPTION;
private static int cooldown_rate = DEFAULT_COOLDOWN_RATE;
private static int reheat_rate = 1;
private final Fluidics.Tank tank_ = new Fluidics.Tank(TANK_CAPACITY, TANK_CAPACITY, TANK_CAPACITY, fs->fs.getFluid()==Fluids.WATER);
private int tick_timer_;
private int energy_stored_;
private int progress_;
private boolean force_block_update_;
public static void on_config(int consumption, int cooldown_per_second)
{
energy_consumption = Mth.clamp(consumption, 8, 4096);
cooldown_rate = Mth.clamp(cooldown_per_second, 1, 5);
reheat_rate = Mth.clamp(cooldown_per_second/2, 1, 5);
ModConfig.log("Config freezer energy consumption:" + energy_consumption + "rf/t, cooldown-rate: " + cooldown_rate + "%/s.");
}
public FreezerTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_FREEZER, pos, state); }
public int progress()
{ return progress_; }
public int phase()
{
if(tank_.getFluidAmount() < 1000) return PHASE_EMPTY;
if(progress_ >= 100) return PHASE_BLUEICE;
if(progress_ >= 70) return PHASE_PACKEDICE;
if(progress_ >= 30) return PHASE_ICE;
return PHASE_WATER;
}
public ItemStack getIceItem(boolean extract)
{
ItemStack stack;
switch(phase()) {
case PHASE_ICE: stack = new ItemStack(Items.ICE); break;
case PHASE_PACKEDICE: stack = new ItemStack(Items.PACKED_ICE); break;
case PHASE_BLUEICE: stack = new ItemStack(Items.BLUE_ICE); break;
default: return ItemStack.EMPTY;
}
if(extract) reset_process();
return stack;
}
public int comparator_signal()
{ return phase() * 4; }
protected void reset_process()
{
force_block_update_ = true;
tank_.drain(1000);
tick_timer_ = 0;
progress_ = 0;
}
public void readnbt(CompoundTag nbt)
{
energy_stored_ = nbt.getInt("energy");
progress_ = nbt.getInt("progress");
tank_.load(nbt);
}
protected void writenbt(CompoundTag nbt)
{
nbt.putInt("energy", Mth.clamp(energy_stored_,0 , MAX_ENERGY_BUFFER));
nbt.putInt("progress", Mth.clamp(progress_,0 , 100));
tank_.save(nbt);
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
// IItemHandler --------------------------------------------------------------------------------
private final LazyOptional<IItemHandler> item_handler_ = LazyOptional.of(() -> new FreezerItemHandler(this));
protected static class FreezerItemHandler implements IItemHandler
{
private final FreezerTileEntity te;
FreezerItemHandler(FreezerTileEntity te)
{ this.te = te; }
@Override
public int getSlots()
{ return 1; }
@Override
public int getSlotLimit(int index)
{ return 1; }
@Override
public boolean isItemValid(int slot, @Nonnull ItemStack stack)
{ return false; }
@Override
@Nonnull
public ItemStack getStackInSlot(int index)
{ return (index!=0) ? ItemStack.EMPTY : te.getIceItem(false); }
@Override
@Nonnull
public ItemStack insertItem(int index, @Nonnull ItemStack stack, boolean simulate)
{ return ItemStack.EMPTY; }
@Override
@Nonnull
public ItemStack extractItem(int index, int amount, boolean simulate)
{ return te.getIceItem(!simulate); }
}
// IFluidHandler --------------------------------------------------------------------------------
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(tank_));
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return MAX_ENERGY_BUFFER; }
@Override
public int getEnergyStored()
{ return energy_stored_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
if(energy_stored_ >= MAX_ENERGY_BUFFER) return 0;
int n = Math.min(maxReceive, (MAX_ENERGY_BUFFER - energy_stored_));
if(n > MAX_ENERGY_TRANSFER) n = MAX_ENERGY_TRANSFER;
if(!simulate) {energy_stored_ += n; setChanged(); }
return n;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(level.isClientSide) return;
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof FreezerBlock)) return;
boolean dirty = false;
final int last_phase = phase();
if(tank_.getFluidAmount() < 1000) {
progress_ = 0;
} else if((energy_stored_ <= 0) || (level.hasNeighborSignal(worldPosition))) {
progress_ = Mth.clamp(progress_-reheat_rate, 0,100);
} else if(progress_ >= 100) {
progress_ = 100;
energy_stored_ = Mth.clamp(energy_stored_-((energy_consumption*TICK_INTERVAL)/20), 0, MAX_ENERGY_BUFFER);
} else {
energy_stored_ = Mth.clamp(energy_stored_-(energy_consumption*TICK_INTERVAL), 0, MAX_ENERGY_BUFFER);
progress_ = Mth.clamp(progress_+cooldown_rate, 0, 100);
}
int new_phase = phase();
if(new_phase > last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundSource.BLOCKS, 0.2f, 0.7f);
} else if(new_phase < last_phase) {
level.playSound(null, worldPosition, SoundEvents.SAND_FALL, SoundSource.BLOCKS, 0.2f, 0.7f);
}
// Block state
if((force_block_update_ || (state.getValue(FreezerBlock.PHASE) != new_phase))) {
state = state.setValue(FreezerBlock.PHASE, new_phase);
level.setBlock(worldPosition, state,3|16);
level.updateNeighborsAt(getBlockPos(), state.getBlock());
force_block_update_ = false;
}
if(dirty) setChanged();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,53 +1,54 @@
/*
* @file EdGlassBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class. Explicitly overrides some
* `Block` methods to return faster due to exclusive block properties.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.StainedGlassBlock;
import net.minecraft.item.DyeColor;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class EdGlassBlock extends StainedGlassBlock implements IDecorBlock
{
public EdGlassBlock(long config, AbstractBlock.Properties properties)
{ super(DyeColor.BLACK, properties); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.TRANSLUCENT; }
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState.getBlock()==this) ? true : super.skipRendering(state, adjacentBlockState, side); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
}
/*
* @file EdGlassBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Full block characteristics class. Explicitly overrides some
* `Block` methods to return faster due to exclusive block properties.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.StainedGlassBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class EdGlassBlock extends StainedGlassBlock implements StandardBlocks.IStandardBlock
{
public EdGlassBlock(long config, BlockBehaviour.Properties properties)
{ super(DyeColor.BLACK, properties); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public StandardBlocks.IStandardBlock.RenderTypeHint getRenderTypeHint()
{ return StandardBlocks.IStandardBlock.RenderTypeHint.TRANSLUCENT; }
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState.getBlock()==this) || (super.skipRendering(state, adjacentBlockState, side)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
}

View file

@ -1,18 +0,0 @@
/*
* @file EdGroundBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Block type for soils, floors, etc. Drawn out into a class
* to enable functionality block overrides.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
public class EdGroundBlock extends DecorBlock.Normal implements IDecorBlock
{
public EdGroundBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
}

View file

@ -1,134 +1,140 @@
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
public class EdHatchBlock extends DecorBlock.HorizontalWaterLoggable implements IDecorBlock
{
public static final BooleanProperty OPEN = BlockStateProperties.OPEN;
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
protected final ArrayList<VoxelShape> vshapes_open;
public EdHatchBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABBClosed, final AxisAlignedBB unrotatedAABBOpen)
{
super(config, builder, unrotatedAABBClosed); vshapes_open = makeHorizontalShapeLookup(new AxisAlignedBB[]{unrotatedAABBOpen});
registerDefaultState(super.defaultBlockState().setValue(OPEN, false).setValue(POWERED, false));
}
public EdHatchBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABBsClosed, final AxisAlignedBB[] unrotatedAABBsOpen)
{ super(config, builder, unrotatedAABBsClosed); vshapes_open = makeHorizontalShapeLookup(unrotatedAABBsOpen); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return state.getValue(OPEN) ? vshapes_open.get((state.getValue(HORIZONTAL_FACING)).get3DDataValue() & 0x7) : super.getShape(state, source, pos, selectionContext); }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, IBlockReader world, BlockPos pos, PathType type)
{ return !state.getValue(OPEN); }
@Override
public boolean isLadder(BlockState state, IWorldReader world, BlockPos pos, LivingEntity entity)
{
if(!state.getValue(OPEN)) return false;
{
final BlockState up_state = world.getBlockState(pos.above());
if(up_state.is(this) && (up_state.getValue(OPEN))) return true;
if(up_state.isLadder(world, pos.above(), entity)) return true;
}
{
final BlockState down_state = world.getBlockState(pos.below());
if(down_state.is(this) && (down_state.getValue(OPEN))) return true;
if(down_state.isLadder(world, pos.below(), entity)) return true;
}
return false;
}
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(OPEN, POWERED); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult rayTraceResult)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open), 1|2);
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
return ActionResultType.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if((world.isClientSide) || (!(state.getBlock() instanceof EdHatchBlock))) return;
boolean powered = world.hasNeighborSignal(pos);
if(powered == state.getValue(POWERED)) return;
if(powered != state.getValue(OPEN)) world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundCategory.BLOCKS, 0.7f, 1.4f);
world.setBlock(pos, state.setValue(OPEN, powered).setValue(POWERED, powered), 1|2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, World world, BlockPos pos, Entity entity)
{
if((!state.getValue(OPEN)) || (!(entity instanceof PlayerEntity))) return;
final PlayerEntity player = (PlayerEntity)entity;
if(entity.getLookAngle().y() > -0.75) return;
if(player.getDirection() != state.getValue(HORIZONTAL_FACING)) return;
Vector3d ppos = player.position();
Vector3d centre = Vector3d.atBottomCenterOf(pos);
Vector3d v = centre.subtract(ppos);
if(ppos.y() < (centre.y()-0.1) || (v.lengthSqr() > 0.3)) return;
v = v.scale(0.3);
player.push(v.x, 0, v.z);
}
}
/*
* @file EdFloorGratingBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Floor gratings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.ArrayList;
public class EdHatchBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final BooleanProperty OPEN = BlockStateProperties.OPEN;
public static final BooleanProperty POWERED = BlockStateProperties.POWERED;
protected final ArrayList<VoxelShape> vshapes_open;
public EdHatchBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABBClosed, final AABB unrotatedAABBOpen)
{
super(config, builder, unrotatedAABBClosed); vshapes_open = makeHorizontalShapeLookup(new AABB[]{unrotatedAABBOpen});
registerDefaultState(super.defaultBlockState().setValue(OPEN, false).setValue(POWERED, false));
}
public EdHatchBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABBsClosed, final AABB[] unrotatedAABBsOpen)
{ super(config, builder, unrotatedAABBsClosed); vshapes_open = makeHorizontalShapeLookup(unrotatedAABBsOpen); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return state.getValue(OPEN) ? vshapes_open.get((state.getValue(HORIZONTAL_FACING)).get3DDataValue() & 0x7) : super.getShape(state, source, pos, selectionContext); }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return state.getValue(OPEN); }
@Override
@SuppressWarnings("deprecation")
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type)
{ return !state.getValue(OPEN); }
@Override
public boolean isLadder(BlockState state, LevelReader world, BlockPos pos, LivingEntity entity)
{
if(!state.getValue(OPEN)) return false;
{
final BlockState up_state = world.getBlockState(pos.above());
if(up_state.is(this) && (up_state.getValue(OPEN))) return true;
if(up_state.isLadder(world, pos.above(), entity)) return true;
}
{
final BlockState down_state = world.getBlockState(pos.below());
if(down_state.is(this) && (down_state.getValue(OPEN))) return true;
if(down_state.isLadder(world, pos.below(), entity)) return true;
}
return false;
}
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(OPEN, POWERED); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult rayTraceResult)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
boolean open = !state.getValue(OPEN);
world.setBlock(pos, state.setValue(OPEN, open), 1|2);
world.playSound(null, pos, open?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
return InteractionResult.CONSUME;
}
@Override
@SuppressWarnings("deprecation")
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
if((world.isClientSide) || (!(state.getBlock() instanceof EdHatchBlock))) return;
boolean powered = world.hasNeighborSignal(pos);
if(powered == state.getValue(POWERED)) return;
if(powered != state.getValue(OPEN)) world.playSound(null, pos, powered?SoundEvents.IRON_DOOR_OPEN:SoundEvents.IRON_DOOR_CLOSE, SoundSource.BLOCKS, 0.7f, 1.4f);
world.setBlock(pos, state.setValue(OPEN, powered).setValue(POWERED, powered), 1|2);
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity)
{
if((!state.getValue(OPEN)) || (!(entity instanceof final Player player))) return;
if(entity.getLookAngle().y() > -0.75) return;
if(player.getDirection() != state.getValue(HORIZONTAL_FACING)) return;
Vec3 ppos = player.position();
Vec3 centre = Vec3.atBottomCenterOf(pos);
Vec3 v = centre.subtract(ppos);
if(ppos.y() < (centre.y()-0.1) || (v.lengthSqr() > 0.3)) return;
v = v.scale(0.3);
player.push(v.x, 0, v.z);
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,166 +1,172 @@
/*
* @file EdHorizontalSupportBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Horizontal ceiling support. Symmetric x axis, fixed in
* xz plane, therefore boolean placement state.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.DirectionalPlaceContext;
import net.minecraft.item.ItemStack;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class EdHorizontalSupportBlock extends DecorBlock.WaterLoggable implements IDecorBlock
{
public static final BooleanProperty EASTWEST = BooleanProperty.create("eastwest");
public static final BooleanProperty LEFTBEAM = BooleanProperty.create("leftbeam");
public static final BooleanProperty RIGHTBEAM = BooleanProperty.create("rightbeam");
public static final IntegerProperty DOWNCONNECT = IntegerProperty.create("downconnect", 0, 2);
protected final Map<BlockState, VoxelShape> AABBs;
public EdHorizontalSupportBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB mainBeamAABB, final AxisAlignedBB eastBeamAABB, final AxisAlignedBB thinDownBeamAABB, final AxisAlignedBB thickDownBeamAABB)
{
super(config|DecorBlock.CFG_HORIZIONTAL, builder);
Map<BlockState, VoxelShape> aabbs = new HashMap<>();
for(boolean eastwest:EASTWEST.getPossibleValues()) {
for(boolean leftbeam:LEFTBEAM.getPossibleValues()) {
for(boolean rightbeam:RIGHTBEAM.getPossibleValues()) {
for(int downconnect:DOWNCONNECT.getPossibleValues()) {
final BlockState state = defaultBlockState().setValue(EASTWEST, eastwest).setValue(LEFTBEAM, leftbeam).setValue(RIGHTBEAM, rightbeam).setValue(DOWNCONNECT, downconnect);
VoxelShape shape = VoxelShapes.create(Auxiliaries.getRotatedAABB(mainBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true));
if(rightbeam) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true)), IBooleanFunction.OR);
if(leftbeam) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.WEST:Direction.SOUTH, true)), IBooleanFunction.OR);
if(downconnect==1) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(thinDownBeamAABB), IBooleanFunction.OR);
if(downconnect==2) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(thickDownBeamAABB), IBooleanFunction.OR);
aabbs.put(state.setValue(WATERLOGGED, false), shape);
aabbs.put(state.setValue(WATERLOGGED, true), shape);
}
}
}
}
AABBs = aabbs;
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return AABBs.get(state); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EASTWEST, RIGHTBEAM, LEFTBEAM, DOWNCONNECT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return temp_block_update_until_better(super.getStateForPlacement(context).setValue(EASTWEST, context.getHorizontalDirection().getAxis()==Direction.Axis.X), context.getLevel(), context.getClickedPos()); }
private BlockState temp_block_update_until_better(BlockState state, IWorld world, BlockPos pos)
{
boolean ew = state.getValue(EASTWEST);
final BlockState rstate = world.getBlockState((!ew) ? (pos.east()) : (pos.south()) );
final BlockState lstate = world.getBlockState((!ew) ? (pos.west()) : (pos.north()) );
final BlockState dstate = world.getBlockState(pos.below());
int down_connector = 0;
if((dstate.getBlock() instanceof EdStraightPoleBlock)) {
final Direction dfacing = dstate.getValue(EdStraightPoleBlock.FACING);
final EdStraightPoleBlock pole = (EdStraightPoleBlock)dstate.getBlock();
if((dfacing.getAxis() == Direction.Axis.Y)) {
if((pole== ModContent.THICK_STEEL_POLE) || ((pole==ModContent.THICK_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 2;
} else if((pole==ModContent.THIN_STEEL_POLE) || ((pole==ModContent.THIN_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 1;
}
}
}
return state.setValue(RIGHTBEAM, (rstate.getBlock()==this) && (rstate.getValue(EASTWEST) != ew))
.setValue(LEFTBEAM , (lstate.getBlock()==this) && (lstate.getValue(EASTWEST) != ew))
.setValue(DOWNCONNECT , down_connector);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return temp_block_update_until_better(state, world, pos); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (held_stack.getItem() != this.asItem())) return ActionResultType.PASS;
if(!(hit.getDirection().getAxis().isVertical())) return ActionResultType.PASS;
final Direction placement_direction = player.getDirection();
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockItemUseContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return ActionResultType.sidedSuccess(world.isClientSide());
final BlockState new_state = getStateForPlacement(ctx);
if(new_state == null) return ActionResultType.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return ActionResultType.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return ActionResultType.sidedSuccess(world.isClientSide());
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return (rot==Rotation.CLOCKWISE_180) ? state : state.setValue(EASTWEST, !state.getValue(EASTWEST)); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
}
/*
* @file EdHorizontalSupportBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Horizontal ceiling support. Symmetric x axis, fixed in
* xz plane, therefore boolean placement state.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class EdHorizontalSupportBlock extends StandardBlocks.WaterLoggable
{
public static final BooleanProperty EASTWEST = BooleanProperty.create("eastwest");
public static final BooleanProperty LEFTBEAM = BooleanProperty.create("leftbeam");
public static final BooleanProperty RIGHTBEAM = BooleanProperty.create("rightbeam");
public static final IntegerProperty DOWNCONNECT = IntegerProperty.create("downconnect", 0, 2);
protected final Map<BlockState, VoxelShape> AABBs;
public EdHorizontalSupportBlock(long config, BlockBehaviour.Properties builder, final AABB mainBeamAABB, final AABB eastBeamAABB, final AABB thinDownBeamAABB, final AABB thickDownBeamAABB)
{
super(config|DecorBlock.CFG_HORIZIONTAL, builder);
Map<BlockState, VoxelShape> aabbs = new HashMap<>();
for(boolean eastwest:EASTWEST.getPossibleValues()) {
for(boolean leftbeam:LEFTBEAM.getPossibleValues()) {
for(boolean rightbeam:RIGHTBEAM.getPossibleValues()) {
for(int downconnect:DOWNCONNECT.getPossibleValues()) {
final BlockState state = defaultBlockState().setValue(EASTWEST, eastwest).setValue(LEFTBEAM, leftbeam).setValue(RIGHTBEAM, rightbeam).setValue(DOWNCONNECT, downconnect);
VoxelShape shape = Shapes.create(Auxiliaries.getRotatedAABB(mainBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true));
if(rightbeam) shape = Shapes.joinUnoptimized(shape, Shapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.EAST:Direction.NORTH, true)), BooleanOp.OR);
if(leftbeam) shape = Shapes.joinUnoptimized(shape, Shapes.create(Auxiliaries.getRotatedAABB(eastBeamAABB, eastwest?Direction.WEST:Direction.SOUTH, true)), BooleanOp.OR);
if(downconnect==1) shape = Shapes.joinUnoptimized(shape, Shapes.create(thinDownBeamAABB), BooleanOp.OR);
if(downconnect==2) shape = Shapes.joinUnoptimized(shape, Shapes.create(thickDownBeamAABB), BooleanOp.OR);
aabbs.put(state.setValue(WATERLOGGED, false), shape);
aabbs.put(state.setValue(WATERLOGGED, true), shape);
}
}
}
}
AABBs = aabbs;
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return AABBs.get(state); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EASTWEST, RIGHTBEAM, LEFTBEAM, DOWNCONNECT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return temp_block_update_until_better(super.getStateForPlacement(context).setValue(EASTWEST, context.getHorizontalDirection().getAxis()==Direction.Axis.X), context.getLevel(), context.getClickedPos()); }
private BlockState temp_block_update_until_better(BlockState state, LevelAccessor world, BlockPos pos)
{
boolean ew = state.getValue(EASTWEST);
final BlockState rstate = world.getBlockState((!ew) ? (pos.east()) : (pos.south()) );
final BlockState lstate = world.getBlockState((!ew) ? (pos.west()) : (pos.north()) );
final BlockState dstate = world.getBlockState(pos.below());
int down_connector = 0;
if((dstate.getBlock() instanceof final EdStraightPoleBlock pole)) {
final Direction dfacing = dstate.getValue(EdStraightPoleBlock.FACING);
if((dfacing.getAxis() == Direction.Axis.Y)) {
if((pole== ModContent.THICK_STEEL_POLE) || ((pole==ModContent.THICK_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 2;
} else if((pole==ModContent.THIN_STEEL_POLE) || ((pole==ModContent.THIN_STEEL_POLE_HEAD) && (dfacing==Direction.UP))) {
down_connector = 1;
}
}
}
return state.setValue(RIGHTBEAM, (rstate.getBlock()==this) && (rstate.getValue(EASTWEST) != ew))
.setValue(LEFTBEAM , (lstate.getBlock()==this) && (lstate.getValue(EASTWEST) != ew))
.setValue(DOWNCONNECT , down_connector);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return temp_block_update_until_better(state, world, pos); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (held_stack.getItem() != this.asItem())) return InteractionResult.PASS;
if(!(hit.getDirection().getAxis().isVertical())) return InteractionResult.PASS;
final Direction placement_direction = player.getDirection();
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockPlaceContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return InteractionResult.sidedSuccess(world.isClientSide());
final BlockState new_state = getStateForPlacement(ctx);
if(new_state == null) return InteractionResult.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return InteractionResult.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return InteractionResult.sidedSuccess(world.isClientSide());
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return (rot==Rotation.CLOCKWISE_180) ? state : state.setValue(EASTWEST, !state.getValue(EASTWEST)); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
}

File diff suppressed because it is too large Load diff

View file

@ -1,118 +1,121 @@
/*
* @file EdLadderBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Ladder block. The implementation is based on the vanilla
* net.minecraft.block.BlockLadder. Minor changes to enable
* later configuration (for block list based construction
* time configuration), does not drop when the block behind
* is broken, etc.
*/
package wile.engineersdecor.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.LivingEntity;
import net.minecraft.world.IWorldReader;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.vector.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.block.BlockState;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
public class EdLadderBlock extends LadderBlock implements IDecorBlock
{
protected static final AxisAlignedBB EDLADDER_UNROTATED_AABB = Auxiliaries.getPixeledAABB(3, 0, 0, 13, 16, 3);
protected static final VoxelShape EDLADDER_SOUTH_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.SOUTH, false));
protected static final VoxelShape EDLADDER_EAST_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.EAST, false));
protected static final VoxelShape EDLADDER_WEST_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.WEST, false));
protected static final VoxelShape EDLADDER_NORTH_AABB = VoxelShapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.NORTH, false));
private static boolean without_speed_boost_ = false;
public static void on_config(boolean without_speed_boost)
{
without_speed_boost_ = without_speed_boost;
ModConfig.log("Config ladder: without-speed-boost:" + without_speed_boost_);
}
public EdLadderBlock(long config, AbstractBlock.Properties builder)
{ super(builder); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
public VoxelShape getShape(BlockState state, IBlockReader worldIn, BlockPos pos)
{
switch ((Direction)state.getValue(FACING)) {
case NORTH: return EDLADDER_NORTH_AABB;
case SOUTH: return EDLADDER_SOUTH_AABB;
case WEST: return EDLADDER_WEST_AABB;
default: return EDLADDER_EAST_AABB;
}
}
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
@Override
public boolean isLadder(BlockState state, IWorldReader world, BlockPos pos, LivingEntity entity)
{ return true; }
// Player update event, forwarded from the main mod instance.
public static void onPlayerUpdateEvent(final PlayerEntity player)
{
if((without_speed_boost_) || (player.isOnGround()) || (!player.onClimbable()) || (player.isSteppingCarefully()) || (player.isSpectator())) return;
double lvy = player.getLookAngle().y;
if(Math.abs(lvy) < 0.92) return;
final BlockPos pos = player.blockPosition();
final BlockState state = player.level.getBlockState(pos);
if(!(state.getBlock() instanceof EdLadderBlock)) return;
player.fallDistance = 0;
if((player.getDeltaMovement().y() < 0) == (player.getLookAngle().y < 0)) {
player.makeStuckInBlock(state, new Vector3d(0.2, (lvy>0)?(3):(6), 0.2));
if(Math.abs(player.getDeltaMovement().y()) > 0.1) {
Vector3d vdiff = Vector3d.atBottomCenterOf(pos).subtract(player.position()).scale(1);
vdiff.add(Vector3d.atBottomCenterOf(state.getValue(FACING).getNormal()).scale(0.5));
vdiff = new Vector3d(vdiff.x, player.getDeltaMovement().y, vdiff.z);
player.setDeltaMovement(vdiff);
}
} else if(player.getLookAngle().y > 0) {
player.makeStuckInBlock(state, new Vector3d(1, 0.05, 1));
}
}
}
/*
* @file EdLadderBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Ladder block. The implementation is based on the vanilla
* net.minecraft.block.BlockLadder. Minor changes to enable
* later configuration (for block list based construction
* time configuration), does not drop when the block behind
* is broken, etc.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.LadderBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class EdLadderBlock extends LadderBlock implements StandardBlocks.IStandardBlock
{
protected static final AABB EDLADDER_UNROTATED_AABB = Auxiliaries.getPixeledAABB(3, 0, 0, 13, 16, 3);
protected static final VoxelShape EDLADDER_SOUTH_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.SOUTH, false));
protected static final VoxelShape EDLADDER_EAST_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.EAST, false));
protected static final VoxelShape EDLADDER_WEST_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.WEST, false));
protected static final VoxelShape EDLADDER_NORTH_AABB = Shapes.create(Auxiliaries.getRotatedAABB(EDLADDER_UNROTATED_AABB, Direction.NORTH, false));
private static boolean without_speed_boost_ = false;
public static void on_config(boolean without_speed_boost)
{
without_speed_boost_ = without_speed_boost;
ModConfig.log("Config ladder: without-speed-boost:" + without_speed_boost_);
}
public EdLadderBlock(long config, BlockBehaviour.Properties builder)
{ super(builder); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.CUTOUT; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos)
{
return switch(state.getValue(FACING)) {
case NORTH -> EDLADDER_NORTH_AABB;
case SOUTH -> EDLADDER_SOUTH_AABB;
case WEST -> EDLADDER_WEST_AABB;
default -> EDLADDER_EAST_AABB;
};
}
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
@Override
public boolean isLadder(BlockState state, LevelReader world, BlockPos pos, LivingEntity entity)
{ return true; }
// Player update event, forwarded from the main mod instance.
public static void onPlayerUpdateEvent(final Player player)
{
if((without_speed_boost_) || (player.isOnGround()) || (!player.onClimbable()) || (player.isSteppingCarefully()) || (player.isSpectator())) return;
double lvy = player.getLookAngle().y;
if(Math.abs(lvy) < 0.92) return;
final BlockPos pos = player.blockPosition();
final BlockState state = player.level.getBlockState(pos);
if(!(state.getBlock() instanceof EdLadderBlock)) return;
player.fallDistance = 0;
if((player.getDeltaMovement().y() < 0) == (player.getLookAngle().y < 0)) {
player.makeStuckInBlock(state, new Vec3(0.2, (lvy>0)?(3):(6), 0.2));
if(Math.abs(player.getDeltaMovement().y()) > 0.1) {
Vec3 vdiff = Vec3.atBottomCenterOf(pos).subtract(player.position()).scale(1);
vdiff.add(Vec3.atBottomCenterOf(state.getValue(FACING).getNormal()).scale(0.5));
vdiff = new Vec3(vdiff.x, player.getDeltaMovement().y, vdiff.z);
player.setDeltaMovement(vdiff);
}
} else if(player.getLookAngle().y > 0) {
player.makeStuckInBlock(state, new Vec3(1, 0.05, 1));
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,278 +1,273 @@
/*
* @file EdPipeValve.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basically a piece of pipe that does not connect to
* pipes on the side, and conducts fluids only in one way.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.entity.LivingEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.Direction;
import net.minecraft.util.Rotation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class EdPipeValve
{
public static final int CFG_CHECK_VALVE = 0x1;
public static final int CFG_ANALOG_VALVE = 0x2;
public static final int CFG_REDSTONE_CONTROLLED_VALVE = 0x4;
public static void on_config(int container_size_decl, int redstone_slope)
{
PipeValveTileEntity.fluid_maxflow_mb = MathHelper.clamp(container_size_decl, 1, 10000);
PipeValveTileEntity.redstone_flow_slope_mb = MathHelper.clamp(redstone_slope, 1, 10000);
ModConfig.log("Config pipe valve: maxflow:" + PipeValveTileEntity.fluid_maxflow_mb + "mb, redstone amp:" + PipeValveTileEntity.redstone_flow_slope_mb + "mb/sig.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock
{
public static final BooleanProperty RS_CN_N = BooleanProperty.create("rs_n");
public static final BooleanProperty RS_CN_S = BooleanProperty.create("rs_s");
public static final BooleanProperty RS_CN_E = BooleanProperty.create("rs_e");
public static final BooleanProperty RS_CN_W = BooleanProperty.create("rs_w");
public static final BooleanProperty RS_CN_U = BooleanProperty.create("rs_u");
public static final BooleanProperty RS_CN_D = BooleanProperty.create("rs_d");
public final int valve_config;
public PipeValveBlock(long config, int valve_config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); this.valve_config = valve_config; }
private BlockState get_rsconnector_state(BlockState state, IWorld world, BlockPos pos, @Nullable BlockPos fromPos)
{
if((valve_config & (CFG_REDSTONE_CONTROLLED_VALVE))==0) return state;
Direction.Axis bfa = state.getValue(FACING).getAxis();
for(Direction f:Direction.values()) {
boolean cn = (f.getAxis() != bfa);
if(cn) {
BlockPos nbp = pos.relative(f);
if((fromPos != null) && (!nbp.equals(fromPos))) continue; // do not change connectors except form the frompos.
BlockState nbs = world.getBlockState(nbp);
if((nbs.getBlock() instanceof PipeValveBlock) || ((!nbs.isSignalSource()) && (!nbs.canConnectRedstone(world, nbp, f.getOpposite())))) cn = false;
}
switch(f) {
case NORTH: state = state.setValue(RS_CN_N, cn); break;
case SOUTH: state = state.setValue(RS_CN_S, cn); break;
case EAST: state = state.setValue(RS_CN_E, cn); break;
case WEST: state = state.setValue(RS_CN_W, cn); break;
case UP: state = state.setValue(RS_CN_U, cn); break;
case DOWN: state = state.setValue(RS_CN_D, cn); break;
}
}
return state;
}
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return VoxelShapes.block(); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RS_CN_N, RS_CN_S, RS_CN_E, RS_CN_W, RS_CN_U, RS_CN_D); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
return super.getStateForPlacement(context).setValue(RS_CN_N, false).setValue(RS_CN_S, false).setValue(RS_CN_E, false)
.setValue(RS_CN_W, false).setValue(RS_CN_U, false).setValue(RS_CN_D, false);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{ return get_rsconnector_state(state, world, pos, null); }
@Override
public void setPlacedBy(World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{ world.updateNeighborsAt(pos,this); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new PipeValveTileEntity(); }
@Override
public BlockState rotate(BlockState state, IWorld world, BlockPos pos, Rotation direction)
{ return get_rsconnector_state(state, world, pos, null); } // don't rotate at all
@Override
public boolean canConnectRedstone(BlockState state, IBlockReader world, BlockPos pos, @Nullable Direction side)
{ return (side!=null) && (side!=state.getValue(FACING)) && (side!=state.getValue(FACING).getOpposite()); }
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public boolean isSignalSource(BlockState state)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, IBlockReader blockAccess, BlockPos pos, Direction side)
{ return 0; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveTileEntity extends TileEntity implements ICapabilityProvider //, IFluidPipe
{
protected static int fluid_maxflow_mb = 1000;
protected static int redstone_flow_slope_mb = 1000/15;
private Direction block_facing_ = null;
private boolean filling_ = false;
private int valve_config_;
public PipeValveTileEntity()
{ this(ModContent.TET_STRAIGHT_PIPE_VALVE); }
public PipeValveTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
private Direction block_facing()
{
BlockState st = getLevel().getBlockState(getBlockPos());
return (st.getBlock() instanceof PipeValveBlock) ? st.getValue(PipeValveBlock.FACING) : Direction.NORTH;
}
private long valve_config()
{
if(valve_config_ <= 0) {
final Block block = getLevel().getBlockState(getBlockPos()).getBlock();
if(block instanceof PipeValveBlock) valve_config_ = ((PipeValveBlock)block).valve_config;
}
return valve_config_;
}
// TileEntity -----------------------------------------------------------------------------
@Override
public void setRemoved()
{
super.setRemoved();
back_flow_handler_.invalidate();
fluid_handler_.invalidate();
}
// ICapabilityProvider --------------------------------------------------------------------
private final LazyOptional<IFluidHandler> back_flow_handler_ = LazyOptional.of(() -> (IFluidHandler)new BackFlowHandler());
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> (IFluidHandler)new MainFlowHandler(this));
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
Direction bf = block_facing();
if(facing == bf) return back_flow_handler_.cast();
if(facing == bf.getOpposite()) return fluid_handler_.cast();
return LazyOptional.empty();
}
return super.getCapability(capability, facing);
}
// IFluidHandlers
@Nullable
private IFluidHandler forward_fluid_handler()
{
final TileEntity te = level.getBlockEntity(worldPosition.relative(block_facing()));
if(te == null) return null;
return te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, block_facing().getOpposite()).orElse(null);
}
// Forward flow handler --
private static class MainFlowHandler implements IFluidHandler
{
private final PipeValveTileEntity te;
public MainFlowHandler(PipeValveTileEntity te) { this.te = te; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return fluid_maxflow_mb; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
@Override public int fill(FluidStack resource, FluidAction action)
{
if(te.filling_) return 0;
final IFluidHandler fh = te.forward_fluid_handler();
if(fh==null) return 0;
FluidStack res = resource.copy();
if((te.valve_config() & CFG_REDSTONE_CONTROLLED_VALVE) != 0) {
int rs = te.level.getBestNeighborSignal(te.worldPosition);
if(rs <= 0) return 0;
if(((te.valve_config() & CFG_ANALOG_VALVE) != 0) && (rs < 15)) res.setAmount(MathHelper.clamp(rs * redstone_flow_slope_mb, 1, res.getAmount()));
}
if(res.getAmount() > fluid_maxflow_mb) res.setAmount(fluid_maxflow_mb);
te.filling_ = true;
int n_filled = fh.fill(res, action);
te.filling_ = false;
return n_filled;
}
}
// Back flow prevention handler --
private static class BackFlowHandler implements IFluidHandler
{
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return 0; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return false; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
}
}
}
/*
* @file EdPipeValve.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basically a piece of pipe that does not connect to
* pipes on the side, and conducts fluids only in one way.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.RsSignals;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class EdPipeValve
{
public static final int CFG_CHECK_VALVE = 0x1;
public static final int CFG_ANALOG_VALVE = 0x2;
public static final int CFG_REDSTONE_CONTROLLED_VALVE = 0x4;
public static void on_config(int container_size_decl, int redstone_slope)
{
PipeValveTileEntity.fluid_maxflow_mb = Mth.clamp(container_size_decl, 1, 10000);
PipeValveTileEntity.redstone_flow_slope_mb = Mth.clamp(redstone_slope, 1, 10000);
ModConfig.log("Config pipe valve: maxflow:" + PipeValveTileEntity.fluid_maxflow_mb + "mb, redstone amp:" + PipeValveTileEntity.redstone_flow_slope_mb + "mb/sig.");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveBlock extends StandardBlocks.DirectedWaterLoggable implements StandardEntityBlocks.IStandardEntityBlock<PipeValveTileEntity>
{
public static final BooleanProperty RS_CN_N = BooleanProperty.create("rs_n");
public static final BooleanProperty RS_CN_S = BooleanProperty.create("rs_s");
public static final BooleanProperty RS_CN_E = BooleanProperty.create("rs_e");
public static final BooleanProperty RS_CN_W = BooleanProperty.create("rs_w");
public static final BooleanProperty RS_CN_U = BooleanProperty.create("rs_u");
public static final BooleanProperty RS_CN_D = BooleanProperty.create("rs_d");
public final int valve_config;
public PipeValveBlock(long config, int valve_config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); this.valve_config = valve_config; }
@Override
@Nullable
public BlockEntityType<EdPipeValve.PipeValveTileEntity> getBlockEntityType()
{ return ModContent.TET_STRAIGHT_PIPE_VALVE; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return Shapes.block(); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(RS_CN_N, RS_CN_S, RS_CN_E, RS_CN_W, RS_CN_U, RS_CN_D); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
return super.getStateForPlacement(context).setValue(RS_CN_N, false).setValue(RS_CN_S, false).setValue(RS_CN_E, false)
.setValue(RS_CN_W, false).setValue(RS_CN_U, false).setValue(RS_CN_D, false);
}
@Override
@SuppressWarnings("deprecation")
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{ return get_rsconnector_state(state, world, pos, null); }
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack)
{ world.updateNeighborsAt(pos,this); }
@Override
public BlockState rotate(BlockState state, LevelAccessor world, BlockPos pos, Rotation direction)
{ return get_rsconnector_state(state, world, pos, null); } // don't rotate at all
@Override
public boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side)
{ return (side!=null) && (side!=state.getValue(FACING)) && (side!=state.getValue(FACING).getOpposite()); }
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
@SuppressWarnings("deprecation") // public boolean canConnectRedstone(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { return true; }
public boolean isSignalSource(BlockState p_60571_)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public int getSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
@Override
@SuppressWarnings("deprecation")
public int getDirectSignal(BlockState blockState, BlockGetter blockAccess, BlockPos pos, Direction side)
{ return 0; }
private BlockState get_rsconnector_state(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockPos fromPos)
{
if((valve_config & (CFG_REDSTONE_CONTROLLED_VALVE))==0) return state;
Direction.Axis bfa = state.getValue(FACING).getAxis();
for(Direction f:Direction.values()) {
boolean cn = (f.getAxis() != bfa);
if(cn) {
BlockPos nbp = pos.relative(f);
if((fromPos != null) && (!nbp.equals(fromPos))) continue; // do not change connectors except form the frompos.
BlockState nbs = world.getBlockState(nbp);
if((nbs.getBlock() instanceof PipeValveBlock) || (!nbs.isSignalSource()) && (RsSignals.hasSignalConnector(nbs, world, nbp, f.getOpposite()))) cn = false;
}
switch (f) {
case NORTH -> state = state.setValue(RS_CN_N, cn);
case SOUTH -> state = state.setValue(RS_CN_S, cn);
case EAST -> state = state.setValue(RS_CN_E, cn);
case WEST -> state = state.setValue(RS_CN_W, cn);
case UP -> state = state.setValue(RS_CN_U, cn);
case DOWN -> state = state.setValue(RS_CN_D, cn);
}
}
return state;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class PipeValveTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
protected static int fluid_maxflow_mb = 1000;
protected static int redstone_flow_slope_mb = 1000/15;
private final Direction block_facing_ = null;
private boolean filling_ = false;
private int valve_config_;
public PipeValveTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_STRAIGHT_PIPE_VALVE, pos, state); }
private Direction block_facing()
{
BlockState st = getLevel().getBlockState(getBlockPos());
return (st.getBlock() instanceof PipeValveBlock) ? st.getValue(PipeValveBlock.FACING) : Direction.NORTH;
}
private long valve_config()
{
if(valve_config_ <= 0) {
final Block block = getLevel().getBlockState(getBlockPos()).getBlock();
if(block instanceof PipeValveBlock) valve_config_ = ((PipeValveBlock)block).valve_config;
}
return valve_config_;
}
// BlockEntity -----------------------------------------------------------------------------
@Override
public void setRemoved()
{
super.setRemoved();
back_flow_handler_.invalidate();
fluid_handler_.invalidate();
}
// ICapabilityProvider --------------------------------------------------------------------
private final LazyOptional<IFluidHandler> back_flow_handler_ = LazyOptional.of(BackFlowHandler::new);
private final LazyOptional<IFluidHandler> fluid_handler_ = LazyOptional.of(() -> new MainFlowHandler(this));
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) {
Direction bf = block_facing();
if(facing == bf) return back_flow_handler_.cast();
if(facing == bf.getOpposite()) return fluid_handler_.cast();
return LazyOptional.empty();
}
return super.getCapability(capability, facing);
}
// IFluidHandlers
@Nullable
private IFluidHandler forward_fluid_handler()
{
final BlockEntity te = level.getBlockEntity(worldPosition.relative(block_facing()));
if(te == null) return null;
return te.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY, block_facing().getOpposite()).orElse(null);
}
// Forward flow handler --
private static class MainFlowHandler implements IFluidHandler
{
private final PipeValveTileEntity te;
public MainFlowHandler(PipeValveTileEntity te) { this.te = te; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return fluid_maxflow_mb; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
@Override public int fill(FluidStack resource, FluidAction action)
{
if(te.filling_) return 0;
final IFluidHandler fh = te.forward_fluid_handler();
if(fh==null) return 0;
FluidStack res = resource.copy();
if((te.valve_config() & CFG_REDSTONE_CONTROLLED_VALVE) != 0) {
int rs = te.level.getBestNeighborSignal(te.worldPosition);
if(rs <= 0) return 0;
if(((te.valve_config() & CFG_ANALOG_VALVE) != 0) && (rs < 15)) res.setAmount(Mth.clamp(rs * redstone_flow_slope_mb, 1, res.getAmount()));
}
if(res.getAmount() > fluid_maxflow_mb) res.setAmount(fluid_maxflow_mb);
te.filling_ = true;
int n_filled = fh.fill(res, action);
te.filling_ = false;
return n_filled;
}
}
// Back flow prevention handler --
private static class BackFlowHandler implements IFluidHandler
{
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return FluidStack.EMPTY; }
@Override public int getTankCapacity(int tank) { return 0; }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return false; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return FluidStack.EMPTY; }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return FluidStack.EMPTY; }
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,96 +1,100 @@
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.*;
import net.minecraft.state.BooleanProperty;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdRailingBlock extends DecorBlock.HorizontalFourWayWaterLoggable implements IDecorBlock
{
public EdRailingBlock(long config, AbstractBlock.Properties properties, final AxisAlignedBB base_aabb, final AxisAlignedBB railing_aabb)
{ super(config, properties, base_aabb, railing_aabb, 0); }
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockItemUseContext useContext)
{ return (useContext.getItemInHand().getItem() == asItem()) ? true : super.canBeReplaced(state, useContext); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
if(context.getClickedFace() != Direction.UP) return null;
BlockState state = context.getLevel().getBlockState(context.getClickedPos());
if(state.getBlock() != this) state = super.getStateForPlacement(context);
final Vector3d rhv = context.getClickLocation().subtract(Vector3d.atCenterOf(context.getClickedPos()));
BooleanProperty side = getDirectionProperty(Direction.getNearest(rhv.x, 0, rhv.z));
return state.setValue(side, true);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return ActionResultType.PASS;
Direction face = hit.getDirection();
if(!face.getAxis().isHorizontal()) return ActionResultType.sidedSuccess(world.isClientSide());
final Vector3d rhv = hit.getLocation().subtract(Vector3d.atCenterOf(hit.getBlockPos()));
if(rhv.multiply(Vector3d.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
state = state.setValue(railing, add);
if((!state.getValue(NORTH)) && (!state.getValue(EAST)) && (!state.getValue(SOUTH)) && (!state.getValue(WEST))) {
state = (world.getFluidState(pos).getType() == Fluids.WATER) ? Blocks.WATER.defaultBlockState() : (Blocks.AIR.defaultBlockState());
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
} else {
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
}
return ActionResultType.sidedSuccess(world.isClientSide());
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, @Nullable TileEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
drops.add(new ItemStack(state.getBlock().asItem(), Math.max(n, 1)));
return drops;
}
}
/*
* @file EdCatwalkBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Bottom aligned platforms with railings.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdRailingBlock extends StandardBlocks.HorizontalFourWayWaterLoggable
{
public EdRailingBlock(long config, BlockBehaviour.Properties properties, final AABB base_aabb, final AABB railing_aabb)
{ super(config, properties, base_aabb, railing_aabb, 0); }
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockPlaceContext useContext)
{ return (useContext.getItemInHand().getItem() == asItem()) || super.canBeReplaced(state, useContext); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
if(context.getClickedFace() != Direction.UP) return null;
BlockState state = context.getLevel().getBlockState(context.getClickedPos());
if(state.getBlock() != this) state = super.getStateForPlacement(context);
final Vec3 rhv = context.getClickLocation().subtract(Vec3.atCenterOf(context.getClickedPos()));
BooleanProperty side = getDirectionProperty(Direction.getNearest(rhv.x, 0, rhv.z));
return state.setValue(side, true);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return InteractionResult.PASS;
Direction face = hit.getDirection();
if(!face.getAxis().isHorizontal()) return InteractionResult.sidedSuccess(world.isClientSide());
final Vec3 rhv = hit.getLocation().subtract(Vec3.atCenterOf(hit.getBlockPos()));
if(rhv.multiply(Vec3.atLowerCornerOf(face.getNormal())).scale(2).lengthSqr() < 0.99) face = face.getOpposite(); // click on railing, not the outer side.
BooleanProperty railing = getDirectionProperty(face);
boolean add = (!state.getValue(railing));
state = state.setValue(railing, add);
if((!state.getValue(NORTH)) && (!state.getValue(EAST)) && (!state.getValue(SOUTH)) && (!state.getValue(WEST))) {
state = (world.getFluidState(pos).getType() == Fluids.WATER) ? Blocks.WATER.defaultBlockState() : (Blocks.AIR.defaultBlockState());
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
} else {
EdCatwalkBlock.place_consume(state, world, pos, player, hand, add ? 1 : -1);
}
return InteractionResult.sidedSuccess(world.isClientSide());
}
// -- IDecorBlock
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion)
{
if(world.isClientSide()) return Collections.singletonList(ItemStack.EMPTY);
List<ItemStack> drops = new ArrayList<>();
int n = (state.getValue(NORTH)?1:0)+(state.getValue(EAST)?1:0)+(state.getValue(SOUTH)?1:0)+(state.getValue(WEST)?1:0);
drops.add(new ItemStack(state.getBlock().asItem(), Math.max(n, 1)));
return drops;
}
}

View file

@ -1,231 +1,226 @@
/*
* @file EdRoofBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.fluid.FluidState;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.state.properties.Half;
import net.minecraft.state.properties.StairsShape;
import net.minecraft.util.*;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorld;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
public class EdRoofBlock extends StandardBlocks.HorizontalWaterLoggable implements IDecorBlock
{
public static final EnumProperty<StairsShape> SHAPE = BlockStateProperties.STAIRS_SHAPE;
public static final EnumProperty<Half> HALF = BlockStateProperties.HALF;
private final VoxelShape[][][] shape_cache_;
public EdRoofBlock(long config, AbstractBlock.Properties properties)
{ this(config, properties.dynamicShape(), VoxelShapes.empty(), VoxelShapes.empty()); }
public EdRoofBlock(long config, AbstractBlock.Properties properties, VoxelShape add, VoxelShape cut)
{
super(config, properties, Auxiliaries.getPixeledAABB(0, 0,0,16, 8, 16));
registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH).setValue(SHAPE, StairsShape.STRAIGHT));
shape_cache_ = makeShapes(add, cut);
}
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return false; }
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public float getShadeBrightness(BlockState state, IBlockReader world, BlockPos pos)
{ return 0.98f; }
@Override
@SuppressWarnings("deprecation")
public int getLightBlock(BlockState state, IBlockReader world, BlockPos pos)
{ return 1; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext context)
{ return shape_cache_[state.getValue(HALF).ordinal()][state.getValue(HORIZONTAL_FACING).get3DDataValue()][state.getValue(SHAPE).ordinal()]; }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SHAPE, HALF); }
@Override
public FluidState getFluidState(BlockState state)
{ return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); }
@Override
public boolean isPathfindable(BlockState state, IBlockReader world, BlockPos pos, PathType type)
{ return false; }
@Override
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockPos pos = context.getClickedPos();
Direction face = context.getClickedFace();
BlockState state = defaultBlockState()
.setValue(HORIZONTAL_FACING, context.getHorizontalDirection())
.setValue(HALF, (face == Direction.DOWN) ? Half.TOP : Half.BOTTOM)
.setValue(WATERLOGGED, context.getLevel().getFluidState(pos).getType()==Fluids.WATER);
return state.setValue(SHAPE, getStairsShapeProperty(state, context.getLevel(), pos));
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
return (facing.getAxis().isHorizontal()) ? (state.setValue(SHAPE, getStairsShapeProperty(state, world, pos))) : (super.updateShape(state, facing, facingState, world, pos, facingPos));
}
@Override
public BlockState rotate(BlockState state, Rotation rot)
{ return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror where)
{
if((where==Mirror.LEFT_RIGHT) && (state.getValue(HORIZONTAL_FACING).getAxis()==Direction.Axis.Z)) {
switch(state.getValue(SHAPE)) {
case INNER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case INNER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case OUTER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
default: return state.rotate(Rotation.CLOCKWISE_180);
}
} else if((where==Mirror.FRONT_BACK) && (state.getValue(HORIZONTAL_FACING).getAxis() == Direction.Axis.X)) {
switch(state.getValue(SHAPE)) {
case INNER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case INNER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case OUTER_LEFT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT: return state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
case STRAIGHT: return state.rotate(Rotation.CLOCKWISE_180);
}
}
return super.mirror(state, where);
}
private static boolean isRoofBlock(BlockState state)
{ return (state.getBlock() instanceof EdRoofBlock); }
private static boolean isOtherRoofState(BlockState state, IBlockReader world, BlockPos pos, Direction facing)
{
BlockState st = world.getBlockState(pos.relative(facing));
return (!isRoofBlock(st)) || (st.getValue(HORIZONTAL_FACING) != state.getValue(HORIZONTAL_FACING));
}
private static VoxelShape[][][] makeShapes(VoxelShape add, VoxelShape cut)
{
VoxelShape[][][] shapes = new VoxelShape[2][6][5];
for(int half_index=0; half_index<Half.values().length; ++half_index) {
for(int direction_index=0; direction_index<Direction.values().length; ++direction_index) {
for(int stairs_shape_index=0; stairs_shape_index<StairsShape.values().length; ++stairs_shape_index) {
VoxelShape shape = makeShape(half_index, direction_index, stairs_shape_index);
try {
// Only in case something changes and this fails, log but do not prevent the game from starting.
// Roof shapes are not the most important thing in the world.
if(!add.isEmpty()) shape = VoxelShapes.joinUnoptimized(shape, add, IBooleanFunction.OR);
if(!cut.isEmpty()) shape = VoxelShapes.joinUnoptimized(shape, cut, IBooleanFunction.ONLY_FIRST);
} catch(Throwable ex) {
Auxiliaries.logError("Failed to cut shape using Boolean function. This is bug.");
}
shapes[half_index][direction_index][stairs_shape_index] = shape;
}
}
}
return shapes;
}
private static VoxelShape makeShape(int half_index, int direction_index, int stairs_shape_index)
{
AxisAlignedBB[] straight = new AxisAlignedBB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 0, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 0, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 0, 16, 16, 16)
};
AxisAlignedBB[] pyramid = new AxisAlignedBB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 4, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 8, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 12, 16, 16, 16)
};
final Half half = Half.values()[half_index];
if(half==Half.TOP) {
straight = Auxiliaries.getMirroredAABB(straight, Axis.Y);
pyramid = Auxiliaries.getMirroredAABB(pyramid, Axis.Y);
}
Direction direction = Direction.from3DDataValue(direction_index);
if((direction==Direction.UP) || (direction==Direction.DOWN)) return VoxelShapes.block();
direction_index = (direction.get2DDataValue()+1) & 0x03; // ref NORTH -> EAST for stairs compliancy.
final StairsShape stairs = StairsShape.values()[stairs_shape_index];
switch(stairs) {
case STRAIGHT:
return Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(straight, direction_index));
case OUTER_LEFT:
return Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index-1));
case OUTER_RIGHT:
return Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index));
case INNER_LEFT:
return Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index-1)
);
case INNER_RIGHT:
return Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index+1)
);
default:
return VoxelShapes.block();
}
}
private static StairsShape getStairsShapeProperty(BlockState state, IBlockReader world, BlockPos pos)
{
Direction direction = state.getValue(HORIZONTAL_FACING);
{
BlockState ns = world.getBlockState(pos.relative(direction));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf.getOpposite())) {
return (nf == direction.getCounterClockWise()) ? StairsShape.OUTER_LEFT : StairsShape.OUTER_RIGHT;
}
}
}
{
BlockState ns = world.getBlockState(pos.relative(direction.getOpposite()));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf)) {
return (nf == direction.getCounterClockWise()) ? StairsShape.INNER_LEFT : StairsShape.INNER_RIGHT;
}
}
}
return StairsShape.STRAIGHT;
}
}
/*
* @file EdRoofBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Roof blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Half;
import net.minecraft.world.level.block.state.properties.StairsShape;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
public class EdRoofBlock extends StandardBlocks.HorizontalWaterLoggable
{
public static final EnumProperty<StairsShape> SHAPE = BlockStateProperties.STAIRS_SHAPE;
public static final EnumProperty<Half> HALF = BlockStateProperties.HALF;
private final VoxelShape[][][] shape_cache_;
public EdRoofBlock(long config, BlockBehaviour.Properties properties)
{ this(config, properties.dynamicShape(), Shapes.empty(), Shapes.empty()); }
public EdRoofBlock(long config, BlockBehaviour.Properties properties, VoxelShape add, VoxelShape cut)
{
super(config, properties, Auxiliaries.getPixeledAABB(0, 0,0,16, 8, 16));
registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH).setValue(SHAPE, StairsShape.STRAIGHT));
shape_cache_ = makeShapes(add, cut);
}
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return false; }
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public float getShadeBrightness(BlockState state, BlockGetter world, BlockPos pos)
{ return 0.98f; }
@Override
@SuppressWarnings("deprecation")
public int getLightBlock(BlockState state, BlockGetter world, BlockPos pos)
{ return 1; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context)
{ return shape_cache_[state.getValue(HALF).ordinal()][state.getValue(HORIZONTAL_FACING).get3DDataValue()][state.getValue(SHAPE).ordinal()]; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(SHAPE, HALF); }
@Override
public FluidState getFluidState(BlockState state)
{ return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); }
@Override
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type)
{ return false; }
@Override
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockPos pos = context.getClickedPos();
Direction face = context.getClickedFace();
BlockState state = defaultBlockState()
.setValue(HORIZONTAL_FACING, context.getHorizontalDirection())
.setValue(HALF, (face == Direction.DOWN) ? Half.TOP : Half.BOTTOM)
.setValue(WATERLOGGED, context.getLevel().getFluidState(pos).getType()==Fluids.WATER);
return state.setValue(SHAPE, getStairsShapeProperty(state, context.getLevel(), pos));
}
@Override
public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
return (facing.getAxis().isHorizontal()) ? (state.setValue(SHAPE, getStairsShapeProperty(state, world, pos))) : (super.updateShape(state, facing, facingState, world, pos, facingPos));
}
@Override
public BlockState rotate(BlockState state, Rotation rot)
{ return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror where)
{
if((where==Mirror.LEFT_RIGHT) && (state.getValue(HORIZONTAL_FACING).getAxis()==Direction.Axis.Z)) {
return switch (state.getValue(SHAPE)) {
case INNER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case INNER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case OUTER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
default -> state.rotate(Rotation.CLOCKWISE_180);
};
} else if((where==Mirror.FRONT_BACK) && (state.getValue(HORIZONTAL_FACING).getAxis() == Direction.Axis.X)) {
return switch (state.getValue(SHAPE)) {
case INNER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_LEFT);
case INNER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.INNER_RIGHT);
case OUTER_LEFT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_RIGHT);
case OUTER_RIGHT -> state.rotate(Rotation.CLOCKWISE_180).setValue(SHAPE, StairsShape.OUTER_LEFT);
case STRAIGHT -> state.rotate(Rotation.CLOCKWISE_180);
};
}
return super.mirror(state, where);
}
private static boolean isRoofBlock(BlockState state)
{ return (state.getBlock() instanceof EdRoofBlock); }
private static boolean isOtherRoofState(BlockState state, BlockGetter world, BlockPos pos, Direction facing)
{
BlockState st = world.getBlockState(pos.relative(facing));
return (!isRoofBlock(st)) || (st.getValue(HORIZONTAL_FACING) != state.getValue(HORIZONTAL_FACING));
}
private static VoxelShape[][][] makeShapes(VoxelShape add, VoxelShape cut)
{
VoxelShape[][][] shapes = new VoxelShape[2][6][5];
for(int half_index=0; half_index<Half.values().length; ++half_index) {
for(int direction_index=0; direction_index<Direction.values().length; ++direction_index) {
for(int stairs_shape_index=0; stairs_shape_index<StairsShape.values().length; ++stairs_shape_index) {
VoxelShape shape = makeShape(half_index, direction_index, stairs_shape_index);
try {
// Only in case something changes and this fails, log but do not prevent the game from starting.
// Roof shapes are not the most important thing in the world.
if(!add.isEmpty()) shape = Shapes.joinUnoptimized(shape, add, BooleanOp.OR);
if(!cut.isEmpty()) shape = Shapes.joinUnoptimized(shape, cut, BooleanOp.ONLY_FIRST);
} catch(Throwable ex) {
Auxiliaries.logError("Failed to cut shape using Boolean function. This is bug.");
}
shapes[half_index][direction_index][stairs_shape_index] = shape;
}
}
}
return shapes;
}
private static VoxelShape makeShape(int half_index, int direction_index, int stairs_shape_index)
{
AABB[] straight = new AABB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 0, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 0, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 0, 16, 16, 16)
};
AABB[] pyramid = new AABB[]{
Auxiliaries.getPixeledAABB( 0, 0, 0, 16, 4, 16),
Auxiliaries.getPixeledAABB( 4, 4, 4, 16, 8, 16),
Auxiliaries.getPixeledAABB( 8, 8, 8, 16, 12, 16),
Auxiliaries.getPixeledAABB(12, 12, 12, 16, 16, 16)
};
final Half half = Half.values()[half_index];
if(half==Half.TOP) {
straight = Auxiliaries.getMirroredAABB(straight, Direction.Axis.Y);
pyramid = Auxiliaries.getMirroredAABB(pyramid, Direction.Axis.Y);
}
Direction direction = Direction.from3DDataValue(direction_index);
if((direction==Direction.UP) || (direction==Direction.DOWN)) return Shapes.block();
direction_index = (direction.get2DDataValue()+1) & 0x03; // ref NORTH -> EAST for stairs compliancy.
final StairsShape stairs = StairsShape.values()[stairs_shape_index];
return switch (stairs) {
case STRAIGHT -> Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(straight, direction_index));
case OUTER_LEFT -> Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index - 1));
case OUTER_RIGHT -> Auxiliaries.getUnionShape(Auxiliaries.getYRotatedAABB(pyramid, direction_index));
case INNER_LEFT -> Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index - 1)
);
case INNER_RIGHT -> Auxiliaries.getUnionShape(
Auxiliaries.getYRotatedAABB(straight, direction_index),
Auxiliaries.getYRotatedAABB(straight, direction_index + 1)
);
};
}
private static StairsShape getStairsShapeProperty(BlockState state, BlockGetter world, BlockPos pos)
{
Direction direction = state.getValue(HORIZONTAL_FACING);
{
BlockState ns = world.getBlockState(pos.relative(direction));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf.getOpposite())) {
return (nf == direction.getCounterClockWise()) ? StairsShape.OUTER_LEFT : StairsShape.OUTER_RIGHT;
}
}
}
{
BlockState ns = world.getBlockState(pos.relative(direction.getOpposite()));
if(isRoofBlock(ns) && (state.getValue(HALF) == ns.getValue(HALF))) {
Direction nf = ns.getValue(HORIZONTAL_FACING);
if(nf.getAxis() != state.getValue(HORIZONTAL_FACING).getAxis() && isOtherRoofState(state, world, pos, nf)) {
return (nf == direction.getCounterClockWise()) ? StairsShape.INNER_LEFT : StairsShape.INNER_RIGHT;
}
}
}
return StairsShape.STRAIGHT;
}
}

View file

@ -1,18 +0,0 @@
/*
* @file EdSlabBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Standard half block horizontal slab characteristics class.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.VariantSlabBlock;
import net.minecraft.block.*;
public class EdSlabBlock extends VariantSlabBlock implements IDecorBlock
{
public EdSlabBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
}

View file

@ -1,19 +0,0 @@
/*
* @file EdSlabSliceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Half slab ("slab slices") characteristics class. Actually
* it's now a quater slab, but who cares.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.SlabSliceBlock;
import net.minecraft.block.AbstractBlock;
public class EdSlabSliceBlock extends SlabSliceBlock implements IDecorBlock
{
public EdSlabSliceBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
}

View file

@ -1,266 +1,229 @@
/*
* @file EdSolarPanel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.LightType;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.AbstractBlock;
public class EdSolarPanel
{
public static void on_config(int peak_power_per_tick)
{ SolarPanelTileEntity.on_config(peak_power_per_tick); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelBlock extends DecorBlock.Cutout implements IDecorBlock
{
public static final IntegerProperty EXPOSITION = IntegerProperty.create("exposition", 0, 4);
public SolarPanelBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(EXPOSITION, 1));
}
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EXPOSITION); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new EdSolarPanel.SolarPanelTileEntity(); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(te instanceof SolarPanelTileEntity) ((SolarPanelTileEntity)te).state_message(player);
return ActionResultType.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelTileEntity extends TileEntity implements ITickableTileEntity, ICapabilityProvider, IEnergyStorage
{
public static final int DEFAULT_PEAK_POWER = 40;
public static final int TICK_INTERVAL = 4;
public static final int ACCUMULATION_INTERVAL = 8;
private static final Direction transfer_directions_[] = {Direction.DOWN, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH };
private static int peak_power_per_tick_ = DEFAULT_PEAK_POWER;
private static final int max_power_storage_ = 64000;
private static final int max_feed_power = 8192;
private static int feeding_threshold = max_power_storage_/5;
private static int balancing_threshold = max_power_storage_/10;
private int tick_timer_ = 0;
private int recalc_timer_ = 0;
private int accumulated_power_ = 0;
private int current_production_ = 0;
private int current_feedin_ = 0;
private boolean output_enabled_ = false;
public static void on_config(int peak_power_per_tick)
{
peak_power_per_tick_ = MathHelper.clamp(peak_power_per_tick, 2, 8192);
feeding_threshold = Math.max(max_power_storage_/5, 1000);
balancing_threshold = Math.max(max_power_storage_/10, 1000);
ModConfig.log("Config small solar panel: Peak production:" + peak_power_per_tick_ + "/t.");
}
//------------------------------------------------------------------------------------------------------------------
public SolarPanelTileEntity()
{ this(ModContent.TET_SMALL_SOLAR_PANEL); }
public SolarPanelTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public void readnbt(CompoundNBT nbt, boolean update_packet)
{ accumulated_power_ = nbt.getInt("energy"); }
protected void writenbt(CompoundNBT nbt, boolean update_packet)
{ nbt.putInt("energy", accumulated_power_); }
public void state_message(PlayerEntity player)
{
String soc = Integer.toString(MathHelper.clamp((accumulated_power_*100/max_power_storage_),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_solar_panel.status", new Object[]{soc, max_power_storage_, current_production_, current_feedin_ }));
}
// IEnergyStorage --------------------------------------------------------------------------
@Override
public boolean canExtract()
{ return true; }
@Override
public boolean canReceive()
{ return false; }
@Override
public int getMaxEnergyStored()
{ return max_power_storage_; }
@Override
public int getEnergyStored()
{ return accumulated_power_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{
int p = Math.min(accumulated_power_, maxExtract);
if(!simulate) accumulated_power_ -= p;
return p;
}
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{ return 0; }
// ICapabilityProvider ---------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> (IEnergyStorage)this);
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt, false); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt, false); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
@Override
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof SolarPanelBlock)) return;
current_feedin_ = 0;
final List<SolarPanelTileEntity> adjacent_panels = new ArrayList<>();
if(output_enabled_) {
for(int i=0; (i<transfer_directions_.length) && (accumulated_power_>0); ++i) {
final Direction f = transfer_directions_[i];
TileEntity te = level.getBlockEntity(worldPosition.relative(f));
if(te==null) continue;
IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, f.getOpposite()).orElse(null);
if(es==null) continue;
if(!es.canReceive()) {
if(!(te instanceof SolarPanelTileEntity)) continue;
adjacent_panels.add((SolarPanelTileEntity)te);
continue;
}
final int feed_power = (accumulated_power_ > (max_power_storage_/10)) ? max_feed_power : Math.max(current_production_*2, (peak_power_per_tick_/4));
int fed = es.receiveEnergy(Math.min(accumulated_power_, feed_power * TICK_INTERVAL), false);
accumulated_power_ = MathHelper.clamp(accumulated_power_-fed,0, accumulated_power_);
current_feedin_ += fed;
}
}
current_feedin_ /= TICK_INTERVAL;
if((current_feedin_ <= 0) && ((accumulated_power_ >= balancing_threshold) || (current_production_ <= 0))) {
for(SolarPanelTileEntity panel: adjacent_panels) {
if(panel.accumulated_power_ >= (accumulated_power_-balancing_threshold)) continue;
panel.accumulated_power_ += balancing_threshold;
accumulated_power_ -= balancing_threshold;
if(accumulated_power_ < balancing_threshold) break;
}
}
if(!level.canSeeSkyFromBelowWater(worldPosition)) {
tick_timer_ = TICK_INTERVAL * 10;
current_production_ = 0;
if((accumulated_power_ > 0)) output_enabled_ = true;
if(state.getValue((SolarPanelBlock.EXPOSITION))!=2) level.setBlockAndUpdate(worldPosition, state.setValue(SolarPanelBlock.EXPOSITION, 2));
return;
}
if(accumulated_power_ <= 0) output_enabled_ = false;
if(--recalc_timer_ > 0) return;
recalc_timer_ = ACCUMULATION_INTERVAL + ((int)(Math.random()+.5));
int theta = ((((int)(level.getSunAngle(1f) * (180.0/Math.PI)))+90) % 360);
int e = 2;
if(theta > 340) e = 2;
else if(theta < 45) e = 0;
else if(theta < 80) e = 1;
else if(theta < 100) e = 2;
else if(theta < 135) e = 3;
else if(theta < 190) e = 4;
BlockState nstate = state.setValue(SolarPanelBlock.EXPOSITION, e);
if(nstate != state) level.setBlock(worldPosition, nstate, 1|2);
final double eff = (1.0-((level.getRainLevel(1f)*0.6)+(level.getThunderLevel(1f)*0.3)));
final double ll = ((double)(level.getLightEngine().getLayerListener(LightType.SKY).getLightValue(getBlockPos())))/15;
final double rf = Math.sin((Math.PI/2) * Math.sqrt(((double)(((theta<0)||(theta>180))?(0):((theta>90)?(180-theta):(theta))))/90));
current_production_ = (int)(Math.min(rf*rf*eff*ll, 1) * peak_power_per_tick_);
accumulated_power_ = Math.min(accumulated_power_ + (current_production_*(TICK_INTERVAL*ACCUMULATION_INTERVAL)), max_power_storage_);
if(accumulated_power_ >= (feeding_threshold)) output_enabled_ = true;
}
}
}
/*
* @file EdSolarPanel.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import wile.engineersdecor.libmc.detail.RfEnergy;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class EdSolarPanel
{
public static final int DEFAULT_PEAK_POWER = 40;
private static int peak_power_per_tick_ = DEFAULT_PEAK_POWER;
private static int max_power_storage_ = 64000;
private static int max_feed_power = 4096;
private static int feeding_threshold = max_power_storage_/5;
private static int balancing_threshold = max_power_storage_/10;
public static void on_config(int peak_power_per_tick, int battery_capacity, int max_feed_in_power)
{
final int t = SolarPanelTileEntity.TICK_INTERVAL;
peak_power_per_tick_ = Mth.clamp(peak_power_per_tick, 12, 8192);
feeding_threshold = Math.max(max_power_storage_/5, 1000);
balancing_threshold = Math.max(max_power_storage_/10, 1000);
max_power_storage_ = battery_capacity;
max_feed_power = max_feed_in_power * t;
ModConfig.log("Config small solar panel: Peak production:" + peak_power_per_tick_ + "/t, capacity:" + max_power_storage_ + "rf, max-feed:" + (max_feed_power/t) + "rf/t");
}
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelBlock extends StandardBlocks.Cutout implements StandardEntityBlocks.IStandardEntityBlock<SolarPanelTileEntity>
{
public static final IntegerProperty EXPOSITION = IntegerProperty.create("exposition", 0, 4);
public SolarPanelBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{
super(config, builder, unrotatedAABB);
registerDefaultState(super.defaultBlockState().setValue(EXPOSITION, 1));
}
@Override
@Nullable
public BlockEntityType<EdSolarPanel.SolarPanelTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_SOLAR_PANEL; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(EXPOSITION); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(te instanceof SolarPanelTileEntity) ((SolarPanelTileEntity)te).state_message(player);
return InteractionResult.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class SolarPanelTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
public static final int TICK_INTERVAL = 4;
public static final int ACCUMULATION_INTERVAL = 8;
private static final Direction[] transfer_directions_ = {Direction.DOWN, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH };
private int tick_timer_ = 0;
private int recalc_timer_ = 0;
private int current_production_ = 0;
private int current_feedin_ = 0;
private boolean output_enabled_ = false;
private final RfEnergy.Battery battery_ = new RfEnergy.Battery(max_power_storage_, 0, 1024);
private final LazyOptional<IEnergyStorage> energy_handler_ = battery_.createEnergyHandler();
//------------------------------------------------------------------------------------------------------------------
public SolarPanelTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_SOLAR_PANEL, pos, state); }
public void readnbt(CompoundTag nbt, boolean update_packet)
{ battery_.load(nbt); }
protected void writenbt(CompoundTag nbt, boolean update_packet)
{ battery_.save(nbt); }
public void state_message(Player player)
{
String soc = Integer.toString(Mth.clamp((battery_.getEnergyStored()*100/max_power_storage_),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_solar_panel.status", soc, max_power_storage_, current_production_, current_feedin_));
}
// ICapabilityProvider ---------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt, false); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt, false); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
@Override
public void tick()
{
if((level.isClientSide) || (--tick_timer_ > 0)) return;
tick_timer_ = TICK_INTERVAL;
BlockState state = level.getBlockState(worldPosition);
if(!(state.getBlock() instanceof SolarPanelBlock)) return;
current_feedin_ = 0;
final List<SolarPanelTileEntity> adjacent_panels = new ArrayList<>();
if(output_enabled_) {
for(int i=0; (i<transfer_directions_.length) && (!battery_.isEmpty()); ++i) {
final Direction f = transfer_directions_[i];
BlockEntity te = level.getBlockEntity(worldPosition.relative(f));
if(te==null) continue;
IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, f.getOpposite()).orElse(null);
if(es==null) continue;
if(!es.canReceive()) {
if(!(te instanceof SolarPanelTileEntity)) continue;
adjacent_panels.add((SolarPanelTileEntity)te);
continue;
}
final int feed_power = (battery_.getEnergyStored() > (max_power_storage_/10)) ? max_feed_power : Math.max(current_production_*2, (peak_power_per_tick_/4));
final int fed = es.receiveEnergy(Math.min(battery_.getEnergyStored(), feed_power * TICK_INTERVAL), false);
battery_.draw(fed);
current_feedin_ += fed;
}
}
current_feedin_ /= TICK_INTERVAL;
if((current_feedin_ <= 0) && ((battery_.getEnergyStored() >= balancing_threshold) || (current_production_ <= 0))) {
for(SolarPanelTileEntity panel: adjacent_panels) {
if(panel.battery_.getEnergyStored() >= (battery_.getEnergyStored()-balancing_threshold)) continue;
panel.battery_.setEnergyStored(panel.battery_.getEnergyStored() + balancing_threshold);
battery_.setEnergyStored(battery_.getEnergyStored() - balancing_threshold);
if(battery_.getEnergyStored() < balancing_threshold) break;
}
}
if(!level.canSeeSkyFromBelowWater(worldPosition)) {
tick_timer_ = TICK_INTERVAL * 10;
current_production_ = 0;
if((!battery_.isEmpty())) output_enabled_ = true;
if(state.getValue((SolarPanelBlock.EXPOSITION))!=2) level.setBlockAndUpdate(worldPosition, state.setValue(SolarPanelBlock.EXPOSITION, 2));
return;
}
if(battery_.isEmpty()) output_enabled_ = false;
if(--recalc_timer_ > 0) return;
recalc_timer_ = ACCUMULATION_INTERVAL + ((int)(Math.random()+.5));
int theta = ((((int)(level.getSunAngle(1f) * (180.0/Math.PI)))+90) % 360);
int e = 2;
if(theta > 340) e = 2;
else if(theta < 45) e = 0;
else if(theta < 80) e = 1;
else if(theta < 100) e = 2;
else if(theta < 135) e = 3;
else if(theta < 190) e = 4;
BlockState nstate = state.setValue(SolarPanelBlock.EXPOSITION, e);
if(nstate != state) level.setBlock(worldPosition, nstate, 1|2);
final double eff = (1.0-((level.getRainLevel(1f)*0.6)+(level.getThunderLevel(1f)*0.3)));
final double ll = ((double)(level.getLightEngine().getLayerListener(LightLayer.SKY).getLightValue(getBlockPos())))/15;
final double rf = Math.sin((Math.PI/2) * Math.sqrt(((double)(((theta<0)||(theta>180))?(0):((theta>90)?(180-theta):(theta))))/90));
current_production_ = (int)(Math.min(rf*rf*eff*ll, 1) * peak_power_per_tick_);
battery_.setEnergyStored(Math.min(battery_.getEnergyStored() + (current_production_*(TICK_INTERVAL*ACCUMULATION_INTERVAL)), max_power_storage_));
if(battery_.getEnergyStored() >= (feeding_threshold)) output_enabled_ = true;
}
}
}

View file

@ -1,22 +0,0 @@
/*
* @file EdStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stairs and roof blocks, almost entirely based on vanilla stairs.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardStairsBlock;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
public class EdStairsBlock extends StandardStairsBlock implements IDecorBlock
{
public EdStairsBlock(long config, BlockState state, AbstractBlock.Properties properties)
{ super(config, state, properties); }
public EdStairsBlock(long config, java.util.function.Supplier<BlockState> state, AbstractBlock.Properties properties)
{ super(config, state, properties); }
}

View file

@ -1,84 +1,88 @@
/*
* @file EdStraightPoleBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItem;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.DirectionalPlaceContext;
import net.minecraft.item.ItemStack;
import net.minecraft.util.*;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.world.World;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.Arrays;
import net.minecraft.block.AbstractBlock;
public class EdStraightPoleBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock
{
private final EdStraightPoleBlock default_pole;
public EdStraightPoleBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB, @Nullable EdStraightPoleBlock defaultPole)
{ super(config, builder, unrotatedAABB); default_pole=(defaultPole==null) ? (this) : (defaultPole); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
Direction facing = context.getClickedFace();
BlockState state = super.getStateForPlacement(context).setValue(FACING, facing);
if((config & DecorBlock.CFG_FLIP_PLACEMENT_IF_SAME) != 0) {
World world = context.getLevel();
BlockPos pos = context.getClickedPos();
if(world.getBlockState(pos.relative(facing.getOpposite())).getBlock() instanceof EdStraightPoleBlock) {
state = state.setValue(FACING, state.getValue(FACING).getOpposite());
}
}
return state;
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if((hit.getDirection().getAxis() == state.getValue(FACING).getAxis())) return ActionResultType.PASS;
final ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (!(held_stack.getItem() instanceof BlockItem))) return ActionResultType.PASS;
if(!(((BlockItem)(held_stack.getItem())).getBlock() instanceof EdStraightPoleBlock)) return ActionResultType.PASS;
if(held_stack.getItem() != default_pole.asItem()) return ActionResultType.sidedSuccess(world.isClientSide());
final Block held_block = ((BlockItem)(held_stack.getItem())).getBlock();
final Direction block_direction = state.getValue(FACING);
final Vector3d block_vec = Vector3d.atLowerCornerOf(state.getValue(FACING).getNormal());
final double colinearity = 1.0-block_vec.cross(player.getLookAngle()).length();
final Direction placement_direction = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis()==block_direction.getAxis()).findFirst().orElse(Direction.NORTH);
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockItemUseContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return ActionResultType.sidedSuccess(world.isClientSide());
final BlockState new_state = held_block.getStateForPlacement(ctx);
if(new_state == null) return ActionResultType.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return ActionResultType.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return ActionResultType.sidedSuccess(world.isClientSide());
}
}
/*
* @file EdStraightPoleBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Smaller (cutout) block with a defined facing.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Inventories;
import javax.annotation.Nullable;
import java.util.Arrays;
public class EdStraightPoleBlock extends StandardBlocks.DirectedWaterLoggable
{
private final EdStraightPoleBlock default_pole;
public EdStraightPoleBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB, @Nullable EdStraightPoleBlock defaultPole)
{ super(config, builder, unrotatedAABB); default_pole=(defaultPole==null) ? (this) : (defaultPole); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
Direction facing = context.getClickedFace();
BlockState state = super.getStateForPlacement(context).setValue(FACING, facing);
if((config & DecorBlock.CFG_FLIP_PLACEMENT_IF_SAME) != 0) {
Level world = context.getLevel();
BlockPos pos = context.getClickedPos();
if(world.getBlockState(pos.relative(facing.getOpposite())).getBlock() instanceof EdStraightPoleBlock) {
state = state.setValue(FACING, state.getValue(FACING).getOpposite());
}
}
return state;
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if((hit.getDirection().getAxis() == state.getValue(FACING).getAxis())) return InteractionResult.PASS;
final ItemStack held_stack = player.getItemInHand(hand);
if((held_stack.isEmpty()) || (!(held_stack.getItem() instanceof BlockItem))) return InteractionResult.PASS;
if(!(((BlockItem)(held_stack.getItem())).getBlock() instanceof EdStraightPoleBlock)) return InteractionResult.PASS;
if(held_stack.getItem() != default_pole.asItem()) return InteractionResult.sidedSuccess(world.isClientSide());
final Block held_block = ((BlockItem)(held_stack.getItem())).getBlock();
final Direction block_direction = state.getValue(FACING);
final Vec3 block_vec = Vec3.atLowerCornerOf(state.getValue(FACING).getNormal());
final double colinearity = 1.0-block_vec.cross(player.getLookAngle()).length();
final Direction placement_direction = Arrays.stream(Direction.orderedByNearest(player)).filter(d->d.getAxis()==block_direction.getAxis()).findFirst().orElse(Direction.NORTH);
final BlockPos adjacent_pos = pos.relative(placement_direction);
final BlockState adjacent = world.getBlockState(adjacent_pos);
final BlockPlaceContext ctx = new DirectionalPlaceContext(world, adjacent_pos, placement_direction, player.getItemInHand(hand), placement_direction.getOpposite());
if(!adjacent.canBeReplaced(ctx)) return InteractionResult.sidedSuccess(world.isClientSide());
final BlockState new_state = held_block.getStateForPlacement(ctx);
if(new_state == null) return InteractionResult.FAIL;
if(!world.setBlock(adjacent_pos, new_state, 1|2)) return InteractionResult.FAIL;
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
if(!player.isCreative()) {
held_stack.shrink(1);
Inventories.setItemInPlayerHand(player, hand, held_stack);
}
return InteractionResult.sidedSuccess(world.isClientSide());
}
}

View file

@ -1,339 +1,335 @@
/*
* @file EdTestBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Creative mod testing block
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.Blocks;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.Items;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.World;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.Hand;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.registries.ForgeRegistries;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.detail.*;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdTestBlock
{
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TestBlock extends DecorBlock.Directed implements Auxiliaries.IExperimentalFeature, IDecorBlock
{
public TestBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return VoxelShapes.block(); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new TestTileEntity(); }
@Override
public boolean canConnectRedstone(BlockState state, IBlockReader world, BlockPos pos, @Nullable Direction side)
{ return true; }
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{ return Collections.singletonList(new ItemStack(this)); }
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(!(te instanceof TestTileEntity)) return ActionResultType.FAIL;
return ((TestTileEntity)te).activated(player, hand, hit) ? ActionResultType.CONSUME : ActionResultType.PASS;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TestTileEntity extends TileEntity implements ITickableTileEntity
{
private final RfEnergy.Battery battery_;
private final LazyOptional<IEnergyStorage> energy_handler_;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
private final Inventories.StorageInventory inventory_;
private final LazyOptional<IItemHandler> item_handler_;
private int tick_timer = 0;
private int rf_fed_avg = 0;
private int rf_fed_total = 0;
private int rf_fed_acc = 0;
private int rf_received_avg = 0;
private int rf_received_total = 0;
private int liq_filled_avg = 0;
private int liq_filled_total = 0;
private int liq_filled_acc = 0;
private int liq_received_avg = 0;
private int liq_received_total = 0;
private int items_inserted_total = 0;
private int items_received_total = 0;
private int rf_feed_setting = 4096;
private FluidStack liq_fill_stack = new FluidStack(Fluids.WATER, 128);
private ItemStack insertion_item = ItemStack.EMPTY;
private Direction block_facing = Direction.NORTH;
private boolean paused = false;
public TestTileEntity()
{ this(ModContent.TET_TEST_BLOCK); }
public TestTileEntity(TileEntityType<?> te_type)
{
super(te_type);
battery_ = new RfEnergy.Battery((int)1e9, (int)1e9, 0, 0);
energy_handler_ = battery_.createEnergyHandler();
tank_ = new Fluidics.Tank((int)1e9);
fluid_handler_ = tank_.createFluidHandler();
inventory_ = new Inventories.StorageInventory(this, 1);
item_handler_ = Inventories.MappedItemHandler.createInsertionHandler(inventory_);
}
@Override
public void load(BlockState state, CompoundNBT nbt)
{
super.load(state, nbt);
tank_.load(nbt);
battery_.load(nbt);
rf_fed_avg = nbt.getInt("rf_fed_avg");
rf_fed_total = nbt.getInt("rf_fed_total");
rf_fed_acc = nbt.getInt("rf_fed_acc");
rf_received_avg = nbt.getInt("rf_received_avg");
rf_received_total = nbt.getInt("rf_received_total");
liq_filled_avg = nbt.getInt("liq_filled_avg");
liq_filled_total = nbt.getInt("liq_filled_total");
liq_filled_acc = nbt.getInt("liq_filled_acc");
liq_received_avg = nbt.getInt("liq_received_avg");
liq_received_total = nbt.getInt("liq_received_total");
rf_feed_setting = nbt.getInt("rf_feed_setting");
items_received_total = nbt.getInt("items_received_total");
items_inserted_total = nbt.getInt("items_inserted_total");
if(nbt.contains("liq_fill_stack")) liq_fill_stack = FluidStack.loadFluidStackFromNBT(nbt.getCompound("liq_fill_stack"));
if(nbt.contains("insertion_item")) insertion_item = ItemStack.of(nbt.getCompound("insertion_item"));
}
@Override
public CompoundNBT save(CompoundNBT nbt)
{
super.save(nbt);
tank_.save(nbt);
battery_.save(nbt);
nbt.putInt("rf_fed_avg", rf_fed_avg);
nbt.putInt("rf_fed_total", rf_fed_total);
nbt.putInt("rf_fed_acc", rf_fed_acc);
nbt.putInt("rf_received_avg", rf_received_avg);
nbt.putInt("rf_received_total", rf_received_total);
nbt.putInt("liq_filled_avg", liq_filled_avg);
nbt.putInt("liq_filled_total", liq_filled_total);
nbt.putInt("liq_filled_acc", liq_filled_acc);
nbt.putInt("liq_received_avg", liq_received_avg);
nbt.putInt("liq_received_total", liq_received_total);
nbt.putInt("rf_feed_setting", rf_feed_setting);
nbt.putInt("items_received_total", items_received_total);
nbt.putInt("items_inserted_total", items_inserted_total);
if(!liq_fill_stack.isEmpty()) nbt.put("liq_fill_stack", liq_fill_stack.writeToNBT(new CompoundNBT()));
if(!insertion_item.isEmpty()) nbt.put("insertion_item", insertion_item.save(new CompoundNBT()));
return nbt;
}
private FluidStack getFillFluid(ItemStack stack)
{
// intentionally not item fluid handler, only specific items.
if(stack.getItem() == Items.WATER_BUCKET) return new FluidStack(Fluids.WATER, 1000);
if(stack.getItem() == Items.LAVA_BUCKET) return new FluidStack(Fluids.LAVA, 1000);
return FluidStack.EMPTY;
}
private ItemStack getRandomItemstack()
{
final int n = (int)Math.floor(Math.random() * ForgeRegistries.ITEMS.getValues().size());
ItemStack stack = new ItemStack(ForgeRegistries.ITEMS.getValues().stream().skip(n).findAny().orElse(Items.COBBLESTONE));
stack.setCount((int)Math.floor(Math.random() * stack.getMaxStackSize()));
return stack;
}
public boolean activated(PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) {
ArrayList<String> msgs = new ArrayList<>();
if(rf_fed_avg > 0) msgs.add("-" + rf_fed_avg + "rf/t");
if(rf_fed_total > 0) msgs.add("-" + rf_fed_total + "rf");
if(rf_received_avg > 0) msgs.add("+" + rf_received_avg + "rf/t");
if(rf_received_total > 0) msgs.add("+" + rf_received_total + "rf");
if(liq_filled_avg > 0) msgs.add("-" + liq_filled_avg + "mb/t");
if(liq_filled_total > 0) msgs.add("-" + liq_filled_total + "mb");
if(liq_received_avg > 0) msgs.add("+" + liq_received_avg + "mb/t");
if(liq_received_total > 0) msgs.add("+" + liq_received_total + "mb");
if(items_received_total > 0) msgs.add("+" + items_received_total + "items");
if(items_inserted_total > 0) msgs.add("-" + items_inserted_total + "items");
if(msgs.isEmpty()) msgs.add("Nothing transferred yet.");
Overlay.show(player, new StringTextComponent(String.join(" | ", msgs)), 1000);
return true;
} else if(paused) {
if(!getFillFluid(held).isEmpty()) {
FluidStack fs = getFillFluid(held);
if(liq_fill_stack.isEmpty() || !liq_fill_stack.isFluidEqual(fs)) {
fs.setAmount(128);
liq_fill_stack = fs;
} else {
int amount = liq_fill_stack.getAmount() * 2;
if(amount > 4096) amount = 16;
liq_fill_stack.setAmount(amount);
}
if(liq_fill_stack.isEmpty()) {
Overlay.show(player, new StringTextComponent("Fluid fill: none"), 1000);
} else {
Overlay.show(player, new StringTextComponent("Fluid fill: " + liq_fill_stack.getAmount() + "mb/t of " + liq_fill_stack.getFluid().getRegistryName()), 1000);
}
} else if(held.getItem() == Items.REDSTONE) {
rf_feed_setting = (rf_feed_setting<<1) & 0x00fffff0;
if(rf_feed_setting == 0) rf_feed_setting = 0x10;
Overlay.show(player, new StringTextComponent("RF feed rate: " + rf_feed_setting + "rf/t"), 1000);
} else {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
insertion_item = held.copy();
Overlay.show(player, new StringTextComponent("Insertion item: " + (insertion_item.getItem()==Items.LEVER ? "random" : insertion_item.toString()) + "/s"), 1000);
}
}
return true;
} else {
return false;
}
}
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if((!paused) && (facing != block_facing)) {
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
if(capability ==CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
}
return super.getCapability(capability, facing);
}
@Override
public void tick()
{
if(level.isClientSide()) return;
block_facing = getBlockState().getValue(TestBlock.FACING);
paused = level.hasNeighborSignal(getBlockPos());
if(!paused) {
boolean dirty = false;
{
int p = RfEnergy.feed(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), rf_feed_setting);
rf_fed_acc += p;
dirty |= p>0;
}
if(!liq_fill_stack.isEmpty()) {
int f = Fluidics.fill(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), liq_fill_stack);
liq_filled_acc += f;
dirty |= f>0;
}
if(!inventory_.isEmpty()) {
int i = inventory_.getItem(0).getCount();
items_received_total += i;
inventory_.clearContent();
dirty |= i>0;
}
if((tick_timer == 1) && (!insertion_item.isEmpty())) {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
ItemStack stack = (insertion_item.getItem()==Items.LEVER) ? getRandomItemstack() : insertion_item.copy();
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
ItemStack remaining = Inventories.insert(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), stack, false);
int n = stack.getCount() - remaining.getCount();
items_inserted_total += n;
dirty |= n>0;
}
}
if(dirty) {
setChanged();
}
}
if(--tick_timer <= 0) {
tick_timer = 20;
rf_fed_avg = rf_fed_acc/20;
rf_fed_total += rf_fed_acc;
rf_fed_acc = 0;
rf_received_avg = battery_.getEnergyStored()/20;
rf_received_total += battery_.getEnergyStored();
battery_.clear();
liq_received_avg = tank_.getFluidAmount();
liq_received_total += tank_.getFluidAmount();
tank_.clear();
liq_filled_avg = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_acc/20);
liq_filled_total = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_total+liq_filled_acc);
liq_filled_acc = 0;
}
}
}
}
/*
* @file EdTestBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Creative mod testing block
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.registries.ForgeRegistries;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.*;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EdTestBlock
{
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TestBlock extends StandardBlocks.Directed implements StandardEntityBlocks.IStandardEntityBlock<TestTileEntity>, Auxiliaries.IExperimentalFeature
{
public TestBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
@Nullable
public BlockEntityType<EdTestBlock.TestTileEntity> getBlockEntityType()
{ return ModContent.TET_TEST_BLOCK; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return Shapes.block(); }
@Override
@SuppressWarnings("deprecation") // public boolean canConnectRedstone(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { return true; }
public boolean isSignalSource(BlockState p_60571_)
{ return true; }
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{ return Collections.singletonList(new ItemStack(this)); }
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof TestTileEntity)) return InteractionResult.FAIL;
return ((TestTileEntity)te).activated(player, hand, hit) ? InteractionResult.CONSUME : InteractionResult.PASS;
}
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TestTileEntity extends StandardEntityBlocks.StandardBlockEntity
{
private final RfEnergy.Battery battery_;
private final LazyOptional<IEnergyStorage> energy_handler_;
private final Fluidics.Tank tank_;
private final LazyOptional<IFluidHandler> fluid_handler_;
private final Inventories.StorageInventory inventory_;
private final LazyOptional<IItemHandler> item_handler_;
private int tick_timer = 0;
private int rf_fed_avg = 0;
private int rf_fed_total = 0;
private int rf_fed_acc = 0;
private int rf_received_avg = 0;
private int rf_received_total = 0;
private int liq_filled_avg = 0;
private int liq_filled_total = 0;
private int liq_filled_acc = 0;
private int liq_received_avg = 0;
private int liq_received_total = 0;
private int items_inserted_total = 0;
private int items_received_total = 0;
private int rf_feed_setting = 4096;
private FluidStack liq_fill_stack = new FluidStack(Fluids.WATER, 128);
private ItemStack insertion_item = ItemStack.EMPTY;
private Direction block_facing = Direction.NORTH;
private boolean paused = false;
public TestTileEntity(BlockPos pos, BlockState state)
{
super(ModContent.TET_TEST_BLOCK, pos, state);
battery_ = new RfEnergy.Battery((int)1e9, (int)1e9, 0, 0);
energy_handler_ = battery_.createEnergyHandler();
tank_ = new Fluidics.Tank((int)1e9);
fluid_handler_ = tank_.createFluidHandler();
inventory_ = new Inventories.StorageInventory(this, 1);
item_handler_ = Inventories.MappedItemHandler.createInsertionHandler(inventory_);
}
@Override
public void load(CompoundTag nbt)
{
super.load(nbt);
tank_.load(nbt);
battery_.load(nbt);
rf_fed_avg = nbt.getInt("rf_fed_avg");
rf_fed_total = nbt.getInt("rf_fed_total");
rf_fed_acc = nbt.getInt("rf_fed_acc");
rf_received_avg = nbt.getInt("rf_received_avg");
rf_received_total = nbt.getInt("rf_received_total");
liq_filled_avg = nbt.getInt("liq_filled_avg");
liq_filled_total = nbt.getInt("liq_filled_total");
liq_filled_acc = nbt.getInt("liq_filled_acc");
liq_received_avg = nbt.getInt("liq_received_avg");
liq_received_total = nbt.getInt("liq_received_total");
rf_feed_setting = nbt.getInt("rf_feed_setting");
items_received_total = nbt.getInt("items_received_total");
items_inserted_total = nbt.getInt("items_inserted_total");
if(nbt.contains("liq_fill_stack")) liq_fill_stack = FluidStack.loadFluidStackFromNBT(nbt.getCompound("liq_fill_stack"));
if(nbt.contains("insertion_item")) insertion_item = ItemStack.of(nbt.getCompound("insertion_item"));
}
@Override
public CompoundTag save(CompoundTag nbt)
{
super.save(nbt);
tank_.save(nbt);
battery_.save(nbt);
nbt.putInt("rf_fed_avg", rf_fed_avg);
nbt.putInt("rf_fed_total", rf_fed_total);
nbt.putInt("rf_fed_acc", rf_fed_acc);
nbt.putInt("rf_received_avg", rf_received_avg);
nbt.putInt("rf_received_total", rf_received_total);
nbt.putInt("liq_filled_avg", liq_filled_avg);
nbt.putInt("liq_filled_total", liq_filled_total);
nbt.putInt("liq_filled_acc", liq_filled_acc);
nbt.putInt("liq_received_avg", liq_received_avg);
nbt.putInt("liq_received_total", liq_received_total);
nbt.putInt("rf_feed_setting", rf_feed_setting);
nbt.putInt("items_received_total", items_received_total);
nbt.putInt("items_inserted_total", items_inserted_total);
if(!liq_fill_stack.isEmpty()) nbt.put("liq_fill_stack", liq_fill_stack.writeToNBT(new CompoundTag()));
if(!insertion_item.isEmpty()) nbt.put("insertion_item", insertion_item.save(new CompoundTag()));
return nbt;
}
private FluidStack getFillFluid(ItemStack stack)
{
// intentionally not item fluid handler, only specific items.
if(stack.getItem() == Items.WATER_BUCKET) return new FluidStack(Fluids.WATER, 1000);
if(stack.getItem() == Items.LAVA_BUCKET) return new FluidStack(Fluids.LAVA, 1000);
return FluidStack.EMPTY;
}
private ItemStack getRandomItemstack()
{
final int n = (int)Math.floor(Math.random() * ForgeRegistries.ITEMS.getValues().size());
ItemStack stack = new ItemStack(ForgeRegistries.ITEMS.getValues().stream().skip(n).findAny().orElse(Items.COBBLESTONE));
stack.setCount((int)Math.floor(Math.random() * stack.getMaxStackSize()));
return stack;
}
public boolean activated(Player player, InteractionHand hand, BlockHitResult hit)
{
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) {
ArrayList<String> msgs = new ArrayList<>();
if(rf_fed_avg > 0) msgs.add("-" + rf_fed_avg + "rf/t");
if(rf_fed_total > 0) msgs.add("-" + rf_fed_total + "rf");
if(rf_received_avg > 0) msgs.add("+" + rf_received_avg + "rf/t");
if(rf_received_total > 0) msgs.add("+" + rf_received_total + "rf");
if(liq_filled_avg > 0) msgs.add("-" + liq_filled_avg + "mb/t");
if(liq_filled_total > 0) msgs.add("-" + liq_filled_total + "mb");
if(liq_received_avg > 0) msgs.add("+" + liq_received_avg + "mb/t");
if(liq_received_total > 0) msgs.add("+" + liq_received_total + "mb");
if(items_received_total > 0) msgs.add("+" + items_received_total + "items");
if(items_inserted_total > 0) msgs.add("-" + items_inserted_total + "items");
if(msgs.isEmpty()) msgs.add("Nothing transferred yet.");
Overlay.show(player, new TextComponent(String.join(" | ", msgs)), 1000);
return true;
} else if(paused) {
if(!getFillFluid(held).isEmpty()) {
FluidStack fs = getFillFluid(held);
if(liq_fill_stack.isEmpty() || !liq_fill_stack.isFluidEqual(fs)) {
fs.setAmount(128);
liq_fill_stack = fs;
} else {
int amount = liq_fill_stack.getAmount() * 2;
if(amount > 4096) amount = 16;
liq_fill_stack.setAmount(amount);
}
if(liq_fill_stack.isEmpty()) {
Overlay.show(player, new TextComponent("Fluid fill: none"), 1000);
} else {
Overlay.show(player, new TextComponent("Fluid fill: " + liq_fill_stack.getAmount() + "mb/t of " + liq_fill_stack.getFluid().getRegistryName()), 1000);
}
} else if(held.getItem() == Items.REDSTONE) {
rf_feed_setting = (rf_feed_setting<<1) & 0x00fffff0;
if(rf_feed_setting == 0) rf_feed_setting = 0x10;
Overlay.show(player, new TextComponent("RF feed rate: " + rf_feed_setting + "rf/t"), 1000);
} else {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
insertion_item = held.copy();
Overlay.show(player, new TextComponent("Insertion item: " + (insertion_item.getItem()==Items.LEVER ? "random" : insertion_item.toString()) + "/s"), 1000);
}
}
return true;
} else {
return false;
}
}
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
fluid_handler_.invalidate();
item_handler_.invalidate();
}
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if((!paused) && (facing != block_facing)) {
if(capability == CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY) return fluid_handler_.cast();
if(capability == CapabilityEnergy.ENERGY) return energy_handler_.cast();
if(capability ==CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) return item_handler_.cast();
}
return super.getCapability(capability, facing);
}
@Override
public void tick()
{
if(level.isClientSide()) return;
block_facing = getBlockState().getValue(TestBlock.FACING);
paused = level.hasNeighborSignal(getBlockPos());
if(!paused) {
boolean dirty = false;
{
int p = RfEnergy.feed(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), rf_feed_setting);
rf_fed_acc += p;
dirty |= p>0;
}
if(!liq_fill_stack.isEmpty()) {
int f = Fluidics.fill(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), liq_fill_stack);
liq_filled_acc += f;
dirty |= f>0;
}
if(!inventory_.isEmpty()) {
int i = inventory_.getItem(0).getCount();
items_received_total += i;
inventory_.clearContent();
dirty |= i>0;
}
if((tick_timer == 1) && (!insertion_item.isEmpty())) {
BlockState adjacent_state = level.getBlockState(worldPosition.relative(block_facing));
ItemStack stack = (insertion_item.getItem()==Items.LEVER) ? getRandomItemstack() : insertion_item.copy();
if(adjacent_state.getBlock()==Blocks.HOPPER || adjacent_state.getBlock()==ModContent.FACTORY_HOPPER) {
ItemStack remaining = Inventories.insert(getLevel(), getBlockPos().relative(block_facing), block_facing.getOpposite(), stack, false);
int n = stack.getCount() - remaining.getCount();
items_inserted_total += n;
dirty |= n>0;
}
}
if(dirty) {
setChanged();
}
}
if(--tick_timer <= 0) {
tick_timer = 20;
rf_fed_avg = rf_fed_acc/20;
rf_fed_total += rf_fed_acc;
rf_fed_acc = 0;
rf_received_avg = battery_.getEnergyStored()/20;
rf_received_total += battery_.getEnergyStored();
battery_.clear();
liq_received_avg = tank_.getFluidAmount();
liq_received_total += tank_.getFluidAmount();
tank_.clear();
liq_filled_avg = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_acc/20);
liq_filled_total = (liq_fill_stack.isEmpty()) ? 0 : (liq_filled_total+liq_filled_acc);
liq_filled_acc = 0;
}
}
}
}

View file

@ -1,274 +1,270 @@
/*
* @file EdTreeCutter.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Tree Cutter
*/
package wile.engineersdecor.blocks;
import net.minecraft.world.IWorldReader;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.particles.ParticleTypes;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.detail.TreeCutting;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.Random;
public class EdTreeCutter
{
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{ TreeCutterTileEntity.on_config(boost_energy_per_tick, cutting_time_seconds,power_required); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterBlock extends DecorBlock.Horizontal implements IDecorBlock
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public TreeCutterBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@Override
public boolean hasTileEntity(BlockState state)
{ return true; }
@Override
@Nullable
public TileEntity createTileEntity(BlockState state, IBlockReader world)
{ return new TreeCutterTileEntity(); }
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, World world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
final double rv = rnd.nextDouble();
if(rv > 0.8) return;
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch(state.getValue(HORIZONTAL_FACING)) {
case WEST: world.addParticle(ParticleTypes.SMOKE, x-xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case EAST: world.addParticle(ParticleTypes.SMOKE, x+xc, yr, z+xr, 0.0, 0.0, 0.0); break;
case NORTH: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z-xc, 0.0, 0.0, 0.0); break;
default: world.addParticle(ParticleTypes.SMOKE, x+xr, yr, z+xc, 0.0, 0.0, 0.0); break;
}
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(world.isClientSide()) return ActionResultType.SUCCESS;
TileEntity te = world.getBlockEntity(pos);
if(te instanceof TreeCutterTileEntity) ((TreeCutterTileEntity)te).state_message(player);
return ActionResultType.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, IWorldReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterTileEntity extends TileEntity implements ITickableTileEntity, IEnergyStorage
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
public static final int BOOST_FACTOR = 6;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_CUTTING_TIME_NEEDED = 60; // 60 secs, so that people don't come to the bright idea to carry one with them.
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = DEFAULT_BOOST_ENERGY * 20;
private static int cutting_time_needed = 20 * DEFAULT_CUTTING_TIME_NEEDED;
private static boolean requires_power = false;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int energy_;
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{
boost_energy_consumption = TICK_INTERVAL * MathHelper.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 10000);
cutting_time_needed = 20 * MathHelper.clamp(cutting_time_seconds, 10, 240);
requires_power = power_required;
ModConfig.log("Config tree cutter: energy consumption:" + (boost_energy_consumption/TICK_INTERVAL) + "rf/t" + (requires_power?" (power required for operation) ":"") + ", cutting time:" + cutting_time_needed + "t." );
}
public TreeCutterTileEntity()
{ super(ModContent.TET_SMALL_TREE_CUTTER); }
public TreeCutterTileEntity(TileEntityType<?> te_type)
{ super(te_type); }
public void readnbt(CompoundNBT nbt)
{ energy_ = nbt.getInt("energy"); }
private void writenbt(CompoundNBT nbt)
{ nbt.putInt("energy", energy_); }
public void state_message(PlayerEntity player)
{
String progress = "0";
if((active_timer_ > 0) && (cutting_time_needed > 0) && (active_timer_ > 0)) {
progress = Integer.toString((int)MathHelper.clamp((((double)proc_time_elapsed_) / ((double)cutting_time_needed) * 100), 0, 100));
}
String soc = Integer.toString(MathHelper.clamp((energy_*100/energy_max),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_tree_cutter.status", new Object[]{soc, energy_max, progress, (cutting_time_needed/20) }));
}
// TileEntity ------------------------------------------------------------------------------
@Override
public void load(BlockState state, CompoundNBT nbt)
{ super.load(state, nbt); readnbt(nbt); }
@Override
public CompoundNBT save(CompoundNBT nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> (IEnergyStorage)this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return boost_energy_consumption*2; }
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
maxReceive = MathHelper.clamp(maxReceive, 0, Math.max((energy_max) - energy_, 0));
if(!simulate) energy_ += maxReceive;
return maxReceive;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(--tick_timer_ > 0) return;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof TreeCutterBlock)) { tick_timer_ = TICK_INTERVAL; return; }
if(level.isClientSide) {
if(!device_state.getValue(TreeCutterBlock.ACTIVE)) {
tick_timer_ = TICK_INTERVAL;
} else {
tick_timer_ = 1;
level.playLocalSound(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), SoundEvents.WOOD_HIT, SoundCategory.BLOCKS, 0.1f, 1.0f, false);
}
} else {
tick_timer_ = TICK_INTERVAL;
final BlockPos tree_pos = worldPosition.relative(device_state.getValue(TreeCutterBlock.HORIZONTAL_FACING));
final BlockState tree_state = level.getBlockState(tree_pos);
if(!TreeCutting.canChop(tree_state) || (level.hasNeighborSignal(worldPosition))) {
if(device_state.getValue(TreeCutterBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
active_timer_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
proc_time_elapsed_ += TICK_INTERVAL;
if(energy_ >= boost_energy_consumption) {
energy_ -= boost_energy_consumption;
proc_time_elapsed_ += TICK_INTERVAL*BOOST_FACTOR;
active_timer_ = 2;
} else if(!requires_power) {
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= cutting_time_needed) {
proc_time_elapsed_ = 0;
TreeCutting.chopTree(level, tree_state, tree_pos, 512, false);
level.playSound(null, worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), SoundEvents.WOOD_BREAK, SoundCategory.BLOCKS, 1.0f, 1.0f);
active = false;
}
if(device_state.getValue(TreeCutterBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, active), 1|2);
}
}
}
}
}
/*
* @file EdTreeCutter.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Small Tree Cutter
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.detail.TreeCutting;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.blocks.StandardEntityBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.Overlay;
import javax.annotation.Nullable;
import java.util.Random;
public class EdTreeCutter
{
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{ TreeCutterTileEntity.on_config(boost_energy_per_tick, cutting_time_seconds,power_required); }
//--------------------------------------------------------------------------------------------------------------------
// Block
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterBlock extends StandardBlocks.Horizontal implements StandardEntityBlocks.IStandardEntityBlock<TreeCutterTileEntity>
{
public static final BooleanProperty ACTIVE = BooleanProperty.create("active");
public TreeCutterBlock(long config, BlockBehaviour.Properties builder, final AABB[] unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
@Nullable
public BlockEntityType<EdTreeCutter.TreeCutterTileEntity> getBlockEntityType()
{ return ModContent.TET_SMALL_TREE_CUTTER; }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(ACTIVE); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{ return super.getStateForPlacement(context).setValue(ACTIVE, false); }
@OnlyIn(Dist.CLIENT)
public void animateTick(BlockState state, Level world, BlockPos pos, Random rnd)
{
if((state.getBlock()!=this) || (!state.getValue(ACTIVE))) return;
// Sound
if(true || (world.getGameTime() & 0x1) == 0) {
world.playLocalSound(pos.getX(), pos.getY(), pos.getZ(), SoundEvents.WOOD_HIT, SoundSource.BLOCKS, 0.1f, 1.0f, false);
}
// Particles
{
final double rv = rnd.nextDouble();
if(rv < 0.8) {
final double x=0.5+pos.getX(), y=0.5+pos.getY(), z=0.5+pos.getZ();
final double xc=0.52, xr=rnd.nextDouble()*0.4-0.2, yr=(y-0.3+rnd.nextDouble()*0.2);
switch(state.getValue(HORIZONTAL_FACING)) {
case WEST -> world.addParticle(ParticleTypes.SMOKE, x - xc, yr, z + xr, 0.0, 0.0, 0.0);
case EAST -> world.addParticle(ParticleTypes.SMOKE, x + xc, yr, z + xr, 0.0, 0.0, 0.0);
case NORTH -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z - xc, 0.0, 0.0, 0.0);
default -> world.addParticle(ParticleTypes.SMOKE, x + xr, yr, z + xc, 0.0, 0.0, 0.0);
}
}
}
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
BlockEntity te = world.getBlockEntity(pos);
if(te instanceof TreeCutterTileEntity) ((TreeCutterTileEntity)te).state_message(player);
return InteractionResult.CONSUME;
}
@Override
public boolean shouldCheckWeakPower(BlockState state, LevelReader world, BlockPos pos, Direction side)
{ return false; }
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity
//--------------------------------------------------------------------------------------------------------------------
public static class TreeCutterTileEntity extends StandardEntityBlocks.StandardBlockEntity implements IEnergyStorage
{
public static final int IDLE_TICK_INTERVAL = 40;
public static final int TICK_INTERVAL = 5;
public static final int BOOST_FACTOR = 6;
public static final int DEFAULT_BOOST_ENERGY = 64;
public static final int DEFAULT_CUTTING_TIME_NEEDED = 60; // 60 secs, so that people don't come to the bright idea to carry one with them.
private static int boost_energy_consumption = DEFAULT_BOOST_ENERGY;
private static int energy_max = DEFAULT_BOOST_ENERGY * 20;
private static int cutting_time_needed = 20 * DEFAULT_CUTTING_TIME_NEEDED;
private static boolean requires_power = false;
private int tick_timer_;
private int active_timer_;
private int proc_time_elapsed_;
private int energy_;
public static void on_config(int boost_energy_per_tick, int cutting_time_seconds, boolean power_required)
{
boost_energy_consumption = TICK_INTERVAL * Mth.clamp(boost_energy_per_tick, 4, 4096);
energy_max = Math.max(boost_energy_consumption * 10, 10000);
cutting_time_needed = 20 * Mth.clamp(cutting_time_seconds, 10, 240);
requires_power = power_required;
ModConfig.log("Config tree cutter: energy consumption:" + (boost_energy_consumption/TICK_INTERVAL) + "rf/t" + (requires_power?" (power required for operation) ":"") + ", cutting time:" + cutting_time_needed + "t." );
}
public TreeCutterTileEntity(BlockPos pos, BlockState state)
{ super(ModContent.TET_SMALL_TREE_CUTTER, pos, state); }
public void readnbt(CompoundTag nbt)
{ energy_ = nbt.getInt("energy"); }
private void writenbt(CompoundTag nbt)
{ nbt.putInt("energy", energy_); }
public void state_message(Player player)
{
String progress = "0";
if((active_timer_ > 0) && (cutting_time_needed > 0) && (active_timer_ > 0)) {
progress = Integer.toString((int)Mth.clamp((((double)proc_time_elapsed_) / ((double)cutting_time_needed) * 100), 0, 100));
}
String soc = Integer.toString(Mth.clamp((energy_*100/energy_max),0,100));
Overlay.show(player, Auxiliaries.localizable("block.engineersdecor.small_tree_cutter.status", soc, energy_max, progress, (cutting_time_needed/20)));
}
// BlockEntity ------------------------------------------------------------------------------
@Override
public void load(CompoundTag nbt)
{ super.load(nbt); readnbt(nbt); }
@Override
public CompoundTag save(CompoundTag nbt)
{ super.save(nbt); writenbt(nbt); return nbt; }
@Override
public void setRemoved()
{
super.setRemoved();
energy_handler_.invalidate();
}
// IEnergyStorage ----------------------------------------------------------------------------
protected LazyOptional<IEnergyStorage> energy_handler_ = LazyOptional.of(() -> this);
@Override
public boolean canExtract()
{ return false; }
@Override
public boolean canReceive()
{ return true; }
@Override
public int getMaxEnergyStored()
{ return boost_energy_consumption*2; }
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{ return 0; }
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
maxReceive = Mth.clamp(maxReceive, 0, Math.max((energy_max) - energy_, 0));
if(!simulate) energy_ += maxReceive;
return maxReceive;
}
// Capability export ----------------------------------------------------------------------------
@Override
public <T> LazyOptional<T> getCapability(net.minecraftforge.common.capabilities.Capability<T> capability, @Nullable Direction facing)
{
if(capability== CapabilityEnergy.ENERGY) return energy_handler_.cast();
return super.getCapability(capability, facing);
}
// ITickable ------------------------------------------------------------------------------------
@Override
public void tick()
{
if(--tick_timer_ > 0) return;
tick_timer_ = TICK_INTERVAL;
final BlockState device_state = level.getBlockState(worldPosition);
if(!(device_state.getBlock() instanceof TreeCutterBlock)) return;
final BlockPos tree_pos = worldPosition.relative(device_state.getValue(TreeCutterBlock.HORIZONTAL_FACING));
final BlockState tree_state = level.getBlockState(tree_pos);
if(!TreeCutting.canChop(tree_state) || (level.hasNeighborSignal(worldPosition))) {
if(device_state.getValue(TreeCutterBlock.ACTIVE)) level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, false), 1|2);
proc_time_elapsed_ = 0;
active_timer_ = 0;
tick_timer_ = IDLE_TICK_INTERVAL;
return;
}
proc_time_elapsed_ += TICK_INTERVAL;
if(energy_ >= boost_energy_consumption) {
energy_ -= boost_energy_consumption;
proc_time_elapsed_ += TICK_INTERVAL*BOOST_FACTOR;
active_timer_ = 2;
} else if(!requires_power) {
active_timer_ = 1024;
} else if(active_timer_ > 0) {
--active_timer_;
}
boolean active = (active_timer_ > 0);
if(requires_power && !active) {
proc_time_elapsed_ = Math.max(0, proc_time_elapsed_ - 2*TICK_INTERVAL);
}
if(proc_time_elapsed_ >= cutting_time_needed) {
proc_time_elapsed_ = 0;
TreeCutting.chopTree(level, tree_state, tree_pos, 512, false);
level.playSound(null, worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), SoundEvents.WOOD_BREAK, SoundSource.BLOCKS, 1.0f, 1.0f);
active = false;
}
if(device_state.getValue(TreeCutterBlock.ACTIVE) != active) {
level.setBlock(worldPosition, device_state.setValue(TreeCutterBlock.ACTIVE, active), 1|2);
}
}
}
}

View file

@ -1,31 +1,35 @@
/*
* @file EdWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.*;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorldReader;
import wile.engineersdecor.libmc.blocks.VariantWallBlock;
public class EdWallBlock extends VariantWallBlock implements IDecorBlock
{
public EdWallBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{
if(facingState==null) return false;
if(super.attachesTo(facingState, world, facingPos, side)) return true;
if(facingState.getBlock() instanceof EdWindowBlock) return true;
if(facingState.getBlock() instanceof PaneBlock) return true;
return false;
}
}
/*
* @file EdWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.IronBarsBlock;
import net.minecraft.world.level.block.StainedGlassPaneBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import wile.engineersdecor.libmc.blocks.VariantWallBlock;
public class EdWallBlock extends VariantWallBlock
{
public EdWallBlock(long config, BlockBehaviour.Properties builder)
{ super(config, builder); }
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{
if(facingState==null) return false;
if(super.attachesTo(facingState, world, facingPos, side)) return true;
if(facingState.getBlock() instanceof EdWindowBlock) return true;
if(facingState.getBlock() instanceof IronBarsBlock) return true;
if(facingState.getBlock() instanceof StainedGlassPaneBlock) return true;
return false;
}
}

View file

@ -1,88 +1,93 @@
/*
* @file EdWindowBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Mod windows.
*/
package wile.engineersdecor.blocks;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.DirectionalPlaceContext;
import net.minecraft.util.*;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import javax.annotation.Nullable;
import java.util.Arrays;
public class EdWindowBlock extends DecorBlock.DirectedWaterLoggable implements IDecorBlock
{
public EdWindowBlock(long config, AbstractBlock.Properties builder, final AxisAlignedBB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.TRANSLUCENT; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
Direction facing = context.getHorizontalDirection();
if(Math.abs(context.getPlayer().getLookAngle().y) > 0.9) {
facing = context.getNearestLookingDirection();
} else {
for(Direction f: Direction.values()) {
BlockState st = context.getLevel().getBlockState(context.getClickedPos().relative(f));
if(st.getBlock() == this) {
facing = st.getValue(FACING);
break;
}
}
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
@Override
@SuppressWarnings("deprecation")
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return ActionResultType.PASS;
final Direction facing = state.getValue(FACING);
if(facing.getAxis() != hit.getDirection().getAxis()) return ActionResultType.PASS;
Arrays.stream(Direction.orderedByNearest(player))
.filter(d->d.getAxis() != facing.getAxis())
.filter(d->world.getBlockState(pos.relative(d)).canBeReplaced((new DirectionalPlaceContext(world, pos.relative(d), facing.getOpposite(), player.getItemInHand(hand), facing))))
.findFirst().ifPresent((d)->{
BlockState st = defaultBlockState()
.setValue(FACING, facing)
.setValue(WATERLOGGED,world.getBlockState(pos.relative(d)).getFluidState().getType()==Fluids.WATER);
world.setBlock(pos.relative(d), st, 1|2);
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundCategory.BLOCKS, 1f, 1f);
player.getItemInHand(hand).shrink(1);
}
);
return ActionResultType.sidedSuccess(world.isClientSide());
}
@Override
public boolean propagatesSkylightDown(BlockState state, IBlockReader reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return true; }
}
/*
* @file EdWindowBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Mod windows.
*/
package wile.engineersdecor.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.DirectionalPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import javax.annotation.Nullable;
import java.util.Arrays;
public class EdWindowBlock extends StandardBlocks.DirectedWaterLoggable
{
public EdWindowBlock(long config, BlockBehaviour.Properties builder, final AABB unrotatedAABB)
{ super(config, builder, unrotatedAABB); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return RenderTypeHint.TRANSLUCENT; }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
Direction facing = context.getHorizontalDirection();
if(Math.abs(context.getPlayer().getLookAngle().y) > 0.9) {
facing = context.getNearestLookingDirection();
} else {
for(Direction f: Direction.values()) {
BlockState st = context.getLevel().getBlockState(context.getClickedPos().relative(f));
if(st.getBlock() == this) {
facing = st.getValue(FACING);
break;
}
}
}
return super.getStateForPlacement(context).setValue(FACING, facing);
}
@Override
@SuppressWarnings("deprecation")
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{
if(player.getItemInHand(hand).getItem() != asItem()) return InteractionResult.PASS;
final Direction facing = state.getValue(FACING);
if(facing.getAxis() != hit.getDirection().getAxis()) return InteractionResult.PASS;
Arrays.stream(Direction.orderedByNearest(player))
.filter(d->d.getAxis() != facing.getAxis())
.filter(d->world.getBlockState(pos.relative(d)).canBeReplaced((new DirectionalPlaceContext(world, pos.relative(d), facing.getOpposite(), player.getItemInHand(hand), facing))))
.findFirst().ifPresent((d)->{
BlockState st = defaultBlockState()
.setValue(FACING, facing)
.setValue(WATERLOGGED,world.getBlockState(pos.relative(d)).getFluidState().getType()==Fluids.WATER);
world.setBlock(pos.relative(d), st, 1|2);
world.playSound(player, pos, SoundEvents.METAL_PLACE, SoundSource.BLOCKS, 1f, 1f);
player.getItemInHand(hand).shrink(1);
}
);
return InteractionResult.sidedSuccess(world.isClientSide());
}
@Override
public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos)
{ return true; }
@Override
@SuppressWarnings("deprecation")
public boolean useShapeForLightOcclusion(BlockState state)
{ return true; }
}

View file

@ -1,15 +0,0 @@
/*
* @file IDecorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Interface for tagging and common default behaviour.
*/
package wile.engineersdecor.blocks;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
public interface IDecorBlock extends StandardBlocks.IStandardBlock
{
}

View file

@ -1,22 +0,0 @@
/*
* @file ExtItems.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Object holder based item references.
*/
package wile.engineersdecor.detail;
import net.minecraft.item.Item;
import net.minecraftforge.registries.ObjectHolder;
public class ExternalObjects
{
@ObjectHolder("bottledmilk:milk_bottle_drinkable")
public static final Item BOTTLED_MILK_BOTTLE_DRINKLABLE = null;
public static final void onPostInit()
{}
}

View file

@ -1,165 +1,169 @@
/*
* @file ModTesrs.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Yet unstructured initial experiments with TESRs.
* May be structured after I know what I am doing there.
*/
package wile.engineersdecor.detail;
import net.minecraft.util.text.ITextComponent;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.blocks.EdCraftingTable;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.vector.*;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.mojang.blaze3d.matrix.MatrixStack;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableBlock;
import wile.engineersdecor.blocks.EdLabeledCrate;
public class ModRenderers
{
//--------------------------------------------------------------------------------------------------------------------
// InvisibleEntityRenderer
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class InvisibleEntityRenderer<T extends Entity> extends EntityRenderer<T>
{
private final Minecraft mc = Minecraft.getInstance();
public InvisibleEntityRenderer(EntityRendererManager renderManagerIn)
{ super(renderManagerIn); }
public void render(T entity, float entityYaw, float partialTicks, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight)
{}
public Vector3d getRenderOffset(T entity, float partialTicks)
{ return Vector3d.ZERO; }
@SuppressWarnings("deprecation")
public ResourceLocation getTextureLocation(T entity)
{ return AtlasTexture.LOCATION_BLOCKS; }
protected boolean shouldShowName(T entity)
{ return false; }
protected void renderNameTag(T entity, ITextComponent displayName, MatrixStack matrixStack, IRenderTypeBuffer buffer, int packedLight)
{}
}
//--------------------------------------------------------------------------------------------------------------------
// Crafting table
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class CraftingTableTer extends TileEntityRenderer<EdCraftingTable.CraftingTableTileEntity>
{
private static int tesr_error_counter = 4;
private static float scaler = 0.1f;
private static float gap = 0.19f;
private static float yrotations[] = {0, 90, 180, 270}; // [hdirection] S-W-N-E
private static float offsets[][][] = { // [hdirection][slotindex][xz]
{ {-1,-1},{+0,-1},{+1,-1}, {-1,+0},{+0,+0},{+1,+0}, {-1,+1},{+0,+1},{+1,+1} }, // S
{ {+1,-1},{+1,+0},{+1,+1}, {+0,-1},{+0,+0},{+0,+1}, {-1,-1},{-1,+0},{-1,+1} }, // W
{ {+1,+1},{+0,+1},{-1,+1}, {+1,+0},{+0,+0},{-1,+0}, {+1,-1},{+0,-1},{-1,-1} }, // N
{ {-1,+1},{-1,+0},{-1,-1}, {+0,+1},{+0,+0},{+0,-1}, {+1,+1},{+1,+0},{+1,-1} }, // E
};
public CraftingTableTer(TileEntityRendererDispatcher dispatcher)
{ super(dispatcher); }
@Override
@SuppressWarnings("deprecation")
public void render(final EdCraftingTable.CraftingTableTileEntity te, float unused1, MatrixStack mxs, IRenderTypeBuffer buf, int i5, int i6)
{
if(tesr_error_counter <= 0) return;
try {
final int di = MathHelper.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(CraftingTableBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
long posrnd = te.getBlockPos().asLong();
posrnd = (posrnd>>16)^(posrnd<<1);
for(int i=0; i<9; ++i) {
final ItemStack stack = te.mainInventory().getItem(i);
if(stack.isEmpty()) continue;
float prnd = ((float)(((Integer.rotateRight(stack.getItem().hashCode()^(int)posrnd,(stack.getCount()+i)&31)))&1023))/1024f;
float rndo = gap * ((prnd*0.1f)-0.05f);
float ox = gap * offsets[di][i][0], oz = gap * offsets[di][i][1];
float oy = 0.5f;
float ry = ((yrotations[di]+180) + ((prnd*60)-30)) % 360;
if(stack.isEmpty()) return;
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.XP.rotationDegrees(90.0f));
mxs.mulPose(Vector3f.ZP.rotationDegrees(ry));
mxs.translate(rndo, rndo, 0);
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType.FIXED, i5, i6, mxs, buf);
mxs.popPose();
}
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled because broken, exception was: " + e.getMessage());
ModEngineersDecor.logger().error(e.getStackTrace());
}
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Labeled Crate
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class DecorLabeledCrateTer extends TileEntityRenderer<EdLabeledCrate.LabeledCrateTileEntity>
{
private static int tesr_error_counter = 4;
private static float scaler = 0.35f;
double tr[][]= { // [hdirection=S-W-N-E][param]
{ +8.0/32, -8.0/32, +15.5/32, 180.0 }, // N
{ -15.5/32, -8.0/32, +8.0/32, 90.0 }, // E
{ -8.0/32, -8.0/32, -15.5/32, 0.0 }, // S param=tx,ty,tz,ry
{ +15.5/32, -8.0/32, -8.0/32, 270.0 }, // W
};
public DecorLabeledCrateTer(TileEntityRendererDispatcher dispatcher)
{ super(dispatcher); }
@Override
@SuppressWarnings("deprecation")
public void render(final EdLabeledCrate.LabeledCrateTileEntity te, float unused1, MatrixStack mxs, IRenderTypeBuffer buf, int i5, int i6)
{
if(tesr_error_counter<=0) return;
try {
final ItemStack stack = te.getItemFrameStack();
if(stack.isEmpty()) return;
final int di = MathHelper.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(EdLabeledCrate.LabeledCrateBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
double ox = tr[di][0], oy = tr[di][1], oz = tr[di][2];
float ry = (float)tr[di][3];
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.YP.rotationDegrees(ry));
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, net.minecraft.client.renderer.model.ItemCameraTransforms.TransformType.FIXED, i5, i6, mxs, buf);
mxs.popPose();
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled (because broken), exception was: " + e.getMessage());
}
}
}
}
}
/*
* @file ModTesrs.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Yet unstructured initial experiments with TESRs.
* May be structured after I know what I am doing there.
*/
package wile.engineersdecor.detail;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Vector3f;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.blocks.EdCraftingTable;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableBlock;
import wile.engineersdecor.blocks.EdLabeledCrate;
public class ModRenderers
{
//--------------------------------------------------------------------------------------------------------------------
// InvisibleEntityRenderer
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class InvisibleEntityRenderer<T extends Entity> extends EntityRenderer<T>
{
private final Minecraft mc = Minecraft.getInstance();
public InvisibleEntityRenderer(EntityRendererProvider.Context context)
{ super(context); }
public void render(T entity, float entityYaw, float partialTicks, PoseStack matrixStack, MultiBufferSource buffer, int packedLight)
{}
public Vec3 getRenderOffset(T entity, float partialTicks)
{ return Vec3.ZERO; }
@SuppressWarnings("deprecation")
public ResourceLocation getTextureLocation(T entity)
{ return TextureAtlas.LOCATION_BLOCKS; }
protected boolean shouldShowName(T entity)
{ return false; }
protected void renderNameTag(T entity, Component displayName, PoseStack matrixStack, MultiBufferSource buffer, int packedLight)
{}
}
//--------------------------------------------------------------------------------------------------------------------
// Crafting table
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class CraftingTableTer implements BlockEntityRenderer<EdCraftingTable.CraftingTableTileEntity>
{
private static int tesr_error_counter = 4;
private static final float scaler = 0.1f;
private static final float gap = 0.19f;
private static final float[] yrotations = {0, 90, 180, 270}; // [hdirection] S-W-N-E
private static final float[][][] offsets = { // [hdirection][slotindex][xz]
{ {-1,-1},{+0,-1},{+1,-1}, {-1,+0},{+0,+0},{+1,+0}, {-1,+1},{+0,+1},{+1,+1} }, // S
{ {+1,-1},{+1,+0},{+1,+1}, {+0,-1},{+0,+0},{+0,+1}, {-1,-1},{-1,+0},{-1,+1} }, // W
{ {+1,+1},{+0,+1},{-1,+1}, {+1,+0},{+0,+0},{-1,+0}, {+1,-1},{+0,-1},{-1,-1} }, // N
{ {-1,+1},{-1,+0},{-1,-1}, {+0,+1},{+0,+0},{+0,-1}, {+1,+1},{+1,+0},{+1,-1} }, // E
};
private final BlockEntityRendererProvider.Context renderer_;
public CraftingTableTer(BlockEntityRendererProvider.Context renderer)
{ this.renderer_ = renderer; }
@Override
@SuppressWarnings("deprecation")
public void render(final EdCraftingTable.CraftingTableTileEntity te, float unused1, PoseStack mxs, MultiBufferSource buf, int i5, int overlayTexture)
{
if(tesr_error_counter <= 0) return;
try {
final int di = Mth.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(CraftingTableBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
long posrnd = te.getBlockPos().asLong();
posrnd = (posrnd>>16)^(posrnd<<1);
for(int i=0; i<9; ++i) {
final ItemStack stack = te.mainInventory().getItem(i);
if(stack.isEmpty()) continue;
float prnd = ((float)(((Integer.rotateRight(stack.getItem().hashCode()^(int)posrnd,(stack.getCount()+i)&31)))&1023))/1024f;
float rndo = gap * ((prnd*0.1f)-0.05f);
float ox = gap * offsets[di][i][0], oz = gap * offsets[di][i][1];
float oy = 0.5f;
float ry = ((yrotations[di]+180) + ((prnd*60)-30)) % 360;
if(stack.isEmpty()) return;
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.XP.rotationDegrees(90.0f));
mxs.mulPose(Vector3f.ZP.rotationDegrees(ry));
mxs.translate(rndo, rndo, 0);
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, ItemTransforms.TransformType.FIXED, i5, overlayTexture, mxs, buf, 0);
mxs.popPose();
}
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled because broken, exception was: " + e.getMessage());
ModEngineersDecor.logger().error(e.getStackTrace());
}
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Labeled Crate
//--------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class DecorLabeledCrateTer implements BlockEntityRenderer<EdLabeledCrate.LabeledCrateTileEntity>
{
private static int tesr_error_counter = 4;
private static final float scaler = 0.35f;
private static final double[][] tr = { // [hdirection=S-W-N-E][param]
{ +8.0/32, -8.0/32, +15.5/32, 180.0 }, // N
{ -15.5/32, -8.0/32, +8.0/32, 90.0 }, // E
{ -8.0/32, -8.0/32, -15.5/32, 0.0 }, // S param=tx,ty,tz,ry
{ +15.5/32, -8.0/32, -8.0/32, 270.0 }, // W
};
private final BlockEntityRendererProvider.Context renderer_;
public DecorLabeledCrateTer(BlockEntityRendererProvider.Context renderer)
{ this.renderer_ = renderer; }
@Override
@SuppressWarnings("deprecation")
public void render(final EdLabeledCrate.LabeledCrateTileEntity te, float unused1, PoseStack mxs, MultiBufferSource buf, int i5, int overlayTexture)
{
if(tesr_error_counter<=0) return;
try {
final ItemStack stack = te.getItemFrameStack();
if(stack.isEmpty()) return;
final int di = Mth.clamp(te.getLevel().getBlockState(te.getBlockPos()).getValue(EdLabeledCrate.LabeledCrateBlock.HORIZONTAL_FACING).get2DDataValue(), 0, 3);
double ox = tr[di][0], oy = tr[di][1], oz = tr[di][2];
float ry = (float)tr[di][3];
mxs.pushPose();
mxs.translate(0.5+ox, 0.5+oy, 0.5+oz);
mxs.mulPose(Vector3f.YP.rotationDegrees(ry));
mxs.scale(scaler, scaler, scaler);
Minecraft.getInstance().getItemRenderer().renderStatic(stack, ItemTransforms.TransformType.FIXED, i5, overlayTexture, mxs, buf, 0);
mxs.popPose();
} catch(Throwable e) {
if(--tesr_error_counter<=0) {
ModEngineersDecor.logger().error("TER was disabled (because broken), exception was: " + e.getMessage());
}
}
}
}
}

View file

@ -1,178 +1,182 @@
/*
* @file TreeCutting.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple tree cutting algorithm.
*/
package wile.engineersdecor.detail;
import net.minecraft.block.*;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.ResourceLocation;
import wile.engineersdecor.ModEngineersDecor;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3i;
import net.minecraft.world.World;
import com.google.common.collect.ImmutableList;
import java.util.*;
public class TreeCutting
{
private static org.apache.logging.log4j.Logger LOGGER = ModEngineersDecor.logger();
public static boolean canChop(BlockState state)
{ return isLog(state); }
// -------------------------------------------------------------------------------------------------------------------
private static final List<Vector3i> hoffsets = ImmutableList.of(
new Vector3i( 1,0, 0), new Vector3i( 1,0, 1), new Vector3i( 0,0, 1),
new Vector3i(-1,0, 1), new Vector3i(-1,0, 0), new Vector3i(-1,0,-1),
new Vector3i( 0,0,-1), new Vector3i( 1,0,-1)
);
private static boolean isLog(BlockState state)
{ return (state.getBlock().is(BlockTags.LOGS)) || (state.getBlock().getTags().contains(new ResourceLocation("minecraft","logs"))); }
private static boolean isSameLog(BlockState a, BlockState b)
{ return (a.getBlock()==b.getBlock()); }
private static boolean isLeaves(BlockState state)
{
if(state.getBlock() instanceof LeavesBlock) return true;
if(state.getBlock().getTags().contains(new ResourceLocation("minecraft","leaves"))) return true;
return false;
}
private static List<BlockPos> findBlocksAround(final World world, final BlockPos centerPos, final BlockState leaf_type_state, final Set<BlockPos> checked, int recursion_left)
{
ArrayList<BlockPos> to_decay = new ArrayList<BlockPos>();
for(int y=-1; y<=1; ++y) {
final BlockPos layer = centerPos.offset(0,y,0);
for(Vector3i v:hoffsets) {
BlockPos pos = layer.offset(v);
if((!checked.contains(pos)) && (world.getBlockState(pos).getBlock()==leaf_type_state.getBlock())) {
checked.add(pos);
to_decay.add(pos);
if(recursion_left > 0) {
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, recursion_left-1));
}
}
}
}
return to_decay;
}
private static void breakBlock(World world, BlockPos pos)
{
Block.dropResources(world.getBlockState(pos), world, pos);
world.setBlock(pos, world.getFluidState(pos).createLegacyBlock(), 1|2|8);
}
public static int chopTree(World world, BlockState broken_state, BlockPos startPos, int max_blocks_to_break, boolean without_target_block)
{
if(world.isClientSide || !isLog(broken_state)) return 0;
final long ymin = startPos.getY();
final long max_leaf_distance = 8;
Set<BlockPos> checked = new HashSet<BlockPos>();
ArrayList<BlockPos> to_break = new ArrayList<BlockPos>();
ArrayList<BlockPos> to_decay = new ArrayList<BlockPos>();
checked.add(startPos);
// Initial simple layer-up search of same logs. This forms the base corpus, and only leaves and
// leaf-enclosed logs attached to this corpus may be broken/decayed.
{
LinkedList<BlockPos> queue = new LinkedList<BlockPos>();
LinkedList<BlockPos> upqueue = new LinkedList<BlockPos>();
queue.add(startPos);
int cutlevel = 0;
int steps_left = 128;
while(!queue.isEmpty() && (--steps_left >= 0)) {
final BlockPos pos = queue.removeFirst();
// Vertical search
final BlockPos uppos = pos.above();
final BlockState upstate = world.getBlockState(uppos);
if(!checked.contains(uppos)) {
checked.add(uppos);
if(isSameLog(upstate, broken_state)) {
// Up is log
upqueue.add(uppos);
to_break.add(uppos);
steps_left = 128;
} else {
boolean isleaf = isLeaves(upstate);
if(isleaf || world.isEmptyBlock(uppos) || (upstate.getBlock() instanceof VineBlock)) {
if(isleaf) to_decay.add(uppos);
// Up is air, check adjacent for diagonal up (e.g. Accacia)
for(Vector3i v:hoffsets) {
final BlockPos p = uppos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
to_decay.add(p);
}
}
}
}
}
// Lateral search
for(Vector3i v:hoffsets) {
final BlockPos p = pos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
if(p.distSqr(new BlockPos(startPos.getX(), p.getY(), startPos.getZ())) > (cutlevel > 2 ? 256 : 9)) continue;
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
queue.add(p);
to_decay.add(p);
}
}
if(queue.isEmpty() && (!upqueue.isEmpty())) {
queue = upqueue;
upqueue = new LinkedList<BlockPos>();
++cutlevel;
}
}
}
{
// Determine lose logs between the leafs
for(BlockPos pos:to_decay) {
int dist = 1;
to_break.addAll(findBlocksAround(world, pos, broken_state, checked, dist));
}
}
if(!to_decay.isEmpty()) {
final BlockState leaf_type_state = world.getBlockState(to_decay.get(0));
final ArrayList<BlockPos> leafs = to_decay;
to_decay = new ArrayList<BlockPos>();
for(BlockPos pos:leafs) {
int dist = 3;
to_decay.add(pos);
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, dist));
}
}
if(without_target_block) {
checked.remove(startPos);
} else {
to_break.add(startPos);
}
for(BlockPos pos:to_break) breakBlock(world, pos);
for(BlockPos pos:to_decay) breakBlock(world, pos);
{
// And now the bill.
return MathHelper.clamp(((to_break.size()*6/5)+(to_decay.size()/10)-1), 1, 65535);
}
}
}
/*
* @file TreeCutting.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Simple tree cutting algorithm.
*/
package wile.engineersdecor.detail;
import com.google.common.collect.ImmutableList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.LeavesBlock;
import net.minecraft.world.level.block.VineBlock;
import net.minecraft.world.level.block.state.BlockState;
import wile.engineersdecor.ModEngineersDecor;
import java.util.*;
public class TreeCutting
{
private static final org.apache.logging.log4j.Logger LOGGER = ModEngineersDecor.logger();
public static boolean canChop(BlockState state)
{ return isLog(state); }
// -------------------------------------------------------------------------------------------------------------------
private static final List<Vec3i> hoffsets = ImmutableList.of(
new Vec3i( 1,0, 0), new Vec3i( 1,0, 1), new Vec3i( 0,0, 1),
new Vec3i(-1,0, 1), new Vec3i(-1,0, 0), new Vec3i(-1,0,-1),
new Vec3i( 0,0,-1), new Vec3i( 1,0,-1)
);
private static boolean isLog(BlockState state)
{ return (state.is(BlockTags.LOGS)) || (state.getBlock().getTags().contains(new ResourceLocation("minecraft","logs"))); }
private static boolean isSameLog(BlockState a, BlockState b)
{ return (a.getBlock()==b.getBlock()); }
private static boolean isLeaves(BlockState state)
{
if(state.getBlock() instanceof LeavesBlock) return true;
if(state.getBlock().getTags().contains(new ResourceLocation("minecraft","leaves"))) return true;
return false;
}
private static List<BlockPos> findBlocksAround(final Level world, final BlockPos centerPos, final BlockState leaf_type_state, final Set<BlockPos> checked, int recursion_left)
{
ArrayList<BlockPos> to_decay = new ArrayList<>();
for(int y=-1; y<=1; ++y) {
final BlockPos layer = centerPos.offset(0,y,0);
for(Vec3i v:hoffsets) {
BlockPos pos = layer.offset(v);
if((!checked.contains(pos)) && (world.getBlockState(pos).getBlock()==leaf_type_state.getBlock())) {
checked.add(pos);
to_decay.add(pos);
if(recursion_left > 0) {
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, recursion_left-1));
}
}
}
}
return to_decay;
}
private static void breakBlock(Level world, BlockPos pos)
{
Block.dropResources(world.getBlockState(pos), world, pos);
world.setBlock(pos, world.getFluidState(pos).createLegacyBlock(), 1|2|8);
}
public static int chopTree(Level world, BlockState broken_state, BlockPos startPos, int max_blocks_to_break, boolean without_target_block)
{
if(world.isClientSide || !isLog(broken_state)) return 0;
final long ymin = startPos.getY();
final long max_leaf_distance = 8;
Set<BlockPos> checked = new HashSet<>();
ArrayList<BlockPos> to_break = new ArrayList<>();
ArrayList<BlockPos> to_decay = new ArrayList<>();
checked.add(startPos);
// Initial simple layer-up search of same logs. This forms the base corpus, and only leaves and
// leaf-enclosed logs attached to this corpus may be broken/decayed.
{
LinkedList<BlockPos> queue = new LinkedList<>();
LinkedList<BlockPos> upqueue = new LinkedList<>();
queue.add(startPos);
int cutlevel = 0;
int steps_left = 128;
while(!queue.isEmpty() && (--steps_left >= 0)) {
final BlockPos pos = queue.removeFirst();
// Vertical search
final BlockPos uppos = pos.above();
final BlockState upstate = world.getBlockState(uppos);
if(!checked.contains(uppos)) {
checked.add(uppos);
if(isSameLog(upstate, broken_state)) {
// Up is log
upqueue.add(uppos);
to_break.add(uppos);
steps_left = 128;
} else {
boolean isleaf = isLeaves(upstate);
if(isleaf || world.isEmptyBlock(uppos) || (upstate.getBlock() instanceof VineBlock)) {
if(isleaf) to_decay.add(uppos);
// Up is air, check adjacent for diagonal up (e.g. Accacia)
for(Vec3i v:hoffsets) {
final BlockPos p = uppos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
to_decay.add(p);
}
}
}
}
}
// Lateral search
for(Vec3i v:hoffsets) {
final BlockPos p = pos.offset(v);
if(checked.contains(p)) continue;
checked.add(p);
if(p.distSqr(new BlockPos(startPos.getX(), p.getY(), startPos.getZ())) > (cutlevel > 2 ? 256 : 9)) continue;
final BlockState st = world.getBlockState(p);
final Block bl = st.getBlock();
if(isSameLog(st, broken_state)) {
queue.add(p);
to_break.add(p);
} else if(isLeaves(st)) {
queue.add(p);
to_decay.add(p);
}
}
if(queue.isEmpty() && (!upqueue.isEmpty())) {
queue = upqueue;
upqueue = new LinkedList<>();
++cutlevel;
}
}
}
{
// Determine lose logs between the leafs
for(BlockPos pos:to_decay) {
int dist = 1;
to_break.addAll(findBlocksAround(world, pos, broken_state, checked, dist));
}
}
if(!to_decay.isEmpty()) {
final BlockState leaf_type_state = world.getBlockState(to_decay.get(0));
final ArrayList<BlockPos> leafs = to_decay;
to_decay = new ArrayList<>();
for(BlockPos pos:leafs) {
int dist = 3;
to_decay.add(pos);
to_decay.addAll(findBlocksAround(world, pos, leaf_type_state, checked, dist));
}
}
if(without_target_block) {
checked.remove(startPos);
} else {
to_break.add(startPos);
}
for(BlockPos pos:to_break) breakBlock(world, pos);
for(BlockPos pos:to_decay) breakBlock(world, pos);
{
// And now the bill.
return Mth.clamp(((to_break.size()*6/5)+(to_decay.size()/10)-1), 1, 65535);
}
}
}

View file

@ -1,93 +1,95 @@
/*
* @file JEIPlugin.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* JEI plugin (see https://github.com/mezz/JustEnoughItems/wiki/Creating-Plugins)
*/
package wile.engineersdecor.eapi.jei;
//public class JEIPlugin {}
import mezz.jei.api.registration.IRecipeCatalystRegistration;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.blocks.EdCraftingTable;
import mezz.jei.api.constants.VanillaRecipeCategoryUid;
import mezz.jei.api.registration.IRecipeTransferRegistration;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableTileEntity;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@mezz.jei.api.JeiPlugin
public class JEIPlugin implements mezz.jei.api.IModPlugin
{
@Override
public ResourceLocation getPluginUid()
{ return new ResourceLocation(ModEngineersDecor.MODID, "jei_plugin_uid"); }
@Override
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
try {
registration.addRecipeTransferHandler(
EdCraftingTable.CraftingTableUiContainer.class,
VanillaRecipeCategoryUid.CRAFTING,
1, 9, 10, 36+CraftingTableTileEntity.NUM_OF_STORAGE_SLOTS
);
} catch(Throwable e) {
ModEngineersDecor.logger().warn("Exception in JEI crafting table handler registration: '" + e.getMessage() + "'.");
}
}
}
@Override
public void onRuntimeAvailable(IJeiRuntime jeiRuntime)
{
HashSet<Item> blacklisted = new HashSet<>();
for(Block e: ModContent.getRegisteredBlocks()) {
if(ModConfig.isOptedOut(e) && (e.asItem().getRegistryName().getPath()).equals((e.getRegistryName().getPath()))) {
blacklisted.add(e.asItem());
}
}
for(Item e: ModContent.getRegisteredItems()) {
if(ModConfig.isOptedOut(e) && (!(e instanceof BlockItem))) {
blacklisted.add(e);
}
}
if(!blacklisted.isEmpty()) {
List<ItemStack> blacklist = blacklisted.stream().map(ItemStack::new).collect(Collectors.toList());
try {
jeiRuntime.getIngredientManager().removeIngredientsAtRuntime(VanillaTypes.ITEM, blacklist);
} catch(Exception e) {
ModEngineersDecor.logger().warn("Exception in JEI opt-out processing: '" + e.getMessage() + "', skipping further JEI optout processing.");
}
}
}
@Override
public void registerRecipeCatalysts(IRecipeCatalystRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.CRAFTING_TABLE), VanillaRecipeCategoryUid.CRAFTING);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_LAB_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_LAB_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_ELECTRICAL_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_ELECTRICAL_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
}
}
/*
* @file JEIPlugin.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* JEI plugin (see https://github.com/mezz/JustEnoughItems/wiki/Creating-Plugins)
*/
package wile.engineersdecor.eapi.jei;
public class JEIPlugin {}
/*
import mezz.jei.api.registration.IRecipeCatalystRegistration;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.ModContent;
import wile.engineersdecor.blocks.EdCraftingTable;
import mezz.jei.api.constants.VanillaRecipeCategoryUid;
import mezz.jei.api.registration.IRecipeTransferRegistration;
import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.runtime.IJeiRuntime;
import net.minecraft.block.Block;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import wile.engineersdecor.blocks.EdCraftingTable.CraftingTableTileEntity;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
@mezz.jei.api.JeiPlugin
public class JEIPlugin implements mezz.jei.api.IModPlugin
{
@Override
public ResourceLocation getPluginUid()
{ return new ResourceLocation(Auxiliaries.modid(), "jei_plugin_uid"); }
@Override
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
try {
registration.addRecipeTransferHandler(
EdCraftingTable.CraftingTableUiContainer.class,
VanillaRecipeCategoryUid.CRAFTING,
1, 9, 10, 36+CraftingTableTileEntity.NUM_OF_STORAGE_SLOTS
);
} catch(Throwable e) {
ModEngineersDecor.logger().warn("Exception in JEI crafting table handler registration: '" + e.getMessage() + "'.");
}
}
}
@Override
public void onRuntimeAvailable(IJeiRuntime jeiRuntime)
{
HashSet<Item> blacklisted = new HashSet<>();
for(Block e: ModContent.getRegisteredBlocks()) {
if(ModConfig.isOptedOut(e) && (e.asItem().getRegistryName().getPath()).equals((e.getRegistryName().getPath()))) {
blacklisted.add(e.asItem());
}
}
for(Item e: ModContent.getRegisteredItems()) {
if(ModConfig.isOptedOut(e) && (!(e instanceof BlockItem))) {
blacklisted.add(e);
}
}
if(!blacklisted.isEmpty()) {
List<ItemStack> blacklist = blacklisted.stream().map(ItemStack::new).collect(Collectors.toList());
try {
jeiRuntime.getIngredientManager().removeIngredientsAtRuntime(VanillaTypes.ITEM, blacklist);
} catch(Exception e) {
ModEngineersDecor.logger().warn("Exception in JEI opt-out processing: '" + e.getMessage() + "', skipping further JEI optout processing.");
}
}
}
@Override
public void registerRecipeCatalysts(IRecipeCatalystRegistration registration)
{
if(!ModConfig.isOptedOut(ModContent.CRAFTING_TABLE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.CRAFTING_TABLE), VanillaRecipeCategoryUid.CRAFTING);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_LAB_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_LAB_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
if(!ModConfig.isOptedOut(ModContent.SMALL_ELECTRICAL_FURNACE)) {
registration.addRecipeCatalyst(new ItemStack(ModContent.SMALL_ELECTRICAL_FURNACE), VanillaRecipeCategoryUid.FURNACE);
}
}
}
*/

View file

@ -1,46 +1,46 @@
/*
* @file EdItem.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basic item functionality for mod items.
*/
package wile.engineersdecor.items;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class EdItem extends Item
{
public static final Collection<ItemGroup> ENABLED_TABS = Collections.singletonList(ModEngineersDecor.ITEMGROUP);
public static final Collection<ItemGroup> DISABLED_TABS = new ArrayList<ItemGroup>();
public EdItem(Item.Properties properties)
{ super(properties.tab(ModEngineersDecor.ITEMGROUP)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable World world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public Collection<ItemGroup> getCreativeTabs()
{ return ModConfig.isOptedOut(this) ? (DISABLED_TABS) : (ENABLED_TABS); }
}
/*
* @file EdItem.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Basic item functionality for mod items.
*/
package wile.engineersdecor.items;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.ModEngineersDecor;
import wile.engineersdecor.ModConfig;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class EdItem extends Item
{
public static final Collection<CreativeModeTab> ENABLED_TABS = Collections.singletonList(ModEngineersDecor.ITEMGROUP);
public static final Collection<CreativeModeTab> DISABLED_TABS = new ArrayList<CreativeModeTab>();
public EdItem(Item.Properties properties)
{ super(properties.tab(ModEngineersDecor.ITEMGROUP)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable Level world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public Collection<CreativeModeTab> getCreativeTabs()
{ return ModConfig.isOptedOut(this) ? (DISABLED_TABS) : (ENABLED_TABS); }
}

View file

@ -1,214 +1,218 @@
/*
* @file BlockDecorHalfSlab.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Half slab ("slab slices") characteristics class. Actually
* it's now a quater slab, but who cares.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.world.World;
import net.minecraft.world.IWorld;
import net.minecraft.world.IBlockReader;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraft.state.IntegerProperty;
import net.minecraft.entity.EntityType;
import net.minecraft.state.StateContainer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.item.*;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.util.*;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.*;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import wile.engineersdecor.libmc.blocks.StandardBlocks.IStandardBlock.RenderTypeHint;
public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14);
protected static final VoxelShape AABBs[] = {
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 2./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 4./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 6./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 8./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 10./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 12./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 14./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 2./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 4./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 6./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 8./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 10./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 12./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0, 14./16, 0, 1, 16./16, 1)),
VoxelShapes.create(new AxisAlignedBB(0,0,0,1,1,1)) // <- with 4bit fill
};
protected static final int num_slabs_contained_in_parts_[] = { 1,2,3,4,5,6,7,8,7,6,5,4,3,2,1 ,0x1 }; // <- with 4bit fill
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
public SlabSliceBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); }
protected boolean is_cube(BlockState state)
{ return state.getValue(PARTS) == 0x07; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip);
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return AABBs[state.getValue(PARTS) & 0xf]; }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PARTS); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
final BlockPos pos = context.getClickedPos();
BlockState state = context.getLevel().getBlockState(pos);
if(state.getBlock() == this) {
int parts = state.getValue(PARTS);
if(parts == 7) return null; // -> is already a full block.
parts += (parts < 7) ? 1 : -1;
if(parts==7) state = state.setValue(WATERLOGGED, false);
return state.setValue(PARTS, parts);
} else {
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context); // fluid state
if(face == Direction.UP) return placement_state.setValue(PARTS, 0);
if(face == Direction.DOWN) return placement_state.setValue(PARTS, 14);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(PARTS, isupper ? 14 : 0);
}
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockItemUseContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final int parts = state.getValue(PARTS);
if(parts == 7) return false;
if((face == Direction.UP) && (parts < 7)) return true;
if((face == Direction.DOWN) && (parts > 7)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (parts==0) : (parts==1);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{ return new ArrayList<ItemStack>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, World world, BlockPos pos, PlayerEntity player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vector3d lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
int parts = state.getValue(PARTS);
if((facing == Direction.DOWN) && (parts <= 7)) {
if(parts > 0) {
world.setBlock(pos, state.setValue(PARTS, parts-1), 3);
} else {
world.removeBlock(pos, false);
}
} else if((facing == Direction.UP) && (parts >= 7)) {
if(parts < 14) {
world.setBlock(pos, state.setValue(PARTS, parts + 1), 3);
} else {
world.removeBlock(pos, false);
}
} else {
return;
}
if(!player.isCreative()) {
stack.grow(1);
if(player.inventory != null) player.inventory.setChanged(); // @todo: check if inventory can actually be null
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundCategory.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(IWorld world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(PARTS)==14) ? false : super.placeLiquid(world, pos, state, fluidState); }
@Override
public boolean canPlaceLiquid(IBlockReader world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(PARTS)==14) ? false : super.canPlaceLiquid(world, pos, state, fluid); }
}
/*
* @file BlockDecorHalfSlab.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Half slab ("slab slices") characteristics class. Actually
* it's now a quater slab, but who cares.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14);
protected static final VoxelShape[] AABBs = {
Shapes.create(new AABB(0, 0./16, 0, 1, 2./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 4./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 6./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 8./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 10./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 12./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 14./16, 1)),
Shapes.create(new AABB(0, 0./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 2./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 4./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 6./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 8./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 10./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 12./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0, 14./16, 0, 1, 16./16, 1)),
Shapes.create(new AABB(0,0,0,1,1,1)) // <- with 4bit fill
};
protected static final int[] num_slabs_contained_in_parts_ = { 1,2,3,4,5,6,7,8,7,6,5,4,3,2,1 ,0x1 }; // <- with 4bit fill
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
public SlabSliceBlock(long config, BlockBehaviour.Properties builder)
{ super(config, builder); }
protected boolean is_cube(BlockState state)
{ return state.getValue(PARTS) == 0x07; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip);
}
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return AABBs[state.getValue(PARTS) & 0xf]; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(PARTS); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
final BlockPos pos = context.getClickedPos();
BlockState state = context.getLevel().getBlockState(pos);
if(state.getBlock() == this) {
int parts = state.getValue(PARTS);
if(parts == 7) return null; // -> is already a full block.
parts += (parts < 7) ? 1 : -1;
if(parts==7) state = state.setValue(WATERLOGGED, false);
return state.setValue(PARTS, parts);
} else {
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context); // fluid state
if(face == Direction.UP) return placement_state.setValue(PARTS, 0);
if(face == Direction.DOWN) return placement_state.setValue(PARTS, 14);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(PARTS, isupper ? 14 : 0);
}
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockPlaceContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final int parts = state.getValue(PARTS);
if(parts == 7) return false;
if((face == Direction.UP) && (parts < 7)) return true;
if((face == Direction.DOWN) && (parts > 7)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (parts==0) : (parts==1);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{ return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, Level world, BlockPos pos, Player player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vec3 lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
int parts = state.getValue(PARTS);
if((facing == Direction.DOWN) && (parts <= 7)) {
if(parts > 0) {
world.setBlock(pos, state.setValue(PARTS, parts-1), 3);
} else {
world.removeBlock(pos, false);
}
} else if((facing == Direction.UP) && (parts >= 7)) {
if(parts < 14) {
world.setBlock(pos, state.setValue(PARTS, parts + 1), 3);
} else {
world.removeBlock(pos, false);
}
} else {
return;
}
if(!player.isCreative()) {
stack.grow(1);
if(player.getInventory() != null) player.getInventory().setChanged();
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(PARTS) != 14) && (super.placeLiquid(world, pos, state, fluidState)); }
@Override
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(PARTS) != 14) && (super.canPlaceLiquid(world, pos, state, fluid)); }
}

View file

@ -1,162 +1,168 @@
/*
* @file StandardDoorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Door blocks, almost entirely based on vanilla.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.state.properties.DoorHingeSide;
import net.minecraft.state.properties.DoubleBlockHalf;
import net.minecraft.util.*;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock
{
private final long config_;
protected final VoxelShape shapes_[][][][];
protected final SoundEvent open_sound_;
protected final SoundEvent close_sound_;
public StandardDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB[] open_aabbs_top, AxisAlignedBB[] open_aabbs_bottom, AxisAlignedBB[] closed_aabbs_top, AxisAlignedBB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound)
{
super(properties);
VoxelShape shapes[][][][] = new VoxelShape[Direction.values().length][2][2][2];
for(Direction facing: Direction.values()) {
for(boolean open: new boolean[]{false,true}) {
for(DoubleBlockHalf half: new DoubleBlockHalf[]{DoubleBlockHalf.UPPER,DoubleBlockHalf.LOWER}) {
for(boolean hinge_right: new boolean[]{false,true}) {
VoxelShape shape = VoxelShapes.empty();
if(facing.getAxis() == Axis.Y) {
shape = VoxelShapes.block();
} else {
final AxisAlignedBB[] aabbs = (open)?((half==DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half==DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom);
for(AxisAlignedBB e:aabbs) {
AxisAlignedBB aabb = Auxiliaries.getRotatedAABB(e, facing, true);
if(!hinge_right) aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis());
shape = VoxelShapes.join(shape, VoxelShapes.create(aabb), IBooleanFunction.OR);
}
}
shapes[facing.ordinal()][open?1:0][hinge_right?1:0][half==DoubleBlockHalf.UPPER?0:1] = shape;
}
}
}
}
config_ = config;
shapes_ = shapes;
open_sound_ = open_sound;
close_sound_ = close_sound;
}
public StandardDoorBlock(long config, AbstractBlock.Properties properties, AxisAlignedBB open_aabb, AxisAlignedBB closed_aabb, SoundEvent open_sound, SoundEvent close_sound)
{ this(config, properties, new AxisAlignedBB[]{open_aabb}, new AxisAlignedBB[]{open_aabb}, new AxisAlignedBB[]{closed_aabb}, new AxisAlignedBB[]{closed_aabb}, open_sound, close_sound); }
public StandardDoorBlock(long config, AbstractBlock.Properties properties, SoundEvent open_sound, SoundEvent close_sound)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
open_sound,
close_sound
);
}
public StandardDoorBlock(long config, AbstractBlock.Properties properties)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
SoundEvents.WOODEN_DOOR_OPEN,
SoundEvents.WOODEN_DOOR_CLOSE
);
}
@Override
public long config()
{ return config_; }
protected void sound(IBlockReader world, BlockPos pos, boolean open)
{ if(world instanceof World) ((World)world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundCategory.BLOCKS, 0.7f, 1f); }
protected void actuate_adjacent_wing(BlockState state, IBlockReader world_ro, BlockPos pos, boolean open)
{
if(!(world_ro instanceof World)) return;
final World world = (World)world_ro;
final BlockPos adjecent_pos = pos.relative( (state.getValue(HINGE)==DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise()));
if(!world.isLoaded(adjecent_pos)) return;
BlockState adjacent_state = world.getBlockState(adjecent_pos);
if(adjacent_state.getBlock()!=this) return;
if(adjacent_state.getValue(OPEN)==open) return;
world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2|10);
}
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext context)
{ return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN)?1:0][state.getValue(HINGE)==DoorHingeSide.RIGHT?1:0][state.getValue(HALF)==DoubleBlockHalf.UPPER?0:1]; }
@Override
public ActionResultType use(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockRayTraceResult hit)
{ setOpen(world, state, pos, !state.getValue(OPEN)); return ActionResultType.sidedSuccess(world.isClientSide()); }
@Override
public void neighborChanged(BlockState state, World world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
if((block == this) || (powered == state.getValue(POWERED))) return;
world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2);
actuate_adjacent_wing(state, world, pos, powered);
if(powered != state.getValue(OPEN)) sound(world, pos, powered);
}
@Override
public void setOpen(World world, BlockState state, BlockPos pos, boolean open)
{
if(!state.is(this) || (state.getValue(OPEN) == open)) return;
state = state.setValue(OPEN, open);
world.setBlock(pos, state, 2|8);
sound(world, pos, open);
actuate_adjacent_wing(state, world, pos, open);
}
}
/*
* @file StandardDoorBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Door blocks, almost entirely based on vanilla.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DoorBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock
{
private final long config_;
protected final VoxelShape[][][][] shapes_;
protected final SoundEvent open_sound_;
protected final SoundEvent close_sound_;
public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB[] open_aabbs_top, AABB[] open_aabbs_bottom, AABB[] closed_aabbs_top, AABB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound)
{
super(properties);
VoxelShape[][][][] shapes = new VoxelShape[Direction.values().length][2][2][2];
for(Direction facing: Direction.values()) {
for(boolean open: new boolean[]{false,true}) {
for(DoubleBlockHalf half: new DoubleBlockHalf[]{DoubleBlockHalf.UPPER,DoubleBlockHalf.LOWER}) {
for(boolean hinge_right: new boolean[]{false,true}) {
VoxelShape shape = Shapes.empty();
if(facing.getAxis() == Direction.Axis.Y) {
shape = Shapes.block();
} else {
final AABB[] aabbs = (open)?((half==DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half==DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom);
for(AABB e:aabbs) {
AABB aabb = Auxiliaries.getRotatedAABB(e, facing, true);
if(!hinge_right) aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis());
shape = Shapes.join(shape, Shapes.create(aabb), BooleanOp.OR);
}
}
shapes[facing.ordinal()][open?1:0][hinge_right?1:0][half==DoubleBlockHalf.UPPER?0:1] = shape;
}
}
}
}
config_ = config;
shapes_ = shapes;
open_sound_ = open_sound;
close_sound_ = close_sound;
}
public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB open_aabb, AABB closed_aabb, SoundEvent open_sound, SoundEvent close_sound)
{ this(config, properties, new AABB[]{open_aabb}, new AABB[]{open_aabb}, new AABB[]{closed_aabb}, new AABB[]{closed_aabb}, open_sound, close_sound); }
public StandardDoorBlock(long config, BlockBehaviour.Properties properties, SoundEvent open_sound, SoundEvent close_sound)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
open_sound,
close_sound
);
}
public StandardDoorBlock(long config, BlockBehaviour.Properties properties)
{
this(
config, properties,
Auxiliaries.getPixeledAABB(13,0, 0, 16,16,16),
Auxiliaries.getPixeledAABB( 0,0,13, 16,16,16),
SoundEvents.WOODEN_DOOR_OPEN,
SoundEvents.WOODEN_DOOR_CLOSE
);
}
@Override
public long config()
{ return config_; }
protected void sound(BlockGetter world, BlockPos pos, boolean open)
{ if(world instanceof Level) ((Level)world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundSource.BLOCKS, 0.7f, 1f); }
protected void actuate_adjacent_wing(BlockState state, BlockGetter world_ro, BlockPos pos, boolean open)
{
if(!(world_ro instanceof final Level world)) return;
final BlockPos adjecent_pos = pos.relative( (state.getValue(HINGE)==DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise()));
if(!world.isLoaded(adjecent_pos)) return;
BlockState adjacent_state = world.getBlockState(adjecent_pos);
if(adjacent_state.getBlock()!=this) return;
if(adjacent_state.getValue(OPEN)==open) return;
world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2|10);
}
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context)
{ return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN)?1:0][state.getValue(HINGE)==DoorHingeSide.RIGHT?1:0][state.getValue(HALF)==DoubleBlockHalf.UPPER?0:1]; }
@Override
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit)
{ setOpen(player, world, state, pos, !state.getValue(OPEN)); return InteractionResult.sidedSuccess(world.isClientSide()); }
@Override
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving)
{
boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
if((block == this) || (powered == state.getValue(POWERED))) return;
world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2);
actuate_adjacent_wing(state, world, pos, powered);
if(powered != state.getValue(OPEN)) sound(world, pos, powered);
}
@Override
public void setOpen(@Nullable Entity entity, Level world, BlockState state, BlockPos pos, boolean open)
{
if(!state.is(this) || (state.getValue(OPEN) == open)) return;
state = state.setValue(OPEN, open);
world.setBlock(pos, state, 2|8);
sound(world, pos, open);
actuate_adjacent_wing(state, world, pos, open);
}
}

View file

@ -0,0 +1,68 @@
/*
* @file StandardEntityBlocks.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Common functionality class for blocks with block entities.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEventListener;
import net.minecraftforge.common.util.FakePlayer;
import javax.annotation.Nullable;
public class StandardEntityBlocks
{
public interface IStandardEntityBlock<ET extends StandardBlockEntity> extends EntityBlock
{
@Nullable
BlockEntityType<ET> getBlockEntityType();
@Override
@Nullable
default BlockEntity newBlockEntity(BlockPos pos, BlockState state)
{ return (getBlockEntityType()==null) ? null : getBlockEntityType().create(pos, state); }
@Override
@Nullable
default <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level world, BlockState state, BlockEntityType<T> te_type)
{ return (world.isClientSide) ? (null) : ((Level w, BlockPos p, BlockState s, T te) -> ((StandardBlockEntity)te).tick()); } // To be evaluated if
@Override
@Nullable
default <T extends BlockEntity> GameEventListener getListener(Level world, T te)
{ return null; }
default InteractionResult useOpenGui(BlockState state, Level world, BlockPos pos, Player player)
{
if(world.isClientSide()) return InteractionResult.SUCCESS;
final BlockEntity te = world.getBlockEntity(pos);
if(!(te instanceof MenuProvider) || ((player instanceof FakePlayer))) return InteractionResult.FAIL;
player.openMenu((MenuProvider)te);
return InteractionResult.CONSUME;
}
}
public static abstract class StandardBlockEntity extends BlockEntity
{
public StandardBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state)
{ super(type, pos, state); }
public void tick()
{}
}
}

View file

@ -1,194 +1,200 @@
/*
* @file StandardFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.pathfinding.PathType;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.properties.BlockStateProperties;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.world.*;
import net.minecraft.fluid.FluidState;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.state.StateContainer;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import javax.annotation.Nullable;
import java.util.*;
public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallHeight> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallHeight> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallHeight> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallHeight> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public StandardFenceBlock(long config, AbstractBlock.Properties properties)
{ this(config, properties, 1.5,16, 1.5, 0, 14, 16); }
public StandardFenceBlock(long config, AbstractBlock.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
super(properties);
shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y);
collision_shape_voxels = buildShapes(pole_width,24, pole_width, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallHeight height, VoxelShape low, VoxelShape high)
{
if(height == WallHeight.TALL) return VoxelShapes.or(pole, high);
if(height == WallHeight.LOW) return VoxelShapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallHeight wh_east : WALL_EAST.getPossibleValues()) {
for(WallHeight wh_north : WALL_NORTH.getPossibleValues()) {
for(WallHeight wh_west : WALL_WEST.getPossibleValues()) {
for(WallHeight wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = VoxelShapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = VoxelShapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
builder.put(bs.setValue(WATERLOGGED, false), shape);
builder.put(bs.setValue(WATERLOGGED, true), shape);
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); }
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side);
}
protected WallHeight selectWallHeight(IWorldReader world, BlockPos pos, Direction direction)
{
return WallHeight.LOW; // @todo: implement
}
public BlockState getStateForPlacement(BlockItemUseContext context)
{
IWorldReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState()
.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE)
.setValue(WATERLOGGED, fs.getType() == Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(BlockStateProperties.WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH)!=WallHeight.NONE);
boolean e = (side==Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) !=WallHeight.NONE);
boolean s = (side==Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH)!=WallHeight.NONE);
boolean w = (side==Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) !=WallHeight.NONE);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE);
}
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}
/*
* @file StandardFenceBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.WallSide;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallSide> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallSide> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallSide> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallSide> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public StandardFenceBlock(long config, BlockBehaviour.Properties properties)
{ this(config, properties, 1.5,16, 1.5, 0, 14, 16); }
public StandardFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
super(properties);
shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y);
collision_shape_voxels = buildShapes(pole_width,24, pole_width, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high)
{
if(height == WallSide.TALL) return Shapes.or(pole, high);
if(height == WallSide.LOW) return Shapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
ImmutableMap.Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallSide wh_east : WALL_EAST.getPossibleValues()) {
for(WallSide wh_north : WALL_NORTH.getPossibleValues()) {
for(WallSide wh_west : WALL_WEST.getPossibleValues()) {
for(WallSide wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = Shapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = Shapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
builder.put(bs.setValue(WATERLOGGED, false), shape);
builder.put(bs.setValue(WATERLOGGED, true), shape);
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); }
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side);
}
protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction)
{
return WallSide.LOW; // @todo: implement
}
public BlockState getStateForPlacement(BlockPlaceContext context)
{
LevelReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState()
.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE)
.setValue(WATERLOGGED, fs.getType() == Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(BlockStateProperties.WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH)!=WallSide.NONE);
boolean e = (side==Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) !=WallSide.NONE);
boolean s = (side==Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH)!=WallSide.NONE);
boolean w = (side==Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) !=WallSide.NONE);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE);
}
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}

View file

@ -1,55 +1,57 @@
/*
* @file StandardStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stairs and roof blocks, almost entirely based on vanilla stairs.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.BlockPos;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.item.ItemStack;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
public class StandardStairsBlock extends StairsBlock implements StandardBlocks.IStandardBlock
{
private final long config;
public StandardStairsBlock(long config, BlockState state, AbstractBlock.Properties properties)
{ super(()->state, properties); this.config = config; }
public StandardStairsBlock(long config, java.util.function.Supplier<BlockState> state, AbstractBlock.Properties properties)
{ super(state, properties); this.config = config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}
/*
* @file StandardStairsBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Stairs and roof blocks, almost entirely based on vanilla stairs.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.StairBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.PushReaction;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
public class StandardStairsBlock extends StairBlock implements StandardBlocks.IStandardBlock
{
private final long config;
public StandardStairsBlock(long config, BlockState state, BlockBehaviour.Properties properties)
{ super(()->state, properties); this.config = config; }
public StandardStairsBlock(long config, java.util.function.Supplier<BlockState> state, BlockBehaviour.Properties properties)
{ super(state, properties); this.config = config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}

View file

@ -1,201 +1,206 @@
/*
* @file VariantSlabBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Standard half block horizontal slab characteristics class.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.IBlockReader;
import net.minecraft.state.StateContainer;
import net.minecraft.block.*;
import net.minecraft.block.BlockState;
import net.minecraft.fluid.Fluid;
import net.minecraft.fluid.FluidState;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.state.IntegerProperty;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.state.properties.SlabType;
import net.minecraft.util.*;
import net.minecraft.util.math.*;
import net.minecraft.util.math.vector.*;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import wile.engineersdecor.libmc.blocks.StandardBlocks.IStandardBlock.RenderTypeHint;
public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final EnumProperty<SlabType> TYPE = BlockStateProperties.SLAB_TYPE;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3);
protected static final VoxelShape AABBs[] = {
VoxelShapes.create(new AxisAlignedBB(0, 8./16, 0, 1, 16./16, 1)), // top slab
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 8./16, 1)), // bottom slab
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 16./16, 1)), // both slabs
VoxelShapes.create(new AxisAlignedBB(0, 0./16, 0, 1, 16./16, 1)) // << 2bit fill
};
protected static final int num_slabs_contained_in_parts_[] = {1,1,2,2};
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
protected boolean is_cube(BlockState state)
{ return state.getValue(TYPE) == SlabType.DOUBLE; }
public VariantSlabBlock(long config, AbstractBlock.Properties builder)
{ super(config, builder); registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true);
}
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState==state) ? true : super.skipRendering(state, adjacentBlockState, side); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, IBlockReader source, BlockPos pos, ISelectionContext selectionContext)
{ return AABBs[state.getValue(TYPE).ordinal() & 0x3]; }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TYPE, TEXTURE_VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockItemUseContext context)
{
BlockPos pos = context.getClickedPos();
if(context.getLevel().getBlockState(pos).getBlock() == this) return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false);
final int rnd = MathHelper.clamp((int)(MathHelper.getSeed(context.getClickedPos()) & 0x3), 0, 3);
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state
if(face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM);
if(face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM);
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockItemUseContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final SlabType type = state.getValue(TYPE);
if((face == Direction.UP) && (type==SlabType.BOTTOM)) return true;
if((face == Direction.DOWN) && (type==SlabType.TOP)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (type==SlabType.BOTTOM) : (type==SlabType.TOP);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, World world, TileEntity te, boolean explosion)
{ return new ArrayList<ItemStack>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, World world, BlockPos pos, PlayerEntity player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vector3d lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
SlabType type = state.getValue(TYPE);
if(facing == Direction.DOWN) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3);
} else {
world.removeBlock(pos, false);
}
} else if(facing == Direction.UP) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3);
} else {
world.removeBlock(pos, false);
}
}
if(!player.isCreative()) {
stack.grow(1);
if(player.inventory != null) player.inventory.setChanged();
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundCategory.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(IWorld world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(TYPE)==SlabType.DOUBLE) ? false : super.placeLiquid(world, pos, state, fluidState); }
@Override
public boolean canPlaceLiquid(IBlockReader world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(TYPE)==SlabType.DOUBLE) ? false : super.canPlaceLiquid(world, pos, state, fluid); }
}
/*
* @file VariantSlabBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Standard half block horizontal slab characteristics class.
*/
package wile.engineersdecor.libmc.blocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Mirror;
import net.minecraft.world.level.block.Rotation;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock
{
public static final EnumProperty<SlabType> TYPE = BlockStateProperties.SLAB_TYPE;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3);
protected static final VoxelShape[] AABBs = {
Shapes.create(new AABB(0, 8./16, 0, 1, 16./16, 1)), // top slab
Shapes.create(new AABB(0, 0./16, 0, 1, 8./16, 1)), // bottom slab
Shapes.create(new AABB(0, 0./16, 0, 1, 16./16, 1)), // both slabs
Shapes.create(new AABB(0, 0./16, 0, 1, 16./16, 1)) // << 2bit fill
};
protected static final int[] num_slabs_contained_in_parts_ = {1,1,2,2};
private static boolean with_pickup = false;
public static void on_config(boolean direct_slab_pickup)
{ with_pickup = direct_slab_pickup; }
protected boolean is_cube(BlockState state)
{ return state.getValue(TYPE) == SlabType.DOUBLE; }
public VariantSlabBlock(long config, BlockBehaviour.Properties builder)
{ super(config, builder); registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); }
@Override
public RenderTypeHint getRenderTypeHint()
{ return (((config & StandardBlocks.CFG_TRANSLUCENT)!=0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{
if(!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return;
if(with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true);
}
@Override
@OnlyIn(Dist.CLIENT)
@SuppressWarnings("deprecation")
public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side)
{ return (adjacentBlockState==state) || (super.skipRendering(state, adjacentBlockState, side)); }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext)
{ return AABBs[state.getValue(TYPE).ordinal() & 0x3]; }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return getShape(state, world, pos, selectionContext); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TYPE, TEXTURE_VARIANT); }
@Override
@Nullable
public BlockState getStateForPlacement(BlockPlaceContext context)
{
BlockPos pos = context.getClickedPos();
if(context.getLevel().getBlockState(pos).getBlock() == this) return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false);
final int rnd = Mth.clamp((int)(Mth.getSeed(context.getClickedPos()) & 0x3), 0, 3);
final Direction face = context.getClickedFace();
final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state
if(face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM);
if(face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP);
if(!face.getAxis().isHorizontal()) return placement_state;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM);
}
@Override
@SuppressWarnings("deprecation")
public boolean canBeReplaced(BlockState state, BlockPlaceContext context)
{
if(context.getItemInHand().getItem() != this.asItem()) return false;
if(!context.replacingClickedOnBlock()) return true;
final Direction face = context.getClickedFace();
final SlabType type = state.getValue(TYPE);
if((face == Direction.UP) && (type==SlabType.BOTTOM)) return true;
if((face == Direction.DOWN) && (type==SlabType.TOP)) return true;
if(!face.getAxis().isHorizontal()) return false;
final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5);
return isupper ? (type==SlabType.BOTTOM) : (type==SlabType.TOP);
}
@Override
@SuppressWarnings("deprecation")
public BlockState rotate(BlockState state, Rotation rot)
{ return state; }
@Override
@SuppressWarnings("deprecation")
public BlockState mirror(BlockState state, Mirror mirrorIn)
{ return state; }
@Override
public boolean hasDynamicDropList()
{ return true; }
@Override
public List<ItemStack> dropList(BlockState state, Level world, BlockEntity te, boolean explosion)
{ return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); }
@Override
@SuppressWarnings("deprecation")
public void attack(BlockState state, Level world, BlockPos pos, Player player)
{
if((world.isClientSide) || (!with_pickup)) return;
final ItemStack stack = player.getMainHandItem();
if(stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return;
if(stack.getCount() >= stack.getMaxStackSize()) return;
Vec3 lv = player.getLookAngle();
Direction facing = Direction.getNearest((float)lv.x, (float)lv.y, (float)lv.z);
if((facing != Direction.UP) && (facing != Direction.DOWN)) return;
if(state.getBlock() != this) return;
SlabType type = state.getValue(TYPE);
if(facing == Direction.DOWN) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3);
} else {
world.removeBlock(pos, false);
}
} else if(facing == Direction.UP) {
if(type == SlabType.DOUBLE) {
world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3);
} else {
world.removeBlock(pos, false);
}
}
if(!player.isCreative()) {
stack.grow(1);
if(player.getInventory() != null) player.getInventory().setChanged();
}
SoundType st = this.getSoundType(state, world, pos, null);
world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume()+1f)/2.5f, 0.9f*st.getPitch());
}
@Override
public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState)
{ return (state.getValue(TYPE) != SlabType.DOUBLE) && super.placeLiquid(world, pos, state, fluidState); }
@Override
public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid)
{ return (state.getValue(TYPE) != SlabType.DOUBLE) && super.canPlaceLiquid(world, pos, state, fluid); }
}

View file

@ -1,197 +1,198 @@
/*
* @file VariantWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import net.minecraft.entity.EntitySpawnPlacementRegistry;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.EnumProperty;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.math.shapes.VoxelShapes;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.world.*;
import net.minecraft.fluid.FluidState;
import net.minecraft.entity.EntityType;
import net.minecraft.util.math.shapes.ISelectionContext;
import net.minecraft.state.StateContainer;
import net.minecraft.util.math.MathHelper;
import net.minecraft.block.*;
import net.minecraft.block.material.PushReaction;
import net.minecraft.block.BlockState;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.fluid.Fluids;
import net.minecraft.item.ItemStack;
import net.minecraft.state.IntegerProperty;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallHeight> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallHeight> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallHeight> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallHeight> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7);
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public VariantWallBlock(long config, AbstractBlock.Properties builder)
{
super(builder);
shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16);
collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallHeight height, VoxelShape low, VoxelShape high)
{
if(height == WallHeight.TALL) return VoxelShapes.or(pole, high);
if(height == WallHeight.LOW) return VoxelShapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallHeight wh_east : WALL_EAST.getPossibleValues()) {
for(WallHeight wh_north : WALL_NORTH.getPossibleValues()) {
for(WallHeight wh_west : WALL_WEST.getPossibleValues()) {
for(WallHeight wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = VoxelShapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = VoxelShapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
final VoxelShape tvs = shape;
TEXTURE_VARIANT.getPossibleValues().forEach((tv)->{
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs);
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs);
});
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, IBlockReader world, BlockPos pos, ISelectionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, VoxelShapes.block()); }
@Override
protected void createBlockStateDefinition(StateContainer.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TEXTURE_VARIANT); }
protected boolean attachesTo(BlockState facingState, IWorldReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof VariantWallBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side);
}
protected WallHeight selectWallHeight(IWorldReader world, BlockPos pos, Direction direction)
{
return WallHeight.LOW; // @todo: implement
}
public BlockState getStateForPlacement(BlockItemUseContext context)
{
IWorldReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState().setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE)
.setValue(WATERLOGGED, fs.getType()==Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, IWorld world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH)!=WallHeight.NONE;
boolean e = (side==Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST)!=WallHeight.NONE;
boolean s = (side==Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH)!=WallHeight.NONE;
boolean w = (side==Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST)!=WallHeight.NONE;
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallHeight.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallHeight.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallHeight.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallHeight.NONE)
.setValue(TEXTURE_VARIANT, ((int)MathHelper.getSeed(pos)) & 0x7);
}
@Override
public boolean canCreatureSpawn(BlockState state, IBlockReader world, BlockPos pos, EntitySpawnPlacementRegistry.PlacementType type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}
/*
* @file VariantWallBlock.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Wall blocks.
*/
package wile.engineersdecor.libmc.blocks;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.FenceGateBlock;
import net.minecraft.world.level.block.WallBlock;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.*;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock
{
public static final BooleanProperty UP = BlockStateProperties.UP;
public static final EnumProperty<WallSide> WALL_EAST = BlockStateProperties.EAST_WALL;
public static final EnumProperty<WallSide> WALL_NORTH = BlockStateProperties.NORTH_WALL;
public static final EnumProperty<WallSide> WALL_SOUTH = BlockStateProperties.SOUTH_WALL;
public static final EnumProperty<WallSide> WALL_WEST = BlockStateProperties.WEST_WALL;
public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7);
private final Map<BlockState, VoxelShape> shape_voxels;
private final Map<BlockState, VoxelShape> collision_shape_voxels;
private final long config;
public VariantWallBlock(long config, BlockBehaviour.Properties builder)
{
super(builder);
shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16);
collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24);
this.config = config;
}
@Override
public long config()
{ return config; }
@Override
@OnlyIn(Dist.CLIENT)
public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag)
{ Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); }
private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high)
{
if(height == WallSide.TALL) return Shapes.or(pole, high);
if(height == WallSide.LOW) return Shapes.or(pole, low);
return pole;
}
protected Map<BlockState, VoxelShape> buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y)
{
final double px0=8.0-pole_width, px1=8.0+pole_width, sx0=8.0-side_width, sx1=8.0+side_width;
VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1);
VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1);
VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16);
VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1);
VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1);
VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1);
VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16);
VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1);
VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1);
Builder<BlockState, VoxelShape> builder = ImmutableMap.builder();
for(Boolean up : UP.getPossibleValues()) {
for(WallSide wh_east : WALL_EAST.getPossibleValues()) {
for(WallSide wh_north : WALL_NORTH.getPossibleValues()) {
for(WallSide wh_west : WALL_WEST.getPossibleValues()) {
for(WallSide wh_south : WALL_SOUTH.getPossibleValues()) {
VoxelShape shape = Shapes.empty();
shape = combinedShape(shape, wh_east, vs4, vs8);
shape = combinedShape(shape, wh_west, vs3, vs7);
shape = combinedShape(shape, wh_north, vs1, vs5);
shape = combinedShape(shape, wh_south, vs2, vs6);
if(up) shape = Shapes.or(shape, vp);
BlockState bs = defaultBlockState().setValue(UP, up)
.setValue(WALL_EAST, wh_east)
.setValue(WALL_NORTH, wh_north)
.setValue(WALL_WEST, wh_west)
.setValue(WALL_SOUTH, wh_south);
final VoxelShape tvs = shape;
TEXTURE_VARIANT.getPossibleValues().forEach((tv)->{
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs);
builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs);
});
}
}
}
}
}
return builder.build();
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext)
{ return collision_shape_voxels.getOrDefault(state, Shapes.block()); }
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder)
{ super.createBlockStateDefinition(builder); builder.add(TEXTURE_VARIANT); }
protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side)
{
final Block block = facingState.getBlock();
if((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true;
final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2));
if(!(oppositeState.getBlock() instanceof VariantWallBlock)) return false;
return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side);
}
protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction)
{ return WallSide.LOW; }
public BlockState getStateForPlacement(BlockPlaceContext context)
{
LevelReader world = context.getLevel();
BlockPos pos = context.getClickedPos();
FluidState fs = context.getLevel().getFluidState(context.getClickedPos());
boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH);
boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST);
boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH);
boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST);
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return defaultBlockState().setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE)
.setValue(WATERLOGGED, fs.getType()==Fluids.WATER);
}
@Override
public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos)
{
if(state.getValue(WATERLOGGED)) world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world));
if(side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos);
boolean n = (side==Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH)!=WallSide.NONE;
boolean e = (side==Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST)!=WallSide.NONE;
boolean s = (side==Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH)!=WallSide.NONE;
boolean w = (side==Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST)!=WallSide.NONE;
boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w);
return state.setValue(UP, not_straight)
.setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE)
.setValue(WALL_EAST , e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE)
.setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE)
.setValue(WALL_WEST , w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE)
.setValue(TEXTURE_VARIANT, ((int)Mth.getSeed(pos)) & 0x7);
}
@Override
public boolean canCreatureSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType<?> entityType)
{ return false; }
@Override
public boolean isPossibleToRespawnInThis()
{ return false; }
@Override
@SuppressWarnings("deprecation")
public PushReaction getPistonPushReaction(BlockState state)
{ return PushReaction.NORMAL; }
}

View file

@ -1,42 +0,0 @@
package wile.engineersdecor.libmc.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.IGuiEventListener;
import net.minecraft.client.gui.IHasContainer;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.client.gui.widget.Widget;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT)
public abstract class ContainerGui<T extends Container> extends ContainerScreen<T> implements IHasContainer<T>
{
public ContainerGui(T screenContainer, PlayerInventory inv, ITextComponent titleIn)
{ super(screenContainer, inv, titleIn); }
protected boolean canHaveDisturbingButtonsFromOtherMods()
{ return false; }
public void init(Minecraft minecraft, int width, int height)
{
this.minecraft = minecraft;
this.itemRenderer = minecraft.getItemRenderer();
this.font = minecraft.font;
this.width = width;
this.height = height;
java.util.function.Consumer<Widget> remove = (b) -> { buttons.remove(b); children.remove(b); };
if((!canHaveDisturbingButtonsFromOtherMods()) || (!net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent.Pre(this, this.buttons, this::addButton, remove)))) {
this.buttons.clear();
this.children.clear();
this.setFocused((IGuiEventListener)null);
this.init();
}
if(canHaveDisturbingButtonsFromOtherMods()) {
net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent.Post(this, this.buttons, this::addButton, remove));
}
}
}

View file

@ -1,48 +0,0 @@
/*
* @file BlockStateDataGen.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Blockstate data generator.
*/
package wile.engineersdecor.libmc.datagen;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.client.model.generators.ItemModelProvider;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraftforge.common.data.ExistingFileHelper;
import net.minecraft.data.*;
public class AssetsDataGen
{
public static class BlockStates extends BlockStateProvider
{
public BlockStates(DataGenerator gen, ExistingFileHelper efh)
{ super(gen, Auxiliaries.modid(), efh); }
@Override
public String getName()
{ return Auxiliaries.modid() + " Block states"; }
@Override
protected void registerStatesAndModels()
{
}
}
public static class ItemModels extends ItemModelProvider
{
public ItemModels(DataGenerator generator, ExistingFileHelper efh)
{ super(generator, Auxiliaries.modid(), efh); }
@Override
public String getName()
{ return Auxiliaries.modid() + "Item models"; }
@Override
protected void registerModels()
{
}
}
}

View file

@ -1,96 +0,0 @@
/*
* @file LootTableGen.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Loot table generator.
*/
package wile.engineersdecor.libmc.datagen;
import wile.engineersdecor.libmc.blocks.StandardBlocks;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import net.minecraft.block.Block;
import net.minecraft.data.*;
import net.minecraft.util.ResourceLocation;
import net.minecraft.loot.*;
import net.minecraft.loot.functions.CopyName;
import net.minecraft.loot.functions.CopyName.Source;
import net.minecraft.loot.functions.CopyNbt;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public class LootTableGen extends LootTableProvider
{
private static final Logger LOGGER = LogManager.getLogger();
private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create();
private final DataGenerator generator;
private final Supplier<List<Block>> block_listing;
//--------------------------------------------------------------------------------------------------------------------
public LootTableGen(DataGenerator gen, Supplier<List<Block>> block_list_supplier)
{ super(gen); generator=gen; block_listing = block_list_supplier; }
//-- LootTableProvider -----------------------------------------------------------------------------------------------
@Override
public String getName()
{ return Auxiliaries.modid() + " Loot Tables"; }
@Override
public void run(DirectoryCache cache)
{ save(cache, generate()); }
//--------------------------------------------------------------------------------------------------------------------
private Map<ResourceLocation, LootTable> generate()
{
final HashMap<ResourceLocation, LootTable> tables = new HashMap<ResourceLocation, LootTable>();
final List<Block> blocks = block_listing.get();
blocks.forEach((block)->{
LOGGER.info("Generating loot table for " + block.getRegistryName());
if((!(block instanceof StandardBlocks.IStandardBlock)) || (!(((StandardBlocks.IStandardBlock)block).hasDynamicDropList()))) {
tables.put(
block.getLootTable(),
defaultBlockDrops(block.getRegistryName().getPath() + "_dlt", block)
.setParamSet(LootParameterSets.BLOCK).build());
} else {
LOGGER.info("Dynamic drop list, skipping loot table for " + block.getRegistryName());
}
});
return tables;
}
private void save(DirectoryCache cache, Map<ResourceLocation, LootTable> tables)
{
final Path root = generator.getOutputFolder();
tables.forEach((rl,tab)->{
Path fp = root.resolve("data/" + rl.getNamespace() + "/loot_tables/" + rl.getPath() + ".json");
try {
IDataProvider.save(GSON, cache, LootTableManager.serialize(tab), fp);
} catch(Exception e) {
LOGGER.error("Failed to save loottable '"+fp+"', exception: " + e);
}
});
}
private LootTable.Builder defaultBlockDrops(String rl_path, Block block)
{
StandaloneLootEntry.Builder iltb = ItemLootEntry.lootTableItem(block);
iltb.apply(CopyName.copyName(Source.BLOCK_ENTITY));
if(block.hasTileEntity(block.defaultBlockState())) {
iltb.apply(CopyNbt.copyData(CopyNbt.Source.BLOCK_ENTITY));
}
return LootTable.lootTable().withPool(LootPool.lootPool().name(rl_path).setRolls(ConstantRange.exactly(1)).add(iltb));
}
}

View file

@ -1,391 +1,505 @@
/*
* @file Auxiliaries.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General commonly used functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.client.util.InputMappings;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.Direction.Axis;
import net.minecraft.util.SharedConstants;
import net.minecraft.util.math.shapes.IBooleanFunction;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.util.math.shapes.VoxelShapes;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.world.IBlockReader;
import net.minecraft.item.ItemStack;
import net.minecraft.client.util.ITooltipFlag;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.util.text.TextFormatting;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import wile.engineersdecor.ModConfig;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class Auxiliaries
{
private static String modid;
private static Logger logger;
private static Supplier<CompoundNBT> server_config_supplier = ()->new CompoundNBT();
public static void init(String modid, Logger logger, Supplier<CompoundNBT> server_config_supplier)
{
Auxiliaries.modid = modid;
Auxiliaries.logger = logger;
Auxiliaries.server_config_supplier = server_config_supplier;
}
// -------------------------------------------------------------------------------------------------------------------
// Mod specific exports
// -------------------------------------------------------------------------------------------------------------------
public static String modid()
{ return modid; }
public static Logger logger()
{ return logger; }
// -------------------------------------------------------------------------------------------------------------------
// Sideness, system/environment, tagging interfaces
// -------------------------------------------------------------------------------------------------------------------
public interface IExperimentalFeature {}
public static final boolean isModLoaded(final String registry_name)
{ return ModList.get().isLoaded(registry_name); }
public static final boolean isDevelopmentMode()
{ return SharedConstants.IS_RUNNING_IN_IDE; }
@OnlyIn(Dist.CLIENT)
public static final boolean isShiftDown()
{
return (InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) ||
InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT));
}
@OnlyIn(Dist.CLIENT)
public static final boolean isCtrlDown()
{
return (InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) ||
InputMappings.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL));
}
// -------------------------------------------------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------------------------------------------------
public static final void logInfo(final String msg)
{ logger.info(msg); }
public static final void logWarn(final String msg)
{ logger.warn(msg); }
public static final void logError(final String msg)
{ logger.error(msg); }
public static final void logDebug(final String msg)
{ if(ModConfig.withDebug()) logger.warn(msg); }
// -------------------------------------------------------------------------------------------------------------------
// Localization, text formatting
// -------------------------------------------------------------------------------------------------------------------
/**
* Text localisation wrapper, implicitly prepends `MODID` to the
* translation keys. Forces formatting argument, nullable if no special formatting shall be applied..
*/
public static TranslationTextComponent localizable(String modtrkey, Object... args)
{
return new TranslationTextComponent((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid+"."+modtrkey), args);
}
public static TranslationTextComponent localizable(String modtrkey)
{ return localizable(modtrkey, new Object[]{}); }
public static TranslationTextComponent localizable_block_key(String blocksubkey)
{ return new TranslationTextComponent("block."+modid+"."+blocksubkey); }
@OnlyIn(Dist.CLIENT)
public static String localize(String translationKey, Object... args)
{
TranslationTextComponent tr = new TranslationTextComponent(translationKey, args);
tr.withStyle(TextFormatting.RESET);
final String ft = tr.getString();
if(ft.contains("${")) {
// Non-recursive, non-argument lang file entry cross referencing.
Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher mt = pt.matcher(ft);
StringBuffer sb = new StringBuffer();
while(mt.find()) {
String m = mt.group(1);
if(m.contains("?")) {
String[] kv = m.split("\\?", 2);
String key = kv[0].trim();
boolean not = key.startsWith("!");
if(not) key = key.replaceFirst("!", "");
m = kv[1].trim();
if(!server_config_supplier.get().contains(key)) {
m = "";
} else {
boolean r = server_config_supplier.get().getBoolean(key);
if(not) r = !r;
if(!r) m = "";
}
}
mt.appendReplacement(sb, Matcher.quoteReplacement((new TranslationTextComponent(m)).getString().trim()));
}
mt.appendTail(sb);
return sb.toString();
} else {
return ft;
}
}
/**
* Returns true if a given key is translated for the current language.
*/
@OnlyIn(Dist.CLIENT)
public static boolean hasTranslation(String key)
{ return net.minecraft.client.resources.I18n.exists(key); }
public static final class Tooltip
{
@OnlyIn(Dist.CLIENT)
public static boolean extendedTipCondition()
{ return isShiftDown(); }
@OnlyIn(Dist.CLIENT)
public static boolean helpCondition()
{ return isShiftDown() && isCtrlDown(); }
/**
* Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT.
* Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or
* no translation found).
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List<ITextComponent> tooltip, ITooltipFlag flag, boolean addAdvancedTooltipHints)
{
// Note: intentionally not using keybinding here, this must be `control` or `shift`.
final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help");
final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip");
if((!help_available) && (!tip_available)) return false;
String tip_text = "";
if(helpCondition()) {
if(help_available) tip_text = localize(helpTranslationKey + ".help");
} else if(extendedTipCondition()) {
if(tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip");
} else if(addAdvancedTooltipHints) {
if(tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : "");
if(help_available) tip_text += localize(modid + ".tooltip.hint.help");
}
if(tip_text.isEmpty()) return false;
String[] tip_list = tip_text.split("\\r?\\n");
for(String tip:tip_list) {
tooltip.add(new StringTextComponent(tip.replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(TextFormatting.GRAY));
}
return true;
}
/**
* Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT.
* Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help".
* Return value see method pattern above.
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(ItemStack stack, @Nullable IBlockReader world, List<ITextComponent> tooltip, ITooltipFlag flag, boolean addAdvancedTooltipHints)
{ return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); }
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(String translation_key, List<ITextComponent> tooltip)
{
if(!Auxiliaries.hasTranslation(translation_key)) return false;
tooltip.add(new StringTextComponent(localize(translation_key).replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(TextFormatting.GRAY));
return true;
}
}
@SuppressWarnings("unused")
public static void playerChatMessage(final PlayerEntity player, final String message)
{
String s = message.trim();
if(!s.isEmpty()) player.sendMessage(new TranslationTextComponent(s), new UUID(0,0));
}
public static @Nullable ITextComponent unserializeTextComponent(String serialized)
{ return ITextComponent.Serializer.fromJson(serialized); }
public static String serializeTextComponent(ITextComponent tc)
{ return (tc==null) ? ("") : (ITextComponent.Serializer.toJson(tc)); }
// -------------------------------------------------------------------------------------------------------------------
// Item NBT data
// -------------------------------------------------------------------------------------------------------------------
/**
* Equivalent to getDisplayName(), returns null if no custom name is set.
*/
public static @Nullable ITextComponent getItemLabel(ItemStack stack)
{
CompoundNBT nbt = stack.getTagElement("display");
if(nbt != null && nbt.contains("Name", 8)) {
try {
ITextComponent tc = unserializeTextComponent(nbt.getString("Name"));
if(tc != null) return tc;
nbt.remove("Name");
} catch(Exception e) {
nbt.remove("Name");
}
}
return null;
}
public static ItemStack setItemLabel(ItemStack stack, @Nullable ITextComponent name)
{
if(name != null) {
CompoundNBT nbt = stack.getOrCreateTagElement("display");
nbt.putString("Name", serializeTextComponent(name));
} else {
if(stack.hasTag()) stack.removeTagKey("display");
}
return stack;
}
// -------------------------------------------------------------------------------------------------------------------
// Block handling
// -------------------------------------------------------------------------------------------------------------------
public static final AxisAlignedBB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1)
{ return new AxisAlignedBB(x0/16.0, y0/16.0, z0/16.0, x1/16.0, y1/16.0, z1/16.0); }
public static final AxisAlignedBB getRotatedAABB(AxisAlignedBB bb, Direction new_facing, boolean horizontal_rotation)
{
if(!horizontal_rotation) {
switch(new_facing.get3DDataValue()) {
case 0: return new AxisAlignedBB(1-bb.maxX, bb.minZ, bb.minY, 1-bb.minX, bb.maxZ, bb.maxY); // D
case 1: return new AxisAlignedBB(1-bb.maxX, 1-bb.maxZ, 1-bb.maxY, 1-bb.minX, 1-bb.minZ, 1-bb.minY); // U
case 2: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AxisAlignedBB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AxisAlignedBB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AxisAlignedBB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
} else {
switch(new_facing.get3DDataValue()) {
case 0: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb
case 1: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb
case 2: return new AxisAlignedBB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AxisAlignedBB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AxisAlignedBB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AxisAlignedBB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
}
return bb;
}
public static final AxisAlignedBB[] getRotatedAABB(AxisAlignedBB[] bbs, Direction new_facing, boolean horizontal_rotation)
{
final AxisAlignedBB[] transformed = new AxisAlignedBB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation);
return transformed;
}
public static final AxisAlignedBB getYRotatedAABB(AxisAlignedBB bb, int clockwise_90deg_steps)
{
final Direction direction_map[] = new Direction[]{Direction.NORTH,Direction.EAST,Direction.SOUTH,Direction.WEST};
return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps+4096) & 0x03], true);
}
public static final AxisAlignedBB[] getYRotatedAABB(AxisAlignedBB[] bbs, int clockwise_90deg_steps)
{
final AxisAlignedBB[] transformed = new AxisAlignedBB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps);
return transformed;
}
public static final AxisAlignedBB getMirroredAABB(AxisAlignedBB bb, Axis axis)
{
switch(axis) {
case X: return new AxisAlignedBB(1-bb.maxX, bb.minY, bb.minZ, 1-bb.minX, bb.maxY, bb.maxZ);
case Y: return new AxisAlignedBB(bb.minX, 1-bb.maxY, bb.minZ, bb.maxX, 1-bb.minY, bb.maxZ);
case Z: return new AxisAlignedBB(bb.minX, bb.minY, 1-bb.maxZ, bb.maxX, bb.maxY, 1-bb.minZ);
default: return bb;
}
}
public static final AxisAlignedBB[] getMirroredAABB(AxisAlignedBB[] bbs, Axis axis)
{
final AxisAlignedBB[] transformed = new AxisAlignedBB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis);
return transformed;
}
public static final VoxelShape getUnionShape(AxisAlignedBB ... aabbs)
{
VoxelShape shape = VoxelShapes.empty();
for(AxisAlignedBB aabb: aabbs) shape = VoxelShapes.join(shape, VoxelShapes.create(aabb), IBooleanFunction.OR);
return shape;
}
public static final VoxelShape getUnionShape(AxisAlignedBB[] ... aabb_list)
{
VoxelShape shape = VoxelShapes.empty();
for(AxisAlignedBB[] aabbs:aabb_list) {
for(AxisAlignedBB aabb: aabbs) shape = VoxelShapes.joinUnoptimized(shape, VoxelShapes.create(aabb), IBooleanFunction.OR);
}
return shape;
}
// -------------------------------------------------------------------------------------------------------------------
// JAR resource related
// -------------------------------------------------------------------------------------------------------------------
public static String loadResourceText(InputStream is)
{
try {
if(is==null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
return br.lines().collect(Collectors.joining("\n"));
} catch(Throwable e) {
return "";
}
}
public static String loadResourceText(String path)
{ return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); }
public static void logGitVersion(String mod_name)
{
try {
// Done during construction to have an exact version in case of a crash while registering.
String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim();
logInfo(mod_name+((version.isEmpty())?(" (dev build)"):(" GIT id #"+version)) + ".");
} catch(Throwable e) {
// (void)e; well, then not. Priority is not to get unneeded crashes because of version logging.
}
}
}
/*
* @file Auxiliaries.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General commonly used functionality.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.ChatFormatting;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import wile.engineersdecor.ModConfig;
import javax.annotation.Nullable;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Auxiliaries
{
private static String modid;
private static Logger logger;
private static Supplier<CompoundTag> server_config_supplier = CompoundTag::new;
public static void init(String modid, Logger logger, Supplier<CompoundTag> server_config_supplier)
{
Auxiliaries.modid = modid;
Auxiliaries.logger = logger;
Auxiliaries.server_config_supplier = server_config_supplier;
}
// -------------------------------------------------------------------------------------------------------------------
// Mod specific exports
// -------------------------------------------------------------------------------------------------------------------
public static String modid()
{ return modid; }
public static Logger logger()
{ return logger; }
// -------------------------------------------------------------------------------------------------------------------
// Sideness, system/environment, tagging interfaces
// -------------------------------------------------------------------------------------------------------------------
public interface IExperimentalFeature {}
public static boolean isModLoaded(final String registry_name)
{ return ModList.get().isLoaded(registry_name); }
public static boolean isDevelopmentMode()
{ return SharedConstants.IS_RUNNING_IN_IDE; }
@OnlyIn(Dist.CLIENT)
public static boolean isShiftDown()
{
return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) ||
InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT));
}
@OnlyIn(Dist.CLIENT)
public static boolean isCtrlDown()
{
return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) ||
InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL));
}
// -------------------------------------------------------------------------------------------------------------------
// Logging
// -------------------------------------------------------------------------------------------------------------------
public static void logInfo(final String msg)
{ logger.info(msg); }
public static void logWarn(final String msg)
{ logger.warn(msg); }
public static void logError(final String msg)
{ logger.error(msg); }
public static void logDebug(final String msg)
{ if(ModConfig.withDebugLogging()) logger.info(msg); }
// -------------------------------------------------------------------------------------------------------------------
// Localization, text formatting
// -------------------------------------------------------------------------------------------------------------------
/**
* Text localisation wrapper, implicitly prepends `MODID` to the
* translation keys. Forces formatting argument, nullable if no special formatting shall be applied..
*/
public static TranslatableComponent localizable(String modtrkey, Object... args)
{
return new TranslatableComponent((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid+"."+modtrkey), args);
}
public static TranslatableComponent localizable(String modtrkey)
{ return localizable(modtrkey, new Object[]{}); }
public static TranslatableComponent localizable_block_key(String blocksubkey)
{ return new TranslatableComponent("block."+modid+"."+blocksubkey); }
@OnlyIn(Dist.CLIENT)
public static String localize(String translationKey, Object... args)
{
TranslatableComponent tr = new TranslatableComponent(translationKey, args);
tr.withStyle(ChatFormatting.RESET);
final String ft = tr.getString();
if(ft.contains("${")) {
// Non-recursive, non-argument lang file entry cross referencing.
Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher mt = pt.matcher(ft);
StringBuffer sb = new StringBuffer();
while(mt.find()) {
String m = mt.group(1);
if(m.contains("?")) {
String[] kv = m.split("\\?", 2);
String key = kv[0].trim();
boolean not = key.startsWith("!");
if(not) key = key.replaceFirst("!", "");
m = kv[1].trim();
if(!server_config_supplier.get().contains(key)) {
m = "";
} else {
boolean r = server_config_supplier.get().getBoolean(key);
if(not) r = !r;
if(!r) m = "";
}
}
mt.appendReplacement(sb, Matcher.quoteReplacement((new TranslatableComponent(m)).getString().trim()));
}
mt.appendTail(sb);
return sb.toString();
} else {
return ft;
}
}
/**
* Returns true if a given key is translated for the current language.
*/
@OnlyIn(Dist.CLIENT)
public static boolean hasTranslation(String key)
{ return net.minecraft.client.resources.language.I18n.exists(key); }
public static final class Tooltip
{
@OnlyIn(Dist.CLIENT)
public static boolean extendedTipCondition()
{ return isShiftDown(); }
@OnlyIn(Dist.CLIENT)
public static boolean helpCondition()
{ return isShiftDown() && isCtrlDown(); }
/**
* Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT.
* Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or
* no translation found).
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List<Component> tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints)
{
// Note: intentionally not using keybinding here, this must be `control` or `shift`.
final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help");
final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip");
if((!help_available) && (!tip_available)) return false;
String tip_text = "";
if(helpCondition()) {
if(help_available) tip_text = localize(helpTranslationKey + ".help");
} else if(extendedTipCondition()) {
if(tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip");
} else if(addAdvancedTooltipHints) {
if(tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : "");
if(help_available) tip_text += localize(modid + ".tooltip.hint.help");
}
if(tip_text.isEmpty()) return false;
String[] tip_list = tip_text.split("\\r?\\n");
for(String tip:tip_list) {
tooltip.add(new TextComponent(tip.replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY));
}
return true;
}
/**
* Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT.
* Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help".
* Return value see method pattern above.
*/
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(ItemStack stack, @Nullable BlockGetter world, List<Component> tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints)
{ return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); }
@OnlyIn(Dist.CLIENT)
public static boolean addInformation(String translation_key, List<Component> tooltip)
{
if(!Auxiliaries.hasTranslation(translation_key)) return false;
tooltip.add(new TextComponent(localize(translation_key).replaceAll("\\s+$","").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY));
return true;
}
}
@SuppressWarnings("unused")
public static void playerChatMessage(final Player player, final String message)
{
String s = message.trim();
if(!s.isEmpty()) player.sendMessage(new TranslatableComponent(s), new UUID(0,0));
}
public static @Nullable Component unserializeTextComponent(String serialized)
{ return Component.Serializer.fromJson(serialized); }
public static String serializeTextComponent(Component tc)
{ return (tc==null) ? ("") : (Component.Serializer.toJson(tc)); }
// -------------------------------------------------------------------------------------------------------------------
// Item NBT data
// -------------------------------------------------------------------------------------------------------------------
/**
* Equivalent to getDisplayName(), returns null if no custom name is set.
*/
public static @Nullable Component getItemLabel(ItemStack stack)
{
CompoundTag nbt = stack.getTagElement("display");
if(nbt != null && nbt.contains("Name", 8)) {
try {
Component tc = unserializeTextComponent(nbt.getString("Name"));
if(tc != null) return tc;
nbt.remove("Name");
} catch(Exception e) {
nbt.remove("Name");
}
}
return null;
}
public static ItemStack setItemLabel(ItemStack stack, @Nullable Component name)
{
if(name != null) {
CompoundTag nbt = stack.getOrCreateTagElement("display");
nbt.putString("Name", serializeTextComponent(name));
} else {
if(stack.hasTag()) stack.removeTagKey("display");
}
return stack;
}
// -------------------------------------------------------------------------------------------------------------------
// Block handling
// -------------------------------------------------------------------------------------------------------------------
public static boolean isWaterLogged(BlockState state)
{ return state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(BlockStateProperties.WATERLOGGED); }
public static AABB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1)
{ return new AABB(x0/16.0, y0/16.0, z0/16.0, x1/16.0, y1/16.0, z1/16.0); }
public static AABB getRotatedAABB(AABB bb, Direction new_facing)
{ return getRotatedAABB(bb, new_facing, false); }
public static AABB[] getRotatedAABB(AABB[] bb, Direction new_facing)
{ return getRotatedAABB(bb, new_facing, false); }
public static AABB getRotatedAABB(AABB bb, Direction new_facing, boolean horizontal_rotation)
{
if(!horizontal_rotation) {
switch(new_facing.get3DDataValue()) {
case 0: return new AABB(1-bb.maxX, bb.minZ, bb.minY, 1-bb.minX, bb.maxZ, bb.maxY); // D
case 1: return new AABB(1-bb.maxX, 1-bb.maxZ, 1-bb.maxY, 1-bb.minX, 1-bb.minZ, 1-bb.minY); // U
case 2: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AABB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AABB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AABB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
} else {
switch(new_facing.get3DDataValue()) {
case 0: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb
case 1: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb
case 2: return new AABB( bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb
case 3: return new AABB(1-bb.maxX, bb.minY, 1-bb.maxZ, 1-bb.minX, bb.maxY, 1-bb.minZ); // S
case 4: return new AABB( bb.minZ, bb.minY, 1-bb.maxX, bb.maxZ, bb.maxY, 1-bb.minX); // W
case 5: return new AABB(1-bb.maxZ, bb.minY, bb.minX, 1-bb.minZ, bb.maxY, bb.maxX); // E
}
}
return bb;
}
public static AABB[] getRotatedAABB(AABB[] bbs, Direction new_facing, boolean horizontal_rotation)
{
final AABB[] transformed = new AABB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation);
return transformed;
}
public static AABB getYRotatedAABB(AABB bb, int clockwise_90deg_steps)
{
final Direction[] direction_map = new Direction[]{Direction.NORTH,Direction.EAST,Direction.SOUTH,Direction.WEST};
return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps+4096) & 0x03], true);
}
public static AABB[] getYRotatedAABB(AABB[] bbs, int clockwise_90deg_steps)
{
final AABB[] transformed = new AABB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps);
return transformed;
}
public static AABB getMirroredAABB(AABB bb, Direction.Axis axis)
{
return switch (axis) {
case X -> new AABB(1 - bb.maxX, bb.minY, bb.minZ, 1 - bb.minX, bb.maxY, bb.maxZ);
case Y -> new AABB(bb.minX, 1 - bb.maxY, bb.minZ, bb.maxX, 1 - bb.minY, bb.maxZ);
case Z -> new AABB(bb.minX, bb.minY, 1 - bb.maxZ, bb.maxX, bb.maxY, 1 - bb.minZ);
};
}
public static AABB[] getMirroredAABB(AABB[] bbs, Direction.Axis axis)
{
final AABB[] transformed = new AABB[bbs.length];
for(int i=0; i<bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis);
return transformed;
}
public static VoxelShape getUnionShape(AABB ... aabbs)
{
VoxelShape shape = Shapes.empty();
for(AABB aabb: aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR);
return shape;
}
public static VoxelShape getUnionShape(AABB[] ... aabb_list)
{
VoxelShape shape = Shapes.empty();
for(AABB[] aabbs:aabb_list) {
for(AABB aabb: aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR);
}
return shape;
}
public static final class BlockPosRange implements Iterable<BlockPos>
{
private final int x0, x1, y0, y1, z0, z1;
public BlockPosRange(int x0, int y0, int z0, int x1, int y1, int z1)
{
this.x0 = Math.min(x0,x1); this.x1 = Math.max(x0,x1);
this.y0 = Math.min(y0,y1); this.y1 = Math.max(y0,y1);
this.z0 = Math.min(z0,z1); this.z1 = Math.max(z0,z1);
}
public static BlockPosRange of(AABB range)
{
return new BlockPosRange(
(int)Math.floor(range.minX),
(int)Math.floor(range.minY),
(int)Math.floor(range.minZ),
(int)Math.floor(range.maxX-.0625),
(int)Math.floor(range.maxY-.0625),
(int)Math.floor(range.maxZ-.0625)
);
}
public int getXSize()
{ return x1-x0+1; }
public int getYSize()
{ return y1-y0+1; }
public int getZSize()
{ return z1-z0+1; }
public int getArea()
{ return getXSize() * getZSize(); }
public int getHeight()
{ return getYSize(); }
public int getVolume()
{ return getXSize() * getYSize() * getZSize(); }
public BlockPos byXZYIndex(int xyz_index)
{
final int xsz=getXSize(), ysz=getYSize(), zsz=getZSize();
xyz_index = xyz_index % (xsz*ysz*zsz);
final int y = xyz_index / (xsz*zsz);
xyz_index -= y * (xsz*zsz);
final int z = xyz_index / xsz;
xyz_index -= z * xsz;
final int x = xyz_index;
return new BlockPos(x0+x, y0+y, z0+z);
}
public BlockPos byXZIndex(int xz_index, int y_offset)
{
final int xsz=getXSize(), zsz=getZSize();
xz_index = xz_index % (xsz*zsz);
final int z = xz_index / xsz;
xz_index -= z * xsz;
final int x = xz_index;
return new BlockPos(x0+x, y0+y_offset, z0+z);
}
public static final class BlockRangeIterator implements Iterator<BlockPos>
{
private final BlockPosRange range_;
private int x,y,z;
public BlockRangeIterator(BlockPosRange range)
{ range_ = range; x=range.x0; y=range.y0; z=range.z0; }
@Override
public boolean hasNext()
{ return (z <= range_.z1); }
@Override
public BlockPos next()
{
if(!hasNext()) throw new NoSuchElementException();
final BlockPos pos = new BlockPos(x,y,z);
++x;
if(x > range_.x1) {
x = range_.x0;
++y;
if(y > range_.y1) {
y = range_.y0;
++z;
}
}
return pos;
}
}
@Override
public BlockRangeIterator iterator()
{ return new BlockRangeIterator(this); }
public Stream<BlockPos> stream()
{ return java.util.stream.StreamSupport.stream(spliterator(), false); }
}
// -------------------------------------------------------------------------------------------------------------------
// JAR resource related
// -------------------------------------------------------------------------------------------------------------------
public static String loadResourceText(InputStream is)
{
try {
if(is==null) return "";
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
return br.lines().collect(Collectors.joining("\n"));
} catch(Throwable e) {
return "";
}
}
public static String loadResourceText(String path)
{ return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); }
public static void logGitVersion(String mod_name)
{
try {
// Done during construction to have an exact version in case of a crash while registering.
String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim();
logInfo(mod_name+((version.isEmpty())?(" (dev build)"):(" GIT id #"+version)) + ".");
} catch(Throwable e) {
// (void)e; well, then not. Priority is not to get unneeded crashes because of version logging.
}
}
}

View file

@ -0,0 +1,364 @@
/*
* @file Recipes.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe utility functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.NonNullList;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.CraftingContainer;
import net.minecraft.world.item.EnchantedBookItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.item.enchantment.Enchantment;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.EnchantmentInstance;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.ComposterBlock;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.brewing.BrewingRecipeRegistry;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.BiPredicate;
public class Crafting
{
// -------------------------------------------------------------------------------------------------------------------
public static final class CraftingGrid extends CraftingContainer
{
protected static final CraftingGrid instance3x3 = new CraftingGrid(3,3);
protected CraftingGrid(int width, int height)
{ super(new AbstractContainerMenu(null,0) { public boolean stillValid(Player player) { return false; } }, width, height); }
protected void fill(Container grid)
{ for(int i=0; i<getContainerSize(); ++i) setItem(i, i>=grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i)); }
public List<CraftingRecipe> getRecipes(Level world, Container grid)
{ fill(grid); return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world); }
public List<ItemStack> getRemainingItems(Level world, Container grid, CraftingRecipe recipe)
{ fill(grid); return recipe.getRemainingItems(this); }
public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe)
{ fill(grid); return recipe.assemble(this); }
}
/**
* Returns a Crafting recipe by registry name.
*/
public static Optional<CraftingRecipe> getCraftingRecipe(Level world, ResourceLocation recipe_id)
{
Recipe<?> recipe = world.getRecipeManager().byKey(recipe_id).orElse(null);
return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe)recipe) : Optional.empty();
}
/**
* Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory.
*/
public static List<CraftingRecipe> get3x3CraftingRecipes(Level world, Container crafting_grid_slots)
{ return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots); }
/**
* Returns a recipe by the first N slots (crafting grid slots).
*/
public static Optional<CraftingRecipe> get3x3CraftingRecipe(Level world, Container crafting_grid_slots)
{ return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst(); }
/**
* Returns the result item of the recipe with the given grid layout.
*/
public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe)
{ return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe); }
/**
* Returns the items remaining in the grid after crafting 3x3.
*/
public static List<ItemStack> get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe)
{ return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe); }
public static List<ItemStack> get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid)
{
final int width = 3;
final int height = 3;
if(!recipe.canCraftInDimensions(width,height)) return Collections.emptyList();
List<ItemStack> used = new ArrayList<>(); //NonNullList.withSize(width*height);
for(int i=width*height; i>0; --i) used.add(ItemStack.EMPTY);
Container check_inventory = Inventories.copyOf(item_inventory);
Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory);
final List<Ingredient> ingredients = recipe.getIngredients();
final List<ItemStack> preferred = new ArrayList<>(width*height);
if(crafting_grid != null) {
for(int i=0; i<crafting_grid.getContainerSize(); ++i) {
ItemStack stack = crafting_grid.getItem(i);
if(stack.isEmpty()) continue;
stack = stack.copy();
stack.setCount(1);
if(!source.extract(stack).isEmpty()) preferred.add(stack);
}
}
for(int i=0; i<ingredients.size(); ++i) {
final Ingredient ingredient = ingredients.get(i);
if(ingredient == Ingredient.EMPTY) continue;
ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY);
if(!stack.isEmpty()) {
preferred.remove(stack);
} else {
stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY);
if(stack.isEmpty()) return Collections.emptyList();
stack = stack.copy();
stack.setCount(1);
if(source.extract(stack).isEmpty()) return Collections.emptyList();
}
used.set(i, stack);
}
if(recipe instanceof ShapedRecipe shaped) {
List<ItemStack> placement = NonNullList.withSize(width*height, ItemStack.EMPTY);
for(int row=0; row<shaped.getRecipeHeight(); ++row) {
for(int col=0; col<shaped.getRecipeWidth(); ++col) {
placement.set(width*row+col, used.get(row*shaped.getRecipeWidth()+col));
}
}
return placement;
} else {
return used;
}
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns the recipe for a given input stack to smelt, null if there is no recipe
* for the given type (SMELTING,BLASTING,SMOKING, etc).
*/
public static <T extends Recipe<?>> Optional<AbstractCookingRecipe> getFurnaceRecipe(RecipeType<T> recipe_type, Level world, ItemStack input_stack)
{
if(input_stack.isEmpty()) {
return Optional.empty();
} else if(recipe_type == RecipeType.SMELTING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null);
return (recipe==null) ? Optional.empty() : Optional.of(recipe);
} else if(recipe_type == RecipeType.BLASTING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null);
return (recipe==null) ? Optional.empty() : Optional.of(recipe);
} else if(recipe_type == RecipeType.SMOKING) {
SimpleContainer inventory = new SimpleContainer(3);
inventory.setItem(0, input_stack);
SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null);
return (recipe==null) ? Optional.empty() : Optional.of(recipe);
} else {
return Optional.empty();
}
}
public static <T extends Recipe<?>> int getSmeltingTimeNeeded(RecipeType<T> recipe_type, Level world, ItemStack stack)
{
if(stack.isEmpty()) return 0;
final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0);
return (t<=0) ? 200 : t;
}
/**
* Returns the burn time of an item when used as fuel, 0 if it is no fuel.
*/
public static int getFuelBurntime(Level world, ItemStack stack)
{
if(stack.isEmpty()) return 0;
int t = ForgeHooks.getBurnTime(stack, null);
return Math.max(t, 0);
}
/**
* Returns true if an item can be used as fuel.
*/
public static boolean isFuel(Level world, ItemStack stack)
{ return (getFuelBurntime(world, stack) > 0) || (stack.getItem()==Items.LAVA_BUCKET); }
/**
* Returns burntime and remaining stack then the item shall be used as fuel.
*/
public static Tuple<Integer,ItemStack> consumeFuel(Level world, ItemStack stack)
{
if(stack.isEmpty()) return new Tuple<>(0,stack);
int burnime = getFuelBurntime(world, stack);
if((stack.getItem()==Items.LAVA_BUCKET)) {
if(burnime <= 0) burnime = 1000*20;
return new Tuple<>(burnime,new ItemStack(Items.BUCKET));
} else if(burnime <= 0) {
return new Tuple<>(0,stack);
} else {
ItemStack left_over = stack.copy();
left_over.shrink(1);
return new Tuple<>(burnime,left_over);
}
}
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns true if the item can be used as brewing fuel.
*/
public static boolean isBrewingFuel(Level world, ItemStack stack)
{ return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD); }
/**
* Returns true if the item can be used as brewing ingredient.
*/
public static boolean isBrewingIngredient(Level world, ItemStack stack)
{ return BrewingRecipeRegistry.isValidIngredient(stack); }
/**
* Returns true if the item can be used as brewing bottle.
*/
public static boolean isBrewingInput(Level world, ItemStack stack)
{ return BrewingRecipeRegistry.isValidInput(stack); }
/**
* Returns the burn time for brewing of the given stack.
*/
public static int getBrewingFuelBurntime(Level world, ItemStack stack)
{
if(stack.isEmpty()) return 0;
if(stack.getItem() == Items.BLAZE_POWDER) return (400*20);
if(stack.getItem() == Items.BLAZE_ROD) return (400*40);
return 0;
}
/**
* Returns brewing burn time and remaining stack if the item shall be used as fuel.
*/
public static Tuple<Integer,ItemStack> consumeBrewingFuel(Level world, ItemStack stack)
{
int burntime = getBrewingFuelBurntime(world, stack);
if(burntime <= 0) return new Tuple<>(0, stack.copy());
stack = stack.copy();
stack.shrink(1);
return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack);
}
public static final class BrewingOutput
{
public static final int DEFAULT_BREWING_TIME = 400;
public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0,0, DEFAULT_BREWING_TIME);
public final ItemStack item;
public final Container potionInventory;
public final Container ingredientInventory;
public final int potionSlot;
public final int ingredientSlot;
public final int brewTime;
public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed)
{
item = output_potion;
potionInventory = potion_inventory;
ingredientInventory = ingredient_inventory;
potionSlot = potion_slot;
ingredientSlot = ingredient_slot;
brewTime = time_needed;
}
public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory)
{
for(int potion_slot = 0; potion_slot<potion_inventory.getContainerSize(); ++potion_slot) {
final ItemStack pstack = potion_inventory.getItem(potion_slot);
if(!isBrewingInput(world, pstack)) continue;
for(int ingredient_slot = 0; ingredient_slot<ingredient_inventory.getContainerSize(); ++ingredient_slot) {
final ItemStack istack = ingredient_inventory.getItem(ingredient_slot);
if((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack))) continue;
final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack);
if(result.isEmpty()) continue;
return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME);
}
}
return BrewingOutput.EMPTY;
}
}
// -------------------------------------------------------------------------------------------------------------------
public static double getCompostingChance(ItemStack stack)
{ return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(),0); }
// -------------------------------------------------------------------------------------------------------------------
/**
* Returns the enchtments bound to the given stack.
*/
public static Map<Enchantment, Integer> getEnchantmentsOnItem(Level world, ItemStack stack)
{ return (stack.isEmpty() || (stack.getTag()==null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack); }
/**
* Returns an enchanted book with the given enchantment, emtpy stack if not applicable.
*/
public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level)
{ return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); }
/**
* Returns the accumulated repair cost for the given enchantments.
*/
public static int getEnchantmentRepairCost(Level world, Map<Enchantment, Integer> enchantments)
{
int repair_cost = 0;
for(Map.Entry<Enchantment, Integer> e:enchantments.entrySet()) repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost()
return repair_cost;
}
/**
* Trys to add an enchtment to the given stack, returns boolean success.
*/
public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level)
{
if(stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel())) return false;
final Map<Enchantment, Integer> on_item = getEnchantmentsOnItem(world, stack);
if(on_item.keySet().stream().anyMatch(ench-> ench.isCompatibleWith(enchantment))) return false;
final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level));
if((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack))) return false;
final int existing_level = on_item.getOrDefault(enchantment, 0);
if(existing_level > 0) level = Mth.clamp(level+existing_level, 1, enchantment.getMaxLevel());
on_item.put(enchantment, level);
EnchantmentHelper.setEnchantments(on_item, stack);
stack.setRepairCost(getEnchantmentRepairCost(world, on_item));
return true;
}
/**
* Removes enchantments from a stack, returns the removed enchantments.
*/
public static Map<Enchantment, Integer> removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate<Enchantment,Integer> filter)
{
if(stack.isEmpty()) return Collections.emptyMap();
final Map<Enchantment, Integer> on_item = getEnchantmentsOnItem(world, stack);
final Map<Enchantment, Integer> removed = new HashMap<>();
for(Map.Entry<Enchantment, Integer> e:on_item.entrySet()) {
if(filter.test(e.getKey(), e.getValue())) {
removed.put(e.getKey(), e.getValue());
}
}
for(Enchantment e:removed.keySet()) {
on_item.remove(e);
}
EnchantmentHelper.setEnchantments(on_item, stack);
stack.setRepairCost(getEnchantmentRepairCost(world, on_item));
return removed;
}
}

View file

@ -1,66 +0,0 @@
/*
* @file DataFixing.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Data fixing and mapping correction functionality encapsulation.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.Items;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.RegistryEvent.MissingMappings.Mapping;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class DataFixing
{
private static String modid = "";
private static Map<String, String> item_registry_renaming = new HashMap<>();
private static Map<String, String> block_registry_renaming = new HashMap<>();
public static void init(String mod_id, @Nullable Map<String, String> item_renaming, @Nullable Map<String, String> block_renaming)
{
modid = mod_id;
block_registry_renaming = new HashMap<>();
item_registry_renaming = new HashMap<>();
if(item_renaming!=null) item_registry_renaming.putAll(item_renaming);
if(block_renaming!=null) { block_registry_renaming.putAll(block_renaming); item_registry_renaming.putAll(block_renaming); }
}
public static void onDataFixMissingItemMapping(net.minecraftforge.event.RegistryEvent.MissingMappings<Item> event)
{
// Handler registered in main mod event subscription.
for(Mapping<Item> mapping: event.getMappings()) {
if(mapping.key.getNamespace() != modid) continue;
final String rm = item_registry_renaming.getOrDefault(mapping.key.getPath(), "");
if(rm.isEmpty()) continue;
final Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(modid, rm));
if((item==null) || (item==Items.AIR)) continue;
mapping.remap(item);
}
}
public static void onDataFixMissingBlockMapping(net.minecraftforge.event.RegistryEvent.MissingMappings<Block> event)
{
// Handler registered in main mod event subscription.
for(Mapping<Block> mapping: event.getMappings()) {
if(mapping.key.getNamespace() != modid) continue;
final String rm = block_registry_renaming.getOrDefault(mapping.key.getPath(), "");
if(rm.isEmpty()) continue;
final Block block = ForgeRegistries.BLOCKS.getValue(new ResourceLocation(modid, rm));
if((block==null) || (block==Blocks.AIR)) continue;
mapping.remap(block);
}
}
// @todo: Find a way to register blockstate data fixing.
}

View file

@ -1,385 +1,390 @@
/*
* @file Fluidics.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General fluid handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.fluid.Fluid;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.Tuple;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidActionResult;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class Fluidics
{
/**
* Dedicated fluid handler for a single tank.
*/
public static class SingleTankFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return tank_.isFluidValid(stack); }
@Override public int fill(FluidStack resource, FluidAction action) { return tank_.fill(resource, action); }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
private static class SingleTankOutputFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankOutputFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid().copy(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
/**
* Simple fluid tank, validator concept according to reference implementation by KingLemming.
*/
public static class Tank implements IFluidTank
{
private Predicate<FluidStack> validator_ = ((e)->true);
private BiConsumer<Tank,Integer> interaction_notifier_ = ((tank,diff)->{});
private FluidStack fluid_ = FluidStack.EMPTY;
private int capacity_;
private int fill_rate_;
private int drain_rate_;
public Tank(int capacity)
{ this(capacity, capacity, capacity); }
public Tank(int capacity, int fill_rate, int drain_rate)
{ this(capacity, fill_rate, drain_rate, e->true); }
public Tank(int capacity, int fill_rate, int drain_rate, Predicate<FluidStack> validator)
{
capacity_ = capacity;
setMaxFillRate(fill_rate);
setMaxDrainRate(drain_rate);
setValidator(validator);
}
public Tank load(CompoundNBT nbt)
{
if(nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) {
setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
} else {
clear();
}
return this;
}
public CompoundNBT save(CompoundNBT nbt)
{ if(!isEmpty()) { nbt.put("tank", fluid_.writeToNBT(new CompoundNBT())); } return nbt; }
public void reset()
{ clear(); }
public Tank clear()
{ setFluid(null); return this; }
public int getCapacity()
{ return capacity_; }
public Tank setCapacity(int capacity)
{ capacity_ = capacity; return this; }
public int getMaxDrainRate()
{ return drain_rate_; }
public Tank setMaxDrainRate(int rate)
{ drain_rate_ = MathHelper.clamp(rate, 0, capacity_); return this; }
public int getMaxFillRate()
{ return fill_rate_; }
public Tank setMaxFillRate(int rate)
{ fill_rate_ = MathHelper.clamp(rate, 0, capacity_); return this; }
public Tank setValidator(Predicate<FluidStack> validator)
{ validator_ = (validator!=null) ? validator : ((e)->true); return this; }
public Tank setInteractionNotifier(BiConsumer<Tank,Integer> notifier)
{ interaction_notifier_ = (notifier!=null) ? notifier : ((tank,diff)->{}); return this; }
public LazyOptional<IFluidHandler> createFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); }
public LazyOptional<IFluidHandler> createOutputFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); }
// IFluidTank ------------------------------------------------------------------------------------
@Nonnull
public FluidStack getFluid()
{ return fluid_; }
public void setFluid(@Nullable FluidStack stack)
{ fluid_ = (stack==null) ? FluidStack.EMPTY : stack; }
public int getFluidAmount()
{ return fluid_.getAmount(); }
public boolean isEmpty()
{ return fluid_.isEmpty(); }
public boolean isFull()
{ return getFluidAmount() >= getCapacity(); }
public boolean isFluidValid(FluidStack stack)
{ return validator_.test(stack); }
public boolean isFluidEqual(FluidStack stack)
{ return (stack==null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs==null) || fs.isEmpty() || (!isFluidValid(fs))) {
return 0;
} else if(action.simulate()) {
if(fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount());
if(!fluid_.isFluidEqual(fs)) return 0;
return Math.min(capacity_-fluid_.getAmount(), fs.getAmount());
} else if(fluid_.isEmpty()) {
fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount()));
return fluid_.getAmount();
} else if(!fluid_.isFluidEqual(fs)) {
return 0;
} else {
int amount = capacity_ - fluid_.getAmount();
if(fs.getAmount() < amount) {
fluid_.grow(fs.getAmount());
amount = fs.getAmount();
} else {
fluid_.setAmount(capacity_);
}
if(amount != 0) interaction_notifier_.accept(this, amount);
return amount;
}
}
@Nonnull
public FluidStack drain(int maxDrain)
{ return drain(maxDrain, FluidAction.EXECUTE); }
@Nonnull
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{ return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); }
@Nonnull
@Override
public FluidStack drain(int maxDrain, FluidAction action)
{
final int amount = Math.min(fluid_.getAmount(), maxDrain);
final FluidStack stack = new FluidStack(fluid_, amount);
if((amount > 0) && action.execute()) {
fluid_.shrink(amount);
if(fluid_.isEmpty()) fluid_ = FluidStack.EMPTY;
if(amount != 0) interaction_notifier_.accept(this, -amount);
}
return stack;
}
}
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
*/
public static boolean manualFluidHandlerInteraction(World world, BlockPos pos, @Nullable Direction side, PlayerEntity player, Hand hand)
{ return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; }
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
* Returns the fluid and (possibly negative) amount that transferred from the item into the block.
*/
public static @Nullable Tuple<Fluid, Integer> manualTrackedFluidHandlerInteraction(World world, BlockPos pos, @Nullable Direction side, PlayerEntity player, Hand hand)
{
if(world.isClientSide()) return null;
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) return null;
final IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null);
if(fh==null) return null;
final IItemHandler ih = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).orElse(null);
if(ih==null) return null;
FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) return null;
final ItemStack rstack = far.getResult().copy();
player.setItemInHand(hand, far.getResult());
final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null);
final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null);
if((fh_before==null) || (fh_after==null) || (fh_after.getTanks()!=fh_before.getTanks())) return null; // should not be, but y'never know.
for(int i=0; i<fh_before.getTanks(); ++i) {
final int vol_before = fh_before.getFluidInTank(i).getAmount();
final int vol_after = fh_after.getFluidInTank(i).getAmount();
if(vol_before != vol_after) {
return new Tuple<>(
(vol_before>0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()),
(vol_before - vol_after)
);
}
}
return null;
}
public static int fill(World world, BlockPos pos, Direction side, FluidStack fs, FluidAction action)
{
IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null);
return (fh==null) ? (0) : (fh.fill(fs, action));
}
public static int fill(World world, BlockPos pos, Direction side, FluidStack fs)
{ return fill(world, pos, side, fs, FluidAction.EXECUTE); }
/**
* Fluid tank access when itemized.
*/
public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider
{
private final LazyOptional<IFluidHandlerItem> handler_ = LazyOptional.of(()->this);
private final Function<ItemStack, CompoundNBT> nbt_getter_;
private final BiConsumer<ItemStack, CompoundNBT> nbt_setter_;
private final Predicate<FluidStack> validator_;
private final ItemStack container_;
private final int capacity_;
private final int transfer_rate_;
public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate,
Function<ItemStack, CompoundNBT> nbt_getter,
BiConsumer<ItemStack, CompoundNBT> nbt_setter,
Predicate<FluidStack> validator)
{
container_ = container;
capacity_ = capacity;
transfer_rate_ = transfer_rate;
nbt_getter_ = nbt_getter;
nbt_setter_ = nbt_setter;
validator_ = (validator!=null) ? validator : (e->true);
}
@Override
public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction side)
{ return (capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY) ? handler_.cast() : LazyOptional.empty(); }
protected FluidStack readnbt()
{
final CompoundNBT nbt = nbt_getter_.apply(container_);
return ((nbt==null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt);
}
protected void writenbt(FluidStack fs)
{
CompoundNBT nbt = new CompoundNBT();
if(!fs.isEmpty()) fs.writeToNBT(nbt);
nbt_setter_.accept(container_, nbt);
}
@Override
public ItemStack getContainer()
{ return container_; }
@Override
public int getTanks()
{ return 1; }
@Override
public FluidStack getFluidInTank(int tank)
{ return readnbt(); }
@Override
public int getTankCapacity(int tank)
{ return capacity_; }
@Override
public boolean isFluidValid(int tank, FluidStack fs)
{ return isFluidValid(fs); }
public boolean isFluidValid(FluidStack fs)
{ return validator_.test(fs); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount()!=1))) return 0;
FluidStack tank = readnbt();
final int amount = Math.min(Math.min(fs.getAmount(),transfer_rate_), capacity_-tank.getAmount());
if(amount <= 0) return 0;
if(tank.isEmpty()) {
if(action.execute()) {
tank = new FluidStack(fs.getFluid(), amount, fs.getTag());
writenbt(tank);
}
} else {
if(!tank.isFluidEqual(fs)) {
return 0;
} else if(action.execute()) {
tank.grow(amount);
writenbt(tank);
}
}
return amount;
}
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (container_.getCount()!=1)) return FluidStack.EMPTY;
final FluidStack tank = readnbt();
if((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY;
return drain(fs.getAmount(), action);
}
@Override
public FluidStack drain(int max, FluidAction action)
{
if((max<=0) || (container_.getCount()!=1)) return FluidStack.EMPTY;
FluidStack tank = readnbt();
if(tank.isEmpty()) return FluidStack.EMPTY;
final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_);
final FluidStack fs = tank.copy();
fs.setAmount(amount);
if(action.execute()) { tank.shrink(amount); writenbt(tank); }
return fs;
}
}
}
/*
* @file Fluidics.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General fluid handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.util.Tuple;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.material.Fluid;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidActionResult;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.IFluidHandlerItem;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.items.IItemHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class Fluidics
{
public static class SingleTankFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return tank_.isFluidValid(stack); }
@Override public int fill(FluidStack resource, FluidAction action) { return tank_.fill(resource, action); }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
private static class SingleTankOutputFluidHandler implements IFluidHandler
{
private final IFluidTank tank_;
public SingleTankOutputFluidHandler(IFluidTank tank) { tank_ = tank; }
@Override public int getTanks() { return 1; }
@Override public FluidStack getFluidInTank(int tank) { return tank_.getFluid().copy(); }
@Override public int getTankCapacity(int tank) { return tank_.getCapacity(); }
@Override public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { return true; }
@Override public int fill(FluidStack resource, FluidAction action) { return 0; }
@Override public FluidStack drain(FluidStack resource, FluidAction action) { return tank_.drain(resource, action); }
@Override public FluidStack drain(int maxDrain, FluidAction action) { return tank_.drain(maxDrain, action); }
}
public static class Tank implements IFluidTank
{
private Predicate<FluidStack> validator_ = ((e)->true);
private BiConsumer<Tank,Integer> interaction_notifier_ = ((tank,diff)->{});
private FluidStack fluid_ = FluidStack.EMPTY;
private int capacity_;
private int fill_rate_;
private int drain_rate_;
public Tank(int capacity)
{ this(capacity, capacity, capacity); }
public Tank(int capacity, int fill_rate, int drain_rate)
{ this(capacity, fill_rate, drain_rate, e->true); }
public Tank(int capacity, int fill_rate, int drain_rate, Predicate<FluidStack> validator)
{
capacity_ = capacity;
setMaxFillRate(fill_rate);
setMaxDrainRate(drain_rate);
setValidator(validator);
}
public Tank load(CompoundTag nbt)
{
if(nbt.contains("tank", Constants.NBT.TAG_COMPOUND)) {
setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank")));
} else {
clear();
}
return this;
}
public CompoundTag save(CompoundTag nbt)
{ if(!isEmpty()) { nbt.put("tank", fluid_.writeToNBT(new CompoundTag())); } return nbt; }
public void reset()
{ clear(); }
public Tank clear()
{ setFluid(null); return this; }
public int getCapacity()
{ return capacity_; }
public Tank setCapacity(int capacity)
{ capacity_ = capacity; return this; }
public int getMaxDrainRate()
{ return drain_rate_; }
public Tank setMaxDrainRate(int rate)
{ drain_rate_ = Mth.clamp(rate, 0, capacity_); return this; }
public int getMaxFillRate()
{ return fill_rate_; }
public Tank setMaxFillRate(int rate)
{ fill_rate_ = Mth.clamp(rate, 0, capacity_); return this; }
public Tank setValidator(Predicate<FluidStack> validator)
{ validator_ = (validator!=null) ? validator : ((e)->true); return this; }
public Tank setInteractionNotifier(BiConsumer<Tank,Integer> notifier)
{ interaction_notifier_ = (notifier!=null) ? notifier : ((tank,diff)->{}); return this; }
public LazyOptional<IFluidHandler> createFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); }
public LazyOptional<IFluidHandler> createOutputFluidHandler()
{ return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); }
// IFluidTank ------------------------------------------------------------------------------------
@Nonnull
public FluidStack getFluid()
{ return fluid_; }
public void setFluid(@Nullable FluidStack stack)
{ fluid_ = (stack==null) ? FluidStack.EMPTY : stack; }
public int getFluidAmount()
{ return fluid_.getAmount(); }
public boolean isEmpty()
{ return fluid_.isEmpty(); }
public boolean isFull()
{ return getFluidAmount() >= getCapacity(); }
public boolean isFluidValid(FluidStack stack)
{ return validator_.test(stack); }
public boolean isFluidEqual(FluidStack stack)
{ return (stack==null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs==null) || fs.isEmpty() || (!isFluidValid(fs))) {
return 0;
} else if(action.simulate()) {
if(fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount());
if(!fluid_.isFluidEqual(fs)) return 0;
return Math.min(capacity_-fluid_.getAmount(), fs.getAmount());
} else if(fluid_.isEmpty()) {
fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount()));
return fluid_.getAmount();
} else if(!fluid_.isFluidEqual(fs)) {
return 0;
} else {
int amount = capacity_ - fluid_.getAmount();
if(fs.getAmount() < amount) {
fluid_.grow(fs.getAmount());
amount = fs.getAmount();
} else {
fluid_.setAmount(capacity_);
}
if(amount != 0) interaction_notifier_.accept(this, amount);
return amount;
}
}
@Nonnull
public FluidStack drain(int maxDrain)
{ return drain(maxDrain, FluidAction.EXECUTE); }
@Nonnull
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{ return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); }
@Nonnull
@Override
public FluidStack drain(int maxDrain, FluidAction action)
{
final int amount = Math.min(fluid_.getAmount(), maxDrain);
final FluidStack stack = new FluidStack(fluid_, amount);
if((amount > 0) && action.execute()) {
fluid_.shrink(amount);
if(fluid_.isEmpty()) fluid_ = FluidStack.EMPTY;
if(amount != 0) interaction_notifier_.accept(this, -amount);
}
return stack;
}
}
// -------------------------------------------------------------------------------------------------------------------
public static @Nullable IFluidHandler handler(Level world, BlockPos pos, @Nullable Direction side)
{ return FluidUtil.getFluidHandler(world, pos, side).orElse(null); }
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
*/
public static boolean manualFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand)
{ return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; }
public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, IFluidHandler handler)
{ return FluidUtil.interactWithFluidHandler(player, hand, handler); }
/**
* Fills or drains items with fluid handlers from or into tile blocks with fluid handlers.
* Returns the fluid and (possibly negative) amount that transferred from the item into the block.
*/
public static @Nullable Tuple<Fluid, Integer> manualTrackedFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand)
{
if(world.isClientSide()) return null;
final ItemStack held = player.getItemInHand(hand);
if(held.isEmpty()) return null;
final IFluidHandler fh = handler(world, pos, side);
if(fh==null) return null;
final IItemHandler ih = player.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).orElse(null);
if(ih==null) return null;
FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true);
if(!far.isSuccess()) return null;
final ItemStack rstack = far.getResult().copy();
player.setItemInHand(hand, far.getResult());
final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null);
final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null);
if((fh_before==null) || (fh_after==null) || (fh_after.getTanks()!=fh_before.getTanks())) return null; // should not be, but y'never know.
for(int i=0; i<fh_before.getTanks(); ++i) {
final int vol_before = fh_before.getFluidInTank(i).getAmount();
final int vol_after = fh_after.getFluidInTank(i).getAmount();
if(vol_before != vol_after) {
return new Tuple<>(
(vol_before>0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()),
(vol_before - vol_after)
);
}
}
return null;
}
public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, Level world, BlockPos pos, @Nullable Direction side)
{ return FluidUtil.interactWithFluidHandler(player, hand, world, pos, side); }
public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs, FluidAction action)
{
IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null);
return (fh==null) ? (0) : (fh.fill(fs, action));
}
public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs)
{ return fill(world, pos, side, fs, FluidAction.EXECUTE); }
/**
* Fluid tank access when itemized.
*/
public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider
{
private final LazyOptional<IFluidHandlerItem> handler_ = LazyOptional.of(()->this);
private final Function<ItemStack, CompoundTag> nbt_getter_;
private final BiConsumer<ItemStack, CompoundTag> nbt_setter_;
private final Predicate<FluidStack> validator_;
private final ItemStack container_;
private final int capacity_;
private final int transfer_rate_;
public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate,
Function<ItemStack, CompoundTag> nbt_getter,
BiConsumer<ItemStack, CompoundTag> nbt_setter,
Predicate<FluidStack> validator)
{
container_ = container;
capacity_ = capacity;
transfer_rate_ = transfer_rate;
nbt_getter_ = nbt_getter;
nbt_setter_ = nbt_setter;
validator_ = (validator!=null) ? validator : (e->true);
}
@Override
public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction side)
{ return (capability == CapabilityFluidHandler.FLUID_HANDLER_ITEM_CAPABILITY) ? handler_.cast() : LazyOptional.empty(); }
protected FluidStack readnbt()
{
final CompoundTag nbt = nbt_getter_.apply(container_);
return ((nbt==null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt);
}
protected void writenbt(FluidStack fs)
{
CompoundTag nbt = new CompoundTag();
if(!fs.isEmpty()) fs.writeToNBT(nbt);
nbt_setter_.accept(container_, nbt);
}
@Override
public ItemStack getContainer()
{ return container_; }
@Override
public int getTanks()
{ return 1; }
@Override
public FluidStack getFluidInTank(int tank)
{ return readnbt(); }
@Override
public int getTankCapacity(int tank)
{ return capacity_; }
@Override
public boolean isFluidValid(int tank, FluidStack fs)
{ return isFluidValid(fs); }
public boolean isFluidValid(FluidStack fs)
{ return validator_.test(fs); }
@Override
public int fill(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount()!=1))) return 0;
FluidStack tank = readnbt();
final int amount = Math.min(Math.min(fs.getAmount(),transfer_rate_), capacity_-tank.getAmount());
if(amount <= 0) return 0;
if(tank.isEmpty()) {
if(action.execute()) {
tank = new FluidStack(fs.getFluid(), amount, fs.getTag());
writenbt(tank);
}
} else {
if(!tank.isFluidEqual(fs)) {
return 0;
} else if(action.execute()) {
tank.grow(amount);
writenbt(tank);
}
}
return amount;
}
@Override
public FluidStack drain(FluidStack fs, FluidAction action)
{
if((fs.isEmpty()) || (container_.getCount()!=1)) return FluidStack.EMPTY;
final FluidStack tank = readnbt();
if((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY;
return drain(fs.getAmount(), action);
}
@Override
public FluidStack drain(int max, FluidAction action)
{
if((max<=0) || (container_.getCount()!=1)) return FluidStack.EMPTY;
FluidStack tank = readnbt();
if(tank.isEmpty()) return FluidStack.EMPTY;
final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_);
final FluidStack fs = tank.copy();
fs.setAmount(amount);
if(action.execute()) { tank.shrink(amount); writenbt(tank); }
return fs;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,309 +1,309 @@
/*
* @file Networking.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Main client/server message handling.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.world.World;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.inventory.container.Container;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkRegistry;
import net.minecraftforge.fml.network.simple.SimpleChannel;
import net.minecraftforge.fml.network.NetworkDirection;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Networking
{
private static final String PROTOCOL = "1";
private static SimpleChannel DEFAULT_CHANNEL;
public static void init(String modid)
{
DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder
.named(new ResourceLocation(modid, "default_ch"))
.clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL)
.simpleChannel();
int discr = -1;
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle);
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity notifications
//--------------------------------------------------------------------------------------------------------------------
public interface IPacketTileNotifyReceiver
{
default void onServerPacketReceived(CompoundNBT nbt) {}
default void onClientPacketReceived(PlayerEntity player, CompoundNBT nbt) {}
}
public static class PacketTileNotifyClientToServer
{
CompoundNBT nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToServer(BlockPos pos, CompoundNBT nbt)
{ if((pos!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); }
public static void sendToServer(TileEntity te, CompoundNBT nbt)
{ if((te!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); }
public PacketTileNotifyClientToServer()
{}
public PacketTileNotifyClientToServer(BlockPos pos, CompoundNBT nbt)
{ this.nbt = nbt; this.pos = pos; }
public PacketTileNotifyClientToServer(TileEntity te, CompoundNBT nbt)
{ this.nbt = nbt; pos = te.getBlockPos(); }
public static PacketTileNotifyClientToServer parse(final PacketBuffer buf)
{ return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyClientToServer pkt, final PacketBuffer buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
PlayerEntity player = ctx.get().getSender();
World world = player.level;
if(world == null) return;
final TileEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketTileNotifyServerToClient
{
CompoundNBT nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToPlayer(PlayerEntity player, TileEntity te, CompoundNBT nbt)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer) || (te==null) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayers(TileEntity te, CompoundNBT nbt)
{
if(te==null || te.getLevel().isClientSide()) return;
for(PlayerEntity player: te.getLevel().players()) sendToPlayer(player, te, nbt);
}
public PacketTileNotifyServerToClient()
{}
public PacketTileNotifyServerToClient(BlockPos pos, CompoundNBT nbt)
{ this.nbt=nbt; this.pos=pos; }
public PacketTileNotifyServerToClient(TileEntity te, CompoundNBT nbt)
{ this.nbt=nbt; pos=te.getBlockPos(); }
public static PacketTileNotifyServerToClient parse(final PacketBuffer buf)
{ return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyServerToClient pkt, final PacketBuffer buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
World world = SidedProxy.getWorldClientSide();
if(world == null) return;
final TileEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onServerPacketReceived(pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// (GUI) Container synchrsonisation
//--------------------------------------------------------------------------------------------------------------------
public interface INetworkSynchronisableContainer
{
void onServerPacketReceived(int windowId, CompoundNBT nbt);
void onClientPacketReceived(int windowId, PlayerEntity player, CompoundNBT nbt);
}
public static class PacketContainerSyncClientToServer
{
int id = -1;
CompoundNBT nbt = null;
public static void sendToServer(int windowId, CompoundNBT nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); }
public static void sendToServer(Container container, CompoundNBT nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); }
public PacketContainerSyncClientToServer()
{}
public PacketContainerSyncClientToServer(int id, CompoundNBT nbt)
{ this.nbt = nbt; this.id = id; }
public static PacketContainerSyncClientToServer parse(final PacketBuffer buf)
{ return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncClientToServer pkt, final PacketBuffer buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
PlayerEntity player = ctx.get().getSender();
if(!(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onClientPacketReceived(pkt.id, player,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketContainerSyncServerToClient
{
int id = -1;
CompoundNBT nbt = null;
public static void sendToPlayer(PlayerEntity player, int windowId, CompoundNBT nbt)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayer(PlayerEntity player, Container container, CompoundNBT nbt)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static <C extends Container & INetworkSynchronisableContainer>
void sendToListeners(World world, C container, CompoundNBT nbt)
{
for(PlayerEntity player: world.players()) {
if(player.containerMenu.containerId != container.containerId) continue;
sendToPlayer(player, container.containerId, nbt);
}
}
public PacketContainerSyncServerToClient()
{}
public PacketContainerSyncServerToClient(int id, CompoundNBT nbt)
{ this.nbt=nbt; this.id=id; }
public static PacketContainerSyncServerToClient parse(final PacketBuffer buf)
{ return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncServerToClient pkt, final PacketBuffer buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
PlayerEntity player = SidedProxy.getPlayerClientSide();
if(!(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onServerPacketReceived(pkt.id,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Main window GUI text message
//--------------------------------------------------------------------------------------------------------------------
public static class OverlayTextMessage
{
public static final int DISPLAY_TIME_MS = 3000;
private static BiConsumer<ITextComponent, Integer> handler_ = null;
private ITextComponent data_;
private int delay_ = DISPLAY_TIME_MS;
private ITextComponent data() { return data_; }
private int delay() { return delay_; }
public static void setHandler(BiConsumer<ITextComponent, Integer> handler)
{ if(handler_==null) handler_ = handler; }
public static void sendToPlayer(PlayerEntity player, ITextComponent message, int delay)
{
if((!(player instanceof ServerPlayerEntity)) || (player instanceof FakePlayer)) return;
DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayerEntity)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public OverlayTextMessage()
{ data_ = new TranslationTextComponent("[unset]"); }
public OverlayTextMessage(final ITextComponent tct, int delay)
{ data_ = (ITextComponent)tct.copy(); delay_ = delay; }
public static OverlayTextMessage parse(final PacketBuffer buf)
{
try {
return new OverlayTextMessage((ITextComponent)buf.readComponent(), DISPLAY_TIME_MS);
} catch(Throwable e) {
return new OverlayTextMessage(new TranslationTextComponent("[incorrect translation]"), DISPLAY_TIME_MS);
}
}
public static void compose(final OverlayTextMessage pkt, final PacketBuffer buf)
{
try {
buf.writeComponent(pkt.data());
} catch(Throwable e) {
Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e.toString());
}
}
public static class Handler
{
public static void handle(final OverlayTextMessage pkt, final Supplier<NetworkEvent.Context> ctx)
{
if(handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay()));
ctx.get().setPacketHandled(true);
}
}
}
}
/*
* @file Networking.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Main client/server message handling.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fmllegacy.network.NetworkEvent;
import net.minecraftforge.fmllegacy.network.NetworkDirection;
import net.minecraftforge.fmllegacy.network.NetworkRegistry;
import net.minecraftforge.fmllegacy.network.simple.SimpleChannel;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
public class Networking
{
private static final String PROTOCOL = "1";
private static SimpleChannel DEFAULT_CHANNEL;
public static void init(String modid)
{
DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder
.named(new ResourceLocation(modid, "default_ch"))
.clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL)
.simpleChannel();
int discr = -1;
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle);
DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle);
}
//--------------------------------------------------------------------------------------------------------------------
// Tile entity notifications
//--------------------------------------------------------------------------------------------------------------------
public interface IPacketTileNotifyReceiver
{
default void onServerPacketReceived(CompoundTag nbt) {}
default void onClientPacketReceived(Player player, CompoundTag nbt) {}
}
public static class PacketTileNotifyClientToServer
{
CompoundTag nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToServer(BlockPos pos, CompoundTag nbt)
{ if((pos!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); }
public static void sendToServer(BlockEntity te, CompoundTag nbt)
{ if((te!=null) && (nbt!=null)) DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); }
public PacketTileNotifyClientToServer()
{}
public PacketTileNotifyClientToServer(BlockPos pos, CompoundTag nbt)
{ this.nbt = nbt; this.pos = pos; }
public PacketTileNotifyClientToServer(BlockEntity te, CompoundTag nbt)
{ this.nbt = nbt; pos = te.getBlockPos(); }
public static PacketTileNotifyClientToServer parse(final FriendlyByteBuf buf)
{ return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyClientToServer pkt, final FriendlyByteBuf buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Player player = ctx.get().getSender();
if(player==null) return;
Level world = player.level;
final BlockEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketTileNotifyServerToClient
{
CompoundTag nbt = null;
BlockPos pos = BlockPos.ZERO;
public static void sendToPlayer(Player player, BlockEntity te, CompoundTag nbt)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (te==null) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayers(BlockEntity te, CompoundTag nbt)
{
if(te==null || te.getLevel()==null) return;
for(Player player: te.getLevel().players()) sendToPlayer(player, te, nbt);
}
public PacketTileNotifyServerToClient()
{}
public PacketTileNotifyServerToClient(BlockPos pos, CompoundTag nbt)
{ this.nbt=nbt; this.pos=pos; }
public PacketTileNotifyServerToClient(BlockEntity te, CompoundTag nbt)
{ this.nbt=nbt; pos=te.getBlockPos(); }
public static PacketTileNotifyServerToClient parse(final FriendlyByteBuf buf)
{ return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); }
public static void compose(final PacketTileNotifyServerToClient pkt, final FriendlyByteBuf buf)
{ buf.writeBlockPos(pkt.pos); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Level world = SidedProxy.getWorldClientSide();
if(world == null) return;
final BlockEntity te = world.getBlockEntity(pkt.pos);
if(!(te instanceof IPacketTileNotifyReceiver)) return;
((IPacketTileNotifyReceiver)te).onServerPacketReceived(pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// (GUI) Container synchronization
//--------------------------------------------------------------------------------------------------------------------
public interface INetworkSynchronisableContainer
{
void onServerPacketReceived(int windowId, CompoundTag nbt);
void onClientPacketReceived(int windowId, Player player, CompoundTag nbt);
}
public static class PacketContainerSyncClientToServer
{
int id = -1;
CompoundTag nbt = null;
public static void sendToServer(int windowId, CompoundTag nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); }
public static void sendToServer(AbstractContainerMenu container, CompoundTag nbt)
{ if(nbt!=null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); }
public PacketContainerSyncClientToServer()
{}
public PacketContainerSyncClientToServer(int id, CompoundTag nbt)
{ this.nbt = nbt; this.id = id; }
public static PacketContainerSyncClientToServer parse(final FriendlyByteBuf buf)
{ return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncClientToServer pkt, final FriendlyByteBuf buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Player player = ctx.get().getSender();
if((player==null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onClientPacketReceived(pkt.id, player,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
public static class PacketContainerSyncServerToClient
{
int id = -1;
CompoundTag nbt = null;
public static void sendToPlayer(Player player, int windowId, CompoundTag nbt)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static void sendToPlayer(Player player, AbstractContainerMenu container, CompoundTag nbt)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt==null)) return;
DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public static <C extends AbstractContainerMenu & INetworkSynchronisableContainer>
void sendToListeners(Level world, C container, CompoundTag nbt)
{
for(Player player: world.players()) {
if(player.containerMenu.containerId != container.containerId) continue;
sendToPlayer(player, container.containerId, nbt);
}
}
public PacketContainerSyncServerToClient()
{}
public PacketContainerSyncServerToClient(int id, CompoundTag nbt)
{ this.nbt=nbt; this.id=id; }
public static PacketContainerSyncServerToClient parse(final FriendlyByteBuf buf)
{ return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); }
public static void compose(final PacketContainerSyncServerToClient pkt, final FriendlyByteBuf buf)
{ buf.writeInt(pkt.id); buf.writeNbt(pkt.nbt); }
public static class Handler
{
public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier<NetworkEvent.Context> ctx)
{
ctx.get().enqueueWork(() -> {
Player player = SidedProxy.getPlayerClientSide();
if((player==null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return;
if(player.containerMenu.containerId != pkt.id) return;
((INetworkSynchronisableContainer)player.containerMenu).onServerPacketReceived(pkt.id,pkt.nbt);
});
ctx.get().setPacketHandled(true);
}
}
}
//--------------------------------------------------------------------------------------------------------------------
// Main window GUI text message
//--------------------------------------------------------------------------------------------------------------------
public static class OverlayTextMessage
{
public static final int DISPLAY_TIME_MS = 3000;
private static BiConsumer<Component, Integer> handler_ = null;
private final Component data_;
private int delay_ = DISPLAY_TIME_MS;
private Component data() { return data_; }
private int delay() { return delay_; }
public static void setHandler(BiConsumer<Component, Integer> handler)
{ if(handler_==null) handler_ = handler; }
public static void sendToPlayer(Player player, Component message, int delay)
{
if((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer)) return;
DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayer)player).connection.connection, NetworkDirection.PLAY_TO_CLIENT);
}
public OverlayTextMessage()
{ data_ = new TranslatableComponent("[unset]"); }
public OverlayTextMessage(final Component tct, int delay)
{ data_ = tct.copy(); delay_ = delay; }
public static OverlayTextMessage parse(final FriendlyByteBuf buf)
{
try {
return new OverlayTextMessage(buf.readComponent(), DISPLAY_TIME_MS);
} catch(Throwable e) {
return new OverlayTextMessage(new TranslatableComponent("[incorrect translation]"), DISPLAY_TIME_MS);
}
}
public static void compose(final OverlayTextMessage pkt, final FriendlyByteBuf buf)
{
try {
buf.writeComponent(pkt.data());
} catch(Throwable e) {
Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e);
}
}
public static class Handler
{
public static void handle(final OverlayTextMessage pkt, final Supplier<NetworkEvent.Context> ctx)
{
if(handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay()));
ctx.get().setPacketHandled(true);
}
}
}
}

View file

@ -1,199 +1,197 @@
/*
* @file OptionalRecipeCondition.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe condition to enable opt'ing out JSON based recipes.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.tags.ITag;
import net.minecraft.tags.TagCollectionManager;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.JSONUtils;
import net.minecraftforge.common.crafting.conditions.ICondition;
import net.minecraftforge.common.crafting.conditions.IConditionSerializer;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.ForgeRegistries;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class OptionalRecipeCondition implements ICondition
{
private static ResourceLocation NAME;
private final List<ResourceLocation> all_required;
private final List<ResourceLocation> any_missing;
private final List<ResourceLocation> all_required_tags;
private final List<ResourceLocation> any_missing_tags;
private final @Nullable ResourceLocation result;
private final boolean result_is_tag;
private final boolean experimental;
private static boolean with_experimental = false;
private static boolean without_recipes = false;
private static Predicate<Block> block_optouts = (block)->false;
private static Predicate<Item> item_optouts = (item)->false;
public static void init(String modid, Logger logger)
{
NAME = new ResourceLocation(modid, "optional");
}
public static void on_config(boolean enable_experimental, boolean disable_all_recipes,
Predicate<Block> block_optout_provider,
Predicate<Item> item_optout_provider)
{
with_experimental = enable_experimental;
without_recipes = disable_all_recipes;
block_optouts = block_optout_provider;
item_optouts = item_optout_provider;
}
public OptionalRecipeCondition(ResourceLocation result, List<ResourceLocation> required, List<ResourceLocation> missing, List<ResourceLocation> required_tags, List<ResourceLocation> missing_tags, boolean isexperimental, boolean result_is_tag)
{
all_required = required;
any_missing = missing;
all_required_tags = required_tags;
any_missing_tags = missing_tags;
this.result = result;
this.result_is_tag = result_is_tag;
experimental=isexperimental;
}
@Override
public ResourceLocation getID()
{ return NAME; }
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Optional recipe, all-required: [");
for(ResourceLocation e:all_required) sb.append(e.toString()).append(",");
for(ResourceLocation e:all_required_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("], any-missing: [");
for(ResourceLocation e:any_missing) sb.append(e.toString()).append(",");
for(ResourceLocation e:any_missing_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("]");
if(experimental) sb.append(" EXPERIMENTAL");
return sb.toString();
}
@Override
public boolean test()
{
if(without_recipes) return false;
if((experimental) && (!with_experimental)) return false;
final IForgeRegistry<Item> item_registry = ForgeRegistries.ITEMS;
final Map<ResourceLocation, ITag<Item>> item_tags = TagCollectionManager.getInstance().getItems().getAllTags();
if(result != null) {
boolean item_registered = item_registry.containsKey(result);
if(!item_registered) return false; // required result not registered
if(item_registered && item_optouts.test(item_registry.getValue(result))) return false;
if(ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) return false;
}
if(!all_required.isEmpty()) {
for(ResourceLocation rl:all_required) {
if(!item_registry.containsKey(rl)) return false;
}
}
if(!all_required_tags.isEmpty()) {
for(ResourceLocation rl:all_required_tags) {
if(!item_tags.containsKey(rl)) return false;
if(item_tags.get(rl).getValues().isEmpty()) return false;
}
}
if(!any_missing.isEmpty()) {
for(ResourceLocation rl:any_missing) {
if(!item_registry.containsKey(rl)) return true;
}
return false;
}
if(!any_missing_tags.isEmpty()) {
for(ResourceLocation rl:any_missing_tags) {
if(!item_tags.containsKey(rl)) return true;
if(item_tags.get(rl).getValues().isEmpty()) return true;
}
return false;
}
return true;
}
public static class Serializer implements IConditionSerializer<OptionalRecipeCondition>
{
public static final Serializer INSTANCE = new Serializer();
@Override
public ResourceLocation getID()
{ return OptionalRecipeCondition.NAME; }
@Override
public void write(JsonObject json, OptionalRecipeCondition condition)
{
JsonArray required = new JsonArray();
JsonArray missing = new JsonArray();
for(ResourceLocation e:condition.all_required) required.add(e.toString());
for(ResourceLocation e:condition.any_missing) missing.add(e.toString());
json.add("required", required);
json.add("missing", missing);
if(condition.result != null) {
json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result.toString());
}
}
@Override
public OptionalRecipeCondition read(JsonObject json)
{
List<ResourceLocation> required = new ArrayList<>();
List<ResourceLocation> missing = new ArrayList<>();
List<ResourceLocation> required_tags = new ArrayList<>();
List<ResourceLocation> missing_tags = new ArrayList<>();
ResourceLocation result = null;
boolean experimental = false;
boolean result_is_tag = false;
if(json.has("result")) {
String s = json.get("result").getAsString();
if(s.startsWith("#")) {
result = new ResourceLocation(s.substring(1));
result_is_tag = true;
} else {
result = new ResourceLocation(s);
}
}
if(json.has("required")) {
for(JsonElement e:JSONUtils.getAsJsonArray(json, "required")) {
String s = e.getAsString();
if(s.startsWith("#")) {
required_tags.add(new ResourceLocation(s.substring(1)));
} else {
required.add(new ResourceLocation(s));
}
}
}
if(json.has("missing")) {
for(JsonElement e:JSONUtils.getAsJsonArray(json, "missing")) {
String s = e.getAsString();
if(s.startsWith("#")) {
missing_tags.add(new ResourceLocation(s.substring(1)));
} else {
missing.add(new ResourceLocation(s));
}
}
}
if(json.has("experimental")) experimental = json.get("experimental").getAsBoolean();
return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag);
}
}
}
/*
* @file OptionalRecipeCondition.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Recipe condition to enable opt'ing out JSON based recipes.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.*;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.common.crafting.conditions.ICondition;
import net.minecraftforge.common.crafting.conditions.IConditionSerializer;
import net.minecraftforge.registries.ForgeRegistries;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraftforge.registries.IForgeRegistry;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class OptionalRecipeCondition implements ICondition
{
private static ResourceLocation NAME;
private final List<ResourceLocation> all_required;
private final List<ResourceLocation> any_missing;
private final List<ResourceLocation> all_required_tags;
private final List<ResourceLocation> any_missing_tags;
private final @Nullable ResourceLocation result;
private final boolean result_is_tag;
private final boolean experimental;
private static boolean with_experimental = false;
private static boolean without_recipes = false;
private static Predicate<Block> block_optouts = (block)->false;
private static Predicate<Item> item_optouts = (item)->false;
public static void init(String modid, Logger logger)
{
NAME = new ResourceLocation(modid, "optional");
}
public static void on_config(boolean enable_experimental, boolean disable_all_recipes,
Predicate<Block> block_optout_provider,
Predicate<Item> item_optout_provider)
{
with_experimental = enable_experimental;
without_recipes = disable_all_recipes;
block_optouts = block_optout_provider;
item_optouts = item_optout_provider;
}
public OptionalRecipeCondition(ResourceLocation result, List<ResourceLocation> required, List<ResourceLocation> missing, List<ResourceLocation> required_tags, List<ResourceLocation> missing_tags, boolean isexperimental, boolean result_is_tag)
{
all_required = required;
any_missing = missing;
all_required_tags = required_tags;
any_missing_tags = missing_tags;
this.result = result;
this.result_is_tag = result_is_tag;
experimental=isexperimental;
}
@Override
public ResourceLocation getID()
{ return NAME; }
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Optional recipe, all-required: [");
for(ResourceLocation e:all_required) sb.append(e.toString()).append(",");
for(ResourceLocation e:all_required_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("], any-missing: [");
for(ResourceLocation e:any_missing) sb.append(e.toString()).append(",");
for(ResourceLocation e:any_missing_tags) sb.append("#").append(e.toString()).append(",");
sb.delete(sb.length()-1, sb.length()).append("]");
if(experimental) sb.append(" EXPERIMENTAL");
return sb.toString();
}
@Override
public boolean test()
{
if(without_recipes) return false;
if((experimental) && (!with_experimental)) return false;
final IForgeRegistry<Item> item_registry = ForgeRegistries.ITEMS;
final Collection<ResourceLocation> item_tags = SerializationTags.getInstance().getOrEmpty(Registry.ITEM_REGISTRY).getAvailableTags();
if(result != null) {
boolean item_registered = item_registry.containsKey(result);
if(!item_registered) return false; // required result not registered
if(item_registered && item_optouts.test(item_registry.getValue(result))) return false;
if(ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) return false;
}
if(!all_required.isEmpty()) {
for(ResourceLocation rl:all_required) {
if(!item_registry.containsKey(rl)) return false;
}
}
if(!all_required_tags.isEmpty()) {
for(ResourceLocation rl:all_required_tags) {
if(!item_tags.contains(rl)) return false;
}
}
if(!any_missing.isEmpty()) {
for(ResourceLocation rl:any_missing) {
if(!item_registry.containsKey(rl)) return true;
}
return false;
}
if(!any_missing_tags.isEmpty()) {
for(ResourceLocation rl:any_missing_tags) {
if(!item_tags.contains(rl)) return true;
}
return false;
}
return true;
}
public static class Serializer implements IConditionSerializer<OptionalRecipeCondition>
{
public static final Serializer INSTANCE = new Serializer();
@Override
public ResourceLocation getID()
{ return OptionalRecipeCondition.NAME; }
@Override
public void write(JsonObject json, OptionalRecipeCondition condition)
{
JsonArray required = new JsonArray();
JsonArray missing = new JsonArray();
for(ResourceLocation e:condition.all_required) required.add(e.toString());
for(ResourceLocation e:condition.any_missing) missing.add(e.toString());
json.add("required", required);
json.add("missing", missing);
if(condition.result != null) {
json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result);
}
}
@Override
public OptionalRecipeCondition read(JsonObject json)
{
List<ResourceLocation> required = new ArrayList<>();
List<ResourceLocation> missing = new ArrayList<>();
List<ResourceLocation> required_tags = new ArrayList<>();
List<ResourceLocation> missing_tags = new ArrayList<>();
ResourceLocation result = null;
boolean experimental = false;
boolean result_is_tag = false;
if(json.has("result")) {
String s = json.get("result").getAsString();
if(s.startsWith("#")) {
result = new ResourceLocation(s.substring(1));
result_is_tag = true;
} else {
result = new ResourceLocation(s);
}
}
if(json.has("required")) {
for(JsonElement e:GsonHelper.getAsJsonArray(json, "required")) {
String s = e.getAsString();
if(s.startsWith("#")) {
required_tags.add(new ResourceLocation(s.substring(1)));
} else {
required.add(new ResourceLocation(s));
}
}
}
if(json.has("missing")) {
for(JsonElement e:GsonHelper.getAsJsonArray(json, "missing")) {
String s = e.getAsString();
if(s.startsWith("#")) {
missing_tags.add(new ResourceLocation(s.substring(1)));
} else {
missing.add(new ResourceLocation(s));
}
}
}
if(json.has("experimental")) experimental = json.get("experimental").getAsBoolean();
return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag);
}
}
}

View file

@ -1,117 +1,116 @@
/*
* @file Overlay.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Renders status messages in one line.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.MainWindow;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.AbstractGui;
import net.minecraft.util.text.StringTextComponent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
public class Overlay
{
public static void register()
{
if(SidedProxy.mc() != null) {
MinecraftForge.EVENT_BUS.register(new TextOverlayGui());
Networking.OverlayTextMessage.setHandler(TextOverlayGui::show);
}
}
public static void show(PlayerEntity player, final ITextComponent message)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); }
public static void show(PlayerEntity player, final ITextComponent message, int delay)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, delay); }
// -----------------------------------------------------------------------------
// Client side handler
// -----------------------------------------------------------------------------
@Mod.EventBusSubscriber(Dist.CLIENT)
@OnlyIn(Dist.CLIENT)
public static class TextOverlayGui extends AbstractGui
{
private static final ITextComponent EMPTY_TEXT = new StringTextComponent("");
private static double overlay_y_ = 0.75;
private static int text_color_ = 0x00ffaa00;
private static int border_color_ = 0xaa333333;
private static int background_color1_ = 0xaa333333;
private static int background_color2_ = 0xaa444444;
private final Minecraft mc;
private static long deadline_;
private static ITextComponent text_;
public static void on_config(double overlay_y)
{
overlay_y_ = overlay_y;
// currently const, just to circumvent "useless variable" warnings
text_color_ = 0x00ffaa00;
border_color_ = 0xaa333333;
background_color1_ = 0xaa333333;
background_color2_ = 0xaa444444;
}
public static synchronized ITextComponent text()
{ return text_; }
public static synchronized long deadline()
{ return deadline_; }
public static synchronized void hide()
{ deadline_ = 0; text_ = EMPTY_TEXT; }
public static synchronized void show(ITextComponent s, int displayTimeoutMs)
{ text_ = (s==null)?(EMPTY_TEXT):(s.copy()); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
public static synchronized void show(String s, int displayTimeoutMs)
{ text_ = ((s==null)||(s.isEmpty()))?(EMPTY_TEXT):(new StringTextComponent(s)); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
TextOverlayGui()
{ super(); mc = SidedProxy.mc(); }
@SubscribeEvent
public void onRenderGui(RenderGameOverlayEvent.Post event)
{
if(event.getType() != RenderGameOverlayEvent.ElementType.CHAT) return;
if(deadline() < System.currentTimeMillis()) return;
if(text()==EMPTY_TEXT) return;
String txt = text().getString();
if(txt.isEmpty()) return;
MatrixStack mxs = event.getMatrixStack();
final MainWindow win = mc.getWindow();
final FontRenderer fr = mc.font;
final boolean was_unicode = fr.isBidirectional();
try {
final int cx = win.getGuiScaledWidth() / 2;
final int cy = (int)(win.getGuiScaledHeight() * overlay_y_);
final int w = fr.width(txt);
final int h = fr.lineHeight;
fillGradient(mxs, cx-(w/2)-3, cy-2, cx+(w/2)+2, cy+h+2, 0xaa333333, 0xaa444444);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy-2, 0xaa333333);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy+h+2, 0xaa333333);
vLine(mxs, cx-(w/2)-3, cy-2, cy+h+2, 0xaa333333);
vLine(mxs, cx+(w/2)+2, cy-2, cy+h+2, 0xaa333333);
drawCenteredString(mxs, fr, text(), cx , cy+1, 0x00ffaa00);
} finally {
; // fr.setBidiFlag(was_unicode);
}
}
}
}
/*
* @file Overlay.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Renders status messages in one line.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.platform.Window;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.Font;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
public class Overlay
{
public static void register()
{
if(SidedProxy.mc() != null) {
MinecraftForge.EVENT_BUS.register(new TextOverlayGui());
Networking.OverlayTextMessage.setHandler(TextOverlayGui::show);
}
}
public static void show(Player player, final Component message)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); }
public static void show(Player player, final Component message, int delay)
{ Networking.OverlayTextMessage.sendToPlayer(player, message, delay); }
// -----------------------------------------------------------------------------
// Client side handler
// -----------------------------------------------------------------------------
@Mod.EventBusSubscriber(Dist.CLIENT)
@OnlyIn(Dist.CLIENT)
public static class TextOverlayGui extends Screen
{
private static final Component EMPTY_TEXT = new TextComponent("");
private static double overlay_y_ = 0.75;
private static int text_color_ = 0x00ffaa00;
private static int border_color_ = 0xaa333333;
private static int background_color1_ = 0xaa333333;
private static int background_color2_ = 0xaa444444;
private final Minecraft mc;
private static long deadline_;
private static Component text_;
public static void on_config(double overlay_y)
{ on_config(overlay_y, 0x00ffaa00, 0xaa333333, 0xaa333333, 0xaa444444); }
public static void on_config(double overlay_y, int text_color, int border_color, int background_color1, int background_color2)
{
overlay_y_ = overlay_y;
text_color_ = text_color;
border_color_ = border_color;
background_color1_ = background_color1;
background_color2_ = background_color2;
}
public static synchronized Component text()
{ return text_; }
public static synchronized long deadline()
{ return deadline_; }
public static synchronized void hide()
{ deadline_ = 0; text_ = EMPTY_TEXT; }
public static synchronized void show(Component s, int displayTimeoutMs)
{ text_ = (s==null)?(EMPTY_TEXT):(s.copy()); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
public static synchronized void show(String s, int displayTimeoutMs)
{ text_ = ((s==null)||(s.isEmpty()))?(EMPTY_TEXT):(new TextComponent(s)); deadline_ = System.currentTimeMillis() + displayTimeoutMs; }
TextOverlayGui()
{ super(new TextComponent("")); mc = SidedProxy.mc(); }
@SubscribeEvent
public void onRenderGui(RenderGameOverlayEvent.Post event)
{
if(event.getType() != RenderGameOverlayEvent.ElementType.CHAT) return;
if(deadline() < System.currentTimeMillis()) return;
if(text()==EMPTY_TEXT) return;
String txt = text().getString();
if(txt.isEmpty()) return;
PoseStack mxs = event.getMatrixStack();
final Window win = mc.getWindow();
final Font fr = mc.font;
final boolean was_unicode = fr.isBidirectional();
final int cx = win.getGuiScaledWidth() / 2;
final int cy = (int)(win.getGuiScaledHeight() * overlay_y_);
final int w = fr.width(txt);
final int h = fr.lineHeight;
fillGradient(mxs, cx-(w/2)-3, cy-2, cx+(w/2)+2, cy+h+2, 0xaa333333, 0xaa444444);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy-2, 0xaa333333);
hLine(mxs, cx-(w/2)-3, cx+(w/2)+2, cy+h+2, 0xaa333333);
vLine(mxs, cx-(w/2)-3, cy-2, cy+h+2, 0xaa333333);
vLine(mxs, cx+(w/2)+2, cy-2, cy+h+2, 0xaa333333);
drawCenteredString(mxs, fr, text(), cx , cy+1, 0x00ffaa00);
}
}
}

View file

@ -1,158 +1,158 @@
/*
* @file RfEnergy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General RF/FE energy handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import javax.annotation.Nullable;
public class RfEnergy
{
public static int feed(World world, BlockPos pos, @Nullable Direction side, int rf_energy)
{
final TileEntity te = world.getBlockEntity(pos);
if(te == null) return 0;
final IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, side).orElse(null);
if(es == null) return 0;
return es.receiveEnergy(rf_energy, false);
}
public static class Battery implements IEnergyStorage
{
protected int capacity_;
protected int charge_rate_;
protected int discharge_rate_;
protected int energy_;
public Battery(int capacity)
{ this(capacity, capacity); }
public Battery(int capacity, int transfer_rate)
{ this(capacity, transfer_rate, transfer_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate)
{ this(capacity, charge_rate, discharge_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate, int energy)
{
capacity_ = Math.max(capacity, 1);
charge_rate_ = MathHelper.clamp(charge_rate, 0, capacity_);
discharge_rate_ = MathHelper.clamp(discharge_rate, 0, capacity_);
energy_ = MathHelper.clamp(energy, 0, capacity_);
}
// ---------------------------------------------------------------------------------------------------
public Battery setMaxEnergyStored(int capacity)
{ capacity_ = Math.max(capacity, 1); return this; }
public Battery setEnergyStored(int energy)
{ energy_ = MathHelper.clamp(energy, 0, capacity_); return this; }
public Battery setChargeRate(int in_rate)
{ charge_rate_ = MathHelper.clamp(in_rate, 0, capacity_); return this; }
public Battery setDischargeRate(int out_rate)
{ discharge_rate_ = MathHelper.clamp(out_rate, 0, capacity_); return this; }
public int getChargeRate()
{ return charge_rate_; }
public int getDischargeRate()
{ return discharge_rate_; }
public boolean isEmpty()
{ return energy_ <= 0; }
public boolean isFull()
{ return energy_ >= capacity_; }
public int getSOC()
{ return (int)MathHelper.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); }
public int getComparatorOutput()
{ return (int)MathHelper.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); }
public boolean draw(int energy)
{
if(energy_ < energy) return false;
energy_ -= energy;
return true;
}
public boolean feed(int energy)
{
energy_ = Math.min(energy_+energy, capacity_);
return energy_ >= capacity_;
}
public Battery clear()
{ energy_ = 0; return this; }
public Battery load(CompoundNBT nbt, String key)
{ setEnergyStored(nbt.getInt(key)); return this; }
public Battery load(CompoundNBT nbt)
{ return load(nbt, "Energy"); }
public CompoundNBT save(CompoundNBT nbt, String key)
{ nbt.putInt(key, energy_); return nbt; }
public CompoundNBT save(CompoundNBT nbt)
{ return save(nbt, "Energy"); }
public LazyOptional<IEnergyStorage> createEnergyHandler()
{ return LazyOptional.of(() -> (IEnergyStorage)this); }
// IEnergyStorage ------------------------------------------------------------------------------------
@Override
public int receiveEnergy(int feed_energy, boolean simulate)
{
if(!canReceive()) return 0;
int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_-energy_);
if(!simulate) energy_ += e;
return e;
}
@Override
public int extractEnergy(int draw_energy, boolean simulate)
{
if(!canExtract()) return 0;
int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_);
if(!simulate) energy_ -= e;
return e;
}
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int getMaxEnergyStored()
{ return capacity_; }
@Override
public boolean canExtract()
{ return discharge_rate_ > 0; }
@Override
public boolean canReceive()
{ return charge_rate_ > 0; }
}
}
/*
* @file RfEnergy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General RF/FE energy handling functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.energy.IEnergyStorage;
import javax.annotation.Nullable;
public class RfEnergy
{
public static int feed(Level world, BlockPos pos, @Nullable Direction side, int rf_energy)
{
final BlockEntity te = world.getBlockEntity(pos);
if(te == null) return 0;
final IEnergyStorage es = te.getCapability(CapabilityEnergy.ENERGY, side).orElse(null);
if(es == null) return 0;
return es.receiveEnergy(rf_energy, false);
}
public static class Battery implements IEnergyStorage
{
protected int capacity_;
protected int charge_rate_;
protected int discharge_rate_;
protected int energy_;
public Battery(int capacity)
{ this(capacity, capacity); }
public Battery(int capacity, int transfer_rate)
{ this(capacity, transfer_rate, transfer_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate)
{ this(capacity, charge_rate, discharge_rate, 0); }
public Battery(int capacity, int charge_rate, int discharge_rate, int energy)
{
capacity_ = Math.max(capacity, 1);
charge_rate_ = Mth.clamp(charge_rate, 0, capacity_);
discharge_rate_ = Mth.clamp(discharge_rate, 0, capacity_);
energy_ = Mth.clamp(energy, 0, capacity_);
}
// ---------------------------------------------------------------------------------------------------
public Battery setMaxEnergyStored(int capacity)
{ capacity_ = Math.max(capacity, 1); return this; }
public Battery setEnergyStored(int energy)
{ energy_ = Mth.clamp(energy, 0, capacity_); return this; }
public Battery setChargeRate(int in_rate)
{ charge_rate_ = Mth.clamp(in_rate, 0, capacity_); return this; }
public Battery setDischargeRate(int out_rate)
{ discharge_rate_ = Mth.clamp(out_rate, 0, capacity_); return this; }
public int getChargeRate()
{ return charge_rate_; }
public int getDischargeRate()
{ return discharge_rate_; }
public boolean isEmpty()
{ return energy_ <= 0; }
public boolean isFull()
{ return energy_ >= capacity_; }
public int getSOC()
{ return (int)Mth.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); }
public int getComparatorOutput()
{ return (int)Mth.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); }
public boolean draw(int energy)
{
if(energy_ < energy) return false;
energy_ -= energy;
return true;
}
public boolean feed(int energy)
{
energy_ = Math.min(energy_+energy, capacity_);
return energy_ >= capacity_;
}
public Battery clear()
{ energy_ = 0; return this; }
public Battery load(CompoundTag nbt, String key)
{ setEnergyStored(nbt.getInt(key)); return this; }
public Battery load(CompoundTag nbt)
{ return load(nbt, "Energy"); }
public CompoundTag save(CompoundTag nbt, String key)
{ nbt.putInt(key, energy_); return nbt; }
public CompoundTag save(CompoundTag nbt)
{ return save(nbt, "Energy"); }
public LazyOptional<IEnergyStorage> createEnergyHandler()
{ return LazyOptional.of(() -> this); }
// IEnergyStorage ------------------------------------------------------------------------------------
@Override
public int receiveEnergy(int feed_energy, boolean simulate)
{
if(!canReceive()) return 0;
int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_-energy_);
if(!simulate) energy_ += e;
return e;
}
@Override
public int extractEnergy(int draw_energy, boolean simulate)
{
if(!canExtract()) return 0;
int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_);
if(!simulate) energy_ -= e;
return e;
}
@Override
public int getEnergyStored()
{ return energy_; }
@Override
public int getMaxEnergyStored()
{ return capacity_; }
@Override
public boolean canExtract()
{ return discharge_rate_ > 0; }
@Override
public boolean canReceive()
{ return charge_rate_ > 0; }
}
}

View file

@ -0,0 +1,45 @@
/*
* @file RsSignals.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General redstone signal related functionality.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.Container;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
public class RsSignals
{
public static boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction realSide)
{
return state.isSignalSource();
}
public static int fromContainer(@Nullable Container container)
{
if(container == null) return 0;
final double max = container.getMaxStackSize();
if(max <= 0) return 0;
boolean nonempty = false;
double fill_level = 0;
for(int i=0; i<container.getContainerSize(); ++i) {
ItemStack stack = container.getItem(i);
if(stack.isEmpty() || (stack.getMaxStackSize()<=0)) continue;
fill_level += ((double)stack.getCount()) / Math.min(max, stack.getMaxStackSize());
nonempty = true;
}
fill_level /= container.getContainerSize();
return (int)(Math.floor(fill_level * 14) + (nonempty?1:0)); // vanilla compliant calculation.
}
}

View file

@ -1,67 +1,66 @@
/*
* @file SidedProxy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General client/server sideness selection proxy.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.world.World;
import net.minecraftforge.fml.DistExecutor;
import javax.annotation.Nullable;
import java.util.Optional;
public class SidedProxy
{
@Nullable
public static PlayerEntity getPlayerClientSide()
{ return proxy.getPlayerClientSide(); }
@Nullable
public static World getWorldClientSide()
{ return proxy.getWorldClientSide(); }
@Nullable
public static Minecraft mc()
{ return proxy.mc(); }
@Nullable
public static Optional<Boolean> isCtrlDown()
{ return proxy.isCtrlDown(); }
@Nullable
public static Optional<Boolean> isShiftDown()
{ return proxy.isShiftDown(); }
// --------------------------------------------------------------------------------------------------------
// @todo: check conditions for safeRunForDist()
private static ISidedProxy proxy = DistExecutor.unsafeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);
private interface ISidedProxy
{
default @Nullable PlayerEntity getPlayerClientSide() { return null; }
default @Nullable World getWorldClientSide() { return null; }
default @Nullable Minecraft mc() { return null; }
default Optional<Boolean> isCtrlDown() { return Optional.empty(); }
default Optional<Boolean> isShiftDown() { return Optional.empty(); }
}
private static final class ClientProxy implements ISidedProxy
{
public @Nullable PlayerEntity getPlayerClientSide() { return Minecraft.getInstance().player; }
public @Nullable World getWorldClientSide() { return Minecraft.getInstance().level; }
public @Nullable Minecraft mc() { return Minecraft.getInstance(); }
public Optional<Boolean> isCtrlDown() { return Optional.of(Auxiliaries.isCtrlDown()); }
public Optional<Boolean> isShiftDown() { return Optional.of(Auxiliaries.isShiftDown()); }
}
private static final class ServerProxy implements ISidedProxy
{
}
}
/*
* @file SidedProxy.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* General client/server sideness selection proxy.
*/
package wile.engineersdecor.libmc.detail;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraftforge.fml.DistExecutor;
import javax.annotation.Nullable;
import java.util.Optional;
public class SidedProxy
{
@Nullable
public static Player getPlayerClientSide()
{ return proxy.getPlayerClientSide(); }
@Nullable
public static Level getWorldClientSide()
{ return proxy.getWorldClientSide(); }
@Nullable
public static Minecraft mc()
{ return proxy.mc(); }
@Nullable
public static Optional<Boolean> isCtrlDown()
{ return proxy.isCtrlDown(); }
@Nullable
public static Optional<Boolean> isShiftDown()
{ return proxy.isShiftDown(); }
// --------------------------------------------------------------------------------------------------------
private static final ISidedProxy proxy = DistExecutor.unsafeRunForDist(()->ClientProxy::new, ()->ServerProxy::new);
private interface ISidedProxy
{
default @Nullable Player getPlayerClientSide() { return null; }
default @Nullable Level getWorldClientSide() { return null; }
default @Nullable Minecraft mc() { return null; }
default Optional<Boolean> isCtrlDown() { return Optional.empty(); }
default Optional<Boolean> isShiftDown() { return Optional.empty(); }
}
private static final class ClientProxy implements ISidedProxy
{
public @Nullable Player getPlayerClientSide() { return Minecraft.getInstance().player; }
public @Nullable Level getWorldClientSide() { return Minecraft.getInstance().level; }
public @Nullable Minecraft mc() { return Minecraft.getInstance(); }
public Optional<Boolean> isCtrlDown() { return Optional.of(Auxiliaries.isCtrlDown()); }
public Optional<Boolean> isShiftDown() { return Optional.of(Auxiliaries.isShiftDown()); }
}
private static final class ServerProxy implements ISidedProxy
{
}
}

View file

@ -1,115 +1,118 @@
/*
* @file Tooltip.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Delayed tooltip for a selected area. Constructed with a
* GUI, invoked in `render()`.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.inventory.container.Container;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.text.ITextComponent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@OnlyIn(Dist.CLIENT)
public class TooltipDisplay
{
private static long default_delay = 800;
private static int default_max_deviation = 1;
public static void config(long delay, int max_deviation)
{
default_delay = MathHelper.clamp(delay, 500, 5000);
default_max_deviation = MathHelper.clamp(max_deviation, 1, 5);
}
// ---------------------------------------------------------------------------------------------------
public static class TipRange
{
public final int x0,y0,x1,y1;
public final Supplier<ITextComponent> text;
public TipRange(int x, int y, int w, int h, ITextComponent text)
{ this(x,y,w,h,()->text); }
public TipRange(int x, int y, int w, int h, Supplier<ITextComponent> text)
{ this.text=text; this.x0=x; this.y0=y; this.x1=x0+w-1; this.y1=y0+h-1; }
}
// ---------------------------------------------------------------------------------------------------
private List<TipRange> ranges = new ArrayList<>();
private long delay = default_delay;
private int max_deviation = default_max_deviation;
private int x_last, y_last;
private long t;
private static boolean had_render_exception = false;
public TooltipDisplay()
{ t = System.currentTimeMillis(); }
public void init(List<TipRange> ranges, long delay_ms, int max_deviation_xy)
{
this.ranges = ranges;
this.delay = delay_ms;
this.max_deviation = max_deviation_xy;
t = System.currentTimeMillis();
x_last = y_last = 0;
}
public void init(List<TipRange> ranges)
{ init(ranges, default_delay, default_max_deviation); }
public void init(TipRange... ranges)
{ init(Arrays.asList(ranges), default_delay, default_max_deviation); }
public void resetTimer()
{ t = System.currentTimeMillis(); }
public <T extends Container> boolean render(MatrixStack mx, final ContainerScreen<T> gui, int x, int y)
{
if(had_render_exception) return false;
if((Math.abs(x-x_last) > max_deviation) || (Math.abs(y-y_last) > max_deviation)) {
x_last = x;
y_last = y;
resetTimer();
return false;
} else if(Math.abs(System.currentTimeMillis()-t) < delay) {
return false;
} else if(ranges.stream().noneMatch(
(tip)->{
if((x<tip.x0) || (x>tip.x1) || (y<tip.y0) || (y>tip.y1)) return false;
String text = tip.text.get().getString();
if(!text.isEmpty() && (!text.startsWith("block."))) {
try {
gui.renderTooltip(mx, tip.text.get(), x, y);
} catch(Exception ex) {
had_render_exception = true;
Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'");
return false;
}
}
return true;
})
){
resetTimer();
return false;
} else {
return true;
}
}
}
/*
* @file Tooltip.java
* @author Stefan Wilhelm (wile)
* @copyright (C) 2020 Stefan Wilhelm
* @license MIT (see https://opensource.org/licenses/MIT)
*
* Delayed tooltip for a selected area. Constructed with a
* GUI, invoked in `render()`.
*/
package wile.engineersdecor.libmc.detail;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
@OnlyIn(Dist.CLIENT)
public class TooltipDisplay
{
private static long default_delay = 450;
private static int default_max_deviation = 1;
public static void config(long delay, int max_deviation)
{
default_delay = Mth.clamp(delay, 500, 5000);
default_max_deviation = Mth.clamp(max_deviation, 1, 5);
}
// ---------------------------------------------------------------------------------------------------
public static class TipRange
{
public final int x0,y0,x1,y1;
public final Supplier<Component> text;
public TipRange(int x, int y, int w, int h, Component text)
{ this(x,y,w,h,()->text); }
public TipRange(int x, int y, int w, int h, Supplier<Component> text)
{ this.text=text; this.x0=x; this.y0=y; this.x1=x0+w-1; this.y1=y0+h-1; }
}
// ---------------------------------------------------------------------------------------------------
private List<TipRange> ranges = new ArrayList<>();
private long delay = default_delay;
private int max_deviation = default_max_deviation;
private int x_last, y_last;
private long t;
private static boolean had_render_exception = false;
public TooltipDisplay()
{ t = System.currentTimeMillis(); }
public TooltipDisplay init(List<TipRange> ranges, long delay_ms, int max_deviation_xy)
{
this.ranges = ranges;
this.delay = delay_ms;
this.max_deviation = max_deviation_xy;
t = System.currentTimeMillis();
x_last = y_last = 0;
return this;
}
public TooltipDisplay init(List<TipRange> ranges)
{ return init(ranges, default_delay, default_max_deviation); }
public TooltipDisplay init(TipRange... ranges)
{ return init(Arrays.asList(ranges), default_delay, default_max_deviation); }
public TooltipDisplay delay(int ms)
{ delay = (ms<=0) ? default_delay : ms; return this; }
public void resetTimer()
{ t = System.currentTimeMillis(); }
public <T extends AbstractContainerMenu> boolean render(PoseStack mx, final AbstractContainerScreen<T> gui, int x, int y)
{
if(had_render_exception) return false;
if((Math.abs(x-x_last) > max_deviation) || (Math.abs(y-y_last) > max_deviation)) {
x_last = x;
y_last = y;
resetTimer();
return false;
} else if(Math.abs(System.currentTimeMillis()-t) < delay) {
return false;
} else if(ranges.stream().noneMatch(
(tip)->{
if((x<tip.x0) || (x>tip.x1) || (y<tip.y0) || (y>tip.y1)) return false;
String text = tip.text.get().getString();
if(text.isEmpty()) return false;
try {
gui.renderTooltip(mx, tip.text.get(), x, y);
} catch(Exception ex) {
had_render_exception = true;
Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'");
return false;
}
return true;
})
){
resetTimer();
return false;
} else {
return true;
}
}
}

View file

@ -0,0 +1,119 @@
package wile.engineersdecor.libmc.ui;
import net.minecraft.util.Mth;
import net.minecraft.world.Container;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import java.util.function.BiConsumer;
public class Containers
{
// -------------------------------------------------------------------------------------------------------------------
// Slots
// -------------------------------------------------------------------------------------------------------------------
public static class StorageSlot extends Slot
{
protected BiConsumer<ItemStack, ItemStack> slot_change_action_ = (oldStack, newStack)->{};
protected int stack_limit_ = 64;
public boolean enabled = true;
public StorageSlot(Container inventory, int index, int x, int y)
{ super(inventory, index, x, y); }
public StorageSlot setSlotStackLimit(int limit)
{ stack_limit_ = Mth.clamp(limit, 1, 64); return this; }
public int getMaxStackSize()
{ return stack_limit_; }
public StorageSlot setSlotChangeNotifier(BiConsumer<ItemStack, ItemStack> action)
{ slot_change_action_ = action; return this; }
@Override
public void onQuickCraft(ItemStack oldStack, ItemStack newStack)
{ slot_change_action_.accept(oldStack, newStack);} // no crafting trigger
@Override
public void set(ItemStack stack)
{
if(stack.sameItem(getItem())) {
super.set(stack);
} else {
final ItemStack before = getItem().copy();
super.set(stack); // whatever this does else next to setting inventory.
slot_change_action_.accept(before, getItem());
}
}
@Override
public boolean mayPlace(ItemStack stack)
{ return enabled && this.container.canPlaceItem(this.getSlotIndex(), stack); }
@Override
public int getMaxStackSize(ItemStack stack)
{ return Math.min(getMaxStackSize(), stack_limit_); }
@OnlyIn(Dist.CLIENT)
public boolean isActive()
{ return enabled; }
}
public static class LockedSlot extends Slot
{
protected int stack_limit_ = 64;
public boolean enabled = true;
public LockedSlot(Container inventory, int index, int x, int y)
{ super(inventory, index, x, y); }
public LockedSlot setSlotStackLimit(int limit)
{ stack_limit_ = Mth.clamp(limit, 1, 64); return this; }
public int getMaxStackSize()
{ return stack_limit_; }
@Override
public int getMaxStackSize(ItemStack stack)
{ return Math.min(getMaxStackSize(), stack_limit_); }
@Override
public boolean mayPlace(ItemStack stack)
{ return false; }
@Override
public boolean mayPickup(Player player)
{ return false; }
@OnlyIn(Dist.CLIENT)
public boolean isActive()
{ return enabled; }
}
public static class HiddenSlot extends Slot
{
public HiddenSlot(Container inventory, int index)
{ super(inventory, index, 0, 0); }
@Override
public int getMaxStackSize(ItemStack stack)
{ return getMaxStackSize(); }
@Override
public boolean mayPlace(ItemStack stack)
{ return false; }
@Override
public boolean mayPickup(Player player)
{ return false; }
@OnlyIn(Dist.CLIENT)
public boolean isActive()
{ return false; }
}
}

View file

@ -0,0 +1,322 @@
package wile.engineersdecor.libmc.ui;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.sounds.SoundManager;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import wile.engineersdecor.libmc.detail.Auxiliaries;
import wile.engineersdecor.libmc.detail.TooltipDisplay;
import java.util.function.Consumer;
import java.util.function.Function;
public class Guis
{
// -------------------------------------------------------------------------------------------------------------------
// Gui base
// -------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static abstract class ContainerGui<T extends AbstractContainerMenu> extends AbstractContainerScreen<T>
{
protected final ResourceLocation background_image_;
protected final Player player_;
protected final Guis.BackgroundImage gui_background_;
protected final TooltipDisplay tooltip_ = new TooltipDisplay();
public ContainerGui(T menu, Inventory player_inv, Component title, String background_image, int width, int height)
{
super(menu, player_inv, title);
this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image);
this.player_ = player_inv.player;
this.imageWidth = width;
this.imageHeight = height;
gui_background_ = new Guis.BackgroundImage(background_image_, width, height, new Coord2d(0,0));
}
public ContainerGui(T menu, Inventory player_inv, Component title, String background_image)
{
super(menu, player_inv, title);
this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image);
this.player_ = player_inv.player;
gui_background_ = new Guis.BackgroundImage(background_image_, imageWidth, imageHeight, new Coord2d(0,0));
}
@Override
public void init()
{
super.init();
gui_background_.init(this, new Coord2d(0,0)).show();
}
@Override
public void render(PoseStack mx, int mouseX, int mouseY, float partialTicks)
{
renderBackground(mx);
super.render(mx, mouseX, mouseY, partialTicks);
if(!tooltip_.render(mx, this, mouseX, mouseY)) renderTooltip(mx, mouseX, mouseY);
}
@Override
protected void renderLabels(PoseStack mx, int x, int y)
{}
@Override
@SuppressWarnings("deprecation")
protected final void renderBg(PoseStack mx, float partialTicks, int mouseX, int mouseY)
{
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
gui_background_.draw(mx, this);
renderBgWidgets(mx, partialTicks, mouseX, mouseY);
RenderSystem.disableBlend();
}
public final ResourceLocation getBackgroundImage()
{ return background_image_; }
protected void renderBgWidgets(PoseStack mx, float partialTicks, int mouseX, int mouseY)
{}
protected void renderItemTemplate(PoseStack mx, ItemStack stack, int x, int y)
{
final ItemRenderer ir = itemRenderer;
final int main_zl = getBlitOffset();
final float zl = ir.blitOffset;
final int x0 = getGuiLeft();
final int y0 = getGuiTop();
ir.blitOffset = -80;
ir.renderGuiItem(stack, x0+x, y0+y);
RenderSystem.disableColorLogicOp(); //RenderSystem.disableColorMaterial();
RenderSystem.enableDepthTest(); //RenderSystem.enableAlphaTest();
RenderSystem.defaultBlendFunc();
RenderSystem.enableBlend();
ir.blitOffset = zl;
setBlitOffset(100);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.setShaderColor(0.7f, 0.7f, 0.7f, 0.8f);
RenderSystem.setShaderTexture(0, background_image_);
blit(mx, x0+x, y0+y, x, y, 16, 16);
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
setBlitOffset(main_zl);
}
}
// -------------------------------------------------------------------------------------------------------------------
// Gui elements
// -------------------------------------------------------------------------------------------------------------------
@OnlyIn(Dist.CLIENT)
public static class Coord2d
{
public final int x, y;
public Coord2d(int x, int y) { this.x=x; this.y=y; }
}
@OnlyIn(Dist.CLIENT)
public static class UiWidget extends net.minecraft.client.gui.components.AbstractWidget
{
protected static final Component EMPTY_TEXT = new TextComponent("");
protected static final Function<UiWidget,Component> NO_TOOLTIP = (uiw)->EMPTY_TEXT;
@SuppressWarnings("all") private Function<UiWidget,Component> tooltip_ = NO_TOOLTIP;
public UiWidget(int x, int y, int width, int height, Component title)
{ super(x, y, width, height, title); }
public UiWidget init(Screen parent)
{
this.x += ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiLeft() : 0);
this.y += ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiTop() : 0);
return this;
}
public UiWidget init(Screen parent, Coord2d position)
{
this.x = position.x + ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiLeft() : 0);
this.y = position.y + ((parent instanceof AbstractContainerScreen<?>) ? ((AbstractContainerScreen<?>)parent).getGuiTop() : 0);
return this;
}
public int getWidth()
{ return this.width; }
public int getHeight()
{ return this.height; }
public UiWidget show()
{ visible = true; return this; }
public UiWidget hide()
{ visible = false; return this; }
@Override
public void renderButton(PoseStack matrixStack, int mouseX, int mouseY, float partialTicks)
{
super.renderButton(matrixStack, mouseX, mouseY, partialTicks);
if(isHovered()) renderToolTip(matrixStack, mouseX, mouseY);
}
@Override
@SuppressWarnings("all")
public void renderToolTip(PoseStack matrixStack, int mouseX, int mouseY)
{
if(tooltip_ == NO_TOOLTIP) return;
/// todo: need a Screen for that, not sure if adding a reference initialized in init() may cause GC problems.
}
@Override
public void updateNarration(NarrationElementOutput element_output)
{}
}
@OnlyIn(Dist.CLIENT)
public static class HorizontalProgressBar extends UiWidget
{
private final Coord2d texture_position_base_;
private final Coord2d texture_position_filled_;
private final ResourceLocation atlas_;
private double progress_max_ = 100;
private double progress_ = 0;
public HorizontalProgressBar(ResourceLocation atlas, int width, int height, Coord2d base_texture_xy, Coord2d filled_texture_xy)
{
super(0, 0, width, height, EMPTY_TEXT);
atlas_ = atlas;
texture_position_base_ = base_texture_xy;
texture_position_filled_ = filled_texture_xy;
}
public HorizontalProgressBar setProgress(double progress)
{ progress_ = Mth.clamp(progress, 0, progress_max_); return this; }
public double getProgress()
{ return progress_; }
public HorizontalProgressBar setMaxProgress(double progress)
{ progress_max_ = Math.max(progress, 0); return this; }
public double getMaxProgress()
{ return progress_max_; }
public HorizontalProgressBar show()
{ visible = true; return this; }
public HorizontalProgressBar hide()
{ visible = false; return this; }
@Override
public void playDownSound(SoundManager handler)
{}
@Override
protected void renderBg(PoseStack mx, Minecraft mc, int x, int y)
{}
@Override
public void renderButton(PoseStack mx, int mouseX, int mouseY, float partialTicks)
{
RenderSystem.setShaderTexture(0, atlas_);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
blit(mx, x, y, texture_position_base_.x, texture_position_base_.y, width, height);
if((progress_max_ > 0) && (progress_ > 0)) {
int w = Mth.clamp((int)Math.round((progress_ * width) / progress_max_), 0, width);
blit(mx, x, y, texture_position_filled_.x, texture_position_filled_.y, w, height);
}
}
}
@OnlyIn(Dist.CLIENT)
public static class BackgroundImage extends UiWidget
{
private final ResourceLocation atlas_;
private final Coord2d atlas_position_;
public boolean visible;
public BackgroundImage(ResourceLocation atlas, int width, int height, Coord2d atlas_position)
{
super(0, 0, width, height, EMPTY_TEXT);
atlas_ = atlas;
atlas_position_ = atlas_position;
this.width = width;
this.height = height;
visible = true;
}
public void draw(PoseStack mx, Screen parent)
{
if(!visible) return;
RenderSystem.setShaderTexture(0, atlas_);
parent.blit(mx, x, y, atlas_position_.x, atlas_position_.y, width, height);
}
}
@OnlyIn(Dist.CLIENT)
public static class CheckBox extends UiWidget
{
private final Coord2d texture_position_off_;
private final Coord2d texture_position_on_;
private final ResourceLocation atlas_;
private boolean checked_ = false;
private Consumer<CheckBox> on_click_ = (checkbox)->{};
public CheckBox(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position_off, Coord2d atlas_texture_position_on)
{
super(0, 0, width, height, EMPTY_TEXT);
texture_position_off_ = atlas_texture_position_off;
texture_position_on_ = atlas_texture_position_on;
atlas_ = atlas;
}
public boolean checked()
{ return checked_; }
public CheckBox checked(boolean on)
{ checked_ = on; return this; }
public CheckBox onclick(Consumer<CheckBox> action)
{ on_click_ = action; return this; }
@Override
public void onClick(double mouseX, double mouseY)
{ checked_ = !checked_; on_click_.accept(this); }
@Override
public void renderButton(PoseStack mx, int mouseX, int mouseY, float partialTicks)
{
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderTexture(0, atlas_);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha);
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
RenderSystem.enableDepthTest();
Coord2d pos = checked_ ? texture_position_on_ : texture_position_off_;
blit(mx, x, y, pos.x, pos.y, width, height);
}
}
}

View file

@ -1,7 +1,7 @@
# @file mods.toml
# @spec TOML v0.5.0 (https://github.com/toml-lang/toml)
modLoader="javafml"
loaderVersion="[33,)"
loaderVersion="[37,)"
issueTrackerURL="https://github.com/stfwi/engineers-decor/issues/"
license="MIT"
@ -19,13 +19,13 @@ logoFile="logo.png"
[[dependencies.engineersdecor]]
modId="forge"
mandatory=true
versionRange="[35.0.2,)"
versionRange="[37,)"
ordering="NONE"
side="BOTH"
[[dependencies.engineersdecor]]
modId="minecraft"
mandatory=true
versionRange="[1.16.4,)"
versionRange="[1.17.1,1.18)"
ordering="NONE"
side="BOTH"

View file

@ -1,14 +0,0 @@
{
"variants": {
"": [
{ "model": "engineersdecor:block/concrete/gas_concrete_model0" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model1" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model2" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model3" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model4" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model5" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model6" },
{ "model": "engineersdecor:block/concrete/gas_concrete_model7" }
]
}
}

View file

@ -1,16 +0,0 @@
{
"variants": {
"tvariant=0,type=bottom": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s0v0_model" },
"tvariant=1,type=bottom": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s0v1_model" },
"tvariant=2,type=bottom": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s0v2_model" },
"tvariant=3,type=bottom": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s0v3_model" },
"tvariant=0,type=top": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s1v0_model" },
"tvariant=1,type=top": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s1v1_model" },
"tvariant=2,type=top": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s1v2_model" },
"tvariant=3,type=top": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s1v3_model" },
"tvariant=0,type=double": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s2v0_model" },
"tvariant=1,type=double": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s2v1_model" },
"tvariant=2,type=double": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s2v2_model" },
"tvariant=3,type=double": { "model": "engineersdecor:block/slab/specific/gas_concrete_slab_s2v3_model" }
}
}

View file

@ -1,44 +0,0 @@
{
"variants": {
"facing=east,half=bottom,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs" },
"facing=west,half=bottom,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "y": 180, "uvlock": true },
"facing=south,half=bottom,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "y": 90, "uvlock": true },
"facing=north,half=bottom,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "y": 270, "uvlock": true },
"facing=east,half=bottom,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer" },
"facing=west,half=bottom,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "y": 180, "uvlock": true },
"facing=south,half=bottom,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "y": 90, "uvlock": true },
"facing=north,half=bottom,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "y": 270, "uvlock": true },
"facing=east,half=bottom,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "y": 270, "uvlock": true },
"facing=west,half=bottom,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "y": 90, "uvlock": true },
"facing=south,half=bottom,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer" },
"facing=north,half=bottom,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "y": 180, "uvlock": true },
"facing=east,half=bottom,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner" },
"facing=west,half=bottom,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "y": 180, "uvlock": true },
"facing=south,half=bottom,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "y": 90, "uvlock": true },
"facing=north,half=bottom,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "y": 270, "uvlock": true },
"facing=east,half=bottom,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "y": 270, "uvlock": true },
"facing=west,half=bottom,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "y": 90, "uvlock": true },
"facing=south,half=bottom,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner" },
"facing=north,half=bottom,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "y": 180, "uvlock": true },
"facing=east,half=top,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "x": 180, "uvlock": true },
"facing=west,half=top,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "x": 180, "y": 180, "uvlock": true },
"facing=south,half=top,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "x": 180, "y": 90, "uvlock": true },
"facing=north,half=top,shape=straight": { "model": "engineersdecor:block/concrete/gas_concrete_stairs", "x": 180, "y": 270, "uvlock": true },
"facing=east,half=top,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "y": 90, "uvlock": true },
"facing=west,half=top,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "y": 270, "uvlock": true },
"facing=south,half=top,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "y": 180, "uvlock": true },
"facing=north,half=top,shape=outer_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "uvlock": true },
"facing=east,half=top,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "uvlock": true },
"facing=west,half=top,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "y": 180, "uvlock": true },
"facing=south,half=top,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "y": 90, "uvlock": true },
"facing=north,half=top,shape=outer_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_outer", "x": 180, "y": 270, "uvlock": true },
"facing=east,half=top,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "y": 90, "uvlock": true },
"facing=west,half=top,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "y": 270, "uvlock": true },
"facing=south,half=top,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "y": 180, "uvlock": true },
"facing=north,half=top,shape=inner_right": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "uvlock": true },
"facing=east,half=top,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "uvlock": true },
"facing=west,half=top,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "y": 180, "uvlock": true },
"facing=south,half=top,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "y": 90, "uvlock": true },
"facing=north,half=top,shape=inner_left": { "model": "engineersdecor:block/concrete/gas_concrete_stairs_inner", "x": 180, "y": 270, "uvlock": true }
}
}

View file

@ -1,13 +0,0 @@
{
"multipart": [
{ "when": { "up": "true" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_post" } },
{ "when": { "north": "low" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "uvlock": true } },
{ "when": { "east": "low" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "y": 90, "uvlock": true } },
{ "when": { "south": "low" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "y": 180, "uvlock": true } },
{ "when": { "west": "low" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "y": 270, "uvlock": true } },
{ "when": { "north": "tall" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "uvlock": true } },
{ "when": { "east": "tall" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "y": 90, "uvlock": true } },
{ "when": { "south": "tall" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "y": 180, "uvlock": true } },
{ "when": { "west": "tall" }, "apply": { "model": "engineersdecor:block/concrete/gas_concrete_wall_side", "y": 270, "uvlock": true } }
]
}

View file

@ -1,19 +0,0 @@
{
"variants": {
"parts=0": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s0_model" },
"parts=1": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s1_model" },
"parts=2": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s2_model" },
"parts=3": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s3_model" },
"parts=4": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s4_model" },
"parts=5": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s5_model" },
"parts=6": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s6_model" },
"parts=7": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s7_model" },
"parts=8": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s8_model" },
"parts=9": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_s9_model" },
"parts=10": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_sa_model" },
"parts=11": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_sb_model" },
"parts=12": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_sc_model" },
"parts=13": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_sd_model" },
"parts=14": { "model": "engineersdecor:block/slab/specific/halfslab_gas_concrete_se_model" }
}
}

View file

@ -1 +0,0 @@
{ "variants": { "": { "model": "engineersdecor:block/furniture/treated_wood_side_table_model" } } }

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Empty barrel: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB of §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Empty, max %2$s mB",
"block.engineersdecor.gas_concrete": "Gas Concrete Block",
"block.engineersdecor.gas_concrete.help": "Low hardness, high production\n yield concrete. Easy to break\n decorative concrete block.",
"block.engineersdecor.gas_concrete_slab": "Gas Concrete Slab",
"block.engineersdecor.gas_concrete_slab.help": "Low hardness concrete slab.\n Easy to break decorative concrete.",
"block.engineersdecor.gas_concrete_stairs": "Gas Concrete Stairs",
"block.engineersdecor.gas_concrete_stairs.help": "Low hardness concrete stairs.\n Easy to break decorative concrete.",
"block.engineersdecor.gas_concrete_wall": "Gas Concrete Wall",
"block.engineersdecor.gas_concrete_wall.help": "Low hardness concrete wall.\n Easy to break decorative concrete.",
"block.engineersdecor.halfslab_gas_concrete": "Gas Concrete Slice",
"block.engineersdecor.halfslab_gas_concrete.help": "Vertically stackable slice.\n Right/left click with the slice\n stack on the top or bottom surface\n to add/remove slices.",
"block.engineersdecor.halfslab_rebar_concrete": "Rebar Concrete Slice",
"block.engineersdecor.halfslab_rebar_concrete.help": "Vertically stackable slice.\n Right/left click with the slice\n stack on the top or bottom surface\n to add/remove slices.",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Aluminum Sheet Metal Slice",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

View file

@ -113,16 +113,6 @@
"block.engineersdecor.fluid_barrel.status.empty": "Barril vacío: §6%1$s§r / %2$s mB",
"block.engineersdecor.fluid_barrel.status.tip": "§6%1$s§r / %2$s mB de §6%3$s§r",
"block.engineersdecor.fluid_barrel.status.tip.empty": "Vacío, máx. %2$s mB",
"block.engineersdecor.gas_concrete": "Bloque de hormigón de baja dureza",
"block.engineersdecor.gas_concrete.help": "Hormigón de baja dureza y alta producción.\n Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_slab": "Losa de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_slab.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_stairs": "Escaleras de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_stairs.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.gas_concrete_wall": "Muro de hormigón de baja dureza",
"block.engineersdecor.gas_concrete_wall.help": "Bloque de hormigón decorativo fácil de romper.",
"block.engineersdecor.halfslab_gas_concrete": "Capa fina de hormigón de baja dureza",
"block.engineersdecor.halfslab_gas_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones.",
"block.engineersdecor.halfslab_rebar_concrete": "Capa fina de hormigón armado",
"block.engineersdecor.halfslab_rebar_concrete.help": "Porción apilable verticalmente.\n Haga clic derecho/izquierdo con la pila de porciones en la superficie superior o inferior para añadir/eliminar porciones..",
"block.engineersdecor.halfslab_sheetmetal_aluminum": "Capa fina de enchapado de aluminio",

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