ID:2066320
 
This is a set of functions I had to write today after discovering that my map had been built completely wrong based on my UI. I spent hours getting the symmetry right and then I had to redo the entire thing at a 90 degree rotation.

So... Rather than do all that, I decided I'd write a tool that would make me able to:

Rotate (0, 90, 180, and 270 degree rotation),
Flip (Horizontal, Vertical),
Mirror (Horizontal, Vertical) (taking the bottom and left axes as input),
Rearrange Z-layers,
Export Z-layers,
Delete Z-layers,
And re-export completed DMMs.

This is a developer tool, and as such is not meant to be used at runtime. It's not optimized because who actually cares about optimizing a developer tool? But it's pretty sprightly. It completed most tasks on a 128x128 map in zero time flat.

#define ceil(x) (-round(-(x)))
#define floor(x) round(x)

client
var
map_name
list/map_glyphs
map_width = 0
map_height = 0
map_depth = 0
list/map_layers
list/layer_order
proc
LoadDMM(m as file)
map_name = "[m]"
var/dmmstr = file2text(m)
var/list/l = splittext(dmmstr,"\n")
var/zpos = l.Find("(1,1,1) = {\"")+1
map_height = l.Find("\"}",zpos) - zpos
map_glyphs = l.Copy(1,zpos-2)
map_width = length(l[zpos])/ceil(log(52,map_glyphs.len))

map_layers = list()
layer_order = list()
map_depth = 0
while(zpos<l.len)
map_layers.len = ++map_depth
map_layers[map_depth] = l.Copy(zpos,zpos+map_height)
layer_order += map_depth
zpos += map_height+3
MapLoaded()

RotateMap(rotation as num|null)
if(rotation==null||map_depth==0) return 0
if(rotation<0) rotation += ceil(rotation/-360)*360
if(rotation>360) rotation -= floor(rotation/360)
if(rotation%90)
src << "You must rotate by a multiple of 90 degrees"
return
var/glyphs = ceil(log(52,map_glyphs.len))
var/pos, str
var/list/spans
var/list/ly
var/z,y,x
switch(rotation)
if(0)
return
if(90)
for(z in 1 to map_depth)
spans = list()
spans.len = map_width
ly = map_layers[z]
for(y in map_height to 1 step -1)
pos = 1
str = ly[y]
for(x in 1 to map_width)
spans[x] += copytext(str,pos,pos+glyphs)
pos += glyphs
map_layers[z] = spans
if(180)
for(z in 1 to map_depth)
spans = list()
spans.len = map_height
ly = map_layers[z]
for(y in map_height to 1 step -1)
pos = (map_width-1)*glyphs+1
str = ly[map_height-y+1]
for(x in 1 to map_width)
spans[y] += copytext(str,pos,pos+glyphs)
pos -= glyphs
map_layers[z] = spans
if(270)
for(z in 1 to map_depth)
spans = list()
spans.len = map_width
ly = map_layers[z]
for(y in 1 to map_height)
pos = (map_width-1)*glyphs+1
str = ly[y]
for(x in 1 to map_width)
spans[x] += copytext(str,pos,pos+glyphs)
pos -= glyphs
map_layers[z] = spans

FlipMap()
if(!map_depth) return 0
var/glyphs = ceil(log(52,map_glyphs.len))
var/pos, str
var/list/spans
var/list/ly
var/z,y,x
switch(alert(src,"Flip along which axis?","FlipMap","X","Y","cancel"))
if("X")
for(z in 1 to map_depth)
spans = list()
spans.len = map_height
ly = map_layers[z]
for(y in 1 to map_height)
pos = (map_width-1)*glyphs+1
str = ly[y]
for(x in 1 to map_width)
spans[y] += copytext(str,pos,pos+glyphs)
pos -= glyphs
map_layers[z] = spans
if("Y")
for(z in 1 to map_depth)
spans = list()
spans.len = map_height
ly = map_layers[z]
for(y in 1 to map_height)
spans[map_height-y+1] = ly[y]
map_layers[z] = spans
if("cancel")
return 0

MirrorMap()
if(!map_depth) return 0
var/glyphs = ceil(log(52,map_glyphs.len))
var/z,y,x
var/str,pos
var/list/spans
var/list/ly
switch(alert(src,"Mirror along which axis (copies left to right, bottom to top)?","MirrorMap","X","Y","cancel"))
if("X")
var/hw = floor(map_width)/2
var/cw = map_width-hw
for(z in 1 to map_depth)
spans = list()
spans.len = map_height
ly = map_layers[z]
for(y in 1 to map_height)
pos = (hw-1)*glyphs+1
str = copytext(ly[y],1,cw*glyphs+1)
for(x in 1 to hw)
str += copytext(str,pos,pos+glyphs)
pos -= glyphs
spans[y] = str
map_layers[z] = spans
if("Y")
var/hh = floor(map_height)/2
var/ch = map_height-hh
for(z in 1 to map_depth)
ly = map_layers[z]
spans = list()
spans.len = hh
for(y in 1 to hh)
spans[y] = ly[map_height-y+1]
spans += ly.Copy(ch+1)
map_layers[z] = spans
if("cancel")
return 0

