Lunacraft Map Data

{{Code| #!/usr/bin/perl $bapropos = q@ mooncraft-map(1r) -- RMCMC (Robert Munafo's Companion to MOONCRAFT) Parse and display mooncraft map files in iOS backup @;

$help = qq@ NAME

mooncraft-map(1r) - RMCMC (Robert Munafo's Companion to MOONCRAFT) Parse and display mooncraft map files in iOS backup

DESCRIPTION

This script provides a command-line interface for browsing the map data from the Mooncraft (aka Lunacraft) iOS app. You need to use a program (like iTunes or iExplorer to get the files from the app's sandbox file area on the iOS device, then chdir to the directory containing the files and run this script. It expects filenames in SHA1-encoded form, e.g. "8c676d7863301fcc8574e0b4599c8046af72fc6e" for the file "Documents/0_0_0.mw".

At the cmd> prompt type '?' for a list of commands. @;

<< 'HEADER_END';

REVISION HISTORY

20111113 First version. Add height map for topsoil and depth map for ice. 20111114 It now takes a moon name and coordinates (and automatically figures out the SHA1 hash filename) Print table of material abundances. Flag exotic materials (such as 'disk8', block code 130) Eliminate dependence on rpmlib; start putting functions into sections; add map.128 Add several more bt_char definitions. Un-reverse the Y coordinate when generating pathname. This also entails changing the Y loop index in map.128. Add code to collect stats on an entire moon (calling map.x1 for each superblock). Add elevation stats. 20111115 Add command loop and clean up a lot of the top-level code. Start redoing the logic in mx1.gotbyte 20111116 Add 'info' and 'moon' commands; clean up input before parsing. 'bigmap' now shows your current location; movement commands (n, s, e, w) handle boundaries properly; add more hill height symbols. 20111119 Sort abundances; add wikitext formatting. 20111120 Generate better wikitext from %bt_name 20111121 'stats' commands now show negative elevations 20111122 More levels of ice thickness 20111123 Begin conversion of map.128 to more logical structure; add display strings for the rest of the material types. &find_a_dir now automatically finds the backup dir of the device containing the most recent saved game, and automatically selects that moon. As before, you can still start in a specific directory and/or specify the desired moon as a command-line argument, but both of these are now optional, and doing either or both of these will override the corresponing action of &find_a_dir. Add 'cs' (cross section) command. Add 'tun' (show tunnels) command, showing three zones of underground excavation using subtractive primary colors. 20111125 Flag astronaut lairs in map.128 display 20111127 Add altitude numbers to cross section 20111129 'cs' command now works in all 4 directions; add coordinates across the top of map and cs displays. Fix direction of coordinate labels for cross-sections. Add 'fl' command and &floats to start parsing the .dat files (I make some progress but there is still quite a way to go) 20111205 Restore column numbers in cross-section display.

20120117 With iOS 5.0.1, lunacraft files are no longer included in iTunes' backup. Until either Apple or the lunacraft developers do something about it, this script is no longer of much use. 20120123 Show directory even if device name not found

20120404 Add descriptions or tun and cs to help

20120807 Only print lair percentage when it's been computed. 20120906 Try a little harder to use files in the current directory: it now works if the current directory is a partial backup, as long as at least one moon has a 0_0.mw file. 20120916 To support my current "hollowing out a hill" project, I add options to set tunnel depth map thresholds. Tunnels are now drawn a 20120917 &map.x1 now displays pathname and filenames of the three save files for the superblock; this aids me in manual restore operations. 20120918 Print total number of blocks in stats command. Add mt_plant[], allow a "tunnel" to have a ceiling made of anything other than plant materials. 20121001 Improve formatting of 'fl' command and figure out most of the format of the 0_X_Y.dat files (which store mobs, minilights, and dropped objects). (I still need to figure out the values for Blue I, Green Mob, Turret and possibly others.) 20121002 Figure out a couple more of the entity type data values. 20121006 Display ice and sand differently if of nonstandard height: this makes my constructions and Brown Mob explosions show up on maps. 20121007 Add #plan# command. Display gravel, rock, etc in a mor easily contrasting black-on-gray for cross-section and plan views (this makes excavations much easier to pick out). 20121008 Add !! command; tweak display codes for a few material types. 20121009 Add "plan +N", "plan -N", and "plan N-M" syntax. 20121011 More tweaks to material types, etc. 20121012 Adjust tunnel height threshold test (was off by 1). Add &show.game_dat (incomplete). 20121015 &floats now identifies mobs by name. Add "mobs" command, and "allstats" now gives total numbers of each type of mob. Identify Blue I. 20121018 In mobs mode, allstats now just counts mobs (much faster) 20120119 In mobs mode, add colored tags in bigmap display. Fix off-by-1 errors in mob positioning. 20121020 #sgd# now parses out reals and shows the last three (the times and sky colour) 20121021 Report buried brown mobs. 20121024 Parse map data into columns and store in a 2-D array, then break the columns up to send to mx1.gotbyte. This refactoring will help in the future with scanning for diffs, and also makes it easy to print maps with north at the top if I ever wish to do so. 20121025 A series of changes to remove the cruft that was being used to produce cross-sections in different directions (it can now be done more simply). 20121029 Add #sgd# options to set color and event timer. 20121030 Add &regm and identify some object subtypes (such as minilight direction). Add #norm# and #objs# commands; the latter shows all objects along with mobs. 20121124 Replace g_sb_x with g_x1. 20130116 Today I discovered that when the game elapsed time goes above approximately 1960000, the game acts like you're holding Chronowinder permanently. Restarting the app does not help. By using Chronoslow you can run the game time backwards and restore normal behaviour. This might have been an intentional feature, to make the game harder after a player has been in the same world a long time, or it may be a bug in the (known to exist) algorithm that has the game timer tick faster during nighttime. This means that increasing the event timer isn't the best way to turn off mob timer autospawning; decreasing the game elapsed time will be better. 20130122 Add -h for help; A little refactoring to move towards being able to scroll by fractions of a superblock.
 * little* differently, but it shouldn't be noticable in most cases.

BUGS and TTD

Implement commands to manipulate the .dat file for a superblock: #clean# (turn disks into Topsoil hoverblocks) #kill# or #genocide# (turn mobs into hoverblocks:   buried brown mobs    all brown mobs    blue I's    friendly astros  #morph# (turn all astronauts into a particular color)  #summon# (turn mini-lights into mobs)    To summon a mob, place two blocks one on top of the other, holding 3 minilights, one pointing down and two pointing up. The bottom minilight will turn into a mob. To select what type of mob is summoned, the two blocks should be:    Cyan Stone: sky blue astro    Beryllium: friendly (bright green) astro    Refined Notchium: mint green astro **    Polymer: white astro    Sulphur Ore: yellow astro    Amethyst Ore: pink astro    Calcite: Giraffe    Xenostone: Small green mob    Dirt: Topsoil hoverblock    Oil Stone: Brown mob **    Blue Stone: Blue I **    ** When summoning a brown mob, Blue I, or friendly astro, it will warn and ask for confirmation first.

Add a command that displays (and possibly modifies) the contents of com.mooncraft.full.plist (which contains the terrain gen and creative/survival options for each moon)

Add a way to change the game elapsed time (similar to the two commands using &show_game_dat). Not important because I can do this in-game by simply holding Chronoslow for a long time with the iPad plugged into the charger.

Research ways to get backup data directly out of the iPad rather than from the iTunes backup directory. These are purported to exist (iFunBox: ifunboxmac.dmg; iExplorer: iExplorer-2.2.1.6.dmg) and allow the user to get at apps' files, but they might just be looking in the iTunes backup directory. If I'm lucky, these programs will permit me to get the maps out of Lunacraft on an iOS 5 device, so I could upgrade and this script would be more useful to others.

Locate backups on Windows. The two places to check are:

Windws XP: \Documents and Settings\(username)\Application Data\Apple Computer\MobileSync\Backup\

Windows Vista and Windows 7: \Users\(username)\AppData\Roaming\Apple Computer\MobileSync\Backup\

We can figure out the latest superblock using the same technique that find_a_dir is using to find the latest saved game. In worlds where I am doing a lot of mining and building, there always seem to be 9 superblocks whose mtime is most recent, in a 3x3 block, and the center one of these is the superblock I was working in. But in other worlds like the terrain stats worlds where I am just walking around, it hasn't even generated a　full 3x3 around the current player location. So it will require a fuzzy algorithm. I can probably get away with modifying map.128 to maintain a list of mtimes, look at the most recent 9 mtimes, keep only those whose mtime is within a few seconds of the very newest, average their X and Y superblock coordinates, and round off to an integer.

I can convert the binary plist files (0_game.dat) to XML using the command:

plutil -convert xml1 /path/to/file.plist

HEADER_END

use Digest::SHA1 qw(sha1_hex); use POSIX qw(floor);

$| = 1;

sub fl { my($x) = @_; return POSIX::floor($x); }


 * 1) Routines that find files and look for individual map superblocks
 * 1) Routines that find files and look for individual map superblocks
 * 1) Routines that find files and look for individual map superblocks

sub path_fn { my($path) = @_; my($p2, $fn);
 * 1) Turn an iOS device pathname into an SHA1 filename. It is assumed that
 * 2) the app is Mooncraft. For example:
 * 3)   print &path_fn("Documents/0_0_0.mw");
 * 4) prints
 * 5)   8c676d7863301fcc8574e0b4599c8046af72fc6e
 * 6) %%% I might need to use a "lunacraft" AppDomain in the future.
 * 1) %%% I might need to use a "lunacraft" AppDomain in the future.

