diff options
author | Treeki <treeki@gmail.com> | 2011-12-08 01:50:52 +0100 |
---|---|---|
committer | Treeki <treeki@gmail.com> | 2011-12-08 01:50:52 +0100 |
commit | c1cd2955c3e9b1dbb7ea72c85f996a19ab37ea3b (patch) | |
tree | 178588c02814c217ca50a5e7a2a49fc55d3219b9 /src | |
parent | 21cf889b5e45a5619a09e46db246abd4abbec3a4 (diff) | |
download | koopatlas-c1cd2955c3e9b1dbb7ea72c85f996a19ab37ea3b.tar.gz koopatlas-c1cd2955c3e9b1dbb7ea72c85f996a19ab37ea3b.zip |
the first version of map exporting
Diffstat (limited to 'src')
-rw-r--r-- | src/editorui/paths.py | 24 | ||||
-rw-r--r-- | src/exporter.py | 335 | ||||
-rw-r--r-- | src/mapdata.py | 18 | ||||
-rw-r--r-- | src/mapfile.py | 1 | ||||
-rw-r--r-- | src/ui.py | 14 |
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 = {} @@ -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): |