From 91562498430c1e09dc3384de2ca2dfb8d656d466 Mon Sep 17 00:00:00 2001 From: bdew Date: Thu, 28 Nov 2013 23:03:28 +0200 Subject: [PATCH] Preparing for public release --- .gitignore | 9 + MMPL-1.0.txt | 87 +++++ build.gradle | 112 ++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 50508 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 +++++++++ gradlew.bat | 90 +++++ resources/mcmod.info | 20 ++ src/net/bdew/lib/BdLib.scala | 11 + src/net/bdew/lib/Event.scala | 34 ++ src/net/bdew/lib/Misc.scala | 66 ++++ src/net/bdew/lib/block/HasTE.scala | 34 ++ src/net/bdew/lib/data/DataSlotBoolean.scala | 19 ++ src/net/bdew/lib/data/DataSlotDirection.scala | 20 ++ src/net/bdew/lib/data/DataSlotFloat.scala | 18 + src/net/bdew/lib/data/DataSlotInt.scala | 18 + src/net/bdew/lib/data/DataSlotItemStack.scala | 32 ++ src/net/bdew/lib/data/DataSlotString.scala | 19 ++ src/net/bdew/lib/data/DataSlotTank.scala | 62 ++++ .../lib/data/base/ContainerDataSlots.scala | 39 +++ src/net/bdew/lib/data/base/DataSlot.scala | 42 +++ .../bdew/lib/data/base/DataSlotNumeric.scala | 22 ++ .../bdew/lib/data/base/DataSlotOrdered.scala | 21 ++ src/net/bdew/lib/data/base/DataSlotVal.scala | 39 +++ .../bdew/lib/data/base/TileDataSlots.scala | 64 ++++ src/net/bdew/lib/data/base/UpdateKind.scala | 16 + src/net/bdew/lib/gui/BaseContainer.scala | 46 +++ src/net/bdew/lib/gui/BaseScreen.scala | 89 +++++ src/net/bdew/lib/gui/Direction.scala | 18 + src/net/bdew/lib/gui/Point.scala | 16 + src/net/bdew/lib/gui/Rect.scala | 16 + src/net/bdew/lib/gui/SlotValidating.scala | 18 + src/net/bdew/lib/gui/TextureLocation.scala | 16 + src/net/bdew/lib/gui/widgets/Widget.scala | 26 ++ .../lib/gui/widgets/WidgetFillDataslot.scala | 39 +++ .../lib/gui/widgets/WidgetFluidGauge.scala | 60 ++++ .../bdew/lib/gui/widgets/WidgetLabel.scala | 19 ++ .../lib/gui/widgets/WidgetProgressBar.scala | 18 + .../bdew/lib/gui/widgets/WidgetTextEdit.scala | 19 ++ src/net/bdew/lib/items/ItemUtils.scala | 79 +++++ src/net/bdew/lib/recipes/DataClasses.scala | 181 ++++++++++ src/net/bdew/lib/recipes/RecipeLoader.scala | 319 ++++++++++++++++++ src/net/bdew/lib/recipes/RecipeParser.scala | 124 +++++++ src/net/bdew/lib/recipes/Vanilla.scala | 42 +++ .../lib/recipes/gencfg/ConfigSection.scala | 56 +++ .../bdew/lib/recipes/gencfg/DataClasses.scala | 24 ++ .../recipes/gencfg/GenericConfigLoader.scala | 33 ++ .../recipes/gencfg/GenericConfigParser.scala | 26 ++ .../bdew/lib/rotate/BaseRotateableBlock.scala | 92 +++++ src/net/bdew/lib/rotate/IconType.scala | 28 ++ src/net/bdew/lib/rotate/RotateableTile.scala | 17 + .../bdew/lib/rotate/RotateableTileBlock.scala | 34 ++ src/net/bdew/lib/tile/Component.scala | 14 + src/net/bdew/lib/tile/ExposeTank.scala | 46 +++ src/net/bdew/lib/tile/TileExtended.scala | 69 ++++ .../lib/tile/inventory/BaseInventory.scala | 66 ++++ .../inventory/BreakableInventoryBlock.scala | 25 ++ .../inventory/BreakableInventoryTile.scala | 25 ++ .../inventory/PersistentInventoryTile.scala | 38 +++ .../lib/tile/inventory/SidedInventory.scala | 23 ++ 60 files changed, 2825 insertions(+) create mode 100644 .gitignore create mode 100644 MMPL-1.0.txt create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 resources/mcmod.info create mode 100644 src/net/bdew/lib/BdLib.scala create mode 100644 src/net/bdew/lib/Event.scala create mode 100644 src/net/bdew/lib/Misc.scala create mode 100644 src/net/bdew/lib/block/HasTE.scala create mode 100644 src/net/bdew/lib/data/DataSlotBoolean.scala create mode 100644 src/net/bdew/lib/data/DataSlotDirection.scala create mode 100644 src/net/bdew/lib/data/DataSlotFloat.scala create mode 100644 src/net/bdew/lib/data/DataSlotInt.scala create mode 100644 src/net/bdew/lib/data/DataSlotItemStack.scala create mode 100644 src/net/bdew/lib/data/DataSlotString.scala create mode 100644 src/net/bdew/lib/data/DataSlotTank.scala create mode 100644 src/net/bdew/lib/data/base/ContainerDataSlots.scala create mode 100644 src/net/bdew/lib/data/base/DataSlot.scala create mode 100644 src/net/bdew/lib/data/base/DataSlotNumeric.scala create mode 100644 src/net/bdew/lib/data/base/DataSlotOrdered.scala create mode 100644 src/net/bdew/lib/data/base/DataSlotVal.scala create mode 100644 src/net/bdew/lib/data/base/TileDataSlots.scala create mode 100644 src/net/bdew/lib/data/base/UpdateKind.scala create mode 100644 src/net/bdew/lib/gui/BaseContainer.scala create mode 100644 src/net/bdew/lib/gui/BaseScreen.scala create mode 100644 src/net/bdew/lib/gui/Direction.scala create mode 100644 src/net/bdew/lib/gui/Point.scala create mode 100644 src/net/bdew/lib/gui/Rect.scala create mode 100644 src/net/bdew/lib/gui/SlotValidating.scala create mode 100644 src/net/bdew/lib/gui/TextureLocation.scala create mode 100644 src/net/bdew/lib/gui/widgets/Widget.scala create mode 100644 src/net/bdew/lib/gui/widgets/WidgetFillDataslot.scala create mode 100644 src/net/bdew/lib/gui/widgets/WidgetFluidGauge.scala create mode 100644 src/net/bdew/lib/gui/widgets/WidgetLabel.scala create mode 100644 src/net/bdew/lib/gui/widgets/WidgetProgressBar.scala create mode 100644 src/net/bdew/lib/gui/widgets/WidgetTextEdit.scala create mode 100644 src/net/bdew/lib/items/ItemUtils.scala create mode 100644 src/net/bdew/lib/recipes/DataClasses.scala create mode 100644 src/net/bdew/lib/recipes/RecipeLoader.scala create mode 100644 src/net/bdew/lib/recipes/RecipeParser.scala create mode 100644 src/net/bdew/lib/recipes/Vanilla.scala create mode 100644 src/net/bdew/lib/recipes/gencfg/ConfigSection.scala create mode 100644 src/net/bdew/lib/recipes/gencfg/DataClasses.scala create mode 100644 src/net/bdew/lib/recipes/gencfg/GenericConfigLoader.scala create mode 100644 src/net/bdew/lib/recipes/gencfg/GenericConfigParser.scala create mode 100644 src/net/bdew/lib/rotate/BaseRotateableBlock.scala create mode 100644 src/net/bdew/lib/rotate/IconType.scala create mode 100644 src/net/bdew/lib/rotate/RotateableTile.scala create mode 100644 src/net/bdew/lib/rotate/RotateableTileBlock.scala create mode 100644 src/net/bdew/lib/tile/Component.scala create mode 100644 src/net/bdew/lib/tile/ExposeTank.scala create mode 100644 src/net/bdew/lib/tile/TileExtended.scala create mode 100644 src/net/bdew/lib/tile/inventory/BaseInventory.scala create mode 100644 src/net/bdew/lib/tile/inventory/BreakableInventoryBlock.scala create mode 100644 src/net/bdew/lib/tile/inventory/BreakableInventoryTile.scala create mode 100644 src/net/bdew/lib/tile/inventory/PersistentInventoryTile.scala create mode 100644 src/net/bdew/lib/tile/inventory/SidedInventory.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08c22bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.gradle +/build +/deps +/eclipse +/out + +*.ipr +*.iml +*.iws diff --git a/MMPL-1.0.txt b/MMPL-1.0.txt new file mode 100644 index 0000000..a445a6f --- /dev/null +++ b/MMPL-1.0.txt @@ -0,0 +1,87 @@ +Minecraft Mod Public License +============================ + +Version 1.0.1 + +0. Definitions +-------------- + +Minecraft: Denotes a copy of the Minecraft game licensed by Mojang AB + +User: Anybody that interacts with the software in one of the following ways: + - play + - decompile + - recompile or compile + - modify + - distribute + +Mod: The mod code designated by the present license, in source form, binary +form, as obtained standalone, as part of a wider distribution or resulting from +the compilation of the original or modified sources. + +Dependency: Code required for the mod to work properly. This includes +dependencies required to compile the code as well as any file or modification +that is explicitely or implicitely required for the mod to be working. + +1. Scope +-------- + +The present license is granted to any user of the mod. As a prerequisite, +a user must own a legally acquired copy of Minecraft + +2. Liability +------------ + +This mod is provided 'as is' with no warranties, implied or otherwise. The owner +of this mod takes no responsibility for any damages incurred from the use of +this mod. This mod alters fundamental parts of the Minecraft game, parts of +Minecraft may not work with this mod installed. All damages caused from the use +or misuse of this mad fall on the user. + +3. Play rights +-------------- + +The user is allowed to install this mod on a client or a server and to play +without restriction. + +4. Modification rights +---------------------- + +The user has the right to decompile the source code, look at either the +decompiled version or the original source code, and to modify it. + +5. Derivation rights +-------------------- + +The user has the rights to derive code from this mod, that is to say to +write code that extends or instanciate the mod classes or interfaces, refer to +its objects, or calls its functions. This code is known as "derived" code, and +can be licensed under a license different from this mod. + +6. Distribution of original or modified copy rights +--------------------------------------------------- + +Is subject to distribution rights this entire mod in its various forms. This +include: + - original binary or source forms of this mod files + - modified versions of these binaries or source files, as well as binaries + resulting from source modifications + - patch to its source or binary files + - any copy of a portion of its binary source files + +The user is allowed to redistribute this mod partially, in totality, or +included in a distribution. + +When distributing binary files, the user must provide means to obtain its +entire set of sources or modified sources at no costs. + +All distributions of this mod must remain licensed under the MMPL. + +All dependencies that this mod have on other mods or classes must be licensed +under conditions comparable to this version of MMPL, with the exception of the +Minecraft code and the mod loading framework (e.g. ModLoader, ModLoaderMP or +Bukkit). + +Modified version of binaries and sources, as well as files containing sections +copied from this mod, should be distributed under the terms of the present +license. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..70baad2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,112 @@ +buildscript { + repositories { + mavenCentral() + maven { + name = "bdew" + url = "http://jenkins.bdew.net/maven" + } + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:1.0-SNAPSHOT' + } +} + +apply plugin: 'forge' +apply plugin: 'scala' + +version = "0.9.0" + +if (project.hasProperty('buildnum')) { + project.version = project.version + '.' + project.buildnum +} else { + project.version = project.version + '-dev' +} + +group = "net.bdew" +archivesBaseName = "bdlib" + +minecraft { + version = "1.6.4-9.11.1.964" +} + +import org.apache.tools.ant.filters.ReplaceTokens + +def myTokens = [BdLibVer: project.version] + +sourceSets { + main { + scala { + srcDir 'src' + } + resources { + srcDir 'resources' + } + } + processed { + scala { + srcDir 'build/stage' + } + } +} + +task stage(type: Sync) { + from sourceSets.main.scala + into 'build/stage' + filter(ReplaceTokens, tokens: myTokens) +} + +compileScala { + source = sourceSets.processed.scala + dependsOn stage +} + +processResources { + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + filter(ReplaceTokens, tokens: myTokens) + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} + +task sourceJar(type: Jar) { + from sourceSets.main.allSource +} + +task deobfJar(type: Jar) { + from sourceSets.main.output +} + +jar { + appendix = 'universal' +} + +artifacts { + archives sourceJar + archives deobfJar +} + +apply plugin: 'maven-publish' + +publishing { + publications { + maven(MavenPublication) { + artifact deobfJar + + artifact sourceJar { + classifier "sources" + } + } + } + repositories { + maven { + url "file://var/www/maven" + } + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..d5c591c9c532da774b062aa6d7ab16405ff8700a GIT binary patch literal 50508 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%0DD3;W?!&t=FUIvF}Y7;NYDq6$vu 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/resources/mcmod.info b/resources/mcmod.info new file mode 100644 index 0000000..a5c5240 --- /dev/null +++ b/resources/mcmod.info @@ -0,0 +1,20 @@ +[ +{ + "modid": "bdlib", + "name": "BD Lib", + "description": "", + "version": "@BdLibVer@", + "url": "bdew.net/mods/", + "updateUrl": "", + "authorList": [ + "bdew" + ], + "credits": "", + "logoFile": "", + "screenshots": [ + ], + "parent":"", + "dependencies": [ + ] +} +] \ No newline at end of file diff --git a/src/net/bdew/lib/BdLib.scala b/src/net/bdew/lib/BdLib.scala new file mode 100644 index 0000000..e6405a0 --- /dev/null +++ b/src/net/bdew/lib/BdLib.scala @@ -0,0 +1,11 @@ +package net.bdew.lib + +import cpw.mods.fml.common.Mod +import cpw.mods.fml.common.event.FMLPreInitializationEvent + +@Mod(modid = "bdlib", version = "@BdLibVer@", modLanguage = "scala") +object BdLib { + def preInit(ev: FMLPreInitializationEvent) { + ev.getModLog.info("bdlib @BdLibVer@ loaded") + } +} diff --git a/src/net/bdew/lib/Event.scala b/src/net/bdew/lib/Event.scala new file mode 100644 index 0000000..07ab008 --- /dev/null +++ b/src/net/bdew/lib/Event.scala @@ -0,0 +1,34 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib + +class EventBase[T] extends { + var listeners = Seq.empty[T] + def listen(x: T) = listeners :+= x + def hasListeners = listeners.size > 0 +} + +class Event0 extends EventBase[() => Unit] { + def trigger() = for (x <- listeners) x() +} + +class Event1[T] extends EventBase[(T) => Unit] { + def trigger(p1: T) = for (x <- listeners) x(p1) +} + +class Event2[T, R] extends EventBase[(T, R) => Unit] { + def trigger(p1: T, p2: R) = for (x <- listeners) x(p1, p2) +} + +object Event { + def apply(t: Unit) = new Event0 + def apply[T] = new Event1[T] + def apply[T, R] = new Event2[T, R] +} \ No newline at end of file diff --git a/src/net/bdew/lib/Misc.scala b/src/net/bdew/lib/Misc.scala new file mode 100644 index 0000000..9bee546 --- /dev/null +++ b/src/net/bdew/lib/Misc.scala @@ -0,0 +1,66 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib + +import cpw.mods.fml.common.registry.GameRegistry +import net.minecraft.nbt.{NBTTagCompound, NBTTagList} +import net.minecraft.util.StatCollector +import net.minecraft.item.ItemStack +import cpw.mods.fml.common.versioning.{ArtifactVersion, VersionParser} +import cpw.mods.fml.common.Loader +import net.minecraftforge.oredict.ShapedOreRecipe + +object Misc { + def iterNbtList[X](list: NBTTagList) = + for (i <- 0 until list.tagCount()) yield list.tagAt(i).asInstanceOf[X] + + def toLocal(s: String) = StatCollector.translateToLocal(s) + def toLocalF(s: String, params: Any*) = StatCollector.translateToLocal(s).format(params: _*) + + def flattenRecipe(pattern: Seq[String], items: Map[Char, AnyRef]) = + pattern ++ items.map { + case (k, v) => Seq(new Character(k), v) + }.flatten + + def wrapTag(n: String, f: (NBTTagCompound) => Any)(t: NBTTagCompound) { + val p = t.getCompoundTag(n) + f(p) + t.setCompoundTag(n, p) + } + + def addRecipe(result: ItemStack, pattern: Seq[String], items: Map[Char, AnyRef]) = + GameRegistry.addRecipe(result, flattenRecipe(pattern, items): _*) + + def addRecipeOD(result: ItemStack, pattern: Seq[String], items: Map[Char, AnyRef]) = + GameRegistry.addRecipe(new ShapedOreRecipe(result, flattenRecipe(pattern, items): _*)) + + def min[T](vals: T*)(implicit o: Ordering[T]): T = vals.min(o) + def max[T](vals: T*)(implicit o: Ordering[T]): T = vals.max(o) + + def clamp[T](value: T, min: T, max: T)(implicit o: Ordering[T]): T = if (o.gt(value,max)) max else if (o.lt(value,min)) min else value + + def iterSome[T](list: Seq[T], indexes: Iterable[Int]): Iterable[T] = for (i <- indexes) yield list(i) + def iterSomeEnum[T](list: Seq[T], indexes: Iterable[Int]): Iterable[(Int, T)] = for (i <- indexes) yield i -> list(i) + + def filterType[T](from: Iterable[_], cls: Class[T]) = from.filter(cls.isInstance).asInstanceOf[Iterable[T]] + + def haveModVersion(spec: String): Boolean = { + val req = VersionParser.parseVersionReference(spec) + val modid = req.getLabel + + if (!Loader.instance.getIndexedModList.containsKey(modid)) return false + + val found: ArtifactVersion = Loader.instance.getIndexedModList.get(modid).getProcessedVersion + + if (found == null || !req.containsVersion(found)) return false + + return true + } +} diff --git a/src/net/bdew/lib/block/HasTE.scala b/src/net/bdew/lib/block/HasTE.scala new file mode 100644 index 0000000..ac69827 --- /dev/null +++ b/src/net/bdew/lib/block/HasTE.scala @@ -0,0 +1,34 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.block + +import net.minecraft.block.{ITileEntityProvider, Block} +import net.minecraft.world.{IBlockAccess, World} +import net.minecraft.tileentity.TileEntity +import cpw.mods.fml.common.FMLCommonHandler + +trait HasTE[T] extends Block with ITileEntityProvider { + val TEClass: Class[_ <: TileEntity] + def createNewTileEntity(world: World): TileEntity = TEClass.newInstance() + def getTE(w: IBlockAccess, x: Int, y: Int, z: Int): T = { + var t = w.getBlockTileEntity(x, y, z) + if ((t == null) || !TEClass.isInstance(t)) { + FMLCommonHandler.instance().getFMLLogger.severe("Tile entity for block %s at (%d,%d,%d) is corrupt or missing - recreating".format(this, x, y, z)) + w match { + case ww: World => + t = createNewTileEntity(ww) + ww.setBlockTileEntity(x, y, z, t) + case _ => + sys.error("Unable to recreate TE") + } + } + return t.asInstanceOf[T] + } +} diff --git a/src/net/bdew/lib/data/DataSlotBoolean.scala b/src/net/bdew/lib/data/DataSlotBoolean.scala new file mode 100644 index 0000000..6798631 --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotBoolean.scala @@ -0,0 +1,19 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.minecraft.nbt.NBTTagCompound +import net.bdew.lib.data.base.{UpdateKind, TileDataSlots, DataSlotVal} + +case class DataSlotBoolean(name: String, parent: TileDataSlots, default: Boolean = false) extends DataSlotVal[Boolean] { + var cval = default + def save(t: NBTTagCompound, kind: UpdateKind.Value) = t.setBoolean(name, cval) + def load(t: NBTTagCompound, kind: UpdateKind.Value) = cval = t.getBoolean(name) +} diff --git a/src/net/bdew/lib/data/DataSlotDirection.scala b/src/net/bdew/lib/data/DataSlotDirection.scala new file mode 100644 index 0000000..e22f052 --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotDirection.scala @@ -0,0 +1,20 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.bdew.lib.data.base.{TileDataSlots, UpdateKind, DataSlotVal} +import net.minecraftforge.common.ForgeDirection +import net.minecraft.nbt.NBTTagCompound + +case class DataSlotDirection(name: String, parent: TileDataSlots) extends DataSlotVal[ForgeDirection] { + var cval = ForgeDirection.UP + def save(t: NBTTagCompound, kind: UpdateKind.Value) = t.setByte(name, cval.ordinal().toByte) + def load(t: NBTTagCompound, kind: UpdateKind.Value) = cval = ForgeDirection.getOrientation(t.getByte(name)) +} diff --git a/src/net/bdew/lib/data/DataSlotFloat.scala b/src/net/bdew/lib/data/DataSlotFloat.scala new file mode 100644 index 0000000..ce6ec8e --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotFloat.scala @@ -0,0 +1,18 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.minecraft.nbt.NBTTagCompound +import net.bdew.lib.data.base.{UpdateKind, TileDataSlots, DataSlotNumeric} + +case class DataSlotFloat(name: String, parent: TileDataSlots, default: Float = 0) extends DataSlotNumeric[Float](default) { + def save(t: NBTTagCompound, kind: UpdateKind.Value) = t.setFloat(name, cval) + def load(t: NBTTagCompound, kind: UpdateKind.Value) = cval = t.getFloat(name) +} diff --git a/src/net/bdew/lib/data/DataSlotInt.scala b/src/net/bdew/lib/data/DataSlotInt.scala new file mode 100644 index 0000000..1fe2a05 --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotInt.scala @@ -0,0 +1,18 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.minecraft.nbt.NBTTagCompound +import net.bdew.lib.data.base.{UpdateKind, TileDataSlots, DataSlotNumeric} + +case class DataSlotInt(name: String, parent: TileDataSlots, default: Int = 0) extends DataSlotNumeric[Int](default) { + def save(t: NBTTagCompound, kind: UpdateKind.Value) = t.setInteger(name, cval) + def load(t: NBTTagCompound, kind: UpdateKind.Value) = cval = t.getInteger(name) +} diff --git a/src/net/bdew/lib/data/DataSlotItemStack.scala b/src/net/bdew/lib/data/DataSlotItemStack.scala new file mode 100644 index 0000000..65c5739 --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotItemStack.scala @@ -0,0 +1,32 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.bdew.lib.data.base.{TileDataSlots, UpdateKind, DataSlotVal} +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.item.ItemStack + +case class DataSlotItemStack(name: String, parent: TileDataSlots) extends DataSlotVal[ItemStack] { + var cval: ItemStack = null + + override def update(v: ItemStack) = { + if (!ItemStack.areItemStacksEqual(v, cval)) { + cval = if (v == null) null else v.copy() + parent.dataSlotChanged(this) + } + } + + def save(t: NBTTagCompound, kind: UpdateKind.Value) = { + val tag = new NBTTagCompound() + if (cval != null) cval.writeToNBT(tag) + t.setCompoundTag(name, tag) + } + def load(t: NBTTagCompound, kind: UpdateKind.Value) = cval = ItemStack.loadItemStackFromNBT(t.getCompoundTag(name)) +} diff --git a/src/net/bdew/lib/data/DataSlotString.scala b/src/net/bdew/lib/data/DataSlotString.scala new file mode 100644 index 0000000..77207f5 --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotString.scala @@ -0,0 +1,19 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.minecraft.nbt.NBTTagCompound +import net.bdew.lib.data.base.{UpdateKind, TileDataSlots, DataSlotVal} + +case class DataSlotString(name: String, parent: TileDataSlots, default: String = null) extends DataSlotVal[String] { + var cval: String = default + def save(t: NBTTagCompound, kind: UpdateKind.Value) = if (cval != null) t.setString(name, cval) + def load(t: NBTTagCompound, kind: UpdateKind.Value) = cval = if (t.hasKey(name)) t.getString(name) else null +} diff --git a/src/net/bdew/lib/data/DataSlotTank.scala b/src/net/bdew/lib/data/DataSlotTank.scala new file mode 100644 index 0000000..120bad5 --- /dev/null +++ b/src/net/bdew/lib/data/DataSlotTank.scala @@ -0,0 +1,62 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data + +import net.minecraftforge.fluids.{IFluidTank, FluidTank, FluidStack} +import net.minecraft.nbt.NBTTagCompound +import net.bdew.lib.data.base.{UpdateKind, TileDataSlots, DataSlot} + +abstract class DataSlotTankBase(sz: Int) extends FluidTank(sz) with IFluidTank with DataSlot { + var oldStack: FluidStack = null + var size: Int + + parent.serverTick.listen(checkUpdate) + + updateKind = Set(UpdateKind.GUI, UpdateKind.SAVE) + + def checkUpdate() { + if (!isSame(oldStack, fluid)) { + oldStack = if (fluid == null) null else fluid.copy() + parent.dataSlotChanged(this) + } + } + + def isSame(v1: FluidStack, v2: FluidStack): Boolean = { + if (v1 == null && v2 == null) return true + if (v1 == null || v2 == null) return false + return v1.isFluidStackIdentical(v2) + } + + def save(t: NBTTagCompound, k: UpdateKind.Value) { + val tag = new NBTTagCompound() + writeToNBT(tag) + if (k == UpdateKind.GUI) + tag.setInteger("size", size) + t.setCompoundTag(name, tag) + } + + def load(t: NBTTagCompound, k: UpdateKind.Value) { + val tag = t.getCompoundTag(name) + readFromNBT(tag) + if (k == UpdateKind.GUI) + size = tag.getInteger("size") + } +} + +case class DataSlotTank(name: String, parent: TileDataSlots, var size: Int) extends DataSlotTankBase(size) + +case class DataSlotTankRestricted(name: String, parent: TileDataSlots, var size: Int, fluidID: Int) extends DataSlotTankBase(size) { + def fill(amount: Int, doFill: Boolean) = super.fill(new FluidStack(fluidID, amount), doFill) + + override def fill(resource: FluidStack, doFill: Boolean): Int = { + if ((resource != null) && (resource.fluidID != fluidID)) return 0 + return super.fill(resource, doFill) + } +} diff --git a/src/net/bdew/lib/data/base/ContainerDataSlots.scala b/src/net/bdew/lib/data/base/ContainerDataSlots.scala new file mode 100644 index 0000000..3464526 --- /dev/null +++ b/src/net/bdew/lib/data/base/ContainerDataSlots.scala @@ -0,0 +1,39 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +import net.minecraft.entity.player.EntityPlayerMP +import net.bdew.lib.gui.BaseContainer +import net.minecraft.inventory.ICrafting + +trait ContainerDataSlots extends BaseContainer { + val dataSource: TileDataSlots + var lastSentChange = dataSource.lastChange + + override def addCraftingToCrafters(crafter: ICrafting) { + super.addCraftingToCrafters(crafter) + if (crafter.isInstanceOf[EntityPlayerMP]) { + val pkt = dataSource.getDataSlotPacket + crafter.asInstanceOf[EntityPlayerMP].playerNetServerHandler.sendPacketToPlayer(pkt) + } + } + + override def detectAndSendChanges() { + super.detectAndSendChanges() + // only send updates every 10 ticks + if (dataSource.lastChange >= lastSentChange + 10 || (dataSource.lastChange > lastSentChange && lastSentChange + 10 < dataSource.worldObj.getTotalWorldTime)) { + lastSentChange = dataSource.lastChange + val pkt = dataSource.getDataSlotPacket + + for (player <- players) + player.playerNetServerHandler.sendPacketToPlayer(pkt) + } + } +} diff --git a/src/net/bdew/lib/data/base/DataSlot.scala b/src/net/bdew/lib/data/base/DataSlot.scala new file mode 100644 index 0000000..8796c59 --- /dev/null +++ b/src/net/bdew/lib/data/base/DataSlot.scala @@ -0,0 +1,42 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +import net.minecraft.nbt.NBTTagCompound + +/** + * Base trait for all data slots + */ +trait DataSlot { + /** + * Tile Entity that owns this slot + * Accessed in constructor, so should be in parameters or a lazy val, otherwise will crash + */ + val parent: TileDataSlots + + /** + * Unique name + * Accessed in constructor, so should be in parameters or a lazy val, otherwise will crash + */ + val name: String + + parent.dataSlots += (name -> this) + + // Where should it sync + var updateKind = Set(UpdateKind.GUI) + + def save(t: NBTTagCompound, kind: UpdateKind.Value) + def load(t: NBTTagCompound, kind: UpdateKind.Value) + + def setUpdate(vals: UpdateKind.Value*): this.type = { + updateKind = vals.toSet + return this + } +} diff --git a/src/net/bdew/lib/data/base/DataSlotNumeric.scala b/src/net/bdew/lib/data/base/DataSlotNumeric.scala new file mode 100644 index 0000000..c5b4f02 --- /dev/null +++ b/src/net/bdew/lib/data/base/DataSlotNumeric.scala @@ -0,0 +1,22 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +/** + * Base class for data slots that hold numeric values + * adds convinience operators to modify the value + */ + +abstract class DataSlotNumeric[T](default: T)(implicit n: Numeric[T]) extends DataSlotVal[T] { + var cval = default + def +=(that: T) = update(n.plus(cval, that)) + def -=(that: T) = update(n.minus(cval, that)) + def *=(that: T) = update(n.times(cval, that)) +} diff --git a/src/net/bdew/lib/data/base/DataSlotOrdered.scala b/src/net/bdew/lib/data/base/DataSlotOrdered.scala new file mode 100644 index 0000000..8bf117e --- /dev/null +++ b/src/net/bdew/lib/data/base/DataSlotOrdered.scala @@ -0,0 +1,21 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +/** + * Base class for data slots that hold values with ordering operators, adds more convinience operators + * TODO: Do i need this? normal operators can be used with the implicit conversion + */ +abstract class DataSlotOrdered[T](implicit n: Ordering[T]) extends DataSlotVal[T] { + def :>(that: T) = n.compare(cval, that) > 0 + def :<(that: T) = n.compare(cval, that) < 0 + def :>=(that: T) = n.compare(cval, that) >= 0 + def :<=(that: T) = n.compare(cval, that) <= 0 +} diff --git a/src/net/bdew/lib/data/base/DataSlotVal.scala b/src/net/bdew/lib/data/base/DataSlotVal.scala new file mode 100644 index 0000000..5a7e5af --- /dev/null +++ b/src/net/bdew/lib/data/base/DataSlotVal.scala @@ -0,0 +1,39 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +import language.implicitConversions + +/** + * Base trait for data slots that hold primitive values + * Defines some convinience operators to access the value and an implicit conversion to the value + */ + +trait DataSlotVal[T] extends DataSlot { + var cval: T + + def isSame(v: T) = v == cval + + def :=(that: T) = update(that) + + def :!=(that: T) = cval != that + def :==(that: T) = cval == that + + def update(v: T) { + if (!isSame(v)) { + cval = v + parent.dataSlotChanged(this) + } + } +} + +object DataSlotVal { + implicit def slot2val[T](slot: DataSlotVal[T]): T = slot.cval +} \ No newline at end of file diff --git a/src/net/bdew/lib/data/base/TileDataSlots.scala b/src/net/bdew/lib/data/base/TileDataSlots.scala new file mode 100644 index 0000000..02869ef --- /dev/null +++ b/src/net/bdew/lib/data/base/TileDataSlots.scala @@ -0,0 +1,64 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +import net.minecraft.network.packet.Packet132TileEntityData +import scala.collection.mutable +import net.minecraft.nbt.NBTTagCompound +import net.bdew.lib.tile.TileExtended + +trait TileDataSlots extends TileExtended { + val dataSlots = mutable.HashMap.empty[String, DataSlot] + var lastChange = 0L + + final val TRACE = true + + def doSave(kind: UpdateKind.Value, t: NBTTagCompound) { + for ((n, s) <- dataSlots if s.updateKind.contains(kind)) { + s.save(t, kind) + if (TRACE) printf("%s: %s S=> %s\n".format(kind, n, t.getTag(n))) + } + } + + def doLoad(kind: UpdateKind.Value, t: NBTTagCompound) { + for ((n, s) <- dataSlots if s.updateKind.contains(kind)) { + if (TRACE) printf("%s: %s L<= %s\n".format(kind, n, t.getTag(n))) + s.load(t, kind) + } + } + + persistSave.listen(doSave(UpdateKind.SAVE, _)) + persistLoad.listen(doLoad(UpdateKind.SAVE, _)) + sendClientUpdate.listen(doSave(UpdateKind.WORLD, _)) + handleClientUpdate.listen(doLoad(UpdateKind.WORLD, _)) + + def dataSlotChanged(slot: DataSlot) { + if (worldObj != null) { + if (slot.updateKind.contains(UpdateKind.GUI)) + lastChange = worldObj.getTotalWorldTime + if (slot.updateKind.contains(UpdateKind.WORLD)) + worldObj.markBlockForUpdate(xCoord, yCoord, zCoord) + if (slot.updateKind.contains(UpdateKind.SAVE)) + worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this) + } + } + + def getDataSlotPacket: Packet132TileEntityData = { + val tag = new NBTTagCompound() + doSave(UpdateKind.GUI, tag) + return new Packet132TileEntityData(this.xCoord, this.yCoord, this.zCoord, ACT_GUI, tag) + } + + override protected def extDataPacket(id: Int, data: NBTTagCompound) { + if (id == ACT_GUI) + doLoad(UpdateKind.GUI, data) + super.extDataPacket(id, data) + } +} diff --git a/src/net/bdew/lib/data/base/UpdateKind.scala b/src/net/bdew/lib/data/base/UpdateKind.scala new file mode 100644 index 0000000..7089627 --- /dev/null +++ b/src/net/bdew/lib/data/base/UpdateKind.scala @@ -0,0 +1,16 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.data.base + +object UpdateKind extends Enumeration { + val WORLD = Value("WORLD") + val GUI = Value("GUI") + val SAVE = Value("SAVE") +} \ No newline at end of file diff --git a/src/net/bdew/lib/gui/BaseContainer.scala b/src/net/bdew/lib/gui/BaseContainer.scala new file mode 100644 index 0000000..85becfe --- /dev/null +++ b/src/net/bdew/lib/gui/BaseContainer.scala @@ -0,0 +1,46 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +import net.minecraft.inventory.{IInventory, Slot, Container} +import net.minecraft.entity.player.{EntityPlayerMP, EntityPlayer, InventoryPlayer} +import net.minecraft.item.ItemStack +import net.bdew.lib.items.ItemUtils +import net.bdew.lib.Misc + +abstract class BaseContainer(te: IInventory) extends Container { + + import scala.collection.JavaConversions._ + + def players = Misc.filterType(crafters, classOf[EntityPlayerMP]) + + protected def bindPlayerInventory(inv: InventoryPlayer, xOffs: Int, yOffsInv: Int, yOffsHotbar: Int) { + for (i <- 0 until 3; j <- 0 until 9) + addSlotToContainer(new Slot(inv, j + i * 9 + 9, xOffs + j * 18, yOffsInv + i * 18)) + + for (i <- 0 until 9) + addSlotToContainer(new Slot(inv, i, xOffs + i * 18, yOffsHotbar)) + } + + override def transferStackInSlot(player: EntityPlayer, slot: Int): ItemStack = { + var stack = getSlot(slot).getStack + if (getSlot(slot).inventory == player.inventory) { + stack = ItemUtils.addStackToSlots(stack, te, 0 until te.getSizeInventory, true) + } else { + stack = ItemUtils.addStackToSlots(stack, player.inventory, 9 until player.inventory.mainInventory.length, true) + // Only put in hotbar if the rest is full + if (stack != null) + stack = ItemUtils.addStackToSlots(stack, player.inventory, 0 until 9, true) + } + getSlot(slot).putStack(stack) + return null + } +} + diff --git a/src/net/bdew/lib/gui/BaseScreen.scala b/src/net/bdew/lib/gui/BaseScreen.scala new file mode 100644 index 0000000..271fca3 --- /dev/null +++ b/src/net/bdew/lib/gui/BaseScreen.scala @@ -0,0 +1,89 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.inventory.Container +import scala.collection.mutable +import net.minecraft.client.gui.GuiButton +import net.bdew.lib.gui.widgets.Widget +import net.minecraft.util.ResourceLocation +import org.lwjgl.opengl.GL11 + +abstract class BaseScreen(cont: Container, xSz: Int, ySz: Int) extends GuiContainer(cont) { + xSize = xSz + ySize = ySz + + val widgets = mutable.MutableList.empty[Widget] + val texture: ResourceLocation + + def rect = new Rect(guiLeft, guiTop, xSize, ySize) + + def getFontRenderer = fontRenderer + + override def initGui() { + super.initGui() + widgets.clear() + buttonList.clear() + } + + def addButton[T <: GuiButton](w: T): T = { + buttonList.asInstanceOf[java.util.ArrayList[GuiButton]].add(w) + return w + } + + def addWidget[T <: Widget](w: T): T = { + widgets += w + w.parent = this + return w + } + + protected override def keyTyped(c: Char, i: Int) { + for (w <- widgets) + if (w.keyTyped(c, i)) + return + super.keyTyped(c, i) + } + + protected override def mouseClicked(x: Int, y: Int, bt: Int) { + super.mouseClicked(x, y, bt) + val p = new Point(x, y) - rect.origin + for (w <- widgets if w.rect.contains(p)) + w.mouseClicked(p - w.rect.origin, bt) + } + + protected override def drawGuiContainerForegroundLayer(x: Int, y: Int) { + for (w <- widgets) + w.draw() + } + + protected override def drawScreen(x: Int, y: Int, f: Float) { + super.drawScreen(x, y, f) + + val tip = mutable.MutableList.empty[String] + + val p = new Point(x, y) - rect.origin + for (w <- widgets if w.rect.contains(p)) + w.handleTooltip(p - w.rect.origin, tip) + + import collection.JavaConversions._ + + if (tip.size > 0) + drawHoveringText(tip, x, y, fontRenderer) + } + + protected def drawGuiContainerBackgroundLayer(f: Float, i: Int, j: Int) { + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F) + mc.renderEngine.bindTexture(texture) + val x = (width - xSize) / 2 + val y = (height - ySize) / 2 + this.drawTexturedModalRect(x, y, 0, 0, xSize, ySize) + } +} diff --git a/src/net/bdew/lib/gui/Direction.scala b/src/net/bdew/lib/gui/Direction.scala new file mode 100644 index 0000000..97b2434 --- /dev/null +++ b/src/net/bdew/lib/gui/Direction.scala @@ -0,0 +1,18 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +object Direction extends Enumeration { + type Direction = Value + val UP = Value("UP") + val DOWN = Value("UP") + val LEFT = Value("UP") + val RIGHT = Value("UP") +} diff --git a/src/net/bdew/lib/gui/Point.scala b/src/net/bdew/lib/gui/Point.scala new file mode 100644 index 0000000..5f10034 --- /dev/null +++ b/src/net/bdew/lib/gui/Point.scala @@ -0,0 +1,16 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +class Point(val x: Int, val y: Int) { + def this(p: Point) = this(p.x, p.y) + def +(that: Point) = new Point(this.x + that.x, this.y + that.y) + def -(that: Point) = new Point(this.x - that.x, this.y - that.y) +} diff --git a/src/net/bdew/lib/gui/Rect.scala b/src/net/bdew/lib/gui/Rect.scala new file mode 100644 index 0000000..93f2526 --- /dev/null +++ b/src/net/bdew/lib/gui/Rect.scala @@ -0,0 +1,16 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +class Rect(val x: Int, val y: Int, val w: Int, val h: Int) { + def this(p: Point, w: Int, h: Int) = this(p.x, p.y, w, h) + def contains(p: Point) = p.x >= x && p.y >= y && p.x <= x + w && p.y <= y + h + def origin = new Point(x, y) +} diff --git a/src/net/bdew/lib/gui/SlotValidating.scala b/src/net/bdew/lib/gui/SlotValidating.scala new file mode 100644 index 0000000..e0a0cf0 --- /dev/null +++ b/src/net/bdew/lib/gui/SlotValidating.scala @@ -0,0 +1,18 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +import net.minecraft.inventory.IInventory +import net.minecraft.inventory.Slot +import net.minecraft.item.ItemStack + +class SlotValidating(inv: IInventory, slot: Int, x: Int, y: Int) extends Slot(inv, slot, x, y) { + override def isItemValid(stack: ItemStack) = inventory.isItemValidForSlot(getSlotIndex, stack) +} \ No newline at end of file diff --git a/src/net/bdew/lib/gui/TextureLocation.scala b/src/net/bdew/lib/gui/TextureLocation.scala new file mode 100644 index 0000000..ff73373 --- /dev/null +++ b/src/net/bdew/lib/gui/TextureLocation.scala @@ -0,0 +1,16 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui + +import net.minecraft.util.ResourceLocation + +class TextureLocation(val resource: ResourceLocation, val p: Point) extends Point(p.x, p.y) { + def this(resource: ResourceLocation, x: Int, y: Int) = this(resource, new Point(x, y)) +} diff --git a/src/net/bdew/lib/gui/widgets/Widget.scala b/src/net/bdew/lib/gui/widgets/Widget.scala new file mode 100644 index 0000000..ed7c4f0 --- /dev/null +++ b/src/net/bdew/lib/gui/widgets/Widget.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui.widgets + +import scala.collection.mutable +import net.bdew.lib.gui.{Point, BaseScreen, Rect} +import net.minecraft.util.ResourceLocation +import net.minecraft.client.Minecraft + +trait Widget { + val rect: Rect + var parent: BaseScreen = null + def handleTooltip(p: Point, tip: mutable.MutableList[String]) {} + def mouseClicked(p: Point, button: Int) {} + def keyTyped(c: Char, i: Int): Boolean = false + def draw() {} + + def bindTexture(res: ResourceLocation) = Minecraft.getMinecraft.renderEngine.bindTexture(res) +} diff --git a/src/net/bdew/lib/gui/widgets/WidgetFillDataslot.scala b/src/net/bdew/lib/gui/widgets/WidgetFillDataslot.scala new file mode 100644 index 0000000..19a58db --- /dev/null +++ b/src/net/bdew/lib/gui/widgets/WidgetFillDataslot.scala @@ -0,0 +1,39 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui.widgets + +import net.bdew.lib.gui.{TextureLocation, Rect} +import net.bdew.lib.gui.Direction +import net.bdew.lib.data.base.DataSlotNumeric + +class WidgetFillDataslot[T](val rect: Rect, val texture: TextureLocation, dir: Direction.Direction, dslot: DataSlotNumeric[T], maxval: T)(implicit num: Numeric[T]) extends Widget { + + def getFillArea(max: Int): Int = (num.toFloat(dslot) / num.toFloat(maxval) * max).round + + override def draw() { + bindTexture(texture.resource) + dir match { + case Direction.UP => + val fill = getFillArea(rect.h) + parent.drawTexturedModalRect(rect.x, rect.y + rect.h - fill, texture.x, texture.y + rect.h - fill, rect.w, fill) + case Direction.DOWN => + val fill = getFillArea(rect.h) + parent.drawTexturedModalRect(rect.x, rect.y, texture.x, texture.y, rect.w, fill) + case Direction.LEFT => + val fill = getFillArea(rect.w) + parent.drawTexturedModalRect(rect.x + rect.w - fill, rect.y, texture.x + rect.w - fill, texture.y, fill, rect.h) + case Direction.RIGHT => + val fill = getFillArea(rect.w) + parent.drawTexturedModalRect(rect.x, rect.y, texture.x, texture.y, fill, rect.h) + } + } +} + + diff --git a/src/net/bdew/lib/gui/widgets/WidgetFluidGauge.scala b/src/net/bdew/lib/gui/widgets/WidgetFluidGauge.scala new file mode 100644 index 0000000..b51dd91 --- /dev/null +++ b/src/net/bdew/lib/gui/widgets/WidgetFluidGauge.scala @@ -0,0 +1,60 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui.widgets + +import net.bdew.lib.gui.{Point, TextureLocation, Rect} +import net.bdew.lib.data.DataSlotTankBase +import scala.collection.mutable +import net.minecraft.client.renderer.texture.TextureMap +import java.text.DecimalFormat +import net.bdew.lib.Misc +import org.lwjgl.opengl.GL11 + +class WidgetFluidGauge(val rect: Rect, overlay: TextureLocation, dslot: DataSlotTankBase) extends Widget { + val formater = new DecimalFormat("#,###") + + override def handleTooltip(p: Point, tip: mutable.MutableList[String]) { + if (dslot.getFluid != null) { + tip += dslot.getFluid.getFluid.getLocalizedName + tip += "%s/%s mB".format(formater.format(dslot.getFluidAmount), formater.format(dslot.getCapacity)) + } else { + tip += Misc.toLocal("gendustry.label.empty") + } + } + + override def draw() { + GL11.glEnable(GL11.GL_BLEND) + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA) + + val fstack = dslot.getFluid + if (fstack != null && fstack.getFluid.getStillIcon != null) { + val icon = fstack.getFluid.getStillIcon + bindTexture(TextureMap.locationBlocksTexture) + var fillHeight: Int = (rect.h * fstack.amount / dslot.getCapacity).round + var yStart: Int = 0 + + while (fillHeight > 0) { + if (fillHeight > 16) { + parent.drawTexturedModelRectFromIcon(rect.x, rect.y + rect.h - 16 - yStart, icon, 16, 16) + fillHeight -= 16 + } else { + parent.drawTexturedModelRectFromIcon(rect.x, rect.y + rect.h - fillHeight - yStart, icon, 16, fillHeight) + fillHeight = 0 + } + yStart = yStart + 16 + } + } + + GL11.glDisable(GL11.GL_BLEND) + + bindTexture(overlay.resource) + parent.drawTexturedModalRect(rect.x, rect.y, overlay.x, overlay.y, rect.w, rect.h) + } +} diff --git a/src/net/bdew/lib/gui/widgets/WidgetLabel.scala b/src/net/bdew/lib/gui/widgets/WidgetLabel.scala new file mode 100644 index 0000000..592c00d --- /dev/null +++ b/src/net/bdew/lib/gui/widgets/WidgetLabel.scala @@ -0,0 +1,19 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui.widgets + +import net.bdew.lib.gui.Rect + +class WidgetLabel(text: String, x: Int, y: Int, color: Int) extends Widget { + val rect: Rect = new Rect(x, y, 0, 0) + override def draw() { + parent.getFontRenderer.drawString(text, x, y, color) + } +} diff --git a/src/net/bdew/lib/gui/widgets/WidgetProgressBar.scala b/src/net/bdew/lib/gui/widgets/WidgetProgressBar.scala new file mode 100644 index 0000000..0690eb3 --- /dev/null +++ b/src/net/bdew/lib/gui/widgets/WidgetProgressBar.scala @@ -0,0 +1,18 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui.widgets + +import net.bdew.lib.gui.{Point, Direction, TextureLocation, Rect} +import net.bdew.lib.data.DataSlotFloat +import scala.collection.mutable + +class WidgetProgressBar(rect: Rect, texture: TextureLocation, dslot: DataSlotFloat) extends WidgetFillDataslot[Float](rect, texture, Direction.RIGHT, dslot, 1) { + override def handleTooltip(p: Point, tip: mutable.MutableList[String]) = tip += "%.0f".format(dslot.cval * 100) + "%" +} diff --git a/src/net/bdew/lib/gui/widgets/WidgetTextEdit.scala b/src/net/bdew/lib/gui/widgets/WidgetTextEdit.scala new file mode 100644 index 0000000..72f5e0b --- /dev/null +++ b/src/net/bdew/lib/gui/widgets/WidgetTextEdit.scala @@ -0,0 +1,19 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.gui.widgets + +import net.minecraft.client.gui.{FontRenderer, GuiTextField} +import net.bdew.lib.gui.{Point, Rect} + +class WidgetTextEdit(val rect: Rect, fr: FontRenderer) extends GuiTextField(fr, rect.x, rect.y, rect.w, rect.h) with Widget { + override def mouseClicked(p: Point, button: Int) = super.mouseClicked(p.x + rect.x, p.y + rect.y, button) + override def keyTyped(c: Char, i: Int): Boolean = super.textboxKeyTyped(c, i) + override def draw() = super.drawTextBox() +} diff --git a/src/net/bdew/lib/items/ItemUtils.scala b/src/net/bdew/lib/items/ItemUtils.scala new file mode 100644 index 0000000..8ec26d0 --- /dev/null +++ b/src/net/bdew/lib/items/ItemUtils.scala @@ -0,0 +1,79 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.items + +import net.minecraft.entity.item.EntityItem +import net.minecraft.item.ItemStack +import net.minecraft.world.World +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.inventory.IInventory +import net.bdew.lib.Misc + +object ItemUtils { + def throwItemAt(world: World, x: Int, y: Int, z: Int, stack: ItemStack) { + if ((stack == null) || world.isRemote) return + val dx = world.rand.nextFloat * 0.8 + val dy = world.rand.nextFloat * 0.8 + val dz = world.rand.nextFloat * 0.8 + val entityitem: EntityItem = new EntityItem(world, x + dx, y + dy, z + dz, stack) + entityitem.motionX = world.rand.nextGaussian * 0.05 + entityitem.motionY = world.rand.nextGaussian * 0.05 + 0.2 + entityitem.motionZ = world.rand.nextGaussian * 0.05 + world.spawnEntityInWorld(entityitem) + } + + def dropItemToPlayer(world: World, player: EntityPlayer, stack: ItemStack) { + if ((stack == null) || world.isRemote) return + world.spawnEntityInWorld(new EntityItem(world, player.posX, player.posY, player.posZ, stack)) + } + + def isSameItem(stack1: ItemStack, stack2: ItemStack): Boolean = { + if (stack1 == null || stack2 == null) + return stack1 == stack2 + if (stack1.itemID != stack2.itemID) + return false + if (stack1.getHasSubtypes && stack2.getItemDamage != stack1.getItemDamage) + return false + return ItemStack.areItemStackTagsEqual(stack2, stack1) + } + + def addStackToSlots(stack: ItemStack, inv: IInventory, slots: Iterable[Int], checkvalid: Boolean): ItemStack = { + if (stack == null) return null + + // Try merging into existing slots + for (slot <- slots if (!checkvalid || inv.isItemValidForSlot(slot, stack)) && isSameItem(stack, inv.getStackInSlot(slot))) { + val target = inv.getStackInSlot(slot) + val toAdd = Misc.min(target.getMaxStackSize - target.stackSize, inv.getInventoryStackLimit - target.stackSize, stack.stackSize) + if (toAdd >= stack.stackSize) { + target.stackSize += stack.stackSize + stack.stackSize = 0 + inv.onInventoryChanged() + return null + } else if (toAdd > 0) { + target.stackSize += toAdd + stack.stackSize -= toAdd + inv.onInventoryChanged() + } + } + + // Now find empty slots and stick any leftovers there + for (slot <- slots if (!checkvalid || inv.isItemValidForSlot(slot, stack)) && inv.getStackInSlot(slot) == null) { + if (inv.getInventoryStackLimit < stack.stackSize) { + inv.setInventorySlotContents(slot, stack.splitStack(inv.getInventoryStackLimit)) + } else { + inv.setInventorySlotContents(slot, stack.copy()) + stack.stackSize = 0 + return null + } + } + + return stack + } +} \ No newline at end of file diff --git a/src/net/bdew/lib/recipes/DataClasses.scala b/src/net/bdew/lib/recipes/DataClasses.scala new file mode 100644 index 0000000..f863bb0 --- /dev/null +++ b/src/net/bdew/lib/recipes/DataClasses.scala @@ -0,0 +1,181 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes + +/** + * Base class for everything that can be resolved to ItemStack or recipe component + * Actual processing is in: + * * [[net.bdew.lib.recipes.RecipeLoader.getRecipeComponent]] + * * [[net.bdew.lib.recipes.RecipeLoader.getConcreteStack]] + * * [[net.bdew.lib.recipes.RecipeLoader.getAllConcreteStacks]] + * + */ +abstract class StackRef + +/** + * Ore Dictionary reference + * Syntax: OD:{identifier} + * Parser: [[net.bdew.lib.recipes.RecipeParser.specOD]] + * @param id OD entry name + */ +case class StackOreDict(id: String) extends StackRef + +/** + * Mod item reference [[cpw.mods.fml.common.registry.GameRegistry.findItemStack]] + * Syntax: {modID}:{name} + * Parser: [[net.bdew.lib.recipes.RecipeParser.specModStack]] + * @param mod modId of owner mod + * @param id name of the stack + */ +case class StackMod(mod: String, id: String) extends StackRef + +/** + * Vanilla block reference + * Syntax: B:{name} [@ {meta}] + * Parser: [[net.bdew.lib.recipes.RecipeParser.specMcBlock]] + * @param name Unlocalized name (without "tile.") + * @param meta Metadata / Damage + */ +case class StackMCBlock(name: String, meta: Int) extends StackRef + +/** + * Vanilla item reference + * Syntax: I:{name} [@ {meta}] + * Parser: [[net.bdew.lib.recipes.RecipeParser.specMcItem]] + * @param name Unlocalized name (without "item.") + * @param meta Metadata / Damage + */ +case class StackMCItem(name: String, meta: Int) extends StackRef + +/** + * Reference to an Item/Block/ItemStack pulic static field + * Syntax: {path}.{to}.{class}[{field}] [@ {meta}] + * Parser: [[net.bdew.lib.recipes.RecipeParser.specReflect]] + * @param cls Class name + * @param field Field name + * @param meta Metadata / Damage (Ignored for ItemStacks) + */ +case class StackReflect(cls: String, field: String, meta: Int) extends StackRef + +/** + * Reference to an Item/Block/ItemStack pulic static getter + * Syntax: {path}.{to}.{class}.{method}({string}) [@ {meta}] + * Parser: [[net.bdew.lib.recipes.RecipeParser.specGetter]] + * @param cls Class name + * @param method Method name + * @param name Parameter to getter + * @param meta Metadata / Damage (Ignored for ItemStacks) + */ +case class StackGetter(cls: String, method: String, name: String, meta: Int) extends StackRef + +/** + * Macro reference to a previous definition + * Syntax: ${char} + * Parser: [[net.bdew.lib.recipes.RecipeParser.specMacro]] + * @param id The underlying character + */ +case class StackMacro(id: Char) extends StackRef + +/** + * Base class for statements + * Actual processing is in [[net.bdew.lib.recipes.RecipeLoader.processStatement]] + */ +abstract class Statement + +/** + * Character assignment + * Syntax: {char} = {ref} + * Parser: [[net.bdew.lib.recipes.RecipeParser.charSpec]] + * @param char Character to assign + * @param spec ItemStack Reference + */ +case class StCharAssign(char: Char, spec: StackRef) extends Statement + +/** + * Define class macro + * Syntax: def {id} = {name} + * Parser: [[net.bdew.lib.recipes.RecipeParser.classMacro]] + * @param id Macro identifier + * @param cls Class name + */ +case class StClassMacro(id: String, cls: String) extends Statement + +/** + * Conditional block - checks if mod is loaded + * Syntax: ifMod {string} { {statements} } [else { {statements} }] + * Parser: [[net.bdew.lib.recipes.RecipeParser.ifMod]] + * @param mod mod and version to check [[cpw.mods.fml.common.versioning.VersionParser]] + * @param thn Statements to run if present + * @param els Statements to run otherwise + */ +case class StIfHaveMod(mod: String, thn: List[Statement], els: List[Statement]) extends Statement + +/** + * Conditional block - checks if mod is loaded + * Syntax: ifOreDict {string} { {statements} } [else { {statements} }] + * Parser: [[net.bdew.lib.recipes.RecipeParser.ifOreDict]] + * @param id Ore Dictionary Entry + * @param thn Statements to run if present + * @param els Statements to run otherwise + */ +case class StIfHaveOD(id: String, thn: List[Statement], els: List[Statement]) extends Statement + +/** + * Shaped recipe definition + * + * Syntax: + * XXX + * XXX => {result} [* {count}] + * XXX + * + * Parser: [[net.bdew.lib.recipes.RecipeParser.recipeShaped3x3]] + * + * Syntax: + * XX + * XX => {result} [* {count}] + * + * Parser: [[net.bdew.lib.recipes.RecipeParser.recipeShaped2x2]] + * + * Syntax: XXX XXX XXX => {result} [* {count}] + * Parser: [[net.bdew.lib.recipes.RecipeParser.recipeShaped9]] + * + * @param rec Recipe pattern + * @param res Result reference + * @param cnt Number of output items + */ +case class StRecipeShaped(rec: Seq[String], res: StackRef, cnt: Int) extends Statement + +/** + * Shapeless recipe definition + * Syntax: shapeless: {characters} => {result} [* {count}] + * Parser: [[net.bdew.lib.recipes.RecipeParser.recipeShapeless]] + * + * @param rec Recipe pattehrn + * @param res Result reference + * @param cnt Number of output items + */ +case class StRecipeShapeless(rec: String, res: StackRef, cnt: Int) extends Statement + +/** + * Smelting recipe definition + * Syntax: smelt: {input} => {output}, {XP} + * Parser [[net.bdew.lib.recipes.RecipeParser.recipeSmelting]] + * + * @param in Input Stack + * @param out Output Stack + * @param cnt Output Count + * @param xp XP for smelting + */ +case class StSmeltRecipe(in: StackRef, out: StackRef, cnt: Int, xp: Float) extends Statement + +/** + * Used in statement processing to abort the current statement and show an error without a stack trace + */ +class StatementError(msg: String) extends Exception(msg) diff --git a/src/net/bdew/lib/recipes/RecipeLoader.scala b/src/net/bdew/lib/recipes/RecipeLoader.scala new file mode 100644 index 0000000..ba30d75 --- /dev/null +++ b/src/net/bdew/lib/recipes/RecipeLoader.scala @@ -0,0 +1,319 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes + +import java.io.Reader +import net.minecraft.item.{Item, ItemStack} +import net.bdew.lib.Misc +import net.minecraft.block.Block +import cpw.mods.fml.common.registry.GameRegistry +import net.minecraftforge.oredict.{ShapelessOreRecipe, OreDictionary} +import java.util.logging.Logger +import cpw.mods.fml.common.FMLLog +import net.minecraft.item.crafting.FurnaceRecipes + +/** + * Main recipe loader class + * The input file is parsed by [[net.bdew.lib.recipes.RecipeParser]] + * Into a list of [[net.bdew.lib.recipes.Statement]] subclasses + * That are then executed here + */ + +class RecipeLoader { + + /** + * Create a new parser, override in subclasses to use an extended parser + * @return New ConfigParser (or subclass) instance + */ + def newParser() = new RecipeParser() + + val log: Logger = Logger.getLogger("RecipeLoader") + log.setParent(FMLLog.getLogger) + + /** + * Current map of recipe characters to parser item references + */ + val currCharMap = collection.mutable.Map.empty[Char, StackRef].withDefault(x => error("Undefined recipe character '%s'", x)) + + /** + * Current map of class macros + */ + val currClassMacros = collection.mutable.Map.empty[String, String].withDefault(x => error("Undefined class macro '%s'", x)) + + /** + * Triggers an error with formatted string + */ + def error(msg: String, params: Any*) = throw new StatementError(msg.format(params: _*)) + + /** + * Looks up a recipe component + * @param s Parser ItemStack reference + * @return ItemStack or String that can be used as recipe components in MC functions + */ + def getRecipeComponent(s: StackRef): AnyRef = s match { + case StackOreDict(id) => id + case StackMacro(ch) => getRecipeComponent(currCharMap(ch)) + case _ => getConcreteStack(s) + } + + /** + * Resolve class name if it's a macro + */ + def getRealClassName(s: String): String = { + if (s.startsWith("$")) { + return currClassMacros(s.stripPrefix("$")) + } + return s + } + /** + * Sanitize items from reflection + * @param x The item + * @param source Human readable source (used in errors and warnings) + * @param meta Metadata or damage + * @param cnt Stack size + * @return + */ + def sanitizeReflectedItem(x: AnyRef, source: String, meta: Int, cnt: Int): ItemStack = x match { + case x: ItemStack => + val l = x.copy() + l.stackSize = cnt + if (meta != OreDictionary.WILDCARD_VALUE && meta != l.getItemDamage) { + log.warning("%s requested with meta %d but specifies %d".format(source, meta, l.getItemDamage)) + l.setItemDamage(meta) + } + return l + case x: Item => + return new ItemStack(x, cnt, meta) + case x: Block => + return new ItemStack(x, cnt, meta) + case _ => + error("%s is %s which cannot be translated to an ItemStack", source, x.getClass.getName) + } + + /** + * Fetches an ItemStack using reflection from static fields + * Item and Block instaces are converted to ItemStacks + * @param clsName Name of class to fetch from (can be a class macro) + * @param fldName Field name + * @param meta Metadata or damage + * @param cnt Stack size + * @return Matching itemstack + */ + def reflectStack(clsName: String, fldName: String, meta: Int, cnt: Int): ItemStack = { + val realName = getRealClassName(clsName) + val cls = Class.forName(realName) + val fld = cls.getField(fldName).get(null) + return sanitizeReflectedItem(fld, "%s.%s".format(realName, fldName), meta, cnt) + } + + /** + * Fetches an ItemStack using reflection from static getters + * Item and Block instaces are converted to ItemStacks + * @param clsName Name of class to fetch from (can be a class macro) + * @param method Method name + * @param param Parameter to method + * @param meta Metadata or damage + * @param cnt Stack size + * @return Matching itemstack + */ + def reflectStackGetter(clsName: String, method: String, param: String, meta: Int, cnt: Int): ItemStack = { + var realName: String = clsName + var realMethod: String = method + + //Some acrobatics to allow use with macros, even bare ones like $foo(bar) + + if ((realName.length == 0) && realMethod.startsWith("$")) { + val v = getRealClassName(realMethod).split('.').reverse + realName = v.tail.reverse.mkString(".") + realMethod = v.head + } + if (realName.startsWith("$")) { + realName = getRealClassName(clsName) + } + + val cls = Class.forName(realName) + val meth = cls.getMethod(realMethod, classOf[String]) + val res = meth.invoke(null, param) + return sanitizeReflectedItem(res, "%s.%s(%s)".format(realName, realMethod, param), meta, cnt) + } + + /** + * Returns all possible ItemStacks that match a reference + * Currently everything except OreDictionary references just returns one item + * @param s Parser ItemStack reference + * @param cnt Stack size + * @return List of matching ItemStacks + */ + def getAllConcreteStacks(s: StackRef, cnt: Int = 1): Iterable[ItemStack] = s match { + case StackOreDict(id) => + import scala.collection.JavaConversions._ + val c = OreDictionary.getOres(id).map(_.copy()) + c.foreach(_.stackSize = cnt) + return c + case _ => Seq(getConcreteStack(s)) + } + + /** + * Returns an ItemStack that match a reference + * This is the main StackRef resolution method + * @param s Parser ItemStack reference + * @param cnt Stack size + * @return A matching ItemStack + */ + def getConcreteStack(s: StackRef, cnt: Int = 1): ItemStack = s match { + case StackOreDict(id) => + val l = OreDictionary.getOres(id) + if (l.size == 0) error("Concrete ItemStack requested for OD entry '%s' that is empty", id) + val s = l.get(0).copy() + s.stackSize = cnt + log.info("Concrete ItemStack for OD entry '%s' -> %s".format(id, s)) + return s + case StackMacro(ch) => getConcreteStack(currCharMap(ch), cnt) + case StackMod(mod, id) => + val i = GameRegistry.findItemStack(mod, id, cnt) + if (i == null) error("Item %s:%s not found", mod, id) + i + case StackMCBlock(id, meta) => new ItemStack(Vanilla.blocks(id), cnt, meta) + case StackMCItem(id, meta) => new ItemStack(Vanilla.items(id), cnt, meta) + case StackReflect(cls, field, meta) => reflectStack(cls, field, meta, cnt) + case StackGetter(cls, method, param, meta) => reflectStackGetter(cls, method, param, meta, cnt) + } + + /** + * Looks up all characters used in the recipe + * @param s The pattern + * @return Map from character to recipe components and a boolean that means OD-aware methods should be used + */ + def resolveRecipeComponents(s: Iterable[Char]): (Map[Char, AnyRef], Boolean) = { + var comp = Map.empty[Char, AnyRef] + var needOd = false + for (x <- s if !comp.contains(x) && x != '_') { + if (!currCharMap.contains(x)) error("Character %s is undefined", x) + val r = getRecipeComponent(currCharMap(x)) + if (r.isInstanceOf[String]) needOd = true + log.info("%s -> %s".format(x, r)) + comp += (x -> r) + } + return (comp, needOd) + } + + /** + * Process a single statement, override this to add more statements + * @param s The statement + */ + def processStatement(s: Statement): Unit = s match { + case StCharAssign(c, r) => + currCharMap(c) = r + log.info("Added %s = %s".format(c, r)) + + case StClassMacro(id, cls) => + currClassMacros(id) = cls + log.info("Added def %s = %s".format(id, cls)) + + case StIfHaveMod(mod, thn, els) => + if (Misc.haveModVersion(mod)) { + log.info("ifMod: %s found".format(mod)) + processStatementsSafe(thn) + } else { + log.info("ifMod: %s not found".format(mod)) + processStatementsSafe(els) + } + + case StIfHaveOD(od, thn, els) => + if (OreDictionary.getOres(od).size() > 0) { + log.info("ifOreDict: %s found".format(od)) + processStatementsSafe(thn) + } else { + log.info("ifOreDict: %s not found".format(od)) + processStatementsSafe(els) + } + + case StRecipeShaped(rec, res, cnt) => + log.info("Adding shaped recipe %s => %s * %d".format(rec, res, cnt)) + val (comp, needOd) = resolveRecipeComponents(rec.mkString("")) + val resStack = getConcreteStack(res, cnt) + + if (resStack.getItemDamage == OreDictionary.WILDCARD_VALUE) { + log.info("Result meta is unset, defaulting to 0") + resStack.setItemDamage(0) + } + + if (needOd) + Misc.addRecipeOD(resStack, rec, comp) + else + Misc.addRecipe(resStack, rec, comp) + + log.info("Done... result=%s, od=%s".format(resStack, needOd)) + + case StRecipeShapeless(rec, res, cnt) => + log.info("Adding shapeless recipe %s => %s * %d".format(rec, res, cnt)) + val (comp, needOd) = resolveRecipeComponents(rec) + val resStack = getConcreteStack(res, cnt) + val recTrans = rec.toCharArray.map(comp(_)) + + if (resStack.getItemDamage == OreDictionary.WILDCARD_VALUE) { + log.info("Result meta is unset, defaulting to 0") + resStack.setItemDamage(0) + } + + if (needOd) + GameRegistry.addRecipe(new ShapelessOreRecipe(resStack, recTrans: _*)) + else + GameRegistry.addShapelessRecipe(resStack, recTrans: _*) + + log.info("Done... result=%s, od=%s".format(resStack, needOd)) + + case StSmeltRecipe(in, out, cnt, xp) => + log.info("Adding smelting recipe %s => %s * %d (%f xp)".format(in, out, cnt, xp)) + val outStack = getConcreteStack(out, cnt) + if (outStack.getItemDamage == OreDictionary.WILDCARD_VALUE) { + log.info("Result meta is unset, defaulting to 0") + outStack.setItemDamage(0) + } + for (inStack <- getAllConcreteStacks(in, 1)) { + if (inStack.getItemDamage == OreDictionary.WILDCARD_VALUE) { + FurnaceRecipes.smelting.addSmelting(inStack.itemID, outStack, xp) + log.info("added %d -> %s".format(inStack.itemID, outStack)) + } else { + FurnaceRecipes.smelting.addSmelting(inStack.itemID, inStack.getItemDamage, outStack, xp) + log.info("added %d@%d -> %s".format(inStack.itemID, inStack.getItemDamage, outStack)) + } + } + + case x => + log.severe("Can't process %s - this is a programing bug!".format(x)) + } + + /** + * Process a list of statements and catch all exceptions + * @param r The list to process + */ + def processStatementsSafe(r: List[Statement]): Unit = { + for (s <- r) { + try { + processStatement(s) + } catch { + case e: StatementError => + log.severe("Error while processing %s: %s".format(s, e.getMessage)) + case e: Throwable => + log.severe("Error while processing %s: %s".format(s, e)) + e.printStackTrace() + } + } + } + + def load(f: Reader) { + log.info("Starting parsing") + val r = newParser().doParse(f) + log.info("Processing %d statements".format(r.size)) + processStatementsSafe(r) + log.info("Done") + } +} diff --git a/src/net/bdew/lib/recipes/RecipeParser.scala b/src/net/bdew/lib/recipes/RecipeParser.scala new file mode 100644 index 0000000..bd13ed6 --- /dev/null +++ b/src/net/bdew/lib/recipes/RecipeParser.scala @@ -0,0 +1,124 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes + +import java.io.Reader + +import scala.util.parsing.combinator._ +import org.apache.commons.lang3.StringEscapeUtils +import net.minecraftforge.oredict.OreDictionary + +class RecipeParser extends JavaTokenParsers { + // Allows C-style comments + protected override val whiteSpace = """(\s|//.*|(?m)/\*(\*(?!/)|[^*])*\*/)+""".r + + // Simple integer without signs + def int = "\\d+".r ^^ (_.toInt) + + // Possible metadata suffix (defaults to wildcard value) + def maybeMeta = ("@" ~> int).? ^^ (_.getOrElse(OreDictionary.WILDCARD_VALUE)) + + // Qualified class name + def clsPath = (ident <~ ".").* ~ ident + + // Single character usable in recipes (this could be extended) + def recipeChar = "[a-zA-Z0-9]".r ^^ (_.charAt(0)) + + // Set of 3 characters for 3x3 recipes (underscore = empty) + def recipeTriplet = repN(3, recipeChar | "_") ^^ (_.mkString("")) + + // Set of 2 characters for 2x2 recipes + def recipeDuplet = repN(2, recipeChar | "_") ^^ (_.mkString("")) + + // Quoted string with escape characters + def unescapeStr = stringLiteral ^^ { case x => StringEscapeUtils.unescapeJava(x.substring(1, x.length - 1)) } + + // Identifier or quoted string + def str = ident | unescapeStr + + // === Item references === + + def specOD = "OD" ~> ":" ~> ident ^^ StackOreDict + def specMcBlock = "B" ~> ":" ~> ident ~ maybeMeta ^^ { case b ~ m => StackMCBlock(b, m) } + def specMcItem = "I" ~> ":" ~> ident ~ maybeMeta ^^ { case b ~ m => StackMCItem(b, m) } + def specModStack = str ~ ":" ~ str ^^ { case m ~ c ~ n => StackMod(m, n) } + def specMacro = "$" ~> recipeChar ^^ StackMacro + + def specReflect = clsPath ~ ("[" ~> ident <~ "]") ~ maybeMeta ^^ { + case (p ~ cl) ~ id ~ m => StackReflect((p :+ cl).mkString("."), id, m) + } + + def specGetter = clsPath ~ ("(" ~> str <~ ")") ~ maybeMeta ^^ { + case (p ~ cl) ~ n ~ m => StackGetter(p.mkString("."), cl, n, m) + } + + def spec = specMcBlock | specMcItem | specOD | specModStack | specGetter | specReflect | specMacro + + // Spec with possible number of items + def specWithCount = spec ~ ("*" ~> int).? + + // === Statements === + + def charSpec = recipeChar ~ "=" ~ spec ^^ { + case ch ~ eq ~ spec => new StCharAssign(ch, spec) + } + + def classMacro = ("def" ~> ident <~ "=") ~ clsPath ^^ { + case id ~ (p ~ cl) => StClassMacro(id, (p :+ cl).mkString(".")) + } + + def ifMod = ("ifMod" ~> str) ~ ("{" ~> statements <~ "}") ~ ("else" ~> "{" ~> statements <~ "}").? ^^ { + case mod ~ thn ~ els => new StIfHaveMod(mod, thn, els.getOrElse(List.empty[Statement])) + } + + def ifOreDict = ("ifOreDict" ~> str) ~ ("{" ~> statements <~ "}") ~ ("else" ~> "{" ~> statements <~ "}").? ^^ { + case id ~ thn ~ els => new StIfHaveOD(id, thn, els.getOrElse(List.empty[Statement])) + } + + def recipeShaped3x3 = recipeTriplet ~ recipeTriplet ~ ("=>" ~> specWithCount) ~ recipeTriplet ^^ { + case r1 ~ r2 ~ (res ~ n) ~ r3 => StRecipeShaped(Seq(r1, r2, r3), res, n.getOrElse(1)) + } + + def recipeShaped2x2 = recipeDuplet ~ recipeDuplet ~ ("=>" ~> specWithCount) ^^ { + case r1 ~ r2 ~ (res ~ n) => StRecipeShaped(Seq(r1, r2), res, n.getOrElse(1)) + } + + def recipeShaped9 = recipeTriplet ~ recipeTriplet ~ recipeTriplet ~ ("=>" ~> specWithCount) ^^ { + case r1 ~ r2 ~ r3 ~ (res ~ n) => StRecipeShaped(Seq(r1, r2, r3), res, n.getOrElse(1)) + } + + def recipeShapeless = "shapeless" ~> ":" ~> recipeChar.+ ~ ("=>" ~> specWithCount) ^^ { + case r ~ (res ~ n) => StRecipeShapeless(r.mkString(""), res, n.getOrElse(1)) + } + + def recipeSmelting = "smelt" ~> ":" ~> spec ~ ("=>" ~> specWithCount) ~ ("," ~> decimalNumber).? ^^ { + case in ~ (out ~ n) ~ xp => StSmeltRecipe(in, out, n.getOrElse(1), xp.getOrElse("0").toFloat) + } + + def statement: Parser[Statement] = ( + charSpec + | classMacro + | ifMod + | recipeShaped3x3 + | recipeShaped2x2 + | recipeShaped9 + | recipeShapeless + | recipeSmelting + ) + + def statements = statement.* + + def doParse(r: Reader): List[Statement] = { + parseAll(statements, r) match { + case Success(res, next) => return res + case NoSuccess(msg, next) => sys.error("Config parsing failed at %s: %s".format(next.pos, msg)) + } + } +} diff --git a/src/net/bdew/lib/recipes/Vanilla.scala b/src/net/bdew/lib/recipes/Vanilla.scala new file mode 100644 index 0000000..794a48a --- /dev/null +++ b/src/net/bdew/lib/recipes/Vanilla.scala @@ -0,0 +1,42 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes + +import net.minecraft.block.Block +import net.minecraft.item.Item +import scala.reflect.ClassTag +import java.lang.reflect.Modifier + +object Vanilla { + + // Maps of vanilla blocks and items (without tile. / item. prefixes) + + lazy val blocks = mkMap[Block](_.getUnlocalizedName.stripPrefix("tile.")).withDefault(x => throw new StatementError("Block %s not found".format(x))) + lazy val items = mkMap[Item](_.getUnlocalizedName.stripPrefix("item.")).withDefault(x => throw new StatementError("Item %s not found".format(x))) + + /** + * Generates the maps + * @tparam T The base class + * @param namer A function that converts objects to names + */ + private def mkMap[T](namer: T => String)(implicit t: ClassTag[T]) = { + val c = t.runtimeClass + // Grab all the fields + c.getFields.filter(x => { + // Keep only static fields that match the base Class + c.isAssignableFrom(x.getType) && Modifier.isStatic(x.getModifiers) + }).map(x => { + // Retrieve the static value + val f = x.get(null).asInstanceOf[T] + // Convert to name -> object tuples + namer(f) -> f + }).toMap // and finally to a map + } +} diff --git a/src/net/bdew/lib/recipes/gencfg/ConfigSection.scala b/src/net/bdew/lib/recipes/gencfg/ConfigSection.scala new file mode 100644 index 0000000..0d2a8c1 --- /dev/null +++ b/src/net/bdew/lib/recipes/gencfg/ConfigSection.scala @@ -0,0 +1,56 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes.gencfg + +abstract class CfgEntry + +case class EntryDouble(v: Double) extends CfgEntry + +case class EntryStr(v: String) extends CfgEntry + +case class ConfigSection(pfx: String = "") extends CfgEntry with Iterable[(String, CfgEntry)] { + var raw = Map.empty[String, CfgEntry].withDefault(x => sys.error("Config value '%s%s' is missing".format(pfx,x))) + + def iterator: Iterator[(String, CfgEntry)] = raw.iterator + def filterType[T<:CfgEntry](cls: Class[T]): Iterable[(String,T)] = raw.filter(x=>cls.isInstance(x._2)).map(x=>x.asInstanceOf[(String,T)]) + + def rawget[T](id: String, t: Class[T]): T = { + val v = raw(id) + if (t.isInstance(v)) + return v.asInstanceOf[T] + else + sys.error("Config value '%s%s' is of the wrong type, expected %s, got %s".format(pfx, id, t.getName, v.getClass.getName)) + } + + def set(id: String, v: CfgEntry) = raw += id -> v + + def getDouble(id: String) = rawget(id, classOf[EntryDouble]).v + def getString(id: String) = rawget(id, classOf[EntryStr]).v + def getSection(id: String) = rawget(id, classOf[ConfigSection]) + + + final val trueVals = Set("y", "true", "yes", "on") + def getBoolean(id: String) = trueVals.contains(getString(id).toLowerCase) + + def getOrAddSection(id: String) = { + if (raw.isDefinedAt(id)) { + rawget(id, classOf[ConfigSection]) + } else { + val newSection = new ConfigSection(pfx + id + ".") + set(id, newSection) + newSection + } + } + + def getInt(id: String) = getDouble(id).round.toInt + def getFloat(id: String) = getDouble(id).toFloat +} + + diff --git a/src/net/bdew/lib/recipes/gencfg/DataClasses.scala b/src/net/bdew/lib/recipes/gencfg/DataClasses.scala new file mode 100644 index 0000000..85a3625 --- /dev/null +++ b/src/net/bdew/lib/recipes/gencfg/DataClasses.scala @@ -0,0 +1,24 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes.gencfg + +import net.bdew.lib.recipes.Statement + +abstract class CfgStatement + +case class CfgVal(id: String, value: CfgEntry) extends CfgStatement + +case class CfgSub(id: String, vals: List[CfgStatement]) extends CfgStatement + +case class StCfg(id: String, vals: List[CfgStatement]) extends Statement + + + + diff --git a/src/net/bdew/lib/recipes/gencfg/GenericConfigLoader.scala b/src/net/bdew/lib/recipes/gencfg/GenericConfigLoader.scala new file mode 100644 index 0000000..1ca7304 --- /dev/null +++ b/src/net/bdew/lib/recipes/gencfg/GenericConfigLoader.scala @@ -0,0 +1,33 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes.gencfg + +import net.bdew.lib.recipes.{Statement, RecipeLoader} + +trait GenericConfigLoader extends RecipeLoader { + val cfgStore: ConfigSection + + def processConfigStatement(section: ConfigSection, s: CfgStatement): Unit = s match { + case CfgVal(id, v) => + log.info("Config: %s%s = %s".format(section.pfx, id, v)) + section.set(id, v) + case CfgSub(id, st) => processConfigBlock(section.getOrAddSection(id), st) + case x => + log.severe("Can't process %s - this is a programing bug!".format(x)) + } + + def processConfigBlock(view: ConfigSection, block: List[CfgStatement]) = for (s <- block) processConfigStatement(view, s) + + override def processStatement(s: Statement) = s match { + case StCfg(id, st) => processConfigBlock(cfgStore.getOrAddSection(id), st) + case _ => super.processStatement(s) + } +} + diff --git a/src/net/bdew/lib/recipes/gencfg/GenericConfigParser.scala b/src/net/bdew/lib/recipes/gencfg/GenericConfigParser.scala new file mode 100644 index 0000000..d5657a3 --- /dev/null +++ b/src/net/bdew/lib/recipes/gencfg/GenericConfigParser.scala @@ -0,0 +1,26 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.recipes.gencfg + +import net.bdew.lib.recipes.RecipeParser + +trait GenericConfigParser extends RecipeParser { + override def statement = statementCfg | super.statement + def cfgStatementNum = ident ~ "=" ~ decimalNumber ^^ { case id ~ eq ~ n => CfgVal(id, EntryDouble(n.toDouble)) } + def cfgStatementStr = ident ~ "=" ~ str ^^ { case id ~ eq ~ s => CfgVal(id, EntryStr(s)) } + def cfgStatementSub = cfgBlock ^^ { case (id, st) => CfgSub(id, st) } + + def cfgStatement = cfgStatementNum | cfgStatementStr | cfgStatementSub + + // A bit of type acrobatics because this is recursive (via cfgStatementSub), the tuple is translated into the proper class in either cfgStatementSub or cfg + def cfgBlock: Parser[(String, List[CfgStatement])] = "cfg" ~> ident ~ ("{" ~> cfgStatement.* <~ "}") ^^ { case id ~ st => (id, st) } + + def statementCfg = cfgBlock ^^ { case (id, st) => StCfg(id, st) } +} diff --git a/src/net/bdew/lib/rotate/BaseRotateableBlock.scala b/src/net/bdew/lib/rotate/BaseRotateableBlock.scala new file mode 100644 index 0000000..e9c3aa7 --- /dev/null +++ b/src/net/bdew/lib/rotate/BaseRotateableBlock.scala @@ -0,0 +1,92 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.rotate + +import cpw.mods.fml.relauncher.Side +import cpw.mods.fml.relauncher.SideOnly +import net.minecraft.block.Block +import net.minecraft.entity.EntityLivingBase +import net.minecraft.item.ItemStack +import net.minecraft.util.Icon +import net.minecraft.util.MathHelper +import net.minecraft.world.IBlockAccess +import net.minecraft.world.World +import net.minecraftforge.common.ForgeDirection +import java.util.EnumSet + +/** + * Basic logic for block rotatation + */ +trait BaseRotateableBlock extends Block { + + /** + * Override this method to provide your textures, also called when rendering as item + */ + def getIcon(meta: Int, kind: IconType.Value): Icon + + /** + * Override this method to provide your textures, if you need to check other state + */ + def getIcon(world: IBlockAccess, x: Int, y: Int, z: Int, kind: IconType.Value): Icon = getIcon(world.getBlockMetadata(x, y, z), kind) + + /** + * Set of valid rotations, default is all of them + */ + def getValidFacings: EnumSet[ForgeDirection] = { + return EnumSet.allOf(classOf[ForgeDirection]) + } + + /** + * Rotation to show when it's unavailable (like rendering item in inventory) + */ + def getDefaultFacing: ForgeDirection = ForgeDirection.UP + + /** + * Those will be overrided to provide concrete storage + */ + def setFacing(world: World, x: Int, y: Int, z: Int, facing: ForgeDirection) + def getFacing(world: IBlockAccess, x: Int, y: Int, z: Int): ForgeDirection + + override def rotateBlock(worldObj: World, x: Int, y: Int, z: Int, axis: ForgeDirection): Boolean = { + if (getValidFacings.contains(axis)) { + setFacing(worldObj, x, y, z, axis) + return true + } + return false + } + + @SideOnly(Side.CLIENT) + override def getIcon(side: Int, meta: Int): Icon = getIcon(meta, IconType.fromSideAndDir(side, getDefaultFacing)) + + @SideOnly(Side.CLIENT) + override def getBlockTexture(world: IBlockAccess, x: Int, y: Int, z: Int, side: Int): Icon = getIcon(world, x, y, z, IconType.fromSideAndDir(side, getFacing(world, x, y, z))) + + override def getValidRotations(worldObj: World, x: Int, y: Int, z: Int): Array[ForgeDirection] = getValidFacings.toArray(Array.empty[ForgeDirection]) + + override def onBlockPlacedBy(world: World, x: Int, y: Int, z: Int, ent: EntityLivingBase, stack: ItemStack) { + val validRotations = getValidFacings + var dir = getDefaultFacing + if (ent != null) { + val pitch: Int = Math.round(ent.rotationPitch) + val yaw: Int = MathHelper.floor_double(ent.rotationYaw * 4.0F / 360.0F + 0.5D) & 3 + dir = yaw match { + case _ if pitch >= 50 && validRotations.contains(ForgeDirection.UP) => ForgeDirection.UP + case _ if pitch <= -50 && validRotations.contains(ForgeDirection.DOWN) => ForgeDirection.DOWN + case 0 if validRotations.contains(ForgeDirection.NORTH) => ForgeDirection.NORTH + case 1 if validRotations.contains(ForgeDirection.EAST) => ForgeDirection.EAST + case 2 if validRotations.contains(ForgeDirection.SOUTH) => ForgeDirection.SOUTH + case 3 if validRotations.contains(ForgeDirection.WEST) => ForgeDirection.WEST + case _ => dir + } + } + setFacing(world, x, y, z, dir) + super.onBlockPlacedBy(world, x, y, z, ent, stack) + } +} \ No newline at end of file diff --git a/src/net/bdew/lib/rotate/IconType.scala b/src/net/bdew/lib/rotate/IconType.scala new file mode 100644 index 0000000..da4fc2e --- /dev/null +++ b/src/net/bdew/lib/rotate/IconType.scala @@ -0,0 +1,28 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.rotate + +import net.minecraftforge.common.ForgeDirection + +object IconType extends Enumeration { + val BACK = Value(0, "BACK") + val FRONT = Value(1, "FRONT") + val SIDE = Value(2, "SIDE") + + def fromSideAndDir(side: Int, facing: ForgeDirection) = this(metaSideMap(side)(facing.ordinal)) + + val metaSideMap = Array( + Array(1, 0, 2, 2, 2, 2), + Array(0, 1, 2, 2, 2, 2), + Array(2, 2, 1, 0, 2, 2), + Array(2, 2, 0, 1, 2, 2), + Array(2, 2, 2, 2, 1, 0), + Array(2, 2, 2, 2, 0, 1)) +} \ No newline at end of file diff --git a/src/net/bdew/lib/rotate/RotateableTile.scala b/src/net/bdew/lib/rotate/RotateableTile.scala new file mode 100644 index 0000000..39af102 --- /dev/null +++ b/src/net/bdew/lib/rotate/RotateableTile.scala @@ -0,0 +1,17 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.rotate + +import net.bdew.lib.data.base.{UpdateKind, TileDataSlots} +import net.bdew.lib.data.DataSlotDirection + +trait RotateableTile extends TileDataSlots { + var rotation = DataSlotDirection("rotation", this).setUpdate(UpdateKind.SAVE, UpdateKind.WORLD) +} diff --git a/src/net/bdew/lib/rotate/RotateableTileBlock.scala b/src/net/bdew/lib/rotate/RotateableTileBlock.scala new file mode 100644 index 0000000..fb70f94 --- /dev/null +++ b/src/net/bdew/lib/rotate/RotateableTileBlock.scala @@ -0,0 +1,34 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.rotate + +import net.minecraft.block.ITileEntityProvider +import net.minecraft.world.IBlockAccess +import net.minecraft.world.World +import net.minecraftforge.common.ForgeDirection +import cpw.mods.fml.common.FMLCommonHandler + +/** + * Stores rotation data in tile entity. The TE should implement {@link RotateableTile} + */ +trait RotateableTileBlock extends BaseRotateableBlock with ITileEntityProvider { + def setFacing(world: World, x: Int, y: Int, z: Int, facing: ForgeDirection) { + world.getBlockTileEntity(x, y, z).asInstanceOf[RotateableTile].rotation := facing + } + + def getFacing(world: IBlockAccess, x: Int, y: Int, z: Int): ForgeDirection = { + val te: RotateableTile = world.getBlockTileEntity(x, y, z).asInstanceOf[RotateableTile] + if (te == null || te.rotation == null) { + FMLCommonHandler.instance().getFMLLogger.warning("Failed gettig rocation for block at %d, %d, %d".format(x, y, z)) + return getDefaultFacing + } + return te.rotation + } +} \ No newline at end of file diff --git a/src/net/bdew/lib/tile/Component.scala b/src/net/bdew/lib/tile/Component.scala new file mode 100644 index 0000000..f68e212 --- /dev/null +++ b/src/net/bdew/lib/tile/Component.scala @@ -0,0 +1,14 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile + +trait Component { + def added(p: TileExtended, name: String) +} diff --git a/src/net/bdew/lib/tile/ExposeTank.scala b/src/net/bdew/lib/tile/ExposeTank.scala new file mode 100644 index 0000000..0169f12 --- /dev/null +++ b/src/net/bdew/lib/tile/ExposeTank.scala @@ -0,0 +1,46 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile + +import net.minecraft.tileentity.TileEntity +import net.minecraftforge.fluids._ +import net.minecraftforge.common.ForgeDirection + +trait ExposeTank extends TileEntity with IFluidHandler { + def getTankFromDirection(dir: ForgeDirection): IFluidTank + + def fill(from: ForgeDirection, resource: FluidStack, doFill: Boolean): Int = { + val tank = getTankFromDirection(from) + if (tank == null) return 0 + return tank.fill(resource, doFill) + } + + def drain(from: ForgeDirection, resource: FluidStack, doDrain: Boolean): FluidStack = { + val tank = getTankFromDirection(from) + if (tank == null || resource == null || !resource.isFluidEqual(tank.getFluid)) return null + return tank.drain(resource.amount, doDrain) + } + + def drain(from: ForgeDirection, maxDrain: Int, doDrain: Boolean): FluidStack = { + val tank = getTankFromDirection(from) + if (tank == null) return null + getTankFromDirection(from).drain(maxDrain, doDrain) + } + + def canFill(from: ForgeDirection, fluid: Fluid): Boolean = getTankFromDirection(from) != null + + def canDrain(from: ForgeDirection, fluid: Fluid): Boolean = getTankFromDirection(from) != null + + def getTankInfo(from: ForgeDirection): Array[FluidTankInfo] = { + val tank = getTankFromDirection(from) + if (tank == null) return Array.empty[FluidTankInfo] + return Array(tank.getInfo) + } +} diff --git a/src/net/bdew/lib/tile/TileExtended.scala b/src/net/bdew/lib/tile/TileExtended.scala new file mode 100644 index 0000000..bf5917f --- /dev/null +++ b/src/net/bdew/lib/tile/TileExtended.scala @@ -0,0 +1,69 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile + +import net.minecraft.tileentity.TileEntity +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.network.INetworkManager +import net.minecraft.network.packet.Packet132TileEntityData +import net.bdew.lib.Event + +class TileExtended extends TileEntity { + final val ACT_CLIENT = 1 + final val ACT_GUI = 2 + + var clientTick = Event() + var serverTick = Event() + var persistSave = Event[NBTTagCompound] + var persistLoad = Event[NBTTagCompound] + var sendClientUpdate = Event[NBTTagCompound] + var handleClientUpdate = Event[NBTTagCompound] + + def tickClient() {} + def tickServer() {} + + override final def writeToNBT(tag: NBTTagCompound) = { + super.writeToNBT(tag) + persistSave.trigger(tag) + } + + override final def readFromNBT(tag: NBTTagCompound) = { + super.readFromNBT(tag) + persistLoad.trigger(tag) + } + + override final def getDescriptionPacket: Packet132TileEntityData = { + if (!sendClientUpdate.hasListeners) return null + val tag = new NBTTagCompound + sendClientUpdate.trigger(tag) + return new Packet132TileEntityData(this.xCoord, this.yCoord, this.zCoord, ACT_CLIENT, tag) + } + + override final def onDataPacket(net: INetworkManager, pkt: Packet132TileEntityData) { + if (pkt.actionType == ACT_CLIENT) + handleClientUpdate.trigger(pkt.data) + else + extDataPacket(pkt.actionType, pkt.data) + } + + override final def updateEntity() { + if (worldObj != null) { + if (worldObj.isRemote) { + tickClient() + clientTick.trigger() + } else { + tickServer() + serverTick.trigger() + } + } + } + + protected def extDataPacket(id: Int, data: NBTTagCompound) {} +} diff --git a/src/net/bdew/lib/tile/inventory/BaseInventory.scala b/src/net/bdew/lib/tile/inventory/BaseInventory.scala new file mode 100644 index 0000000..024d22e --- /dev/null +++ b/src/net/bdew/lib/tile/inventory/BaseInventory.scala @@ -0,0 +1,66 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile.inventory + +import net.minecraft.inventory.IInventory +import net.minecraft.item.ItemStack +import net.minecraft.entity.player.EntityPlayer + +trait BaseInventory extends IInventory { + var inv: Array[ItemStack] = new Array(getSizeInventory) + + def getStackInSlot(i: Int): ItemStack = inv(i) + + def getInvName = "" + + def isInvNameLocalized = false + + def getInventoryStackLimit = 64 + + def isUseableByPlayer(player: EntityPlayer) = true + + def openChest() = Unit + + def closeChest() = Unit + + def isItemValidForSlot(slot: Int, stack: ItemStack) = true + + def decrStackSize(slot: Int, n: Int): ItemStack = { + var item = inv(slot) + if (item != null) { + if (item.stackSize <= n) { + inv(slot) = null + onInventoryChanged() + return item + } else { + var newstack = item.splitStack(n) + if (item.stackSize == 0) { + inv(slot) = null + } + onInventoryChanged() + return newstack + } + } else { + return null + } + } + + def getStackInSlotOnClosing(slot: Int): ItemStack = { + var tmp = inv(slot) + inv(slot) = null + onInventoryChanged() + return tmp + } + + def setInventorySlotContents(slot: Int, stack: ItemStack) = { + inv(slot) = stack + onInventoryChanged() + } +} diff --git a/src/net/bdew/lib/tile/inventory/BreakableInventoryBlock.scala b/src/net/bdew/lib/tile/inventory/BreakableInventoryBlock.scala new file mode 100644 index 0000000..aa8e507 --- /dev/null +++ b/src/net/bdew/lib/tile/inventory/BreakableInventoryBlock.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile.inventory + +import net.minecraft.block.Block +import net.minecraft.world.World + +trait BreakableInventoryBlock extends Block { + override def breakBlock(world: World, x: Int, y: Int, z: Int, blockId: Int, meta: Int) { + if (!world.isRemote) { + val te = world.getBlockTileEntity(x, y, z).asInstanceOf[BreakableInventoryTile] + if (te != null) { + te.dropItems() + } + } + super.breakBlock(world, x, y, z, blockId, meta) + } +} diff --git a/src/net/bdew/lib/tile/inventory/BreakableInventoryTile.scala b/src/net/bdew/lib/tile/inventory/BreakableInventoryTile.scala new file mode 100644 index 0000000..024c558 --- /dev/null +++ b/src/net/bdew/lib/tile/inventory/BreakableInventoryTile.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile.inventory + +import net.minecraft.tileentity.TileEntity +import net.minecraft.item.ItemStack +import net.bdew.lib.items.ItemUtils + +trait BreakableInventoryTile extends TileEntity with BaseInventory { + def dropItems() { + if (worldObj != null && !worldObj.isRemote) { + for (stack <- inv if stack != null) { + ItemUtils.throwItemAt(worldObj, xCoord, yCoord, zCoord, stack) + } + inv = new Array[ItemStack](inv.size) + } + } +} diff --git a/src/net/bdew/lib/tile/inventory/PersistentInventoryTile.scala b/src/net/bdew/lib/tile/inventory/PersistentInventoryTile.scala new file mode 100644 index 0000000..644c051 --- /dev/null +++ b/src/net/bdew/lib/tile/inventory/PersistentInventoryTile.scala @@ -0,0 +1,38 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile.inventory + +import net.minecraft.item.ItemStack +import net.minecraft.nbt.{NBTTagList, NBTTagCompound} +import net.bdew.lib.Misc +import net.bdew.lib.tile.TileExtended + +trait PersistentInventoryTile extends TileExtended with BaseInventory { + persistLoad.listen((tag: NBTTagCompound) => { + inv = new Array[ItemStack](getSizeInventory) + for (nbtItem <- Misc.iterNbtList[NBTTagCompound](tag.getTagList("Items"))) { + val slot = nbtItem.getByte("Slot") + if (slot >= 0 && slot < inv.size) { + inv(slot) = ItemStack.loadItemStackFromNBT(nbtItem) + } + } + }) + + persistSave.listen((tag: NBTTagCompound) => { + val itemList = new NBTTagList + for ((item, i) <- inv.view.zipWithIndex if item != null) { + val itemNbt: NBTTagCompound = new NBTTagCompound + itemNbt.setByte("Slot", i.asInstanceOf[Byte]) + item.writeToNBT(itemNbt) + itemList.appendTag(itemNbt) + } + tag.setTag("Items", itemList) + }) +} diff --git a/src/net/bdew/lib/tile/inventory/SidedInventory.scala b/src/net/bdew/lib/tile/inventory/SidedInventory.scala new file mode 100644 index 0000000..3ae9a20 --- /dev/null +++ b/src/net/bdew/lib/tile/inventory/SidedInventory.scala @@ -0,0 +1,23 @@ +/* + * Copyright (c) bdew, 2013 + * https://github.com/bdew/bdlib + * + * This mod is distributed under the terms of the Minecraft Mod Public + * License 1.0, or MMPL. Please check the contents of the license located in + * https://raw.github.com/bdew/bdlib/master/MMPL-1.0.txt + */ + +package net.bdew.lib.tile.inventory + +import net.minecraft.inventory.ISidedInventory +import net.minecraft.item.ItemStack + +trait SidedInventory extends BaseInventory with ISidedInventory { + var allowSided = false + + def getAccessibleSlotsFromSide(side: Int): Array[Int] = (0 until inv.size).toArray + + def canInsertItem(slot: Int, stack: ItemStack, side: Int) = allowSided && isItemValidForSlot(slot, stack) + + def canExtractItem(slot: Int, stack: ItemStack, side: Int) = allowSided +}