$p2 = "AppDomain-com.mooncraft.full-". $path; # print "p2: $p2\n"; $fn = sha1_hex($p2); return $fn; }

sub superblock_path { my($moon, $x, $y, $ext) = @_; my($path, $fn);
 * 1) Get the filename for a superblock (given the moon, X and Y, and filename
 * 2) extension). For example:
 * 3)   print (&superblock_path("A", 0, 0, 'mw') . "\n");
 * 4) prints
 * 5)   Documents/0_0_0.mw

$path = "Documents/". sprintf("%d_%d_%d", $moon, 128*$x, 128*$y) . '.' . $ext; # print "path: $path\n"; return $path; }

sub superblock_fn { my($moon, $x, $y, $ext) = @_; my($path, $fn);
 * 1) Get the filename for a superblock (given the moon, X and Y, and filename
 * 2) extension). For example:
 * 3)   print &superblock_fn("A", 0, 0, 'mw');
 * 4) prints
 * 5)   8c676d7863301fcc8574e0b4599c8046af72fc6e

$path = &superblock_path($moon, $x, $y, $ext); $fn = &path_fn($path); return $fn; }

sub superblock_mw { my($moon, $x, $y) = @_;
 * 1) Get the filename for a superblock (given the moon, X and Y). For example:
 * 2)   print &superblock_mw("A", 0, 0);
 * 3) prints
 * 4)   8c676d7863301fcc8574e0b4599c8046af72fc6e

return(&superblock_fn($moon, $x, $y, 'mw')); }

sub sb_exists { my($moon, $x1, $y1) = @_; my($fn, $rv, $x, $y);
 * 1) Return true if a given superblock exists. This can be used to determine if
 * 2) a given moon has been created (pass 0 for X and Y) and also for mapping
 * 3) out the limits of the world.

$x = &fl($x1/128); $y = &fl($y1/128); $fn = &superblock_mw($moon, $x, $y); $rv = (-e $fn); return $rv; }

sub date3 { local($tm, $numeric) = @_; local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst); local($l, $fmt);
 * 1) Date routine used by show_device_info

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($tm); #                     YYYYMMDD.HH:MM:SS $fmt = $numeric ? "%04d%02d%02d.%02d%02d%02d" : "%04d%02d%02d.%02d:%02d:%02d"; $l = sprintf($fmt, 1900 + $year, $mon+1, $mday, $hour, $min, $sec);

return $l; }

sub path_mtime { local($path) = @_;
 * 1) Return modification time of a file.

return((stat($path))[9]); }

sub show_device_info { my $info_pn = "Info.plist"; my $status_pn = "Status.plist"; my($l, $state, $curdir);
 * 1) Display name and backup time of the iOS device whose data we are currently
 * 2) looking at.

$curdir = `pwd`; chomp $curdir;

if(!(-f $info_pn)) { print STDERR "Device name unknown (no '$info_pn' in the current dir)\n"; return; } if(!(-f $status_pn)) { print STDERR "Backup date unknown (no '$status_pn' in the current dir)\n"; return; } $g_device_name = ''; open(my $IN, $info_pn); $state = 0; while($l = <$IN>) { chomp $l; if (($state == 0) && ($l =~ m|\bDOCTYPE +plist\b|)) { $state = 1; }   if (($state == 1) && ($l =~ m| Device Name |)) { $state = 2; }   if (($state == 2) && ($l =~ m| (.+) |)) { $state = 3; $g_device_name = $1; } }  close $IN;

print " Directory: $curdir\n";

if ($g_device_name eq '') { print STDERR "Did not find device name in '$info_pn'.\n"; return; }

$g_device_date = &date3(&path_mtime($status_pn), 0);

print "Device name: $g_device_name\n"; print " Backed up: $g_device_date\n"; }

sub find_a_dir { my ($td, $i);
 * 1) Find a directory to work in.
 * 2) %%% We want to check the Windows locations too.

# First do a couple quick tests to see if the current directory appears # to be

# print (&path_fn("Library/Preferences/com.mooncraft.full.plist") . "\n"); if(-e (&path_fn("Library/Preferences/com.mooncraft.full.plist"))) { # Current directory is OK, just use it   return; }

for($i=0; $i<4; $i++) { # print (&path_fn("Documents/0_0_0.mw") . "\n"); if(-e (&path_fn("Documents/${i}_0_0.mw"))) { # Current directory is OK, just use it     return; } }

# We need to seek out an iOS device backup directory # Start in home dir chdir;

# Try to find iTunes device backups one dir at a time if ($td = "Library", (-d $td)) { chdir($td); } else { die("Could not find ~/$td\n"); }

if ($td = "Application Support", (-d $td)) { chdir($td); } else { die("Could not find ~/.../$td\n"); }

if ($td = "MobileSync", (-d $td)) { chdir($td); } else { die("Could not find ~/.../$td\n"); }

if ($td = "Backup", (-d $td)) { chdir($td); } else { die("Could not find ~/.../$td\n"); }

# Now we're in .../MobileSync/Backup. Look at all subdirs to find one # that has the Mooncraft prefs file. my $best_dt = 0; my $best_dir = ''; if (opendir(my $DIR, '.')) { my($bk, $moon); my @bks = readdir($DIR); closedir $DIR;

foreach $bk (@bks) { if (-d $bk) { for ($moon=0; $moon<4; $moon++) { my $fn = &path_fn("Documents/${moon}_game.dat"); if (-f "$bk/$fn") { my $tm = &path_mtime("$bk/$fn"); my $dt = &date3($tm); my $mn = chr(65+$moon); print "$dt Moon $mn, $bk\n"; if ($tm > $best_dt) { $best_dt = $tm; $best_dir = $bk; $best_moon = $mn; $best_save = $dt; }         }        }      }    }  }

if ($best_dir ne '') { chdir($best_dir); print "Using backup files from this device:\n"; &show_device_info; print "Because Moon $best_moon was saved at $best_save.\n"; $g_cur_moon = &cv_moon($best_moon); return; }

print STDERR "Please go into an iOS device backup directory\n"; exit -1; }

sub show_game_dat { my($moon, $prnt, $match_real, $subst_real) = @_; my($p1, $fn, $d1, $d2, $f1, $f2);
 * 1) Convert the N_game.dat file into ASCII and display its contents.
 * 1) Convert the N_game.dat file into ASCII and display its contents.
 * 1) Convert the N_game.dat file into ASCII and display its contents.

$f1 = sprintf("%d", $moon). "_game.dat"; $p1 = "Documents/". $f1; print "iOS path: $p1\n" if ($prnt);

$fn = &path_fn($p1); print "fn: $fn\n" if ($prnt);

