Compare commits

..
19 Commits
Author SHA1 Message Date
bdew f43dd28e03 Added easy NBT constructor - see https://gist.github.com/bdew/0a44363dcac4be85415c 2015-05-25 15:29:31 +03:00
bdew 289f8f7120 derp 2015-05-24 04:03:23 +03:00
bdew e7787fb9ca bumped version to 1.8.0, removed deprecated methods 2015-05-24 04:01:36 +03:00
bdew 58e336f8a9 Misc.haveModVersion should check APIs too, added getModVersion 2015-05-24 04:00:23 +03:00
bdew bea6a13896 Added async helpers and ServerTickExecutionContext (executes futures in server thread using tick event) 2015-05-21 18:05:33 +03:00
bdew 793ceb0024 Merge pull request #16 from Mazdallier/patch-1
Create fr_FR.lang
2015-05-13 12:07:16 +03:00
Yves 66d9be58c8 Update fr_FR.lang
--file return back
2015-05-12 17:13:57 +02:00
Yves 87d64208c8 Create fr_FR.lang 2015-05-12 15:59:10 +02:00
bdew 6f6496c1e3 /oredistribution - added dense ore support 2015-05-12 16:04:44 +03:00
bdew 934e984abf /oredistribution - Added option to dump to file 2015-05-12 16:02:03 +03:00
bdew 4900537a82 /oredistribution - Fixed meta detection with some ores (e.g. lapis) 2015-05-12 10:19:37 +03:00
bdew 31b5ab9d52 Added /oredistribution command 2015-05-10 22:37:04 +03:00
bdew 8a4ccf7d81 Prevent build failure when CF api key is not available 2015-05-10 15:49:37 +03:00
bdew 429107215f Allow fractional chances in config lootlists 2015-05-10 15:36:31 +03:00
bdew 0c60d596b6 Jenkins will now upload builds to curseforge 2015-05-10 15:35:45 +03:00
bdew da35094895 Merge pull request #14 from theelixzammuto/mc1710
Added Italian Localization
2015-04-27 23:30:19 +03:00
theelixzammuto 83d30f12c2 Added Italian Localization 2015-04-26 21:42:29 +02:00
bdew 8d4484ca18 DataSlotTankRestricted now takes fluid object instead of fluid ID 2015-04-20 14:53:25 +03:00
bdew 624df87299 Updated to grade 2.3, reworked build script to use properties file, changed version format to include MC version 2015-04-20 14:37:23 +03:00
18 changed files with 393 additions and 48 deletions

No files matched your search

