Compare commits

..
20 Commits
Author SHA1 Message Date
Martin Robertz 8ab8532915 Merge remote-tracking branch 'ReignOfFROZE/master' into dev 2025-06-19 21:10:42 +02:00
Martin Robertz 67242aa138 Merge branch 'master' into dev 2025-06-19 21:10:38 +02:00
ReignOfFROZE 963975864c Comments 2 2025-06-19 14:53:43 -04:00
ReignOfFROZE 658c4231c4 Comments 2025-06-03 15:36:13 -04:00
Martin Robertz 8b9b4fe25a more h4 2025-04-30 18:04:34 +02:00
Martin Robertz ffd5570bcf update and fix 2025-04-30 17:48:52 +02:00
Martin Robertz 2c50a0ff16 Merge branch 'master' into master 2025-04-30 16:16:16 +02:00
Martin Robertz 54abc5edc1 sa+update deps 2025-04-30 16:15:39 +02:00
Martin Robertz 997da93033 Merge branch 'master' into master 2025-04-30 16:00:31 +02:00
ReignOfFROZE bbcf3a8669 Moved keybind to CommonProxy 2025-04-20 11:52:25 -04:00
ReignOfFROZE f1473bbb3a Bapy-ized textures 2025-04-20 11:49:45 -04:00
ReignOfFROZE 086b0a00ff Spotless 2025-04-20 11:48:23 -04:00
ReignOfFROZE 6a4886796a Switched to using SyncedKeybind instead of vanilla keybind implementation 2025-04-20 11:46:35 -04:00
ReignOfFROZE e6c82d422d Revert file mode changes 2025-04-20 11:24:25 -04:00
ReignOfFROZE b4b6f4e5eb Spotless apply 2025-04-11 18:45:17 -04:00
reign dfe910ebd3 Fix a few bugs, add removal checking 2025-04-11 18:39:15 -04:00
reign 5b0b9ba3c5 Streamline codepaths and add SU compat for permission checking 2025-04-09 21:35:01 -04:00
reign bd76be4f6f Comment out GT5U Dependency 2025-04-08 10:58:39 -04:00
reign d3e258dd30 Remove logs and found workaround for updating mode bug 2025-04-08 10:56:50 -04:00
reign 0999964f1a Add removing and updating modes to ItemConstructableTrigger 2025-04-07 21:03:28 -04:00
48 changed files with 747 additions and 300 deletions

No files matched your search