if (!(-e $fn)) { print qq@ ${moon}_game.dat was not found (should be called '$fn') Perhaps this command will work if you run #mooncraft-map# within the directory containing the backup files. @;   return; }

print "$fn exists\n" if ($prnt);

$d1 = $ENV{"HOME"}. "/tmp"; if (!(-d $d1)) { print qq@ This command requires a directory at '$d1' which can be used to hold temporary files. @;   return; }

print "dir $d1 exists\n" if ($prnt);

$d2 = $d1. "/mooncraft-map"; if (-d $d2) { # Okay print "dir $d2 exists\n" if ($prnt); } elsif (-e $d2) { print "Want to create '$d2', but something else is already there!\n"; return; } else { print "mkdir $d2\n" if ($prnt); system("mkdir", $d2); }

# Make a copy $f2 = "$d2/mooncraft_$f1.plist"; print "cp $fn $f2\n" if ($prnt); system("cp", $fn, $f2);

# Convert plist to ASCII (XML) format. This command creates an output # file with the same name except with "plist" changed to "txt". print "plutil -convert xml1 -e txt $f2\n" if ($prnt); system("plutil", "-convert", "xml1", "-e", "txt", $f2); $f3 = $f2; $f3 =~ s/plist$/txt/;

if (!(-e $f3)) { print "Output file not found: $f3\n"; return; }

my ($made_change); $made_change = 0; my($IN, $OUT, $l, $v, $v1, $v2, $v3); open($IN, $f3); $f4 = "$d2/mod-$f1.txt"; open($OUT, "> $f4"); while($l = <$IN>) { if ($l =~ m| ([-.0-9]+) |) { $v = $1; $v1 = $v2; $v2 = $v3; $v3 = $v; print " Real: $v\n" if ($prnt); if ($v == $match_real) { $l =~ s| ([-.0-9]+) | $subst_real |; print "Substituting '$subst_real' for '$match_real'\n"; $made_change++; }   }

print $OUT $l; } close $IN; close $OUT; print "Output written to $f4\n" if ($prnt);

if ($prnt) { print " Next Event Time: $v1\n"; print "      Sky Colour: $v2\n"; print "    Current Time: $v3\n"; }

if ($made_change > 1) { print "Too many changes (matched $made_change reals); not writing output\n"; } elsif ($made_change) { # plutil -convert binary1 -e plist mod_0_game.dat.txt print "plutil -convert binary1 -e plist $f4\n" if ($prnt); system("plutil", "-convert", "binary1", "-e", "plist", $f4); $f5 = $f4; $f5 =~ s/txt$/plist/; if (-f $f5) { print "$f5 exists\n"; }

# Make a copy print "cp $f5 $fn\n" if ($prnt); system("cp", $f5, $fn); }

return(($v1, $v2, $v3)); }

$esc = "\033"; $c_bk_grn = $esc. "[0;30;42m";  # black on green $c_bk_yel = $esc. "[0;30;43m";  # black on yellow $c_bk_cyn = $esc. "[30;46m";    # black on cyan $c_bk_gry = $esc. "[0;1;30;47m"; # black on gray $c_bold  = $esc. "[0;1;30m";   # bold black on white $c_rd_blk = $esc. "[0;1;31;40m"; # red on black $c_rd_red = $esc. "[0;1;31;41m"; # red on red $c_rd_yel = $esc. "[0;1;31;43m"; # red on yellow $c_rd_wht = $esc. "[0;1;31;47m"; # red on white $c_gr_grn = $esc. "[0;32;42m";  # green on green $c_gr_gry = $esc. "[0;32;47m";  # green on white (really light gray) $c_ye_blk = $esc. "[0;1;33;40m"; # yellow on black $c_ye_grn = $esc. "[0;33;42m";  # yellow on green $c_ye_yel = $esc. "[0;33;43m";  # yellow on yellow $c_ye_gry = $esc. "[0;1;33;47m"; # yellow on gray $c_ye_wht = $esc. "[0;1;33m";   # yellow on white $c_bl_blk = $esc. "[0;34;40m";  # blue on black $c_bl_blu = $esc. "[0;34;44m";  # blue on blue $c_mg_blk = $esc. "[0;35;40m";  # magenta on black $c_mg_mag = $esc. "[0;35;45m";  # magenta on magenta $c_mg_gry = $esc. "[0;35;47m";  # magenta on gray $c_mg_wht = $esc. "[0;1;35m";  # magenta on white $c_cy_cyn = $esc. "[0;36;46m";  # cyan on cyan $c_cy_blu = $esc. "[0;1;36;44m"; # cyan on blue $c_cy_gry = $esc. "[0;1;36;47m"; # cyan on gray $c_cy_wht = $esc. "[0;1;36m";   # cyan on white $c_wt_wht = $esc. "[0;1;37m";   # white on white $c_wt_blk = $esc. "[0;37;40m";  # white on black $c_wt_red = $esc. "[0;1;37;41m"; # white on red $c_wt_grn = $esc. "[0;1;37;42m"; # white on green $c_wt_yel = $esc. "[0;1;37;43m"; # white on yellow $c_wt_blu = $esc. "[0;1;37;44m"; # white on blue $c_wt_mag = $esc. "[0;1;37;45m"; # white on magenta $c_wt_cyn = $esc. "[0;1;37;46m"; # white on cyan $c_wt_gry = $esc. "[0;1;37;47m"; # white on white (really light gray) $norm = $esc. "[0m"; $avc_match_pattern = "\033\[[0-9;]+m";
 * 1) Color modifiers
 * 1) Color modifiers


 * 1) Parse the mob data file
 * 1) Parse the mob data file

my $FLOATS_IN;

sub flval { my($b0, $b1, $b2, $b3, $pr) = @_; my($exp, $mant, $sign, $fval, $ff);
 * 1) Convert 4 bytes into a floating-point value by IEEE 32-bit, then print
 * 2) it out if the result is "reasonable"

$sign = ($b3 & 0x80) ? -1 : 1; $exp = ((($b3 & 0x7f) << 1) | ($b2 >> 7)) - 127; $mant = (($b2 & 0x7f) << 16) | ($b1 << 8) | $b0; $mant = 1.0 + ($mant / 8388608.0); # 2^23


 * 1) $exp = ((($b3 & 0x7f) << 4) | ($b2 >> 4)) - 1023;
 * 2) $mant = ((($b2 & 0x0f) | 0x10) << 16) | ($b1 << 8) | $b0;
 * 3) $mant = $mant / 1048576.0;

$fval = -9.9e99; # For some reason we need to divide all values by 2 $mant *= 0.5; if (($exp >= 0) && ($exp < 30)) { $fval = $sign * $mant * (1 << $exp); } elsif ($exp == -1) { $fval = $sign * $mant * 0.5; } elsif ($exp == -2) { $fval = $sign * $mant * 0.25; } elsif ($exp == -3) { $fval = $sign * $mant * 0.125; } elsif ($exp == -4) { $fval = $sign * $mant * 0.0625; } elsif ($exp == -5) { $fval = $sign * $mant * 0.03125; } elsif ($exp == -127) { $fval = 0; } else { # print sprintf("%02x %02x %02x %02x exp $exp mant $mant\n",   #   $b3, $b2, $b1, $b0); }

if ($fval > -9.0e98) { $ff = sprintf("%11.3f", $fval); print sprintf("%12s", $ff) if ($pr); } else { print sprintf(" %02x %02x %02x %02x", $b3, $b2, $b1, $b0) if ($pr); } return $fval; }

sub getbyte { my($l, $gg, $rv); $gg = 0; $rv = 0; if (read($FLOATS_IN, $l, 1)) { $gg = 1; $rv = ord($l); $g_bytes_p++; } return(($gg, $rv)); }

sub one_float { my($fl_gg, $pr) = @_; my($b0, $b1, $b2, $b3, $rv);

($fl_gg, $b0) = &getbyte; ($fl_gg, $b1) = &getbyte; ($fl_gg, $b2) = &getbyte; ($fl_gg, $b3) = &getbyte; if ($fl_gg) { $rv = &flval($b0, $b1, $b2, $b3, $pr); } return (($fl_gg, $rv)); }


 * 1) Look for floating-point values in a .dat file.
 * 2) For dropped objects the "angle" is an age in units of 1/20 second, i.e.
 * 3)   1200 per minute
 * 4) Ty field:
 * 5)   01 hoverblock (Fc = type of material; Ex=quantity; angle=age, they die when it gets to about 500)
 * 6)    4 giraffe    (Fc = health)
 * 7)    5 dropped object (Fc = type; Ex = quantity in stack)
 * 8)       25=mechanism
 * 9)       27=magnet
 * 10)       31=biogel
 * 11)       44=adhesive
 * 12)       46-63=disk
 * 13)       77=camera
 * 14)       80=turretV3
 * 15)       84=chronoboost
 * 16)    6 minilight  (Fc = direction (0=S, 1=W, 2=N, 3=E, 4=U, 5=D); Ex=0)
 * 17)    7 green mob  (Fc = health; Ex = 0 or 3?)
 * 18)    8 astronaut  (Fc = health; Ex = color (0=white, 1=green, 2=blue, 3=pink, 4=yellow))
 * 19)   10 brown mob  (Fc = health)
 * 20)   11 turret     (Fc = health; Ex = 4)
 * 21)   12 Blue I     (tags unknown, suspect: Fc = health; Ex = item stolen)
 * 22)    red  31  276.026
 * 23) orange  37  205.127
 * 24) yellow  36  149.275
 * 25)  green  30   95.412
 * 26)   blue  33   36.291
 * 1)  green  30   95.412
 * 2)   blue  33   36.291

sub regm { my($ix, $name, $tag, $show, $prio) = @_;

$mobname{$ix} = $name; $mobtag[$ix] = $tag; $showmob[$ix] = $show; $mobprio[$ix] = $prio; }

&regm(1, "Hoverblock"); &regm(4, "Giraffe   ", $c_wt_mag, 1, "p06 ${c_wt_mag}G$norm"); &regm(5, "Dropped Object"); &regm("5 31", "Biogel"); &regm("5 44", "Adhesive"); &regm("5 84", "Chronoboost"); for($i=46; $i<=63; $i++) { &regm("5 $i", "Disk"); } &regm(6, "Minilight"); &regm("6 0", "Minilight south"); &regm("6 1", "Minilight west"); &regm("6 2", "Minilight north"); &regm("6 3", "Minilight east"); &regm("6 4", "Minilight up"); &regm("6 5", "Minilight down"); &regm(7, "Small Green", $c_wt_grn, 1, "p05 ${c_wt_grn}g$norm"); &regm(8, "Astronaut ", $c_wt_cyn, 1, "p03 ${c_wt_cyn}A$norm"); &regm(10,"Brown Mob ", $c_wt_red, 1, "p02 ${c_wt_red}B$norm"); &regm(11,"Turret    ", $c_wt_yel, 1, "p04 ${c_wt_yel}T$norm"); &regm(12,"Blue I    ", $c_wt_blu, 1, "p01 ${c_wt_blu}I$norm");

sub floats { my($moon, $x1, $y1, $prnt, $match_type, $new_type) = @_; my($l, $i, $k, $fp, $b3, $rv, $sb_x, $sb_y); my($pos_we, $pos_h, $pos_ns, $rel_we, $rel_ns, $angle, $type); my($base_we, $base_ns); my %present;
 * 1) Parse the .dat file; store mobs in %mobs; if $prnt parameter is
 * 2) set, print the full list of objects.
 * 3) {%%% not yet implemented: Any objects that match match_type will get
 * 4) turned into new_type, with output written into float-out-tmp.dat}

