[Megathread] Map/Condo External Tooling & Scripts

I don’t have much to say at the moment, other than that there is so much potential for external tools in map making

The goal of this megathread is to develop new tools outside of Tower Unite for manipulating maps at a large scale, or doing things that couldn’t be done otherwise

NOTE: Any tools/scripts posted or shared here are not officially affiliated with PixelTail Games nor Tower Unite. They are unofficial tools created by the community for the purpose of making community content and maps

Some personal examples of external code I developed for map making:

Creating Invisible Collision Geometry

In my ttt_minecraft_b5 remake, I use canvas cones and canvas pyramids to create the collision geometry that you walk on, so that you can smoothly walk up/down blocks without having to jump (and without having to size the players up). I created a script to give all the unset canvases the invisible material, so I wouldn’t have to do it manually

I didn’t want to set them invisible as I was going, because if I did there’d be no way to visualize the collision geometry. In retrospect I could have just used blocking volumes, but it would have been a bit more unwieldy (with more frustrating edge-cases)

Creating Invisible Collision Geometry: Code

This is pretty messy, but the idea is to inject CondoInvisibleMaterial into all of the CanvasWedge sections of the file:

import os.path
import struct

def main():
    filename = 'CondoData'
    payload = b'SurfaceMaterial\x00\x0f\x00\x00\x00ObjectProperty\x00\x42' \
        + (b'\x00' * 8) + b'\x3e\x00\x00\x00' \
        + b'/Game/Materials/CondoInvisibleMaterial.CondoInvisibleMaterial'
    
    target = b'SurfaceMaterial\x00\x0f\x00\x00\x00ObjectProperty\x00\x09' \
        + (b'\x00' * 8) + b'\x05\x00\x00\x00' \
        + b'None'
    
    buf = bytearray(os.path.getsize(filename))
    with open(filename, 'rb') as fd:
        fd.readinto(buf)
    
    delim = b'CanvasWedge_C_'
    
    sections = buf.split(delim)
    wedges = sections[1:]
        
    wedges = [wedge.replace(target, payload, 1) for wedge in wedges]
    
    for x in range(len(wedges)):
        idx = wedges[x].find(b'\x00')
        wedges[x] = wedges[x][:(idx+1)] + b':' + wedges[x][(idx+2):]
    
    header_delim = b'CanvasWedge'
    headers = sections[0].split(header_delim)
    
    header_payload = b'\x10\x00\x00\x00' + payload + b'\x00'
    
    for x in range(len(headers))[1:]:        
        if headers[x].find(b'SurfaceMaterial') > 0:
            continue
        
        headers[x] = headers[x][:0x1d] + b'\x38\x01' + headers[x][0x1f:]
        idx = headers[x].find(b'Vector') + 36
        
        headers[x] = headers[x][:idx] + header_payload + headers[x][idx:]
        
    new_header = header_delim.join(headers)
    
    new_sections = [new_header] + wedges
    new_buf = delim.join(new_sections)
    with open('experiment_output', 'wb') as fd:
        fd.write(new_buf)

if __name__ == '__main__':
    main()

It’s straight-forward to do the same for CanvasPyramid, replacing “CanvasCone” with “CanvasPyramid” and replacing some size bytes.

Resizing Cones

In LC Deathrun, I wanted to resize these cones but didn’t feel confident about group-scaling them. Instead I wrote a script to replace the bytes corresponding to world scale in the .map file

Resizing Cones: Code
import os.path
import struct
import re

def scaleset(match):
    floats = match.group(4)
    scale_x, scale_y, scale_z = struct.unpack('<fff', floats)
    scale_z *= 2 
    out_bytes = struct.pack('<fff', scale_x, scale_y, scale_z)
    return b''.join(match.groups()[:-1]) + out_bytes

def main():
    filename = 'CondoData'
    
    buf = bytearray(os.path.getsize(filename))
    with open(filename, 'rb') as fd:
        fd.readinto(buf)
    
    buf = re.sub(br'(CanvasCone)(.+?)(WorldScale.+?Vector\x00{18})(.{12})', scaleset, buf)
    
    with open('experiment_output', 'wb') as fd:
        fd.write(buf)

if __name__ == '__main__':
    main()

Discord server link: Tower Unite Tooling Developers

6 Likes

As of writing, there are only a few other external tools that I’m aware of:

In the near-term future I think it’ll be best to explore what’s possible with Python scripting and other small programs, with the goal ultimately being to make a fully external parser and editor for Tower Unite maps

2 Likes

I had no idea that imhex pattern was public, I’ve hidden it for now.

1 Like

I’ve also removed it from the list, which reduces the list to ONE publicly-available external tool

This is really cool. There’s definitely some good applications for scripting helper functions like your examples.

1 Like

Procedural map test: lots of stone blocks!

1 Like

really cool and exciting stuff, i sadly wouldn’t be able to contribute myself but it’s amazing to see advancements in condo building tech, i can’t wait to see what’s possible that we haven’t explored yet for the game :two_hearts:

2 Likes

Since my last post here, I’ve been working on developing the tiling tool into an entire API for editing condo/.map files in Python.

As of today, this tool (PyTower) has officially reached its first milestone and first major release (v0.1.0)! PyTower is being hosted on GitHub: repo link. PyTower also has a WIP wiki, hosted through GitHub Pages: PyTower Wiki Home Page.

Some of its features include:

  • A dozen built-in tools, such as Tile, Filter, Rotate, SetURL, and more!
  • High-level Python API for creating drop-in custom tools
  • Rapid prototyping utilities for making maps with PyTower
  • Utility subcommands for tooling development
  • (WIP) A way to download and back-up all canvases locally

PyTower Discord server link: https://discord.gg/NUufVuu4Ve

2 Likes

I edited the included tiling script to change the color of an object based upon its index and got this cool effect:

1 Like