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
*little* differently, but it shouldn't be noticable in most cases.
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 ®m 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.
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);
}
# ----------------------------------------------------------------------------
#
# Routines that find files and look for individual map superblocks
# Turn an iOS device pathname into an SHA1 filename. It is assumed that
# the app is Mooncraft. For example:
# print &path_fn("Documents/0_0_0.mw");
# prints
# 8c676d7863301fcc8574e0b4599c8046af72fc6e
#
# %%% I might need to use a "lunacraft" AppDomain in the future.
sub path_fn
{
my($path) = @_;
my($p2, $fn);
$p2 = "AppDomain-com.mooncraft.full-" . $path;
# print "p2: $p2\n";
$fn = sha1_hex($p2);
return $fn;
}
# Get the filename for a superblock (given the moon, X and Y, and filename
# extension). For example:
# print (&superblock_path("A", 0, 0, 'mw') . "\n");
# prints
# Documents/0_0_0.mw
sub superblock_path
{
my($moon, $x, $y, $ext) = @_;
my($path, $fn);
$path = "Documents/" . sprintf("%d_%d_%d", $moon, 128*$x, 128*$y)
. '.' . $ext;
# print "path: $path\n";
return $path;
}
# Get the filename for a superblock (given the moon, X and Y, and filename
# extension). For example:
# print &superblock_fn("A", 0, 0, 'mw');
# prints
# 8c676d7863301fcc8574e0b4599c8046af72fc6e
sub superblock_fn
{
my($moon, $x, $y, $ext) = @_;
my($path, $fn);
$path = &superblock_path($moon, $x, $y, $ext);
$fn = &path_fn($path);
return $fn;
}
# Get the filename for a superblock (given the moon, X and Y). For example:
# print &superblock_mw("A", 0, 0);
# prints
# 8c676d7863301fcc8574e0b4599c8046af72fc6e
sub superblock_mw
{
my($moon, $x, $y) = @_;
return(&superblock_fn($moon, $x, $y, 'mw'));
}
# Return true if a given superblock exists. This can be used to determine if
# a given moon has been created (pass 0 for X and Y) and also for mapping
# out the limits of the world.
sub sb_exists
{
my($moon, $x1, $y1) = @_;
my($fn, $rv, $x, $y);
$x = &fl($x1/128); $y = &fl($y1/128);
$fn = &superblock_mw($moon, $x, $y);
$rv = (-e $fn);
return $rv;
}
# Date routine used by show_device_info
sub date3
{
local($tm, $numeric) = @_;
local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
local($l, $fmt);
($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;
}
# Return modification time of a file.
sub path_mtime
{
local($path) = @_;
return((stat($path))[9]);
}
# Display name and backup time of the iOS device whose data we are currently
# looking at.
sub show_device_info
{
my $info_pn = "Info.plist";
my $status_pn = "Status.plist";
my($l, $state, $curdir);
$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|<key>Device Name</key>|)) {
$state = 2;
}
if (($state == 2) && ($l =~ m|<string>(.+)</string>|)) {
$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";
}
# Find a directory to work in.
# %%% We want to check the Windows locations too.
sub find_a_dir
{
my ($td, $i);
# 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;
}
# ----------------------------------------------------------------------------
#
# Convert the N_game.dat file into ASCII and display its contents.
sub show_game_dat
{
my($moon, $prnt, $match_real, $subst_real) = @_;
my($p1, $fn, $d1, $d2, $f1, $f2);
$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|<real>([-.0-9]+)</real>|) {
$v = $1;
$v1 = $v2; $v2 = $v3; $v3 = $v;
print " Real: $v\n" if ($prnt);
if ($v == $match_real) {
$l =~ s|<real>([-.0-9]+)</real>|<real>$subst_real</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));
}
# ----------------------------------------------------------------------------
# Color modifiers
$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";
# ----------------------------------------------------------------------------
# Parse the mob data file
my $FLOATS_IN;
# Convert 4 bytes into a floating-point value by IEEE 32-bit, then print
# it out if the result is "reasonable"
sub flval
{
my($b0, $b1, $b2, $b3, $pr) = @_;
my($exp, $mant, $sign, $fval, $ff);
$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
# $exp = ((($b3 & 0x7f) << 4) | ($b2 >> 4)) - 1023;
# $mant = ((($b2 & 0x0f) | 0x10) << 16) | ($b1 << 8) | $b0;
# $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));
}
# Look for floating-point values in a .dat file.
#
# For dropped objects the "angle" is an age in units of 1/20 second, i.e.
# 1200 per minute
# Ty field:
# 01 hoverblock (Fc = type of material; Ex=quantity; angle=age, they die when it gets to about 500)
# 4 giraffe (Fc = health)
# 5 dropped object (Fc = type; Ex = quantity in stack)
# 25=mechanism
# 27=magnet
# 31=biogel
# 44=adhesive
# 46-63=disk
# 77=camera
# 80=turretV3
# 84=chronoboost
# 6 minilight (Fc = direction (0=S, 1=W, 2=N, 3=E, 4=U, 5=D); Ex=0)
# 7 green mob (Fc = health; Ex = 0 or 3?)
# 8 astronaut (Fc = health; Ex = color (0=white, 1=green, 2=blue, 3=pink, 4=yellow))
# 10 brown mob (Fc = health)
# 11 turret (Fc = health; Ex = 4)
# 12 Blue I (tags unknown, suspect: Fc = health; Ex = item stolen)
#
# red 31 276.026
# orange 37 205.127
# yellow 36 149.275
# green 30 95.412
# blue 33 36.291
sub regm
{
my($ix, $name, $tag, $show, $prio) = @_;
$mobname{$ix} = $name;
$mobtag[$ix] = $tag;
$showmob[$ix] = $show;
$mobprio[$ix] = $prio;
}
®m(1, "Hoverblock");
®m(4, "Giraffe ", $c_wt_mag, 1, "p06 ${c_wt_mag}G$norm");
®m(5, "Dropped Object");
®m("5 31", "Biogel");
®m("5 44", "Adhesive");
®m("5 84", "Chronoboost");
for($i=46; $i<=63; $i++) { ®m("5 $i", "Disk"); }
®m(6, "Minilight");
®m("6 0", "Minilight south");
®m("6 1", "Minilight west");
®m("6 2", "Minilight north");
®m("6 3", "Minilight east");
®m("6 4", "Minilight up");
®m("6 5", "Minilight down");
®m(7, "Small Green", $c_wt_grn, 1, "p05 ${c_wt_grn}g$norm");
®m(8, "Astronaut ", $c_wt_cyn, 1, "p03 ${c_wt_cyn}A$norm");
®m(10,"Brown Mob ", $c_wt_red, 1, "p02 ${c_wt_red}B$norm");
®m(11,"Turret ", $c_wt_yel, 1, "p04 ${c_wt_yel}T$norm");
®m(12,"Blue I ", $c_wt_blu, 1, "p01 ${c_wt_blu}I$norm");
# Parse the .dat file; store mobs in %mobs; if $prnt parameter is
# set, print the full list of objects.
# {%%% not yet implemented: Any objects that match match_type will get
# turned into new_type, with output written into float-out-tmp.dat}
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;
$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;
}
# ----------------------------------------------------------------------------
#
# Mapping at 1x1 scale
# Various heights of hill terrain
$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";
# Different colors and symbols for the various materials
# NOTE 1: For transparent materials, it would be nice to actually make them
# transparent, by displaying a unique symbol for the transparent block ("[]"
# for glass) combined with the VT color or whatever is under/behind the
# glass. Thus, dirt under glass would be a red "[]" on black background.
$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)
$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
# Special display for certain materials in plan and cross-section views
@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)
# "name" field from items.txt. The index is off-by-one from what we need.
%bt_name = (
0 => "[[Dirt|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"
);
# All materials with a name except 129 are "normal" or "mundane" (not exotic)
for($i=0; $i<=42; $i++) {
$bt_mundane{$i} = 1;
}
$bt_mundane{40} = 0;
# Exceptions that remain in the list in mooncraft.wikia.com/wiki/Brown_Mob:
# Blue_Gravel -> Refined_Aluminum bluegravel -> bt40 (undefined)
# Colorwood -> Dirt colorwood -> bt43 (undefined)
# 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
# Here is the old code:
#
# sub mx1_do_byte
# {
# my($token) = @_;
# if ($token =~ m/^ *$/) {
# } else {
# &mx1.gotbyte($token);
# }
# }
#
# my($token);
# open(my $IN, "od -A n -t u1 -v '$fn' |");
# while($l = <$IN>) {
# foreach $token (split(/ +/, $l)) {
# &mx1_do_byte($token);
# }
# }
# Generate and print a map at 1x scale (each block is two characters
# in output). This requires the terminal to be at least 257 columns wide.
# %%% In future, I can display only as much of the map as fits in the current
# terminal width. See $screen_size variable in #mira#.
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);
$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;
}
}
# Clear abundance and other stats
sub clear_abundance
{
%bt_total = ();
@elev_freq = ();
@height_freq = ();
}
# Print out a simple report of material abundances
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");
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]);
}
}
}
# ----------------------------------------------------------------------------
#
# Mapping at superblock (128x scale)
# Generate an overview map of an enire moon (128x scale: each superblock is
# just 2 characters).
#
# The second parameter $survey_all is used for the 'allstats' command,
# which takes stats on materials or mobs and tags the map display to
# show the locations of astronaut lairs, mobs, and/or the current map
# position.
#
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;
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
# ----------------------------------------------------------------------------
#
# 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";
}
# ----------------------------------------------------------------------------
#
# 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;
|
Advertisement
Lunacraft Map Data
Advertisement