summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exporter.py111
-rw-r--r--src/mapdata.py5
-rw-r--r--src/ui.py6
-rw-r--r--src/unlock.py37
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
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)')