commit 91562498430c1e09dc3384de2ca2dfb8d656d466 Author: bdew Date: Thu Nov 28 23:03:28 2013 +0200 Preparing for public release 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 0000000..d5c591c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2b64450 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Nov 28 11:30:59 IST 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + 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 +}