diff options
-rw-r--r-- | src/exporter.py | 111 | ||||
-rw-r--r-- | src/mapdata.py | 5 | ||||
-rw-r--r-- | src/ui.py | 6 | ||||
-rw-r--r-- | src/unlock.py | 37 |
4 files changed, 89 insertions, 70 deletions
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 @@ -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)') |