Added new tool: PackMap, to convert an Outlaw XML file to an Apple II packed map file.

This commit is contained in:
Martin Haye 2013-09-13 08:40:53 -07:00
parent 5bd05308af
commit 7445ee219d
9 changed files with 1955 additions and 3 deletions

6
.gitignore vendored
View File

@ -5,12 +5,12 @@
# Skip build directories
**/build/
/OutlawEditor/target/
/Platform/Apple/tools/A2Copy/nbproject/private/
/Platform/Apple/tools/A2Copy/dist/
/Platform/Apple/tools/*/nbproject/private/
/Platform/Apple/tools/*/dist/
# Don't check in data specific to any particular game
/Platform/Apple/virtual/data/images/*.bin
# Only check in sample.build.props; each person's build.props will be different.
/Platform/Apple/virtual/src/include/build.props
/Platform/Apple/tools/A2Copy/build/
/Platform/Apple/tools/A2Copy/build/

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="PackMap" default="default" basedir=".">
<description>Builds, tests, and runs the project PackMap.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="PackMap-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
X-COMMENT: Main-Class will be added automatically by build

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
build.xml.data.CRC32=69409762
build.xml.script.CRC32=89726637
build.xml.stylesheet.CRC32=8064a381@1.68.1.46
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=69409762
nbproject/build-impl.xml.script.CRC32=12156ac0
nbproject/build-impl.xml.stylesheet.CRC32=cdba79fa@1.68.1.46
nbproject/groovy-build.xml.data.CRC32=69409762
nbproject/groovy-build.xml.script.CRC32=629135a5
nbproject/groovy-build.xml.stylesheet.CRC32=919c82d5@1.4.1

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
*** GENERATED FROM project.xml - DO NOT EDIT ***
*** EDIT ../build.xml INSTEAD ***
-->
<project xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc">
<target name="-groovy-init-macrodef-javac">
<macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
<attribute name="srcdir" default="${src.dir}"/>
<attribute name="destdir" default="${build.classes.dir}"/>
<attribute name="classpath" default="${javac.classpath}"/>
<attribute name="includes" default="${includes}"/>
<attribute name="excludes" default="${excludes}"/>
<attribute name="debug" default="${javac.debug}"/>
<attribute name="sourcepath" default="${empty.dir}"/>
<attribute name="gensrcdir" default="${empty.dir}"/>
<attribute name="processorpath" default="${javac.processorpath}"/>
<attribute name="apgeneratedsrcdir" default="${build.generated.sources.dir}/ap-source-output"/>
<element name="customize" optional="true"/>
<sequential>
<taskdef name="groovyc" classpath="${javac.classpath}" classname="org.codehaus.groovy.ant.Groovyc"/>
<property name="empty.dir" location="${build.dir}/empty"/>
<mkdir dir="${empty.dir}"/>
<groovyc srcdir="@{srcdir}" sourcepath="@{sourcepath}" destdir="@{destdir}" encoding="${source.encoding}" includes="@{includes}" excludes="@{excludes}" includeAntRuntime="false" fork="true">
<src>
<dirset dir="@{gensrcdir}" erroronmissingdir="false">
<include name="*"/>
</dirset>
</src>
<classpath>
<path path="@{classpath}"/>
</classpath>
<javac debug="@{debug}" deprecation="${javac.deprecation}" encoding="${source.encoding}" source="${javac.source}" target="${javac.target}">
<compilerarg line="${javac.compilerargs} ${javac.compilerargs.jaxws}"/>
<customize/>
</javac>
</groovyc>
</sequential>
</macrodef>
<macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
<attribute name="srcdir" default="${src.dir}"/>
<attribute name="destdir" default="${build.classes.dir}"/>
<attribute name="classpath" default="${javac.classpath}"/>
<sequential>
<depend srcdir="@{srcdir}" destdir="@{destdir}" cache="${build.dir}/depcache" includes="${includes}" excludes="${excludes}">
<classpath>
<path path="@{classpath}"/>
</classpath>
</depend>
</sequential>
</macrodef>
<macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
<attribute name="destdir" default="${build.classes.dir}"/>
<sequential>
<fail unless="javac.includes">Must set javac.includes</fail>
<pathconvert property="javac.includes.binary" pathsep=",">
<path>
<filelist dir="@{destdir}" files="${javac.includes}"/>
</path>
<globmapper from="*.java" to="*.class"/>
</pathconvert>
<delete>
<files includes="${javac.includes.binary}"/>
</delete>
</sequential>
</macrodef>
</target>
<target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run-with-groovy">
<j2seproject3:test testincludes=""/>
</target>
<target depends="init,compile-test,-pre-test-run,-do-test-run-with-groovy" if="have.tests" name="-post-test-run-with-groovy">
<fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
</target>
<target depends="init,compile-test,-pre-test-run,-do-test-run-with-groovy,test-report,-post-test-run-with-groovy,-test-browse" description="Run unit tests." name="test-with-groovy"/>
<target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single-groovy">
<fail unless="test.binarytestincludes">Must select some files in the IDE or set test.includes</fail>
<j2seproject3:test testincludes=""/>
</target>
<target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-groovy" if="have.tests" name="-post-test-run-single-groovy">
<fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
</target>
<target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-groovy,-post-test-run-single-groovy" description="Run single unit test." name="test-single-groovy"/>
<target depends="init,compile-test-single,-pre-test-run-single,-debug-start-debugger-test" name="-do-test-debug-single-groovy">
<fail unless="test.binarytestincludes">Must select some files in the IDE or set test.binarytestincludes</fail>
<j2seproject3:test-debug testincludes=""/>
</target>
<target depends="init,compile-test-single,-pre-test-run-single,-do-test-debug-single-groovy" if="have.tests" name="-post-test-debug-single-groovy">
<fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
</target>
<target depends="init,compile-test-single,-pre-test-run-single,-debug-start-debugger-test,-do-test-debug-single-groovy,-post-test-debug-single-groovy" name="debug-test-with-groovy"/>
</project>

View File

@ -0,0 +1,75 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
application.title=PackMap
application.vendor=mhaye
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form,**/*.groovy
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.test.classpath=\
${run.test.classpath}
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=${dist.dir}/PackMap.jar
dist.javadoc.dir=${dist.dir}/javadoc
endorsed.classpath=
excludes=
includes=**
jar.compress=false
javac.classpath=\
${libs.groovy-all.classpath}
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.processorpath=\
${javac.classpath}
javac.source=1.7
javac.target=1.7
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
main.class=org.demo.PackMap
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=false
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
source.encoding=UTF-8
src.dir=src
test.src.dir=test
compile.on.save.unsupported.groovy=true

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<buildExtensions xmlns="http://www.netbeans.org/ns/ant-build-extender/1">
<extension file="groovy-build.xml" id="groovy">
<dependency dependsOn="-groovy-init-macrodef-javac" target="-pre-pre-compile"/>
</extension>
</buildExtensions>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>PackMap</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

