Browse Source

Implement localization export operator

This is like the Package Python operator in that the artist can now
export the loc data independent of the rest of the age. The difference
is that due to the extremely different loc formats from PotS+UAM to
MOUL, we can't easily allow the user to specify where the data will be
exported to. So, we only allow this operation in the context of a game.
pull/130/head
Adam Johnson 6 years ago
parent
commit
ad2b463d71
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 1
      korman/exporter/__init__.py
  2. 89
      korman/exporter/locman.py
  3. 48
      korman/operators/op_export.py
  4. 11
      korman/ui/ui_world.py

1
korman/exporter/__init__.py

@ -18,5 +18,6 @@ from PyHSPlasma import *
from .convert import * from .convert import *
from .explosions import * from .explosions import *
from .locman import *
from .python import * from .python import *
from . import utils from . import utils

89
korman/exporter/locman.py

@ -13,6 +13,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>. # along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bpy
from contextlib import contextmanager
from .explosions import NonfatalExportError
from .. import korlib
from . import logger
from pathlib import Path
from PyHSPlasma import * from PyHSPlasma import *
import weakref import weakref
from xml.sax.saxutils import escape as xml_escape from xml.sax.saxutils import escape as xml_escape
@ -20,8 +26,17 @@ from xml.sax.saxutils import escape as xml_escape
_SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"} _SP_LANGUAGES = {"English", "French", "German", "Italian", "Spanish"}
class LocalizationConverter: class LocalizationConverter:
def __init__(self, exporter): def __init__(self, exporter=None, **kwargs):
self._exporter = weakref.ref(exporter) 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._journals = {}
self._strings = {} self._strings = {}
@ -37,13 +52,28 @@ class LocalizationConverter:
trans_element = trans_set.setdefault(element_name, {}) trans_element = trans_set.setdefault(element_name, {})
trans_element[language] = value 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): def _generate_journal_texts(self):
age_name = self._exporter().age_name age_name = self._age_name
output = self._exporter().output
def write_journal_file(language, file_name, contents): def write_journal_file(language, file_name, contents):
try: try:
with output.generate_dat_file(dirname="ageresources", filename=file_name) as stream: with self._generate_file(dirname="ageresources", filename=file_name) as stream:
stream.write(contents.encode("windows-1252")) stream.write(contents.encode("windows-1252"))
except UnicodeEncodeError: except UnicodeEncodeError:
self._report.error("Translation '{}': Contents contains characters that cannot be used in this version of Plasma", self._report.error("Translation '{}': Contents contains characters that cannot be used in this version of Plasma",
@ -89,10 +119,10 @@ class LocalizationConverter:
line = "".join((whitespace, value, "\n")) line = "".join((whitespace, value, "\n"))
stream.write(line.encode("utf-16_le")) stream.write(line.encode("utf-16_le"))
age_name = self._exporter().age_name age_name = self._age_name
enc = plEncryptedStream.kEncAes if self._version == pvEoa else None enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
file_name = "{}.loc".format(age_name) file_name = "{}.loc".format(age_name)
with self._exporter().output.generate_dat_file(file_name, enc=enc) as stream: with self._generate_file(file_name, enc=enc) as stream:
# UTF-16 little endian byte order mark # UTF-16 little endian byte order mark
stream.write(b"\xFF\xFE") stream.write(b"\xFF\xFE")
@ -127,16 +157,45 @@ class LocalizationConverter:
write_line("</age>", indent=1) write_line("</age>", indent=1)
write_line("</localizations>") write_line("</localizations>")
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): def save(self):
if self._version > pvPots: if self._version > pvPots:
self._generate_loc_file() self._generate_loc_file()
else: else:
self._generate_journal_texts() self._generate_journal_texts()
@property
def _report(self):
return self._exporter().report
@property
def _version(self):
return self._exporter().mgr.getVer()

48
korman/operators/op_export.py

@ -218,6 +218,54 @@ class PlasmaAgeExportOperator(ExportOperator, bpy.types.Operator):
setattr(PlasmaAge, name, prop(**age_options)) 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): class PlasmaPythonExportOperator(ExportOperator, bpy.types.Operator):
bl_idname = "export.plasma_pak" bl_idname = "export.plasma_pak"
bl_label = "Package Scripts" bl_label = "Package Scripts"

11
korman/ui/ui_world.py

@ -55,6 +55,7 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel):
row = layout.row(align=True) row = layout.row(align=True)
legal_game = bool(age.age_name.strip()) and active_game is not None legal_game = bool(age.age_name.strip()) and active_game is not None
# Export Age
row.operator_context = "EXEC_DEFAULT" row.operator_context = "EXEC_DEFAULT"
row.enabled = legal_game row.enabled = legal_game
op = row.operator("export.plasma_age", icon="EXPORT") op = row.operator("export.plasma_age", icon="EXPORT")
@ -62,6 +63,7 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel):
op.dat_only = False op.dat_only = False
op.filepath = str((Path(active_game.path) / "dat" / age.age_name).with_suffix(".age")) op.filepath = str((Path(active_game.path) / "dat" / age.age_name).with_suffix(".age"))
op.version = active_game.version op.version = active_game.version
# Package Age
row = row.row(align=True) row = row.row(align=True)
row.enabled = legal_game row.enabled = legal_game
row.operator_context = "INVOKE_DEFAULT" row.operator_context = "INVOKE_DEFAULT"
@ -70,6 +72,15 @@ class PlasmaGamePanel(AgeButtonsPanel, bpy.types.Panel):
op.dat_only = False op.dat_only = False
op.filepath = "{}.zip".format(age.age_name) op.filepath = "{}.zip".format(age.age_name)
op.version = active_game.version op.version = active_game.version
# Export Localization
row = row.row(align=True)
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
# Package Scripts
row = row.row(align=True) row = row.row(align=True)
row.operator_context = "EXEC_DEFAULT" row.operator_context = "EXEC_DEFAULT"
row.enabled = legal_game and active_game.version != "pvMoul" row.enabled = legal_game and active_game.version != "pvMoul"

Loading…
Cancel
Save