Advertisement
Guest User

Untitled

a guest
Aug 26th, 2016
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 48.21 KB | None | 0 0
  1. --[[
  2.   WEdit library (http://silvermods.com/WEdit/)
  3.  
  4.   The bresemham function falls under a different license, refer to it's documentation for licensing information.
  5.   Hit ALT + 0 in NP++ to fold all, and get an overview of the contents of this script.
  6. ]]
  7.  
  8. --[[
  9.   WEdit table, variables and functions accessed with 'wedit.' are stored here.
  10.   Configuration values should be accessed with 'wedit.config.key'.
  11.   Variables in wedit.user are prioritized over wedit.default.
  12.   Variables in wedit.user are primarily set by the WEdit configuration interface, it is not recommended to set them in this script.
  13. ]]
  14. wedit = {
  15.   default = {
  16.     delay = 15,
  17.     -- Tasks with a blank description aren't logged.
  18.     description = "",
  19.     -- Note: synchronization is not optimized; setting this value to true will cause critical issues.
  20.     synchronized = false,
  21.     doubleIterations = false,
  22.     clearSchematics = false,
  23.     iterationDelay = 15,
  24.     lineSpacing = 1,
  25.     pencilSize = 1,
  26.     blockSize = 1,
  27.     matmodSize = 1,
  28.     brushShape = "square",
  29.     noclipBind = "g",
  30.     noclipSpeed = 0.75,
  31.     updateConfig = false
  32.   },
  33.   user = {},
  34.   config = {}
  35. }
  36.  
  37. -- Set wedit.config to return the value found in wedit.user or in wedit.default.
  38. setmetatable(wedit.config, {__index = wedit.user})
  39. setmetatable(wedit.default, {
  40.   __index = function(t, k)
  41.     error("SIP: Could not find the default value for the parameter '" .. k .. "'.")
  42.   end
  43. })
  44. setmetatable(wedit.user, {
  45.   __index = function(t, k)
  46.     return wedit.default[k]
  47.   end
  48. })
  49.  
  50. --[[
  51.   Available matmods. Has to be updated when game updates add or remove options.
  52. ]]
  53. wedit.mods = {
  54.   [1] = "aegisalt",
  55.   [2] = "aliengrass",
  56.   [3] = "alpinegrass",
  57.   [4] = "aridgrass",
  58.   [5] = "ash",
  59.   [6] = "blackash",
  60.   [7] = "bone",
  61.   [8] = "ceilingslimegrass",
  62.   [9] = "ceilingsnow",
  63.   [10] = "charredgrass",
  64.   [11] = "coal",
  65.   [12] = "colourfulgrass",
  66.   [13] = "copper",
  67.   [14] = "corefragment",
  68.   [15] = "crystal",
  69.   [16] = "crystalgrass",
  70.   [17] = "diamond",
  71.   [18] = "durasteel",
  72.   [19] = "erchius",
  73.   [20] = "ferozium",
  74.   [21] = "fleshgrass",
  75.   [22] = "flowerygrass",
  76.   [23] = "gold",
  77.   [24] = "grass",
  78.   [25] = "heckgrass",
  79.   [26] = "hiveceilinggrass",
  80.   [27] = "hivegrass",
  81.   [28] = "iron",
  82.   [29] = "junglegrass",
  83.   [30] = "lead",
  84.   [31] = "metal",
  85.   [32] = "moonstone",
  86.   [33] = "moss",
  87.   [34] = "platinum",
  88.   [35] = "plutonium",
  89.   [36] = "prisilite",
  90.   [37] = "roots",
  91.   [38] = "sand",
  92.   [39] = "savannahgrass",
  93.   [40] = "silver",
  94.   [41] = "slimegrass",
  95.   [42] = "snow",
  96.   [43] = "snowygrass",
  97.   [44] = "solarium",
  98.   [45] = "sulphur",
  99.   [46] = "tar",
  100.   [47] = "tarceiling",
  101.   [48] = "tentaclegrass",
  102.   [49] = "thickgrass",
  103.   [50] = "tilled",
  104.   [51] = "tilleddry",
  105.   [52] = "titanium",
  106.   [53] = "toxicgrass",
  107.   [54] = "trianglium",
  108.   [55] = "tungsten",
  109.   [56] = "undergrowth",
  110.   [57] = "uranium",
  111.   [58] = "veingrowth",
  112.   [59] = "violium"
  113. }
  114.  
  115. wedit.breakMods = {
  116.   aegisalt = true,
  117.   coal = true,
  118.   copper = true,
  119.   corefragment = true,
  120.   crystal = true,
  121.   diamond = true,
  122.   durasteel = true,
  123.   erchius = true,
  124.   ferozium = true,
  125.   gold = true,
  126.   iron = true,
  127.   lead = true,
  128.   metal = true,
  129.   moonstone = true,
  130.   platinum = true,
  131.   plutonium = true,
  132.   prisilite = true,
  133.   silver = true,
  134.   solarium = true,
  135.   sulphur = true,
  136.   titanium = true,
  137.   trianglium = true,
  138.   tungsten = true,
  139.   uranium = true,
  140.   violium = true
  141. }
  142.  
  143. --[[
  144.   Available liquids. Has to be updated when game updates add or remove options.
  145. ]]
  146. wedit.liquids = {
  147.   [1] = { name = "water", id = 1 },
  148.   [2] = { name = "lava", id = 2 },
  149.   [3] = { name = "poison", id = 3 },
  150.   [4] = { name = "tarliquid", id = 5 },
  151.   [5] = { name = "healingliquid", id = 6 },
  152.   [6] = { name = "milk", id = 7 },
  153.   [7] = { name = "corelava", id = 8 },
  154.   [8] = { name = "fuel", id = 11 },
  155.   [9] = { name = "swampwater", id = 12 },
  156.   [10] = { name = "slimeliquid", id = 13 },
  157.   [11] = { name = "jellyliquid", id = 17 }
  158. }
  159.  
  160. wedit.liquidsByID = {}
  161. for i,v in ipairs(wedit.liquids) do
  162.   wedit.liquidsByID[v.id] = v.name
  163. end
  164.  
  165. --[[
  166.   Storage for positions that should be ignored by certain tools, such as the pencil.
  167.   This is to prevent multiple tasks from being created for the same tile.
  168.   Regular usage: wedit.lockedPositions[x .. "-" .. y .. layer] = true to lock and = nil to unlock.
  169. ]]
  170. wedit.lockedPositions = {}
  171.  
  172. --[[
  173.   Locks the block at the given position, which prevents the usage of the wedit.pencil
  174.   and wedit.removeMod on this block.
  175.   @param pos - {X, Y}, representing the position of the block to lock.
  176.   @param layer - "foreground" or "background".
  177.   @return - True if locking succeeded, false if it's already locked.
  178. ]]
  179. function wedit.lockPosition(pos, layer)
  180.   layer = layer or "foreground"
  181.   local p = math.floor(pos[1]) .. "-" .. math.floor(pos[2]) .. layer
  182.   if wedit.lockedPositions[p] then
  183.     return false
  184.   else
  185.     wedit.lockedPositions[p] = true
  186.     return true
  187.   end
  188. end
  189.  
  190. --[[
  191.   Unlocks the block at the given position, which allows the usage of the wedit.pencil
  192.   and wedit.removeMod on this block if it was already locked.
  193.   @param pos - {X, Y}, representing the position of the block to lock.
  194.   @param layer - "foreground" or "background".
  195.   @return - True if unlocking succeeded, false if it's already unlocked.
  196. ]]
  197. function wedit.unlockPosition(pos, layer)
  198.   layer = layer or "foreground"
  199.   local p = math.floor(pos[1]) .. "-" .. math.floor(pos[2]) .. layer
  200.   if wedit.lockedPositions[p] then
  201.     wedit.lockedPositions[p] = nil
  202.     return true
  203.   else
  204.     return false
  205.   end
  206. end
  207.  
  208. --[[
  209.   Draws lines on the edges of the given rectangle.
  210.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  211.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  212.   @param [color="green"] - "color" or {r, g, b}, where r/g/b are values between 0 and 255.
  213. ]]
  214. function wedit.debugRectangle(bottomLeft, topRight, color)
  215.   color = type(color) == "table" and color or type(color) == "string" and color or "green"
  216.  
  217.   world.debugLine({bottomLeft[1], topRight[2]}, {topRight[1], topRight[2]}, color) -- top edge
  218.   world.debugLine(bottomLeft, {bottomLeft[1], topRight[2]}, color) -- left edge
  219.   world.debugLine({topRight[1], bottomLeft[2]}, {topRight[1], topRight[2]}, color) -- right edge
  220.   world.debugLine({bottomLeft[1], bottomLeft[2]}, {topRight[1], bottomLeft[2]}, color) -- bottom edge
  221. end
  222.  
  223. --[[
  224.   Calls wedit.debugRectangle for the block at the given position.
  225.   @param pos - Position of the block, can be a floating-point number.
  226.   @param [color="green"] - "color" or {r, g, b}, where r/g/b are values between 0 and 255.
  227. ]]
  228. function wedit.debugBlock(pos, color)
  229.   local bl, tr = {math.floor(pos[1]), math.floor(pos[2])}, {math.ceil(pos[1]), math.ceil(pos[2])}
  230.   if bl[1] == tr[1] then tr[1] = tr[1] + 1 end
  231.   if bl[2] == tr[2] then tr[2] = tr[2] + 1 end
  232.   wedit.debugRectangle(bl, tr, color)
  233. end
  234.  
  235. --[[
  236.   Draws debug text below the user's character, or with an offset relative to it.
  237.   @param str - Text to draw.
  238.   @param [offset={0,0}] - {x,y} Offset relative to the feet of the player's character.
  239. ]]
  240. function wedit.info(str, offset)
  241.   if type(offset) == "nil" then offset = {0,0} end
  242.   if wedit.config.lineSpacing and wedit.config.lineSpacing ~= 1 then offset[2] = offset[2] * wedit.config.lineSpacing end
  243.   wedit.debugText(str, {mcontroller.position()[1] + offset[1], mcontroller.position()[2] - 3 + offset[2]})
  244. end
  245.  
  246. --[[
  247.   Draws debug text at the given world position.
  248.   @param str - Text to draw.
  249.   @param pos - Position in blocks.
  250.   @param [color="green"] - "color" or {r, g, b}, where r/g/b are values between 0 and 255.
  251. ]]
  252. function wedit.debugText(str, pos, color)
  253.   color = type(color) == "table" and color or type(color) == "string" and color or "green"
  254.   world.debugText(str, pos, color)
  255. end
  256.  
  257. --[[
  258.   Logs the given string with a WEdit prefix.
  259.   @param str - Text to log.
  260. ]]
  261. function wedit.logInfo(str)
  262.   sb.logInfo("WEdit: %s", str)
  263. end
  264.  
  265. --[[
  266.   Adds an entry to the debug log map, with a WEdit prefix.
  267.   @param key - Log map key. 'WEdit' is added in front of this key.
  268.   @param val - Log map value.
  269. ]]
  270. function wedit.setLogMap(key, val)
  271.   sb.setLogMap(string.format("^cyan;WEdit %s", key), val)
  272. end
  273.  
  274. --[[
  275.   Returns a copy of the given point.
  276.   Generally used to prevent having a bunch of references to the same points,
  277.   meaning asynchronous tasks will have undesired effects when changing your selection mid-task.
  278.   @param point - Point to clone.
  279.   @return - Cloned point.
  280. ]]
  281. function wedit.clonePoint(point)
  282.   return {point[1], point[2]}
  283. end
  284.  
  285. --[[
  286.   Quick attempt to lessen the amount of iterations needed to complete tasks such as filling an area.
  287.   For each block, see if there's a foreground material. If there is, see how far it's away from the furthest edge.
  288.   If this number is smaller than than the current amount of iterations, less iterations are needed.
  289.   Problem: Since every block is compared to the furthest edge and not other blocks, this generally misses a lot of skippable iterations.
  290.   @param bottomLeft - {X, Y}, representing the bottom left corner of the rectangle.
  291.   @param size - {X, Y}, representing the dimensions of the rectangle.
  292.   @param layer - "foreground" or "background", representing the layer to calculate iterations needed to fill for.
  293.     Note: The algorithm will check the OPPOSITE layer, as it assumes the given layer will be emptied before filling.
  294. ]]
  295. function wedit.calculateIterations(bottomLeft, size, layer)
  296.   local oppositeLayer = layer == "foreground" and "background" or "foreground"
  297.   local maxIterations = size[1] > size[2] and size[1] or size[2]
  298.   local iterations = maxIterations
  299.   local airFound = false
  300.   for i=0, size[1]-1 do
  301.     for j=0, size[2]-1 do
  302.       local mat = world.material({bottomLeft[1] + 0.5 + i, bottomLeft[2] + 0.5 + j}, oppositeLayer)
  303.       if mat then
  304.         local hPercentage = i / size[1]
  305.         local vPercentage = j / size[2]
  306.  
  307.         hPercentage = math.max(1 - hPercentage, hPercentage)
  308.         vPercentage = math.max(1 - vPercentage, vPercentage)
  309.         local maxPercentage = math.max(hPercentage,vPercentage)
  310.  
  311.         iterations = math.min(iterations, math.ceil(maxPercentage * maxIterations))
  312.       else
  313.         airFound = true
  314.       end
  315.     end
  316.   end
  317.  
  318.   if iterations and wedit.config.doubleIterations then iterations = iterations * 2 end
  319.   return airFound and iterations or 1
  320. end
  321.  
  322. --[[
  323.   List of all active or queued tasks.
  324.   Tasks queueing is done with wedit.Task:start(), do not manually add entries.
  325. ]]
  326. wedit.tasks = {}
  327.  
  328. --[[
  329.   Inject task handling into the update function.
  330. ]]
  331. local oldUpdate = update
  332. update = function(args)
  333.   oldUpdate(args)
  334.  
  335.   wedit.setLogMap("", string.format("(%s) Tasks.", #wedit.tasks))
  336.  
  337.   local first = true
  338.   for k,task in pairs(wedit.tasks) do
  339.     if coroutine.status(task.coroutine) == "dead" then
  340.       wedit.tasks[k] = nil
  341.     else
  342.       -- Allow first synchronized and all asynchronous tasks.
  343.       if not task.synchronized or first then
  344.         local a, b = coroutine.resume(task.coroutine)
  345.         if b then error(b) end
  346.  
  347.         if task.callback then
  348.           task.callback()
  349.         end
  350.  
  351.         if task.synchronized then
  352.           first = false
  353.         end
  354.       end
  355.     end
  356.   end
  357. end
  358.  
  359. --[[
  360.   Task Class.
  361.   Used to run actions in multiple steps over time.
  362. ]]
  363. wedit.Task = {}
  364. wedit.Task.__index = wedit.Task
  365. wedit.Task.__tostring = function() return "weditTask" end
  366.  
  367. --[[
  368.   Creates and returns a wedit Task object.
  369.   @param stages - Table of functions, each function defining code for one stage of the task.
  370.     Stages are repeated until changed or the task is completed.
  371.     Each stage function is passed the task object as it's first argument, used to easily access the task properties.
  372.     task.stage: Stage index, can be set to switch between stages.
  373.     task:nextStage():  Increases task.stage by 1. Does not abort remaining code when called in a stage function.
  374.     task.progress: Can be used to manually keep track of progress. Starts at 0.
  375.     task.progressLimit: Can be used to manually keep track of progress. Starts at 1.
  376.     task.parameters: Empty table that can be used to save and read parameters, without having to worry about reserved names.
  377.     task.complete(): Sets task.completed to true. Does not abort remaining code when called in a stage function.
  378.     task.callback: Function called every tick, regardless of delay and stage.
  379.   @param [delay=wedit.config.delay] - Delay, in game ticks, between each step.
  380.   @param [synchronized=wedit.config.synchronized] - Value indicating whether this task should run synchronized (true) or asynchronized (false).
  381.   @param [description=wedit.config.description] - Description used to log task details.
  382.   @return - Task object.
  383. ]]
  384. function wedit.Task.create(stages, delay, synchronized, description)
  385.   local task = {}
  386.  
  387.   task.stages = type(stages) == "table" and stages or {stages}
  388.  
  389.   task.delay = delay or wedit.config.delay
  390.   if wedit.config.doubleIterations then task.delay = math.ceil(task.delay / 2) end
  391.  
  392.   if type(synchronized) == "boolean" then
  393.     task.synchronized = synchronized
  394.   else
  395.     task.synchronized = wedit.config.synchronized
  396.   end
  397.   task.description = description or wedit.config.description
  398.   task.stage = 1
  399.   task.tick = 0
  400.   task.progress = 0
  401.   task.progressLimit = 1
  402.   task.completed = false
  403.   task.parameters = {}
  404.  
  405.   task.coroutine = coroutine.create(function()
  406.     while not task.completed do
  407.       task.tick = task.tick + 1
  408.       if task.tick > task.delay then
  409.         task.tick = 0
  410.  
  411.         task.stages[task.stage](task)
  412.       end
  413.       coroutine.yield()
  414.     end
  415.  
  416.     -- Soft reset task to allow repetition.
  417.     task.completed = false
  418.     task.stage = 1
  419.     task.progress = 0
  420.     task.progressLimit = 1
  421.     task.parameters = {}
  422.   end)
  423.  
  424.   setmetatable(task, wedit.Task)
  425.  
  426.   return task
  427. end
  428.  
  429. --[[
  430.   Queues the initialized task for execution.
  431. ]]
  432. function wedit.Task:start()
  433.   if self.description ~= "" then
  434.     local msg = self.synchronized and "Synchronized task (%s) queued. It will automatically start." or "Asynchronous task (%s) started."
  435.     wedit.logInfo(string.format(msg, self.description))
  436.   end
  437.   table.insert(wedit.tasks, self)
  438. end
  439.  
  440. --[[
  441.   Increases the stage index of the task by one.
  442.   @param [keepProgress=0] - Value indicating whether task.progress should be kept, or reset.
  443. ]]
  444. function wedit.Task:nextStage(keepProgress)
  445.   self.stage = self.stage + 1
  446.   if (self.stage > #self.stages) then self:complete() return end
  447.  
  448.   if not keepProgress then self.progress = 0 end
  449.   if self.description ~= "" then
  450.     wedit.logInfo(string.format("Task (%s) stage increased to %s.", self.description, self.stage))
  451.   end
  452. end
  453.  
  454. --[[
  455.   Sets the status of the task to complete.
  456. ]]
  457. function wedit.Task:complete()
  458.   if self.description ~= "" then
  459.     wedit.logInfo(string.format("Task (%s) completed.", self.description))
  460.   end
  461.   self.completed = true
  462. end
  463.  
  464. --[[
  465.   Starbound Block Class.
  466.   Identifiable with tostring(obj).
  467. ]]
  468. wedit.Block = {}
  469. wedit.Block.__index = wedit.Block
  470. wedit.Block.__tostring = function() return "starboundBlock" end
  471.  
  472. --[[
  473.   Creates and returns a block object.
  474.   Each optional parameter is automatically fetched when left blank.
  475.   @param position - Original position of the block.
  476.   @param offset - Offset from the bottom left corner of the copied area.
  477.   @param [foreground] - Material found in the foreground layer.
  478.   @param [foregroundMod] - Matmod found in the foreground layer.
  479.   @param [background] - Material found in the background layer.
  480.   @param [backgroundMod] - Matmod found in the background layer.
  481.   @param [liquid] - Liquid data found.
  482. ]]
  483. function wedit.Block.create(position, offset, foreground, foregroundMod, background, backgroundMod, liquid)
  484.   if not position then error("WEdit: Attempted to create a Block object for a block without a valid original position.") end
  485.   if not offset then error(string.format("WEdit: Attempted to create a Block object for a block at (%s, %s) without a valid offset.", position[1], position[2])) end
  486.  
  487.   local block = {
  488.     position = position,
  489.     offset = offset
  490.   }
  491.  
  492.   setmetatable(block, wedit.Block)
  493.  
  494.   block.foreground = {
  495.     material = foreground or block:getMaterial("foreground"),
  496.     mod = foregroundMod or block:getMod("foreground")
  497.   }
  498.  
  499.   block.background = {
  500.     material = background or block:getMaterial("background"),
  501.     mod = backgroundMod or block:getMod("background")
  502.   }
  503.  
  504.   block.liquid = liquid or block:getLiquid()
  505.  
  506.   return block
  507. end
  508.  
  509. --[[
  510.   Returns the material name of this block, if any.
  511.   @param layer - "foreground" or "background".
  512.   @return - Material name in the given layer.
  513. ]]
  514. function wedit.Block:getMaterial(layer)
  515.   return world.material(self.position, layer)
  516. end
  517.  
  518. --[[
  519.   Returns the matmod name of this block, if any.
  520.   @param layer - "foreground" or "background".
  521.   @return - Matmod name in the given layer.
  522. ]]
  523. function wedit.Block:getMod(layer)
  524.   return world.mod(self.position, layer)
  525. end
  526.  
  527. --[[
  528.   Returns the liquid datas of this block, if any.
  529.   @return - Nil or liquid data: {liquidID, liquidAmnt}.
  530. ]]
  531. function wedit.Block:getLiquid()
  532.   return world.liquidAt(self.position)
  533. end
  534.  
  535. --[[
  536.   Starbound Object Class. Contains data of a placeable object.
  537.   Identifiable with tostring(obj).
  538. ]]
  539. wedit.Object = {}
  540. wedit.Object.__index = wedit.Object
  541. wedit.Object.__tostring = function() return "starboundObject" end
  542.  
  543. --[[
  544.   Creates and returns a Starbound object.. object.
  545.   @param id - Entity id of the source object.
  546.   @param offset - Offset from the bottom left corner of the copied area.
  547.   @return - Starbound Object data. Contains id, offset, name, parameters, [items].
  548. ]]
  549. function wedit.Object.create(id, offset, name, parameters)
  550.   if not id then error("WEdit: Attempted to create a Starbound Object object without a valid entity id") end
  551.   if not offset then error(string.format("WEdit: Attempted to create a Starbound Object for (%s) without a valid offset", id)) end
  552.  
  553.   local object = {
  554.     id = id,
  555.     offset = offset
  556.   }
  557.  
  558.   setmetatable(object, wedit.Object)
  559.  
  560.   object.name = name or object:getName()
  561.   object.parameters = parameters or object:getParameters()
  562.   object.items = object:getItems(true)
  563.  
  564.   return object
  565. end
  566.  
  567. --[[
  568.   Returns the identifier of the object.
  569.   @return - Object name.
  570. ]]
  571. function wedit.Object:getName()
  572.   return world.entityName(self.id)
  573. end
  574.  
  575. --[[
  576.   Returns the full parameters of the object.
  577.   @return - Object parameters.
  578. ]]
  579. function wedit.Object:getParameters()
  580.   return world.getObjectParameter(self.id, "", nil)
  581. end
  582.  
  583. --[[
  584.   Returns the items of the container object, or nil if the object isn't a container.
  585.   @param clearTreasure - If true, sets the treasurePools parameter to nil, to avoid random loot after breaking the object.
  586.   @return - Contained items, or nil.
  587. ]]
  588. function wedit.Object:getItems(clearTreasure)
  589.   if clearTreasure then
  590.     self.parameters.treasurePools = nil
  591.   end
  592.  
  593.   if self.parameters.objectType == "container" then
  594.     return world.containerItems(self.id)
  595.   else
  596.     return nil
  597.   end
  598. end
  599.  
  600. --[[
  601.   Fills all air blocks in the given layer between the two points.
  602.   Calls wedit.breakBlocks using the given arguments if the block is nil, false or "air".
  603.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  604.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  605.   @param layer - "foreground" or "background".
  606.   @param block - String representation of material to use.
  607.   @return - Copy of the selection prior to the fill command.
  608. ]]
  609. function wedit.fillBlocks(bottomLeft, topRight, layer, block)
  610.   bottomLeft = wedit.clonePoint(bottomLeft)
  611.   topRight = wedit.clonePoint(topRight)
  612.  
  613.   if not block or block == "air" then return wedit.breakBlocks(bottomLeft, topRight, layer) end
  614.  
  615.   local width = topRight[1] - bottomLeft[1]
  616.   local height = topRight[2] - bottomLeft[2]
  617.  
  618.   if width < 0 or height < 0 then error(string.format("WEdit: Attempted to fill an area smaller than 0 blocks: (%s, %s) > (%s, %s).", bottomLeft[1], bottomLeft[2], topRight[1], topRight[2])) end
  619.  
  620.   local copyOptions = {
  621.     ["foreground"] = false,
  622.     ["foregroundMods"] = false,
  623.     ["background"] = false,
  624.     ["backgroundMods"] = false,
  625.     ["liquids"] = true,
  626.     ["objects"] = false,
  627.     ["containerLoot"] = false
  628.   }
  629.   copyOptions[layer] = true
  630.  
  631.   local copy = wedit.copy(bottomLeft, topRight, copyOptions)
  632.  
  633.   local iterations = wedit.calculateIterations(bottomLeft, {width, height}, layer)
  634.  
  635.   local task = wedit.Task.create({
  636.     function(task)
  637.       if task.progress < iterations then
  638.         task.progress = task.progress + 1
  639.  
  640.         for i=0,width-1 do
  641.           for j=0,height-1 do
  642.             local pos = {bottomLeft[1] + 0.5 + i, bottomLeft[2] + 0.5 + j}
  643.             world.placeMaterial(pos, layer, block, 0, true)
  644.           end
  645.         end
  646.       else
  647.         task:complete()
  648.       end
  649.     end
  650.   }, nil, false)
  651.  
  652.   task.callback = function()
  653.     wedit.debugRectangle(bottomLeft, topRight, "orange")
  654.     world.debugText(string.format("^shadow;WEdit Fill (%s-%s) %s/%s", layer, block, task.progress, iterations), {bottomLeft[1], topRight[2] - 1}, "orange")
  655.   end
  656.  
  657.   task:start()
  658.  
  659.   wedit.setLogMap("Fill", string.format("Task started with %s!", block))
  660.  
  661.   return copy
  662. end
  663.  
  664. --[[
  665.   Break all blocks in the given layer between the two points.
  666.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  667.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  668.   @param layer - "foreground" or "background".
  669.   @return - Copy of the selection prior to the break command.
  670. ]]
  671. function wedit.breakBlocks(bottomLeft, topRight, layer)
  672.   bottomLeft = wedit.clonePoint(bottomLeft)
  673.   topRight = wedit.clonePoint(topRight)
  674.  
  675.   local width = topRight[1] - bottomLeft[1]
  676.   local height = topRight[2] - bottomLeft[2]
  677.  
  678.   if width < 0 or height < 0 then error(string.format("WEdit: Attempted to break an area smaller than 0 blocks: (%s, %s) > (%s, %s).", bottomLeft[1], bottomLeft[2], topRight[1], topRight[2])) end
  679.  
  680.   local copyOptions = {
  681.     ["foreground"] = false,
  682.     ["foregroundMods"] = false,
  683.     ["background"] = false,
  684.     ["backgroundMods"] = false,
  685.     ["liquids"] = false,
  686.     ["objects"] = false,
  687.     ["containerLoot"] = false
  688.   }
  689.   copyOptions[layer] = true
  690.   copyOptions[layer .. "Mods"] = true
  691.  
  692.   local copy = wedit.copy(bottomLeft, topRight, copyOptions)
  693.  
  694.   for i=0,width-1 do
  695.     for j=0,height-1 do
  696.       local pos = {bottomLeft[1] + 0.5 + i, bottomLeft[2] + 0.5 + j}
  697.       world.damageTiles({pos}, layer, pos, "blockish", 9999, 0)
  698.     end
  699.   end
  700.  
  701.   wedit.setLogMap("Break", "Task executed!")
  702.  
  703.   return copy
  704. end
  705.  
  706. --[[
  707.   Draws a block, or break an existing block, at the location.
  708.   @param pos - World position in blocks.
  709.   @param layer - "foreground" or "background".
  710.   @param block - String representation of material to use.
  711. ]]
  712. function wedit.pencil(pos, layer, block)
  713.   local mat = world.material(pos, layer)
  714.   if (mat and mat ~= block) or not block then
  715.     world.damageTiles({pos}, layer, pos, "blockish", 9999, 0)
  716.     if block and wedit.lockPosition(pos, layer) then
  717.       wedit.Task.create({function(task)
  718.         world.placeMaterial(pos, layer, block, 0, true)
  719.         wedit.unlockPosition(pos, layer)
  720.         task:complete()
  721.       end}, nil, false):start()
  722.     end
  723.   else
  724.     world.placeMaterial(pos, layer, block, 0, true)
  725.   end
  726.  
  727.   wedit.setLogMap("Pencil", string.format("Drawn %s.", block))
  728. end
  729.  
  730. --[[
  731.   Copies and returns the given selection. Used in combination with wedit.paste.
  732.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  733.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  734.   @param [copyOptions] - Table with options representing what should be copied. Default is all-true.
  735.     Supported options:
  736.     foreground, foregroundMods, background, backgroundMods, liquids, objects, containerLoot
  737.     Options not defined will be set to true if any match has been found in the selection.
  738. ]]
  739. function wedit.copy(bottomLeft, topRight, copyOptions, logMaterials)
  740.   bottomLeft = wedit.clonePoint(bottomLeft)
  741.   topRight = wedit.clonePoint(topRight)
  742.  
  743.   local width = topRight[1] - bottomLeft[1]
  744.   local height = topRight[2] - bottomLeft[2]
  745.  
  746.   if width <= 0 or height <= 0 then
  747.     sb.logInfo("WEdit: Failed to copy area at (%s, %s) sized %sx%s.", bottomLeft[1], bottomLeft[2], width, height)
  748.     return
  749.   end
  750.  
  751.   -- Default copy options
  752.   if not copyOptions then
  753.     copyOptions = {
  754.       ["foreground"] = true,
  755.       ["foregroundMods"] = true,
  756.       ["background"] = true,
  757.       ["backgroundMods"] = true,
  758.       ["liquids"] = true,
  759.       ["objects"] = true,
  760.       ["containerLoot"] = true
  761.     }
  762.   end
  763.  
  764.   -- TODO: Implement ignorable options. EG: Objects = true, but no objects were found.
  765.   ignorableOptions = {}
  766.  
  767.   local copy = {
  768.     options = copyOptions,
  769.     origin = bottomLeft,
  770.     size = {width, height},
  771.     blocks = {},
  772.     objects = {}
  773.   }
  774.  
  775.   -- Table set containing objects in the selection.
  776.   -- objectIds[id] = true
  777.   local objectIds = {}
  778.  
  779.   local materialCount = {}
  780.   local matmodCount = {}
  781.   local liquidCount = {}
  782.  
  783.   local increaseCount = function(tbl, key)
  784.     if not tbl or not key then return end
  785.     if not tbl[key] then tbl[key] = 1 else tbl[key] = tbl[key] + 1 end
  786.   end
  787.  
  788.   -- Iterate over every block
  789.   for i=0,width-1 do
  790.     copy.blocks[i+1] = {}
  791.  
  792.     for j=0,height-1 do
  793.       -- Block coordinate
  794.       local pos = {bottomLeft[1] + 0.5 + i, bottomLeft[2] + 0.5 + j}
  795.  
  796.       -- Object check
  797.       if copy.options.objects ~= false then
  798.         local objects = world.objectQuery(pos, 1, {order="nearest"})
  799.         if objects and objects[1] then
  800.           objectIds[objects[1]] = true
  801.  
  802.           if copy.options.objects == nil then copy.options.objects = true end
  803.         end
  804.       end
  805.  
  806.       -- Block check
  807.       local block = wedit.Block.create(pos, {i, j})
  808.       copy.blocks[i+1][j+1] = block
  809.  
  810.       -- Count materials.
  811.       if logMaterials then
  812.         increaseCount(materialCount, block.foreground.material)
  813.         increaseCount(materialCount, block.background.material)
  814.         increaseCount(matmodCount, block.foreground.mod)
  815.         increaseCount(matmodCount, block.background.mod)
  816.         if block.liquid then
  817.           local liqName = wedit.liquidsByID[block.liquid[1]] or "unknown"
  818.           increaseCount(liquidCount, liqName)
  819.         end
  820.       end
  821.  
  822.       if copy.options.foreground == nil and block.foreground.material then copy.options.foreground = true end
  823.       if copy.options.foregroundMods == nil and block.foreground.mod then copy.options.foregroundMods = true end
  824.       if copy.options.background == nil and block.background.material then copy.options.background = true end
  825.       if copy.options.backgroundMods == nil and block.background.mod then copy.options.backgroundMods = true end
  826.       if copy.options.liquids == nil and block.liquid then copy.options.liquids = true end
  827.     end
  828.   end
  829.  
  830.   if copy.options.objects == nil and #objectIds > 0 then copy.options.objects = true end
  831.  
  832.   local objectCount = {}
  833.   -- Iterate over every found object
  834.   for id,_ in pairs(objectIds) do
  835.     local offset = world.entityPosition(id)
  836.     offset = {
  837.       offset[1] - bottomLeft[1],
  838.       offset[2] - bottomLeft[2]
  839.     }
  840.  
  841.     local object = wedit.Object.create(id, offset)
  842.  
  843.     -- Count objects.
  844.     if logMaterials then
  845.       increaseCount(objectCount, object.name)
  846.     end
  847.  
  848.     -- Set undefined containerLoot option to true if containers with items have been found.
  849.     if copy.options.containerLoot == nil and object.items then copy.options.containerLoot = true end
  850.  
  851.     table.insert(copy.objects, object)
  852.   end
  853.  
  854.   if logMaterials then
  855.     -- Logging materials found in the copy.
  856.     local sLog = "WEdit: A new copy has been made. Copy details:\nBlocks: %s\nMatMods: %s\nObjects: %s\nLiquids: %s"
  857.     local formatString = function(list)
  858.       local s = ""
  859.       for i,v in pairs(list) do
  860.         s = s .. i .. " x" .. v .. ", "
  861.       end
  862.       if s ~= "" then s = s:sub(1, -3) .. "." end
  863.       return s
  864.     end
  865.  
  866.     local sMaterials = formatString(materialCount)
  867.     local sObjects = formatString(objectCount)
  868.     local sMatmods = formatString(matmodCount)
  869.     local sLiquids = formatString(liquidCount)
  870.  
  871.     sb.logInfo(sLog, sMaterials, sMatmods, sObjects, sLiquids)
  872.   end
  873.   return copy
  874. end
  875.  
  876. --[[
  877.   Initializes and begins a paste with the given values. The position represents the bottom left corner of the paste.
  878.   @param copy - Copy table; see wedit.copy.
  879.   @param position - {X, Y}, representing the bottom left corner of the paste area.
  880.   @return - Copy of the selection prior to the paste command.
  881. ]]
  882. function wedit.paste(copy, position)
  883.   position = wedit.clonePoint(position)
  884.  
  885.   local paste = {
  886.     copy = copy,
  887.     placeholders = {}
  888.   }
  889.  
  890.   local backup = wedit.copy(position, {position[1] + copy.size[1], position[2] + copy.size[2]})
  891.  
  892.   local stages = {}
  893.  
  894.   local topRight = { position[1] + copy.size[1], position[2] + copy.size[2] }
  895.  
  896.   ---
  897.   -- Stage one: If copy has a background, break original background
  898.   if copy.options.background then
  899.     table.insert(stages, function(task)
  900.       task.progress = task.progress + 1
  901.  
  902.       local it = wedit.config.doubleIterations and 6 or 3
  903.       task.parameters.message = string.format("^shadow;Breaking background blocks (%s/%s).", task.progress - 1, it)
  904.  
  905.       if task.progress <= it then
  906.         wedit.breakBlocks(position, topRight, "background")
  907.       else
  908.         task:nextStage()
  909.       end
  910.     end)
  911.   end
  912.   -- /Stage one
  913.   ---
  914.  
  915.   local iterations = wedit.calculateIterations(position, copy.size)
  916.  
  917.   -- Stage two: If copy has background OR foreground, place background and/or placeholders.
  918.   if copy.options.background or copy.options.foreground then
  919.     table.insert(stages, function(task)
  920.       task.progress = task.progress + 1
  921.  
  922.       local lessIterations = wedit.calculateIterations(position, copy.size, "foreground")
  923.       lessIterations = iterations < lessIterations and iterations or lessIterations
  924.  
  925.       task.parameters.message = string.format("^shadow;Placing background and placeholder blocks (%s/%s).", task.progress - 1, lessIterations)
  926.  
  927.       if task.progress > lessIterations then
  928.         task:nextStage()
  929.         return
  930.       end
  931.  
  932.       for i=0, copy.size[1]-1 do
  933.         for j=0, copy.size[2]-1 do
  934.           local pos = {position[1] + 0.5 + i, position[2] + 0.5 + j}
  935.           -- Check if there's a background block here
  936.           local block = copy.blocks[i+1][j+1]
  937.           if block and copy.options.background and block.background.material then
  938.             -- Place the background block.
  939.             world.placeMaterial(pos, "background", block.background.material, 0, true)
  940.           else
  941.             if copy.options.foreground then
  942.               -- Add a placeholder that reminds us later to remove the dirt placed here temporarily.
  943.               if not paste.placeholders[i+1] then paste.placeholders[i+1] = {} end
  944.               if not world.material(pos, "background") then
  945.                 paste.placeholders[i+1][j+1] = true
  946.  
  947.                 world.placeMaterial(pos, "background", "dirt", 0, true)
  948.               end
  949.             end
  950.           end
  951.         end
  952.       end
  953.     end)
  954.   end
  955.   -- /Stage two
  956.   ---
  957.  
  958.   if copy.options.foreground then
  959.     -- Stage three: If copy has foreground, break it.
  960.     table.insert(stages, function(task)
  961.       task.progress = task.progress + 1
  962.  
  963.       local it = wedit.config.doubleIterations and 6 or 3
  964.       task.parameters.message = string.format("^shadow;Breaking foreground blocks (%s/%s).", task.progress - 1, it)
  965.  
  966.       if task.progress <= it then
  967.         wedit.breakBlocks(position, topRight, "foreground")
  968.       else
  969.         task:nextStage()
  970.       end
  971.     end)
  972.     -- /Stage three
  973.     ---
  974.  
  975.     -- Stage four: If copy has foreground, place it.
  976.     table.insert(stages, function(task)
  977.  
  978.       task.parameters.message = string.format("^shadow;Placing foreground blocks.")
  979.  
  980.       for i=0, copy.size[1]-1 do
  981.         for j=0, copy.size[2]-1 do
  982.           local pos = {position[1] + 0.5 + i, position[2] + 0.5 + j}
  983.  
  984.           -- Check if there's a background block here
  985.           local block = copy.blocks[i+1][j+1]
  986.           if block and block.foreground.material then
  987.             -- Place the background block.
  988.             world.placeMaterial(pos, "foreground", block.foreground.material, 0, true)
  989.           end
  990.         end
  991.       end
  992.  
  993.       task:nextStage()
  994.     end)
  995.     -- /Stage four
  996.     ---
  997.   end
  998.  
  999.   -- Stage five: If copy has liquids, place them.
  1000.   if copy.options.liquids then
  1001.     table.insert(stages, function(task)
  1002.  
  1003.       task.parameters.message = string.format("^shadow;Placing liquids.")
  1004.  
  1005.       for i=0,copy.size[1]-1 do
  1006.         for j=0,copy.size[2]-1 do
  1007.  
  1008.           local pos = {position[1] + 0.5 + i, position[2] + 0.5 + j}
  1009.  
  1010.           local block = copy.blocks[i+1][j+1]
  1011.           if block and block.liquid then
  1012.             world.spawnLiquid(pos, block.liquid[1], block.liquid[2])
  1013.           end
  1014.         end
  1015.       end
  1016.       task:nextStage()
  1017.     end)
  1018.   end
  1019.   -- /Stage five
  1020.   ---
  1021.  
  1022.   -- Stage six: If paste has foreground, and thus may need placeholders, remove the placeholders.
  1023.   if copy.options.foreground then
  1024.     table.insert(stages, function(task)
  1025.       task.parameters.message = "^shadow;Removing placeholder blocks."
  1026.       for i,v in pairs(paste.placeholders) do
  1027.         for j,k in pairs(v) do
  1028.           local pos = {position[1] - 0.5 + i, position[2] - 0.5 + j}
  1029.           world.damageTiles({pos}, "background", pos, "blockish", 9999, 0)
  1030.         end
  1031.       end
  1032.       task:nextStage()
  1033.     end)
  1034.   end
  1035.   -- /Stage six
  1036.   ---
  1037.  
  1038.   if copy.options.objects and #copy.objects > 0 then
  1039.     local hasItems = false
  1040.  
  1041.     -- Stage seven: If copy has objects, place them.
  1042.     table.insert(stages, function(task)
  1043.       task.parameters.message = "^shadow;Placing objects."
  1044.       local centerOffset = copy.size[1] / 2
  1045.       for _,v in pairs(copy.objects) do
  1046.         -- TODO: Object Direction
  1047.         local dir = v.parameters and v.parameters.direction == "left" and -1 or
  1048.           v.parameters and v.parameters.direction == "right" and 1 or
  1049.           v.offset[1] < centerOffset and 1 or -1
  1050.  
  1051.         -- Create unique ID
  1052.         local tempId = nil
  1053.         if v.parameters and v.parameters.uniqueId then
  1054.           tempId, v.parameters.uniqueId = v.parameters.uniqueId, sb.makeUuid()
  1055.         end
  1056.         world.placeObject(v.name, {position[1] + v.offset[1], position[2] + v.offset[2]}, dir, v.parameters)
  1057.  
  1058.         -- Restore unique ID of original object.
  1059.         if tempId then v.parameters.uniqueId = tempId end
  1060.  
  1061.         if v.items ~= nil then hasItems = true end
  1062.       end
  1063.       task:nextStage()
  1064.     end)
  1065.     -- /Stage seven
  1066.     ---
  1067.  
  1068.     -- Stage eight: If copy has containers, place items in them.
  1069.     if copy.options.containerLoot then
  1070.       table.insert(stages, function(task)
  1071.         task.parameters.message = "^shadow;Placing items in containers."
  1072.         if hasItems then
  1073.           for i,v in pairs(copy.objects) do
  1074.             if v.items then
  1075.               local ids = world.objectQuery({position[1] + v.offset[1], position[2] + v.offset[2]}, 1, {order="nearest"})
  1076.               if ids and ids[1] then
  1077.                 for j,k in ipairs(v.items) do
  1078.                   world.containerAddItems(ids[1], k)
  1079.                 end
  1080.               end
  1081.             end
  1082.           end
  1083.         end
  1084.         task:nextStage()
  1085.       end)
  1086.     end
  1087.     -- /Stage eight
  1088.     ---
  1089.   end
  1090.  
  1091.   -- Stage nine: If copy has matmods, place them
  1092.   if copy.options.foregroundMods or copy.options.backgroundMods then
  1093.       table.insert(stages, function(task)
  1094.           task.parameters.message = "^shadow;Placing material mods."
  1095.           for i=0, copy.size[1]-1 do
  1096.               for j=0, copy.size[2]-1 do
  1097.                   local pos = {position[1] + 0.5 + i, position[2] + 0.5 + j}
  1098.                   local block = copy.blocks[i+1][j+1]
  1099.  
  1100.                   if copy.options.foregroundMods and block.foreground.mod then
  1101.                       world.placeMod(pos, "foreground", block.foreground.mod, nil, false)
  1102.                   end
  1103.  
  1104.                   if copy.options.backgroundMods and block.background.mod then
  1105.                       world.placeMod(pos, "background", block.background.mod, nil, false)
  1106.                   end
  1107.               end
  1108.           end
  1109.  
  1110.           task:nextStage()
  1111.       end)
  1112.   end
  1113.   -- /Stage nine
  1114.   ---
  1115.  
  1116.   -- Stage ten: Done
  1117.   table.insert(stages, function(task)
  1118.     task.parameters.message = "^shadow;Done pasting!"
  1119.     task:complete()
  1120.   end)
  1121.   -- /Stage ten
  1122.   ---
  1123.  
  1124.   -- Create paste task, and start it.
  1125.   -- Add a callback to display a message every tick, rather than every step.
  1126.   local task = wedit.Task.create(stages, nil, nil, "Paste")
  1127.   task.parameters.message = ""
  1128.   task.callback = function()
  1129.     wedit.debugRectangle(position, topRight, "orange")
  1130.     if task.parameters.message then
  1131.       wedit.debugText(task.parameters.message, {position[1], topRight[2]-1}, "orange")
  1132.     end
  1133.   end
  1134.  
  1135.   task:start()
  1136.  
  1137.   wedit.setLogMap("Paste", string.format("Beginning new paste at (%s,%s)", position[1], position[2]))
  1138.  
  1139.   return backup
  1140. end
  1141.  
  1142. --[[
  1143.   Flips the given copy horizontally or vertically.
  1144.   Affects blocks, objects and matmods.
  1145.   Vertically flipping will cause issues with objects and matmods, since
  1146.   most objects can not be placed on air and some matmods will still appear
  1147.   on the top (or bottom) side of the flipped block.
  1148.   @param copy - Copy to flip, made by wedit.copy().
  1149.   @param direction - horizontal or vertical.
  1150.   @return - Flipped copy. Note that the original Lua object is being modified.
  1151. ]]
  1152. function wedit.flip(copy, direction)
  1153.   direction = direction:lower()
  1154.  
  1155.   if direction == "horizontal" then
  1156.     -- Flip blocks horizontally
  1157.     for i=1, math.floor(#copy.blocks / 2) do
  1158.       for j,v in ipairs(copy.blocks[i]) do
  1159.         v.offset[1] = copy.size[1] - i
  1160.       end
  1161.       for j,v in ipairs(copy.blocks[#copy.blocks - i + 1]) do
  1162.         v.offset[1] = i - 1
  1163.       end
  1164.       copy.blocks[i], copy.blocks[#copy.blocks - i + 1] = copy.blocks[#copy.blocks - i + 1], copy.blocks[i]
  1165.     end
  1166.  
  1167.     -- Flip objects horizontally
  1168.     for i,v in ipairs(copy.objects) do
  1169.       v.offset[1] = copy.size[1] - v.offset[1]
  1170.       if v.parameters and v.parameters.direction then
  1171.         v.parameters.direction = v.parameters.direction == "right" and "left" or "right"
  1172.       end
  1173.     end
  1174.  
  1175.     copy.flipX = not copy.flipX
  1176.   elseif direction == "vertical" then
  1177.     for _,w in ipairs(copy.blocks) do
  1178.       for i=1, math.floor(#w / 2) do
  1179.         for j,v in ipairs(w[i]) do
  1180.           v.offset[1] = copy.size[1] - i
  1181.         end
  1182.         for j,v in ipairs(w[#w- i + 1]) do
  1183.           v.offset[1] = i - 1
  1184.         end
  1185.         w[i], w[#w- i + 1] = w[#w - i + 1], w[i]
  1186.       end
  1187.     end
  1188.  
  1189.     for i,v in ipairs(copy.objects) do
  1190.       v.offset[2] = copy.size[2] - v.offset[2]
  1191.     end
  1192.  
  1193.     copy.flipY = not copy.flipY
  1194.   else
  1195.     wedit.logInfo("Could not flip copy in direction '" .. direction .. "'.")
  1196.   end
  1197.  
  1198.   return copy
  1199. end
  1200.  
  1201. --[[
  1202.   Initializes and begins a replace operation.
  1203.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  1204.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  1205.   @param layer - "foreground" or "background".
  1206.   @param toBlock - String representation of material to replace blocks with. Replaces with air when value is nil or false.
  1207.   @param [fromBlock] - String representation of material to replace. Replaces all blocks when value is nil or false.
  1208.   @returns - Copy of the selection prior to the replace command.
  1209. ]]
  1210. function wedit.replace(bottomLeft, topRight, layer, toBlock, fromBlock)
  1211.   bottomLeft = wedit.clonePoint(bottomLeft)
  1212.   topRight = wedit.clonePoint(topRight)
  1213.  
  1214.   local copyOptions = {
  1215.     ["foreground"] = false,
  1216.     ["foregroundMods"] = false,
  1217.     ["background"] = false,
  1218.     ["backgroundMods"] = false,
  1219.     ["liquids"] = false,
  1220.     ["objects"] = false,
  1221.     ["containerLoot"] = false
  1222.   }
  1223.   copyOptions[layer] = true
  1224.   copyOptions[layer .. "Mods"] = true
  1225.  
  1226.   local copy = wedit.copy(bottomLeft, topRight, copyOptions)
  1227.  
  1228.   bottomLeft = wedit.clonePoint(bottomLeft)
  1229.   topRight = wedit.clonePoint(topRight)
  1230.  
  1231.   local size = {topRight[1] - bottomLeft[1], topRight[2] - bottomLeft[2]}
  1232.   local oppositeLayer = layer == "foreground" and "background" or "foreground"
  1233.  
  1234.   local placeholders = {}
  1235.   local replacing = {}
  1236.  
  1237.   wedit.Task.create({
  1238.     function(task)
  1239.       -- Placeholders
  1240.       if toBlock then
  1241.         for i=0, size[1]-1 do
  1242.           if not placeholders[i+1] then placeholders[i+1] = {} end
  1243.           for j=0, size[2]-1 do
  1244.             local pos = {bottomLeft[1] + 0.5 + i, bottomLeft[2] + 0.5 + j}
  1245.             if not world.material(pos, oppositeLayer) then
  1246.               world.placeMaterial(pos, oppositeLayer, "dirt", 0, true)
  1247.               placeholders[i+1][j+1] = true
  1248.             end
  1249.           end
  1250.         end
  1251.       end
  1252.       task:nextStage()
  1253.     end,
  1254.     function(task)
  1255.       -- Break matching
  1256.       for i=0, size[1]-1 do
  1257.         if not replacing[i+1] then replacing[i+1] = {} end
  1258.         for j=0, size[2]-1 do
  1259.           local pos = {bottomLeft[1] + 0.5 + i, bottomLeft[2] + 0.5 + j}
  1260.           local block = world.material(pos, layer)
  1261.           if block and (not fromBlock or (block and block == fromBlock)) then
  1262.             replacing[i+1][j+1] = true
  1263.             world.damageTiles({pos}, layer, pos, "blockish", 9999, 0)
  1264.           end
  1265.         end
  1266.       end
  1267.       task:nextStage()
  1268.     end,
  1269.     function(task)
  1270.       -- Place new
  1271.       if toBlock then
  1272.         for i,v in pairs(replacing) do
  1273.           for j,k in pairs(v) do
  1274.             local pos = {bottomLeft[1] - 0.5 + i, bottomLeft[2] - 0.5 + j}
  1275.             world.placeMaterial(pos, layer, toBlock, 0, true)
  1276.           end
  1277.         end
  1278.       end
  1279.       task:nextStage()
  1280.     end,
  1281.     function(task)
  1282.       -- Remove placeholders
  1283.       for i,v in pairs(placeholders) do
  1284.         for j,k in pairs(v) do
  1285.           local pos = {bottomLeft[1] - 0.5 + i, bottomLeft[2] - 0.5 + j}
  1286.           world.damageTiles({pos}, oppositeLayer, pos, "blockish", 9999, 0)
  1287.         end
  1288.       end
  1289.       task:complete()
  1290.     end
  1291.   }):start()
  1292.  
  1293.   wedit.setLogMap("Replace", "Command started!")
  1294.  
  1295.   return copy
  1296. end
  1297.  
  1298. --[[
  1299.   Modifies the block at the given position, using a (hopefully validated) material mod type.
  1300.   @param pos - {X, Y}, representing the block position.
  1301.   @param layer - "foreground" or "background".
  1302.   @param block - String representation of material to replace blocks with. Replaces with air when value is nil or false.
  1303. ]]
  1304. function wedit.placeMod(pos, layer, block)
  1305.   world.placeMod(pos, layer, block, nil, false)
  1306. end
  1307.  
  1308. --[[
  1309.   Removes the matmod at the given position and layer.
  1310.   Uses wedit.breakMods to determine whether the block has to be broken and replaced or not.
  1311.   @param pos - {X, Y}, representing the block position.
  1312.   @param layer - "foreground" or "background"
  1313. ]]
  1314. function wedit.removeMod(pos, layer)
  1315.   local mod = world.mod(pos, layer)
  1316.   local mat = world.material(pos, layer)
  1317.   if not mod or not mat then return end
  1318.  
  1319.   if not wedit.breakMods[mod] then
  1320.     world.damageTiles({pos}, layer, pos, "blockish", 0, 0)
  1321.   elseif wedit.lockPosition(pos, layer) then
  1322.     wedit.Task.create({function(task)
  1323.       world.placeMod(pos, layer, "grass", nil, false)
  1324.       task:nextStage()
  1325.     end, function(task)
  1326.       world.damageTiles({pos}, layer, pos, "blockish", 0, 0)
  1327.       wedit.unlockPosition(pos, layer)
  1328.       task:complete()
  1329.     end}):start()
  1330.   end
  1331. end
  1332.  
  1333. --[[
  1334.   Drains any liquid in the given selection.
  1335.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  1336.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  1337. ]]
  1338. function wedit.drain(bottomLeft, topRight)
  1339.   for i=0,math.ceil(topRight[1]-bottomLeft[1])-1 do
  1340.     for j=0,math.ceil(topRight[2]-bottomLeft[2])-1 do
  1341.       world.destroyLiquid({bottomLeft[1] + i, bottomLeft[2] + j})
  1342.     end
  1343.   end
  1344. end
  1345.  
  1346. --[[
  1347.   Fills the given selection with liquid.
  1348.   @param bottomLeft - {X1, Y1}, representing the bottom left corner of the rectangle.
  1349.   @param topRight - {X2, Y2}, representing the top right corner of the rectangle.
  1350.   @param liquidId - ID of the liquid to use.
  1351. ]]
  1352. function wedit.hydrate(bottomLeft, topRight, liquidId)
  1353.   for i=0,math.ceil(topRight[1]-bottomLeft[1])-1 do
  1354.     for j=0,math.ceil(topRight[2]-bottomLeft[2])-1 do
  1355.       world.spawnLiquid({bottomLeft[1] + i, bottomLeft[2] + j}, liquidId, 1)
  1356.     end
  1357.   end
  1358. end
  1359.  
  1360. --[[
  1361.   Runs callback function with parameters (currentX, currentY) for each block in a line between the given points using Bresenham's Line Algorithm.
  1362.   Base code found at https://github.com/kikito/bresenham.lua is licensed under https://github.com/kikito/bresenham.lua/blob/master/MIT-LICENSE.txt
  1363.   @param startPos - {X1, Y1}, representing the start of the line.
  1364.   @param endPos - {X2, Y2}, representing the end of the line.
  1365.   @param callback - Callback function, called for every block on the line with the X and Y value as separate arguments.
  1366. ]]
  1367. function wedit.bresenham(startPos, endPos, callback)
  1368.   local x0, y0, x1, y1 = startPos[1], startPos[2], endPos[1], endPos[2]
  1369.  
  1370.   local sx,sy,dx,dy
  1371.  
  1372.   if x0 < x1 then
  1373.     sx = 1
  1374.     dx = x1 - x0
  1375.   else
  1376.     sx = -1
  1377.     dx = x0 - x1
  1378.   end
  1379.  
  1380.   if y0 < y1 then
  1381.     sy = 1
  1382.     dy = y1 - y0
  1383.   else
  1384.     sy = -1
  1385.     dy = y0 - y1
  1386.   end
  1387.  
  1388.   local err, e2 = dx-dy, nil
  1389.  
  1390.   callback(x0, y0)
  1391.  
  1392.   while not(x0 == x1 and y0 == y1) do
  1393.     e2 = err + err
  1394.     if e2 > -dy then
  1395.       err = err - dy
  1396.       x0  = x0 + sx
  1397.     end
  1398.     if e2 < dx then
  1399.       err = err + dx
  1400.       y0  = y0 + sy
  1401.     end
  1402.  
  1403.     callback(x0, y0)
  1404.  
  1405.   end
  1406. end
  1407.  
  1408. --[[
  1409.   Call bresenham function with a callback function to place the given block.
  1410.   @param startPos - {X1, Y1}, representing the start of the line.
  1411.   @param endPos - {X2, Y2}, representing the end of the line.
  1412.   @param layer - "foreground" or background".
  1413.   @param block - String representation of material to use.
  1414. ]]
  1415. function wedit.line(startPos, endPos, layer, block)
  1416.   if block ~= "air" and block ~= "none" then
  1417.     wedit.bresenham(startPos, endPos, function(x, y) world.placeMaterial({x, y}, layer, block, 0, true) end)
  1418.   else
  1419.     wedit.bresenham(startPos, endPos, function(x, y) world.damageTiles({{x,y}}, layer, {x,y}, "blockish", 9999, 0) end)
  1420.   end
  1421. end
  1422.  
  1423. --[[
  1424.   Runs the callback function with the position of each block in a rectangle around the given position and dimensions.
  1425.   @param pos - {X1, Y1}, representing the center of the rectangle.
  1426.   @param width - Horizontal size of the rectangle.
  1427.   @param [height=width] - Vertical size of the rectangle.
  1428.   @param callback - Callback function, called for every block in the circle.
  1429. ]]
  1430. function wedit.rectangle(pos, width, height, callback)
  1431.   height = height or width
  1432.   local blocks = {}
  1433.   local left, bottom  = (width - 1) / 2, (height - 1) / 2
  1434.   for x=0,width-1 do
  1435.     for y=0, height-1 do
  1436.       local block = {pos[1] - left + x, pos[2] - bottom + y}
  1437.       table.insert(blocks, block)
  1438.       if callback then
  1439.         callback(block)
  1440.       end
  1441.     end
  1442.   end
  1443.   return blocks
  1444. end
  1445.  
  1446. --[[
  1447.   Runs the callback function with the position of each block in a circle around the given position and radius.
  1448.   @param pos - {X1, Y1}, representing the center of the circle.
  1449.   @param radius - Radius of the circle from the center, in blocks. This does not include the center block.
  1450.   @param callback - Callback function, called for every block in the circle.
  1451. ]]
  1452. function wedit.circle(pos, radius, callback)
  1453.   radius = radius and math.abs(radius) or 1
  1454.   local blocks = {}
  1455.   for y=-radius,radius do
  1456.     for x=-radius,radius do
  1457.       if (x*x)+(y*y) <= (radius*radius) then
  1458.         local block = {pos[1] + x, pos[2] + y}
  1459.         table.insert(blocks, block)
  1460.         callback(block)
  1461.       end
  1462.     end
  1463.   end
  1464.   return blocks
  1465. end
  1466.  
  1467. --[[
  1468.   Function that logs environmental values and functions.
  1469.   You can't have enough of these at your disposal.
  1470. ]]
  1471. function wedit.logENV()
  1472.   for k,v in pairs(_ENV) do
  1473.     if type(v) == "table" then
  1474.       for k2,v2 in pairs(v) do
  1475.         sb.logInfo("%s.%s", k, k2)
  1476.       end
  1477.     else
  1478.       sb.logInfo("%s", k)
  1479.     end
  1480.   end
  1481. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement