Topographic Scanner

Post your coolest Hexcasting creations here.

New topics are NOT for chatting or asking help, put those in the comments of a post or in a different forum.
User avatar
Robotgiggle
Posts: 23
Joined: Fri Dec 02, 2022 12:38 am

Topographic Scanner

Post by Robotgiggle »

Overview
This spell creates a topographical scan of the area surrounding your Greater Sentinel, in the form of a list of heights ranging from 0 to 13. The area encompassed by the scan is a cylinder with a diameter of 29 blocks and a height of 13 blocks, centered on the Sentinel's position. The spell isn't really intended to be used on its own, but rather as part of a larger device that can convert the raw data produced by the scan into a readable format.

How to Use the Spell
First, you'll need to make a cleric circle that can read the main spell list from a focus. You'll also need an additional focus to hold the list of 665 offsets that make up the scan area. The list of offsets describes a circle with a diameter of 29 blocks, with a profile as follows:

Code: Select all

O O O O
|       O O O
|             O O
|                 O
|                   O
|                     O
|                       O
|                       O
|                         O
|                         O
|                         O
|                           O
|                           O
|                           O
X - - - - - - - - - - - - - O
Below is the main body of the spell. As written, the list of offsets should be placed in an item frame one block south of the circle's lower-north-west corner, and the akashic reference should be located one block diagonally north+up from the impetus. Feel free to change all of these relative positions to better suit your circle.

Code: Select all

{
  Lesser Fold Reflection  // |
  Vector Reflection +Z    // | this section finds the itemframe for the offset list
  Additive Distillation   // |
  Entity Purification
  Scribe's Purification
  Huginn's Gambit
  Consideration
  Consideration
  // unrolled section gets injected here
  Numerical Reflection (0)
  Numerical Reflection (19)
  Gemini's Gambit
  Numerical Reflection (19)
  Flock's Gambit
  Thoth's Gambit
  Bookkeeper's Gambit (v)
  Muninn's Reflection
  Locate Sentinel
  Vector Disintegration
  Numerical Reflection (3.0)
  Flock's Gambit
  Speaker's Distillation
  Lesser Fold Reflection  // |
  Vector Reflection +Z    // |
  Vector Reflection +Y    // | this section finds the akashic reference
  Additive Distillation   // |
  Additive Distillation   // |
  Consideration
  Consideration
  // library key goes here
  Rotation Gambit
  Akasha's Gambit
}
This is the unrolled section, which should be injected into the main body at the specified location. Note that only the patterns within the curly brackets should be unrolled - the first two patterns and the last pattern should be added to the list after the unrolling is done. The final list to be injected should contain the first two patterns, 35 copies of the bracketed patterns, and then the last one.

Code: Select all

Bookkeeper's Gambit (v)
Muninn's Reflection
{{{
Speaker's Decomposition
Locate Sentinel
Additive Distillation
Gemini Decomposition
Vector Reflection -Y
Archer's Distillation
Gemini Decomposition
Identity Purification
Jester's Gambit
Numerical Reflection (2)
Augur's Purification
Vector Reflection -Y
Numerical Reflection (13)
Multiplicative Distillation
Additive Distillation
Augur's Exaltation
Subtractive Distillation
Length Purification
Integration Distillation
}}}
Huginn's Gambit
The final output of the spell is a list of 665 numbers, each representing the height of a specific X,Y position within the circle (the higher the block, the lower the number), along with single sublist at index 0 of the main list containing the X, Y, and Z position of the sentinel. This list will be saved into an akashic library at the provided location, using the provided key.

How to Read the Output
A list of 665 numbers is, of course, not exactly the most readable output. This is because it's not meant to be read by the player, but rather by a computer. The Block Reader from Advanced Peripherals can be attached to the bookshelf storing the output list to allow a computer to read the stored data. Then, each height can be converted into one of 13 different colors, and displayed as a pixel on a monitor in a location corresponding to its location in the actual scanned circle. The sublist containing the coordinates of the sentinel can be used to display some additional info on the monitor such as the location being scanned and the distance to the location.

