From d64667a1a15dc9c13f1883d338c5cfd15074f4a9 Mon Sep 17 00:00:00 2001 From: Colin Noga Date: Fri, 30 Dec 2011 03:10:11 -0600 Subject: Hopefully this works. Associated Path/Node layers pretty much good to go, I think? --- src/editorui/editormain.py | 126 ++++++++++++++++++- src/editorui/paths.py | 4 +- src/mapdata.py | 117 ++++++++++++++++-- src/ui.py | 293 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 506 insertions(+), 34 deletions(-) diff --git a/src/editorui/editormain.py b/src/editorui/editormain.py index 5575453..46660f4 100644 --- a/src/editorui/editormain.py +++ b/src/editorui/editormain.py @@ -20,7 +20,7 @@ class KPMapScene(QtGui.QGraphicsScene): self.ticker.setLoopCount(0) self.ticker.setCurveShape(4) self.ticker.setFrameRange(0,100000) - self.ticker.valueChanged.connect(self.thing) + self.ticker.valueChanged.connect(self.viewportUpdateProxy) self.ticker.setUpdateInterval(16.6666666666667) self.grid = False @@ -64,7 +64,7 @@ class KPMapScene(QtGui.QGraphicsScene): @QtCore.pyqtSlot(int) - def thing(self, value): + def viewportUpdateProxy(self, value): self.views()[0].viewport().update() @@ -249,6 +249,68 @@ class KPMapScene(QtGui.QGraphicsScene): srcY += 1 destY += 24 + + elif isinstance(layer, KPPathLayer): + for pnLayer in reversed(KP.mainWindow.pathNodeList.getLayers()): + + # Render Tiles + left, top = pnLayer.cacheBasePos + width, height = pnLayer.cacheSize + right, bottom = left+width, top+height + + if not (width == 0) or (height == 0) or (right < areaLeftT) or (left > areaRightT) or (bottom < areaTopT) or (top > areaBottomT): + + drawLeft = int(max(areaLeftT, left)) + drawRight = int(min(areaRightT, right)) + + drawTop = int(max(areaTopT, top)) + drawBottom = int(min(areaBottomT, bottom)) + + srcY = drawTop - top + destY = drawTop * 24 + + baseSrcX = drawLeft - left + baseDestX = drawLeft * 24 + + rows = pnLayer.cache + tileset = KP.map.loadedTilesets[KP.map.pathNodeTileset] + tileList = tileset.tiles + + for y in xrange(drawTop, drawBottom): + srcX = baseSrcX + destX = baseDestX + row = rows[srcY] + + for x in xrange(drawLeft, drawRight): + tile = row[srcX] + if tile != -1: + painter.drawPixmap(destX, destY, tileList[tile]) + + srcX += 1 + destX += 24 + + srcY += 1 + destY += 24 + + # Render Doodads + try: + toDraw = visibleDoodadsByLayer[pnLayer] + except KeyError: + continue + + for item in reversed(toDraw): + + painter.save() + + if self.playing == False: + painter.setWorldTransform(item.sceneTransform(), True) + p = item._boundingRect + painter.drawPixmap(p.x(), p.y(), p.width(), p.height(), item.pixmap) + + else: + self.animateDoodad(painter, item) + painter.restore() + def animateDoodad(self, painter, item): @@ -317,7 +379,7 @@ class KPEditorWidget(QtGui.QGraphicsView): self.setVerticalScrollBar(self.yScrollBar) self.assignNewScene(scene) - + def assignNewScene(self, scene): self.setScene(scene) self.centerOn(0,0) @@ -326,6 +388,8 @@ class KPEditorWidget(QtGui.QGraphicsView): self.objectToPaint = None self.objectIDToPaint = None self.doodadToPaint = None + self.typeToPaint = None + self._resetPaintVars() def _resetPaintVars(self): @@ -430,6 +494,7 @@ class KPEditorWidget(QtGui.QGraphicsView): path = KPPath(sourceNode, destNode) KP.map.pathLayer.paths.append(path) + KP.mainWindow.pathNodeList.addLayer(path) item = KPEditorPath(path) self.scene().addItem(item) @@ -444,6 +509,7 @@ class KPEditorWidget(QtGui.QGraphicsView): node = KPNode() node.position = (x - 12, y - 12) KP.map.pathLayer.nodes.append(node) + KP.mainWindow.pathNodeList.addLayer(node) # Start node => Original path => New node => New path => End node @@ -464,6 +530,7 @@ class KPEditorWidget(QtGui.QGraphicsView): newPath = KPPath(node, endNode, origPath) KP.map.pathLayer.paths.append(newPath) + KP.mainWindow.pathNodeList.addLayer(newPath) pathItem = KPEditorPath(newPath) self.scene().addItem(pathItem) @@ -474,6 +541,7 @@ class KPEditorWidget(QtGui.QGraphicsView): node = KPNode() node.position = (x - 12, y - 12) KP.map.pathLayer.nodes.append(node) + KP.mainWindow.pathNodeList.addLayer(node) item = KPEditorNode(node) self.scene().addItem(item) @@ -499,6 +567,7 @@ class KPEditorWidget(QtGui.QGraphicsView): path = KPPath(sourceNode, node) KP.map.pathLayer.paths.append(path) + KP.mainWindow.pathNodeList.addLayer(path) pathItem = KPEditorPath(path) self.scene().addItem(pathItem) @@ -512,6 +581,57 @@ class KPEditorWidget(QtGui.QGraphicsView): self.paintingItem = item self.paintBeginPosition = (x - 12, y - 12) + elif isinstance(layer, KPPathTileLayer): + if self.typeToPaint == 'object': + paint = self.objectToPaint + if paint is None: return + + clicked = self.mapToScene(event.x(), event.y()) + x, y = clicked.x(), clicked.y() + if x < 0: x = 0 + if y < 0: y = 0 + + x = int(x / 24) + y = int(y / 24) + + obj = KPObject() + obj.position = (x,y) + obj.size = (1,1) + obj.tileset = layer.tileset + obj.kind = paint + obj.updateCache() + layer.objects.append(obj) + layer.updateCache() + + item = KPEditorObject(obj, layer) + self.scene().addItem(item) + + self.painting = obj + self.paintingItem = item + self.paintBeginPosition = (x, y) + + elif self.typeToPaint == 'doodad': + + paint = self.doodadToPaint + if paint is None: return + + clicked = self.mapToScene(event.x(), event.y()) + x, y = clicked.x(), clicked.y() + if x < 0: x = 0 + if y < 0: y = 0 + + obj = KPDoodad() + obj.position = [x,y] + obj.source = paint + obj.setDefaultSize() + layer.doodads.append(obj) + + item = KPEditorDoodad(obj, layer) + self.scene().addItem(item) + + self.painting = obj + self.paintingItem = item + self.paintBeginPosition = (x, y) def _movedWhilePainting(self, event): diff --git a/src/editorui/paths.py b/src/editorui/paths.py index 617e981..55bfac0 100644 --- a/src/editorui/paths.py +++ b/src/editorui/paths.py @@ -325,6 +325,7 @@ class KPEditorNode(KPEditorItem): layer = KP.map.pathLayer layer.nodes.remove(node) + KP.mainWindow.pathNodeList.removeLayer(node) if len(node.exits) == 2: # let's try to join the two! @@ -354,6 +355,7 @@ class KPEditorNode(KPEditorItem): if not nope: joinedPath = KPPath(start, end, pathOne) + KP.mainWindow.pathNodeList.addLayer(joinedPath) layer.paths.append(joinedPath) item = KPEditorPath(joinedPath) self.scene().addItem(item) @@ -501,7 +503,6 @@ class KPEditorPath(QtGui.QGraphicsLineItem): self.setPalette(palette) - class UnlockButton(QtGui.QPushButton): def __init__(self, pathRef): QtGui.QPushButton.__init__(self) @@ -682,6 +683,7 @@ class KPEditorPath(QtGui.QGraphicsLineItem): path = self._pathRef() layer = KP.map.pathLayer + KP.mainWindow.pathNodeList.removeLayer(path) layer.paths.remove(path) diff --git a/src/mapdata.py b/src/mapdata.py index 23c6b83..6f71daf 100644 --- a/src/mapdata.py +++ b/src/mapdata.py @@ -147,6 +147,108 @@ class KPTileLayer(KPLayer): y += 1 +@mapfile.dumpable('associate_layer') +class KPPathTileLayer(KPLayer): + __dump_attribs__ = KPLayer.__dump_attribs__ + ('tileset', 'objects', 'doodads', 'associate') + + def _load(self, mapObj, src): + self.updateCache() + + def __repr__(self): + return "" % (self.tileset, self.associate) + + def __init__(self, pathnode): + KPLayer.__init__(self) + self.tileset = KP.map.pathNodeTileset + self.objects = [] + self.doodads = [] + self.associate = pathnode + + self.cache = ['DUMMY_FLAG'] + self.updateCache() + + self.icon = KP.icon('LayerTile') + + def associate(self): + return self.associate + + def _visibilityChanged(self, value): + for obj in self.objects: + item = obj.qtItem + if item: + item.setVisible(value) + + for obj in self.doodads: + item = obj.qtItem + if item: + item.setVisible(value) + + def setActivated(self, value, listToUse=None): + flag1 = QtGui.QGraphicsItem.ItemIsSelectable + flag2 = QtGui.QGraphicsItem.ItemIsMovable + + if listToUse is None: + listToUse = self.objects + self.doodads + + for obj in listToUse: + item = obj.qtItem + if item: + item.setFlag(flag1, value) + item.setFlag(flag2, value) + + def updateCache(self): + if len(self.objects) == 0: + if len(self.cache) != 0: + self.cache = [] + self.cacheBasePos = (0,0) + self.cacheSize = (0,0) + return + + x1, x2 = MAP_SIZE_IN_TILES[0] - 1, 0 + y1, y2 = MAP_SIZE_IN_TILES[1] - 1, 0 + + for obj in self.objects: + x, y = obj.position + w, h = obj.size + right, bottom = (x+w-1), (y+h-1) + + if x < x1: + x1 = x + if y < y1: + y1 = y + if right > x2: + x2 = right + if bottom > y2: + y2 = bottom + + + # create the cache + # I was going to just resize it, but setting every tile to -1 + # in Python would probably be slower than creating a new one ... + size = (x2 - x1 + 1, y2 - y1 + 1) + width, height = size + + cache = [[-1 for i in xrange(width)] for j in xrange(height)] + self.cache = cache + + self.cacheBasePos = (x1, y1) + self.cacheSize = size + + # now generate the thing + for obj in self.objects: + oX, oY = obj.position + baseX = oX - x1 + y = oY - y1 + + for row in obj.cache: + destRow = cache[y] + x = baseX + for tile in row: + destRow[x] = tile + x += 1 + y += 1 + + @mapfile.dumpable('doodad') class KPDoodad(object): __dump_attribs__ = ('position', 'size', 'angle', 'animations') @@ -241,11 +343,6 @@ class KPDoodadLayer(KPLayer): item.setVisible(value) -class KPNodeAction(object): - def __init__(self): - pass - - @mapfile.dumpable('node') class KPNode(object): __dump_attribs__ = ( @@ -274,7 +371,6 @@ class KPNode(object): return True if (self.level or self.mapChange or len(self.exits) != 2) else False - @mapfile.dumpable('path') class KPPath(object): __dump_attribs__ = ('unlocks', 'secret', 'animation', 'movementSpeed') @@ -369,7 +465,7 @@ class KPPathLayer(KPLayer): @mapfile.dumpable('map_root') class KPMap(object): - __dump_attribs__ = ('layers', 'nextLayerNumber', 'doodadDefinitions') + __dump_attribs__ = ('layers', 'nextLayerNumber', 'doodadDefinitions', 'pathNodeTileset') def _preload(self, src): # we need this early so we can use the deref methods! @@ -413,6 +509,13 @@ class KPMap(object): self.loadTilesets() + try: + self.pathNodeTileset = self.tilesets.keys()[0] + except: + # TODO: This should probably error out or something. Seriously. + # It'll ruin the path or object layers if there are NO tilesets. + self.pathNodeTileset = '' + # LAYERS class LayerModel(QtCore.QAbstractListModel): diff --git a/src/ui.py b/src/ui.py index 9656137..13d0039 100644 --- a/src/ui.py +++ b/src/ui.py @@ -7,6 +7,202 @@ import os import os.path +class KPPathNodeList(QtGui.QWidget): + + class KPPathNodeItem(QtGui.QTreeWidgetItem): + def __init__(self, parent, layer, associate): + QtGui.QTreeWidgetItem.__init__(self, parent) + + self.layer = layer + self.associate = associate + + self.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled) + + + def data(self, index, role=Qt.DisplayRole): + + if role == Qt.DecorationRole: + if isinstance(self.associate, KPNode): + node = self.associate + + if node.level: + return QtGui.QIcon('LevelNodeIcon') + elif node.mapChange != None: + return QtGui.QIcon('ExitNodeIcon') + elif len(node.exits) != 2: + return QtGui.QIcon('StopNodeIcon') + else: + return QtGui.QIcon('ThroughNodeIcon') + + else: + return QtGui.QIcon('PathIcon') + + elif role == Qt.DisplayRole: + if isinstance(self.associate, KPNode): + node = self.associate + + if node.level: + return "Level Node: {0}".format(node.level) + elif node.mapChange != None: + return "Exit to World {0}, entrance {1}".format(mapID, foreignID) + elif len(node.exits) == 3: + return "3-way Junction" + elif len(node.exits) == 4: + return "4-way Junction" + elif len(node.exits) > 4: + return "Illegal Node" + else: + return "Pass Through Node" + + else: + AnimationList = ["Walk", "WalkSand", "WalkSnow", "WalkIce", + "Jump", "JumpSand", "JumpSnow", "SpinJump", + "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) + + else: + return QtGui.QTreeWidgetItem.data(self, index, role) + + + def layer(self): + return self.layer + + def associate(self): + return self.associate + + + def __init__(self): + QtGui.QWidget.__init__(self) + + self.layout = QtGui.QVBoxLayout() + self.layout.setSpacing(0) + + self.tree = QtGui.QTreeWidget() + self.tree.setColumnCount(1) + self.tree.setDragEnabled(True) + self.tree.setDragDropMode(self.tree.InternalMove) + self.tree.itemClicked.connect(self.handleRowChanged) + self.layout.addWidget(self.tree) + + self.toolbar = QtGui.QToolBar() + self.layout.addWidget(self.toolbar) + + self.setupToolbar(self.toolbar) + self.setLayout(self.layout) + + + def setupToolbar(self, tb): + self.actAddFolder = tb.addAction(KP.icon('LayerNewTile'), 'Add Folder', self.addFolder) + self.actRemoveFolder = tb.addAction(KP.icon('LayerNewObjects'), 'Remove Folder', self.removeFolder) + self.selectTileset = tb.addAction(KP.icon('LayerNewTile'), 'Select Tileset', self.setTileset) + + selectedLayerChanged = QtCore.pyqtSignal(KPLayer) + + @QtCore.pyqtSlot() + def handleRowChanged(self): + item = self.tree.currentItem() + try: + self.selectedLayerChanged.emit(item.layer) + except: + pass + + def addFolder(self): + item = QtGui.QTreeWidgetItem(self.tree) + item.setIcon(0, QtGui.QIcon('Folder')) + item.setText(0, 'Untitled Folder') + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | Qt.ItemIsEditable | Qt.ItemIsEnabled) + + def removeFolder(self): + item = self.tree.currentItem() + if not isinstance(item, self.KPPathNodeItem): + kids = item.takeChildren() + parent = item.parent() + if parent: + parent.takeChild(item) + else: + self.tree.takeTopLevelItem(self.tree.currentIndex().row()) + + self.tree.addTopLevelItems(kids) + + def setTileset(self): + from dialogs import KPTilesetChooserDialog + + tilesetName = KPTilesetChooserDialog.run('Choose a tileset for the path/node layers') + if tilesetName is None: + return + + KP.map.pathNodeTileset = tilesetName + + def addLayer(self, associate): + layer = KPPathTileLayer(associate) + item = self.KPPathNodeItem(self.tree, layer, associate) + + def removeLayer(self, associate): + trash = None + + for itemI in xrange(self.tree.topLevelItemCount()): + item = self.tree.topLevelItem(itemI) + + if item.associate == associate: + trash = item + else: + trash = self.removeRecursor(item, associate) + + if trash != None: + break + + parent = trash.parent() + if parent: + parent.takeChild(trash) + else: + self.tree.takeTopLevelItem(self.tree.indexOfTopLevelItem(trash)) + + def removeRecursor(self, parent, associate): + trash = None + + for itemI in xrange(parent.childCount()): + item = parent.child(itemI) + + if item.associate == associate: + trash = item + else: + trash = self.removeRecursor(item, associate) + + if trash != None: + return trash + + return None + + def getLayers(self): + layerList = [] + for itemI in xrange(self.tree.topLevelItemCount()): + item = self.tree.topLevelItem(itemI) + + if not isinstance(item, self.KPPathNodeItem): + self.recursiveLayerRetriever(item, layerList) + else: + layerList.append(item.layer) + + return layerList + + def recursiveLayerRetriever(self, parent, layerList): + + for itemI in xrange(parent.childCount()): + item = parent.child(itemI) + + if not isinstance(item, self.KPPathNodeItem): + self.recursiveLayerRetriever(item, layerList) + else: + layerList.append(item.layer) + + class KPLayerList(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self) @@ -29,7 +225,7 @@ class KPLayerList(QtGui.QWidget): def updateModel(self): self.model = KP.map.layerModel self.listView.setModel(self.model) - self.listView.selectionModel().currentRowChanged.connect(self.handleRowChanged) + self.listView.clicked.connect(self.handleRowChanged) self.setButtonStates() @@ -63,12 +259,12 @@ class KPLayerList(QtGui.QWidget): def selectedLayerIndex(self): return self.listView.selectionModel().currentIndex().row() def selectedLayer(self): - return KP.map.layers[self.listView.selectionModel().currentIndex().row()] + return KP.map.layers[self.selectedLayerIndex()] selectedLayerChanged = QtCore.pyqtSignal(KPLayer) - @QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex) - def handleRowChanged(self, current, previous): + @QtCore.pyqtSlot(QtCore.QModelIndex) + def handleRowChanged(self, current): self.selectedLayerChanged.emit(KP.map.layers[current.row()]) self.setButtonStates() @@ -466,8 +662,9 @@ class KPMainWindow(QtGui.QMainWindow): self.lf = l.addAction('Move Layer to Top', self.layerList.moveTop, QKeySequence("Ctrl+Shift+Up")) self.lg = l.addAction('Move Layer to Bottom', self.layerList.moveBottom, QKeySequence("Ctrl+Shift+Down")) l.addSeparator() - self.lh = l.addAction('Add Tileset...', self.moveTilesetToFolder, QKeySequence("Ctrl+Shift+T")) self.li = l.addAction('Add Doodad...', self.doodadSelector.addDoodadFromFile, QKeySequence("Ctrl+Shift+R")) + self.lh = l.addAction('Add Tileset...', self.moveTilesetToFolder, QKeySequence("Ctrl+Shift+T")) + self.lj = l.addAction('Change Tileset...', self.changeTileset, QKeySequence("Ctrl+Shift+Alt+T")) a = mb.addMenu('Animate') self.aa = a.addAction('Play Animations', self.playAnim, QKeySequence("Ctrl+P")) @@ -512,6 +709,11 @@ class KPMainWindow(QtGui.QMainWindow): self.layerList.selectedLayerChanged.connect(self.handleSelectedLayerChanged) self.layerList.playPaused.connect(self.playAnim) + self.pathNodeList = KPPathNodeList() + self.pathNodeDock = QtGui.QDockWidget('Path/Node Layers') + self.pathNodeDock.setWidget(self.pathNodeList) + self.pathNodeList.selectedLayerChanged.connect(self.handleSelectedPathNodeLayerChanged) + self.objectSelector = KPObjectSelector() self.objectSelector.objChanged.connect(self.handleSelectedObjectChanged) @@ -527,6 +729,7 @@ class KPMainWindow(QtGui.QMainWindow): self.doodadSelectorDock.hide() self.addDockWidget(Qt.RightDockWidgetArea, self.layerListDock) + self.addDockWidget(Qt.RightDockWidgetArea, self.pathNodeDock) self.addDockWidget(Qt.RightDockWidgetArea, self.objectSelectorDock) self.addDockWidget(Qt.RightDockWidgetArea, self.doodadSelectorDock) @@ -553,12 +756,14 @@ class KPMainWindow(QtGui.QMainWindow): - ##################### - # Slots for Widgets # - ##################### +##################### +# Slots for Widgets # +##################### @QtCore.pyqtSlot(KPLayer) def handleSelectedLayerChanged(self, layer): + self.pathNodeList.tree.selectionModel().clearSelection() + self.scene.setCurrentLayer(layer) showObjects, showDoodads = False, False @@ -576,23 +781,45 @@ class KPMainWindow(QtGui.QMainWindow): self.doodadSelectorDock.setVisible(showDoodads) + @QtCore.pyqtSlot(KPLayer) + def handleSelectedPathNodeLayerChanged(self, layer): + + self.layerList.listView.selectionModel().clearSelection() + + self.scene.setCurrentLayer(layer) + + KP.map.reloadTileset(KP.map.pathNodeTileset) + + self.objectSelector.setModel(KP.map.loadedTilesets[KP.map.pathNodeTileset].getModel()) + + self.objectSelectorDock.setVisible(True) + self.doodadSelectorDock.setVisible(True) + + + @QtCore.pyqtSlot(int, KPTileObject) def handleSelectedObjectChanged(self, index, obj): + self.doodadSelector.listView.selectionModel().clearSelection() + self.editor.objectToPaint = obj self.editor.objectIDToPaint = index + self.editor.typeToPaint = 'object' @QtCore.pyqtSlot(object) def handleSelectedDoodadChanged(self, doodad): + self.objectSelector.listView.selectionModel().clearSelection() + self.editor.doodadToPaint = doodad + self.editor.typeToPaint = 'doodad' - ######################## - # Slots for Menu Items # - ######################## +######################## +# Slots for Menu Items # +######################## - # File - ######################## +# File +######################## def newMap(self): if self.checkDirty(): return @@ -680,8 +907,8 @@ class KPMainWindow(QtGui.QMainWindow): ScreenshotImage.save(fn, 'PNG', 50) - # Edit - ######################## +# Edit +######################## @QtCore.pyqtSlot() def selectAll(self): @@ -696,8 +923,8 @@ class KPMainWindow(QtGui.QMainWindow): self.scene.clearSelection() - # Layers - ######################## +# Layers +######################## @QtCore.pyqtSlot() def moveTilesetToFolder(self): @@ -722,8 +949,28 @@ class KPMainWindow(QtGui.QMainWindow): KP.map.loadedTilesets[name] = KPTileset.loadFromArc(path) - # Animate - ######################## + @QtCore.pyqtSlot() + def changeTileset(self): + + layer = self.layerList.selectedLayer() + + if not isinstance(layer, KPTileLayer): + return + + + from dialogs import KPTilesetChooserDialog + + tilesetName = KPTilesetChooserDialog.run('Choose a tileset to change to') + if tilesetName is None: + return + + KPTileLayer.tileset = tilesetName + + self.objectSelector.setModel(KP.map.loadedTilesets[layer.tileset].getModel()) + + +# Animate +######################## @QtCore.pyqtSlot() def playAnim(self): @@ -834,8 +1081,8 @@ class KPMainWindow(QtGui.QMainWindow): settings.setValue('AnimationPresetData', mapfile.dump([])) - # Window - ######################## +# Window +######################## @QtCore.pyqtSlot() def ZoomActual(self): @@ -893,8 +1140,8 @@ class KPMainWindow(QtGui.QMainWindow): pass - # Help - ######################## +# Help +######################## @QtCore.pyqtSlot(bool) def aboutDialog(self): -- cgit v1.2.3