From 5ffe425f18db72abdb49cbbceaf8b8dd00ac2a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Wed, 20 Nov 2013 01:45:32 +0100 Subject: [PATCH] more configurable stuffs; added a wrapper layer for the config class when fetching values to make defining settings less verbose, and to make it easier to switch to some other backend implementation in case config really goes away in 1.7; less direct use of IDs; now checking if a robot can actually break a block with the currently equipped tool via ForgeHooks. so now they cannot break obsidian anymore unless they have a diamond pickaxe, for example --- li/cil/oc/Config.scala | 512 +++++++++--------- li/cil/oc/common/block/Item.scala | 4 +- li/cil/oc/common/item/Delegate.scala | 4 +- li/cil/oc/common/item/Delegator.scala | 2 +- li/cil/oc/server/component/Robot.scala | 9 +- li/cil/oc/server/component/robot/Player.scala | 8 +- li/cil/oc/server/driver/item/FileSystem.scala | 2 +- li/cil/oc/server/driver/item/Item.scala | 2 +- li/cil/oc/server/driver/item/Memory.scala | 2 +- li/cil/oc/util/ExtendedConfiguration.scala | 43 ++ 10 files changed, 330 insertions(+), 258 deletions(-) create mode 100644 li/cil/oc/util/ExtendedConfiguration.scala diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index 64ada22c3..328eadc7e 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -1,6 +1,7 @@ package li.cil.oc import java.io.File +import li.cil.oc.util.ExtendedConfiguration._ import li.cil.oc.util.PackedColor object Config { @@ -67,7 +68,7 @@ object Config { // server.filesystem var fileCost = 512 - var filesBuffered = true + var bufferChanges = true var maxHandles = 16 var maxReadBuffer = 8 * 1024 @@ -84,6 +85,8 @@ object Config { var canAttackPlayers = false var canPlaceInAir = false var itemDamageRate = 0.05 + var swingRange = 0.49 + var useAndPlaceRange = 0.65 // ----------------------------------------------------------------------- // // robot.delays @@ -96,7 +99,6 @@ object Config { var turnDelay = 0.4 var useDelay = 0.4 - // ----------------------------------------------------------------------- // def load(file: File) = { @@ -121,309 +123,331 @@ object Config { config.getCategory("client"). setComment("Client side settings, presentation and performance related stuff.") - maxScreenTextRenderDistance = config.get("client", "maxScreenTextRenderDistance", maxScreenTextRenderDistance, "" + - "The maximum distance at which to render text on screens. Rendering text\n" + - "can be pretty expensive, so if you have a lot of screens you'll want\n" + - "to avoid huge numbers here. Note that this setting is client-sided, and\n" + - "only has an impact on render performance on clients."). - getDouble(maxScreenTextRenderDistance) + maxScreenTextRenderDistance = config.fetch("client.maxScreenTextRenderDistance", maxScreenTextRenderDistance, + """|The maximum distance at which to render text on screens. Rendering text + |can be pretty expensive, so if you have a lot of screens you'll want + |to avoid huge numbers here. Note that this setting is client-sided, and + |only has an impact on render performance on clients.""".stripMargin) - screenTextFadeStartDistance = config.get("client", "screenTextFadeStartDistance", screenTextFadeStartDistance, "" + - "The distance at which to start fading out the text on screens. This is\n" + - "purely cosmetic, to avoid text disappearing instantly when moving too far\n" + - "away from a screen. This should have no measurable impact on performance.\n" + - "Note that this needs OpenGL 1.4 to work, otherwise text will always just\n" + - "instantly disappear when moving away from the screen displaying it."). - getDouble(screenTextFadeStartDistance) + screenTextFadeStartDistance = config.fetch("client.screenTextFadeStartDistance", screenTextFadeStartDistance, + """|The distance at which to start fading out the text on screens. This is + |purely cosmetic, to avoid text disappearing instantly when moving too far + |away from a screen. This should have no measurable impact on performance. + |Note that this needs OpenGL 1.4 to work, otherwise text will always just + |instantly disappear when moving away from the screen displaying it.""".stripMargin) - textLinearFiltering = config.get("client", "textLinearFiltering", textLinearFiltering, "" + - "Whether to apply linear filtering for text displayed on screens when the\n" + - "screen has to be scaled down - i.e. the text is rendered at a resolution\n" + - "lower than their native one, e.g. when the GUI scale is less than one or\n" + - "when looking at a far away screen. This leads to smoother text for scaled\n" + - "down text but results in characters not perfectly connecting anymore (for\n" + - "example for box drawing characters. Look it up on Wikipedia.)"). - getBoolean(textLinearFiltering) + textLinearFiltering = config.fetch("client.textLinearFiltering", textLinearFiltering, + """|Whether to apply linear filtering for text displayed on screens when the + |screen has to be scaled down - i.e. the text is rendered at a resolution + |lower than their native one, e.g. when the GUI scale is less than one or + |when looking at a far away screen. This leads to smoother text for scaled + |down text but results in characters not perfectly connecting anymore (for + |example for box drawing characters. Look it up on Wikipedia.)""".stripMargin) // --------------------------------------------------------------------- // config.getCategory("power"). setComment("Power settings, buffer sizes and power consumption.") - ignorePower = config.get("power", "ignorePower", ignorePower, "" + - "Whether to ignore any power requirements. Whenever something requires\n" + - "power to function, it will try to get the amount of energy it needs from\n" + - "the buffer of its connector node, and in case it fails it won't perform\n" + - "the action / trigger a shutdown / whatever. Setting this to `true` will\n" + - "simply make the check 'is there enough energy' succeed unconditionally.\n" + - "Note that buffers are still filled and emptied following the usual rules,\n" + - "there just is no failure case anymore."). - getBoolean(ignorePower) + ignorePower = config.fetch("power.ignorePower", ignorePower, + """|Whether to ignore any power requirements. Whenever something requires + |power to function, it will try to get the amount of energy it needs from + |the buffer of its connector node, and in case it fails it won't perform + |the action / trigger a shutdown / whatever. Setting this to `true` will + |simply make the check 'is there enough energy' succeed unconditionally. + |Note that buffers are still filled and emptied following the usual rules, + |there just is no failure case anymore.""".stripMargin) // --------------------------------------------------------------------- // - bufferCapacitor = config.get("power.buffer", "bufferCapacitor", bufferCapacitor, "" + - "The amount of energy a capacitor can store."). - getDouble(bufferCapacitor) max 0 + bufferCapacitor = config.fetch("power.buffer.capacitor", bufferCapacitor, + """|The amount of energy a capacitor can store.""".stripMargin) max 0 - bufferConverter = config.get("power.buffer", "bufferConverter", bufferConverter, "" + - "The amount of energy a power converter can store."). - getDouble(bufferConverter) max 0 + bufferConverter = config.fetch("power.buffer.converter", bufferConverter, + """|The amount of energy a power converter can store.""".stripMargin) max 0 - bufferPowerSupply = config.get("power.buffer", "bufferPowerSupply", bufferPowerSupply, "" + - "The amount of energy a power supply can store."). - getDouble(bufferPowerSupply) max 0 + bufferPowerSupply = config.fetch("power.buffer.powerSupply", bufferPowerSupply, + """|The amount of energy a power supply can store.""".stripMargin) max 0 // --------------------------------------------------------------------- // - computerCost = config.get("power.cost", "computerCost", computerCost, "" + - "The amount of energy a computer consumes per tick when running."). - getDouble(computerCost) max 0 + computerCost = config.fetch("power.cost.computer", computerCost, + """|The amount of energy a computer consumes per tick when running.""".stripMargin) max 0 - gpuFillCost = config.get("power.cost", "gpuFillCost", gpuFillCost, "" + - "Energy it takes to change a single 'pixel' via the fill command. This\n" + - "means the total cost of the fill command will be its area times this."). - getDouble(gpuFillCost) max 0 + gpuFillCost = config.fetch("power.cost.gpuFill", gpuFillCost, + """|Energy it takes to change a single 'pixel' via the fill command. This + |means the total cost of the fill command will be its area times this.""".stripMargin) max 0 - gpuClearCost = config.get("power.cost", "gpuClearCost", gpuClearCost, "" + - "Energy it takes to change a single 'pixel' to blank using the fill\n" + - "command. This means the total cost of the fill command will be its\n" + - "area times this."). - getDouble(gpuClearCost) max 0 + gpuClearCost = config.fetch("power.cost.gpuClear", gpuClearCost, + """|Energy it takes to change a single 'pixel' to blank using the fill + |command. This means the total cost of the fill command will be its + |area times this.""".stripMargin) max 0 - gpuCopyCost = config.get("power.cost", "gpuCopyCost", gpuCopyCost, "" + - "Energy it takes to move a single 'pixel' via the copy command. This\n" + - "means the total cost of the copy command will be its area times this."). - getDouble(gpuCopyCost) max 0 + gpuCopyCost = config.fetch("power.cost.gpuCopy", gpuCopyCost, + """|Energy it takes to move a single 'pixel' via the copy command. This + |means the total cost of the copy command will be its area times this.""".stripMargin) max 0 - gpuSetCost = config.get("power.cost", "gpuSetCost", gpuSetCost, "" + - "Energy it takes to change a single 'pixel' via the set command. For\n" + - "calls to set with a string, this means the total cost will be the\n" + - "string length times this."). - getDouble(gpuSetCost) max 0 + gpuSetCost = config.fetch("power.cost.gpuSet", gpuSetCost, + """|Energy it takes to change a single 'pixel' via the set command. For + |calls to set with a string, this means the total cost will be the + |string length times this.""".stripMargin) max 0 - hddReadCost = config.get("power.cost", "hddReadCost", hddReadCost, "" + - "Energy it takes read a single byte from a file system. Note that non\n" + - "I/O operations on file systems such as `list` or `getFreeSpace` do\n" + - "*not* consume power."). - getDouble(hddReadCost) max 0 + hddReadCost = config.fetch("power.cost.hddRead", hddReadCost, + """|Energy it takes read a single byte from a file system. Note that non + |I/O operations on file systems such as `list` or `getFreeSpace` do + |*not* consume power.""".stripMargin) max 0 - hddWriteCost = config.get("power.cost", "hddWriteCost", hddWriteCost, "" + - "Energy it takes to write a single byte to a file system."). - getDouble(hddWriteCost) max 0 + hddWriteCost = config.fetch("power.cost.hddWrite", hddWriteCost, + """|Energy it takes to write a single byte to a file system.""".stripMargin) max 0 - powerSupplyCost = config.get("power.cost", "powerSupplyCost", powerSupplyCost, "" + - "The amount of energy a power supply (item) produces per tick. This is\n" + - "basically just a consumer, but instead of taking energy it puts it\n" + - "back into the network. This is slightly more than what a computer\n" + - "consumes per tick. It's meant as an easy way to powering a small\n" + - "setup, mostly for testing. "). - getDouble(powerSupplyCost) + powerSupplyCost = config.fetch("power.cost.powerSupply", powerSupplyCost, + """|The amount of energy a power supply (item) produces per tick. This is + |basically just a consumer, but instead of taking energy it puts it + |back into the network. This is slightly more than what a computer + |consumes per tick. It's meant as an easy way to powering a small + |setup, mostly for testing.""".stripMargin) - screenCost = config.get("power.cost", "screenCost", screenCost, "" + - "The amount of energy a screen consumes per tick. If a screen cannot\n" + - "consume the defined amount of energy it will stop rendering the text\n" + - "that should be displayed on it. It will *not* forget that text,\n" + - "however, so when enough power is available again it will restore the\n" + - "previously displayed text (with any changes possibly made in the\n" + - "meantime). Note that for multi-block screens *each* screen that is\n" + - "part of it will consume this amount of energy per tick."). - getDouble(screenCost) max 0 + screenCost = config.fetch("power.cost.screen", screenCost, + """|The amount of energy a screen consumes per displayed character per + |tick. If a screen cannot consume the defined amount of energy it will + |stop rendering the text that should be displayed on it. It will *not* + |forget that text, however, so when enough power is available again it + |will restore the previously displayed text (with any changes possibly + |made in the meantime). Note that for multi-block screens *each* + |screen that is part of it will consume this amount of energy per + |tick.""".stripMargin) max 0 - wirelessCostPerRange = config.get("power.cost", "wirelessCostPerRange", wirelessCostPerRange, "" + - "The amount of energy it costs to send a signal with strength one,\n" + - "which means the signal reaches one block. This is scaled up linearly,\n" + - "so for example to send a signal 400 blocks a signal strength of 400\n" + - "is required, costing a total of 400 * `wirelessCostPerRange`. In\n" + - "other words, the higher this value, the higher the cost of wireless\n" + - "messages.\n" + - "See also: `maxWirelessRange`."). - getDouble(wirelessCostPerRange) max 0 + wirelessCostPerRange = config.fetch("power.cost.wirelessStrength", wirelessCostPerRange, + """|The amount of energy it costs to send a signal with strength one, + |which means the signal reaches one block. This is scaled up linearly, + |so for example to send a signal 400 blocks a signal strength of 400 + |is required, costing a total of 400 * `wirelessCostPerRange`. In + |other words, the higher this value, the higher the cost of wireless + |messages.[nl] + |See also: `maxWirelessRange`.""".stripMargin) max 0 // --------------------------------------------------------------------- // config.getCategory("server"). setComment("Server side settings, gameplay and security related stuff.") - baseMemory = config.get("server.computer", "baseMemory", baseMemory, "" + - "The base amount of memory made available in computers even if they\n" + - "have no RAM installed. Use this if you feel you can't get enough RAM\n" + - "using the given means, that being RAM components. Just keep in mind\n" + - "that this is global and applies to all computers!"). - getInt(baseMemory) + baseMemory = config.fetch("server.computer.baseMemory", baseMemory, + """|The base amount of memory made available in computers even if they + |have no RAM installed. Use this if you feel you can't get enough RAM + |using the given means, that being RAM components. Just keep in mind + |that this is global and applies to all computers!""".stripMargin) - canComputersBeOwned = config.get("server.computer", "canComputersBeOwned", canComputersBeOwned, "" + - "This determines whether computers can only be used by players that\n" + - "are registered as users on them. Per default a newly placed computer\n" + - "has no users. Whenever there are no users the computer is free for\n" + - "all. Users can be managed via the Lua API (os.addUser, os.removeUser,\n" + - "os.users). If this is true, the following interactions are only\n" + - "possible for users:\n" + - " - input via the keyboard.\n" + - " - inventory management.\n" + - " - breaking the computer block.\n" + - "If this is set to false, all computers will always be usable by all\n" + - "players, no matter the contents of the user list. Note that operators\n" + - "are treated as if they were in the user list of every computer, i.e.\n" + - "no restrictions apply to them.\n" + - "See also: `maxUsers` and `maxUsernameLength`."). - getBoolean(canComputersBeOwned) + canComputersBeOwned = config.fetch("server.computer.canComputersBeOwned", canComputersBeOwned, + """|This determines whether computers can only be used by players that + |are registered as users on them. Per default a newly placed computer + |has no users. Whenever there are no users the computer is free for + |all. Users can be managed via the Lua API (os.addUser, os.removeUser, + |os.users). If this is true, the following interactions are only + |possible for users:[nl] + | - input via the keyboard.[nl] + | - inventory management.[nl] + | - breaking the computer block.[nl] + |If this is set to false, all computers will always be usable by all + |players, no matter the contents of the user list. Note that operators + |are treated as if they were in the user list of every computer, i.e. + |no restrictions apply to them.[nl] + |See also: `maxUsers` and `maxUsernameLength`.""".stripMargin) - maxUsernameLength = config.get("server.computer", "maxUsernameLength", maxUsernameLength, "" + - "Sanity check for username length for users registered with computers.\n" + - "We store the actual user names instead of a hash to allow iterating\n" + - "the list of registered users on the Lua side.\n" + - "See also: `canComputersBeOwned`."). - getInt(maxUsernameLength) max 0 + maxUsernameLength = config.fetch("server.computer.maxUsernameLength", maxUsernameLength, + """|Sanity check for username length for users registered with computers. + |We store the actual user names instead of a hash to allow iterating + |the list of registered users on the Lua side.[nl] + |See also: `canComputersBeOwned`.""".stripMargin) max 0 - maxUsers = config.get("server.computer", "maxUsers", maxUsers, "" + - "The maximum number of users that can be registered with a single\n" + - "computer. This is used to avoid computers allocating unchecked\n" + - "amounts of memory by registering an unlimited number of users.\n" + - "See also: `canComputersBeOwned`."). - getInt(maxUsers) max 0 + maxUsers = config.fetch("server.computer.maxUsers", maxUsers, + """|The maximum number of users that can be registered with a single + |computer. This is used to avoid computers allocating unchecked + |amounts of memory by registering an unlimited number of users. + |See also: `canComputersBeOwned`.""".stripMargin) max 0 - startupDelay = config.get("server.computer", "startupDelay", startupDelay, "" + - "The time in seconds to wait after a computer has been restored before\n" + - "it continues to run. This is meant to allow the world around the\n" + - "computer to settle, avoiding issues such as components in neighboring\n" + - "chunks being removed and then re-connected and other odd things that\n" + - "might happen."). - getDouble(startupDelay) max 0 + startupDelay = config.fetch("server.computer.startupDelay", startupDelay, + """|The time in seconds to wait after a computer has been restored before + |it continues to run. This is meant to allow the world around the + |computer to settle, avoiding issues such as components in neighboring + |chunks being removed and then re-connected and other odd things that + |might happen.""".stripMargin) max 0 - threads = config.get("server.computer", "threads", threads, "" + - "The overall number of threads to use to drive computers. Whenever a\n" + - "computer should run, for example because a signal should be processed\n" + - "or some sleep timer expired it is queued for execution by a worker\n" + - "thread. The higher the number of worker threads, the less likely it\n" + - "will be that computers block each other from running, but the higher\n" + - "the host system's load may become."). - getInt(threads) max 1 + threads = config.fetch("server.computer.threads", threads, + """|The overall number of threads to use to drive computers. Whenever a + |computer should run, for example because a signal should be processed + |or some sleep timer expired it is queued for execution by a worker + |thread. The higher the number of worker threads, the less likely it + |will be that computers block each other from running, but the higher + |the host system's load may become.""".stripMargin) max 1 - timeout = config.get("server.computer", "timeout", timeout, "" + - "The time in seconds a program may run without yielding before it is\n" + - "forcibly aborted. This is used to avoid stupidly written or malicious\n" + - "programs blocking other computers by locking down the executor\n" + - "threads. Note that changing this won't have any effect on computers\n" + - "that are already running - they'll have to be rebooted for this to\n" + - "take effect."). - getDouble(timeout) max 0 + timeout = config.fetch("server.computer.timeout", timeout, + """|The time in seconds a program may run without yielding before it is + |forcibly aborted. This is used to avoid stupidly written or malicious + |programs blocking other computers by locking down the executor + |threads. Note that changing this won't have any effect on computers + |that are already running - they'll have to be rebooted for this to + |take effect.""".stripMargin) max 0 // --------------------------------------------------------------------- // - fileCost = config.get("server.filesystem", "fileCost", fileCost, "" + - "The base 'cost' of a single file or directory on a limited file\n" + - "system, such as hard drives. When computing the used space we add\n" + - "this cost to the real size of each file (and folders, which are zero\n" + - "sized otherwise). This is to ensure that users cannot spam the file\n" + - "system with an infinite number of files and/or folders. Note that the\n" + - "size returned via the API will always be the real file size, however."). - getInt(fileCost) max 0 + fileCost = config.fetch("server.filesystem.fileCost", fileCost, + """|The base 'cost' of a single file or directory on a limited file + |system, such as hard drives. When computing the used space we add + |this cost to the real size of each file (and folders, which are zero + |sized otherwise). This is to ensure that users cannot spam the file + |system with an infinite number of files and/or folders. Note that the + |size returned via the API will always be the real file size, however.""".stripMargin) max 0 - filesBuffered = config.get("server.filesystem", "filesBuffered", filesBuffered, "" + - "Whether persistent file systems such as disk drivers should be\n" + - "'buffered', and only written to disk when the world is saved. This\n" + - "applies to all hard drives. The advantage of having this enabled is\n" + - "that data will never go 'out of sync' with the computer's state if\n" + - "the game crashes. The price is slightly higher memory consumption,\n" + - "since all loaded files have to be kept in memory (loaded as in when\n" + - "the hard drive is in a computer)."). - getBoolean(filesBuffered) + bufferChanges = config.fetch("server.filesystem.bufferChanges", bufferChanges, + """|Whether persistent file systems such as disk drivers should be + |'buffered', and only written to disk when the world is saved. This + |applies to all hard drives. The advantage of having this enabled is + |that data will never go 'out of sync' with the computer's state if + |the game crashes. The price is slightly higher memory consumption, + |since all loaded files have to be kept in memory (loaded as in when + |the hard drive is in a computer).""".stripMargin) - maxHandles = config.get("server.filesystem", "maxHandles", maxHandles, "" + - "The maximum number of file handles any single computer may have open\n" + - "at a time. Note that this is *per filesystem*. Also note that this is\n" + - "only enforced by the filesystem node - if an add-on decides to be\n" + - "fancy it may well ignore this. Since file systems are usually\n" + - "'virtual' this will usually not have any real impact on performance\n" + - "and won't be noticeable on the host operating system.") - .getInt(maxHandles) max 0 + maxHandles = config.fetch("server.filesystem.maxHandles", maxHandles, + """|The maximum number of file handles any single computer may have open + |at a time. Note that this is *per filesystem*. Also note that this is + |only enforced by the filesystem node - if an add-on decides to be + |fancy it may well ignore this. Since file systems are usually + |'virtual' this will usually not have any real impact on performance + |and won't be noticeable on the host operating system.""".stripMargin) max 0 - maxReadBuffer = config.get("server.filesystem", "maxReadBuffer", maxReadBuffer, "" + - "The maximum block size that can be read in one 'read' call on a file\n" + - "system. This is used to limit the amount of memory a call from a user\n" + - "program can cause to be allocated on the host side: when 'read' is,\n" + - "called a byte array with the specified size has to be allocated. So\n" + - "if this weren't limited, a Lua program could trigger massive memory\n" + - "allocations regardless of the amount of RAM installed in the computer\n" + - "it runs on. As a side effect this pretty much determines the read\n" + - "performance of file systems.") - .getInt(maxReadBuffer) max 0 + maxReadBuffer = config.fetch("server.filesystem.maxReadBuffer", maxReadBuffer, + """|The maximum block size that can be read in one 'read' call on a file + |system. This is used to limit the amount of memory a call from a user + |program can cause to be allocated on the host side: when 'read' is, + |called a byte array with the specified size has to be allocated. So + |if this weren't limited, a Lua program could trigger massive memory + |allocations regardless of the amount of RAM installed in the computer + |it runs on. As a side effect this pretty much determines the read + |performance of file systems.""".stripMargin) max 0 // --------------------------------------------------------------------- // - commandUser = config.get("server.misc", "commandUser", commandUser, "" + - "The user name to specify when executing a command via a command\n" + - "block. If you leave this empty it will use the address of the network\n" + - "node that sent the execution request - which will usually be a\n" + - "computer."). - getString.trim + commandUser = config.fetch("server.misc.commandUser", commandUser, + """|The user name to specify when executing a command via a command + |block. If you leave this empty it will use the address of the network + |node that sent the execution request - which will usually be a + |computer.""".stripMargin).trim - maxScreenHeight = config.get("server.misc", "maxScreenHeight", maxScreenHeight, "" + - "The maximum height of multi-block screens, in blocks. This is limited\n" + - "to avoid excessive computations for merging screens. If you really\n" + - "need bigger screens it's probably safe to bump this quite a bit\n" + - "before you notice anything, since at least incremental updates should\n" + - "be very efficient (i.e. when adding/removing a single screen).") - .getInt(maxScreenHeight) max 1 + maxScreenHeight = config.fetch("server.misc.maxScreenHeight", maxScreenHeight, + """|The maximum height of multi-block screens, in blocks. This is limited + |to avoid excessive computations for merging screens. If you really + |need bigger screens it's probably safe to bump this quite a bit + |before you notice anything, since at least incremental updates should + |be very efficient (i.e. when adding/removing a single screen).""".stripMargin) max 1 - maxScreenWidth = config.get("server.misc", "maxScreenWidth", maxScreenWidth, "" + - "The maximum width of multi-block screens, in blocks.\n" + - "See also: `maxScreenHeight`.") - .getInt(maxScreenWidth) max 1 + maxScreenWidth = config.fetch("server.misc.maxScreenWidth", maxScreenWidth, + """|The maximum width of multi-block screens, in blocks.[nl] + |See also: `maxScreenHeight`.""".stripMargin) max 1 - maxWirelessRange = config.get("server.misc", "maxWirelessRange", maxWirelessRange, "" + - "The maximum distance a wireless message can be sent. In other words,\n" + - "this is the maximum signal strength a wireless network card supports.\n" + - "This is used to limit the search range in which to check for modems,\n" + - "which may or may not lead to performance issues for ridiculous\n" + - "ranges - like, you know, more than the loaded area.\n" + - "See also: `wirelessCostPerRange`."). - getDouble(maxWirelessRange) max 0 + maxWirelessRange = config.fetch("server.misc.maxWirelessRange", maxWirelessRange, + """|The maximum distance a wireless message can be sent. In other words, + |this is the maximum signal strength a wireless network card supports. + |This is used to limit the search range in which to check for modems, + |which may or may not lead to performance issues for ridiculous + |ranges - like, you know, more than the loaded area.[nl] + |See also: `wirelessCostPerRange`.""".stripMargin) max 0 // --------------------------------------------------------------------- // config.getCategory("robot"). setComment("Robot related settings, what they may do and general balancing.") - allowActivateBlocks = config.get("robot", "allowActivateBlocks", allowActivateBlocks, "" + - "Whether robots may 'activate' blocks in the world. This includes\n" + - "pressing buttons and flipping switches, for example. Disable this if\n" + - "it causes problems with some mod (but let me know!) or if you think\n" + - "this feature is too over-powered."). - getBoolean(allowActivateBlocks) + allowActivateBlocks = config.fetch("robot.allowActivateBlocks", allowActivateBlocks, + """|Whether robots may 'activate' blocks in the world. This includes + |pressing buttons and flipping levers, for example. Disable this if + |it causes problems with some mod (but let me know!) or if you think + |this feature is too over-powered.""".stripMargin) - canAttackPlayers = config.get("robot", "canAttackPlayers", canAttackPlayers, "" + - "Whether robots may damage players if they get in their way. This\n" + - "includes all 'player' entities, which may be more than just real\n" + - "players in the game."). - getBoolean(canAttackPlayers) + canAttackPlayers = config.fetch("robot.canAttackPlayers", canAttackPlayers, + """|Whether robots may damage players if they get in their way. This + |includes all 'player' entities, which may be more than just real + |players in the game.""".stripMargin) - canPlaceInAir = config.get("robot", "canPlaceInAir", canPlaceInAir, "" + - "Whether robots may place blocks in thin air, i.e. without a reference\n" + - "point (as is required for real players). Set this to true to emulate\n" + - "ComputerCraft's Turtles' behavior. When left false robots have to\n" + - "target an existing block face to place another block. For example,\n" + - "if the robots stands on a perfect plain, you have to call\n" + - "`robot.place(sides.down)` to place a block, instead of just\n" + - "`robot.place()`, which will default to `robot.place(sides.front)`."). - getBoolean(canPlaceInAir) + canPlaceInAir = config.fetch("robot.canPlaceInAir", canPlaceInAir, + """|Whether robots may place blocks in thin air, i.e. without a reference + |point (as is required for real players). Set this to true to emulate + |ComputerCraft's Turtles' behavior. When left false robots have to + |target an existing block face to place another block. For example, + |if the robots stands on a perfect plane, you have to call + |`robot.place(sides.down)` to place a block, instead of just + |`robot.place()`, which will default to `robot.place(sides.front)`.""".stripMargin) - itemDamageRate = config.get("robot", "itemDamageRate", itemDamageRate, "" + - "The rate at which items used as tools by robots take damage. A value\n" + - "of one means that items lose durability as quickly as when they are\n" + - "used by a real player. A value of zero means they will not lose any\n" + - "durability at all. This only applies to items that can actually be\n" + - "damaged (such as swords, pickaxes, axes and shovels).\n" + - "Note that this actually is the *chance* of an item losing durability\n" + - "when it is used. Or in other words, it's the inverse chance that the\n" + - "item will be automatically repaired for the damage it just took\n" + - "immediately after it was used."). - getDouble(itemDamageRate) max 0 min 1 + itemDamageRate = config.fetch("robot.itemDamageRate", itemDamageRate, + """|The rate at which items used as tools by robots take damage. A value + |of one means that items lose durability as quickly as when they are + |used by a real player. A value of zero means they will not lose any + |durability at all. This only applies to items that can actually be + |damaged (such as swords, pickaxes, axes and shovels).[nl] + |Note that this actually is the *chance* of an item losing durability + |when it is used. Or in other words, it's the inverse chance that the + |item will be automatically repaired for the damage it just took + |immediately after it was used.""".stripMargin) max 0 min 1 + + swingRange = config.fetch("robot.swingRange", swingRange, + """|The 'range' of robots when swinging an equipped tool (left click). + |This is the distance to the center of block the robot swings the + |tool in to the side the tool is swung towards. I.e. for the collision + |check, which is performed via ray tracing, this determines the end + |point of the ray like so:[nl] + |`block_center + unit_vector_towards_side * swingRange`[nl] + |This defaults to a value just below 0.5 to ensure the robots will not + |hit anything that's actually outside said block.""".stripMargin) + + useAndPlaceRange = config.fetch("robot.useAndPlaceRange", useAndPlaceRange, + """|The 'range' of robots when using an equipped tool (right click) or + |when placing items from their inventory. See `robot.swingRange`. This + |defaults to a value large enough to allow robots to detect 'farmland', + |i.e. tilled dirt, so that they can plant seeds.""".stripMargin) + + // ----------------------------------------------------------------------- // + // robot.delays + + dropDelay = config.fetch("robot.delays.drop", dropDelay, + """|The time in seconds to pause execution after an item was successfully + |dropped from a robot's inventory.""".stripMargin) max 0 + + moveDelay = config.fetch("robot.delays.move", moveDelay, + """|The time in seconds to pause execution after a robot issued a + |successful move command. Note that this essentially determines how + |fast robots can move around, since this also determines the length of + |the move animation.""".stripMargin) max 0.1 + + placeDelay = config.fetch("robot.delays.place", placeDelay, + """|The time in seconds to pause execution after a robot successfully + |placed an item from its inventory.""".stripMargin) max 0 + + suckDelay = config.fetch("robot.delays.suck", suckDelay, + """|The time in seconds to pause execution after a robot successfully + |picked up an item after triggering a suck command.""".stripMargin) max 0 + + swingDelay = config.fetch("robot.delays.swing", swingDelay, + """|The time in seconds to pause execution after a robot successfully + |swung a tool (or it's 'hands' if nothing is equipped). Successful in + |this case means that it hit something, either by attacking an entity + |or by breaking a block.""".stripMargin) max 0 + + turnDelay = config.fetch("robot.delays.turn", turnDelay, + """|The time in seconds to pause execution after a robot turned either + |left or right. Note that this essentially determines hw fast robots + |can turn around, since this also determines the length of the turn + |animation.""".stripMargin) max 0.1 + + useDelay = config.fetch("robot.delays.use", useDelay, + """|The time in seconds to pause execution after a robot successfully + |used an equipped tool (or it's 'hands' if nothing is equipped). + |Successful in this case means that it either used the equipped item, + |for example bone meal, or that it activated a block, for example by + |pushing a button.""".stripMargin) max 0 // --------------------------------------------------------------------- // - if (config.hasChanged) + if (config.hasChanged) { config.save() + } } } \ No newline at end of file diff --git a/li/cil/oc/common/block/Item.scala b/li/cil/oc/common/block/Item.scala index 640add8b9..6e99f92c8 100644 --- a/li/cil/oc/common/block/Item.scala +++ b/li/cil/oc/common/block/Item.scala @@ -17,8 +17,8 @@ class Item(id: Int) extends ItemBlock(id) { override def getUnlocalizedName = Config.namespace + "block" override def getUnlocalizedName(item: ItemStack) = - Block.blocksList(item.itemID) match { - case multiBlock: Delegator[_] => Config.namespace + "block." + multiBlock.getUnlocalizedName(item.getItemDamage) + Block.blocksList(getBlockID) match { + case delegator: Delegator[_] => Config.namespace + "block." + delegator.getUnlocalizedName(item.getItemDamage) case block => block.getUnlocalizedName } diff --git a/li/cil/oc/common/item/Delegate.scala b/li/cil/oc/common/item/Delegate.scala index a45efd3ed..218b2e3ab 100644 --- a/li/cil/oc/common/item/Delegate.scala +++ b/li/cil/oc/common/item/Delegate.scala @@ -33,6 +33,6 @@ trait Delegate { def registerIcons(iconRegister: IconRegister) {} - def equals(item: ItemStack) = - item != null && item.itemID == parent.itemID && parent.subItem(item).exists(_.itemId == itemId) + def equals(stack: ItemStack) = + stack != null && stack.getItem == parent && parent.subItem(stack).exists(_ == this) } diff --git a/li/cil/oc/common/item/Delegator.scala b/li/cil/oc/common/item/Delegator.scala index 012ca1f7f..27cff5d76 100644 --- a/li/cil/oc/common/item/Delegator.scala +++ b/li/cil/oc/common/item/Delegator.scala @@ -29,7 +29,7 @@ class Delegator(id: Int) extends Item(id) { def subItem(item: ItemStack): Option[Delegate] = subItem(item.getItemDamage) match { - case Some(subItem) if item.itemID == this.itemID => Some(subItem) + case Some(subItem) if item.getItem == this => Some(subItem) case _ => None } diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index 03ed2a49d..0330b42d4 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -150,7 +150,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { } else { player.setSneaking(sneaky) - val what = Option(pick(facing, side, 0.51)) match { + val what = Option(pick(facing, side, Config.useAndPlaceRange)) match { case Some(hit) if hit.typeOfHit == EnumMovingObjectType.TILE => val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit) player.placeBlock(stack, bx, by, bz, hit.sideHit, hx, hy, hz) @@ -207,7 +207,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { throw new IllegalArgumentException("invalid side") } val player = robot.player(facing, side) - Option(pick(facing, side, 0.49)) match { + Option(pick(facing, side, Config.swingRange)) match { case Some(hit) => val what = hit.typeOfHit match { case EnumMovingObjectType.ENTITY => @@ -257,11 +257,12 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { case _ => result(false) } player.setSneaking(sneaky) - val what = Option(pick(facing, side, 0.51)) match { + val what = Option(pick(facing, side, Config.useAndPlaceRange)) match { case Some(hit) => hit.typeOfHit match { case EnumMovingObjectType.ENTITY => // TODO Is there any practical use for this? Most of the stuff related to this is still 'obfuscated'... + // TODO I think this is used for shearing sheep, for example... needs looking into. result(false, "entity") case EnumMovingObjectType.TILE => val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit) @@ -380,7 +381,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { // ----------------------------------------------------------------------- // private def haveSameItemType(stackA: ItemStack, stackB: ItemStack) = - stackA.itemID == stackB.itemID && + stackA.getItem == stackB.getItem && (!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage) private def stackInSlot(slot: Int) = Option(robot.getStackInSlot(actualSlot(slot))) diff --git a/li/cil/oc/server/component/robot/Player.scala b/li/cil/oc/server/component/robot/Player.scala index 60e4c3e80..c748e9819 100644 --- a/li/cil/oc/server/component/robot/Player.scala +++ b/li/cil/oc/server/component/robot/Player.scala @@ -12,7 +12,7 @@ import net.minecraft.potion.PotionEffect import net.minecraft.server.MinecraftServer import net.minecraft.util.{Vec3, AxisAlignedBB, DamageSource, ChunkCoordinates} import net.minecraft.world.World -import net.minecraftforge.common.{ForgeDirection, FakePlayer} +import net.minecraftforge.common.{ForgeHooks, MinecraftForge, ForgeDirection, FakePlayer} import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action import net.minecraftforge.event.{Event, ForgeEventFactory} import net.minecraftforge.fluids.FluidRegistry @@ -179,6 +179,7 @@ class Player(val robot: Robot) extends FakePlayer(robot.world, "OpenComputers") val blockId = world.getBlockId(x, y, z) val block = Block.blocksList(blockId) + val metadata = world.getBlockMetadata(x, y, z) val mayBreakBlock = event.useBlock != Event.Result.DENY && blockId > 0 && block != null val canBreakBlock = mayBreakBlock && !block.isAirBlock(world, x, y, z) && @@ -192,12 +193,15 @@ class Player(val robot: Robot) extends FakePlayer(robot.world, "OpenComputers") return false } + if (!ForgeHooks.canHarvestBlock(block, this, metadata)) { + return false + } + val stack = getCurrentEquippedItem if (stack != null && stack.getItem.onBlockStartBreak(stack, x, y, z, this)) { return false } - val metadata = world.getBlockMetadata(x, y, z) world.playAuxSFXAtEntity(this, 2001, x, y, z, blockId + (metadata << 12)) if (stack != null) { diff --git a/li/cil/oc/server/driver/item/FileSystem.scala b/li/cil/oc/server/driver/item/FileSystem.scala index 30d263711..5d0bb85f1 100644 --- a/li/cil/oc/server/driver/item/FileSystem.scala +++ b/li/cil/oc/server/driver/item/FileSystem.scala @@ -48,7 +48,7 @@ object FileSystem extends Item { // if necessary. No one will know, right? Right!? val address = addressFromTag(nbt(item)) Option(oc.api.FileSystem.asManagedEnvironment(oc.api.FileSystem. - fromSaveDirectory(address, capacity, Config.filesBuffered), new ItemLabel(item))) match { + fromSaveDirectory(address, capacity, Config.bufferChanges), new ItemLabel(item))) match { case Some(environment) => environment.node.asInstanceOf[oc.server.network.Node].address = address environment diff --git a/li/cil/oc/server/driver/item/Item.scala b/li/cil/oc/server/driver/item/Item.scala index 0e2809f2a..c95777ca3 100644 --- a/li/cil/oc/server/driver/item/Item.scala +++ b/li/cil/oc/server/driver/item/Item.scala @@ -18,7 +18,7 @@ trait Item extends api.driver.Item { } protected def isOneOf(stack: ItemStack, items: common.item.Delegate*) = - stack.itemID == Items.multi.itemID && (Items.multi.subItem(stack) match { + stack.getItem == Items.multi && (Items.multi.subItem(stack) match { case None => false case Some(subItem) => items.contains(subItem) }) diff --git a/li/cil/oc/server/driver/item/Memory.scala b/li/cil/oc/server/driver/item/Memory.scala index 54c3a2d1f..1ba3576df 100644 --- a/li/cil/oc/server/driver/item/Memory.scala +++ b/li/cil/oc/server/driver/item/Memory.scala @@ -6,7 +6,7 @@ import li.cil.oc.api.driver.Slot import net.minecraft.item.ItemStack object Memory extends Item with driver.Memory { - def amount(item: ItemStack) = if (item.itemID == Items.multi.itemID) Items.multi.subItem(item) match { + def amount(stack: ItemStack) = if (stack.getItem == Items.multi) Items.multi.subItem(stack) match { case Some(memory: li.cil.oc.common.item.Memory) => memory.kiloBytes * 1024 case _ => 0 } else 0 diff --git a/li/cil/oc/util/ExtendedConfiguration.scala b/li/cil/oc/util/ExtendedConfiguration.scala new file mode 100644 index 000000000..fd2680fc6 --- /dev/null +++ b/li/cil/oc/util/ExtendedConfiguration.scala @@ -0,0 +1,43 @@ +package li.cil.oc.util + +import net.minecraftforge.common.Configuration +import scala.language.implicitConversions + +object ExtendedConfiguration { + implicit def extendedConfiguration(config: Configuration) = new ExtendedConfiguration(config) + + class ExtendedConfiguration(config: Configuration) { + def fetch(path: String, default: Boolean, comment: String) = { + val (category, name) = parse(path) + config.get(category, name, default, wrapComment(category, comment)).getBoolean(default) + } + + def fetch(path: String, default: Int, comment: String) = { + val (category, name) = parse(path) + config.get(category, name, default, wrapComment(category, comment)).getInt(default) + } + + def fetch(path: String, default: Double, comment: String) = { + val (category, name) = parse(path) + config.get(category, name, default, wrapComment(category, comment)).getDouble(default) + } + + def fetch(path: String, default: String, comment: String) = { + val (category, name) = parse(path) + config.get(category, name, default, wrapComment(category, comment)).getString + } + + private def parse(path: String) = { + val (category, name) = path.splitAt(path.lastIndexOf(".")) + (category, name.substring(1)) + } + + private def wrapComment(category: String, comment: String) = { + val indent = 1 + category.count(_ == '.') + val wrapRegEx = ("""(.{1,""" + (78 - indent * 4 - 2) + """})(\s|\z)""").r + val cleaned = comment.replace("\r\n", " ").replace("\n", " ").replace("\r", " ").replace("[nl]", "\n").trim() + wrapRegEx.replaceAllIn(cleaned, m => m.group(1).trim() + "\n").stripLineEnd + } + } + +}