CC:Tweaked is not necessarily the only way to translate the output data into a readable format. Integrated Dynamics, or really any other mod capable of reading block data, can probably provide an alternate way of transforming the output into something nice-looking. You could even make another Hex Casting spell to display the data, by rebuilding the scanned area at a specified location.

Keep in mind that the bookshelf storing the output data will need to be erased somehow if you want to run another scan. My setup using CC:Tweaked does this by breaking and replacing it with a turtle, but there are plenty of other options.
User avatar
Robotgiggle
Posts: 23
Joined: Fri Dec 02, 2022 12:38 am

Re: Topographic Scanner

Post by Robotgiggle »

Computercraft Setup
As mentioned in the main post, the topographic mapper spell is designed to be used along with an external device to store and display the data it records. This section describes how to set up such a device, based on the design I used when making the spell. To start, you'll need the following components: 6 Advanced Monitors, 1 Advanced Computer, 1 Redstone Integrator, 1 Block Reader, and 1 Disk Drive. I also highly recommend getting a Mining Turtle to break and replace the akashic bookshelf before each scan, but you can easily make do with a different kind of block-replacement setup. Place the 6 monitors in a vertical 2x3 rectangle, and place the computer on the middle-right side of the monitor setup. Place the Block Reader behind the computer (pointing into the akashic bookshelf which stores the spell output), place the Redstone Integrator underneath the computer, and place the Disk Drive on top of the computer. Finally, connect the Redstone Integrator to both the impetus for the spell circle and the mechanism for breaking and replacing the bookshelf. The physical setup is now done, and you should have a 3x3 wall of blocks that can be placed flush with a wall, with all of the other components behind it. Below, you can find the various scripts which should be loaded onto the computer to make it work.

scan.lua
This is the most important script, which reads the data from the bookshelf and parses it into a .nfp file to be displayed on the monitors. Some additional data about the scan is included in the final line of the file - this won't cause any errors if you display the file via the built-in loadImage() method, but to properly display the data you should use the custom-made show.lua script in the next code block.

Code: Select all

reader = peripheral.wrap("back")
mon = peripheral.wrap("left")
red = peripheral.wrap("bottom")
 
red.setOutput("front",true)
sleep(0.2)
red.setOutput("front",false)
sleep(4)
 
heights = {}
center = {}
for k,v in pairs(reader.getBlockData().iota["hexcasting:data"]) do
    if k == 0 then
        center.x = math.floor(v["hexcasting:data"][0]["hexcasting:data"])
        center.y = math.floor(v["hexcasting:data"][1]["hexcasting:data"])
        center.z = math.floor(v["hexcasting:data"][2]["hexcasting:data"])
        center.d = math.floor(math.sqrt((center.x-53)^2+(center.y-233)^2+(center.z-3)^2))
    else
        table.insert(heights,math.min(13,v["hexcasting:data"]))
    end
end
minDist = math.min(table.unpack(heights))
maxDist = math.max(table.unpack(heights))
 
prefixes = {string.rep("f",36),"f00000ffffff",
            "f0fffffff","f0fffff","f0ffff",
            "f0fff","ffff","fff","fff",
            "ff","ff","ff",
            "f","f","f","f","f","f","f",
            "ff","ff","ff",
            "fff","fff","ffff","f0fff",
            "f0ffff","f0fffff","f0fffffff",
            "f00000ffffff",string.rep("f",36)}
postfixes = {"","ffffff00000ffffff","fffffff0ff111f",
             "fffff0ffffff","ffff0ff222f",
             "fff0ffffff","fffff333f","ffffffff",
             "ffff444f","fffffff","fff555f",
             "fffffff","ff666f","ffffff","ff777f",
             "ffffff","ff888f","ffffff","ff999f",
             "fffffff","fffaaaf","fffffff",
             "ffffbbbf","ffffffff","fffffcccf",
             "fff0ffffff","ffff0ffdddf","fffff0ffffff",
             "fffffff0ffeeef","ffffff00000ffffff",""}
 
