Fixed crash
All checks were successful
Build Dusk / build-linux (push) Successful in 51s
Build Dusk / build-psp (push) Successful in 56s

This commit is contained in:
2025-11-16 17:24:54 -06:00
parent 1b741a81e5
commit ae941a0fdb
12 changed files with 215 additions and 225 deletions

View File

@@ -25,9 +25,9 @@ class Camera:
(self.pixelsPerUnit * self.scale) * math.tan(math.radians(self.fov / 2.0))
)
lookAt = [
self.parent.parent.map.position[0] * TILE_WIDTH,
self.parent.parent.map.position[1] * TILE_HEIGHT,
self.parent.parent.map.position[2] * TILE_DEPTH,
self.parent.map.position[0] * TILE_WIDTH,
self.parent.map.position[1] * TILE_HEIGHT,
self.parent.map.position[2] * TILE_DEPTH,
]
aspectRatio = vw / vh

View File

@@ -1,14 +1,53 @@
from editortool.map.chunk import Tile
import json
import os
from dusk.event import Event
class Chunk:
def __init__(self, x=0, y=0, z=0, size_x=1, size_y=1, size_z=1):
def __init__(self, map, x, y, z):
self.map = map
self.x = x
self.y = y
self.z = z
self.data = [[[Tile() for _ in range(size_z)] for _ in range(size_y)] for _ in range(size_x)]
self.current = {}
self.original = {}
self.onChunkData = Event()
def draw(self):
for x in range(len(self.data)):
for y in range(len(self.data[x])):
for z in range(len(self.data[x][y])):
self.data[x][y][z].draw(x, y, z)
def load(self):
fname = self.getFilename()
if not fname or not os.path.exists(fname):
self.new()
return
try:
with open(fname, 'r') as f:
self.current = json.load(f)
self.original = json.loads(json.dumps(self.current)) # Deep copy
self.onChunkData.invoke(self.current)
except Exception as e:
raise RuntimeError(f"Failed to load chunk file: {e}")
def save(self):
fname = self.getFilename()
if not fname:
raise ValueError("No filename specified for saving chunk.")
try:
with open(fname, 'w') as f:
json.dump(self.current, f, indent=2)
self.original = json.loads(json.dumps(self.current)) # Deep copy
except Exception as e:
raise RuntimeError(f"Failed to save chunk file: {e}")
def new(self):
self.current = {}
self.original = {}
self.onChunkData.invoke(self.current)
def isDirty(self):
return json.dumps(self.current, sort_keys=True) != json.dumps(self.original, sort_keys=True)
def getFilename(self):
if not self.map or not hasattr(self.map, 'getMapDirectory'):
return None
dir_path = self.map.getMapDirectory()
if dir_path is None:
return None
return f"{dir_path}/{self.x}_{self.y}_{self.z}.json"

View File

@@ -1,53 +0,0 @@
import json
import os
from dusk.event import Event
class ChunkData:
def __init__(self, mapData, x, y, z):
self.mapData = mapData
self.x = x
self.y = y
self.z = z
self.current = {}
self.original = {}
self.onChunkData = Event()
def load(self):
fname = self.getFilename()
if not fname or not os.path.exists(fname):
self.new()
return
try:
with open(fname, 'r') as f:
self.current = json.load(f)
self.original = json.loads(json.dumps(self.current)) # Deep copy
self.onChunkData.invoke(self.current)
except Exception as e:
raise RuntimeError(f"Failed to load chunk file: {e}")
def save(self):
fname = self.getFilename()
if not fname:
raise ValueError("No filename specified for saving chunk.")
try:
with open(fname, 'w') as f:
json.dump(self.current, f, indent=2)
self.original = json.loads(json.dumps(self.current)) # Deep copy
except Exception as e:
raise RuntimeError(f"Failed to save chunk file: {e}")
def new(self):
self.current = {}
self.original = {}
self.onChunkData.invoke(self.current)
def isDirty(self):
return json.dumps(self.current, sort_keys=True) != json.dumps(self.original, sort_keys=True)
def getFilename(self):
if not self.mapData or not hasattr(self.mapData, 'getMapDirectory'):
return None
dir_path = self.mapData.getMapDirectory()
if dir_path is None:
return None
return f"{dir_path}/{self.x}_{self.y}_{self.z}.json"

View File