$sb_x = &fl($x1/128); $sb_y = &fl($y1/128);

my $fn = &superblock_fn($moon, $sb_x, $sb_y, 'dat'); # $fn = &path_fn(sprintf("Documents/%d_game.dat", $moon)); if(!(-f $fn)) { print "Superblock ($sb_x, $sb_y) does not exist on moon $moon.\n" if ($prnt); return; }

%mobs = ; $mob_summary = ""; %present = ; $base_we = $sb_x * 128; $base_ns = $sb_y * 128;

print "Reading '$fn'...\n" if ($prnt); #         12      35.242      59.000      64.191     174.087 08 .  .  .  64 .  .  .  04 .  .  .  .  .  .  .  .  .  .  .   print "offset        W-E       height        N-S    age/angle Ty          Fc          Ex\n" if ($prnt);

open($FLOATS_IN, $fn); binmode($FLOATS_IN); my $gg = 1; $i = 0; $g_bytes_p = 0;

# Show the header print sprintf("%6d", $g_bytes_p) if ($prnt); print ("  " x 24) if ($prnt); while (($i < 12) && $gg) { ($gg, $l) = &getbyte; print sprintf(" %02x", $l) if ($prnt); $i++; } print "\n" if ($prnt);

# Show each object while($gg) { print sprintf("%6d", $g_bytes_p) if ($prnt); ($gg, $pos_we) = &one_float($gg, $prnt); ($gg, $pos_h) = &one_float($gg, $prnt); ($gg, $pos_ns) = &one_float($gg, $prnt); ($gg, $angle) = &one_float($gg, $prnt);

$i = 0; while (($i < 20) && $gg) { ($gg, $b3) = &getbyte; $idata[$i] = $b3; if ($b3 == 0) { print " . " if ($prnt); } else { print sprintf("%3d", $b3) if ($prnt); }     $i++; }   $type = $idata[0]; $attr_fc = $idata[4]; $tstr = $mobname{"$type $attr_fc"}; if ($tstr eq '') { $tstr = $mobname{$type}; }   if ($gg && $prnt) { print " $tstr"; }   print "\n" if ($prnt); if ($gg) { $pos_we = &fl($pos_we); $rel_we = $pos_we - $base_we; if ($rel_we>127) {$rel_we=127;} $pos_h = &fl($pos_h); $pos_ns = &fl($pos_ns); $rel_ns = $pos_ns - $base_ns; if ($rel_ns>127) {$rel_ns=127;} $angle = int($angle); if ($pos_h >= 0) { $mob_pop{"$type n"}++; $present{$mobprio[$type]} = 1; } else { $mob_pop{"$type b"}++; }     if ($show_all_objs || $showmob[$type]) { $tstr = $mobname{$type}; if ($pos_h >= 0) { if ($tstr eq '') { $mobs{"$pos_we $pos_ns"} = $c_wt_blk; } else { if ($show_all_objs && $mobtag[$type] eq '') { $mobs{"$pos_we $pos_ns"} = $c_wt_blk; } else { $mobs{"$pos_we $pos_ns"} = $mobtag[$type]; }         }        }        if ($tstr eq '') { $tstr = "*** UNKNOWN ***"; }       if ($pos_h >= 0) { $mob_summary .= " $tstr   ($rel_we, $rel_ns) @ $pos_h\n"; } else { $mob_summary .= " $tstr   ($rel_we, $rel_ns) @ $pos_h (buried)\n"; }     }    }  }

close $FLOATS_IN; print "\n" if ($prnt);

$i = 0; $rv = ""; foreach $k (sort (keys %present)) { if (($k ne '') && ($i < 2)) { $rv .= $k; $i++; } }  $rv =~ s/p[0-9][0-9] //g; while ($i < 2) { $rv .= ' '; $i++; }

return $rv; }


 * 1) Mapping at 1x1 scale
 * 1) Mapping at 1x1 scale
 * 1) Mapping at 1x1 scale

$hill_1 = ".."; $hill_2 = "::"; $hill_3 = "cc"; $hill_4 = "##"; $hill_5 = "MM"; $hill_6 = "${c_bk_gry}::$norm"; $hill_7 = "${c_bk_gry}xx$norm"; $hill_8 = "$c_bk_gry\@\@$norm"; $hill_9 = "$c_wt_blk##$norm"; $hill_A = "${c_wt_blk}xx$norm"; $hill_B = "${c_wt_blk}::$norm"; $hill_C = "${c_wt_blk} $norm"; $hill_D = "${c_mg_blk}..$norm"; $hill_E = "${c_mg_blk}::$norm"; $hill_F = "${c_mg_blk}cc$norm"; $hill_G = "${c_mg_blk}##$norm"; $hill_H = "${c_mg_blk}MM$norm";
 * 1) Various heights of hill terrain

$bt_char[0]   = "  ";                   # nothing / "air" $bt_char[1+0] = $hill_4;                # topdirt $bt_char[1+1] = "$c_rd_blk##$norm";     # normal dirt $bt_char[1+2] = "${c_cy_wht};:$norm";   # sand $sand_modified = "$c_cy_wht;$c_wt_blu:$norm"; # sand at a nonstandard height $bt_char[1+3] = "^_";                   # basalt rock $rock_modified = "$c_wt_gry^${norm}_"; # rock below ground level $bt_char[1+4] = " '";                   # snow $bt_char[1+5] = "${c_bk_gry}/\\$norm";    # gravel $bt_char[1+6] = "$c_bk_grn\@\@$norm";   # leaf $ice_1        = "${c_wt_cyn}XX$norm";   # thinnest ice $ice_2        = "$c_wt_cyn//$norm";     #. $ice_3        = "$c_wt_cyn,'$norm";     #. $ice_4        = "$c_bk_cyn  $norm";     # middle ice $ice_5        = "$c_bk_cyn,'$norm";     #. $ice_6        = "$c_bk_cyn//$norm";     #. $ice_7        = "${c_bk_cyn}XX$norm";   # thickest ice $ice_modified = "$c_wt_cyn/$c_wt_blu/$norm"; # ice at a nonstandard height $bt_char[1+7] = $ice_3;                 # ice $bt_char[1+8] = "$c_bk_cyn\?9$norm";    # "unknown" (looks like ice; symbol '?9'                                         #   for The Mysterious Number Nine) $bt_char[1+9] = "$c_wt_wht\:\:$norm";   # light $bt_char[1+10] = "${c_ye_gry}A${c_rd_wht}l$norm";  # Aluminum ore $bt_char[1+11] = "${c_mg_wht}Mo$norm";  # Molybdenum ore $bt_char[1+12] = "${c_ye_wht}Au$norm";  # gold ore $bt_char[1+13] = "${c_wt_gry}Ag$norm";  # silver ore $bt_char[1+14] = "C;";                  # Carbon ore $bt_char[1+15] = "$c_ye_gry||$norm";    # yellow crystal $bt_char[1+16] = "$c_mg_gry'.$norm";    # rubelite $bt_char[1+17] = "${c_wt_blk}\[\]$norm"; # hyperglass (See NOTE 1) $bt_char[1+18] = "Ma";                  # Magnetite $bt_char[1+19] = "${c_rd_blk}OS$norm";  # Oil Stone (like dirt but darker) $bt_char[1+20] = "";                  # Polymer $bt_char[1+21] = "Ra";                  # Refined Aluminum $bt_char[1+22] = "${c_bold}Ti$norm";    # Titanium ore $bt_char[1+23] = "Rt";                  # Refined Titanium $bt_char[1+24] = "$c_gr_gry'.$norm";    # Notchium ore $bt_char[1+25] = "${c_gr_gry}Rn$norm";  # Refined Notchium $bt_char[1+26] = "${c_bl_blk}\%\&$norm"; # blue crystal $bt_char[1+27] = "$c_bk_yel$norm";    # moon wood $bt_char[1+28] = "',";                  # whitestone (Calcite) $bt_char[1+29] = "$c_wt_blk',$norm";    # graphite $bt_char[1+30] = "$c_gr_grn##$norm";    # green stone $bt_char[1+31] = "$c_rd_red##$norm";    # red stone $bt_char[1+32] = "$c_mg_mag##$norm";    # purple (magenta) stone $bt_char[1+33] = "$c_bl_blu##$norm";    # blue stone $bt_char[1+34] = "$c_cy_cyn##$norm";    # cyan stone $bt_char[1+35] = "${c_ye_grn}\%\&$norm"; # blob stone $bt_char[1+36] = "$c_ye_yel##$norm";    # yellow stone $bt_char[1+37] = "$c_rd_yel##$norm";    # orange stone $bt_char[1+38] = "$c_rd_wht##$norm";    # brown stone $bt_char[1+39] = "${c_cy_blu}\%\&$norm"; # blue gravel $bt_char[1+41] = "$c_bk_yel\%\%$norm";  # neptunium $bt_char[1+42] = "$c_rd_yel\%\&$norm";  # colorwood $bt_char[130] = "??";                   # block type 130 (disk8)
 * 1) Different colors and symbols for the various materials
 * 2)   NOTE 1: For transparent materials, it would be nice to actually make them
 * 3)   transparent, by displaying a unique symbol for the transparent block ("[]"
 * 4)   for glass) combined with the VT color or whatever is under/behind the
 * 5)   glass. Thus, dirt under glass would be a red "[]" on black background.