+35 -13
View File
@@ -21,21 +21,30 @@ buildscript {
apply plugin: 'scala'
apply plugin: 'forge'
version = "1.7.1"
file "build.properties" withReader {
def prop = new Properties()
prop.load(it)
ext.config = new ConfigSlurper().parse prop
}
if (project.hasProperty('forgeOverride')) {
config.forge.version = forgeOverride
}
if (project.hasProperty('buildnum')) {
project.version = project.version + '.' + project.buildnum
ext.simpleVersion = "${config.bdlib.version}.${project.buildnum}"
} else {
project.version = "0-SNAPSHOT"
ext.simpleVersion = "${config.bdlib.version}-DEV"
}
version = simpleVersion + '-mc' + config.minecraft.version
group = "net.bdew"
archivesBaseName = "bdlib"
ext.versuffix = "mc1710"
minecraft {
version = "1.7.10-10.13.2.1291"
replace("BDLIB_VER", project.version)
version = "${config.minecraft.version}-${config.forge.version}"
replace("BDLIB_VER", simpleVersion.toString())
}
import org.apache.tools.ant.filters.ReplaceTokens
@@ -66,16 +75,18 @@ processResources {
}
task sourceJar(type: Jar) {
from sourceSets.main.allSource
classifier "sources"
}
task deobfJar(type: Jar) {
from sourceSets.main.output
classifier "dev"
duplicatesStrategy "exclude"
}
jar {
appendix = project.versuffix
afterEvaluate { project ->
// Fudge the inputs of source jars so we get the version after replacements
tasks.getByPath(":sourceJar").from(tasks.getByPath(":sourceMainScala").outputs.files)
}
artifacts {
@@ -89,10 +100,7 @@ publishing {
publications {
maven(MavenPublication) {
artifact deobfJar
artifact sourceJar {
classifier "sources"
}
artifact sourceJar
}
}
repositories {
@@ -100,4 +108,18 @@ publishing {
url "file://var/www/maven"
}
}
}
apply plugin: 'curseforge'
curse {
projectId = config.curseforge.id
apiKey = project.hasProperty("curseForgeApiKey") ? project.curseForgeApiKey : ""
releaseType = 'alpha'
artifact = jar
changelog = project.hasProperty("changelog") ? project.changelog : "No changelog available"
additionalArtifact deobfJar
additionalArtifact sourceJar
}
+5
View File
@@ -0,0 +1,5 @@
bdlib.version=1.8.0
curseforge.id=70496
minecraft.version=1.7.10
forge.version=10.13.2.1291
+1 -1
View File
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-bin.zip
Vendored Regular → Executable
View File
File mode changed.
+7
View File
@@ -17,3 +17,10 @@ bdlib.rsmode.rs_off=Disable on redstone
bdlib.label.empty=Empty
bdlib.oredistribution.usage=/oredistribution <radius> [<min height>] [<max height>] [--file]
bdlib.oredistribution.start1=Checking ore distribution in (%s,%s,%s) to (%s,%s,%s) dim %s
bdlib.oredistribution.start2=Server may lag for a while
bdlib.oredistribution.entry=%s %s - %s (%s%%)
bdlib.oredistribution.warn=%s Warning: %s has multiple variants generated:
bdlib.oredistribution.saved=Ore distribution dumped to %s
bdlib.oredistribution.error=Failed to save ore distribution dump: %s
+26
View File
@@ -0,0 +1,26 @@
bdlib.multiblock.face.north=Nord
bdlib.multiblock.face.south=Sud
bdlib.multiblock.face.east=Est
bdlib.multiblock.face.west=Ouest
bdlib.multiblock.face.up=Dessus
bdlib.multiblock.face.down=Dessous
bdlib.multiblock.disabled=Désactivé
bdlib.multiblock.incomplete=Cette machine est incomplète, elle exige au moins :
bdlib.multiblock.notconnected=Pas connecté à une machine valide
bdlib.multiblock.toomanyoutputs=Trop de soties connectées, Incapable d'ajouter une nouvelle connexion
bdlib.rsmode.always=Toujours actif
bdlib.rsmode.never=Désactivé
bdlib.rsmode.rs_on=Activé par redstone
bdlib.rsmode.rs_off=Désactivé par redstone
bdlib.label.empty=Vide
bdlib.oredistribution.usage=/oredistribution <rayon> [<hauteur mini>] [<hauteur maxi>] [--file]
bdlib.oredistribution.start1=Valide la distribution de minerai entre (%s,%s,%s) et (%s,%s,%s) dim %s
bdlib.oredistribution.start2=Le server peut ralentir pendantun certain temps
bdlib.oredistribution.entry=%s %s - %s (%s%%)
bdlib.oredistribution.warn=%s Attention: %s fait produire des variantes multiples :
bdlib.oredistribution.saved=Distribution de minerai déposée dans %s
bdlib.oredistribution.error=Impossible d'enregistrer la distribution de minerai : %s
+18
View File
@@ -0,0 +1,18 @@
bdlib.multiblock.face.north=Nord
bdlib.multiblock.face.south=Sud
bdlib.multiblock.face.east=Est
bdlib.multiblock.face.west=Ovest
bdlib.multiblock.face.up=Sopra
bdlib.multiblock.face.down=Sotto
bdlib.multiblock.disabled=Disattivo
bdlib.multiblock.incomplete=Questa macchina è incompleta, richiede almeno:
bdlib.multiblock.notconnected=Non connesso ad una macchina valida
bdlib.multiblock.toomanyoutputs=Troppi Output collegati,impossibile inserire una nuova connessione
bdlib.rsmode.always=Sempre Attivo
bdlib.rsmode.never=Disattivo
bdlib.rsmode.rs_on=Attivo su Impulso di Pietrarossa
bdlib.rsmode.rs_off=Disattivo su Impulso di Pietrarossa
bdlib.label.empty=Vuoto
+2 -1
View File
@@ -42,7 +42,8 @@ object BdLib {
@EventHandler
def serverStarting(event: FMLServerStartingEvent) {
val commandHandler = event.getServer.getCommandManager.asInstanceOf[CommandHandler]
commandHandler.registerCommand(new CommandDumpRegistry)
commandHandler.registerCommand(CommandDumpRegistry)
commandHandler.registerCommand(CommandOreDistribution)
onServerStarting.trigger(event)
}
+1 -1
View File
@@ -19,7 +19,7 @@ import net.minecraft.item.ItemStack
import net.minecraftforge.fluids.FluidRegistry
import net.minecraftforge.oredict.OreDictionary
class CommandDumpRegistry extends CommandBase {
object CommandDumpRegistry extends CommandBase {
def getCommandName = "dumpregistry"
override def getRequiredPermissionLevel = 2
def getCommandUsage(c: ICommandSender) = "dumpregistry"
@@ -0,0 +1,148 @@
/*
* Copyright (c) bdew, 2013 - 2014
* 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
* http://bdew.net/minecraft-mod-public-license/
*/
package net.bdew.lib
import java.io.{BufferedWriter, File, FileWriter}
import cpw.mods.fml.common.registry.GameData
import cpw.mods.fml.relauncher.FMLInjectionData
import net.minecraft.command.{CommandBase, ICommandSender, WrongUsageException}
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.item.Item
import net.minecraft.util.EnumChatFormatting
import net.minecraftforge.oredict.OreDictionary
import scala.collection.mutable
object CommandOreDistribution extends CommandBase {
def getCommandName = "oredistribution"
override def getRequiredPermissionLevel = 2
def getCommandUsage(c: ICommandSender) = "bdlib.oredistribution.usage"
def safeInt(s: String) =
try {
s.toInt
} catch {
case e: NumberFormatException => throw new WrongUsageException("bdlib.oredistribution.usage")
}
def wrapColor(s: String, c: EnumChatFormatting) = c + s + EnumChatFormatting.RESET
def processCommand(sender: ICommandSender, baseParams: Array[String]) {
val (flags, params) = baseParams.partition(_.startsWith("--"))
if (params.length < 1 || params.length > 3)
throw new WrongUsageException(getCommandUsage(sender))
val (startX, startZ, world) =
sender match {
case p: EntityPlayerMP => (p.posX.toInt, p.posZ.toInt, p.worldObj)
case _ => (0, 0, sender.getEntityWorld)
}
val radius = safeInt(params.head)
val minY = if (params.length >= 2) safeInt(params(1)) else 0
val maxY = if (params.length >= 3) safeInt(params(2)) else world.getHeight - 1
if (minY < 0 || radius <= 0 || minY > maxY || maxY > world.getHeight - 1)
throw new WrongUsageException(getCommandUsage(sender))
val toFile = flags.contains("--file")
// why java, WHY?
CommandBase.func_152373_a(sender, this, "bdlib.oredistribution.start1",
Array[Integer](startX - radius, minY, startZ - radius, startX + radius, maxY, startZ + radius, world.provider.dimensionId): _*)
CommandBase.func_152373_a(sender, this, "bdlib.oredistribution.start2")
import scala.collection.JavaConversions._
val ores = (OreDictionary.getOreNames.filter(x => x.startsWith("ore") || x.startsWith("denseore")) flatMap { name =>
val fixedName =
if (name.startsWith("ore"))
name.substring(3)
else if (name.startsWith("denseore"))
"Dense " + name.substring(8)
else name
for (s <- OreDictionary.getOres(name)) yield (s.getItem, s.getItemDamage) -> fixedName
}).toMap
val distribution = mutable.Map.empty[String, Int].withDefaultValue(0)
val kinds = mutable.Map.empty[String, Set[(Item, Int)]].withDefaultValue(Set.empty)
for {
x <- startX - radius to startX + radius
y <- minY to maxY
z <- startZ - radius to startZ + radius
if !world.isAirBlock(x, y, z)
} {
val key = (Item.getItemFromBlock(world.getBlock(x, y, z)), world.getBlockMetadata(x, y, z))
for (ore <- ores.get(key)) {
distribution(ore) += 1
kinds(ore) += key
}
}
val total = distribution.map(_._2).sum
val warnings = kinds.filter(_._2.size > 1)
if (toFile) {
// === OUTPUT TO FILE ===
val mcHome = FMLInjectionData.data()(6).asInstanceOf[File] //is there a better way to get this?
val dumpFile = new File(mcHome, "ore_distribution.txt")
val dumpWriter = new BufferedWriter(new FileWriter(dumpFile))
try {
dumpWriter.write("Ore distribution report - (%d,%d,%d) to (%d,%d,%d) dim %d".format(
startX - radius, minY, startZ - radius, startX + radius, maxY, startZ + radius, world.provider.dimensionId
))
dumpWriter.newLine()
dumpWriter.newLine()
if (warnings.size > 0) {
for ((id, types) <- warnings.toList.sortBy(_._1)) {
dumpWriter.write("Warning: %s has multiple variants generated:".format(id))
dumpWriter.newLine()
for ((item, dmg) <- types) {
dumpWriter.write(" - %s@%s".format(GameData.getItemRegistry.getNameForObject(item), dmg))
dumpWriter.newLine()
}
dumpWriter.newLine()
}
dumpWriter.write("=================")
dumpWriter.newLine()
dumpWriter.newLine()
}
for ((id, num) <- distribution.filter(_._2 > 0).toList.sortBy(-_._2)) {
dumpWriter.write("%s - %s (%s%%)".format(id, DecFormat.round(num), DecFormat.short(100F * num / total)))
dumpWriter.newLine()
}
CommandBase.func_152373_a(sender, this, "bdlib.oredistribution.saved", dumpFile.getCanonicalPath)
} catch {
case e: Throwable =>
CommandBase.func_152373_a(sender, this, "bdlib.oredistribution.error", e.toString)
BdLib.logErrorException("Failed to save ore distribution dump", e)
} finally {
dumpWriter.close()
}
} else {
// === OUTPUT TO CHAT ===
for ((id, types) <- warnings.toList.sortBy(_._1)) {
CommandBase.func_152373_a(sender, this, "bdlib.oredistribution.warn",
wrapColor(" !", EnumChatFormatting.RED), wrapColor(id, EnumChatFormatting.RED))
for ((item, dmg) <- types)
CommandBase.func_152373_a(sender, this, wrapColor(" - ", EnumChatFormatting.RED) + GameData.getItemRegistry.getNameForObject(item) + "@" + dmg)
}
for ((id, num) <- distribution.filter(_._2 > 0).toList.sortBy(-_._2)) {
CommandBase.func_152373_a(sender, this, "bdlib.oredistribution.entry",
" *", wrapColor(id, EnumChatFormatting.YELLOW),
DecFormat.round(num), DecFormat.short(100F * num / total))
}
}
}
}
+24 -23
View File
@@ -9,9 +9,11 @@
package net.bdew.lib
import cpw.mods.fml.common.Loader
import java.util
import cpw.mods.fml.common.registry.GameRegistry
import cpw.mods.fml.common.versioning.{ArtifactVersion, VersionParser}
import cpw.mods.fml.common.versioning.VersionParser
import cpw.mods.fml.common.{Loader, ModAPIManager, ModContainer}
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.tileentity.TileEntity
@@ -22,18 +24,6 @@ import net.minecraftforge.fluids.{Fluid, FluidStack}
import net.minecraftforge.oredict.ShapedOreRecipe
object Misc {
@deprecated("Use net.bdew.lib.nbt instead", "bdlib 1.7.0")
def iterNbtCompoundList(parent: NBTTagCompound, name: String): Iterable[NBTTagCompound] = {
import net.bdew.lib.nbt._
parent.getList[NBTTagCompound](name)
}
@deprecated("Use net.bdew.lib.nbt instead", "bdlib 1.7.0")
def iterNbtIntArray(parent: NBTTagCompound, name: String): Iterable[Array[Int]] = {
import net.bdew.lib.nbt._
parent.getList[Array[Int]](name)
}
def getActiveModId = Loader.instance().activeModContainer().getModId
def toLocal(s: String) = StatCollector.translateToLocal(s)
@@ -41,9 +31,9 @@ object Misc {
def hasLocal(s: String) = StatCollector.canTranslate(s)
def flattenRecipe(pattern: Seq[String], items: Map[Char, AnyRef]) =
pattern ++ items.map {
pattern ++ items.flatMap {
case (k, v) => Seq(new Character(k), v)
}.flatten
}
def wrapTag(n: String, f: (NBTTagCompound) => Any)(t: NBTTagCompound) {
val p = t.getCompoundTag(n)
@@ -69,17 +59,28 @@ object Misc {
def getBiomeByName(name: String) = BiomeGenBase.getBiomeGenArray.find(x => x != null && x.biomeName == name).orNull
def haveModVersion(spec: String): Boolean = {
val req = VersionParser.parseVersionReference(spec)
val modId = req.getLabel
private lazy val modLookup = {
val mods = new util.ArrayList[ModContainer]
val nameLookup = new util.HashMap[String, ModContainer]
if (!Loader.instance.getIndexedModList.containsKey(modId)) return false
nameLookup.putAll(Loader.instance().getIndexedModList)
ModAPIManager.INSTANCE.injectAPIModContainers(mods, nameLookup)
val found: ArtifactVersion = Loader.instance.getIndexedModList.get(modId).getProcessedVersion
import scala.collection.JavaConverters._
nameLookup.asScala
}
if (found == null || !req.containsVersion(found)) return false
def haveModVersion(modId: String) = {
val spec = VersionParser.parseVersionReference(modId)
modLookup.contains(spec.getLabel) && spec.containsVersion(modLookup(spec.getLabel).getProcessedVersion)
}
return true
def getModVersion(modId: String) =
modLookup.get(modId) map (_.getVersion)
def getModVersionString(modId: String): String = {
val cont = modLookup.getOrElse(modId, return "NOT FOUND")
cont.getModId + " " + cont.getVersion
}
// Because writing this every time is awkward
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (c) bdew, 2013 - 2015
* 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
* http://bdew.net/minecraft-mod-public-license/
*/
package net.bdew.lib.async
import scala.concurrent.{Future, Promise}
object Async {
def inOtherThread[T](f: => T) = {
val p = Promise[T]()
val t = new Thread(new Runnable {
override def run(): Unit =
try {
p.success(f)
} catch {
case t: Throwable => p.failure(t)
}
})
t.start()
p.future
}
def inServerThread[T](f: => T) = Future(f)(ServerTickExecutionContext)
}
@@ -0,0 +1,53 @@
/*
* Copyright (c) bdew, 2013 - 2015
* 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
* http://bdew.net/minecraft-mod-public-license/
*/
package net.bdew.lib.async
import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent
import net.bdew.lib.BdLib
import scala.collection.mutable
import scala.concurrent.ExecutionContext
object ServerTickExecutionContext extends ExecutionContext {
private val queue = mutable.Queue.empty[Runnable]
FMLCommonHandler.instance.bus.register(this)
BdLib.logDebug("Installed ServerTickExecutionContext")
private def getNextBatch = this.synchronized {
if (queue.nonEmpty) {
val res = queue.toList
queue.clear()
res
} else List.empty
}
override def execute(runnable: Runnable): Unit =
this.synchronized {
queue += runnable
}
override def reportFailure(cause: Throwable): Unit =
BdLib.logErrorException("Error in execution context", cause)
@SubscribeEvent
def onTick(ev: ServerTickEvent): Unit = {
for (r <- getNextBatch) {
try {
r.run()
} catch {
case t: Throwable => reportFailure(t)
}
}
}
}
+2 -4
View File
@@ -11,7 +11,7 @@ package net.bdew.lib.data
import net.bdew.lib.data.base.{DataSlot, DataSlotContainer, UpdateKind}
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.fluids.{FluidRegistry, FluidStack, FluidTank, IFluidTank}
import net.minecraftforge.fluids._
abstract class DataSlotTankBase(sz: Int) extends FluidTank(sz) with IFluidTank with DataSlot {
var oldStack: FluidStack = null
@@ -69,9 +69,7 @@ abstract class DataSlotTankBase(sz: Int) extends FluidTank(sz) with IFluidTank w
case class DataSlotTank(name: String, parent: DataSlotContainer, size: Int) extends DataSlotTankBase(size)
case class DataSlotTankRestricted(name: String, parent: DataSlotContainer, var size: Int, fluidID: Int) extends DataSlotTankBase(size) {
val filterFluid = FluidRegistry.getFluid(fluidID)
case class DataSlotTankRestricted(name: String, parent: DataSlotContainer, var size: Int, filterFluid: Fluid) extends DataSlotTankBase(size) {
def fill(amount: Int, doFill: Boolean) = super.fill(new FluidStack(filterFluid, amount), doFill)
override def fill(resource: FluidStack, doFill: Boolean): Int = {
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright (c) bdew, 2013 - 2015
* 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
* http://bdew.net/minecraft-mod-public-license/
*/
package net.bdew.lib.nbt
import net.minecraft.nbt.{NBTBase, NBTTagCompound, NBTTagList}
import scala.language.implicitConversions
object NBT {
class NBTSerialized(val value: NBTBase) extends AnyVal
implicit def serializeNbtPrimitive[T: Type](v: T): NBTSerialized = new NBTSerialized(Type[T].toNBT(v))
implicit def serializeNbtList[T: Type](v: List[T]): NBTSerialized = {
val list = new NBTTagList
for (x <- v) list.appendTag(Type[T].toNBT(x))
new NBTSerialized(list)
}
def apply(pairs: (String, NBTSerialized)*): NBTTagCompound = apply(pairs.toMap)
def apply(m: Map[String, NBTSerialized]): NBTTagCompound = {
val tag = new NBTTagCompound()
for ((k, v) <- m)
tag.setTag(k, v.value)
tag
}
}
@@ -14,10 +14,10 @@ import net.bdew.lib.recipes.gencfg.ConfigEntry
/**
* Allows definition of random-weighted loot/drop lists in config sections
* @param list List on (chance percent, stack reference) pairs, resolve to
* @param list List of (chance percent, stack reference) pairs, resolve to
* real ItemStacks with LootListLoader.resolveLootList
*/
case class EntryLootList(list: List[(Int, StackRef)]) extends ConfigEntry
case class EntryLootList(list: List[(Double, StackRef)]) extends ConfigEntry
@@ -19,8 +19,8 @@ import net.minecraftforge.oredict.OreDictionary
* Loader mixin for loot lists
*/
trait LootListLoader extends GenericConfigLoader {
def resolveLootList(entry: EntryLootList): List[(Int, ItemStack)] = resolveLootList(entry.list)
def resolveLootList(list: List[(Int, StackRef)]) =
def resolveLootList(entry: EntryLootList): List[(Double, ItemStack)] = resolveLootList(entry.list)
def resolveLootList(list: List[(Double, StackRef)]) =
(for ((chance, ref) <- list) yield {
try {
val itemStack = getConcreteStack(ref)
@@ -19,7 +19,7 @@ import net.bdew.lib.recipes.gencfg.GenericConfigParser
* )
*/
trait LootListParser extends GenericConfigParser {
def dropsEntry = (wholeNumber <~ "%") ~ spec ^^ { case n ~ s => (n.toInt, s) }
def dropsEntry = (decimalNumber <~ "%") ~ spec ^^ { case n ~ s => (n.toDouble, s) }
def ceDrops = "DropsList" ~> "(" ~> dropsEntry.* <~ ")" ^^ EntryLootList
override def cfgValue = ceDrops | super.cfgValue
}