@@ -36,7 +36,11 @@ class ChunkPanel(QWidget):
QTreeWidgetItem(parentItem2, ["Tile C"])
QTreeWidgetItem(parentItem2, ["Tile D"])
self.tree.expandAll() # Expand by default, remove if you want collapsed
layout.addWidget(self.tree, 1) # Give tree stretch factor so it expands
layout.addWidget(self.tree) # Removed invalid stretch factor
# Add stretch so tree expands
layout.setStretchFactor(grid, 0)
layout.setStretchFactor(self.tree, 1)
# Event subscriptions
self.btnN.clicked.connect(lambda: self.parent.map.moveRelative(0, -1, 0))

View File

@@ -2,9 +2,6 @@ from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QOpenGLWidget
from OpenGL.GL import *
from OpenGL.GLU import *
from editortool.map.selectbox import SelectBox
from editortool.map.camera import Camera
from editortool.map.grid import Grid
class GLWidget(QOpenGLWidget):
def __init__(self, parent):
@@ -13,9 +10,6 @@ class GLWidget(QOpenGLWidget):
self.timer = QTimer(self)
self.timer.timeout.connect(self.update)
self.timer.start(16) # ~60 FPS
self.camera = Camera(self)
self.grid = Grid()
self.selectBox = SelectBox(self)
def initializeGL(self):
glClearColor(0.392, 0.584, 0.929, 1.0)
@@ -36,7 +30,6 @@ class GLWidget(QOpenGLWidget):
w = 1
glViewport(0, 0, w, h)
self.camera.setup(w, h)
self.grid.draw()
self.selectBox.draw()
self.parent.camera.setup(w, h)
self.parent.grid.draw()
self.parent.selectBox.draw()

View File