$exotic = "$c_wt_mag\?\?$norm";

$depth_mod[1] = $c_wt_mag; # 0 0 1 $depth_mod[2] = $c_wt_yel; # 0 1 0 $depth_mod[3] = $c_wt_red; # 0 1 1 $depth_mod[4] = $c_wt_cyn; # 1 0 0 $depth_mod[5] = $c_wt_blu; # 1 0 1 $depth_mod[6] = $c_wt_grn; # 1 1 0 $depth_mod[7] = $c_wt_blk; # 1 1 1

$mt_common[1+0] = 1; # topdirt $mt_common[1+1] = 1; # dirt $mt_common[1+2] = 1; # sand $mt_common[1+3] = 1; # basalt $mt_common[1+5] = 1; # gravel $mt_common[1+7] = 1; # ice

$mt_plant[1+6] = 1; # leaf $mt_plant[1+9] = 1; # light $mt_plant[1+15] = 1; # yellow crystal $mt_plant[1+16] = 1; # rubelite $mt_plant[1+27] = 1; # moon wood $mt_plant[1+42] = 1; # colorwood

@pl_char = @bt_char; $pl_char[1+3] = "${c_bk_gry}^_$norm";    # basalt rock $pl_char[1+4] = "${c_bk_gry} '$norm";    # snow $pl_char[1+28] = "${c_wt_gry}',$norm"; # whitestone (Calcite)
 * 1) Special display for certain materials in plan and cross-section views

%bt_name = ( 0 => "topsoil",  1 => "dirt",  2 => "sand",  3 => "rock",  4 => "snow",  5 => "gravel",  6 => "leaf",  7 => "water",  8 => "unknown",  9 => "light", 10 => "aluminum", 11 => "molybdenum", 12 => "gold", 13 => "silver", 14 => "carbon", 15 => "yellow_crystal", 16 => "pink_crystal", 17 => "glass", 18 => "magnetite", 19 => "oil_stone", 20 => "polymer", 21 => "refined_aluminum", 22 => "titanium", 23 => "refined_titanium", 24 => "notchium", 25 => "refined_notchium", 26 => "blue_crystal", 27 => "moon_wood", 28 => "white_stone", 29 => "dark_stone", 30 => "green_stone", 31 => "red_stone", 32 => "purple_stone", 33 => "blue_stone", 34 => "cyan_stone", 35 => "blob_stone", 36 => "yellow_stone", 37 => "orange_stone", 38 => "brown_stone", 39 => "blue_gravel", 41 => "neptunium", 42 => "colorwood", 129=> "disk" );
 * 1) "name" field from items.txt. The index is off-by-one from what we need.

for($i=0; $i<=42; $i++) { $bt_mundane{$i} = 1; } $bt_mundane{40} = 0;
 * 1) All materials with a name except 129 are "normal" or "mundane" (not exotic)


 * 1)   Exceptions that remain in the list in mooncraft.wikia.com/wiki/Brown_Mob:
 * 2) Blue_Gravel -> Refined_Aluminum     bluegravel -> bt40 (undefined)
 * 3)   Colorwood -> Dirt                 colorwood -> bt43 (undefined)
 * 4)        Sand -> Light                     sand -> rock

$BT_ROCK = 3; $BT_POLYMER = 20;

sub mx1_start_column { # old vars $topblock = 0; $solidheight = 0; $saw_exotic = 0;

$v_base = 0;        # Vert. coord. of the base of this layer $v_top = 0;         # Vert. coord. of the top of this layer $m_type = $BT_ROCK+1; # Type of material of this layer $m_thick = 0;       # Thickness of this layer # (perhaps add if needed: u_top==v_base, and u_thick) $u_base = 0; $u_top = 0; $u_type = $BT_ROCK+1; # Type of material in the layer under this one }

sub tun_default { $g_d3_bot = 64-64; $g_d3_top = 64-25; $g_d2_bot = 64-25; $g_d2_top = 64-10; $g_d1_bot = 64-10; $g_d1_top = 127; }

sub mx1_gotbyte { my($x) = @_; my($i, $mt);

# print sprintf("%d\n", $x); if ($expect_len) { $expect_len = 0; # This is a length byte; next byte will be a blocktype $raw_len = $x;

# Types of maps I want: # normal (what it prints now) # plan view (horizontal cross-section) # diff one map file against another (older version), using cyan/magenta/ #   yellow to show places where blocks were added, removed, changed. # hdiff (height differences): show places where elevation differs #   from original (to detect brown mob explosions and player activity) # gdiff (ground differences): like hdiff but also detects changes #   to underground strata thicknesses (most difficult: anything    #    that's not a natural material, like plants and mineral    #    deposits, needs to be given the benefit of the doubt) # building: try to detect player, astronaut and mob building, and display #   them in three separate colors # X if lost: identify a single block of material placed on ground level, #   and normal ice and sand at ground level, for use in detecting an "X" #   placed by a lost player.

# Info for under-layer is old info for current layer $u_type = $m_type; $u_base = $v_base; $u_top = $v_top; # (If I add u_top and u_thick, they would be updated here)

# Update info for current layer $v_base = $v_top; $v_top = $v_base + $raw_len; $m_type = $raw_bt; $m_thick = $raw_len;

# mx1_tlen should be equal to v_top # if ($mx1_tlen != $v_top) { die("mx1_tlen != v_top\n"); }

# Update top block (air doesn't count) if ($m_type > 0) { $topblock = $m_type; $solidheight = $v_top; }

# Keep track of ice layer thickness. (Note: if there are multiple layers   # of ice, we only care about the topmost one) if ($m_type == (1+7)) { $ice_depth = $m_thick; }

# Make note of any unknown material types if (($m_type > 0) && ($bt_mundane{($m_type-1)} == 0)) { $saw_exotic = 1; }

# Note voids underground (for tunnel map coloring) I use three primary # colors (cyan/yellow/magenta) and their combinations to show the presence # of excavated space at each of three different bands of altitude. if (($u_type == 0)      && (($mt_plant[$m_type] == 0) # A void under anything other than plant # material is man-made || ($v_base < 64))      # Any void below "sea level" is definitely                                    # a tunnel    ) { if (    ($u_top > $g_d3_bot) && ($u_base < $g_d3_top)) { $depth_map |= 1; } elsif (($u_top > $g_d2_bot) && ($u_base < $g_d2_top)) { $depth_map |= 2; } elsif (($u_top > $g_d1_bot) && ($u_base < $g_d1_top)) { $depth_map |= 4; }   }

# Note columns of polymer if (($m_type == ($BT_POLYMER+1)) && ($m_thick > 10)) { $astro_lair = 1; }

# Accumulate statistics $bt_total{$m_type} += $m_thick;

# Save data for cross-section if ($mx1_coord_y == $cs_coord) { # Fill part of this column (from v_base to v_top) with block data for($i=$v_base; $i<$v_top; $i++) { $i1 = $i + 1; $cs_data{"$mx1_coord_x $i1"} = $pl_char[$m_type]; }   }

# Save data for plan view if ($g_planview && ($plan_coord > $v_base) && ($plan_coord <= $v_top)) { $plan_output = $pl_char[$m_type]; }

# See if we've reached 127 if ($v_top == 127) { # It's now time to determine what to display based on what type of     # material is topmost $elev = $solidheight - 64; # Height of highest non-air block, relative to                                # normal ground level $height_freq[$solidheight]++; if ($saw_exotic) { $opt = $exotic; } elsif($bt_char[$topblock] ne '') { if ($topblock == 1) { # Top is plain dirt: use height map if ($solidheight > 92) { $opt = $hill_H; } elsif ($solidheight > 90) { $opt = $hill_G; } elsif ($solidheight > 88) { $opt = $hill_F; } elsif ($solidheight > 86) { $opt = $hill_E; } elsif ($solidheight > 84) { $opt = $hill_D; } elsif ($solidheight > 82) { $opt = $hill_C; } elsif ($solidheight > 80) { $opt = $hill_B; } elsif ($solidheight > 78) { $opt = $hill_A; } elsif ($solidheight > 76) { $opt = $hill_9; } elsif ($solidheight > 74) { $opt = $hill_8; } elsif ($solidheight > 72) { $opt = $hill_7; } elsif ($solidheight > 70) { $opt = $hill_6; } elsif ($solidheight > 68) { $opt = $hill_5; } elsif ($solidheight > 67) { $opt = $hill_4; } elsif ($solidheight > 66) { $opt = $hill_3; } elsif ($solidheight > 65) { $opt = $hill_2; } else { # Show normal dirt $opt = $hill_1; }         if ($elev > 0) { $elev_freq[$elev]++; }

} elsif ($topblock == (1+2)) { # sand $opt = ($elev != 0) ? $sand_modified : $bt_char[$topblock];

} elsif ($topblock == (1+3)) { # rock $opt = ($elev < 0) ? $rock_modified : $bt_char[$topblock];

} elsif ($topblock == (1+7)) { # Top is ice: use depth map if ($ice_depth > 12) { $opt = $ice_7; } elsif ($ice_depth > 10) { $opt = $ice_6; } elsif ($ice_depth > 8) { $opt = $ice_5; } elsif ($ice_depth > 6) { $opt = $ice_4; } elsif ($ice_depth > 4) { $opt = $ice_3; } elsif ($ice_depth > 2) { $opt = $ice_2; } else { $opt = $ice_1; }         # Change ice colour if it the surface is not at the standard height if ($elev != 0) { $opt = $ice_modified; }         if ($elev > 0) { $elev_freq[$elev]++; }       } else { # Top is something else: yellowcrystal, leaf, light fruit, exposed # gravel from a brown mob explosion, etc.         $opt = $bt_char[$topblock]; }     } else { $opt = sprintf("%2d", $topblock); }

if ($show_tunnels) { # Color only indicates tunnels $opt =~ s/$avc_match_pattern//g; if ($depth_map) { $opt = $depth_mod[$depth_map]. $opt. $norm; }     } elsif ($show_mobs) { # Color only indicates mobs $opt =~ s/$avc_match_pattern//g; $mt = $signal_mobs; if ($mt ne '') { $opt = $mt. $opt. $norm; }     }

# Override for plan view if ($g_planview) { $opt = $plan_output; }

# Append this block to the existing output for this row $output = $output. $opt;

# Begin a new column of blocks &mx1_start_column; $topblock = 0; $solidheight = 0; $depth_map = 0; $saw_exotic = 0; $mx1_coord_x++; # If we are printing normal map output, emit the output line now. if ($mx1_coord_x >= 128) { if ($mx1_print) { # Determine what coordinate to show at the right end of the line my $c1 = ($topdir =~ m/[nw]/) ? (127 - $mx1_coord_y) : $mx1_coord_y; print "$output $c1\n"; }       $output = ""; $mx1_coord_x = 0; $mx1_coord_y++; }   } elsif ($v_top > 127) { # Column of blocks is too tall. This test is redundant now, because we     # parse the columns in map.x1      print STDERR "tlen==$v_top > 128!\n"; exit(-1); } } else { $expect_len = 1; # This is a blocktype; next byte will be a length # Take note of the block type $raw_bt = $x; } } # End of mx1.gotbyte


 * 1) Here is the old code:
 * 2) sub mx1_do_byte
 * 3)   my($token) = @_;
 * 4)   if ($token =~ m/^ *$/) {
 * 5)   } else {
 * 6)     &mx1.gotbyte($token);
 * 7)   }
 * }
 * 1) my($token);
 * 2) open(my $IN, "od -A n -t u1 -v '$fn' |");
 * 3) while($l = <$IN>) {
 * 4)   foreach $token (split(/ +/, $l)) {
 * 5)     &mx1_do_byte($token);
 * 6)   }
 * }
 * 1)     &mx1_do_byte($token);
 * 2)   }
 * }

