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):