summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTreeki <treeki@gmail.com>2011-12-08 01:50:52 +0100
committerTreeki <treeki@gmail.com>2011-12-08 01:50:52 +0100
commitc1cd2955c3e9b1dbb7ea72c85f996a19ab37ea3b (patch)
tree178588c02814c217ca50a5e7a2a49fc55d3219b9 /src
parent21cf889b5e45a5619a09e46db246abd4abbec3a4 (diff)
downloadkoopatlas-c1cd2955c3e9b1dbb7ea72c85f996a19ab37ea3b.tar.gz
koopatlas-c1cd2955c3e9b1dbb7ea72c85f996a19ab37ea3b.zip
the first version of map exporting
Diffstat (limited to 'src')
-rw-r--r--src/editorui/paths.py24
-rw-r--r--src/exporter.py335
-rw-r--r--src/mapdata.py18
-rw-r--r--src/mapfile.py1
-rw-r--r--src/ui.py14
5 files changed, 327 insertions, 65 deletions
diff --git a/src/editorui/paths.py b/src/editorui/paths.py
index 2f4388a..617e981 100644
--- a/src/editorui/paths.py
+++ b/src/editorui/paths.py
@@ -18,7 +18,6 @@ class KPEditorNode(KPEditorItem):
self.iconList = [QtGui.QIcon("Resources/Through.png"),
QtGui.QIcon("Resources/Level.png"),
- QtGui.QIcon("Resources/Stop.png"),
QtGui.QIcon("Resources/Exit.png")]
self.state = 0
@@ -33,7 +32,7 @@ class KPEditorNode(KPEditorItem):
def toggle(self):
self.state += 1
- if self.state == 4:
+ if self.state == 3:
self.state = 0
self.stateToggled.emit(self.state)
@@ -156,8 +155,7 @@ class KPEditorNode(KPEditorItem):
node.mapChange = None
node.mapID = None
node.foreignID = None
- node.isStop = True
- node.level = [0,0]
+ node.level = None
if state == 1:
node.level = [1, 1]
@@ -165,9 +163,6 @@ class KPEditorNode(KPEditorItem):
self.stage.setValue(node.level[1])
elif state == 2:
- pass
-
- elif state == 3:
node.transition = 0
node.mapChange = 'None.arc'
node.foreignID = 0
@@ -187,10 +182,6 @@ class KPEditorNode(KPEditorItem):
self.transition.setCurrentIndex(0)
- else:
- node.isStop = False
-
-
self.update()
@@ -254,7 +245,7 @@ class KPEditorNode(KPEditorItem):
selectionRect = None
- if node.level != [0,0]:
+ if node.level:
painter.setBrush(QtGui.QColor(0, 0, 0, 0))
painter.setPen(QtGui.QColor(0, 0, 0, 0))
painter.drawPixmap(self._boundingRect.topLeft(), QtGui.QPixmap("Resources/BlackLevel.png"))
@@ -279,7 +270,7 @@ class KPEditorNode(KPEditorItem):
selectionRect = self._boundingRect.adjusted(1,5,-1,-5)
- elif node.isStop:
+ elif len(node.exits) != 2:
brush = QtGui.QBrush(QtGui.QColor(255, 220, 220))
painter.setPen(QtGui.QColor(255, 255, 255))
painter.setBrush(brush)
@@ -301,7 +292,7 @@ class KPEditorNode(KPEditorItem):
self.buttonProxy.show()
- if node.level != [0,0]:
+ if node.level:
self.worldProxy.show()
self.stageProxy.show()
@@ -474,7 +465,6 @@ class KPEditorPath(QtGui.QGraphicsLineItem):
path = self._pathRef()
path.animation = buttonID
- print path.animation
path.qtItem.update()
@@ -619,6 +609,9 @@ class KPEditorPath(QtGui.QGraphicsLineItem):
startNode = path._startNodeRef().qtItem
endNode = path._endNodeRef().qtItem
+ startNode.update()
+ endNode.update()
+
self._startNodeRef = weakref.ref(startNode)
self._endNodeRef = weakref.ref(endNode)
self._pathRef = weakref.ref(path)
@@ -696,6 +689,7 @@ class KPEditorPath(QtGui.QGraphicsLineItem):
node = ref()._nodeRef()
try:
node.exits.remove(path)
+ node.qtItem.update()
except ValueError:
pass
diff --git a/src/exporter.py b/src/exporter.py
index fec2f86..5233cdd 100644
--- a/src/exporter.py
+++ b/src/exporter.py
@@ -1,7 +1,54 @@
from common import *
import array
import sys
+from ctypes import create_string_buffer
+def RGB4A3Encode(tex):
+ tex = tex.toImage()
+ w, h = tex.width(), tex.height()
+ padW = (w + 3) & ~3
+ padH = (h + 3) & ~3
+
+ destBuffer = create_string_buffer(padW * padH * 2)
+
+ shortstruct = struct.Struct('>H')
+ sspack = shortstruct.pack_into
+ offset = 0
+
+ for ytile in xrange(0, padW, 4):
+ for xtile in xrange(0, padH, 4):
+ for ypixel in xrange(ytile, ytile + 4):
+ for xpixel in xrange(xtile, xtile + 4):
+
+ if xpixel >= w or ypixel >= h:
+ rgbDAT = 0x7FFF
+ else:
+ pixel = tex.pixel(xpixel, ypixel)
+
+ a = pixel >> 24
+ r = (pixel >> 16) & 0xFF
+ g = (pixel >> 8) & 0xFF
+ b = pixel & 0xFF
+
+ if a < 245: #RGB4A3
+ alpha = a/32
+ red = r/16
+ green = g/16
+ blue = b/16
+
+ rgbDAT = (blue) | (green << 4) | (red << 8) | (alpha << 12)
+
+ else: # RGB555
+ red = r/8
+ green = g/8
+ blue = b/8
+
+ rgbDAT = (blue) | (green << 5) | (red << 10) | (0x8000) # 0rrrrrgggggbbbbb
+
+ sspack(destBuffer, offset, rgbDAT)
+ offset += 2
+
+ return destBuffer.raw
class KPMapExporter:
class LayerExporter:
@@ -10,6 +57,7 @@ class KPMapExporter:
def buildSectors(self, sectors, indices):
# we'll use the cache held by the layer: why reinvent the wheel?
+ layer = self.layer
layer.updateCache()
cache = layer.cache
@@ -34,13 +82,13 @@ class KPMapExporter:
destY = worldY % 16
destRow = rawSectors[sectorY]
-
- for srcX in xrange(layerX, layerX+layerWidth):
+
+ for srcX in xrange(layerWidth):
worldX = srcX + layerX
sectorX = worldX / 16
destX = worldX % 16
- tile = srcRow[i]
+ tile = srcRow[srcX]
if tile == -1: continue
destSector = destRow[sectorX]
@@ -51,80 +99,279 @@ class KPMapExporter:
destSector[destY][destX] = tile
# now add the created sectors to the data
- sectorMap = [-1 for i in xrange(len(self.rawSectors))]
+ count = reduce(lambda x,y: x+len(y), rawSectors, 0)
+ sectorMap = [-1 for i in xrange(count)]
+ destIdx = 0
- for srcRow, mapRow in zip(self.rawSectors, sectorMap):
- for index, sector in enumerate(srcRow):
+ for srcRow in rawSectors:
+ for sector in srcRow:
if sector is not None:
# see if it's a duplicate or not
sectorKey = '|'.join(map(lambda x: ','.join(map(str, x)), sector))
try:
- mapRow[index] = sectorIndices[sectorKey]
- except ValueError:
- sectorIndices[sectorKey] = len(sectors)
- mapRow[index] = len(sectors)
+ sectorMap[destIdx] = indices[sectorKey]
+ except KeyError:
+ indices[sectorKey] = len(sectors)
+ sectorMap[destIdx] = len(sectors)
sectors.append(sector)
+ destIdx += 1
+
self.sectorBounds = (sectorLeft, sectorTop, sectorRight, sectorBottom)
self.sectorMap = sectorMap
-
+
def __init__(self, mapObj):
self.map = mapObj
self.layers = map(KPMapExporter.LayerExporter, self.map.layers)
- self.archive = WiiArchiveU8()
-
- def buildAndPack(self, handle=None):
- # make the BG data
+ def build(self):
+ u32 = struct.Struct('>I')
+ u16 = struct.Struct('>H')
+ zero32 = u32.pack(0)
+
+ requiredFixUps = []
+ stringsToAdd = set()
+ textures = set()
+ offsets = {None: 0xFFFFFFFF}
+
+ # first off, build the sectors
sectors = []
sectorIndices = {}
for layer in self.layers:
- layer.buildSectors(sectors, sectorIndices)
+ if isinstance(layer.layer, KPTileLayer):
+ layer.buildSectors(sectors, sectorIndices)
- # pack it into the arc
- self.archive.resolvePath('/sectors.bin').data = self._packSectorData(sectors)
- self.archive.resolvePath('/sectorMaps.bin').data = self._packSectorMaps()
+ sectorData = self._packSectorData(sectors)
- return self.archive.pack(handle)
+ # now that we've got that, we can pack the first part of the file
+ data = bytearray(struct.pack('>IIII', len(self.layers), 16 + len(sectorData), 0, 0))
+ # list of layer pointers goes here.. or will, later
+ data += sectorData
- def _packSectorData(self, sectors):
- rowStruct = struct.Struct('>16h')
- output = []
+ for layer in self.layers:
+ requiredFixUps.append((len(data), layer))
+ data += zero32
- for sector in sectors:
- for row in sector:
- output.append(rowStruct.pack(*row))
+ # now build the layers
+ for eLayer in self.layers:
+ layer = eLayer.layer
- return ''.join(output)
+ offsets[eLayer] = len(data)
+ offsets[layer] = len(data)
+
+ if isinstance(layer, KPTileLayer):
+ data += u32.pack(0)
+
+ # tileset name
+ stringsToAdd.add(layer.tileset)
+ requiredFixUps.append((len(data), layer.tileset))
+ data += zero32
+
+ # sector info
+ data += struct.pack('>IIII', *eLayer.sectorBounds)
+ data += ''.join(map(u16.pack, eLayer.sectorMap))
+
+ pad = (4 - (len(data) & 3)) % 4
+ data += ('\0' * pad)
+
+ elif isinstance(layer, KPDoodadLayer):
+ data += u32.pack(1)
+
+ # doodad list
+ data += u32.pack(len(layer.objects))
+ for doodad in layer.objects:
+ requiredFixUps.append((len(data), doodad))
+ data += zero32
+
+ # now pack them ...
+ for doodad in layer.objects:
+ offsets[doodad] = len(data)
+
+ x, y = doodad.position
+ w, h = doodad.size
+ data += struct.pack('>fffffii', x, y, w, h, doodad.angle, 0, len(doodad.animations))
+
+ texture = doodad.source[1]
+ textures.add(texture)
+ requiredFixUps.append((len(data) - 8, texture))
+
+ for anim in doodad.animations:
+ rLoop, rCurve, rFrames, rType, rStart, rEnd = anim
+
+ loopid = self.ANIM_LOOPS.index(rLoop)
+ curveid = self.ANIM_CURVES.index(rCurve)
+ typeid = self.ANIM_TYPES.index(rType)
+ data += struct.pack('>iififf', loopid, curveid, rFrames, typeid, rStart, rEnd)
+
+ elif isinstance(layer, KPPathLayer):
+ data += u32.pack(2)
+
+ # lists
+ current = len(data)
+ nodeArray = current + 16
+ pathArray = nodeArray + (len(layer.nodes) * 4)
+
+ data += struct.pack('>IIII', len(layer.nodes), nodeArray, len(layer.paths), pathArray)
+
+ for node in layer.nodes:
+ requiredFixUps.append((len(data), node))
+ data += zero32
+ for path in layer.paths:
+ requiredFixUps.append((len(data), path))
+ data += zero32
+
+ # now do the actual structs
+ for node in layer.nodes:
+ offsets[node] = len(data)
- def _packSectorMaps(self):
- offsets = array.array('I')
- assert offsets.itemsize == 4
+ x, y = node.position
+ current = len(data)
+ data += struct.pack('>hhiiii', x, y, 0, 0, 0, 0)
- currentOffset = len(self.layers) * 4
+ exits = node.exits + [None,None,None,None] # TODO
+ requiredFixUps.append((current+4, exits[0]))
+ requiredFixUps.append((current+8, exits[1]))
+ requiredFixUps.append((current+12, exits[2]))
+ requiredFixUps.append((current+16, exits[3]))
- data = []
+ if node.isStop():
+ if node.level:
+ level1, level2 = node.level
+ data += struct.pack('>ibbbb', 2, level1, level2, 0, 0)
+ typeid = 2
- for index, layer in enumerate(self.layers):
- offsets.append(currentOffset)
+ elif node.mapChange:
+ data += u32.pack(3)
- data.append(struct.pack('>hhhh', *layer.sectorBounds))
- currentOffset += 8
-
- first = layer.sectorMap[0]
- rowStruct = struct.Struct('>%dh' % len(first))
- for row in layer.sectorMap:
- data.append(rowStruct.pack(*row))
+ destMap = node.mapChange
+ requiredFixUps.append((len(data), destMap))
+ stringsToAdd.add(destMap)
- currentOffset += (len(first) * len(layer.sectorMap) * 2)
+ data += struct.pack('>ibbbb', 0, node.mapID, node.foreignID, node.transition, 0)
- if sys.byteorder == 'little': offsets.byteswap()
- return offsets.tostring() + ''.join(data)
+ else:
+ data += u32.pack(1)
+ else:
+ data += u32.pack(0)
+
+ for path in layer.paths:
+ offsets[path] = len(data)
+
+ start = path._startNodeRef()
+ end = path._endNodeRef()
+ current = len(data)
+
+ requiredFixUps.append((current, start))
+ requiredFixUps.append((current+4, end))
+ requiredFixUps.append((current+8, path.linkedLayer))
+ data += (zero32 * 3)
+
+ data += struct.pack('>fi', path.movementSpeed, path.animation)
+
+ # unlocking paths/nodes!
+ unlocks = []
+ us = struct.Struct('>bbbbi')
+
+ for node in self.map.pathLayer.nodes:
+ if not node.level: continue
+
+ checked = set()
+ affected = []
+ self._findUnlocksForNode(node, checked, affected, True)
+
+ level1, level2 = node.level
+
+ for item, secret in affected:
+ unlocks.append(us.pack(secret, level1, level2, 0, offsets[item]))
+
+ struct.pack_into('>ii', data, 8, len(unlocks), len(data))
+ data += ''.join(unlocks)
+
+ # now that we're almost done... pack the strings
+ for string in stringsToAdd:
+ offsets[string] = len(data)
+ data += str(string)
+ data += '\0'
+
+ # textures
+ texHeaderStartOffset = len(data)
+ texHeaderEndOffset = texHeaderStartOffset + (len(textures) * 8)
+
+ texDataStartOffset = (texHeaderEndOffset + 0x1F) & ~0x1F
+ texPadding = texDataStartOffset - texHeaderEndOffset
+
+ currentTexOffset = texDataStartOffset
+
+ imageData = []
+
+ for tex in textures:
+ offsets[tex] = len(data)
+ data += struct.pack('>hhi', tex.width(), tex.height(), currentTexOffset)
+
+ converted = RGB4A3Encode(tex)
+ imageData.append(converted)
+ currentTexOffset += len(converted)
+
+ data += ('\0' * texPadding)
+ for piece in imageData:
+ data += piece
+
+ # to finish up, correct every offset
+ for offset, target in requiredFixUps:
+ u32.pack_into(data, offset, offsets[target])
+
+ return data
+
+ ANIM_LOOPS = ['Contiguous', 'Loop', 'Reversible Loop']
+ ANIM_CURVES = ['Linear', 'Sinusoidial', 'Cosinoidial']
+ ANIM_TYPES = ['X Position', 'Y Position', 'Angle', 'X Scale', 'Y Scale', 'Opacity']
+
+
+ def _findUnlocksForNode(self, node, checked, affected, isFirstBranch=False, secret=None):
+ if node in checked: return
+
+ checked.add(node)
+
+ for path in node.exits:
+ if path not in checked:
+ checked.add(path)
+ self._findUnlocksForPath(path, node, checked, affected, isFirstBranch, secret)
+
+
+ def _findUnlocksForPath(self, path, sourceNode, checked, affected, isFirstBranch, secret=None):
+ start, end = path._startNodeRef(), path._endNodeRef()
+ if start == sourceNode:
+ destNode = end
+ if isFirstBranch and path.unlocks != 1:
+ return
+ else:
+ destNode = start
+ if isFirstBranch and path.unlocks != 2:
+ return
+
+ if secret is None:
+ secret = path.secret
+ affected.append((path, secret))
+
+ if not destNode.isStop():
+ self._findUnlocksForNode(destNode, checked, affected, False, secret)
+
+
+
+ def _packSectorData(self, sectors):
+ rowStruct = struct.Struct('>16h')
+ output = []
+
+ for sector in sectors:
+ for row in sector:
+ output.append(rowStruct.pack(*row))
+
+ return ''.join(output)
diff --git a/src/mapdata.py b/src/mapdata.py
index ec0feff..23c6b83 100644
--- a/src/mapdata.py
+++ b/src/mapdata.py
@@ -1,7 +1,6 @@
from common import *
import weakref
import mapfile
-import base64
TILE_SIZE = (24,24)
MAP_SIZE_IN_TILES = (512,512)
@@ -250,7 +249,7 @@ class KPNodeAction(object):
@mapfile.dumpable('node')
class KPNode(object):
__dump_attribs__ = (
- 'position', 'actions', 'level', 'isStop', 'mapChange',
+ 'position', 'actions', 'level', 'mapChange',
'transition', 'mapID', 'foreignID')
def _dump(self, mapObj, dest):
@@ -265,12 +264,15 @@ class KPNode(object):
self.position = (0,0)
self.actions = []
self.exits = []
- self.level = [0,0]
- self.isStop = False
+ self.level = None
self.mapChange = None
self.transition = 0
self.mapID = None
self.foreignID = None
+
+ def isStop(self):
+ return True if (self.level or self.mapChange or len(self.exits) != 2) else False
+
@mapfile.dumpable('path')
@@ -304,7 +306,7 @@ class KPPath(object):
self.secret = 0 # 0 = unlocks from normal exit, 1 = unlocks from secret exit
if cloneFrom is None:
- self.animation = None
+ self.animation = 0
else:
self.animation = cloneFrom.animation
@@ -388,6 +390,12 @@ class KPMap(object):
dumped = mapfile.dump(self)
open(path, 'wb').write(dumped)
+ def export(self, path):
+ from exporter import KPMapExporter
+ exp = KPMapExporter(self)
+ data = exp.build()
+ open(path, 'wb').write(data)
+
def __init__(self):
self.filePath = None
diff --git a/src/mapfile.py b/src/mapfile.py
index 16d57db..a6ebede 100644
--- a/src/mapfile.py
+++ b/src/mapfile.py
@@ -1,4 +1,5 @@
from common import *
+import base64
import json
DUMPABLE_CLASSES_BY_NAME = {}
diff --git a/src/ui.py b/src/ui.py
index 6106e2a..74dc87e 100644
--- a/src/ui.py
+++ b/src/ui.py
@@ -442,7 +442,7 @@ class KPMainWindow(QtGui.QMainWindow):
f.addSeparator()
self.fd = f.addAction('Save', self.saveMap, QKeySequence("Ctrl+S"))
self.fe = f.addAction('Save As...', self.saveMapAs, QKeySequence("Ctrl+Shift+S"))
- self.ff = f.addAction('Export...') # E
+ self.ff = f.addAction('Export...', self.exportMap, QKeySequence("Ctrl+Shift+E"))
f.addSeparator()
self.fg = f.addAction('Take Screenshot...', self.screenshot, QKeySequence("Ctrl+Alt+S"))
f.addSeparator()
@@ -636,6 +636,18 @@ class KPMainWindow(QtGui.QMainWindow):
self.saveMap(True)
+ def exportMap(self):
+ target = KP.map.filePath
+
+ dialogDir = '' if target is None else os.path.dirname(target)
+ target = unicode(QtGui.QFileDialog.getSaveFileName(
+ self, 'Export Map', dialogDir, 'Koopatlas binary map (*.kpbin)'))
+
+ if len(target) == 0:
+ return
+
+ KP.map.export(target)
+
@QtCore.pyqtSlot()
def screenshot(self):