sub map_x1 { my($moon, $x1, $y1, $print, $cs_dir, $cs_at, $plan) = @_; my($v, $h, $l, $sb_x, $sb_y, $base_we, $base_ns, $pos_we, $pos_ns);
 * 1) Generate and print a map at 1x scale (each block is two characters
 * 2) in output). This requires the terminal to be at least 257 columns wide.
 * 3) %%% In future, I can display only as much of the map as fits in the current
 * 4) terminal width. See $screen_size variable in #mira#.

$sb_x = &fl($x1/128); $sb_y = &fl($y1/128); $base_we = 128 * $sb_x; $base_ns = 128 * $sb_y;

$mx1_print = $print;

if(!(&sb_exists($moon, 0, 0))) { print "Moon $moon does not exist on this device.\n"; return; }

if(!(&sb_exists($moon, $x1, $y1))) { print "Superblock ($sb_x, $sb_y) does not exist on moon $moon.\n"; return; }

# If showing mobs, scan the .dat file to find out where they are. if ($show_mobs) { &floats($moon, $sb_x*128, $sb_y*128, 0); }

my($fn, $f2, $f3); $fn = &superblock_path($moon, $sb_x, $sb_y, 'mw'); print "Pathname: $fn" if ($print);

$fn = &superblock_mw($moon, $sb_x, $sb_y); $f2 = &superblock_fn($moon, $sb_x, $sb_y, 'hm'); $f3 = &superblock_fn($moon, $sb_x, $sb_y, 'dat'); print "; files: $fn $f2  $f3 (mw, hm, dat)\n" if ($print);

# We already checked to see if the superblock exists # print "Reading '$fn'...\n";

&mx1_start_column; # Init all the vars for parsing a single column $mx1_coord_x = 0; # Start at one end of a south-to-north row $mx1_coord_y = 0;

# Set up cross-section %cs_data = ; # cs_dir will specify n, s, e or w if ($cs_at ne '') { $cs_coord = $cs_at; }

# Set up plan view if ($plan ne '') { $g_planview = 1; $plan_coord = $plan; } else { $g_planview = 0; }

# Print coordinate header if ($mx1_print || ($cs_at ne '')) { # Print header $y2 = 100; # Start by printing the 100's digit for($v=2; $v>=0; $v -= 1) { for($h=127; $h>=0; $h -=1) { $x2 = ($topdir =~ m/[sw]/) ? (127-$h) : $h; if (int($x2/$y2) > 0) { print((int(($x2 / $y2)) % 10) . " "); } else { print " "; }     }      print "\n"; $y2 = $y2 / 10; # Next print the 10's digit, then the 1's digit } }

# Read in the binary data and collect into column-strings $expect_len = 0; # First byte of data in the file is a blocktype my($i, $c_ew, $c_ns, $j, $astr, $cht, $c); my @md; @md = ; open(my $IN, $fn); binmode($IN); $i = 0; $astr = ''; $cht = 0; $c_ns = 0; $c_ew = 0; while(read($IN, $l, 1)) { # We have the beginning of a column-string $astr = $l; # Get the first length read($IN, $l, 1); $astr .= $l; $cht = ord($l); while(($cht < 127) && read($IN, $l, 1)) { $astr .= $l; read($IN, $l, 1); $astr .= $l; $cht += ord($l); }

if ($cht > 127) { print "i == $i, bad column height $cht\n"; exit(-1); }

# We have a good column $md[$c_ew][$c_ns] = $astr; $i++; # The .mw files order the columns in north-south rows, starting with the # easternmost row; each row starts with the southmost column. So as we read # the data, out inner loop is the N-S coordinate (increasing towards the   # north) and the outer loop is the E-W coordinate (increasing towards the    # west). $c_ns++; if ($c_ns > 127) { $c_ew++; $c_ns = 0; } }  close $IN; if ($i != 16384) { print "i == $i, wrong number of columns\n"; exit(-1); }

# Send the column-strings to the map parsing. The loop index order is # changed to get maps in different orientations if ($topdir eq 'e') { for($c_ew = 0; $c_ew <= 127; $c_ew++) { for($c_ns = 127; $c_ns >= 0; $c_ns--) { $pos_we = $base_we + $c_ew; $pos_ns = $base_ns + $c_ns; $signal_mobs = $mobs{"$pos_we $pos_ns"}; foreach $c (split (//, $md[$c_ew][$c_ns])) { &mx1_gotbyte(ord($c)); }     }    }  } elsif ($topdir eq 'w') { for($c_ew = 127; $c_ew >= 0; $c_ew--) { for($c_ns = 0; $c_ns <= 127; $c_ns++) { $pos_we = $base_we + $c_ew; $pos_ns = $base_ns + $c_ns; $signal_mobs = $mobs{"$pos_we $pos_ns"}; foreach $c (split (//, $md[$c_ew][$c_ns])) { &mx1_gotbyte(ord($c)); }     }    }  } elsif ($topdir eq 'n') { for($c_ns = 127; $c_ns >= 0; $c_ns--) { for($c_ew = 127; $c_ew >= 0; $c_ew--) { $pos_we = $base_we + $c_ew; $pos_ns = $base_ns + $c_ns; $signal_mobs = $mobs{"$pos_we $pos_ns"}; foreach $c (split (//, $md[$c_ew][$c_ns])) { &mx1_gotbyte(ord($c)); }     }    }  } else { # south for($c_ns = 0; $c_ns <= 127; $c_ns++) { for($c_ew = 0; $c_ew <= 127; $c_ew++) { $pos_we = $base_we + $c_ew; $pos_ns = $base_ns + $c_ns; $signal_mobs = $mobs{"$pos_we $pos_ns"}; foreach $c (split (//, $md[$c_ew][$c_ns])) { &mx1_gotbyte(ord($c)); }     }    }  }

# Show cross-section output if ($cs_at ne '') { for($v=127; $v>0; $v-=1) { for($h=0; $h<128; $h++) { print $cs_data{"$h $v"}; }     print " $v\n"; } }

if ($show_mobs && $print) { print $mob_summary; } }

sub clear_abundance { %bt_total = ; @elev_freq = ; @height_freq = ; }
 * 1) Clear abundance and other stats