widths = {0,7,13,17,19,21,23,25,25,27,27,27,29,29,29,29,
          29,29,29,27,27,27,25,25,23,21,19,17,13,7,0}
 
file = fs.open("temp.nfp","w")
c = 1
for i=1,31 do
    file.write(prefixes[i])
    for j=1,widths[i] do
        file.write(string.format("%x",heights[c]+1))
        c = c + 1
    end
    file.write(postfixes[i].."\n")
end
file.write(center.x.." "..center.y.." "..center.z.." "..center.d)
file.write(" "..math.floor(center.y+6-maxDist))
file.write(" "..math.floor(center.y+6-minDist))
file.close()
 
shell.run("show")
show.lua
This script reads the custom .nfp files produced by scan.lua and displays them on the screen. Since it's designed to handle the custom data line at the bottom of the files produced by the scan script, it may behave oddly if used to display other kinds of images.

Code: Select all

mon = peripheral.wrap("left")
tArgs = {...}
 
term.redirect(mon)
term.setBackgroundColor(colors.black)
term.clear()
 
if tArgs[1] ~= nil then
    if tArgs[2] == "disk" then
        file = fs.open("disk/"..tArgs[1]..".nfp","r")
    else
        file = fs.open("scans/"..tArgs[1]..".nfp","r")
else
    file = fs.open("temp.nfp","r")
end
for i=1,31 do
    paintutils.drawImage(paintutils.parseImage(file.readLine()),1,i)
end
data_iter = string.gmatch(file.readLine(),"%S+")
file.close()
 
term.setCursorPos(1,32)
print(" --< Topographical Scan Details >--\n")
print(" Center point:",data_iter(),data_iter(),data_iter())
print(" Distance to center:",data_iter())
print(" Lowest Y value:",data_iter())
print(" Highest Y value:",data_iter())
saveas.lua
This script is used to save a copy of the current scan onto your computer, placing the copy into a folder called "scans". It should be run on the command line with an argument to provide a filename for the saved copy (no file extension necessary). If you add the optional "disk" argument, it will save the file to the "disk" directory instead - this directory should automatically appear whenever a disk is loaded into the drive.

Code: Select all

tArgs = {...}
 
if tArgs[1] == nil then
    print("You need to provide a filename.")
    return
end
 
if tArgs[2] == "disk" then
    dir = "disk/"
else
    dir = "scans/"
end
 
shell.run("cp temp.nfp",dir..tArgs[1]..".nfp")
startup.lua
This script, due to its name, will be automatically invoked whenever your computer starts up. It serves to configure the monitor, shrinking the text size and replacing the default CC:Tweaked monitor colors with a smooth gradient.

Code: Select all

mon = peripheral.wrap("left")
 
mon.setTextScale(0.5)
 
mon.setPaletteColor(colors.orange,0xff00ff)
mon.setPaletteColor(colors.magenta,0xff00aa)
mon.setPaletteColor(colors.lightBlue,0xff0055)
mon.setPaletteColor(colors.yellow,0xff0000)
mon.setPaletteColor(colors.lime,0xff5500)
mon.setPaletteColor(colors.pink,0xffaa00)
mon.setPaletteColor(colors.gray,0xffff00)
mon.setPaletteColor(colors.lightGray,0xaaff00)
mon.setPaletteColor(colors.cyan,0x55ff00)
mon.setPaletteColor(colors.purple,0x00ff00)
mon.setPaletteColor(colors.blue,0x00ff55)
mon.setPaletteColor(colors.brown,0x00ffaa)
mon.setPaletteColor(colors.green,0x00ffff)
mon.setPaletteColor(colors.red,0x00aaff)