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

This commit is contained in:
Florian Nücke 2013-11-20 01:45:32 +01:00
parent 3324371ec6
commit 5ffe425f18
10 changed files with 330 additions and 258 deletions

View File

@ -1,6 +1,7 @@
package li.cil.oc package li.cil.oc
import java.io.File import java.io.File
import li.cil.oc.util.ExtendedConfiguration._
import li.cil.oc.util.PackedColor import li.cil.oc.util.PackedColor
object Config { object Config {
@ -67,7 +68,7 @@ object Config {
// server.filesystem // server.filesystem
var fileCost = 512 var fileCost = 512
var filesBuffered = true var bufferChanges = true
var maxHandles = 16 var maxHandles = 16
var maxReadBuffer = 8 * 1024 var maxReadBuffer = 8 * 1024
@ -84,6 +85,8 @@ object Config {
var canAttackPlayers = false var canAttackPlayers = false
var canPlaceInAir = false var canPlaceInAir = false
var itemDamageRate = 0.05 var itemDamageRate = 0.05
var swingRange = 0.49
var useAndPlaceRange = 0.65
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
// robot.delays // robot.delays
@ -96,7 +99,6 @@ object Config {
var turnDelay = 0.4 var turnDelay = 0.4
var useDelay = 0.4 var useDelay = 0.4
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def load(file: File) = { def load(file: File) = {
@ -121,309 +123,331 @@ object Config {
config.getCategory("client"). config.getCategory("client").
setComment("Client side settings, presentation and performance related stuff.") setComment("Client side settings, presentation and performance related stuff.")
maxScreenTextRenderDistance = config.get("client", "maxScreenTextRenderDistance", maxScreenTextRenderDistance, "" + maxScreenTextRenderDistance = config.fetch("client.maxScreenTextRenderDistance", maxScreenTextRenderDistance,
"The maximum distance at which to render text on screens. Rendering text\n" + """|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\n" + |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\n" + |to avoid huge numbers here. Note that this setting is client-sided, and
"only has an impact on render performance on clients."). |only has an impact on render performance on clients.""".stripMargin)
getDouble(maxScreenTextRenderDistance)
screenTextFadeStartDistance = config.get("client", "screenTextFadeStartDistance", screenTextFadeStartDistance, "" + screenTextFadeStartDistance = config.fetch("client.screenTextFadeStartDistance", screenTextFadeStartDistance,
"The distance at which to start fading out the text on screens. This is\n" + """|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\n" + |purely cosmetic, to avoid text disappearing instantly when moving too far
"away from a screen. This should have no measurable impact on performance.\n" + |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\n" + |Note that this needs OpenGL 1.4 to work, otherwise text will always just
"instantly disappear when moving away from the screen displaying it."). |instantly disappear when moving away from the screen displaying it.""".stripMargin)
getDouble(screenTextFadeStartDistance)
textLinearFiltering = config.get("client", "textLinearFiltering", textLinearFiltering, "" + textLinearFiltering = config.fetch("client.textLinearFiltering", textLinearFiltering,
"Whether to apply linear filtering for text displayed on screens when the\n" + """|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\n" + |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\n" + |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\n" + |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\n" + |down text but results in characters not perfectly connecting anymore (for
"example for box drawing characters. Look it up on Wikipedia.)"). |example for box drawing characters. Look it up on Wikipedia.)""".stripMargin)
getBoolean(textLinearFiltering)
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
config.getCategory("power"). config.getCategory("power").
setComment("Power settings, buffer sizes and power consumption.") setComment("Power settings, buffer sizes and power consumption.")
ignorePower = config.get("power", "ignorePower", ignorePower, "" + ignorePower = config.fetch("power.ignorePower", ignorePower,
"Whether to ignore any power requirements. Whenever something requires\n" + """|Whether to ignore any power requirements. Whenever something requires
"power to function, it will try to get the amount of energy it needs from\n" + |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\n" + |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\n" + |the action / trigger a shutdown / whatever. Setting this to `true` will
"simply make the check 'is there enough energy' succeed unconditionally.\n" + |simply make the check 'is there enough energy' succeed unconditionally.
"Note that buffers are still filled and emptied following the usual rules,\n" + |Note that buffers are still filled and emptied following the usual rules,
"there just is no failure case anymore."). |there just is no failure case anymore.""".stripMargin)
getBoolean(ignorePower)
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
bufferCapacitor = config.get("power.buffer", "bufferCapacitor", bufferCapacitor, "" + bufferCapacitor = config.fetch("power.buffer.capacitor", bufferCapacitor,
"The amount of energy a capacitor can store."). """|The amount of energy a capacitor can store.""".stripMargin) max 0
getDouble(bufferCapacitor) max 0
bufferConverter = config.get("power.buffer", "bufferConverter", bufferConverter, "" + bufferConverter = config.fetch("power.buffer.converter", bufferConverter,
"The amount of energy a power converter can store."). """|The amount of energy a power converter can store.""".stripMargin) max 0
getDouble(bufferConverter) max 0
bufferPowerSupply = config.get("power.buffer", "bufferPowerSupply", bufferPowerSupply, "" + bufferPowerSupply = config.fetch("power.buffer.powerSupply", bufferPowerSupply,
"The amount of energy a power supply can store."). """|The amount of energy a power supply can store.""".stripMargin) max 0
getDouble(bufferPowerSupply) max 0
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
computerCost = config.get("power.cost", "computerCost", computerCost, "" + computerCost = config.fetch("power.cost.computer", computerCost,
"The amount of energy a computer consumes per tick when running."). """|The amount of energy a computer consumes per tick when running.""".stripMargin) max 0
getDouble(computerCost) max 0
gpuFillCost = config.get("power.cost", "gpuFillCost", gpuFillCost, "" + gpuFillCost = config.fetch("power.cost.gpuFill", gpuFillCost,
"Energy it takes to change a single 'pixel' via the fill command. This\n" + """|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."). |means the total cost of the fill command will be its area times this.""".stripMargin) max 0
getDouble(gpuFillCost) max 0
gpuClearCost = config.get("power.cost", "gpuClearCost", gpuClearCost, "" + gpuClearCost = config.fetch("power.cost.gpuClear", gpuClearCost,
"Energy it takes to change a single 'pixel' to blank using the fill\n" + """|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\n" + |command. This means the total cost of the fill command will be its
"area times this."). |area times this.""".stripMargin) max 0
getDouble(gpuClearCost) max 0
gpuCopyCost = config.get("power.cost", "gpuCopyCost", gpuCopyCost, "" + gpuCopyCost = config.fetch("power.cost.gpuCopy", gpuCopyCost,
"Energy it takes to move a single 'pixel' via the copy command. This\n" + """|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."). |means the total cost of the copy command will be its area times this.""".stripMargin) max 0
getDouble(gpuCopyCost) max 0
gpuSetCost = config.get("power.cost", "gpuSetCost", gpuSetCost, "" + gpuSetCost = config.fetch("power.cost.gpuSet", gpuSetCost,
"Energy it takes to change a single 'pixel' via the set command. For\n" + """|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\n" + |calls to set with a string, this means the total cost will be the
"string length times this."). |string length times this.""".stripMargin) max 0
getDouble(gpuSetCost) max 0
hddReadCost = config.get("power.cost", "hddReadCost", hddReadCost, "" + hddReadCost = config.fetch("power.cost.hddRead", hddReadCost,
"Energy it takes read a single byte from a file system. Note that non\n" + """|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\n" + |I/O operations on file systems such as `list` or `getFreeSpace` do
"*not* consume power."). |*not* consume power.""".stripMargin) max 0
getDouble(hddReadCost) max 0
hddWriteCost = config.get("power.cost", "hddWriteCost", hddWriteCost, "" + hddWriteCost = config.fetch("power.cost.hddWrite", hddWriteCost,
"Energy it takes to write a single byte to a file system."). """|Energy it takes to write a single byte to a file system.""".stripMargin) max 0
getDouble(hddWriteCost) max 0
powerSupplyCost = config.get("power.cost", "powerSupplyCost", powerSupplyCost, "" + powerSupplyCost = config.fetch("power.cost.powerSupply", powerSupplyCost,
"The amount of energy a power supply (item) produces per tick. This is\n" + """|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\n" + |basically just a consumer, but instead of taking energy it puts it
"back into the network. This is slightly more than what a computer\n" + |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\n" + |consumes per tick. It's meant as an easy way to powering a small
"setup, mostly for testing. "). |setup, mostly for testing.""".stripMargin)
getDouble(powerSupplyCost)
screenCost = config.get("power.cost", "screenCost", screenCost, "" + screenCost = config.fetch("power.cost.screen", screenCost,
"The amount of energy a screen consumes per tick. If a screen cannot\n" + """|The amount of energy a screen consumes per displayed character per
"consume the defined amount of energy it will stop rendering the text\n" + |tick. If a screen cannot consume the defined amount of energy it will
"that should be displayed on it. It will *not* forget that text,\n" + |stop rendering the text that should be displayed on it. It will *not*
"however, so when enough power is available again it will restore the\n" + |forget that text, however, so when enough power is available again it
"previously displayed text (with any changes possibly made in the\n" + |will restore the previously displayed text (with any changes possibly
"meantime). Note that for multi-block screens *each* screen that is\n" + |made in the meantime). Note that for multi-block screens *each*
"part of it will consume this amount of energy per tick."). |screen that is part of it will consume this amount of energy per
getDouble(screenCost) max 0 |tick.""".stripMargin) max 0
wirelessCostPerRange = config.get("power.cost", "wirelessCostPerRange", wirelessCostPerRange, "" + wirelessCostPerRange = config.fetch("power.cost.wirelessStrength", wirelessCostPerRange,
"The amount of energy it costs to send a signal with strength one,\n" + """|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,\n" + |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\n" + |so for example to send a signal 400 blocks a signal strength of 400
"is required, costing a total of 400 * `wirelessCostPerRange`. In\n" + |is required, costing a total of 400 * `wirelessCostPerRange`. In
"other words, the higher this value, the higher the cost of wireless\n" + |other words, the higher this value, the higher the cost of wireless
"messages.\n" + |messages.[nl]
"See also: `maxWirelessRange`."). |See also: `maxWirelessRange`.""".stripMargin) max 0
getDouble(wirelessCostPerRange) max 0
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
config.getCategory("server"). config.getCategory("server").
setComment("Server side settings, gameplay and security related stuff.") setComment("Server side settings, gameplay and security related stuff.")
baseMemory = config.get("server.computer", "baseMemory", baseMemory, "" + baseMemory = config.fetch("server.computer.baseMemory", baseMemory,
"The base amount of memory made available in computers even if they\n" + """|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\n" + |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\n" + |using the given means, that being RAM components. Just keep in mind
"that this is global and applies to all computers!"). |that this is global and applies to all computers!""".stripMargin)
getInt(baseMemory)
canComputersBeOwned = config.get("server.computer", "canComputersBeOwned", canComputersBeOwned, "" + canComputersBeOwned = config.fetch("server.computer.canComputersBeOwned", canComputersBeOwned,
"This determines whether computers can only be used by players that\n" + """|This determines whether computers can only be used by players that
"are registered as users on them. Per default a newly placed computer\n" + |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\n" + |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,\n" + |all. Users can be managed via the Lua API (os.addUser, os.removeUser,
"os.users). If this is true, the following interactions are only\n" + |os.users). If this is true, the following interactions are only
"possible for users:\n" + |possible for users:[nl]
" - input via the keyboard.\n" + | - input via the keyboard.[nl]
" - inventory management.\n" + | - inventory management.[nl]
" - breaking the computer block.\n" + | - breaking the computer block.[nl]
"If this is set to false, all computers will always be usable by all\n" + |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\n" + |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.\n" + |are treated as if they were in the user list of every computer, i.e.
"no restrictions apply to them.\n" + |no restrictions apply to them.[nl]
"See also: `maxUsers` and `maxUsernameLength`."). |See also: `maxUsers` and `maxUsernameLength`.""".stripMargin)
getBoolean(canComputersBeOwned)
maxUsernameLength = config.get("server.computer", "maxUsernameLength", maxUsernameLength, "" + maxUsernameLength = config.fetch("server.computer.maxUsernameLength", maxUsernameLength,
"Sanity check for username length for users registered with computers.\n" + """|Sanity check for username length for users registered with computers.
"We store the actual user names instead of a hash to allow iterating\n" + |We store the actual user names instead of a hash to allow iterating
"the list of registered users on the Lua side.\n" + |the list of registered users on the Lua side.[nl]
"See also: `canComputersBeOwned`."). |See also: `canComputersBeOwned`.""".stripMargin) max 0
getInt(maxUsernameLength) max 0
maxUsers = config.get("server.computer", "maxUsers", maxUsers, "" + maxUsers = config.fetch("server.computer.maxUsers", maxUsers,
"The maximum number of users that can be registered with a single\n" + """|The maximum number of users that can be registered with a single
"computer. This is used to avoid computers allocating unchecked\n" + |computer. This is used to avoid computers allocating unchecked
"amounts of memory by registering an unlimited number of users.\n" + |amounts of memory by registering an unlimited number of users.
"See also: `canComputersBeOwned`."). |See also: `canComputersBeOwned`.""".stripMargin) max 0
getInt(maxUsers) max 0
startupDelay = config.get("server.computer", "startupDelay", startupDelay, "" + startupDelay = config.fetch("server.computer.startupDelay", startupDelay,
"The time in seconds to wait after a computer has been restored before\n" + """|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\n" + |it continues to run. This is meant to allow the world around the
"computer to settle, avoiding issues such as components in neighboring\n" + |computer to settle, avoiding issues such as components in neighboring
"chunks being removed and then re-connected and other odd things that\n" + |chunks being removed and then re-connected and other odd things that
"might happen."). |might happen.""".stripMargin) max 0
getDouble(startupDelay) max 0
threads = config.get("server.computer", "threads", threads, "" + threads = config.fetch("server.computer.threads", threads,
"The overall number of threads to use to drive computers. Whenever a\n" + """|The overall number of threads to use to drive computers. Whenever a
"computer should run, for example because a signal should be processed\n" + |computer should run, for example because a signal should be processed
"or some sleep timer expired it is queued for execution by a worker\n" + |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\n" + |thread. The higher the number of worker threads, the less likely it
"will be that computers block each other from running, but the higher\n" + |will be that computers block each other from running, but the higher
"the host system's load may become."). |the host system's load may become.""".stripMargin) max 1
getInt(threads) max 1
timeout = config.get("server.computer", "timeout", timeout, "" + timeout = config.fetch("server.computer.timeout", timeout,
"The time in seconds a program may run without yielding before it is\n" + """|The time in seconds a program may run without yielding before it is
"forcibly aborted. This is used to avoid stupidly written or malicious\n" + |forcibly aborted. This is used to avoid stupidly written or malicious
"programs blocking other computers by locking down the executor\n" + |programs blocking other computers by locking down the executor
"threads. Note that changing this won't have any effect on computers\n" + |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\n" + |that are already running - they'll have to be rebooted for this to
"take effect."). |take effect.""".stripMargin) max 0
getDouble(timeout) max 0
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
fileCost = config.get("server.filesystem", "fileCost", fileCost, "" + fileCost = config.fetch("server.filesystem.fileCost", fileCost,
"The base 'cost' of a single file or directory on a limited file\n" + """|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\n" + |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\n" + |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\n" + |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\n" + |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."). |size returned via the API will always be the real file size, however.""".stripMargin) max 0
getInt(fileCost) max 0
filesBuffered = config.get("server.filesystem", "filesBuffered", filesBuffered, "" + bufferChanges = config.fetch("server.filesystem.bufferChanges", bufferChanges,
"Whether persistent file systems such as disk drivers should be\n" + """|Whether persistent file systems such as disk drivers should be
"'buffered', and only written to disk when the world is saved. This\n" + |'buffered', and only written to disk when the world is saved. This
"applies to all hard drives. The advantage of having this enabled is\n" + |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\n" + |that data will never go 'out of sync' with the computer's state if
"the game crashes. The price is slightly higher memory consumption,\n" + |the game crashes. The price is slightly higher memory consumption,
"since all loaded files have to be kept in memory (loaded as in when\n" + |since all loaded files have to be kept in memory (loaded as in when
"the hard drive is in a computer)."). |the hard drive is in a computer).""".stripMargin)
getBoolean(filesBuffered)
maxHandles = config.get("server.filesystem", "maxHandles", maxHandles, "" + maxHandles = config.fetch("server.filesystem.maxHandles", maxHandles,
"The maximum number of file handles any single computer may have open\n" + """|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\n" + |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\n" + |only enforced by the filesystem node - if an add-on decides to be
"fancy it may well ignore this. Since file systems are usually\n" + |fancy it may well ignore this. Since file systems are usually
"'virtual' this will usually not have any real impact on performance\n" + |'virtual' this will usually not have any real impact on performance
"and won't be noticeable on the host operating system.") |and won't be noticeable on the host operating system.""".stripMargin) max 0
.getInt(maxHandles) max 0
maxReadBuffer = config.get("server.filesystem", "maxReadBuffer", maxReadBuffer, "" + maxReadBuffer = config.fetch("server.filesystem.maxReadBuffer", maxReadBuffer,
"The maximum block size that can be read in one 'read' call on a file\n" + """|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\n" + |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,\n" + |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\n" + |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\n" + |if this weren't limited, a Lua program could trigger massive memory
"allocations regardless of the amount of RAM installed in the computer\n" + |allocations regardless of the amount of RAM installed in the computer
"it runs on. As a side effect this pretty much determines the read\n" + |it runs on. As a side effect this pretty much determines the read
"performance of file systems.") |performance of file systems.""".stripMargin) max 0
.getInt(maxReadBuffer) max 0
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
commandUser = config.get("server.misc", "commandUser", commandUser, "" + commandUser = config.fetch("server.misc.commandUser", commandUser,
"The user name to specify when executing a command via a command\n" + """|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\n" + |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\n" + |node that sent the execution request - which will usually be a
"computer."). |computer.""".stripMargin).trim
getString.trim
maxScreenHeight = config.get("server.misc", "maxScreenHeight", maxScreenHeight, "" + maxScreenHeight = config.fetch("server.misc.maxScreenHeight", maxScreenHeight,
"The maximum height of multi-block screens, in blocks. This is limited\n" + """|The maximum height of multi-block screens, in blocks. This is limited
"to avoid excessive computations for merging screens. If you really\n" + |to avoid excessive computations for merging screens. If you really
"need bigger screens it's probably safe to bump this quite a bit\n" + |need bigger screens it's probably safe to bump this quite a bit
"before you notice anything, since at least incremental updates should\n" + |before you notice anything, since at least incremental updates should
"be very efficient (i.e. when adding/removing a single screen).") |be very efficient (i.e. when adding/removing a single screen).""".stripMargin) max 1
.getInt(maxScreenHeight) max 1
maxScreenWidth = config.get("server.misc", "maxScreenWidth", maxScreenWidth, "" + maxScreenWidth = config.fetch("server.misc.maxScreenWidth", maxScreenWidth,
"The maximum width of multi-block screens, in blocks.\n" + """|The maximum width of multi-block screens, in blocks.[nl]
"See also: `maxScreenHeight`.") |See also: `maxScreenHeight`.""".stripMargin) max 1
.getInt(maxScreenWidth) max 1
maxWirelessRange = config.get("server.misc", "maxWirelessRange", maxWirelessRange, "" + maxWirelessRange = config.fetch("server.misc.maxWirelessRange", maxWirelessRange,
"The maximum distance a wireless message can be sent. In other words,\n" + """|The maximum distance a wireless message can be sent. In other words,
"this is the maximum signal strength a wireless network card supports.\n" + |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,\n" + |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\n" + |which may or may not lead to performance issues for ridiculous
"ranges - like, you know, more than the loaded area.\n" + |ranges - like, you know, more than the loaded area.[nl]
"See also: `wirelessCostPerRange`."). |See also: `wirelessCostPerRange`.""".stripMargin) max 0
getDouble(maxWirelessRange) max 0
// --------------------------------------------------------------------- // // --------------------------------------------------------------------- //
config.getCategory("robot"). config.getCategory("robot").
setComment("Robot related settings, what they may do and general balancing.") setComment("Robot related settings, what they may do and general balancing.")
allowActivateBlocks = config.get("robot", "allowActivateBlocks", allowActivateBlocks, "" + allowActivateBlocks = config.fetch("robot.allowActivateBlocks", allowActivateBlocks,
"Whether robots may 'activate' blocks in the world. This includes\n" + """|Whether robots may 'activate' blocks in the world. This includes
"pressing buttons and flipping switches, for example. Disable this if\n" + |pressing buttons and flipping levers, for example. Disable this if
"it causes problems with some mod (but let me know!) or if you think\n" + |it causes problems with some mod (but let me know!) or if you think
"this feature is too over-powered."). |this feature is too over-powered.""".stripMargin)
getBoolean(allowActivateBlocks)
canAttackPlayers = config.get("robot", "canAttackPlayers", canAttackPlayers, "" + canAttackPlayers = config.fetch("robot.canAttackPlayers", canAttackPlayers,
"Whether robots may damage players if they get in their way. This\n" + """|Whether robots may damage players if they get in their way. This
"includes all 'player' entities, which may be more than just real\n" + |includes all 'player' entities, which may be more than just real
"players in the game."). |players in the game.""".stripMargin)
getBoolean(canAttackPlayers)
canPlaceInAir = config.get("robot", "canPlaceInAir", canPlaceInAir, "" + canPlaceInAir = config.fetch("robot.canPlaceInAir", canPlaceInAir,
"Whether robots may place blocks in thin air, i.e. without a reference\n" + """|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\n" + |point (as is required for real players). Set this to true to emulate
"ComputerCraft's Turtles' behavior. When left false robots have to\n" + |ComputerCraft's Turtles' behavior. When left false robots have to
"target an existing block face to place another block. For example,\n" + |target an existing block face to place another block. For example,
"if the robots stands on a perfect plain, you have to call\n" + |if the robots stands on a perfect plane, you have to call
"`robot.place(sides.down)` to place a block, instead of just\n" + |`robot.place(sides.down)` to place a block, instead of just
"`robot.place()`, which will default to `robot.place(sides.front)`."). |`robot.place()`, which will default to `robot.place(sides.front)`.""".stripMargin)
getBoolean(canPlaceInAir)
itemDamageRate = config.get("robot", "itemDamageRate", itemDamageRate, "" + itemDamageRate = config.fetch("robot.itemDamageRate", itemDamageRate,
"The rate at which items used as tools by robots take damage. A value\n" + """|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\n" + |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\n" + |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\n" + |durability at all. This only applies to items that can actually be
"damaged (such as swords, pickaxes, axes and shovels).\n" + |damaged (such as swords, pickaxes, axes and shovels).[nl]
"Note that this actually is the *chance* of an item losing durability\n" + |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\n" + |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\n" + |item will be automatically repaired for the damage it just took
"immediately after it was used."). |immediately after it was used.""".stripMargin) max 0 min 1
getDouble(itemDamageRate) 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() config.save()
} }
}
} }

View File

@ -17,8 +17,8 @@ class Item(id: Int) extends ItemBlock(id) {
override def getUnlocalizedName = Config.namespace + "block" override def getUnlocalizedName = Config.namespace + "block"
override def getUnlocalizedName(item: ItemStack) = override def getUnlocalizedName(item: ItemStack) =
Block.blocksList(item.itemID) match { Block.blocksList(getBlockID) match {
case multiBlock: Delegator[_] => Config.namespace + "block." + multiBlock.getUnlocalizedName(item.getItemDamage) case delegator: Delegator[_] => Config.namespace + "block." + delegator.getUnlocalizedName(item.getItemDamage)
case block => block.getUnlocalizedName case block => block.getUnlocalizedName
} }

View File

@ -33,6 +33,6 @@ trait Delegate {
def registerIcons(iconRegister: IconRegister) {} def registerIcons(iconRegister: IconRegister) {}
def equals(item: ItemStack) = def equals(stack: ItemStack) =
item != null && item.itemID == parent.itemID && parent.subItem(item).exists(_.itemId == itemId) stack != null && stack.getItem == parent && parent.subItem(stack).exists(_ == this)
} }

View File

@ -29,7 +29,7 @@ class Delegator(id: Int) extends Item(id) {
def subItem(item: ItemStack): Option[Delegate] = def subItem(item: ItemStack): Option[Delegate] =
subItem(item.getItemDamage) match { 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 case _ => None
} }

View File

@ -150,7 +150,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
} }
else { else {
player.setSneaking(sneaky) 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 => case Some(hit) if hit.typeOfHit == EnumMovingObjectType.TILE =>
val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit) val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit)
player.placeBlock(stack, bx, by, bz, hit.sideHit, hx, hy, hz) 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") throw new IllegalArgumentException("invalid side")
} }
val player = robot.player(facing, side) val player = robot.player(facing, side)
Option(pick(facing, side, 0.49)) match { Option(pick(facing, side, Config.swingRange)) match {
case Some(hit) => case Some(hit) =>
val what = hit.typeOfHit match { val what = hit.typeOfHit match {
case EnumMovingObjectType.ENTITY => case EnumMovingObjectType.ENTITY =>
@ -257,11 +257,12 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
case _ => result(false) case _ => result(false)
} }
player.setSneaking(sneaky) player.setSneaking(sneaky)
val what = Option(pick(facing, side, 0.51)) match { val what = Option(pick(facing, side, Config.useAndPlaceRange)) match {
case Some(hit) => case Some(hit) =>
hit.typeOfHit match { hit.typeOfHit match {
case EnumMovingObjectType.ENTITY => case EnumMovingObjectType.ENTITY =>
// TODO Is there any practical use for this? Most of the stuff related to this is still 'obfuscated'... // 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") result(false, "entity")
case EnumMovingObjectType.TILE => case EnumMovingObjectType.TILE =>
val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit) 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) = private def haveSameItemType(stackA: ItemStack, stackB: ItemStack) =
stackA.itemID == stackB.itemID && stackA.getItem == stackB.getItem &&
(!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage) (!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage)
private def stackInSlot(slot: Int) = Option(robot.getStackInSlot(actualSlot(slot))) private def stackInSlot(slot: Int) = Option(robot.getStackInSlot(actualSlot(slot)))

View File

@ -12,7 +12,7 @@ import net.minecraft.potion.PotionEffect
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.util.{Vec3, AxisAlignedBB, DamageSource, ChunkCoordinates} import net.minecraft.util.{Vec3, AxisAlignedBB, DamageSource, ChunkCoordinates}
import net.minecraft.world.World 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.entity.player.PlayerInteractEvent.Action
import net.minecraftforge.event.{Event, ForgeEventFactory} import net.minecraftforge.event.{Event, ForgeEventFactory}
import net.minecraftforge.fluids.FluidRegistry 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 blockId = world.getBlockId(x, y, z)
val block = Block.blocksList(blockId) val block = Block.blocksList(blockId)
val metadata = world.getBlockMetadata(x, y, z)
val mayBreakBlock = event.useBlock != Event.Result.DENY && blockId > 0 && block != null val mayBreakBlock = event.useBlock != Event.Result.DENY && blockId > 0 && block != null
val canBreakBlock = mayBreakBlock && val canBreakBlock = mayBreakBlock &&
!block.isAirBlock(world, x, y, z) && !block.isAirBlock(world, x, y, z) &&
@ -192,12 +193,15 @@ class Player(val robot: Robot) extends FakePlayer(robot.world, "OpenComputers")
return false return false
} }
if (!ForgeHooks.canHarvestBlock(block, this, metadata)) {
return false
}
val stack = getCurrentEquippedItem val stack = getCurrentEquippedItem
if (stack != null && stack.getItem.onBlockStartBreak(stack, x, y, z, this)) { if (stack != null && stack.getItem.onBlockStartBreak(stack, x, y, z, this)) {
return false return false
} }
val metadata = world.getBlockMetadata(x, y, z)
world.playAuxSFXAtEntity(this, 2001, x, y, z, blockId + (metadata << 12)) world.playAuxSFXAtEntity(this, 2001, x, y, z, blockId + (metadata << 12))
if (stack != null) { if (stack != null) {

View File

@ -48,7 +48,7 @@ object FileSystem extends Item {
// if necessary. No one will know, right? Right!? // if necessary. No one will know, right? Right!?
val address = addressFromTag(nbt(item)) val address = addressFromTag(nbt(item))
Option(oc.api.FileSystem.asManagedEnvironment(oc.api.FileSystem. 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) => case Some(environment) =>
environment.node.asInstanceOf[oc.server.network.Node].address = address environment.node.asInstanceOf[oc.server.network.Node].address = address
environment environment

View File

@ -18,7 +18,7 @@ trait Item extends api.driver.Item {
} }
protected def isOneOf(stack: ItemStack, items: common.item.Delegate*) = 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 None => false
case Some(subItem) => items.contains(subItem) case Some(subItem) => items.contains(subItem)
}) })

View File

@ -6,7 +6,7 @@ import li.cil.oc.api.driver.Slot
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
object Memory extends Item with driver.Memory { 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 Some(memory: li.cil.oc.common.item.Memory) => memory.kiloBytes * 1024
case _ => 0 case _ => 0
} else 0 } else 0

View File

@ -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
}
}
}