sub report_abundance { my($i, $baseline, $fs, $nm, $tt); $baseline = 0; for($i=1; $i<256; $i++) { $baseline += $bt_total{$i}; } if ($g_stats_fmt == 1) { print " BT -name- abundance"; } else { print "{|\n"; print "|+ Material Abundances\n"; print "! blocktype !! name !! abundance"; } $tt = 0; foreach $i (sort {$bt_total{$b} <=> $bt_total{$a}} (keys %bt_total)) { if (($i > 0) && ($bt_total{$i} > 0)) { print (($g_stats_fmt == 1) ? "\n" : "\n|-\n"); $fs = ($g_stats_fmt == 1) ? "%3d %22s %9d" : "| %3d || %22s || %9d"; $nm = $bt_name{($i-1)}; if ($g_stats_fmt == 1) { $nm =~ s/^\[\(.+)\]\]/$1/;       $nm =~ s/_//g;      } else {        if(!($nm =~ m/\[\[/)) {          $nm =~ s/^([a-z])/uc($1)/e;          $nm =~ s/_([a-z])/'_'.uc($1)/e;          $nm = '[[' . $nm . '';        }      }      print sprintf($fs, $i, $nm, $bt_total{$i});      $tt += $bt_total{$i};      $ppm = $bt_total{$i} * 1000000.0 / $baseline;      if ($ppm < 3000) {        $fs = ($g_stats_fmt == 1) ? " %7.2f PPM" : " || %7.2f PPM";        print sprintf($fs, $ppm);      } else {        $ppm /= 10000.0; # Convert from parts per million to percent        $fs = ($g_stats_fmt == 1) ? "   %5.2f %%" : " ||   %5.2f %%";        print sprintf($fs, $ppm);      }    }  }  print (($g_stats_fmt == 1) ? "\n" : "\n|}\n");
 * 1) Print out a simple report of material abundances

print "Total: $tt blocks.\n";

print "elev. frequency\n"; # Show stuff that ends below normal surface level # %%% 20121007: I don't know why I need both height_freq and elev_freq for($i=0; $i<=64; $i++) { if($height_freq[$i]) { print sprintf("h %3d %d\n", $i - 64, $height_freq[$i]); } }  # Handle elevations above ground level for($i=0; $i<=64; $i++) { if($elev_freq[$i]) { print sprintf("e %2d  %d\n", $i, $elev_freq[$i]); } } }


 * 1) Mapping at superblock (128x scale)
 * 1) Mapping at superblock (128x scale)
 * 1) Mapping at superblock (128x scale)

sub map_128 { my($moon, $survey_all) = @_; my %xy_defined = ; # Superblocks we have found my %xy_checked = ; # Superblocks we have checked for neighbors my($k, $sb_x, $sb_y, $tag); my $defcnt = 0;
 * 1) Generate an overview map of an enire moon (128x scale: each superblock is
 * 2) just 2 characters).
 * 3) The second parameter $survey_all is used for the 'allstats' command,
 * 4) which takes stats on materials or mobs and tags the map display to
 * 5) show the locations of astronaut lairs, mobs, and/or the current map
 * 6) position.
 * 1) position.

if (!(&sb_exists($moon, 0, 0))) { print "map_128: Moon '$moon' does not exist on this device.\n"; return; }