View File

@ -0,0 +1,274 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.demo
import java.nio.ByteBuffer
import java.nio.channels.Channels
/**
*
* @author mhaye
*/
class PackMap
{
def parseMap(tiles, map)
{
// Parse each row of the map
map.chunk.row.collect
{
// The map data is a space-separated string of tile ids. Look up those
// tiles.
//
it.text().split(" ").collect { tileId ->
(tileId == "_") ? null : tiles.find{ it.@id == tileId }
}
}
}
def pixelize(dataEl)
{
def nBytes = dataEl.@width as int
def nLines = dataEl.@height as int
def hexStr = dataEl.text()
return (0..<nLines).collect { lineNum ->
def outRow = []
def pix = 0
def pixBits = 0
for (def byteNum in 0..<nBytes) {
def pos = (lineNum*nBytes + byteNum) * 2 // two hex chars per byte
def val = Integer.parseInt(hexStr[pos..pos+1], 16)
for (def bitNum in 0..6) {
if (pixBits == 0)
pix = (val & 0x80) ? 4 : 0 // grab high bit of first byte of pix
if (val & (1<<bitNum))
pix |= (1<<pixBits)
pixBits++
if (pixBits == 2) {
outRow.add(pix)
pixBits = 0
}
}
}
return outRow
}
}
def parseImage(imgEl)
{
// Locate the data for the Apple II (as opposed to C= etc.)
def data = imgEl.displayData?.find { it.@platform == "AppleII" }
assert data : "image '${imgEl.@name}' missing AppleII platform data"
def rows = pixelize(data)
// Retain only the upper-left 64 lines x 32 pixels
def result = rows[0..63].collect { it[0..31] }
// Kludge alert! strip high bits in Building6.
if (imgEl.@name == "Building6") {
println "hacking high bits in Building6"
def rowNum = 0
result = result.collect { row ->
rowNum++
row.collect { pix ->
(rowNum <= 25 && pix == 3) ? 7 :
(rowNum <= 25 && pix == 0) ? 4 :
(rowNum > 25 && pix == 4) ? 0 :
(rowNum > 25 && pix == 7) ? 3 :
(rowNum > 25 && pix == 5) ? 0 :
pix
}
}
}
return result
}
class MultiPix
{
def colorValues = [:]
def add(color, value) {
colorValues[color] = colorValues.get(color,0) + value
}
def getHighColor() {
def kv = colorValues.grep().sort { a, b -> b.value <=> a.value }[0]
return [kv.key, kv.value]
}
def addError(pix, skipColor, frac) {
pix.colorValues.each { color, value ->
if (color != skipColor)
add(color, value * frac)
}
}
}
/**
* Produce a new mip-map level by halving the image's resolution.
*/
def reduceImage(imgIn)
{
def inWidth = imgIn[0].size()
def inHeight = imgIn.size()
def outWidth = inWidth >> 1
def outHeight = inHeight >> 1
def pixBuf = new MultiPix[outHeight][outWidth]
// Distribute the input pixels to the output pixels
imgIn.eachWithIndex { row, sy ->
def dy = sy >> 1
row.eachWithIndex { color, sx ->
def dx = sx >> 1
if (!pixBuf[dy][dx])
pixBuf[dy][dx] = new MultiPix()
pixBuf[dy][dx].add(color, 1)
}
}
// Apply error diffusion to form the final result
def outRows = []
pixBuf.eachWithIndex { row, dy ->
def outRow = []
row.eachWithIndex { pix, dx ->
def (color, value) = pix.getHighColor()
outRow.add(color)
// Distribute the error: 50% to the right, 25% down, 25% down-right
/*
if (dx+1 < outWidth)
pixBuf[dy][dx+1].addError(pix, color, 0.5)
if (dy+1 < outHeight)
pixBuf[dy+1][dx].addError(pix, color, 0.25)
if (dx+1 < outWidth && dy+1 < outHeight)
pixBuf[dy+1][dx+1].addError(pix, color, 0.25)
*/
}
outRows.add(outRow)
}
return outRows
}
def printImage(rows)
{
rows.each { row ->
print " "
row.each { pix -> print pix }
println ""
}
}
def writeMap(stream, rows, names)
{
def width = rows[0].size()
def height = rows.size()
// Header: one-char code followed by two-byte length
// (length should not include the 5-byte header)
//
def len = width*height
stream.write((int)'M') // for "Map"
stream.write(width)
stream.write(height)
stream.write(len & 0xFF)
stream.write((len>>8) & 0xFF)
// After the header comes the raw data
rows.each { row ->
row.each { tile ->
stream.write(names.findIndexOf { it == tile?.@name } + 1)
}
}
}
// The renderer wants bits of the two pixels interleaved
def combine(pix1, pix2) {
return ((pix1 & 1) << 0) | ((pix2 & 1) << 1) |
((pix1 & 2) << 1) | ((pix2 & 2) << 2) |
((pix1 & 4) << 2) | ((pix2 & 4) << 3);
}
def writeImage(stream, image)
{
// First, accumulate pixel data for all 5 mip levels plus the orig image
def buf = ByteBuffer.allocate(50000)
for (def mipLevel in 0..5)
{
// Process double rows
for (x in 0..<image[0].size()) {
for (y in (0..<image.size).step(2))
buf.put((byte) combine(image[y][x], image[y+1][x]))
}
// Generate next mip-map level
if (mipLevel < 5)
image = reduceImage(image)
}
def len = buf.position() // len doesn't include 3-byte header
// Write the header now that we have the length.
stream.write((int)'T') // for 'Texture'
stream.write(len & 0xFF)
stream.write((len>>8) & 0xFF)
// And copy the data to the output
def tmp = new byte[buf.position()]
buf.position(0)
buf.get(tmp)
stream.write(tmp)
}
def pack(xmlPath, binPath)
{
// Open the XML data file produced by Outlaw Editor
def dataIn = new XmlParser().parse(xmlPath)
// Locate the map named 'main', or failing that, the first map
def map = dataIn.map.find { it.@name == 'main' }
if (!map)
map = dataIn.map[0]
assert map : "Can't find map 'main' nor any map at all";
// Identify all the tiles and make a list of rows
def rows = parseMap(dataIn.tile, map);
// Determine the unique names of all the 'obstruction' tiles. Those
// are the ones that turn into texture images.
//
def names = rows.flatten().grep{it?.@obstruction == 'true'}.
collect{it.@name}.sort().unique()
println "Parsing images."
def images = names.collect { name ->
parseImage(dataIn.image.find { it.@name == name })
}
// Ready to start writing the output file.
new File(binPath).withOutputStream { stream ->
println "Writing map."
writeMap(stream, rows, names)
images.eachWithIndex { image, idx ->
println "Writing image #${idx+1}."
writeImage(stream, image)
}
stream.write(0) // properly terminate the file
}
println "Done."
}
static void main(String[] args)
{
if (args.size() != 2) {
println "Usage: convert yourMapFile.xml out.bin"
System.exit(1);
}
new PackMap().pack(args[0], args[1])
}
}