diff --git a/korman/exporter/__init__.py b/korman/exporter/__init__.py index e8652a8..60123e0 100644 --- a/korman/exporter/__init__.py +++ b/korman/exporter/__init__.py @@ -18,5 +18,6 @@ from PyHSPlasma import * from .convert import * from .explosions import * +from .locman import * from .python import * from . import utils diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index a55e18d..bd5353d 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -24,6 +24,7 @@ from . import camera from . import explosions from . import etlight from . import image +from . import locman from . import logger from . import manager from . import mesh @@ -52,6 +53,7 @@ class Exporter: self.output = outfile.OutputFiles(self, self._op.filepath) self.camera = camera.CameraConverter(self) self.image = image.ImageCache(self) + self.locman = locman.LocalizationConverter(self) # Step 0.8: Init the progress mgr self.mesh.add_progress_presteps(self.report) @@ -368,6 +370,7 @@ class Exporter: # If something bad happens in the final flush, it would be a shame to # simply toss away the potentially freshly regenerated texture cache. try: + self.locman.save() self.mgr.save_age() self.output.save() finally: diff --git a/korman/exporter/locman.py b/korman/exporter/locman.py new file mode 100644 index 0000000..205b7d1 --- /dev/null +++ b/korman/exporter/locman.py @@ -0,0 +1,201 @@ +# This file is part of Korman. +# +# Korman is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Korman is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Korman. If not, see . + +import bpy +from contextlib import contextmanager +from .explosions import NonfatalExportError +from .. import korlib +from . import logger +from pathlib import Path +from PyHSPlasma import * +import weakref +from xml.sax.saxutils import escape as xml_escape + +_SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"} + +class LocalizationConverter: + def __init__(self, exporter=None, **kwargs): + if exporter is not None: + self._exporter = weakref.ref(exporter) + self._age_name = exporter.age_name + self._report = exporter.report + self._version = exporter.mgr.getVer() + else: + self._exporter = None + self._age_name = kwargs.get("age_name") + self._path = kwargs.get("path") + self._version = kwargs.get("version") + self._journals = {} + self._strings = {} + + def add_journal(self, name, language, text_id, indent=0): + if text_id.is_modified: + self._report.warn("Journal '{}' translation for '{}' is modified on the disk but not reloaded in Blender.", + name, language, indent=indent) + journal = self._journals.setdefault(name, {}) + journal[language] = text_id.as_string() + + def add_string(self, set_name, element_name, language, value): + trans_set = self._strings.setdefault(set_name, {}) + trans_element = trans_set.setdefault(element_name, {}) + trans_element[language] = value + + @contextmanager + def _generate_file(self, filename, **kwargs): + if self._exporter is not None: + with self._exporter().output.generate_dat_file(filename, **kwargs) as handle: + yield handle + else: + dirname = kwargs.get("dirname", "dat") + filepath = str(Path(self._path) / dirname / filename) + handle = open(filepath, "wb") + try: + yield handle + except: + raise + finally: + handle.close() + + def _generate_journal_texts(self): + age_name = self._age_name + + def write_journal_file(language, file_name, contents): + try: + with self._generate_file(dirname="ageresources", filename=file_name) as stream: + stream.write(contents.encode("windows-1252")) + except UnicodeEncodeError: + self._report.error("Translation '{}': Contents contains characters that cannot be used in this version of Plasma", + language, indent=2) + return False + else: + return True + + for journal_name, translations in self._journals.items(): + self._report.msg("Copying Journal '{}'", journal_name, indent=1) + for language_name, value in translations.items(): + if language_name not in _SP_LANGUAGES: + self._report.warn("Translation '{}' will not be used because it is not supported in this version of Plasma.", + language_name, indent=2) + continue + suffix = "_{}".format(language_name.lower()) if language_name != "English" else "" + file_name = "{}--{}{}.txt".format(age_name, journal_name, suffix) + write_journal_file(language_name, file_name, value) + + # Ensure that default (read: "English") journal is available + if "English" not in translations: + language_name, value = next(((language_name, value) for language_name, value in translations.items() + if language_name in _SP_LANGUAGES), (None, None)) + if language_name is not None: + file_name = "{}--{}.txt".format(age_name, journal_name) + # If you manage to screw up this badly... Well, I am very sorry. + if write_journal_file(language_name, file_name, value): + self._report.warn("No 'English' translation available, so '{}' will be used as the default", + language_name, indent=2) + else: + self._report.port("No 'English' nor any other suitable default translation available", indent=2) + + def _generate_loc_file(self): + # Only generate this junk if needed + if not self._strings and not self._journals: + return + + def write_line(value, *args, **kwargs): + # tabs suck, then you die... + whitespace = " " * kwargs.pop("indent", 0) + if args or kwargs: + value = value.format(*args, **kwargs) + line = "".join((whitespace, value, "\n")) + stream.write(line.encode("utf-16_le")) + + age_name = self._age_name + enc = plEncryptedStream.kEncAes if self._version == pvEoa else None + file_name = "{}.loc".format(age_name) + with self._generate_file(file_name, enc=enc) as stream: + # UTF-16 little endian byte order mark + stream.write(b"\xFF\xFE") + + write_line("") + write_line("") + write_line("", age_name, indent=1) + + # Arbitrary strings defined by something like a GUI or a node tree + for set_name, elements in self._strings.items(): + write_line("", set_name, indent=2) + for element_name, translations in elements.items(): + write_line("", element_name, indent=3) + for language_name, value in translations.items(): + write_line("{translation}", + language=language_name, translation=xml_escape(value), indent=4) + write_line("", indent=3) + write_line("", indent=2) + + # Journals + if self._journals: + write_line("", indent=2) + for journal_name, translations in self._journals.items(): + write_line("", journal_name, indent=3) + for language_name, value in translations.items(): + write_line("{translation}", + language=language_name, translation=xml_escape(value), indent=4) + write_line("", indent=3) + write_line("", indent=2) + + # Verbose XML junk... + # You call it verbose. I call it unambiguously complete. + write_line("", indent=1) + write_line("") + + def run(self): + age_props = bpy.context.scene.world.plasma_age + loc_path = str(Path(self._path) / "dat" / "{}.loc".format(self._age_name)) + log = logger.ExportVerboseLogger if age_props.verbose else logger.ExportProgressLogger + with korlib.ConsoleToggler(age_props.show_console), log(loc_path) as self._report: + self._report.progress_add_step("Harvesting Journals") + self._report.progress_add_step("Generating Localization") + self._report.progress_start("Exporting Localization Data") + + self._run_harvest_journals() + self._run_generate() + + # DONE + self._report.progress_end() + self._report.raise_errors() + + def _run_harvest_journals(self): + objects = bpy.context.scene.objects + self._report.progress_advance() + self._report.progress_range = len(objects) + inc_progress = self._report.progress_increment + + for i in objects: + journal = i.plasma_modifiers.journalbookmod + if journal.enabled: + translations = [j for j in journal.journal_translations if j.text_id is not None] + if not translations: + self._report.error("Journal '{}': No content translations available. The journal will not be exported.", + i.name, indent=2) + for j in translations: + self.add_journal(journal.key_name, j.language, j.text_id, indent=1) + inc_progress() + + def _run_generate(self): + self._report.progress_advance() + self.save() + + def save(self): + if self._version > pvPots: + self._generate_loc_file() + else: + self._generate_journal_texts() diff --git a/korman/operators/op_export.py b/korman/operators/op_export.py index e4df68e..f5fa58e 100644 --- a/korman/operators/op_export.py +++ b/korman/operators/op_export.py @@ -218,6 +218,54 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator): setattr(PlasmaAge, name, prop(**age_options)) +class PlasmaLocalizationExportOperator(ExportOperator, bpy.types.Operator): + bl_idname = "export.plasma_loc" + bl_label = "Export Localization" + bl_description = "Export Age Localization Data" + + filepath = StringProperty(subtype="DIR_PATH") + filter_glob = StringProperty(default="*.pak", options={'HIDDEN'}) + + version = EnumProperty(name="Version", + description="Plasma version to export this age for", + items=game_versions, + default="pvPots", + options=set()) + + def execute(self, context): + path = Path(self.filepath) + if not self.filepath: + self.report({"ERROR"}, "No file specified") + return {"CANCELLED"} + else: + if not path.exists: + try: + path.mkdir(parents=True) + except OSError: + self.report({"ERROR"}, "Failed to create export directory") + return {"CANCELLED"} + + # Age names cannot be python keywords + age_name = context.scene.world.plasma_age.age_name + if korlib.is_python_keyword(age_name): + self.report({"ERROR"}, "The Age name conflicts with the Python keyword '{}'".format(age_name)) + return {"CANCELLED"} + + # Bonus Fun: Implement Profile-mode here (later...) + e = exporter.LocalizationConverter(age_name=age_name, path=self.filepath, + version=globals()[self.version]) + try: + e.run() + except exporter.ExportError as error: + self.report({"ERROR"}, str(error)) + return {"CANCELLED"} + except exporter.NonfatalExportError as error: + self.report({"WARNING"}, str(error)) + return {"FINISHED"} + else: + return {"FINISHED"} + + class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator): bl_idname = "export.plasma_pak" bl_label = "Package Scripts" diff --git a/korman/properties/modifiers/gui.py b/korman/properties/modifiers/gui.py index cc3adf7..773ec5a 100644 --- a/korman/properties/modifiers/gui.py +++ b/korman/properties/modifiers/gui.py @@ -21,19 +21,11 @@ from bpy.props import * from PyHSPlasma import * from ...addon_prefs import game_versions -from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz +from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable from ... import idprops journal_pfms = { - pvPrime : { - "filename": "xJournalBookGUIPopup.py", - "attribs": ( - { 'id': 1, 'type': "ptAttribActivator", "name": "actClickableBook" }, - { 'id': 3, 'type': "ptAttribString", "name": "JournalName" }, - { 'id': 10, 'type': "ptAttribBoolean", 'name': "StartOpen" }, - ) - }, pvPots : { # Supplied by the OfflineKI script: # https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleJournal.py @@ -59,6 +51,26 @@ journal_pfms = { }, } +# Do not change the numeric IDs. They allow the list to be rearranged. +_languages = [("Dutch", "Nederlands", "Dutch", 0), + ("English", "English", "", 1), + ("Finnish", "Suomi", "Finnish", 2), + ("French", "Français", "French", 3), + ("German", "Deutsch", "German", 4), + ("Hungarian", "Magyar", "Hungarian", 5), + ("Italian", "Italiano ", "Italian", 6), + # Blender 2.79b can't render 日本語 by default + ("Japanese", "Nihongo", "Japanese", 7), + ("Norwegian", "Norsk", "Norwegian", 8), + ("Polish", "Polski", "Polish", 9), + ("Romanian", "Română", "Romanian", 10), + ("Russian", "Pyccĸий", "Russian", 11), + ("Spanish", "Español", "Spanish", 12), + ("Swedish", "Svenska", "Swedish", 13)] +languages = sorted(_languages, key=lambda x: x[1]) +_DEFAULT_LANGUAGE_NAME = "English" +_DEFAULT_LANGUAGE_ID = 1 + class ImageLibraryItem(bpy.types.PropertyGroup): image = bpy.props.PointerProperty(name="Image Item", @@ -91,6 +103,22 @@ class PlasmaImageLibraryModifier(PlasmaModifierProperties): exporter.mesh.material.export_prepared_image(owner=ilmod, image=item.image, allowed_formats={"JPG", "PNG"}, extension="hsm") +class PlasmaJournalTranslation(bpy.types.PropertyGroup): + def _poll_nonpytext(self, value): + return not value.name.endswith(".py") + + language = EnumProperty(name="Language", + description="Language of this translation", + items=languages, + default=_DEFAULT_LANGUAGE_NAME, + options=set()) + text_id = PointerProperty(name="Journal Contents", + description="Text data block containing the journal's contents for this language", + type=bpy.types.Text, + poll=_poll_nonpytext, + options=set()) + + class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz): pl_id = "journalbookmod" @@ -122,28 +150,69 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz description="Height scale", default=100, min=0, max=100, subtype="PERCENTAGE") - book_source_locpath = StringProperty(name="Book Source LocPath", - description="LocPath for book's text (MO:UL)", - default="Global.Journals.Empty") - book_source_filename = StringProperty(name="Book Source Filename", - description="Filename for book's text (Uru:CC)", - default="") - book_source_name = StringProperty(name="Book Source Name", - description="Name of xJournalBookDefs.py entry for book's text (Uru:ABM)", - default="Dummy") clickable_region = PointerProperty(name="Region", description="Region inside which the avatar must stand to be able to open the journal (optional)", type=bpy.types.Object, poll=idprops.poll_mesh_objects) + def _get_translation(self): + # Ensure there is always a default (read: English) translation available. + default_idx, default = next(((idx, translation) for idx, translation in enumerate(self.journal_translations) + if translation.language == _DEFAULT_LANGUAGE_NAME), (None, None)) + if default is None: + default_idx = len(self.journal_translations) + default = self.journal_translations.add() + default.language = _DEFAULT_LANGUAGE_NAME + if self.active_translation_index < len(self.journal_translations): + language = self.journal_translations[self.active_translation_index].language + else: + self.active_translation_index = default_idx + language = default.language + + # Due to the fact that we are using IDs to keep the data from becoming insane on new + # additions, we must return the integer id... + return next((idx for key, _, _, idx in languages if key == language)) + + def _set_translation(self, value): + # We were given an int here, must change to a string + language_name = next((key for key, _, _, i in languages if i == value)) + idx = next((idx for idx, translation in enumerate(self.journal_translations) + if translation.language == language_name), None) + if idx is None: + self.active_translation_index = len(self.journal_translations) + translation = self.journal_translations.add() + translation.language = language_name + else: + self.active_translation_index = idx + + journal_translations = CollectionProperty(name="Journal Translations", + type=PlasmaJournalTranslation, + options=set()) + active_translation_index = IntProperty(options={"HIDDEN"}) + active_translation = EnumProperty(name="Language", + description="Language of this translation", + items=languages, + get=_get_translation, set=_set_translation, + options=set()) + def export(self, exporter, bo, so): - our_versions = [globals()[j] for j in self.versions] + our_versions = (globals()[j] for j in self.versions) version = exporter.mgr.getVer() if version not in our_versions: # We aren't needed here - exporter.report.port("Object '{}' has a JournalMod not enabled for export to the selected engine. Skipping.".format(bo.name, version), indent=2) + exporter.report.port("Object '{}' has a JournalMod not enabled for export to the selected engine. Skipping.", + bo.name, version, indent=2) return + # Export the Journal translation contents + translations = [i for i in self.journal_translations if i.text_id is not None] + if not translations: + exporter.report.error("Journal '{}': No content translations available. The journal will not be exported.", + bo.name, indent=2) + return + for i in translations: + exporter.locman.add_journal(self.key_name, i.language, i.text_id, indent=2) + if self.clickable_region is None: # Create a region for the clickable's condition rgn_mesh = bpy.data.meshes.new("{}_Journal_ClkRgn".format(self.key_name)) @@ -163,14 +232,14 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz self.temp_rgn = self.clickable_region # Generate the logic nodes - with self.generate_logic(bo, version=version) as tree: + with self.generate_logic(bo, age_name=exporter.age_name, version=version) as tree: tree.export(exporter, bo, so) # Get rid of our temporary clickable region if self.clickable_region is None: bpy.context.scene.objects.unlink(self.temp_rgn) - def logicwiz(self, bo, tree, version): + def logicwiz(self, bo, tree, age_name, version): nodes = tree.nodes # Assign journal script based on target version @@ -188,36 +257,12 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz new_attr.attribute_name = attr["name"] journalnode.update() - if version == pvPrime: - self.create_prime_nodes(bo, nodes, journalnode) - elif version == pvPots: - self.create_pots_nodes(bo, nodes, journalnode) - elif version == pvMoul: - self.create_moul_nodes(bo, nodes, journalnode) - - def create_prime_nodes(self, clickable_object, nodes, journalnode): - clickable_region = nodes.new("PlasmaClickableRegionNode") - clickable_region.region_object = self.temp_rgn - - facing_object = nodes.new("PlasmaFacingTargetNode") - facing_object.directional = False - facing_object.tolerance = math.degrees(-1) - - clickable = nodes.new("PlasmaClickableNode") - clickable.link_input(clickable_region, "satisfies", "region") - clickable.link_input(facing_object, "satisfies", "facing") - clickable.link_output(journalnode, "satisfies", "actClickableBook") - clickable.clickable_object = clickable_object - - start_open = nodes.new("PlasmaAttribBoolNode") - start_open.link_output(journalnode, "pfm", "StartOpen") - start_open.value = self.start_state == "OPEN" - - journal_name = nodes.new("PlasmaAttribStringNode") - journal_name.link_output(journalnode, "pfm", "JournalName") - journal_name.value = self.book_source_name + if version <= pvPots: + self._create_pots_nodes(bo, nodes, journalnode, age_name) + else: + self._create_moul_nodes(bo, nodes, journalnode, age_name) - def create_pots_nodes(self, clickable_object, nodes, journalnode): + def _create_pots_nodes(self, clickable_object, nodes, journalnode, age_name): clickable_region = nodes.new("PlasmaClickableRegionNode") clickable_region.region_object = self.temp_rgn @@ -233,7 +278,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz srcfile = nodes.new("PlasmaAttribStringNode") srcfile.link_output(journalnode, "pfm", "journalFileName") - srcfile.value = self.book_source_filename + srcfile.value = self.key_name guitype = nodes.new("PlasmaAttribBoolNode") guitype.link_output(journalnode, "pfm", "isNotebook") @@ -247,7 +292,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz height.link_output(journalnode, "pfm", "BookHeight") height.value_float = self.book_scale_h / 100.0 - def create_moul_nodes(self, clickable_object, nodes, journalnode): + def _create_moul_nodes(self, clickable_object, nodes, journalnode, age_name): clickable_region = nodes.new("PlasmaClickableRegionNode") clickable_region.region_object = self.temp_rgn @@ -275,7 +320,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz locpath = nodes.new("PlasmaAttribStringNode") locpath.link_output(journalnode, "pfm", "LocPath") - locpath.value = self.book_source_locpath + locpath.value = "{}.Journals.{}".format(age_name, self.key_name) guitype = nodes.new("PlasmaAttribStringNode") guitype.link_output(journalnode, "pfm", "GUIType") diff --git a/korman/ui/modifiers/gui.py b/korman/ui/modifiers/gui.py index 143469d..68fe543 100644 --- a/korman/ui/modifiers/gui.py +++ b/korman/ui/modifiers/gui.py @@ -36,22 +36,34 @@ def imagelibmod(modifier, layout, context): def journalbookmod(modifier, layout, context): layout.prop_menu_enum(modifier, "versions") + layout.separator() - if not {"pvPrime", "pvMoul"}.isdisjoint(modifier.versions): - layout.prop(modifier, "start_state") + split = layout.split() + main_col = split.column() - if not {"pvPots", "pvMoul"}.isdisjoint(modifier.versions): - layout.prop(modifier, "book_type") - row = layout.row(align=True) - row.label("Book Scaling:") - row.prop(modifier, "book_scale_w", text="Width", slider=True) - row.prop(modifier, "book_scale_h", text="Height", slider=True) - - if "pvPrime" in modifier.versions: - layout.prop(modifier, "book_source_name", text="Name") - if "pvPots" in modifier.versions: - layout.prop(modifier, "book_source_filename", text="Filename") - if "pvMoul" in modifier.versions: - layout.prop(modifier, "book_source_locpath", text="LocPath") - - layout.prop(modifier, "clickable_region") + main_col.label("Display Settings:") + col = main_col.column() + col.active = "pvMoul" in modifier.versions + col.prop(modifier, "start_state", text="") + main_col.prop(modifier, "book_type", text="") + main_col.separator() + main_col.label("Book Scaling:") + col = main_col.column(align=True) + col.prop(modifier, "book_scale_w", text="Width", slider=True) + col.prop(modifier, "book_scale_h", text="Height", slider=True) + + main_col = split.column() + main_col.label("Content Translations:") + main_col.prop(modifier, "active_translation", text="") + # This should never fail... + try: + translation = modifier.journal_translations[modifier.active_translation_index] + except Exception as e: + main_col.label(text="Error (see console)", icon="ERROR") + print(e) + else: + main_col.prop(translation, "text_id", text="") + main_col.separator() + + main_col.label("Clickable Region:") + main_col.prop(modifier, "clickable_region", text="") diff --git a/korman/ui/ui_world.py b/korman/ui/ui_world.py index 6618bfb..220466c 100644 --- a/korman/ui/ui_world.py +++ b/korman/ui/ui_world.py @@ -29,7 +29,59 @@ class AgeButtonsPanel: return context.world and context.scene.render.engine == "PLASMA_GAME" -class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel): +class PlasmaGameHelper: + @property + def active_game(self): + games = bpy.context.world.plasma_games + prefs = bpy.context.user_preferences.addons["korman"].preferences + active_game_index = games.active_game_index + if active_game_index < len(prefs.games): + return prefs.games[active_game_index] + else: + return None + + def format_path(self, dirname="dat", ext=".age"): + active_game = self.active_game + if active_game is None: + return "" + age_name = bpy.context.world.plasma_age.age_name + return str((Path(active_game.path) / dirname / age_name).with_suffix(ext)) + + @property + def legal_game(self): + if self.active_game is not None: + return bool(bpy.context.world.plasma_age.age_name.strip()) + + +class PlasmaGameExportMenu(PlasmaGameHelper, bpy.types.Menu): + bl_label = "Plasma Export Menu" + + def draw(self, context): + layout = self.layout + age_name = context.world.plasma_age.age_name + active_game = self.active_game + legal_game = self.legal_game + + # Localization + row = layout.row() + row.operator_context = "EXEC_DEFAULT" + row.enabled = legal_game + op = row.operator("export.plasma_loc", icon="FILE_SCRIPT") + if active_game is not None: + op.filepath = active_game.path + op.version = active_game.version + + # Python + row = layout.row() + row.operator_context = "EXEC_DEFAULT" + row.enabled = legal_game and active_game.version != "pvMoul" + op = row.operator("export.plasma_pak", icon="FILE_SCRIPT") + op.filepath = self.format_path("Python", ".pak") + if active_game is not None: + op.version = active_game.version + + +class PlasmaGamePanel(AgeButtonsPanel, PlasmaGameHelper, bpy.types.Panel): bl_label = "Plasma Games" def draw(self, context): @@ -37,6 +89,8 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel): prefs = context.user_preferences.addons["korman"].preferences games = context.world.plasma_games age = context.world.plasma_age + active_game = self.active_game + legal_game = self.legal_game row = layout.row() # Remember: game storage moved to addon preferences! @@ -44,39 +98,31 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel): "active_game_index", rows=2) row.operator("ui.korman_open_prefs", icon="PREFERENCES", text="") - # Game Tools - active_game_index = games.active_game_index - if active_game_index < len(prefs.games): - active_game = prefs.games[active_game_index] - else: - active_game = None - layout.separator() row = layout.row(align=True) - legal_game = bool(age.age_name.strip()) and active_game is not None + # Export Age row.operator_context = "EXEC_DEFAULT" row.enabled = legal_game op = row.operator("export.plasma_age", icon="EXPORT") if active_game is not None: op.dat_only = False - op.filepath = str((Path(active_game.path) / "dat" / age.age_name).with_suffix(".age")) + op.filepath = self.format_path() op.version = active_game.version + + # Package Age row = row.row(align=True) row.enabled = legal_game row.operator_context = "INVOKE_DEFAULT" op = row.operator("export.plasma_age", icon="PACKAGE", text="Package Age") + op.dat_only = False + op.filepath = "{}.zip".format(age.age_name) if active_game is not None: - op.dat_only = False - op.filepath = "{}.zip".format(age.age_name) op.version = active_game.version + + # Special Menu row = row.row(align=True) - row.operator_context = "EXEC_DEFAULT" - row.enabled = legal_game and active_game.version != "pvMoul" - op = row.operator("export.plasma_pak", icon="FILE_SCRIPT") - if active_game is not None: - op.filepath = str((Path(active_game.path) / "Python" / age.age_name).with_suffix(".pak")) - op.version = active_game.version + row.menu("PlasmaGameExportMenu", icon='DOWNARROW_HLT', text="") class PlasmaGameListRO(bpy.types.UIList):