%mob_pop = ; $xy_defined{"0,0"} = 1; $defcnt++; my $gg = 1; while($gg) { $gg = 0; my $nk; foreach $k (keys %xy_defined) { if(!($xy_checked{$k})) { if ($k =~ m|^(-?[0-9]+),(-?[0-9]+)$|) { $sb_x = $1; $sb_y = $2; } else { print STDERR "key parse error '$k'\n"; exit(-1); }

if (&sb_exists($moon, 128*($sb_x-1), 128*$sb_y)) { $nk = ($sb_x-1). "," . $sb_y; if (!($xy_defined{$nk})) { # We found a new block $xy_defined{$nk} = 1; $defcnt++; $gg = 1; }       }        if (&sb_exists($moon, 128*($sb_x+1), 128*$sb_y)) { $nk = ($sb_x+1). "," . $sb_y; if (!($xy_defined{$nk})) { # We found a new block $xy_defined{$nk} = 1; $defcnt++; $gg = 1; }       }        if (&sb_exists($moon, 128*$sb_x, 128*($sb_y-1))) { $nk = $sb_x. "," . ($sb_y-1); if (!($xy_defined{$nk})) { # We found a new block $xy_defined{$nk} = 1; $defcnt++; $gg = 1; }       }        if (&sb_exists($moon, 128*$sb_x, 128*($sb_y+1))) { $nk = $sb_x. "," . ($sb_y+1); if (!($xy_defined{$nk})) { # We found a new block $xy_defined{$nk} = 1; $defcnt++; $gg = 1; }       }        $xy_checked{$k} = 1; }   }  }

my($xmin, $xmax, $ymin, $ymax); $xmin = $xmax = $ymin = $ymax = 0; foreach $k (keys %xy_defined) { if ($k =~ m|^(-?[0-9]+),(-?[0-9]+)$|) { $sb_x = $1; $sb_y = $2; if ($sb_x < $xmin) { $xmin = $sb_x; } if ($sb_x > $xmax) { $xmax = $sb_x; } if ($sb_y < $ymin) { $ymin = $sb_y; } if ($sb_y > $ymax) { $ymax = $sb_y; } } }

$mn_let = chr(65+$moon); if ($survey_all) { &clear_abundance; print " Collecting stats (please wait)...\n"; } else { print " Superblock map for moon $mn_let (East is UP, currently at 'XX'):\n"; }

$spc = "." x (2*($ymax - $ymin + 1) - 4); if ($show_mobs) { $spc =~ s/\.\./ ../g; $spc .= ' '; } print sprintf("     %2d%s%2d\n", $ymax, $spc, $ymin); $num_lairs = 0; $tag = ' '; for($sb_x=$xmin; $sb_x<=$xmax; $sb_x++) { print sprintf("%4d ", $sb_x); for($sb_y=$ymax; $sb_y>=$ymin; $sb_y--) { if ($xy_defined{"$sb_x,$sb_y"}) { $astro_lair = 0; $tag = ' '; if ($show_mobs) { $tag = &floats($moon, $sb_x*128, $sb_y*128, 0); } elsif ($survey_all) { &map_x1($moon, $sb_x*128, $sb_y*128, 0, , , ''); if ($astro_lair) { $num_lairs++; }       }        if ($tag ne '  ') { # Already set by floats } elsif (($sb_x == &fl($g_x1/128)) && ($sb_y == &fl($g_y1/128))) { # Show current position $tag = $astro_lair ? "X*" : "XX"; } elsif ($astro_lair) { $tag = "**"; } else { $tag = "[]"; }     } else { $tag = " :"; }     if ($show_mobs) { $tag .= ' '; }     print $tag; }   print "\n"; }

print sprintf(   "World is %d superblocks from east to west, and %d from north to south.\n",    $xmax-$xmin+1, $ymax-$ymin+1); print sprintf(   "Total area: %d superblocks (%.1f sq km), %d blocks",    $defcnt, $defcnt / 9.23, 16384*$defcnt); if ($survey_all) { print sprintf(", lairs %d%%", 100.0 * $num_lairs / $defcnt); } print "\n"; if ($survey_all) { if ($show_mobs) { print " ID  Pop.  Description\n"; foreach $k (sort {$a <=> $b} (keys %mob_pop)) { my ($type, $buried); if ($k =~ m|^([0-9]+) ([nb])$|) { $type = $1; $buried = $2; if ($buried eq 'n') { print sprintf(" %2d %4d   %s\n",              $type, $mob_pop{$k}, $mobname{$type}); } else { print sprintf(" %2d %4d   %s (buried)\n",              $type, $mob_pop{$k}, $mobname{$type}); }       } else { print " ?? $k $mob_pop{$k}\n"; }     }    } else { &report_abundance; } } } # End of map.128


 * 1) Other misc routines
 * 1) Other misc routines
 * 1) Other misc routines

sub cv_moon { my($letter) = @_; my($moon);

$letter = uc($letter); if ($letter eq 'A') { $moon = 0; } elsif ($letter eq 'B') { $moon = 1; } elsif ($letter eq 'C') { $moon = 2; } elsif ($letter eq 'D') { $moon = 3; } else { print "Moon must be A, B, C or D (got '$letter')\n"; return -1; } return $moon; }

sub try_move { my($dx, $dy) = @_; my($tx, $ty);

$tx = $g_x1 + $dx; $ty = $g_y1 + $dy; if(!(&sb_exists($g_cur_moon, $tx, $ty))) { print "Superblock ($tx, $ty) does not exist on this moon.\n"; return -1; } $g_x1 = $tx; $g_y1 = $ty; return 0; }

sub tunset { print "Maps will now be colored to show "; if ($show_tunnels) { print "tunnels ($g_d3_bot-$g_d3_top $g_d2_bot-$g_d2_top $g_d1_bot-$g_d1_top)"; } else { print "surface height and materials"; } print ".\n"; }


 * 1) Main command loop
 * 1) Main command loop
 * 1) Main command loop

while ($arg = shift) { if ($arg =~ m/^[-]?[-]h(elp)?$/) { print $help; exit 0; } elsif ($arg =~ m/^ *[abcd] *$/i) { $t = &cv_moon($arg); if ($t < 0) { print "Moon must be A, B, C or D (got '$arg')\n"; exit -1; }   $g_cur_moon = $t; } else { print "Argument '$arg' not understood.\n"; exit -1; } }

&find_a_dir;

&show_device_info;

$banner = qq@ [][]   []  []    [][]  []  []    [][]  []  []  [][][]  []      [][][]  []  [][]    [][][]  []      [][][]  []  []  []  []  []  []      []  []  []  []  []  []  []    [][]  []  []    [][]

Robert Munafo's Companion for MOONCRAFT @; $banner =~ s/\[\]/$c_cy_blu\[\]$norm/g; print "$banner\n";

&tun_default; $topdir = 'n';

$main_command_loop = 1; $last_good_cmd = 'help'; while($main_command_loop) { print "Current moon: Moon ". (chr(65+$g_cur_moon)); if ($g_x1 ne '') { print ("; superblock: (" . &fl($g_x1/128). ", " . &fl($g_y1/128). ")"); }  print ".\n"; print "Enter command, or HELP for a list of commands.\n"; print "cmd> "; $cmd = <>; chomp $cmd; $cmd = lc($cmd); $cmd =~ s/[ \t]+/ /g; $cmd =~ s/^ *//; $cmd =~ s/ *$//;

if ($cmd eq '!!') { $cmd = $last_good_cmd; print "Command: '$cmd'\n"; } else { print "\n"; } $syntax_ok = 1;

if (0) { } elsif (($cmd eq '?') || ($cmd eq 'help')) { print qq@ Available commands:

info     Show name and backup date/time of the iOS device bigmap   Show a superblock map of the whole world map 0 -1 Display a map of superblock (0, -1) map      Display the same superblock again top e    Put east at top of map cs 127 w Show cross-section at coordinate 127 looking west (this also puts            west at the top of the map for subsequent #map# commands) east     (or north, west, south) Go one superblock east and show map again plan 65    Show a plan view (horizontal cross-section) at height 65 plan 90-60 Show a series of plan views from height 90 down to 60 tun                   Toggle display of tunnels by color-keyed depth mapping tun 58-61 63-66 68-71 Set specific ranges for tunnel depth map tun default           Set default ranges for tunnel depth map mobs                  Toggle display of mobs (color-keyed) sgd                   Show game.dat sgd color 123.45      Set sky colour sgd timer 123456789   Set game event timer stats    Show material abundances for current superblock allstats Show material abundances for the entire world !!       Repeat previous command quit     (or q) Exit this awexome program

@;   $syntax_ok = 0; # This makes it retain the previous command } elsif ($cmd eq 'allstats') { &map_128($g_cur_moon, 1); } elsif ($cmd eq 'bigmap') { &map_128($g_cur_moon, 0); } elsif ($cmd =~ m|^cs +([0-9]+) +([nsew])$|) { # Do a cross-section $p1 = $1; $p2 = $2; $topdir = $p2; if ($topdir =~ m/[wn]/) { $p1 = 127 - $p1; }   &map_x1($g_cur_moon, $g_x1, $g_y1, 0, 'e', $p1, ''); } elsif (($cmd eq "e") || ($cmd eq "east")) { if (&try_move(-128, 0) == 0) { &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } } elsif ($cmd eq "fl") { &floats($g_cur_moon, $g_x1, $g_y1, 1); } elsif ($cmd eq "info") { &show_device_info; } elsif ($cmd =~ m|^map +(-?[0-9]+) +(-?[0-9]+)$|) { $g_x1 = $1 * 128; $g_y1 = $2 * 128; &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } elsif ($cmd eq "map") { # Use the same X and Y as before &map_x1($g_cur_moon, $g_x1, $g_y1, 1);

} elsif ($cmd eq "mobs") { $show_mobs = (!($show_mobs)); if ($show_mobs) { $show_tunnels = 0; }   &map_x1($g_cur_moon, $g_x1, $g_y1, 1);

} elsif ($cmd eq "norm") { $show_mobs = 0; $show_all_objs = 0; $show_tunnels = 0; &map_x1($g_cur_moon, $g_x1, $g_y1, 1);

} elsif ($cmd eq "objs") { $show_all_objs = (!($show_all_objs)); if ($show_all_objs) { $show_mobs = 1; $show_tunnels = 0; }   &map_x1($g_cur_moon, $g_x1, $g_y1, 1);

} elsif ($cmd =~ m/^moon ([a-d])$/) { $t = $1; $t = &cv_moon($t); if ($t < 0) { print "Moon must be A, B, C or D\n"; } else { $g_cur_moon = $t; print "Switching to moon ". (chr(65+$g_cur_moon)). ".\n"; $g_x1 = $g_y1 = 0; print "X and Y are now set to ($g_x1, $g_y1)\n"; } } elsif (($cmd eq "n") || ($cmd eq "north")) { if (&try_move(0, 128) == 0) { &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } } elsif ($cmd =~ m|^plan +([-+]?[0-9]+)$|) { # Do a plan view $p1 = $1; if ($p1 =~ m/^[+]([0-9]+)$/) { $p1 = $prev_plan_coord + $1; } elsif ($p1 =~ m/^[-]([0-9]+)$/) { $p1 = $prev_plan_coord - $1; }   $p1 += 0; if (($p1 >= 0) && ($p1 <= 127)) { $prev_plan_coord = $p1; &map_x1($g_cur_moon, $g_x1, $g_y1, 1, , , $p1); } else { print "plan coordinate must be in range (0 .. 127).\n"; } } elsif ($cmd =~ m|^plan +([0-9]+) *[-] *([0-9]+)$|) { # Do a series of plan views $p1 = $1; $p2 = $2; if ($p1 < $p2) { # Upward loop for ($i = $p1; $i <= $p2; $i++) { print "\n\n\n"; print ((" " x 120) . "Plan view at level $i\n"); &map_x1($g_cur_moon, $g_x1, $g_y1, 1, , , $i); }   } else { # Downward loop for ($i = $p1; $i >= $p2; $i--) { print "\n\n\n"; print ((" " x 120) . "Plan view at level $i\n"); &map_x1($g_cur_moon, $g_x1, $g_y1, 1, , , $i); }   }  } elsif (($cmd eq 'q') || ($cmd eq 'quit')        || ($cmd eq 'exit') || ($cmd eq 'bye')) {   $main_command_loop = 0; } elsif (($cmd eq "s") || ($cmd eq "south")) { if (&try_move(0, -128) == 0) { &map_x1($g_cur_moon, $g_x1, $g_y1, 1); }

} elsif ($cmd eq "sgd") { ($gd_timer, $gd_color, $gd_curtime) = &show_game_dat($g_cur_moon); print qq@"got: Timer: $gd_timer  Color: $gd_color   Time: $gd_curtime @;

} elsif ($cmd =~ m|^sgd +color +([.0-9]+)$|) { # Set sky colour $new_color = $1; ($gd_timer, $gd_color, $gd_curtime) = &show_game_dat($g_cur_moon); &show_game_dat($g_cur_moon, 1, $gd_color, $new_color);

} elsif ($cmd =~ m|^sgd +timer +([.0-9]+)$|) { # Set event timer $new_timer = $1; ($gd_timer, $gd_color, $gd_curtime) = &show_game_dat($g_cur_moon); &show_game_dat($g_cur_moon, 1, $gd_timer, $new_timer);

} elsif ($cmd eq "stats") { &clear_abundance; &map_x1($g_cur_moon, $g_x1, $g_y1, 0); &report_abundance;

} elsif ($cmd =~ m|^top +([nsew])$|) { # Set top direction $topdir = $1; print "Set top direction to '$topdir'\n"; &map_x1($g_cur_moon, $g_x1, $g_y1, 1);

} elsif ($cmd =~ m/^tun ([0-9]+)-([0-9]+) +([0-9]+)-([0-9]+) +([0-9]+)-([0-9]+)/) { $g_d3_bot = $1; $g_d3_top = $2; $g_d2_bot = $3; $g_d2_top = $4; $g_d1_bot = $5; $g_d1_top = $6; $show_tunnels = 1; $show_mobs = 0; $show_all_objs = 0; &tunset; &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } elsif ($cmd =~ m/^tun .*[0-9]/) { print "Syntax should be like: 'tun 1-57 58-59 60-127'\n"; } elsif ($cmd =~ m/^tun +default/) { # Show tunnels with default height mapping $show_tunnels = 1; $show_mobs = 0; $show_all_objs = 0; &tun_default; &tunset; &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } elsif ($cmd =~ m/^tun/) { $show_tunnels = (!($show_tunnels)); if ($show_tunnels) { $show_mobs = 0; $show_all_objs = 0; }   &tunset; &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } elsif (($cmd eq "w") || ($cmd eq "west")) { if (&try_move(128, 0) == 0) { &map_x1($g_cur_moon, $g_x1, $g_y1, 1); } } elsif ($cmd eq 'xyzzy') { print "Nothing interesting happens.\n"; } else { print "What?\n"; $syntax_ok = 0; } if ($syntax_ok) { $last_good_cmd = $cmd; }}