SwapLayer(layer as num,swaplayer as num)
if(map_depth<2) return 0
if(layer<=0||swaplayer<=0||layer>map_depth||swaplayer>map_depth||floor(layer)!=layer||floor(swaplayer)!=swaplayer&&layer!=swaplayer)
src << "layers must be different positive integer values between 1 and [map_depth]"
var/list/ol = map_layers[layer]
map_layers[layer] = map_layers[swaplayer]
map_layers[swaplayer] = ol
var/oo = layer_order[layer]
layer_order[layer] = layer_order[swaplayer]
layer_order[swaplayer] = oo

DeleteLayer(layer as num)
if(!map_depth) return 0
if(layer<=0||layer>map_depth||floor(layer)!=layer)
src << "layer must be a positive integer value between 1 and [map_depth]"
map_layers.Cut(layer,layer+1)
layer_order.Cut(layer,layer+1)
--map_depth
if(map_depth==0)
MapUnloaded()

ExportLayer(layer as num)
if(map_depth<2) return 0
if(layer<=0||layer>map_depth||floor(layer)!=layer)
src << "layer must be a positive integer value between 1 and [map_depth]"
if(fexists("temp.dmm")) fdel("temp.dmm")

for(var/v in map_glyphs)
text2file(v,"temp.dmm")
text2file("\n(1,1,1) = {\"","temp.dmm")
var/list/ly = map_layers[layer]
for(var/v in ly)
text2file(v,"temp.dmm")
text2file("\"}","temp.dmm")
src << "Individually exported DMMs contain junk data that will not be removed until they are compiled by Dream Maker."
src << ftp("temp.dmm","layer.dmm")
fdel("temp.dmm")

ExportDMM()
if(!map_depth) return 0
if(fexists("temp.dmm")) fdel("temp.dmm")

for(var/v in map_glyphs)
text2file(v,"temp.dmm")
var/list/ly
for(var/z in 1 to map_depth)
text2file("\n(1,1,[z]) = {\"","temp.dmm")
ly = map_layers[z]
for(var/v in ly)
text2file(v,"temp.dmm")
text2file("\"}","temp.dmm")
src << "Maps that have been modified by this tool may contain junk data that will not be removed until they are compiled by Dream Maker. Pop ids can be unreliable, but Dream Maker will read them correctly and adjust them on the first load."
src << ftp("temp.dmm","export.dmm")
fdel("temp.dmm")

UnloadDMM()
if(!map_depth) return 0
MapUnloaded()

MapUnloaded()
map_name = null
map_depth = 0
map_height = 0
map_width = 0
map_glyphs = null
map_layers = null
layer_order = null
verbs.Remove(/client/proc/UnloadDMM,/client/proc/ExportDMM,/client/proc/ExportLayer,/client/proc/DeleteLayer,/client/proc/SwapLayer,/client/proc/MirrorMap,/client/proc/FlipMap,/client/proc/RotateMap)
verbs += /client/proc/LoadDMM

MapLoaded()
verbs -= /client/proc/LoadDMM
verbs.Add(/client/proc/FlipMap,/client/proc/MirrorMap,/client/proc/RotateMap)
if(map_depth>1)
verbs.Add(/client/proc/SwapLayer,/client/proc/DeleteLayer,/client/proc/ExportLayer)
verbs.Add(/client/proc/ExportDMM,/client/proc/UnloadDMM)

New()
. = ..()
if(.)
verbs += /client/proc/LoadDMM

Stat()
if(map_depth&&statpanel("[map_name]"))
stat("Glyphs:",map_glyphs.len)
stat(null)
stat("Width:",map_width)
stat("Height:",map_height)
stat("Depth:",map_depth)
if(map_depth>1)
stat(null)
stat("Layer order:")
var/count = 0
for(var/v in layer_order)
stat("[++count]:",v)


It should be noted that these methods do not alter the pop dictionary or any prototypes. So step_x and step_y values will be copied over. If you want these mirrored, it's gonna cost you some time and getting familiar to pops.

Some junk data can be exported with a DMM if you start deleting layers. Loading it up in Dream Maker and saving the map again will clear all of this junk data. Pop ids will be misaligned a bit here and there if you flip/rotate/mirror. Again, loading the map in Dream Maker and compiling will clear the junk out.
Your defines for ceil and floor are both the same o.o
#define ceil(x) (-round(-(x)))
#define floor(x) (-round(-(x)))
Good catch, thanks.