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
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()
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

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