Experimental: Add generate script for when a game is launched. It's partially for debugging, and also for players to customize games beyond the scope of the launcher.
This commit is contained in:
		@@ -284,6 +284,7 @@ class MenuDialog(QDialog):
 | 
				
			|||||||
        super().__init__(parent)
 | 
					        super().__init__(parent)
 | 
				
			||||||
        self.setWindowTitle(title)
 | 
					        self.setWindowTitle(title)
 | 
				
			||||||
        self.dialogOptions = options
 | 
					        self.dialogOptions = options
 | 
				
			||||||
 | 
					        self.generateScript = False  # Flag to indicate script generation
 | 
				
			||||||
        self.init_dialog_ui()
 | 
					        self.init_dialog_ui()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def init_dialog_ui(self):
 | 
					    def init_dialog_ui(self):
 | 
				
			||||||
@@ -316,11 +317,28 @@ class MenuDialog(QDialog):
 | 
				
			|||||||
            setattr(self, f"{key}_widget", dialogWidget)
 | 
					            setattr(self, f"{key}_widget", dialogWidget)
 | 
				
			||||||
            dialogLayout.addWidget(dialogWidget)
 | 
					            dialogLayout.addWidget(dialogWidget)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dialogButtons = QDialogButtonBox(
 | 
					        # Custom button box with both Launch and Generate Script options
 | 
				
			||||||
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 | 
					        buttonBox = QDialogButtonBox()
 | 
				
			||||||
        dialogButtons.accepted.connect(self.accept)
 | 
					        self.launchButton = buttonBox.addButton("Launch Game", QDialogButtonBox.AcceptRole)
 | 
				
			||||||
        dialogButtons.rejected.connect(self.reject)
 | 
					        self.scriptButton = buttonBox.addButton("Generate Script", QDialogButtonBox.ActionRole)
 | 
				
			||||||
        dialogLayout.addWidget(dialogButtons)
 | 
					        buttonBox.addButton(QDialogButtonBox.Cancel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Connect buttons
 | 
				
			||||||
 | 
					        self.launchButton.clicked.connect(self.acceptLaunch)
 | 
				
			||||||
 | 
					        self.scriptButton.clicked.connect(self.acceptGenerateScript)
 | 
				
			||||||
 | 
					        buttonBox.rejected.connect(self.reject)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialogLayout.addWidget(buttonBox)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def acceptLaunch(self):
 | 
				
			||||||
 | 
					        """Accept dialog with launch flag"""
 | 
				
			||||||
 | 
					        self.generateScript = False
 | 
				
			||||||
 | 
					        self.accept()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def acceptGenerateScript(self):
 | 
				
			||||||
 | 
					        """Accept dialog with script generation flag"""
 | 
				
			||||||
 | 
					        self.generateScript = True
 | 
				
			||||||
 | 
					        self.accept()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_dialog_values(self) -> dict:
 | 
					    def get_dialog_values(self) -> dict:
 | 
				
			||||||
        """Get the current values from all dialog widgets"""
 | 
					        """Get the current values from all dialog widgets"""
 | 
				
			||||||
@@ -811,6 +829,19 @@ class CustomGameDialog(QDialog):
 | 
				
			|||||||
class DoomLauncher(QMainWindow):
 | 
					class DoomLauncher(QMainWindow):
 | 
				
			||||||
    """Main launcher window for Toby Doom"""
 | 
					    """Main launcher window for Toby Doom"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deathmatchMaps = [
 | 
				
			||||||
 | 
					        "Com Station (2-4 players)",
 | 
				
			||||||
 | 
					        "Warehouse (2-4 players)",
 | 
				
			||||||
 | 
					        "Sector 3 (2-4 players)",
 | 
				
			||||||
 | 
					        "Dungeon of Doom (2-4 players)",
 | 
				
			||||||
 | 
					        "Ocean Fortress (2-4 players)",
 | 
				
			||||||
 | 
					        "Water Treatment Facility (2-4 players)",
 | 
				
			||||||
 | 
					        "Phobos Base Site 4 (2-4 players)",
 | 
				
			||||||
 | 
					        "Hangar Bay 18 (2-4 players)",
 | 
				
			||||||
 | 
					        "Garden of Demon (2-4 players)",
 | 
				
			||||||
 | 
					        "Outpost 69 (2-4 players)"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        super().__init__()
 | 
					        super().__init__()
 | 
				
			||||||
        self.setWindowTitle("Toby Doom Launcher")
 | 
					        self.setWindowTitle("Toby Doom Launcher")
 | 
				
			||||||
@@ -827,6 +858,563 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
        self.iwadSelector = IWADSelector()  # Add IWAD selector
 | 
					        self.iwadSelector = IWADSelector()  # Add IWAD selector
 | 
				
			||||||
        self.init_launcher_ui()
 | 
					        self.init_launcher_ui()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_single_player_script(self):
 | 
				
			||||||
 | 
					        """Generate script for single player game"""
 | 
				
			||||||
 | 
					        selectedGame = self.gameCombo.currentText()
 | 
				
			||||||
 | 
					        if selectedGame == "Custom Game":
 | 
				
			||||||
 | 
					            self.generate_custom_game_script()
 | 
				
			||||||
 | 
					        elif selectedGame == "Audio Manual":
 | 
				
			||||||
 | 
					            QMessageBox.information(
 | 
				
			||||||
 | 
					                self,
 | 
				
			||||||
 | 
					                "Not Applicable",
 | 
				
			||||||
 | 
					                "Scripts cannot be generated for Audio Manual"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            gameFiles = self.get_selected_game_files()
 | 
				
			||||||
 | 
					            if gameFiles:
 | 
				
			||||||
 | 
					                self.generate_launcher_script(gameFiles)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_deathmatch_script(self):
 | 
				
			||||||
 | 
					        """Open deathmatch dialog and generate script from settings"""
 | 
				
			||||||
 | 
					        # First show map selection
 | 
				
			||||||
 | 
					        mapOptions = {
 | 
				
			||||||
 | 
					            'map': {
 | 
				
			||||||
 | 
					                'type': 'combobox',
 | 
				
			||||||
 | 
					                'label': 'Select Map',
 | 
				
			||||||
 | 
					                'items': self.deathmatchMaps
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mapDialog = MenuDialog("Select Map", mapOptions, self)
 | 
				
			||||||
 | 
					        if not mapDialog.exec():
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectedMap = mapDialog.get_dialog_values()['map']
 | 
				
			||||||
 | 
					        mapIndex = mapOptions['map']['items'].index(selectedMap) + 1  # 1-based index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Show game options dialog
 | 
				
			||||||
 | 
					        options = {
 | 
				
			||||||
 | 
					            'mode': {
 | 
				
			||||||
 | 
					                'type': 'combobox',
 | 
				
			||||||
 | 
					                'label': 'Game Mode',
 | 
				
			||||||
 | 
					                'items': [
 | 
				
			||||||
 | 
					                    "Host Game",
 | 
				
			||||||
 | 
					                    "Join Game",
 | 
				
			||||||
 | 
					                    "Bots Only"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'ip': {
 | 
				
			||||||
 | 
					                'type': 'text',
 | 
				
			||||||
 | 
					                'placeholder': 'Enter IP address to join (required for joining)'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'fraglimit': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Frag Limit',
 | 
				
			||||||
 | 
					                'min': 1,
 | 
				
			||||||
 | 
					                'max': 500,
 | 
				
			||||||
 | 
					                'default': 20
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'players': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Number of Players',
 | 
				
			||||||
 | 
					                'min': 2,
 | 
				
			||||||
 | 
					                'max': 4,
 | 
				
			||||||
 | 
					                'default': 2
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'skill': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Skill Level',
 | 
				
			||||||
 | 
					                'min': 1,
 | 
				
			||||||
 | 
					                'max': 5,
 | 
				
			||||||
 | 
					                'default': 3
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialog = MenuDialog("Deathmatch Options", options, self)
 | 
				
			||||||
 | 
					        if dialog.exec():
 | 
				
			||||||
 | 
					            values = dialog.get_dialog_values()
 | 
				
			||||||
 | 
					            gameFiles = self.get_selected_game_files()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add deathmatch map
 | 
				
			||||||
 | 
					            deathMatchMap = str(self.gamePath / "Addons/MAPS/TobyDeathArena_V1-5.wad")
 | 
				
			||||||
 | 
					            if Path(deathMatchMap).exists():
 | 
				
			||||||
 | 
					                gameFiles.append(deathMatchMap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            gameFlags = self.get_deathmatch_flags(values)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add map selection flag
 | 
				
			||||||
 | 
					            gameFlags.extend(["-warp", str(mapIndex)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check/set freedm.wad as IWAD
 | 
				
			||||||
 | 
					            freedmPath = self.find_freedm()
 | 
				
			||||||
 | 
					            if not freedmPath:
 | 
				
			||||||
 | 
					                QMessageBox.critical(self, "Error", "Could not find freedm.wad")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Force freedm.wad selection
 | 
				
			||||||
 | 
					            for i in range(self.iwadCombo.count()):
 | 
				
			||||||
 | 
					                if "freedm" in self.iwadCombo.itemText(i).lower():
 | 
				
			||||||
 | 
					                    self.iwadCombo.setCurrentIndex(i)
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_custom_deathmatch_script(self):
 | 
				
			||||||
 | 
					        """Generate script for custom deathmatch"""
 | 
				
			||||||
 | 
					        # First find available PK3s for customization
 | 
				
			||||||
 | 
					        pk3List = []
 | 
				
			||||||
 | 
					        for item in self.gamePath.glob('*.pk3'):
 | 
				
			||||||
 | 
					            if item.stat().st_size > 10 * 1024 * 1024:  # >10MB
 | 
				
			||||||
 | 
					                pk3List.append(str(item))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Add Army of Darkness if available
 | 
				
			||||||
 | 
					        aodWad = self.gamePath / "aoddoom1.wad"
 | 
				
			||||||
 | 
					        if aodWad.exists():
 | 
				
			||||||
 | 
					            pk3List.append(str(aodWad))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not pk3List:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "No custom mods found")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Create mod selection dialog
 | 
				
			||||||
 | 
					        modDialog = QDialog(self)
 | 
				
			||||||
 | 
					        modDialog.setWindowTitle("Select Customization")
 | 
				
			||||||
 | 
					        dialogLayout = QVBoxLayout(modDialog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        modLabel = QLabel("Select Mod:")
 | 
				
			||||||
 | 
					        modCombo = AccessibleComboBox(modDialog)
 | 
				
			||||||
 | 
					        modCombo.setAccessibleName("Mod Selection")
 | 
				
			||||||
 | 
					        for pk3 in pk3List:
 | 
				
			||||||
 | 
					            modCombo.addItem(Path(pk3).stem, userData=pk3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialogLayout.addWidget(modLabel)
 | 
				
			||||||
 | 
					        dialogLayout.addWidget(modCombo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 | 
				
			||||||
 | 
					        buttons.accepted.connect(modDialog.accept)
 | 
				
			||||||
 | 
					        buttons.rejected.connect(modDialog.reject)
 | 
				
			||||||
 | 
					        dialogLayout.addWidget(buttons)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not modDialog.exec():
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectedMod = modCombo.currentData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Show map selection dialog (same as regular deathmatch)
 | 
				
			||||||
 | 
					        mapOptions = {
 | 
				
			||||||
 | 
					            'map': {
 | 
				
			||||||
 | 
					                'type': 'combobox',
 | 
				
			||||||
 | 
					                'label': 'Select Map',
 | 
				
			||||||
 | 
					                'items': self.deathmatchMaps
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mapDialog = MenuDialog("Select Map", mapOptions, self)
 | 
				
			||||||
 | 
					        if not mapDialog.exec():
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectedMap = mapDialog.get_dialog_values()['map']
 | 
				
			||||||
 | 
					        mapIndex = mapOptions['map']['items'].index(selectedMap) + 1  # 1-based index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Show game options dialog
 | 
				
			||||||
 | 
					        options = {
 | 
				
			||||||
 | 
					            'mode': {
 | 
				
			||||||
 | 
					                'type': 'combobox',
 | 
				
			||||||
 | 
					                'label': 'Game Mode',
 | 
				
			||||||
 | 
					                'items': [
 | 
				
			||||||
 | 
					                    "Host Game",
 | 
				
			||||||
 | 
					                    "Join Game",
 | 
				
			||||||
 | 
					                    "Bots Only"
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'ip': {
 | 
				
			||||||
 | 
					                'type': 'text',
 | 
				
			||||||
 | 
					                'placeholder': 'Enter IP address to join (required for joining)'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'fraglimit': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Frag Limit',
 | 
				
			||||||
 | 
					                'min': 1,
 | 
				
			||||||
 | 
					                'max': 500,
 | 
				
			||||||
 | 
					                'default': 20
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'players': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Number of Players',
 | 
				
			||||||
 | 
					                'min': 2,
 | 
				
			||||||
 | 
					                'max': 4,
 | 
				
			||||||
 | 
					                'default': 2
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'skill': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Skill Level',
 | 
				
			||||||
 | 
					                'min': 1,
 | 
				
			||||||
 | 
					                'max': 5,
 | 
				
			||||||
 | 
					                'default': 3
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialog = MenuDialog("Deathmatch Options", options, self)
 | 
				
			||||||
 | 
					        if dialog.exec():
 | 
				
			||||||
 | 
					            values = dialog.get_dialog_values()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Set up game files
 | 
				
			||||||
 | 
					            gameFiles = [
 | 
				
			||||||
 | 
					                str(self.gamePath / f"TobyAccMod_V{self.tobyVersion}.pk3")
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add menu addons
 | 
				
			||||||
 | 
					            menuPath = self.gamePath / "Addons/MENU"
 | 
				
			||||||
 | 
					            if menuPath.exists():
 | 
				
			||||||
 | 
					                gameFiles.extend(str(p) for p in menuPath.glob("Toby*.pk3"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add selected mod
 | 
				
			||||||
 | 
					            gameFiles.append(selectedMod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add deathmatch map
 | 
				
			||||||
 | 
					            deathMatchMap = str(self.gamePath / "Addons/MAPS/TobyDeathArena_V1-5.wad")
 | 
				
			||||||
 | 
					            if Path(deathMatchMap).exists():
 | 
				
			||||||
 | 
					                gameFiles.append(deathMatchMap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Get deathmatch flags and add map selection
 | 
				
			||||||
 | 
					            gameFlags = self.get_deathmatch_flags(values)
 | 
				
			||||||
 | 
					            gameFlags.extend(["-warp", str(mapIndex)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check/set freedm.wad as IWAD
 | 
				
			||||||
 | 
					            freedmPath = self.find_freedm()
 | 
				
			||||||
 | 
					            if not freedmPath:
 | 
				
			||||||
 | 
					                QMessageBox.critical(self, "Error", "Could not find freedm.wad")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Force freedm.wad selection
 | 
				
			||||||
 | 
					            for i in range(self.iwadCombo.count()):
 | 
				
			||||||
 | 
					                if "freedm" in self.iwadCombo.itemText(i).lower():
 | 
				
			||||||
 | 
					                    self.iwadCombo.setCurrentIndex(i)
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_coop_script(self):
 | 
				
			||||||
 | 
					        """Generate script for co-op mode"""
 | 
				
			||||||
 | 
					        options = {
 | 
				
			||||||
 | 
					            'host': {
 | 
				
			||||||
 | 
					                'type': 'radio',
 | 
				
			||||||
 | 
					                'label': 'Host Game'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'ip': {
 | 
				
			||||||
 | 
					                'type': 'text',
 | 
				
			||||||
 | 
					                'placeholder': 'Enter IP address to join'
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'players': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Number of Players',
 | 
				
			||||||
 | 
					                'min': 2,
 | 
				
			||||||
 | 
					                'max': 10,
 | 
				
			||||||
 | 
					                'default': 2
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            'skill': {
 | 
				
			||||||
 | 
					                'type': 'spinbox',
 | 
				
			||||||
 | 
					                'label': 'Skill Level',
 | 
				
			||||||
 | 
					                'min': 1,
 | 
				
			||||||
 | 
					                'max': 5,
 | 
				
			||||||
 | 
					                'default': 3
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialog = MenuDialog("Co-op Options", options, self)
 | 
				
			||||||
 | 
					        if dialog.exec():
 | 
				
			||||||
 | 
					            values = dialog.get_dialog_values()
 | 
				
			||||||
 | 
					            gameFiles = self.get_selected_game_files()
 | 
				
			||||||
 | 
					            # Add keyshare for co-op
 | 
				
			||||||
 | 
					            keyshareFile = str(self.gamePath / "keyshare-universal.pk3")
 | 
				
			||||||
 | 
					            if Path(keyshareFile).exists():
 | 
				
			||||||
 | 
					                gameFiles.append(keyshareFile)
 | 
				
			||||||
 | 
					            gameFlags = self.get_coop_flags(values)
 | 
				
			||||||
 | 
					            self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_custom_game_script(self):
 | 
				
			||||||
 | 
					        """Generate script for custom game"""
 | 
				
			||||||
 | 
					        customGames = self.load_custom_games()
 | 
				
			||||||
 | 
					        if not customGames:
 | 
				
			||||||
 | 
					            QMessageBox.warning(
 | 
				
			||||||
 | 
					                self,
 | 
				
			||||||
 | 
					                "No Custom Games",
 | 
				
			||||||
 | 
					                "No custom game configurations found in TobyCustom directory."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialog = CustomGameDialog(customGames, self)
 | 
				
			||||||
 | 
					        if not dialog.exec():
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        selectedGame = dialog.get_selected_game()
 | 
				
			||||||
 | 
					        if selectedGame and selectedGame in customGames:
 | 
				
			||||||
 | 
					            config = customGames[selectedGame]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check dependencies before launching
 | 
				
			||||||
 | 
					            if not self.check_dependencies(config.get('dependencies', [])):
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            gameFiles = []  # We'll build this up as we go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Always start with TobyAccMod
 | 
				
			||||||
 | 
					            tobyMod = self.gamePath / f"TobyAccMod_V{self.tobyVersion}.pk3"
 | 
				
			||||||
 | 
					            if not tobyMod.exists():
 | 
				
			||||||
 | 
					                QMessageBox.critical(self, "Error", f"Could not find {tobyMod}")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					            gameFiles.append(str(tobyMod))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Handle map selection right after TobyAccMod if specified
 | 
				
			||||||
 | 
					            if config.get('use_map_menu', False) and 'submenu' not in config:
 | 
				
			||||||
 | 
					                mapFiles = ["None"]  # Start with None option
 | 
				
			||||||
 | 
					                mapsDir = self.gamePath / "Addons/MAPS"
 | 
				
			||||||
 | 
					                if mapsDir.exists():
 | 
				
			||||||
 | 
					                    mapFiles.extend([p.name for p in mapsDir.glob("*.wad") 
 | 
				
			||||||
 | 
					                                if p.name != "TobyDeathArena_V1-5.wad"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Add Operation MDK as special case
 | 
				
			||||||
 | 
					                opMDK = self.gamePath / "OpMDK.wad"
 | 
				
			||||||
 | 
					                if opMDK.exists():
 | 
				
			||||||
 | 
					                    mapFiles.append("OpMDK.wad")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                mapDialog = QDialog(self)
 | 
				
			||||||
 | 
					                mapDialog.setWindowTitle("Select Map")
 | 
				
			||||||
 | 
					                dialogLayout = QVBoxLayout(mapDialog)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                mapLabel = QLabel("Select Map:")
 | 
				
			||||||
 | 
					                mapCombo = AccessibleComboBox(mapDialog)
 | 
				
			||||||
 | 
					                mapCombo.setAccessibleName("Map Selection")
 | 
				
			||||||
 | 
					                mapCombo.addItems(mapFiles)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                dialogLayout.addWidget(mapLabel)
 | 
				
			||||||
 | 
					                dialogLayout.addWidget(mapCombo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 | 
				
			||||||
 | 
					                buttons.accepted.connect(mapDialog.accept)
 | 
				
			||||||
 | 
					                buttons.rejected.connect(mapDialog.reject)
 | 
				
			||||||
 | 
					                dialogLayout.addWidget(buttons)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if not mapDialog.exec():
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                selectedMap = mapCombo.currentText()
 | 
				
			||||||
 | 
					                if selectedMap != "None":
 | 
				
			||||||
 | 
					                    if selectedMap == "OpMDK.wad":
 | 
				
			||||||
 | 
					                        mapPath = str(self.gamePath / selectedMap)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        mapPath = str(self.gamePath / "Addons/MAPS" / selectedMap)
 | 
				
			||||||
 | 
					                    if Path(mapPath).exists():
 | 
				
			||||||
 | 
					                        gameFiles.append(mapPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if selectedMap == "TobyDoomLevels.wad":
 | 
				
			||||||
 | 
					                        musicRenamer = self.gamePath / "Toby-Doom-Level-Music-Renamer.pk3"
 | 
				
			||||||
 | 
					                        if musicRenamer.exists():
 | 
				
			||||||
 | 
					                            gameFiles.append(str(musicRenamer))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Handle submenu if present
 | 
				
			||||||
 | 
					            if 'submenu' in config:
 | 
				
			||||||
 | 
					                selectedFile = self.show_submenu_dialog(config['submenu'])
 | 
				
			||||||
 | 
					                if not selectedFile:
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					                gameFiles.append(selectedFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add remaining files
 | 
				
			||||||
 | 
					            tobyBaseVersion = self.tobyVersion.split('-')[0]
 | 
				
			||||||
 | 
					            for filePath in config.get('files', []):
 | 
				
			||||||
 | 
					                filePath = filePath.format(toby_base_version=tobyBaseVersion)
 | 
				
			||||||
 | 
					                # Handle glob patterns
 | 
				
			||||||
 | 
					                if '*' in filePath:
 | 
				
			||||||
 | 
					                    pathObj = self.gamePath / filePath.split('*')[0]
 | 
				
			||||||
 | 
					                    pattern = filePath.split('/')[-1]
 | 
				
			||||||
 | 
					                    if pathObj.parent.exists():
 | 
				
			||||||
 | 
					                        matches = list(pathObj.parent.glob(pattern))
 | 
				
			||||||
 | 
					                        gameFiles.extend(str(p) for p in matches)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    fullPath = self.gamePath / filePath
 | 
				
			||||||
 | 
					                    if fullPath.exists():
 | 
				
			||||||
 | 
					                        gameFiles.append(str(fullPath))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add optional files last
 | 
				
			||||||
 | 
					            for optFile in config.get('optional_files', []):
 | 
				
			||||||
 | 
					                optPath = self.gamePath / optFile
 | 
				
			||||||
 | 
					                if optPath.exists():
 | 
				
			||||||
 | 
					                    gameFiles.append(str(optPath))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Get any custom flags
 | 
				
			||||||
 | 
					            gameFlags = config.get('flags', [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Generate the script if we have files
 | 
				
			||||||
 | 
					            if gameFiles:
 | 
				
			||||||
 | 
					                iwadIndex = self.iwadCombo.currentIndex()
 | 
				
			||||||
 | 
					                if iwadIndex < 0:
 | 
				
			||||||
 | 
					                    QMessageBox.critical(self, "Error", "Please select an IWAD first")
 | 
				
			||||||
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def generate_launcher_script(self, gameFiles, gameFlags=None):
 | 
				
			||||||
 | 
					        """Generate a batch or bash script for launching the game"""
 | 
				
			||||||
 | 
					        if not gameFiles:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gzdoomPath = self.find_gzdoom()
 | 
				
			||||||
 | 
					        if not gzdoomPath:
 | 
				
			||||||
 | 
					            QMessageBox.critical(self, "Error", "GZDoom executable not found")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Get selected IWAD
 | 
				
			||||||
 | 
					        iwadIndex = self.iwadCombo.currentIndex()
 | 
				
			||||||
 | 
					        if iwadIndex < 0:
 | 
				
			||||||
 | 
					            QMessageBox.critical(self, "Error", "Please select an IWAD first")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        iwadPath = self.iwadCombo.itemData(iwadIndex)
 | 
				
			||||||
 | 
					        iwadName = self.iwadCombo.currentText().lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Get selected game type
 | 
				
			||||||
 | 
					        selectedGame = self.gameCombo.currentText().replace(" ", "_").lower()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize gameFlags if None
 | 
				
			||||||
 | 
					        if gameFlags is None:
 | 
				
			||||||
 | 
					            gameFlags = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Get additional flags from doom_flags.txt
 | 
				
			||||||
 | 
					        additionalFlags = self.get_flags_from_file()
 | 
				
			||||||
 | 
					        if additionalFlags:
 | 
				
			||||||
 | 
					            gameFlags.extend(additionalFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Determine file format based on OS
 | 
				
			||||||
 | 
					        extension = ".bat" if platform.system() == "Windows" else ".sh"
 | 
				
			||||||
 | 
					        baseFileName = f"{iwadName}_{selectedGame}{extension}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Handle special case for custom games or different dialogs
 | 
				
			||||||
 | 
					        if selectedGame == "custom_game":
 | 
				
			||||||
 | 
					            baseFileName = f"{iwadName}_custom_game{extension}"
 | 
				
			||||||
 | 
					        elif "-deathmatch" in gameFlags or any("deathmatch" in flag.lower() for flag in gameFlags):
 | 
				
			||||||
 | 
					            baseFileName = f"{iwadName}_deathmatch{extension}"
 | 
				
			||||||
 | 
					        elif "-join" in gameFlags or "-host" in gameFlags:
 | 
				
			||||||
 | 
					            baseFileName = f"{iwadName}_coop{extension}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Clean up the filename (remove any unsafe characters)
 | 
				
			||||||
 | 
					        baseFileName = re.sub(r'[^\w\-\.]', '_', baseFileName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if platform.system() == "Windows":
 | 
				
			||||||
 | 
					            # Windows: save in current directory
 | 
				
			||||||
 | 
					            baseDir = Path.cwd()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Build Windows batch file content
 | 
				
			||||||
 | 
					            content = ["@echo off"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Use gzdoom.exe with line continuation character
 | 
				
			||||||
 | 
					            content.append("gzdoom.exe ^")
 | 
				
			||||||
 | 
					            content.append("  -stdout ^")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add config file 
 | 
				
			||||||
 | 
					            configFile = Path.cwd() / 'TobyConfig.ini'
 | 
				
			||||||
 | 
					            if configFile.exists():
 | 
				
			||||||
 | 
					                content.append(f"  -config TobyConfig.ini ^")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add narration type
 | 
				
			||||||
 | 
					            narrationType = self.get_narration_type()
 | 
				
			||||||
 | 
					            content.append(f"  +Toby_NarrationOutputType {narrationType} ^")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add IWAD
 | 
				
			||||||
 | 
					            content.append(f"  -iwad \"{iwadPath}\" ^")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add game files
 | 
				
			||||||
 | 
					            for file in gameFiles:
 | 
				
			||||||
 | 
					                # Use relative paths with ./ prefix for better readability if possible
 | 
				
			||||||
 | 
					                if str(file).startswith(str(self.gamePath)):
 | 
				
			||||||
 | 
					                    relPath = Path(file).relative_to(self.gamePath)
 | 
				
			||||||
 | 
					                    content.append(f"  -file \"./{relPath}\" ^")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    content.append(f"  -file \"{file}\" ^")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add game flags
 | 
				
			||||||
 | 
					            for flag in gameFlags:
 | 
				
			||||||
 | 
					                content.append(f"  {flag} ^")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Remove the trailing ^ from the last line
 | 
				
			||||||
 | 
					            if content[-1].endswith(" ^"):
 | 
				
			||||||
 | 
					                content[-1] = content[-1][:-2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add TTS powershell script
 | 
				
			||||||
 | 
					            content.append("  | powershell -ExecutionPolicy Bypass -File DoomTTS.ps1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Linux/Mac: save in ~/.local/games/doom
 | 
				
			||||||
 | 
					            baseDir = Path.home() / ".local/games/doom"
 | 
				
			||||||
 | 
					            baseDir.mkdir(parents=True, exist_ok=True)  # Create directory if it doesn't exist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Build bash script content
 | 
				
			||||||
 | 
					            content = ["#!/usr/bin/env bash"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Use 'exec' with stdbuf, but with line continuations
 | 
				
			||||||
 | 
					            content.append("exec stdbuf -oL /usr/bin/gzdoom \\")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add IWAD
 | 
				
			||||||
 | 
					            content.append(f"  -iwad \"{iwadPath}\" \\")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add -file flag before listing the files
 | 
				
			||||||
 | 
					            content.append("  -file \\")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add each game file on its own line
 | 
				
			||||||
 | 
					            for i, file in enumerate(gameFiles):
 | 
				
			||||||
 | 
					                if i < len(gameFiles) - 1:
 | 
				
			||||||
 | 
					                    content.append(f"    \"{file}\" \\")
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    # Last file doesn't need continuation
 | 
				
			||||||
 | 
					                    content.append(f"    \"{file}\"")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add game flags if present
 | 
				
			||||||
 | 
					            if gameFlags:
 | 
				
			||||||
 | 
					                content.append("  \\")  # Add continuation
 | 
				
			||||||
 | 
					                for i, flag in enumerate(gameFlags):
 | 
				
			||||||
 | 
					                    if i < len(gameFlags) - 1:
 | 
				
			||||||
 | 
					                        content.append(f"  {flag} \\")
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        # Last flag doesn't need continuation
 | 
				
			||||||
 | 
					                        content.append(f"  {flag}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # This waits for a line of dashes, then starts piping to speech-dispatcher
 | 
				
			||||||
 | 
					            content[-1] = content[-1] + " |"
 | 
				
			||||||
 | 
					            content.append("grep --line-buffered -A 1000000 '^-\\+-*$' |")
 | 
				
			||||||
 | 
					            content.append("grep --line-buffered -v -e '^Unknown' -e '^fluidsynth:'|")
 | 
				
			||||||
 | 
					            content.append("sed -u -e 's/^\\[Toby Accessibility Mod\\] //' -e 's/^M_//' -e 's/\\([a-z]\\)\\([A-Z]\\)/\\1 \\2/g' -e 's/\\([A-Za-z]\\+\\)menu\\>/\\1 menu/g' -e 's/^\\([A-Z][A-Z]*\\)G$/\\L\\1\\E game/g' |")
 | 
				
			||||||
 | 
					            content.append("spd-say --wait -e")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generate a unique filename
 | 
				
			||||||
 | 
					        fileName = baseFileName
 | 
				
			||||||
 | 
					        filePath = baseDir / fileName
 | 
				
			||||||
 | 
					        counter = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Check if file exists and generate new name if needed
 | 
				
			||||||
 | 
					        while filePath.exists():
 | 
				
			||||||
 | 
					            nameBase, extension = baseFileName.rsplit('.', 1)
 | 
				
			||||||
 | 
					            fileName = f"{nameBase}_{counter}.{extension}"
 | 
				
			||||||
 | 
					            filePath = baseDir / fileName
 | 
				
			||||||
 | 
					            counter += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with open(filePath, 'w') as f:
 | 
				
			||||||
 | 
					                f.write('\n'.join(content))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Make the file executable on Linux/Mac
 | 
				
			||||||
 | 
					            if platform.system() != "Windows":
 | 
				
			||||||
 | 
					                os.chmod(filePath, 0o755)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            QMessageBox.information(
 | 
				
			||||||
 | 
					                self,
 | 
				
			||||||
 | 
					                "Success",
 | 
				
			||||||
 | 
					                f"Launcher script saved to {filePath}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            QMessageBox.critical(
 | 
				
			||||||
 | 
					                self,
 | 
				
			||||||
 | 
					                "Error",
 | 
				
			||||||
 | 
					                f"Failed to save launcher script: {e}"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def keyPressEvent(self, event):
 | 
					    def keyPressEvent(self, event):
 | 
				
			||||||
        """Handle key press events"""
 | 
					        """Handle key press events"""
 | 
				
			||||||
        if event.key() == Qt.Key_Escape:
 | 
					        if event.key() == Qt.Key_Escape:
 | 
				
			||||||
@@ -881,31 +1469,62 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
        mainLayout.addWidget(QLabel("Narration Style:"))
 | 
					        mainLayout.addWidget(QLabel("Narration Style:"))
 | 
				
			||||||
        mainLayout.addWidget(self.narrationCombo)
 | 
					        mainLayout.addWidget(self.narrationCombo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Create buttons
 | 
					        # Create button layouts with pairs of launch and generate buttons
 | 
				
			||||||
 | 
					        # Single Player
 | 
				
			||||||
 | 
					        singlePlayerLayout = QHBoxLayout()
 | 
				
			||||||
        self.singlePlayerBtn = QPushButton("&Single Player")
 | 
					        self.singlePlayerBtn = QPushButton("&Single Player")
 | 
				
			||||||
        self.deathMatchBtn = QPushButton("&Deathmatch")
 | 
					        self.singlePlayerGenBtn = QPushButton("Generate Single Player Script")
 | 
				
			||||||
        self.customDeathMatchBtn = QPushButton("C&ustom Deathmatch")  # Alt+U
 | 
					 | 
				
			||||||
        self.coopBtn = QPushButton("&Co-op")
 | 
					 | 
				
			||||||
        self.audioManualBtn = QPushButton("&Audio Manual")  # Alt+A
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.singlePlayerBtn.clicked.connect(self.launch_single_player)
 | 
					        self.singlePlayerBtn.clicked.connect(self.launch_single_player)
 | 
				
			||||||
 | 
					        self.singlePlayerGenBtn.clicked.connect(self.generate_single_player_script)
 | 
				
			||||||
 | 
					        singlePlayerLayout.addWidget(self.singlePlayerBtn)
 | 
				
			||||||
 | 
					        singlePlayerLayout.addWidget(self.singlePlayerGenBtn)
 | 
				
			||||||
 | 
					        mainLayout.addLayout(singlePlayerLayout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Deathmatch
 | 
				
			||||||
 | 
					        deathMatchLayout = QHBoxLayout()
 | 
				
			||||||
 | 
					        self.deathMatchBtn = QPushButton("&Deathmatch")
 | 
				
			||||||
 | 
					        self.deathMatchGenBtn = QPushButton("Generate Deathmatch Script")
 | 
				
			||||||
        self.deathMatchBtn.clicked.connect(self.show_deathmatch_dialog)
 | 
					        self.deathMatchBtn.clicked.connect(self.show_deathmatch_dialog)
 | 
				
			||||||
        self.customDeathMatchBtn.clicked.connect(self.show_custom_deathmatch_dialog)  # New line
 | 
					        self.deathMatchGenBtn.clicked.connect(self.generate_deathmatch_script)
 | 
				
			||||||
 | 
					        deathMatchLayout.addWidget(self.deathMatchBtn)
 | 
				
			||||||
 | 
					        deathMatchLayout.addWidget(self.deathMatchGenBtn)
 | 
				
			||||||
 | 
					        mainLayout.addLayout(deathMatchLayout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Custom Deathmatch
 | 
				
			||||||
 | 
					        customDeathMatchLayout = QHBoxLayout()
 | 
				
			||||||
 | 
					        self.customDeathMatchBtn = QPushButton("C&ustom Deathmatch")  # Alt+U
 | 
				
			||||||
 | 
					        self.customDeathMatchGenBtn = QPushButton("Generate Custom Deathmatch Script")
 | 
				
			||||||
 | 
					        self.customDeathMatchBtn.clicked.connect(self.show_custom_deathmatch_dialog)
 | 
				
			||||||
 | 
					        self.customDeathMatchGenBtn.clicked.connect(self.generate_custom_deathmatch_script)
 | 
				
			||||||
 | 
					        customDeathMatchLayout.addWidget(self.customDeathMatchBtn)
 | 
				
			||||||
 | 
					        customDeathMatchLayout.addWidget(self.customDeathMatchGenBtn)
 | 
				
			||||||
 | 
					        mainLayout.addLayout(customDeathMatchLayout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Co-op
 | 
				
			||||||
 | 
					        coopLayout = QHBoxLayout()
 | 
				
			||||||
 | 
					        self.coopBtn = QPushButton("&Co-op")
 | 
				
			||||||
 | 
					        self.coopGenBtn = QPushButton("Generate Co-op Script")
 | 
				
			||||||
        self.coopBtn.clicked.connect(self.show_coop_dialog)
 | 
					        self.coopBtn.clicked.connect(self.show_coop_dialog)
 | 
				
			||||||
 | 
					        self.coopGenBtn.clicked.connect(self.generate_coop_script)
 | 
				
			||||||
 | 
					        coopLayout.addWidget(self.coopBtn)
 | 
				
			||||||
 | 
					        coopLayout.addWidget(self.coopGenBtn)
 | 
				
			||||||
 | 
					        mainLayout.addLayout(coopLayout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Audio Manual (no script generation for this)
 | 
				
			||||||
 | 
					        self.audioManualBtn = QPushButton("&Audio Manual")  # Alt+A
 | 
				
			||||||
        self.audioManualBtn.clicked.connect(self.show_audio_manual)
 | 
					        self.audioManualBtn.clicked.connect(self.show_audio_manual)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.singlePlayerBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.singlePlayerBtn)
 | 
					 | 
				
			||||||
        self.deathMatchBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.deathMatchBtn)
 | 
					 | 
				
			||||||
        self.customDeathMatchBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.customDeathMatchBtn)  # New line
 | 
					 | 
				
			||||||
        self.coopBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.coopBtn)
 | 
					 | 
				
			||||||
        self.audioManualBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.audioManualBtn)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        mainLayout.addWidget(self.singlePlayerBtn)
 | 
					 | 
				
			||||||
        mainLayout.addWidget(self.deathMatchBtn)
 | 
					 | 
				
			||||||
        mainLayout.addWidget(self.customDeathMatchBtn)
 | 
					 | 
				
			||||||
        mainLayout.addWidget(self.coopBtn)
 | 
					 | 
				
			||||||
        mainLayout.addWidget(self.audioManualBtn)
 | 
					        mainLayout.addWidget(self.audioManualBtn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set key press event handlers
 | 
				
			||||||
 | 
					        self.singlePlayerBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.singlePlayerBtn)
 | 
				
			||||||
 | 
					        self.deathMatchBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.deathMatchBtn)
 | 
				
			||||||
 | 
					        self.customDeathMatchBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.customDeathMatchBtn)
 | 
				
			||||||
 | 
					        self.coopBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.coopBtn)
 | 
				
			||||||
 | 
					        self.audioManualBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.audioManualBtn)
 | 
				
			||||||
 | 
					        self.singlePlayerGenBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.singlePlayerGenBtn)
 | 
				
			||||||
 | 
					        self.deathMatchGenBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.deathMatchGenBtn)
 | 
				
			||||||
 | 
					        self.customDeathMatchGenBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.customDeathMatchGenBtn)
 | 
				
			||||||
 | 
					        self.coopGenBtn.keyPressEvent = lambda e: self.handle_button_keypress(e, self.coopGenBtn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_narration_type(self) -> int:
 | 
					    def get_narration_type(self) -> int:
 | 
				
			||||||
        """Get the current narration type from config file"""
 | 
					        """Get the current narration type from config file"""
 | 
				
			||||||
@@ -1145,18 +1764,7 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
            'map': {
 | 
					            'map': {
 | 
				
			||||||
                'type': 'combobox',
 | 
					                'type': 'combobox',
 | 
				
			||||||
                'label': 'Select Map',
 | 
					                'label': 'Select Map',
 | 
				
			||||||
                'items': [
 | 
					                'items': self.deathmatchMaps
 | 
				
			||||||
                    "Com Station (2-4 players)",
 | 
					 | 
				
			||||||
                    "Warehouse (2-4 players)",
 | 
					 | 
				
			||||||
                    "Sector 3 (2-4 players)",
 | 
					 | 
				
			||||||
                    "Dungeon of Doom (2-4 players)",
 | 
					 | 
				
			||||||
                    "Ocean Fortress (2-4 players)",
 | 
					 | 
				
			||||||
                    "Water Treatment Facility (2-4 players)",
 | 
					 | 
				
			||||||
                    "Phobos Base Site 4 (2-4 players)",
 | 
					 | 
				
			||||||
                    "Hangar Bay 18 (2-4 players)",
 | 
					 | 
				
			||||||
                    "Garden of Demon (2-4 players)",
 | 
					 | 
				
			||||||
                    "Outpost 69 (2-4 players)"
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1243,8 +1851,11 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
                    self.iwadCombo.setCurrentIndex(i)
 | 
					                    self.iwadCombo.setCurrentIndex(i)
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Launch the game
 | 
					            # Check if we should generate a script or launch the game
 | 
				
			||||||
            self.launch_game(gameFiles, gameFlags)
 | 
					            if dialog.generateScript:
 | 
				
			||||||
 | 
					                self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.launch_game(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def load_custom_games(self) -> Dict[str, dict]:
 | 
					    def load_custom_games(self) -> Dict[str, dict]:
 | 
				
			||||||
        """Load all custom game configurations"""
 | 
					        """Load all custom game configurations"""
 | 
				
			||||||
@@ -1336,6 +1947,9 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
                return
 | 
					                return
 | 
				
			||||||
            gameFiles.append(str(tobyMod))
 | 
					            gameFiles.append(str(tobyMod))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Add script generation option to map selection dialog
 | 
				
			||||||
 | 
					            generateScript = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Handle map selection right after TobyAccMod if specified
 | 
					            # Handle map selection right after TobyAccMod if specified
 | 
				
			||||||
            if config.get('use_map_menu', False) and 'submenu' not in config:
 | 
					            if config.get('use_map_menu', False) and 'submenu' not in config:
 | 
				
			||||||
                mapFiles = ["None"]  # Start with None option
 | 
					                mapFiles = ["None"]  # Start with None option
 | 
				
			||||||
@@ -1361,14 +1975,28 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
                dialogLayout.addWidget(mapLabel)
 | 
					                dialogLayout.addWidget(mapLabel)
 | 
				
			||||||
                dialogLayout.addWidget(mapCombo)
 | 
					                dialogLayout.addWidget(mapCombo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 | 
					                # Create custom button box with Launch and Generate Script options
 | 
				
			||||||
                buttons.accepted.connect(mapDialog.accept)
 | 
					                buttonBox = QDialogButtonBox()
 | 
				
			||||||
                buttons.rejected.connect(mapDialog.reject)
 | 
					                launchButton = buttonBox.addButton("Launch Game", QDialogButtonBox.AcceptRole)
 | 
				
			||||||
                dialogLayout.addWidget(buttons)
 | 
					                scriptButton = buttonBox.addButton("Generate Script", QDialogButtonBox.ActionRole)
 | 
				
			||||||
 | 
					                buttonBox.addButton(QDialogButtonBox.Cancel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Connect buttons
 | 
				
			||||||
 | 
					                launchButton.clicked.connect(mapDialog.accept)
 | 
				
			||||||
 | 
					                scriptButton.clicked.connect(lambda: setattr(mapDialog, "generateScript", True) or mapDialog.accept())
 | 
				
			||||||
 | 
					                buttonBox.rejected.connect(mapDialog.reject)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                dialogLayout.addWidget(buttonBox)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Initialize generateScript flag
 | 
				
			||||||
 | 
					                mapDialog.generateScript = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if not mapDialog.exec():
 | 
					                if not mapDialog.exec():
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Capture the generateScript flag
 | 
				
			||||||
 | 
					                generateScript = getattr(mapDialog, "generateScript", False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                selectedMap = mapCombo.currentText()
 | 
					                selectedMap = mapCombo.currentText()
 | 
				
			||||||
                if selectedMap != "None":
 | 
					                if selectedMap != "None":
 | 
				
			||||||
                    if selectedMap == "OpMDK.wad":
 | 
					                    if selectedMap == "OpMDK.wad":
 | 
				
			||||||
@@ -1385,10 +2013,12 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            # Handle submenu if present
 | 
					            # Handle submenu if present
 | 
				
			||||||
            if 'submenu' in config:
 | 
					            if 'submenu' in config:
 | 
				
			||||||
                selectedFile = self.show_submenu_dialog(config['submenu'])
 | 
					                submenuResult = self.show_submenu_dialog_with_script_option(config['submenu'])
 | 
				
			||||||
                if not selectedFile:
 | 
					                if not submenuResult:
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					                selectedFile, submenuGenerateScript = submenuResult
 | 
				
			||||||
                gameFiles.append(selectedFile)
 | 
					                gameFiles.append(selectedFile)
 | 
				
			||||||
 | 
					                generateScript = submenuGenerateScript
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Add remaining files
 | 
					            # Add remaining files
 | 
				
			||||||
            tobyBaseVersion = self.tobyVersion.split('-')[0]
 | 
					            tobyBaseVersion = self.tobyVersion.split('-')[0]
 | 
				
			||||||
@@ -1422,10 +2052,22 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
                    QMessageBox.critical(self, "Error", "Please select an IWAD first")
 | 
					                    QMessageBox.critical(self, "Error", "Please select an IWAD first")
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.launch_game(gameFiles, gameFlags)
 | 
					                # Either generate a script or launch the game based on user choice
 | 
				
			||||||
 | 
					                if generateScript:
 | 
				
			||||||
 | 
					                    self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    self.launch_game(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_submenu_dialog(self, submenu_config) -> Optional[str]:
 | 
					    def show_submenu_dialog(self, submenu_config) -> Optional[str]:
 | 
				
			||||||
        """Show dialog for selecting submenu option"""
 | 
					        """Show dialog for selecting submenu option"""
 | 
				
			||||||
 | 
					        # For backward compatibility - calls new method
 | 
				
			||||||
 | 
					        result = self.show_submenu_dialog_with_script_option(submenu_config)
 | 
				
			||||||
 | 
					        if result:
 | 
				
			||||||
 | 
					            return result[0]  # Return just the file path
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def show_submenu_dialog_with_script_option(self, submenu_config) -> Optional[Tuple[str, bool]]:
 | 
				
			||||||
 | 
					        """Show dialog for selecting submenu option with script generation option"""
 | 
				
			||||||
        dialog = QDialog(self)
 | 
					        dialog = QDialog(self)
 | 
				
			||||||
        dialog.setWindowTitle(submenu_config['title'])
 | 
					        dialog.setWindowTitle(submenu_config['title'])
 | 
				
			||||||
        dialogLayout = QVBoxLayout(dialog)
 | 
					        dialogLayout = QVBoxLayout(dialog)
 | 
				
			||||||
@@ -1442,14 +2084,24 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
        dialogLayout.addWidget(label)
 | 
					        dialogLayout.addWidget(label)
 | 
				
			||||||
        dialogLayout.addWidget(gameCombo)
 | 
					        dialogLayout.addWidget(gameCombo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Dialog buttons
 | 
					        # Create custom button box with Launch and Generate Script options
 | 
				
			||||||
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
 | 
					        buttonBox = QDialogButtonBox()
 | 
				
			||||||
        buttons.accepted.connect(dialog.accept)
 | 
					        launchButton = buttonBox.addButton("Launch Game", QDialogButtonBox.AcceptRole)
 | 
				
			||||||
        buttons.rejected.connect(dialog.reject)
 | 
					        scriptButton = buttonBox.addButton("Generate Script", QDialogButtonBox.ActionRole)
 | 
				
			||||||
        dialogLayout.addWidget(buttons)
 | 
					        buttonBox.addButton(QDialogButtonBox.Cancel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Connect buttons
 | 
				
			||||||
 | 
					        launchButton.clicked.connect(dialog.accept)
 | 
				
			||||||
 | 
					        scriptButton.clicked.connect(lambda: setattr(dialog, "generateScript", True) or dialog.accept())
 | 
				
			||||||
 | 
					        buttonBox.rejected.connect(dialog.reject)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dialogLayout.addWidget(buttonBox)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize generateScript flag
 | 
				
			||||||
 | 
					        dialog.generateScript = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if dialog.exec():
 | 
					        if dialog.exec():
 | 
				
			||||||
            return gameCombo.currentData()
 | 
					            return gameCombo.currentData(), getattr(dialog, "generateScript", False)
 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def launch_single_player(self):
 | 
					    def launch_single_player(self):
 | 
				
			||||||
@@ -1490,18 +2142,7 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
            'map': {
 | 
					            'map': {
 | 
				
			||||||
                'type': 'combobox',
 | 
					                'type': 'combobox',
 | 
				
			||||||
                'label': 'Select Map',
 | 
					                'label': 'Select Map',
 | 
				
			||||||
                'items': [
 | 
					                'items': self.deathmatchMaps
 | 
				
			||||||
                    "Com Station (2-4 players)",
 | 
					 | 
				
			||||||
                    "Warehouse (2-4 players)",
 | 
					 | 
				
			||||||
                    "Sector 3 (2-4 players)",
 | 
					 | 
				
			||||||
                    "Dungeon of Doom (2-4 players)",
 | 
					 | 
				
			||||||
                    "Ocean Fortress (2-4 players)",
 | 
					 | 
				
			||||||
                    "Water Treatment Facility (2-4 players)",
 | 
					 | 
				
			||||||
                    "Phobos Base Site 4 (2-4 players)",
 | 
					 | 
				
			||||||
                    "Hangar Bay 18 (2-4 players)",
 | 
					 | 
				
			||||||
                    "Garden of Demon (2-4 players)",
 | 
					 | 
				
			||||||
                    "Outpost 69 (2-4 players)"
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1548,7 +2189,7 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
                'max': 5,
 | 
					                'max': 5,
 | 
				
			||||||
                'default': 3
 | 
					                'default': 3
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
}
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dialog = MenuDialog("Deathmatch Options", options, self)
 | 
					        dialog = MenuDialog("Deathmatch Options", options, self)
 | 
				
			||||||
        if dialog.exec():
 | 
					        if dialog.exec():
 | 
				
			||||||
@@ -1574,7 +2215,11 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
                    self.iwadCombo.setCurrentIndex(i)
 | 
					                    self.iwadCombo.setCurrentIndex(i)
 | 
				
			||||||
                    break
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.launch_game(gameFiles, gameFlags)
 | 
					            # Check if we should generate a script or launch the game
 | 
				
			||||||
 | 
					            if dialog.generateScript:
 | 
				
			||||||
 | 
					                self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.launch_game(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def show_coop_dialog(self):
 | 
					    def show_coop_dialog(self):
 | 
				
			||||||
        """Show co-op configuration dialog"""
 | 
					        """Show co-op configuration dialog"""
 | 
				
			||||||
@@ -1612,7 +2257,12 @@ class DoomLauncher(QMainWindow):
 | 
				
			|||||||
            if Path(keyshareFile).exists():
 | 
					            if Path(keyshareFile).exists():
 | 
				
			||||||
                gameFiles.append(keyshareFile)
 | 
					                gameFiles.append(keyshareFile)
 | 
				
			||||||
            gameFlags = self.get_coop_flags(values)
 | 
					            gameFlags = self.get_coop_flags(values)
 | 
				
			||||||
            self.launch_game(gameFiles, gameFlags)
 | 
					
 | 
				
			||||||
 | 
					            # Check if we should generate a script or launch the game
 | 
				
			||||||
 | 
					            if dialog.generateScript:
 | 
				
			||||||
 | 
					                self.generate_launcher_script(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.launch_game(gameFiles, gameFlags)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_deathmatch_flags(self, values: dict) -> List[str]:
 | 
					    def get_deathmatch_flags(self, values: dict) -> List[str]:
 | 
				
			||||||
        """Get command line flags for deathmatch mode"""
 | 
					        """Get command line flags for deathmatch mode"""
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user