+1 -2
View File
@@ -1,7 +1,7 @@
javadoc {
javadocTool = javaToolchains.javadocToolFor {
// Last version with frames, but it also has search
languageVersion = JavaLanguageVersion.of(11)
languageVersion = JavaLanguageVersion.of(21)
vendor = JvmVendorSpec.ADOPTIUM
}
@@ -20,7 +20,6 @@ javadoc {
overview "src/main/javadoc/overview.html"
links 'https://docs.oracle.com/javase/8/docs/api/'
links 'https://skmedix.github.io/ForgeJavaDocs/javadoc/forge/1.7.10-10.13.4.1614/'
addBooleanOption("-frames", true)
}
}
+6 -3
View File
@@ -1,10 +1,13 @@
// Add your dependencies here
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.8.44:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.48-GTNH:dev")
api("com.github.GTNewHorizons:GTNHLib:0.6.31:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.7.56-GTNH:dev")
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly ('org.junit.platform:junit-platform-launcher')
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:GT5-Unofficial:5.09.51.261:dev") {
// exclude group: "com.github.GTNewHorizons", module: "StructureLib"
// }
}
+2 -17
View File
@@ -43,10 +43,6 @@ developmentEnvironmentUserName = Developer
# See https://github.com/bsideup/jabel for details on how this works.
enableModernJavaSyntax = true
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
# and should *not* be used in most situations. -1 disables this.
# forceToolchainVersion = -1
# Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
enableGenericInjection = true
@@ -100,9 +96,7 @@ usesMixinDebug = false
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
mixinPlugin =
# Specify the package that contains all of your Mixins. The package must exist or
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
# it must match with this or the build will fail.
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
mixinsPackage = mixins
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
@@ -157,7 +151,7 @@ modrinthProjectId =
# type can be one of [project, version],
# and the name is the Modrinth project or version slug/id of the other mod.
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
modrinthRelations =
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
@@ -178,12 +172,6 @@ curseForgeRelations =
# projects. New projects should not use this parameter.
# customArchiveBaseName =
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
# runClientWorkingDirectory = run/client
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
# runServerWorkingDirectory = run/server
# Optional parameter to have the build automatically fail if an illegal version is used.
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
# The check is ONLY performed if the version is a git tag.
@@ -213,6 +201,3 @@ curseForgeRelations =
# This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
org.gradle.configuration-cache = true
org.gradle.parallel = true
-12
View File
@@ -1,12 +0,0 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/46949723aaa20c7b64d7ecfed7207034/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d6690dfd71c4c91e08577437b5b2beb0/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3cd7045fca9a72cd9bc7d14a385e594c/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/552c7bffe0370c66410a51c55985b511/redirect
toolchainVersion=25
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+5 -2
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -114,6 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -171,6 +172,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -210,7 +212,8 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
Vendored
+2 -1
View File
@@ -70,10 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%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
+1
View File
@@ -1,4 +1,5 @@
// Add any additional repositiroes for your dependencies here
repositories {
mavenLocal()
}
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.10'
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.38'
}
@@ -46,6 +46,7 @@ import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
import cpw.mods.fml.client.config.GuiConfig;
import cpw.mods.fml.client.event.ConfigChangedEvent;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.EventPriority;
@@ -14,9 +14,12 @@ import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import com.gtnewhorizon.gtnhlib.keybind.SyncedKeybind;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
@@ -108,6 +111,16 @@ public class CommonProxy {
}
}
private static final SyncedKeybind MODE_SWAP_KEY = SyncedKeybind.createConfigurable(
"item.structurelib.constructableTrigger.keys.cycleMode",
"item.structurelib.constructableTrigger.keyCategory",
0).registerGlobalListener((entityPlayerMP, syncedKeybind) -> {
final ItemStack held = entityPlayerMP.getHeldItem();
if (held != null && held.getItem() instanceof ItemConstructableTrigger) {
ItemConstructableTrigger.cycleMode(held);
}
});
public void displayConfigGUI(String category) {}
public static class ForgeEventHandler {
@@ -1,8 +1,11 @@
package com.gtnewhorizon.structurelib;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
public interface IStructureCompat {
void markTextureUsed(IIcon o);
boolean checkServerUtilitiesPermission(World world, EntityPlayer actor, int x, int z);
}
@@ -33,6 +33,7 @@ import com.gtnewhorizon.structurelib.util.XSTR;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLInterModComms;
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
@@ -92,7 +93,7 @@ public class StructureLib {
@Mod.Instance
static StructureLib INSTANCE;
@Mod.Instance(STRUCTURECOMPAT_MODID)
static Object COMPAT;
public static Object COMPAT;
static Block blockHint;
static Item itemBlockHint;
@@ -14,28 +14,8 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
public interface IAlignmentLimits {
/**
* Can be placed in any orientation.
*/
IAlignmentLimits UNLIMITED = (direction, rotation, flip) -> true;
/**
* Must be standing upright; i.e., top side of the block facing up.
*/
IAlignmentLimits UPRIGHT = (direction, rotation, flip) -> direction.offsetY == 0
&& (flip.isVerticallyFliped() ? rotation.isUpsideDown() : rotation.isNotRotated());
/**
* Can be in any orientation except upside down; i.e., top side of the block facing down.
*/
IAlignmentLimits NOT_UPSIDE_DOWN = (direction, rotation, flip) -> direction.offsetY != 0
|| (flip.isVerticallyFliped() ? !rotation.isNotRotated() : !rotation.isUpsideDown());
/**
* Front side of the block must be facing up.
*/
IAlignmentLimits FACING_UP = (direction, rotation, flip) -> direction == ForgeDirection.UP;
boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip);
default boolean isNewExtendedFacingValid(ExtendedFacing alignment) {
@@ -57,12 +57,12 @@ public class ConstructableUtility {
}
IConstructable constructable = null;
if (tTileEntity instanceof IConstructableProvider)
constructable = ((IConstructableProvider) tTileEntity).getConstructable();
else if (tTileEntity instanceof IConstructable) {
constructable = (IConstructable) tTileEntity;
if (tTileEntity instanceof IConstructableProvider aConstructableProvider)
constructable = aConstructableProvider.getConstructable();
else if (tTileEntity instanceof IConstructable aConstructable) {
constructable = aConstructable;
} else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) {
ExtendedFacing facing = tTileEntity instanceof IAlignment ? ((IAlignment) tTileEntity).getExtendedFacing()
ExtendedFacing facing = tTileEntity instanceof IAlignment alignment ? alignment.getExtendedFacing()
: ExtendedFacing.of(ForgeDirection.getOrientation(aSide));
constructable = IMultiblockInfoContainer.<TileEntity>get(tTileEntity.getClass())
.toConstructable(tTileEntity, facing);
@@ -70,14 +70,13 @@ public class ConstructableUtility {
if (constructable == null) return false;
if (aPlayer instanceof EntityPlayerMP) {
EntityPlayerMP playerMP = (EntityPlayerMP) aPlayer;
if (aPlayer instanceof EntityPlayerMP playerMP) {
// server side and sneaking (already checked above)
// do construct
if (aPlayer.capabilities.isCreativeMode) {
constructable.construct(aStack, false);
} else if (constructable instanceof ISurvivalConstructable) {
int built = ((ISurvivalConstructable) constructable).survivalConstruct(
} else if (constructable instanceof ISurvivalConstructable aConstructable) {
int built = aConstructable.survivalConstruct(
aStack,
ConfigurationHandler.INSTANCE.getAutoPlaceBudget(),
ISurvivalBuildEnvironment.create(IItemSource.fromPlayer(playerMP), playerMP));
@@ -50,10 +50,6 @@ public class BlockHint extends Block {
@Override
public IIcon getIcon(int aSide, int aMeta) {
// For numbered hints, top and bottom are blank
if (aSide <= 1 && aMeta <= 11) {
return hint[13];
}
return hint[aMeta];
}
@@ -1,20 +1,34 @@
package com.gtnewhorizon.structurelib.core;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
import com.gtnewhorizon.structurelib.mixins.Mixins;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
@IFMLLoadingPlugin.MCVersion("1.7.10")
public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
public static final Logger log = LogManager.getLogger("StructureLibCore");
@Override
public String getMixinConfig() {
return "mixins.structurelib.early.json";
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
return Mixins.getEarlyMixins(loadedCoreMods);
}
@Override
public String[] getASMTransformerClass() {
return null;
return new String[0];
}
@Override
@@ -36,14 +50,4 @@ public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
public String getAccessTransformerClass() {
return null;
}
@Override
public String getMixinConfig() {
return "mixins.structurelib.early.json";
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
return Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld");
}
}
@@ -164,17 +164,40 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
list.onGuiInit(this);
autoCompleteList.onGuiInit(this);
addButton(new GuiButton(ADD_BTN, guiLeft + 12, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "add")));
addButton(new GuiButton(UNSET_BTN, guiLeft + 65, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "unset")));
addButton(new GuiButton(WIPE_BTN, guiLeft + 118, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "wipe")));
addButton(
new GuiButton(
ADD_BTN,
guiLeft + 12,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.add")));
addButton(
new GuiButton(
UNSET_BTN,
guiLeft + 65,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.unset")));
addButton(
new GuiButton(
WIPE_BTN,
guiLeft + 118,
guiTop + 157,
47,
20,
I18n.format("item.structurelib.constructableTrigger.gui.wipe")));
updateButtons();
tooltipSplitCache.clear();
info = StreamSupport
.stream(
Splitter.on("\\n").split(StatCollector.translateToLocal(I18N_PREFIX + "info")).spliterator(),
Splitter.on("\\n").split(
StatCollector.translateToLocal("item.structurelib.constructableTrigger.gui.info"))
.spliterator(),
false)
.flatMap(line -> fontRendererObj.listFormattedStringToWidth(line, width * 1 / 3).stream())
.flatMap(line -> fontRendererObj.listFormattedStringToWidth(line, width * 3 / 5).stream())
.collect(Collectors.toList());
}
@@ -218,7 +241,8 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
actionPerformed(but);
}
public List<GuiButton> getButtonList() {
@SuppressWarnings("unchecked")
private List<GuiButton> getButtonList() {
return buttonList;
}
@@ -261,11 +285,6 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
switch (aKey) {
case Keyboard.KEY_TAB:
if (key.isFocused()) {
if (autoCompleteList.getSelectedElement() != null) {
key.setText(autoCompleteList.getSelectedElement());
return;
}
key.setFocused(false);
value.setFocused(true);
} else {
@@ -303,10 +322,11 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
}
break;
}
if (key.textboxKeyTyped(aChar, aKey) || value.textboxKeyTyped(aChar, aKey)) {
if (key.textboxKeyTyped(aChar, aKey)) {
updateButtons();
return;
}
if (value.textboxKeyTyped(aChar, aKey)) return;
super.keyTyped(aChar, aKey);
}
@@ -317,8 +337,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
getButtonList().get(ADD_BTN).displayString = existing ? I18n.format(I18N_PREFIX + "set")
: I18n.format(I18N_PREFIX + "add");
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText())
&& !StringUtils.isBlank(value.getText())
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(value.getText())
&& Integer.parseInt(value.getText()) > 0;
getButtonList().get(UNSET_BTN).enabled = existing && !StringUtils.isBlank(value.getText());
@@ -378,10 +397,22 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
int topLeftY = (this.height - this.getYSize()) / 2;
drawTexturedModalRect(topLeftX, topLeftY, 0, 0, getXSize(), getYSize());
list.drawScreen(mX, mY, partialTick);
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "title"), guiLeft + 12, guiTop + 9, 0);
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "key"), guiLeft + 12, guiTop + 122, 0);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.title"),
guiLeft + 12,
guiTop + 9,
0);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.key"),
guiLeft + 12,
guiTop + 122,
0);
key.drawTextBox();
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "value"), guiLeft + 12, guiTop + 142, 0);
fontRendererObj.drawString(
I18n.format("item.structurelib.constructableTrigger.gui.value"),
guiLeft + 12,
guiTop + 142,
0);
value.drawTextBox();
}
@@ -424,7 +455,7 @@ public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScre
tooltip.addAll(
fontRendererObj.listFormattedStringToWidth(
StatCollector.translateToLocalFormatted(
I18N_PREFIX + "channels.from",
"item.structurelib.constructableTrigger.gui.channels.from",
Loader.instance().getIndexedModList().get(e.getKey()).getName()),
maxLine));
tooltip.addAll(
@@ -7,9 +7,14 @@ import static net.minecraft.util.StatCollector.translateToLocalFormatted;
import java.util.List;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagInt;
import net.minecraft.util.IIcon;
import net.minecraft.util.StatCollector;
import net.minecraft.world.World;
import org.lwjgl.input.Keyboard;
@@ -18,22 +23,65 @@ import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
public class ItemConstructableTrigger extends Item {
public class ItemConstructableTrigger extends ModeToggleableItem {
private static final IIcon[] textures = new IIcon[3];
public ItemConstructableTrigger() {
setUnlocalizedName("structurelib.constructableTrigger");
setTextureName(MOD_ID + ":itemConstructableTrigger");
setCreativeTab(StructureLib.creativeTab);
}
@Override
public void registerIcons(IIconRegister register) {
textures[0] = register.registerIcon(MOD_ID + ":constructableTrigger/BUILDING");
textures[1] = register.registerIcon(MOD_ID + ":constructableTrigger/UPDATING");
textures[2] = register.registerIcon(MOD_ID + ":constructableTrigger/REMOVING");
}
@Override
public IIcon getIcon(ItemStack stack, int renderPass, EntityPlayer player, ItemStack usingItem, int useRemaining) {
return getIcon(stack, renderPass);
}
@Override
public IIcon getIcon(ItemStack stack, int pass) {
if (!stack.hasTagCompound() || !stack.getTagCompound().hasKey(TAG_MODE)) {
return textures[0];
}
return textures[stack.getTagCompound().getInteger(TAG_MODE)];
}
@Override
public String getItemStackDisplayName(ItemStack stack) {
if (!stack.hasTagCompound() || !stack.getTagCompound().hasKey(TAG_MODE)) {
return getDisplayName(0);
}
return getDisplayName(stack.getTagCompound().getInteger(TAG_MODE));
}
@Override
public IIcon getIconIndex(ItemStack stack) {
return getIcon(stack, 0);
}
@Override
public IIcon getIconFromDamage(int damage) {
return textures[damage];
}
private String getDisplayName(int mode) {
return StatCollector.translateToLocal("item.structurelib.constructableTrigger.name") + " - "
+ StatCollector.translateToLocal("item.structurelib.constructableTrigger.modes." + mode);
}
@Override
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
if (getMovingObjectPositionFromPlayer(world, player, true) == null) {
// channel gui is opened from server and config gui from client
if (!world.isRemote && !player.isSneaking()) {
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
} else if (world.isRemote && player.isSneaking()) {
if (!world.isRemote && getMovingObjectPositionFromPlayer(world, player, true) == null) {
if (player.isSneaking()) {
StructureLib.instance().proxy().displayConfigGUI("registries");
} else {
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
}
}
return super.onItemRightClick(stack, world, player);
@@ -0,0 +1,32 @@
package com.gtnewhorizon.structurelib.item;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagInt;
public class ModeToggleableItem extends Item {
public enum TriggerMode {
BUILDING,
UPDATING,
REMOVING
}
public static TriggerMode getMode(ItemStack held) {
if (held == null || !held.hasTagCompound() || !held.getTagCompound().hasKey(TAG_MODE)) {
// default behavior
return TriggerMode.BUILDING;
}
// get user-defined mode
return TriggerMode.values()[held.getTagCompound().getInteger(TAG_MODE)];
}
public static void cycleMode(ItemStack held) {
if (held.getTagCompound() == null) held.setTagCompound(new NBTTagCompound());
if (!held.getTagCompound().hasKey(TAG_MODE)) held.getTagCompound().setTag(TAG_MODE, new NBTTagInt(0));
held.getTagCompound().setInteger(TAG_MODE, (held.getTagCompound().getInteger(TAG_MODE) + 1) % 3);
}
protected static final String TAG_MODE = "mode";
}
@@ -0,0 +1,188 @@
package com.gtnewhorizon.structurelib.mixins;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import com.gtnewhorizon.structurelib.core.StructureLibCore;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
public enum Mixins {
BLOCK_CHANGE_NOTIFIER(new Builder("Block Change Notifier").setPhase(Phase.EARLY)
.addMixinClasses("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld")
.addTargetedMod(TargetedMod.VANILLA).setSide(Side.BOTH).setApplyIf(() -> true));
private final List<String> mixinClasses;
private final List<TargetedMod> targetedMods;
private final List<TargetedMod> excludedMods;
private final Supplier<Boolean> applyIf;
private final Phase phase;
private final Side side;
Mixins(Builder builder) {
this.mixinClasses = builder.mixinClasses;
this.targetedMods = builder.targetedMods;
this.excludedMods = builder.excludedMods;
this.applyIf = builder.applyIf;
this.phase = builder.phase;
this.side = builder.side;
if (this.mixinClasses.isEmpty()) {
throw new RuntimeException("No mixin class specified for Mixin : " + this.name());
}
if (this.targetedMods.isEmpty()) {
throw new RuntimeException("No targeted mods specified for Mixin : " + this.name());
}
if (this.applyIf == null) {
throw new RuntimeException("No ApplyIf function specified for Mixin : " + this.name());
}
if (this.phase == null) {
throw new RuntimeException("No Phase specified for Mixin : " + this.name());
}
if (this.side == null) {
throw new RuntimeException("No Side function specified for Mixin : " + this.name());
}
}
public static List<String> getEarlyMixins(Set<String> loadedCoreMods) {
final List<String> mixins = new ArrayList<>();
final List<String> notLoading = new ArrayList<>();
for (Mixins mixin : Mixins.values()) {
if (mixin.phase == Phase.EARLY) {
if (mixin.shouldLoad(loadedCoreMods, Collections.emptySet())) {
mixins.addAll(mixin.mixinClasses);
} else {
notLoading.addAll(mixin.mixinClasses);
}
}
}
StructureLibCore.log.info("Not loading the following EARLY mixins: {}", notLoading.toString());
return mixins;
}
public static List<String> getLateMixins(Set<String> loadedMods) {
// NOTE: Any targetmod here needs a modid, not a coremod id
final List<String> mixins = new ArrayList<>();
final List<String> notLoading = new ArrayList<>();
for (Mixins mixin : Mixins.values()) {
if (mixin.phase == Phase.LATE) {
if (mixin.shouldLoad(Collections.emptySet(), loadedMods)) {
mixins.addAll(mixin.mixinClasses);
} else {
notLoading.addAll(mixin.mixinClasses);
}
}
}
StructureLibCore.log.info("Not loading the following LATE mixins: {}", notLoading.toString());
return mixins;
}
private boolean shouldLoadSide() {
return side == Side.BOTH || (side == Side.SERVER && FMLLaunchHandler.side().isServer())
|| (side == Side.CLIENT && FMLLaunchHandler.side().isClient());
}
private boolean allModsLoaded(List<TargetedMod> targetedMods, Set<String> loadedCoreMods, Set<String> loadedMods) {
if (targetedMods.isEmpty()) return false;
for (TargetedMod target : targetedMods) {
if (target == TargetedMod.VANILLA) continue;
// Check coremod first
if (!loadedCoreMods.isEmpty() && target.coreModClass != null
&& !loadedCoreMods.contains(target.coreModClass))
return false;
else if (!loadedMods.isEmpty() && target.modId != null && !loadedMods.contains(target.modId)) return false;
}
return true;
}
private boolean noModsLoaded(List<TargetedMod> targetedMods, Set<String> loadedCoreMods, Set<String> loadedMods) {
if (targetedMods.isEmpty()) return true;
for (TargetedMod target : targetedMods) {
if (target == TargetedMod.VANILLA) continue;
// Check coremod first
if (!loadedCoreMods.isEmpty() && target.coreModClass != null
&& loadedCoreMods.contains(target.coreModClass))
return false;
else if (!loadedMods.isEmpty() && target.modId != null && loadedMods.contains(target.modId)) return false;
}
return true;
}
private boolean shouldLoad(Set<String> loadedCoreMods, Set<String> loadedMods) {
return (shouldLoadSide() && applyIf.get()
&& allModsLoaded(targetedMods, loadedCoreMods, loadedMods)
&& noModsLoaded(excludedMods, loadedCoreMods, loadedMods));
}
private static class Builder {
private final String name;
private final List<String> mixinClasses = new ArrayList<>();
private final List<TargetedMod> targetedMods = new ArrayList<>();
private final List<TargetedMod> excludedMods = new ArrayList<>();
private Supplier<Boolean> applyIf = null;
private Phase phase = null;
private Side side = null;
public Builder(String name) {
this.name = name;
}
public Builder addMixinClasses(String... mixinClasses) {
this.mixinClasses.addAll(Arrays.asList(mixinClasses));
return this;
}
public Builder setPhase(Phase phase) {
if (this.phase != null) {
throw new RuntimeException("Trying to define Phase twice for " + this.name);
}
this.phase = phase;
return this;
}
public Builder setSide(Side side) {
if (this.side != null) {
throw new RuntimeException("Trying to define Side twice for " + this.name);
}
this.side = side;
return this;
}
public Builder setApplyIf(Supplier<Boolean> applyIf) {
this.applyIf = applyIf;
return this;
}
public Builder addTargetedMod(TargetedMod mod) {
this.targetedMods.add(mod);
return this;
}
public Builder addExcludedMod(TargetedMod mod) {
this.excludedMods.add(mod);
return this;
}
}
private enum Side {
BOTH,
CLIENT,
SERVER
}
private enum Phase {
EARLY,
LATE,
}
}
@@ -319,22 +319,6 @@ public interface IStructureDefinition<T> {
return walker.getBuilt();
}
default void iterate(String piece, World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY,
int basePositionZ, int basePositionA, int basePositionB, int basePositionC, IStructureWalker<T> predicate) {
StructureUtility.iterateV2(
getStructureFor(piece),
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
predicate,
"customIterate");
}
/**
* Low level utility.
*
@@ -364,77 +348,47 @@ public interface IStructureDefinition<T> {
}
if (checkBlocksIfNotNullForceCheckAllIfTrue != null) {
boolean success;
if (checkBlocksIfNotNullForceCheckAllIfTrue) {
success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
(e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z),
"check");
} else {
success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
skipBlockUnloaded((e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z)),
"check force");
}
boolean success = StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
checkBlocksIfNotNullForceCheckAllIfTrue ? (e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z)
: skipBlockUnloaded((e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z)),
checkBlocksIfNotNullForceCheckAllIfTrue ? "check" : "check force");
if (StructureLibAPI.isDebugEnabled() && success) {
StructureLib.LOGGER
.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
}
return success;
} else {
if (hintsOnly) {
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.spawnHint(object, world, x, y, z, trigger);
if (ChannelDataAccessor.hasSubChannel(trigger, StructureLibAPI.CHANNEL_SHOW_ERROR)
&& !e.couldBeValid(object, world, x, y, z, trigger)) {
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), world, x, y, z);
}
return true;
}),
"spawnHint");
} else {
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.placeBlock(object, world, x, y, z, trigger);
return true;
}),
"placeBlock");
}
StructureUtility.iterateV2(
elements,
world,
extendedFacing,
basePositionX,
basePositionY,
basePositionZ,
basePositionA,
basePositionB,
basePositionC,
hintsOnly ? ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.spawnHint(object, world, x, y, z, trigger);
if (ChannelDataAccessor.hasSubChannel(trigger, "show_error")
&& !e.couldBeValid(object, world, x, y, z, trigger)) {
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), world, x, y, z);
}
return true;
}) : ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
e.placeBlock(object, world, x, y, z, trigger);
return true;
}),
hintsOnly ? "spawnHint" : "placeBlock");
}
return true;
}
@@ -2,6 +2,8 @@ package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import static com.gtnewhorizon.structurelib.item.ModeToggleableItem.TriggerMode.REMOVING;
import static com.gtnewhorizon.structurelib.structure.StructureUtility.checkForRemoving;
import java.util.Arrays;
import java.util.Collections;
@@ -21,6 +23,7 @@ import net.minecraft.util.IChatComponent;
import net.minecraft.world.World;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
@@ -137,12 +140,14 @@ public interface IStructureElement<T> {
*/
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
IItemSource source = env.getSource();
EntityPlayer actor = env.getActor();
Consumer<IChatComponent> chatter = env.getChatter();
if (e != null) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
PlaceResult skipOrRejectBasedOnCheck = checkForRemoving(mode, check(t, world, x, y, z), world, x, y, z);
if (skipOrRejectBasedOnCheck != PlaceResult.ACCEPT) return skipOrRejectBasedOnCheck;
if (e.getStacks() == null) {
ItemStack taken = source.takeOne(e.getPredicate(), true);
return StructureUtility.survivalPlaceBlock(
@@ -159,7 +164,7 @@ public interface IStructureElement<T> {
chatter);
}
for (ItemStack stack : e.getStacks()) {
if (!source.takeOne(stack, true)) continue;
if (mode != REMOVING && !source.takeOne(stack, true)) continue;
return StructureUtility.survivalPlaceBlock(
stack,
NBTMode.EXACT,
@@ -2,25 +2,8 @@ package com.gtnewhorizon.structurelib.structure;
import net.minecraft.world.World;
/**
* A functional interface that is used to iterate over an arbitrary structure.
*/
@FunctionalInterface
public interface IStructureWalker<T> {
interface IStructureWalker<T> {
/**
* Called once per structure element.
*
* @param element The current element
* @param world The current world
* @param x The absolute X position of the element in world coordinates
* @param y The absolute Y position of the element in world coordinates
* @param z The absolute Z position of the element in world coordinates
* @param a The relative A position of the element in structure-local coordinates (pre transform)
* @param b The relative B position of the element in structure-local coordinates (pre transform)
* @param c The relative C position of the element in structure-local coordinates (pre transform)
* @return False to stop iterating, true to continue.
*/
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
@@ -1,33 +1,23 @@
package com.gtnewhorizon.structurelib.structure;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import static java.lang.Integer.MIN_VALUE;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizon.structurelib.IStructureCompat;
import com.gtnewhorizon.structurelib.StructureEvent.StructureElementVisitedEvent;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
import com.gtnewhorizon.structurelib.structure.adders.IBlockAdder;
import com.gtnewhorizon.structurelib.structure.adders.ITileAdder;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.block.Block;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
@@ -41,24 +31,18 @@ import net.minecraft.util.IChatComponent;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizon.structurelib.StructureEvent.StructureElementVisitedEvent;
import com.gtnewhorizon.structurelib.StructureLib;
import com.gtnewhorizon.structurelib.StructureLibAPI;
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
import com.gtnewhorizon.structurelib.structure.IStructureElement.PlaceResult;
import com.gtnewhorizon.structurelib.structure.adders.IBlockAdder;
import com.gtnewhorizon.structurelib.structure.adders.ITileAdder;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import javax.annotation.Nullable;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.*;
import cpw.mods.fml.common.registry.GameRegistry;
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
import static com.gtnewhorizon.structurelib.item.ModeToggleableItem.TriggerMode.*;
import static java.lang.Integer.MIN_VALUE;
/**
* A brief index of everything contained
@@ -146,6 +130,8 @@ public class StructureUtility {
private static final String NICE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz=|!@#$%&()[]{};:<>/?_,.*^'`";
private static final boolean isSULoaded = Loader.isModLoaded("serverutilities");
@SuppressWarnings("rawtypes")
private static final Map<Vec3Impl, IStructureNavigate> STEP = new HashMap<>();
@@ -206,7 +192,11 @@ public class StructureUtility {
@Override
public boolean placeBlock(Object o, World world, int x, int y, int z, ItemStack trigger) {
world.setBlock(x, y, z, StructureLibAPI.getBlockHint(), 14, 2);
placeRemoveOrUpdate(
trigger,
() -> world.setBlock(x, y, z, StructureLibAPI.getBlockHint(), 14, 2),
() -> world.setBlock(x, y, z, StructureLibAPI.getBlockHint(), 14, 2),
() -> world.setBlock(x, y, z, Blocks.air, 0, 2));
return true;
}
@@ -219,11 +209,16 @@ public class StructureUtility {
@Override
public PlaceResult survivalPlaceBlock(Object o, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(o, world, x, y, z)) return PlaceResult.SKIP;
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
if (mode != REMOVING && check(o, world, x, y, z)) return PlaceResult.SKIP;
// user should place anything here.
// maybe make this configurable, but for now we try to take some cobble from user
if (env.getSource().takeOne(new ItemStack(Blocks.cobblestone), false)) {
world.setBlock(x, y, z, Blocks.cobblestone, 0, 2);
placeRemoveOrUpdate(
trigger,
() -> world.setBlock(x, y, z, Blocks.cobblestone, 0, 2),
() -> world.setBlock(x, y, z, Blocks.cobblestone, 0, 2),
() -> world.setBlock(x, y, z, Blocks.air, 0, 2));
}
return PlaceResult.REJECT;
}
@@ -330,28 +325,91 @@ public class StructureUtility {
public static PlaceResult survivalPlaceBlock(Block block, int meta, World world, int x, int y, int z, IItemSource s,
EntityPlayer actor, Consumer<IChatComponent> chatter) {
if (block == null) throw new NullPointerException();
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
ItemConstructableTrigger.TriggerMode mode = BUILDING;
if (actor.getHeldItem() != null && actor.getHeldItem().getItem() instanceof ItemConstructableTrigger) {
// best effort lookup if the item in the player's hand is a trigger, and if it is, get the mode from the
// ...trigger instead of assuming default behavior
mode = ItemConstructableTrigger.getMode(actor.getHeldItem());
}
if (mode == BUILDING && !StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor))
return PlaceResult.REJECT;
if (mode == UPDATING) {
if (!world.isAirBlock(x, y, z)) {
if (world.getTileEntity(x, y, z) instanceof TileEntity) {
// We're in updating mode and the block we're looking at might be important, so let's just leave it
// alone
// since it's probably not something we want to replace with something else
return PlaceResult.SKIP;
}
Block blockInWorld = world.getBlock(x, y, z);
if (blockInWorld.isAssociatedBlock(block) && blockInWorld.getDamageValue(world, x, y, z) == meta) {
// This is the block we are looking for, move along
return PlaceResult.SKIP;
}
}
}
Item itemBlock = Item.getItemFromBlock(block);
int itemMeta = itemBlock instanceof ISpecialItemBlock
? ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta)
: meta;
ItemStack stack = new ItemStack(itemBlock, 1, itemMeta);
if (!s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation(
"structurelib.autoplace.error.no_simple_block",
stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (block instanceof ICustomBlockSetting) {
ICustomBlockSetting blockCustom = (ICustomBlockSetting) block;
blockCustom.setBlock(world, x, y, z, meta);
} else if (!stack.copy()
.tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f)) {
if (mode != REMOVING) {
if (!s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation(
"structurelib.autoplace.error.no_simple_block",
stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (block instanceof ICustomBlockSetting) {
ICustomBlockSetting blockCustom = (ICustomBlockSetting) block;
blockCustom.setBlock(world, x, y, z, meta);
} else {
if (mode == UPDATING && !world.isAirBlock(x, y, z) && checkPermission(world, actor, x, y, z)) {
// We're in updating mode and there's a block in our way (grr), so let's remove whatever is there
// ...and drop it to the player
ItemStack toGive = new ItemStack(world.getBlock(x, y, z));
toGive.setItemDamage(world.getBlock(x, y, z).getDamageValue(world, x, y, z));
if (!world.setBlock(x, y, z, Blocks.air, 0, 4) || !tryGiveOrDropItem(world, actor, toGive)) {
// We either weren't able to give the player the block that was there, or we weren't able to
// remove
// the block, either way, we need to reject the update. Also, since we hid the block update from
// the client in an effort to make the exchange as seamless as possible, we need to now notify
// the client if we did successfully remove the block.
if (world.isAirBlock(x, y, z)) world.notifyBlockChange(x, y, z, Blocks.air);
return PlaceResult.REJECT;
}
}
if (!stack.copy()
.tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f)) {
return PlaceResult.REJECT;
}
if (!s.takeOne(stack, false)) {
// rollback
}
}
if (mode == REMOVING) {
// We're in removing mode, so let's check some stuff
if (world.isAirBlock(x, y, z)) {
return PlaceResult.SKIP;
}
ItemStack toGive = new ItemStack(world.getBlock(x, y, z));
toGive.setItemDamage(world.getBlock(x, y, z).getDamageValue(world, x, y, z));
if (!checkPermission(world, actor, x, y, z)) {
return PlaceResult.REJECT;
}
world.setBlockToAir(x, y, z);
if (!tryGiveOrDropItem(world, actor, toGive)) {
// We're in removing mode and something went wrong while trying to give the player the block we removed,
// let's just fail for now
return PlaceResult.REJECT;
}
} else if (!s.takeOne(stack, false)) {
// If the user didn't have the block in their inventory, and we're in some
// other mode, let's check some stuff
if (world.isAirBlock(x, y, z)) {
return PlaceResult.SKIP;
}
ItemStack toGive = new ItemStack(world.getBlock(x, y, z));
toGive.setItemDamage(world.getBlock(x, y, z).getDamageValue(world, x, y, z));
world.setBlockToAir(x, y, z);
}
return PlaceResult.ACCEPT;
@@ -444,24 +502,90 @@ public class StructureUtility {
if (stack == null) throw new NullPointerException();
if (stack.stackSize != 1) throw new IllegalArgumentException();
if (!(stack.getItem() instanceof ItemBlock)) throw new IllegalArgumentException();
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
if (!assumeStackPresent && !s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation("structurelib.autoplace.error.no_item_stack", stack.func_151000_E()));
return PlaceResult.REJECT;
ItemConstructableTrigger.TriggerMode mode = BUILDING;
if (actor.getHeldItem() != null && actor.getHeldItem().getItem() instanceof ItemConstructableTrigger) {
// best effort lookup if the item in the player's hand is a trigger, and if it is, get the mode from the
// ...trigger instead of assuming default behavior
mode = ItemConstructableTrigger.getMode(actor.getHeldItem());
}
if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f))
return PlaceResult.REJECT;
if (!s.takeOne(stack, false))
// this is bad! probably an exploit somehow. Let's nullify the block we just placed instead
if (mode != REMOVING) {
if (mode == BUILDING) {
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) {
return PlaceResult.SKIP;
}
}
if (mode == UPDATING) {
if (!world.isAirBlock(x, y, z)) {
if (world.getTileEntity(x, y, z) instanceof TileEntity) {
return PlaceResult.SKIP;
}
Block blockInWorld = world.getBlock(x, y, z);
ItemStack blockInWorldItem = new ItemStack(blockInWorld);
blockInWorldItem.setItemDamage(blockInWorld.getDamageValue(world, x, y, z));
if (blockInWorldItem.isItemEqual(stack)) {
// This is the block we are looking for, move along
return PlaceResult.SKIP;
}
}
}
if (!assumeStackPresent && !s.takeOne(stack, true)) {
if (chatter != null) chatter.accept(
new ChatComponentTranslation(
"structurelib.autoplace.error.no_item_stack",
stack.func_151000_E()));
return PlaceResult.REJECT;
}
if (mode == UPDATING && !world.isAirBlock(x, y, z) && checkPermission(world, actor, x, y, z)) {
// We're in updating mode and there's a block in our way (grr), so let's remove whatever is there
// ...and drop it to the player
ItemStack toGive = new ItemStack(world.getBlock(x, y, z));
toGive.setItemDamage(world.getBlock(x, y, z).getDamageValue(world, x, y, z));
if (!world.setBlock(x, y, z, Blocks.air, 0, 4) || !tryGiveOrDropItem(world, actor, toGive)) {
// We either weren't able to give the player the block that was there, or we weren't able to remove
// the block, either way, we need to reject the update. Also, since we hid the block update from
// the client in an effort to make the exchange as seamless as possible, we need to now notify
// the client if we did successfully remove the block.
if (world.isAirBlock(x, y, z)) world.notifyBlockChange(x, y, z, Blocks.air);
return PlaceResult.REJECT;
}
}
if (!stack.copy()
.tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f)) {
return PlaceResult.REJECT;
}
}
if (mode == REMOVING) {
if (world.isAirBlock(x, y, z)) {
return PlaceResult.SKIP;
}
// If we're in removing mode, we set it to air
ItemStack toGive = new ItemStack(world.getBlock(x, y, z));
toGive.setItemDamage(world.getBlock(x, y, z).getDamageValue(world, x, y, z));
if (!checkPermission(world, actor, x, y, z)) {
return PlaceResult.REJECT;
}
world.setBlockToAir(x, y, z);
if (!tryGiveOrDropItem(world, actor, toGive)) {
// We were in removing mode and something went wrong while trying to give the player the block we
// ...removed, let's just fail for now
return PlaceResult.REJECT;
}
} else if (!s.takeOne(stack, false)) {
if (world.isAirBlock(x, y, z)) {
return PlaceResult.SKIP;
}
// If the user didn't have the item in their inventory, we set it to air
ItemStack toGive = new ItemStack(world.getBlock(x, y, z));
toGive.setItemDamage(world.getBlock(x, y, z).getDamageValue(world, x, y, z));
world.setBlockToAir(x, y, z);
}
return PlaceResult.ACCEPT;
}
/**
* Return a structure element that only allow air (or air equivalents, like Railcraft hidden blocks). You usually
* don't need to call this yourselves. Use {@code -} in shape to automatically use this. Provided nontheless in case
* you want this as a fallback to something else.
* don't need to call this yourselves. Use {@code -} in shape to automatically use this. Provided nonetheless in
* case you want this as a fallback to something else.
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElement<T> isAir() {
@@ -470,8 +594,8 @@ public class StructureUtility {
/**
* Return a structure element that allow anything but air (or air equivalents, like Railcraft hidden blocks). You
* usually don't need to call this yourselves. Use {@code +} in shape to automatically use this. Provided nontheless
* in case you want this as a fallback to something else.
* usually don't need to call this yourselves. Use {@code +} in shape to automatically use this. Provided
* nonetheless in case you want this as a fallback to something else.
*/
@SuppressWarnings("unchecked")
public static <T> IStructureElement<T> notAir() {
@@ -637,7 +761,7 @@ public class StructureUtility {
* given tier, then player will be allowed to use multiple kinds of blocks, but their tier is guaranteed to be the
* same.
* <p>
* <h3>WARNING</h3> <b>You SHOULD NOT return notSet from your tierExtractor.</b> If you do so, we will have this
* <h4>WARNING</h4> <b>You SHOULD NOT return notSet from your tierExtractor.</b> If you do so, we will have this
* chain of events:
* <ul>
* <li>at check start, tier is reset to notSet
@@ -650,7 +774,7 @@ public class StructureUtility {
* doing its business
* <li>Player enjoy a (probably) hilariously shaped multi and (probably) reduced build cost.
* </ul>
* <h3>Example Implementation</h3>
* <h4>Example Implementation</h4>
* <p>
* Assume you have 16 tier, each map to one particular block's 16 different meta. You will usually want something
* like this
@@ -709,7 +833,7 @@ public class StructureUtility {
List<Pair<Block, Integer>> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers;
if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException();
IStructureElementCheckOnly<T> check = ofBlocksTiered(tierExtractor, notSet, setter, getter);
return new StructureElement_Bridge<T>() {
return new StructureElement_Bridge<>() {
@Override
public boolean check(T t, World world, int x, int y, int z) {
@@ -734,6 +858,11 @@ public class StructureUtility {
return Objects.equals(hintTier, worldTier);
}
private boolean isInAllKnownTiers(Block b) {
return allKnownTiers != null
&& allKnownTiers.stream().anyMatch(pair -> pair.getLeft().isAssociatedBlock(b));
}
@Override
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
Pair<Block, Integer> hint = getHint(trigger);
@@ -766,13 +895,21 @@ public class StructureUtility {
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
Pair<Block, Integer> hint = getHint(trigger);
if (hint == null) return PlaceResult.REJECT; // TODO or SKIP?
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
TIER tier = tierExtractor.convert(block, meta);
if (Objects.equals(tier, tierExtractor.convert(hint.getKey(), hint.getValue())))
return PlaceResult.SKIP;
if (mode == REMOVING) {
if (!isInAllKnownTiers(block) && !world.isAirBlock(x, y, z)) {
return PlaceResult.REJECT;
}
} else {
if ((Objects.equals(tier, tierExtractor.convert(hint.getKey(), hint.getValue())))) {
return PlaceResult.SKIP;
}
}
return StructureUtility.survivalPlaceBlock(
hint.getKey(),
hint.getValue(),
@@ -861,7 +998,9 @@ public class StructureUtility {
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
PlaceResult skipOrRejectBasedOnCheck = checkForRemoving(mode, check(t, world, x, y, z), world, x, y, z);
if (skipOrRejectBasedOnCheck != PlaceResult.ACCEPT) return skipOrRejectBasedOnCheck;
if (getBlock() == null) return PlaceResult.REJECT;
return StructureUtility.survivalPlaceBlock(
getBlock(),
@@ -948,7 +1087,9 @@ public class StructureUtility {
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
PlaceResult skipOrRejectBasedOnCheck = checkForRemoving(mode, check(t, world, x, y, z), world, x, y, z);
if (skipOrRejectBasedOnCheck != PlaceResult.ACCEPT) return skipOrRejectBasedOnCheck;
if (init()) return StructureUtility.survivalPlaceBlock(block, meta, world, x, y, z, s, actor, chatter);
return fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
}
@@ -956,7 +1097,9 @@ public class StructureUtility {
@Override
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
PlaceResult skipOrRejectBasedOnCheck = checkForRemoving(mode, check(t, world, x, y, z), world, x, y, z);
if (skipOrRejectBasedOnCheck != PlaceResult.ACCEPT) return skipOrRejectBasedOnCheck;
if (init()) return StructureUtility.survivalPlaceBlock(
block,
meta,
@@ -1584,8 +1727,15 @@ public class StructureUtility {
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta)
return PlaceResult.SKIP;
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
PlaceResult skipOrRejectBasedOnCheck = checkForRemoving(
mode,
world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta,
world,
x,
y,
z);
if (skipOrRejectBasedOnCheck != PlaceResult.ACCEPT) return skipOrRejectBasedOnCheck;
return StructureUtility.survivalPlaceBlock(
defaultBlock,
defaultMeta,
@@ -1630,8 +1780,15 @@ public class StructureUtility {
@Deprecated
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
AutoPlaceEnvironment env) {
if (world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta)
return PlaceResult.SKIP;
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
PlaceResult skipOrRejectBasedOnCheck = checkForRemoving(
mode,
world.getBlock(x, y, z) == defaultBlock && world.getBlockMetadata(x, y, z) == defaultMeta,
world,
x,
y,
z);
if (skipOrRejectBasedOnCheck != PlaceResult.ACCEPT) return skipOrRejectBasedOnCheck;
return StructureUtility.survivalPlaceBlock(
defaultBlock,
defaultMeta,
@@ -3265,4 +3422,57 @@ public class StructureUtility {
}
return shape;
}
/**
* Performs a creation, removal, or update operation at the given position
*
* @param trigger ItemStack for the trigger item
* @param onCreate Operation to be performed when the mode of the trigger is set to building mode
* @param onUpdate Operation to be performed when the mode of the trigger is set to updating mode
* @param onRemove Operation to be performed when the mode of the trigger is set to removing mode
*/
public static void placeRemoveOrUpdate(ItemStack trigger, Runnable onCreate, Runnable onUpdate, Runnable onRemove) {
ItemConstructableTrigger.TriggerMode mode = ItemConstructableTrigger.getMode(trigger);
switch (mode) {
case BUILDING:
onCreate.run();
break;
case UPDATING:
onUpdate.run();
break;
case REMOVING:
onRemove.run();
break;
}
}
public static boolean tryGiveOrDropItem(World world, EntityPlayer actor, ItemStack stack) {
if (actor.inventory.addItemStackToInventory(stack)) {
actor.inventoryContainer.detectAndSendChanges();
return true;
}
EntityItem toSpawn = new EntityItem(world, actor.posX, actor.posY, actor.posZ, stack);
// For some reason these things sometimes spawn with the velocity of an F1 car, so let's prevent that
toSpawn.motionX = 0;
toSpawn.motionY = 0;
toSpawn.motionZ = 0;
return world.spawnEntityInWorld(toSpawn);
}
public static boolean checkPermission(World world, EntityPlayer actor, int x, int y, int z) {
boolean res = true;
if (isSULoaded && StructureLib.COMPAT instanceof IStructureCompat isc) {
res = isc.checkServerUtilitiesPermission(world, actor, x, z);
}
res = res && !world.getBlock(x, y, z).isAssociatedBlock(Blocks.bedrock);
// maybe check mining level who knows :shrug:
return res;
}
public static PlaceResult checkForRemoving(ItemConstructableTrigger.TriggerMode mode, boolean checkResult,
World world, int x, int y, int z) {
if (mode != REMOVING && checkResult) return PlaceResult.SKIP;
else if (mode != BUILDING && !checkResult && !world.isAirBlock(x, y, z)) return PlaceResult.REJECT;
return PlaceResult.ACCEPT;
}
}
+10
View File
@@ -133,6 +133,16 @@ StructureLib API documentation.
Usually there is no reason to reimplement this on your own.
</p>
<h2>{@link com.gtnewhorizon.structurelib.item.ModeToggleableItem}</h2>
<p>
This class is the base class for {@link com.gtnewhorizon.structurelib.item.ItemConstructableTrigger} that defines
the <code>TriggerMode</code>s for the {@link com.gtnewhorizon.structurelib.item.ItemConstructableTrigger}. There are
3 modes, <code>BUILDING</code>, <code>UPDATING</code>, and <code>REMOVING</code>. Each of these modes does as they
say, where building will build a new multi from scratch, updating will remove existing an existing structure and
replace it with new components (think upgrading glass on a multi, changing from a tier 1 to tier 2 of a multi, etc),
and removing will just straight up remove the multi.
</p>
<h2><a href="#channels" id="channels">Channels</a></h2>
<p>
@@ -22,6 +22,9 @@ item.structurelib.frontRotationTool.desc.1=Rotates only the front panel,
item.structurelib.frontRotationTool.desc.2=which allows structure rotation.
item.structurelib.constructableTrigger.name=Multiblock Structure Hologram Projector
item.structurelib.constructableTrigger.modes.0=Building Mode
item.structurelib.constructableTrigger.modes.1=Updating Mode
item.structurelib.constructableTrigger.modes.2=Removing Mode
item.structurelib.constructableTrigger.desc.0=Triggers Constructable Interface
item.structurelib.constructableTrigger.desc.1=Shows multiblock construction schematic,
item.structurelib.constructableTrigger.desc.2=use on the multiblock controller.
@@ -34,11 +37,17 @@ item.structurelib.constructableTrigger.gui.title=Sub Channels
item.structurelib.constructableTrigger.gui.key=Key:
item.structurelib.constructableTrigger.gui.value=Value:
item.structurelib.constructableTrigger.gui.add=Add
item.structurelib.constructableTrigger.gui.set=Update
item.structurelib.constructableTrigger.gui.unset=Remove
item.structurelib.constructableTrigger.gui.wipe=§4Clear
item.structurelib.constructableTrigger.gui.set=Set
item.structurelib.constructableTrigger.gui.unset=Unset
item.structurelib.constructableTrigger.gui.wipe=§4Wipe
item.structurelib.constructableTrigger.gui.error.show=Show Errors
item.structurelib.constructableTrigger.gui.error.hide=Hide Errors
item.structurelib.constructableTrigger.gui.gt_no_hatch.enable=No Hatch
item.structurelib.constructableTrigger.gui.gt_no_hatch.disable=Hatches
item.structurelib.constructableTrigger.keyCategory=Multiblock Structure Hologram Projector Keys
item.structurelib.constructableTrigger.keys.cycleMode=Cycle Projector Mode
item.structurelib.constructableTrigger.gui.channels.from=§rMod: §9§o%s
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in their structure and a sub channel allows indication of which variant to use.\n All sub channels have a sequence of lowercase characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\n You can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of "§oIndicates§r §ovalue§r §oX§r §ofor§r §osub§r §ochannel§r §oY§r".
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in its structure and use sub channels to indicate which variant to use.\n Sub channels are has a sequence of lower case characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\nYou can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of §oIndicates value X for sub channel Y§r.
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 B

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 B

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 B

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 B

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 667 B