@@ -1,15 +1,135 @@
import json
from dusk.event import Event
from editortool.map.mapdata import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QTimer
import os
from editortool.map.chunk import Chunk
from editortool.map.mapdefs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH
import traceback
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../../assets/map/')
EDITOR_CONFIG_PATH = os.path.join(os.path.dirname(__file__), '.editor')
class Map:
def __init__(self, parent):
self.parent = parent
self.data = {}
self.dataOriginal = {}
self.position = [0, 0, 0] # x, y, z
self.positionEvent = Event()
self.chunks = {}
self.onMapData = Event()
self.onPositionChange = Event()
self.mapFileName = None
self.lastFile = None
index = 0
for x in range(MAP_WIDTH):
for y in range(MAP_HEIGHT):
for z in range(MAP_DEPTH):
self.chunks[index] = Chunk(self, x, y, z)
index += 1
QTimer.singleShot(16, self.loadLastFile)
def loadLastFile(self):
if not os.path.exists(EDITOR_CONFIG_PATH):
return
try:
with open(EDITOR_CONFIG_PATH, 'r') as f:
config = json.load(f)
lastFile = config.get('lastFile')
lastPosition = config.get('lastPosition')
if lastFile and os.path.exists(lastFile):
self.load(lastFile)
if lastPosition and isinstance(lastPosition, list) and len(lastPosition) == 3:
self.moveTo(*lastPosition)
except Exception:
traceback.print_exc()
def updateEditorConfig(self):
print("Updating editor config...")
try:
mapFileName = self.getMapFilename()
config = {
'lastFile': mapFileName if mapFileName else "",
'lastPosition': self.position
}
config_dir = os.path.dirname(EDITOR_CONFIG_PATH)
if not os.path.exists(config_dir):
os.makedirs(config_dir, exist_ok=True)
with open(EDITOR_CONFIG_PATH, 'w') as f:
json.dump(config, f, indent=2)
except Exception:
traceback.print_exc()
def newFile(self):
self.data = {}
self.dataOriginal = {}
self.mapFileName = None
self.lastFile = None
for chunk in self.chunks.values():
chunk.new()
self.moveTo(0, 0, 0)
self.onMapData.invoke(self.data)
self.updateEditorConfig()
def save(self, fname=None):
if not self.getMapFilename() and fname is None:
filePath, _ = QFileDialog.getSaveFileName(None, "Save Map File", MAP_DEFAULT_PATH, "Map Files (*.json)")
if not filePath:
return
self.mapFileName = filePath
if fname:
self.mapFileName = fname
try:
with open(self.getMapFilename(), 'w') as f:
json.dump(self.data, f, indent=2)
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
for chunk in self.chunks.values():
chunk.save()
self.updateEditorConfig()
except Exception as e:
traceback.print_exc()
QMessageBox.critical(None, "Save Error", f"Failed to save map file:\n{e}")
def load(self, fileName):
try:
with open(fileName, 'r') as f:
self.data = json.load(f)
self.mapFileName = fileName
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
for chunk in self.chunks.values():
chunk.load()
self.onMapData.invoke(self.data)
self.updateEditorConfig()
except Exception as e:
traceback.print_exc()
QMessageBox.critical(None, "Load Error", f"Failed to load map file:\n{e}")
def isMapFileDirty(self):
return json.dumps(self.data, sort_keys=True) != json.dumps(self.dataOriginal, sort_keys=True)
def isDirty(self):
return self.isMapFileDirty() or self.anyChunksDirty()
def getMapFilename(self):
return self.mapFileName if self.mapFileName and os.path.exists(self.mapFileName) else None
def getMapDirectory(self):
fname = self.getMapFilename()
if not fname or not fname.endswith('.json'):
return None
return fname[:-5] # Remove '.json' extension
def anyChunksDirty(self):
for chunk in self.chunks.values():
if chunk.isDirty():
return True
return False
def moveTo(self, x, y, z):
self.position = [x, y, z]
self.positionEvent.invoke(self.position)
self.onPositionChange.invoke(self.position)
self.updateEditorConfig()
def moveRelative(self, x, y, z):
self.moveTo(

View File

@@ -1,109 +0,0 @@
import json
from dusk.event import Event
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QTimer
import os
from editortool.map.chunkdata import ChunkData
from editortool.map.mapdefs import MAP_WIDTH, MAP_HEIGHT, MAP_DEPTH
import traceback
MAP_DEFAULT_PATH = os.path.join(os.path.dirname(__file__), '../../../assets/map/')
EDITOR_CONFIG_PATH = os.path.join(os.path.dirname(__file__), '.editor')
class MapData:
def __init__(self):
self.data = {}
self.dataOriginal = {}
self.onMapData = Event()
self.mapFileName = None
self.chunks = {}
index = 0
for x in range(MAP_WIDTH):
for y in range(MAP_HEIGHT):
for z in range(MAP_DEPTH):
self.chunks[index] = ChunkData(self, x, y, z)
index += 1
QTimer.singleShot(16, self.loadLastFile)
def loadLastFile(self):
if os.path.exists(EDITOR_CONFIG_PATH):
try:
with open(EDITOR_CONFIG_PATH, 'r') as f:
config = json.load(f)
lastFile = config.get('lastFile')
if lastFile and os.path.exists(lastFile):
self.load(lastFile)
except Exception:
pass
def updateEditorConfig(self):
try:
config = {'lastFile': self.getMapFilename() if self.getMapFilename() else ""}
with open(EDITOR_CONFIG_PATH, 'w') as f:
json.dump(config, f, indent=2)
except Exception:
pass
def newFile(self):
self.data = {}
self.dataOriginal = {}
self.mapFileName = None
for chunk in self.chunks.values():
chunk.new()
self.onMapData.invoke(self.data)
self.updateEditorConfig()
def save(self, fname=None):
if not self.getMapFilename() and fname is None:
filePath, _ = QFileDialog.getSaveFileName(None, "Save Map File", MAP_DEFAULT_PATH, "Map Files (*.json)")
if not filePath:
return
self.mapFileName = filePath
if fname:
self.mapFileName = fname
try:
with open(self.getMapFilename(), 'w') as f:
json.dump(self.data, f, indent=2)
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
for chunk in self.chunks.values():
chunk.save()
self.updateEditorConfig()
except Exception as e:
traceback.print_exc()
QMessageBox.critical(None, "Save Error", f"Failed to save map file:\n{e}")
def load(self, fileName):
try:
with open(fileName, 'r') as f:
self.data = json.load(f)
self.mapFileName = fileName
self.dataOriginal = json.loads(json.dumps(self.data)) # Deep copy
for chunk in self.chunks.values():
chunk.load()
self.onMapData.invoke(self.data)
self.updateEditorConfig()
except Exception as e:
traceback.print_exc()
QMessageBox.critical(None, "Load Error", f"Failed to load map file:\n{e}")
def isMapFileDirty(self):
return json.dumps(self.data, sort_keys=True) != json.dumps(self.dataOriginal, sort_keys=True)
def isDirty(self):
return self.isMapFileDirty() or self.anyChunksDirty()
def getMapFilename(self):
return self.mapFileName if self.mapFileName else None
def getMapDirectory(self):
fname = self.getMapFilename()
if not fname or not fname.endswith('.json'):
return None
return fname[:-5] # Remove '.json' extension
def anyChunksDirty(self):
for chunk in self.chunks.values():
if chunk.isDirty():
return True
return False

View File

@@ -43,8 +43,8 @@ class MapInfoPanel(QWidget):
self.tileXInput.returnPressed.connect(self.goToPosition)
self.tileYInput.returnPressed.connect(self.goToPosition)
self.tileZInput.returnPressed.connect(self.goToPosition)
self.parent.map.positionEvent.sub(self.updatePositionLabels)
self.parent.mapData.onMapData.sub(self.onMapData)
self.parent.map.onPositionChange.sub(self.updatePositionLabels)
self.parent.map.onMapData.sub(self.onMapData)
self.mapTitleInput.textChanged.connect(self.onMapNameChange)
# Initial label setting
@@ -55,7 +55,7 @@ class MapInfoPanel(QWidget):
x = int(self.tileXInput.text())
y = int(self.tileYInput.text())
z = int(self.tileZInput.text())
map.moveTo(x, y, z)
self.parent.map.moveTo(x, y, z)
except ValueError:
QMessageBox.warning(self, "Invalid Input", "Please enter valid integer coordinates.")
@@ -80,4 +80,4 @@ class MapInfoPanel(QWidget):
self.mapTitleInput.setText(data.get("mapName", ""))
def onMapNameChange(self, text):
self.parent.mapData.data['mapName'] = text
self.parent.map.data['mapName'] = text

View File

@@ -1,13 +1,14 @@
import os
from PyQt5.QtWidgets import QAction, QMenuBar, QFileDialog
from editortool.map.mapdata import MAP_DEFAULT_PATH
from editortool.map.map import MAP_DEFAULT_PATH
class MapToolbar:
class MapMenubar:
def __init__(self, parent):
self.parent = parent
self.menubar = parent.menuBar()
self.fileMenu = self.menubar.addMenu("File")
self.menubar = QMenuBar(parent)
parent.setMenuBar(self.menubar)
self.fileMenu = self.menubar.addMenu("File")
self.actionNew = QAction("New", parent)
self.actionOpen = QAction("Open", parent)
self.actionSave = QAction("Save", parent)
@@ -23,17 +24,17 @@ class MapToolbar:
self.fileMenu.addAction(self.actionSaveAs)
def newFile(self):
self.parent.mapData.newFile()
self.parent.map.newFile()
def openFile(self):
filePath, _ = QFileDialog.getOpenFileName(self.menubar, "Open Map File", MAP_DEFAULT_PATH, "Map Files (*.json)")
if filePath:
self.parent.mapData.load(filePath)
self.parent.map.load(filePath)
def saveFile(self):
self.parent.mapData.save()
self.parent.map.save()
def saveAsFile(self):
filePath, _ = QFileDialog.getSaveFileName(self.menubar, "Save Map File As", MAP_DEFAULT_PATH, "Map Files (*.json)")
if filePath:
self.parent.mapData.save(filePath)
self.parent.map.save(filePath)

View File

@@ -10,9 +10,9 @@ class SelectBox:
def draw(self):
position = [
self.parent.parent.map.position[0] * TILE_WIDTH - (TILE_WIDTH / 2.0),
self.parent.parent.map.position[1] * TILE_HEIGHT - (TILE_HEIGHT / 2.0),
self.parent.parent.map.position[2] * TILE_DEPTH - (TILE_DEPTH / 2.0)
self.parent.map.position[0] * TILE_WIDTH - (TILE_WIDTH / 2.0),
self.parent.map.position[1] * TILE_HEIGHT - (TILE_HEIGHT / 2.0),
self.parent.map.position[2] * TILE_DEPTH - (TILE_DEPTH / 2.0)
]
vertices = [

View File

@@ -9,10 +9,10 @@ class StatusBar(QStatusBar):
self.addWidget(self.leftLabel, 1)
self.addPermanentWidget(self.rightLabel)
parent.mapData.onMapData.sub(self.onMapData)
parent.map.onMapData.sub(self.onMapData)
def setStatus(self, message):
self.leftLabel.setText(message)
def onMapData(self, data):
self.rightLabel.setText(self.parent.mapData.mapFileName if self.parent.mapData.mapFileName else "Untitled.json")
self.rightLabel.setText(self.parent.map.mapFileName if self.parent.map.mapFileName else "Untitled.json")