From d502df57548a1ff5a3c6852829dee4b22a226067 Mon Sep 17 00:00:00 2001 From: Treeki Date: Mon, 23 Jul 2012 23:27:30 +0200 Subject: going home, committing this before I lose 3G signal :p --- src/editorui/paths.py | 112 ++++++---------------------------------- src/unlock.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 97 deletions(-) create mode 100644 src/unlock.py (limited to 'src') diff --git a/src/editorui/paths.py b/src/editorui/paths.py index 4110188..a0e65d6 100644 --- a/src/editorui/paths.py +++ b/src/editorui/paths.py @@ -538,93 +538,6 @@ class KPEditorPath(QtGui.QGraphicsLineItem): self.setPalette(palette) - class UnlockButton(QtGui.QPushButton): - def __init__(self, pathRef): - QtGui.QPushButton.__init__(self) - - self.setFixedSize(48,48) - - self.iconList = [QtGui.QIcon("Resources/Key.png"), - QtGui.QIcon("Resources/SecretKey.png")] - - self.unlockIcon = QtGui.QIcon("Resources/Unlock.png") - self.arrowIcon = [QtGui.QIcon("Resources/KeyArrow.png"), - QtGui.QIcon("Resources/SecretKeyArrow.png")] - - - self._pathRef = pathRef - - self.secret = 0 - self.path = 0 - - if not hasattr(KPEditorPath.UnlockButton, 'PALETTE'): - KPEditorPath.UnlockButton.PALETTE = QtGui.QPalette(Qt.transparent) - - self.setPalette(self.PALETTE) - - self.released.connect(self.toggle) - - - def toggle(self): - - path = self._pathRef() - - if KP.app.keyboardModifiers() == Qt.ShiftModifier: - if self.secret == 1: - self.secret = 0 - else: - self.secret = 1 - - path.secret = self.secret - - else: - self.path += 1 - - if self.path > 2: - self.path = 0 - - path.unlocks = self.path - - - def paintEvent(self, event): - painter = QtGui.QPainter(self) - contentsRect = self.contentsRect() - smallRect = QtCore.QRect(12, 12, 24, 24) - painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform) - - if self.path > 0: - painter.save() - - displaceX, displaceY = contentsRect.width() / 2, contentsRect.height() / 2 - - pathItem = self._pathRef().qtItem.line() - - if self.path == 1: - angle = 90 - pathItem.angle() - else: - angle = 270 - pathItem.angle() - - painter.translate(displaceX, displaceY) - painter.rotate(angle) - painter.translate(-displaceX, -displaceY) - self.arrowIcon[self.secret].paint(painter, contentsRect, Qt.AlignCenter) - - painter.restore() - - if self.isDown(): - self.iconList[self.secret].paint(painter, smallRect, Qt.AlignCenter, QtGui.QIcon.Disabled) - else: - self.iconList[self.secret].paint(painter, smallRect, Qt.AlignCenter) - - else: - if self.isDown(): - self.unlockIcon.paint(painter, smallRect, Qt.AlignCenter, QtGui.QIcon.Disabled) - else: - self.unlockIcon.paint(painter, smallRect, Qt.AlignCenter) - - painter.end() - - class HiddenProxy(QtGui.QGraphicsProxyWidget): def __init__(self, button, parent, x, y): QtGui.QGraphicsProxyWidget.__init__(self, parent) @@ -663,12 +576,6 @@ class KPEditorPath(QtGui.QGraphicsLineItem): if not hasattr(KPEditorPath, 'SELECTION_PEN'): KPEditorPath.SELECTION_PEN = QtGui.QPen(Qt.blue, 1, Qt.DotLine) - self.unlock = self.UnlockButton(self._pathRef) - self.unlockProxy = self.HiddenProxy(self.unlock, self, -24, -24) - - self.unlock.secret = path.secret - self.unlock.path = path.unlocks - self.options = self.PathOptionsMenuButton(self._pathRef) self.optionsProxy = self.HiddenProxy(self.options, self, -54, +24) @@ -677,6 +584,20 @@ class KPEditorPath(QtGui.QGraphicsLineItem): self.updatePosition() + def mousePressEvent(self, event): + if event.button() != Qt.LeftButton: + return + if QtGui.QApplication.keyboardModifiers() != QtCore.Qt.ControlModifier: + return + + # modify the unlock settings + from unlock import KPUnlockSpecDialog + + # todo: set the existing thing there + result = KPUnlockSpecDialog.exec_() + if result == QtGui.QDialog.Accepted: + print "OK!" + def updatePosition(self): path = self._pathRef() @@ -720,12 +641,9 @@ class KPEditorPath(QtGui.QGraphicsLineItem): painter.setPen(self.SELECTION_PEN) painter.setBrush(QtGui.QColor(0,0,0,0)) painter.drawPath(self.shape()) - - self.unlockProxy.show() self.optionsProxy.show() else: - self.unlockProxy.hide() self.optionsProxy.hide() @@ -753,4 +671,4 @@ class KPEditorPath(QtGui.QGraphicsLineItem): self.scene().removeItem(self) - \ No newline at end of file + diff --git a/src/unlock.py b/src/unlock.py new file mode 100644 index 0000000..0f954d6 --- /dev/null +++ b/src/unlock.py @@ -0,0 +1,138 @@ +import re + +LEVEL_RE = re.compile(r'^([0-9]{1,2})-([0-9]{1,2})( secret)?$') +COMBINER_RE = re.compile(r'[ ]*(and|or)[ ]*') + +class UnlockParseError(ValueError): + # todo: is this the proper way to make an Error? + pass + +def parseUnlockText(text): + return _parseUnlockBit(text) + +def _parseUnlockBit(text): + # blank criterion + if text == '': + return ('always',) + + # is this a simple one...? + m = LEVEL_RE.match(text) + if m: + one, two, secret = m.groups() + return ('level', int(one), int(two), (secret != None)) + + # OK, let's parse parentheses + pLevel = 0 + endAt = len(text) - 1 + + # this could be either AND or OR or nothing at all + # we won't know it until we finish parsing! + whatCombiner = None + + subTerms = [] + currentSubTermStart = None + + skip = 0 + + for index, char in enumerate(text): + if skip > 0: + skip -= 1 + continue + + if char == '(': + if pLevel == 0: + currentSubTermStart = index + pLevel += 1 + elif char == ')': + pLevel -= 1 + if pLevel < 0: + raise UnlockParseError('close parenthesis without a matching open') + elif pLevel == 0: + subTerms.append((currentSubTermStart, index, text[currentSubTermStart+1:index])) + # are we expecting to see something else? + if index == endAt: break + + m = COMBINER_RE.match(text, index + 1) + if not m: + raise UnlockParseError('something unexpected at %d' % (index+1)) + + # what is it? + nextCombiner = m.group(1) + if whatCombiner is not None and nextCombiner != whatCombiner: + raise UnlockParseError('mixed %s and %s in one term. use more parentheses!' % (whatCombiner,nextCombiner)) + whatCombiner = nextCombiner + + # go right past this, to the next subterm + skip = len(m.group(0)) + else: + if pLevel == 0: + raise UnlockParseError('something unexpected at %d' % index) + + # now that we're here, we must have parsed these subterms + # do we have a combiner? + if whatCombiner is None: + assert len(subTerms) == 1 + + return _parseUnlockBit(subTerms[0][2]) + else: + return (whatCombiner, map(lambda x: _parseUnlockBit(x[2]), subTerms)) + + + +if __name__ == '__main__': + print repr(parseUnlockText('((01-01 secret) and (01-02)) or (02-99 secret) or (01-01)')) + + from sys import exit + exit() + + + + + +from common import * + + + + +class KPUnlockSpecDialog(QtGui.QDialog): + def __init__(self, forWhat, unlockAdjective): + QtGui.QDialog.__init__(self) + + self.setWindowTitle('Set Unlock Criteria') + + text = """You may enter various criteria that must be fulfilled for this {0} to be {1}.
+
+ Here are some examples of what you can use: + + Each criterion used on the sides of AND and OR must be surrounded by parentheses.
+
+ To leave this {0} permanently unlocked, leave the box blank. + """.format(forWhat, unlockAdjective) + + self.label = QtGui.QLabel(text) + self.label.setWordWrap(True) + + self.textBox = QtGui.QLineEdit() + self.textBox.textChanged.connect(self.checkInputValidity) + + self.buttons = QtGui.QDialogButtonBox( + QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) + + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + + self.layout = QtGui.VBoxLayout() + self.layout.addWidget(self.label) + self.layout.addWidget(self.textBox) + self.layout.addWidget(self.buttons) + self.setLayout(self.layout) + + def checkInputValidity(self, text): + self.parsed = parseUnlockText(text) + self.buttons.button(QtGui.QDialogButtonBox.Ok).setEnabled(not (self.parsed is None)) + -- cgit v1.2.3 From 4a9608453a6010979e3c99edabeaf2061edd8c49 Mon Sep 17 00:00:00 2001 From: Treeki Date: Tue, 24 Jul 2012 04:18:54 +0200 Subject: finished up the UI for editing unlocks, and the parser --- src/editorui/paths.py | 11 ++++++--- src/unlock.py | 65 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/editorui/paths.py b/src/editorui/paths.py index a0e65d6..43a1465 100644 --- a/src/editorui/paths.py +++ b/src/editorui/paths.py @@ -593,10 +593,15 @@ class KPEditorPath(QtGui.QGraphicsLineItem): # modify the unlock settings from unlock import KPUnlockSpecDialog - # todo: set the existing thing there - result = KPUnlockSpecDialog.exec_() + dlg = KPUnlockSpecDialog('path', 'unlocked') + + if hasattr(self._pathRef(), 'unlockSpec'): + dlg.setSpec(self._pathRef().unlockSpec) + + result = dlg.exec_() if result == QtGui.QDialog.Accepted: - print "OK!" + print "New spec:", dlg.spec + self._pathRef().unlockSpec = dlg.spec def updatePosition(self): diff --git a/src/unlock.py b/src/unlock.py index 0f954d6..16063ca 100644 --- a/src/unlock.py +++ b/src/unlock.py @@ -54,7 +54,7 @@ def _parseUnlockBit(text): m = COMBINER_RE.match(text, index + 1) if not m: - raise UnlockParseError('something unexpected at %d' % (index+1)) + raise UnlockParseError('something unexpected at position %d' % (index+1)) # what is it? nextCombiner = m.group(1) @@ -64,23 +64,52 @@ def _parseUnlockBit(text): # go right past this, to the next subterm skip = len(m.group(0)) + if (index + skip) == endAt: + raise UnlockParseError('%s what?!' % (whatCombiner.upper())) else: if pLevel == 0: - raise UnlockParseError('something unexpected at %d' % index) + if index == 0: + raise UnlockParseError('that\'s not right') + else: + raise UnlockParseError('something unexpected at position %d' % index) + + if pLevel > 0: + raise UnlockParseError('unclosed parenthesis') # now that we're here, we must have parsed these subterms # do we have a combiner? if whatCombiner is None: - assert len(subTerms) == 1 + if len(subTerms) != 1: + raise UnlockParseError('unclosed parenthesis') return _parseUnlockBit(subTerms[0][2]) else: return (whatCombiner, map(lambda x: _parseUnlockBit(x[2]), subTerms)) +def stringifyUnlockData(data): + kind = data[0] + + if kind == 'always': + return '' + elif kind == 'level': + return '%02d-%02d%s' % (data[1], data[2], (' secret' if data[3] else '')) + elif kind == 'and' or kind == 'or': + return (' %s ' % kind).join(map(lambda x: '(%s)' % stringifyUnlockData(x), data[1])) + if __name__ == '__main__': - print repr(parseUnlockText('((01-01 secret) and (01-02)) or (02-99 secret) or (01-01)')) + p1 = parseUnlockText('((01-01 secret) and (01-02)) or (02-99 secret) or (01-01)') + p2 = parseUnlockText('(1-1 secret) or ((1-2) and (1-3 secret)) or (2-1)') + + print + print repr(p1) + print + print stringifyUnlockData(p1) + print + print repr(p2) + print + print stringifyUnlockData(p2) from sys import exit exit() @@ -109,7 +138,8 @@ class KPUnlockSpecDialog(QtGui.QDialog):
  • (01-01 secret) and (01-02) - combine two criteria
  • ((01-01 secret) or (01-02)) and (01-04) - nested criteria
  • - Each criterion used on the sides of AND and OR must be surrounded by parentheses.
    + Each criterion used on the sides of AND and OR must be surrounded by parentheses. + You may use more than one, for example: (01-01) or (02-02) or (03-03)

    To leave this {0} permanently unlocked, leave the box blank. """.format(forWhat, unlockAdjective) @@ -120,19 +150,38 @@ class KPUnlockSpecDialog(QtGui.QDialog): self.textBox = QtGui.QLineEdit() self.textBox.textChanged.connect(self.checkInputValidity) + self.statusLabel = QtGui.QLabel() + self.statusLabel.setWordWrap(True) + self.buttons = QtGui.QDialogButtonBox( QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) - self.layout = QtGui.VBoxLayout() + self.layout = QtGui.QVBoxLayout() self.layout.addWidget(self.label) self.layout.addWidget(self.textBox) + self.layout.addWidget(self.statusLabel) self.layout.addWidget(self.buttons) self.setLayout(self.layout) + def setSpec(self, spec): + self.textBox.setText(stringifyUnlockData(spec)) + def checkInputValidity(self, text): - self.parsed = parseUnlockText(text) - self.buttons.button(QtGui.QDialogButtonBox.Ok).setEnabled(not (self.parsed is None)) + valid = True + try: + self.spec = parseUnlockText(str(text)) + except UnlockParseError as e: + valid = False + error = str(e) + self.spec = None + + self.buttons.button(QtGui.QDialogButtonBox.Ok).setEnabled(valid) + + if valid: + self.statusLabel.setText('Your input is valid.') + else: + self.statusLabel.setText('[!] %s' % error) -- cgit v1.2.3 From 1a7f17d93189ae9981ff286d67c9b0406e99ce96 Mon Sep 17 00:00:00 2001 From: Treeki Date: Tue, 24 Jul 2012 15:55:58 +0200 Subject: preparing for exporting --- src/exporter.py | 11 ++++++++++- src/unlock.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/exporter.py b/src/exporter.py index 2396633..f39497f 100644 --- a/src/exporter.py +++ b/src/exporter.py @@ -191,7 +191,8 @@ class KPMapExporter: sectorData = self._packSectorData(sectors) # 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)) + data = bytearray(struct.pack('>IIIII', len(self.layers), 20 + len(sectorData), 0, 0, 0)) + requiredFixUps.append((16, 'UnlockBytecode')) # list of layer pointers goes here.. or will, later data += sectorData @@ -420,6 +421,14 @@ class KPMapExporter: for piece in imageData: data += piece + # at the end comes the unlock bytecode + offsets['UnlockBytecode'] = len(data) + unlockBytecode = '' + # note: subtract 1 from the world and level number when saving + # note: also limit world to 1-10 in the parser + # note: enforce maximum term limit (64) + # note: block recursion too much + # to finish up, correct every offset for offset, target in requiredFixUps: u32.pack_into(data, offset, offsets[target]) diff --git a/src/unlock.py b/src/unlock.py index 16063ca..dfeaa33 100644 --- a/src/unlock.py +++ b/src/unlock.py @@ -8,7 +8,7 @@ class UnlockParseError(ValueError): pass def parseUnlockText(text): - return _parseUnlockBit(text) + return _parseUnlockBit(text.lower()) def _parseUnlockBit(text): # blank criterion -- cgit v1.2.3 From faaa82bd81f24fc897a5d0ae912381413625ddeb Mon Sep 17 00:00:00 2001 From: Treeki Date: Wed, 25 Jul 2012 00:36:42 +0200 Subject: moving to desktop --- src/exporter.py | 111 ++++++++++++++++++++++++++------------------------------ src/mapdata.py | 5 +-- src/ui.py | 6 +-- src/unlock.py | 37 ++++++++++++++++++- 4 files changed, 89 insertions(+), 70 deletions(-) (limited to 'src') diff --git a/src/exporter.py b/src/exporter.py index f39497f..c4bb410 100644 --- a/src/exporter.py +++ b/src/exporter.py @@ -201,22 +201,6 @@ class KPMapExporter: requiredFixUps.append((len(data), layer)) data += zero32 - # map all paths to unlock info - unlockInfo = {} - - for node in self.map.pathLayer.nodes: - if not node.level: continue - - checked = set() - affected = [] - self._findUnlocksForNode(node, checked, affected) - - level1, level2 = node.level - - for item, secret in affected: - unlockInfo[item] = (level1, level2, secret) - - # now build the layers for eLayer in self.layers: layer = eLayer.layer @@ -279,6 +263,14 @@ class KPMapExporter: elif isinstance(eLayer, self.PathLayerExporter): data += u32.pack(2) + # before we do anything, build the list of secret levels + # we'll need that + levelsWithSecrets = set() + + for path in layer.paths: + if hasattr(path, 'unlockSpec'): + self._checkSpecForSecrets(path.unlockSpec, levelsWithSecrets) + # lists current = len(data) nodeArray = current + 16 @@ -344,8 +336,9 @@ class KPMapExporter: if node.isStop(): if node.level: level1, level2 = node.level - # i i b b b b: node type, Extra pointer, world, level, padding (hasSecret?), padding - data += struct.pack('>iibbbb', 2, 0, level1, level2, 0, 0) + hasSecret = (1 if ((level1,level2) in levelsWithSecrets) else 0) + # i i b b b b: node type, Extra pointer, world, level, hasSecret, padding + data += struct.pack('>iibbbb', 2, 0, level1, level2, hasSecret, 0) elif node.mapChange: data += u32.pack(3) # node type @@ -364,7 +357,10 @@ class KPMapExporter: data += u32.pack(0) # node type data += u32.pack(0) # Extra pointer - for path in layer.paths: + pathIndices = {} + + for i, path in enumerate(layer.paths): + pathIndices[path] = i offsets[path] = len(data) start = path._startNodeRef() @@ -380,13 +376,7 @@ class KPMapExporter: data += (zero32 * 4) - try: - unlockL1, unlockL2, isSecret = unlockInfo[path] - unlockType = (2 if isSecret else 1) - except KeyError: - unlockL1, unlockL2, unlockType = 0, 0, 0 - - data += struct.pack('>bbbbfi', unlockType, unlockL1, unlockL2, 1, path.movementSpeed, path.animation) + data += struct.pack('>bbbbfi', 1, 0, 0, 0, path.movementSpeed, path.animation) # now that we're almost done... pack the strings for string in stringsToAdd: @@ -423,11 +413,32 @@ class KPMapExporter: # at the end comes the unlock bytecode offsets['UnlockBytecode'] = len(data) - unlockBytecode = '' - # note: subtract 1 from the world and level number when saving - # note: also limit world to 1-10 in the parser - # note: enforce maximum term limit (64) - # note: block recursion too much + + # first off, build a map of unlocks + unlockLists = {} + + for path in self.map.pathLayer.paths: + if not hasattr(path, 'unlockSpec'): + continue + spec = path.unlockSpec + if spec is None: + continue + + try: + lst = unlockLists[spec] + except KeyError: + lst = [] + unlockLists[spec] = lst + lst.append(path) + + # now produce the thing + from unlock import packUnlockSpec + + for spec, lst in unlockLists.iteritems(): + data += packUnlockSpec(spec) + data += chr(len(lst)) + for p in lst: + data += u16.pack(pathIndices[p]) # to finish up, correct every offset for offset, target in requiredFixUps: @@ -439,36 +450,16 @@ class KPMapExporter: ANIM_CURVES = ['Linear', 'Sinusoidial', 'Cosinoidial'] ANIM_TYPES = ['X Position', 'Y Position', 'Angle', 'X Scale', 'Y Scale', 'Opacity'] + def _checkSpecForSecrets(self, spec, levelSet): + kind = spec[0] - def _findUnlocksForNode(self, node, checked, affected, isFirstBranch=True, 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) - + if kind == 'level': + k, one, two, secret = spec + if secret: + levelSet.add((one, two)) + elif kind == 'and' or kind == 'or': + for term in spec[1]: + self._checkSpecForSecrets(term, levelSet) def _buildGXTexObjRGB5A3(self, width, height, imgOffset): # Format: RGB5A3 (5) diff --git a/src/mapdata.py b/src/mapdata.py index c32bd7b..0332280 100644 --- a/src/mapdata.py +++ b/src/mapdata.py @@ -451,7 +451,7 @@ class KPNode(object): @mapfile.dumpable('path') class KPPath(object): - __dump_attribs__ = ('unlocks', 'secret', 'animation', 'movementSpeed') + __dump_attribs__ = ('unlockSpec', 'animation', 'movementSpeed') def _dump(self, mapObj, dest): dest['startNodeLink'] = mapObj.refNode(self._startNodeRef()) @@ -476,8 +476,7 @@ class KPPath(object): startNode.exits.append(self) endNode.exits.append(self) - self.unlocks = 0 # 0 = always unlocked, 1 = unlocked from startNode, 2 = unlocked from endNode - self.secret = 0 # 0 = unlocks from normal exit, 1 = unlocks from secret exit + self.unlockSpec = None # always unlocked, by default if cloneFrom is None: self.animation = 0 diff --git a/src/ui.py b/src/ui.py index 6754f17..540c52b 100644 --- a/src/ui.py +++ b/src/ui.py @@ -61,12 +61,8 @@ class KPPathNodeList(QtGui.QWidget): "Ladder", "LadderLeft", "LadderRight", "Fall", "Swim", "Run", "Pipe", "Door"] animation = AnimationList[self.associate.animation] - if self.associate.secret == 0: - unlock = 'Normal' - else: - unlock = 'Secret' - return 'Path: {0} Exit, {1}'.format(unlock, animation) + return 'Path: {1}'.format(None, animation) elif role == Qt.CheckStateRole: return (Qt.Checked if self.layer.visible else Qt.Unchecked) diff --git a/src/unlock.py b/src/unlock.py index dfeaa33..446cd0a 100644 --- a/src/unlock.py +++ b/src/unlock.py @@ -8,7 +8,9 @@ class UnlockParseError(ValueError): pass def parseUnlockText(text): - return _parseUnlockBit(text.lower()) + parsed = _parseUnlockBit(text.lower()) + if parsed == ('always',): + return None def _parseUnlockBit(text): # blank criterion @@ -19,7 +21,11 @@ def _parseUnlockBit(text): m = LEVEL_RE.match(text) if m: one, two, secret = m.groups() - return ('level', int(one), int(two), (secret != None)) + w = int(one) + l = int(two) + if w < 1 or w > 10: + raise UnlockParseError('world must be between 1 to 10 inclusive; not %s' % w) + return ('level', w, l, (secret != None)) # OK, let's parse parentheses pLevel = 0 @@ -49,6 +55,9 @@ def _parseUnlockBit(text): raise UnlockParseError('close parenthesis without a matching open') elif pLevel == 0: subTerms.append((currentSubTermStart, index, text[currentSubTermStart+1:index])) + if len(subTerms) > 64: + raise UnlockParseError('no more than 64 subterms in one %s condition' % whatCombiner.upper()) + # are we expecting to see something else? if index == endAt: break @@ -88,6 +97,9 @@ def _parseUnlockBit(text): def stringifyUnlockData(data): + if data == None: + return '' + kind = data[0] if kind == 'always': @@ -98,6 +110,27 @@ def stringifyUnlockData(data): return (' %s ' % kind).join(map(lambda x: '(%s)' % stringifyUnlockData(x), data[1])) +def packUnlockSpec(spec): + kind = data[0] + + if kind == 'always': + return '\x0F' + + elif kind == 'level': + k, world, level, secret = data + + one = (1 << 6) | (0x10 if secret else 0) | (worldNumber - 1) + + return chr(one) + chr(level - 1) + + elif kind == 'and' or kind == 'or': + terms = data[1] + cond = 2 if (kind == 'and') else 3 + one = (cond << 6) | (len(terms) - 1) + + return chr(one) + ''.join(map(packUnlockSpec, terms)) + + if __name__ == '__main__': p1 = parseUnlockText('((01-01 secret) and (01-02)) or (02-99 secret) or (01-01)') p2 = parseUnlockText('(1-1 secret) or ((1-2) and (1-3 secret)) or (2-1)') -- cgit v1.2.3 From e0aebec63011cbcb676caa92948e25c62fd6c17b Mon Sep 17 00:00:00 2001 From: Treeki Date: Wed, 25 Jul 2012 14:11:19 +0200 Subject: exporting of unlockable paths done --- src/exporter.py | 37 ++++++++++++++++++++++++++----------- src/mapdata.py | 6 ++++-- src/unlock.py | 8 ++++++-- 3 files changed, 36 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/exporter.py b/src/exporter.py index c4bb410..2f99a56 100644 --- a/src/exporter.py +++ b/src/exporter.py @@ -210,6 +210,7 @@ class KPMapExporter: if isinstance(eLayer, self.TileLayerExporter): data += u32.pack(0) + data += u32.pack(0xFF000000) # tileset name tileset = '/Maps/Texture/%s.bin' % layer.tileset @@ -228,6 +229,7 @@ class KPMapExporter: elif isinstance(eLayer, self.DoodadLayerExporter): data += u32.pack(1) + data += u32.pack(0xFF000000) # doodad list try: @@ -262,13 +264,14 @@ class KPMapExporter: elif isinstance(eLayer, self.PathLayerExporter): data += u32.pack(2) + data += zero32 # before we do anything, build the list of secret levels # we'll need that levelsWithSecrets = set() for path in layer.paths: - if hasattr(path, 'unlockSpec'): + if hasattr(path, 'unlockSpec') and path.unlockSpec is not None: self._checkSpecForSecrets(path.unlockSpec, levelsWithSecrets) # lists @@ -337,8 +340,8 @@ class KPMapExporter: if node.level: level1, level2 = node.level hasSecret = (1 if ((level1,level2) in levelsWithSecrets) else 0) - # i i b b b b: node type, Extra pointer, world, level, hasSecret, padding - data += struct.pack('>iibbbb', 2, 0, level1, level2, hasSecret, 0) + # i i i b b b b: node type, isNew, Extra pointer, world, level, hasSecret, padding + data += struct.pack('>iiibbbb', 2, 0, 0, level1, level2, hasSecret, 0) elif node.mapChange: data += u32.pack(3) # node type @@ -347,15 +350,17 @@ class KPMapExporter: requiredFixUps.append((len(data)+4, destMap)) stringsToAdd.add(destMap) - # i i b b b b: Extra pointer, dest map, map ID, foreign ID, transition, padding - data += struct.pack('>iibbbb', 0, 0, node.mapID, node.foreignID, node.transition, 0) + # i i i b b b b: isNew, Extra pointer, dest map, map ID, foreign ID, transition, padding + data += struct.pack('>iiibbbb', 0, 0, 0, node.mapID, node.foreignID, node.transition, 0) else: data += u32.pack(1) # node type - data += u32.pack(0) # Extra pointer + data += zero32 # isNew + data += zero32 # Extra pointer else: - data += u32.pack(0) # node type - data += u32.pack(0) # Extra pointer + data += zero32 # node type + data += zero32 # isNew + data += zero32 # Extra pointer pathIndices = {} @@ -376,7 +381,11 @@ class KPMapExporter: data += (zero32 * 4) - data += struct.pack('>bbbbfi', 1, 0, 0, 0, path.movementSpeed, path.animation) + available = 0 + if (not hasattr(path, 'unlockSpec')) or path.unlockSpec is None: + available = 3 + + data += struct.pack('>bbbbfi', available, 0, 0, 0, path.movementSpeed, path.animation) # now that we're almost done... pack the strings for string in stringsToAdd: @@ -416,6 +425,8 @@ class KPMapExporter: # first off, build a map of unlocks unlockLists = {} + + from unlock import stringifyUnlockData for path in self.map.pathLayer.paths: if not hasattr(path, 'unlockSpec'): @@ -424,6 +435,10 @@ class KPMapExporter: if spec is None: continue + # we stringify it first because the specs become lists when + # imported from the kpmap (not tuples) and those can't be + # used as dict keys + spec = stringifyUnlockData(spec) try: lst = unlockLists[spec] except KeyError: @@ -432,10 +447,10 @@ class KPMapExporter: lst.append(path) # now produce the thing - from unlock import packUnlockSpec + from unlock import parseUnlockText, packUnlockSpec for spec, lst in unlockLists.iteritems(): - data += packUnlockSpec(spec) + data += packUnlockSpec(parseUnlockText(spec)) data += chr(len(lst)) for p in lst: data += u16.pack(pathIndices[p]) diff --git a/src/mapdata.py b/src/mapdata.py index 0332280..d175046 100644 --- a/src/mapdata.py +++ b/src/mapdata.py @@ -464,6 +464,10 @@ class KPPath(object): # self.linkedLayer = mapObj.derefLayer(src['linkedLayer']) def __init__(self, startNode=None, endNode=None, cloneFrom=None): + # this is placed before the null ctor in case we load an old + # kpmap that didn't have unlockSpec + self.unlockSpec = None # always unlocked, by default + if startNode is None and endNode is None: # null ctor, ignore this # we're probably loaded from a file, so trust @@ -476,8 +480,6 @@ class KPPath(object): startNode.exits.append(self) endNode.exits.append(self) - self.unlockSpec = None # always unlocked, by default - if cloneFrom is None: self.animation = 0 else: diff --git a/src/unlock.py b/src/unlock.py index 446cd0a..cf3e6fd 100644 --- a/src/unlock.py +++ b/src/unlock.py @@ -11,6 +11,8 @@ def parseUnlockText(text): parsed = _parseUnlockBit(text.lower()) if parsed == ('always',): return None + else: + return parsed def _parseUnlockBit(text): # blank criterion @@ -110,7 +112,7 @@ def stringifyUnlockData(data): return (' %s ' % kind).join(map(lambda x: '(%s)' % stringifyUnlockData(x), data[1])) -def packUnlockSpec(spec): +def packUnlockSpec(data): kind = data[0] if kind == 'always': @@ -119,7 +121,7 @@ def packUnlockSpec(spec): elif kind == 'level': k, world, level, secret = data - one = (1 << 6) | (0x10 if secret else 0) | (worldNumber - 1) + one = (1 << 6) | (0x10 if secret else 0) | (world - 1) return chr(one) + chr(level - 1) @@ -198,6 +200,8 @@ class KPUnlockSpecDialog(QtGui.QDialog): self.layout.addWidget(self.statusLabel) self.layout.addWidget(self.buttons) self.setLayout(self.layout) + + self.spec = None def setSpec(self, spec): self.textBox.setText(stringifyUnlockData(spec)) -- cgit v1.2.3