Browse Source

Fix #129

pull/130/head
Adam Johnson 6 years ago
parent
commit
4f7f8a05fe
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 3
      korman/exporter/convert.py
  2. 142
      korman/exporter/locman.py
  3. 153
      korman/properties/modifiers/gui.py
  4. 46
      korman/ui/modifiers/gui.py

3
korman/exporter/convert.py

@ -24,6 +24,7 @@ from . import camera
from . import explosions from . import explosions
from . import etlight from . import etlight
from . import image from . import image
from . import locman
from . import logger from . import logger
from . import manager from . import manager
from . import mesh from . import mesh
@ -52,6 +53,7 @@ class Exporter:
self.output = outfile.OutputFiles(self, self._op.filepath) self.output = outfile.OutputFiles(self, self._op.filepath)
self.camera = camera.CameraConverter(self) self.camera = camera.CameraConverter(self)
self.image = image.ImageCache(self) self.image = image.ImageCache(self)
self.locman = locman.LocalizationConverter(self)
# Step 0.8: Init the progress mgr # Step 0.8: Init the progress mgr
self.mesh.add_progress_presteps(self.report) 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 # If something bad happens in the final flush, it would be a shame to
# simply toss away the potentially freshly regenerated texture cache. # simply toss away the potentially freshly regenerated texture cache.
try: try:
self.locman.save()
self.mgr.save_age() self.mgr.save_age()
self.output.save() self.output.save()
finally: finally:

142
korman/exporter/locman.py

@ -0,0 +1,142 @@
# 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 <http://www.gnu.org/licenses/>.
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):
self._exporter = weakref.ref(exporter)
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
def _generate_journal_texts(self):
age_name = self._exporter().age_name
output = self._exporter().output
def write_journal_file(language, file_name, contents):
try:
with output.generate_dat_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._exporter().age_name
enc = plEncryptedStream.kEncAes if self._version == pvEoa else None
file_name = "{}.loc".format(age_name)
with self._exporter().output.generate_dat_file(file_name, enc=enc) as stream:
# UTF-16 little endian byte order mark
stream.write(b"\xFF\xFE")
write_line("<?xml version=\"1.0\" encoding=\"utf-16\"?>")
write_line("<localizations>")
write_line("<age name=\"{}\">", 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=\"{}\">", set_name, indent=2)
for element_name, translations in elements.items():
write_line("<element name=\"{}\">", element_name, indent=3)
for language_name, value in translations.items():
write_line("<translation language=\"{language}\">{translation}</translation>",
language=language_name, translation=xml_escape(value), indent=4)
write_line("</element>", indent=3)
write_line("</set>", indent=2)
# Journals
if self._journals:
write_line("<set name=\"Journals\">", indent=2)
for journal_name, translations in self._journals.items():
write_line("<element name=\"{}\">", journal_name, indent=3)
for language_name, value in translations.items():
write_line("<translation language=\"{language}\">{translation}</translation>",
language=language_name, translation=xml_escape(value), indent=4)
write_line("</element>", indent=3)
write_line("</set>", indent=2)
# Verbose XML junk...
# <Deledrius> You call it verbose. I call it unambiguously complete.
write_line("</age>", indent=1)
write_line("</localizations>")
def save(self):
if self._version > pvPots:
self._generate_loc_file()
else:
self._generate_journal_texts()
@property
def _report(self):
return self._exporter().report
@property
def _version(self):
return self._exporter().mgr.getVer()

153
korman/properties/modifiers/gui.py

@ -21,19 +21,11 @@ from bpy.props import *
from PyHSPlasma import * from PyHSPlasma import *
from ...addon_prefs import game_versions from ...addon_prefs import game_versions
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable
from ... import idprops from ... import idprops
journal_pfms = { 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 : { pvPots : {
# Supplied by the OfflineKI script: # Supplied by the OfflineKI script:
# https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleJournal.py # 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): class ImageLibraryItem(bpy.types.PropertyGroup):
image = bpy.props.PointerProperty(name="Image Item", 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") 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): class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "journalbookmod" pl_id = "journalbookmod"
@ -122,27 +150,68 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
description="Height scale", description="Height scale",
default=100, min=0, max=100, default=100, min=0, max=100,
subtype="PERCENTAGE") 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", clickable_region = PointerProperty(name="Region",
description="Region inside which the avatar must stand to be able to open the journal (optional)", description="Region inside which the avatar must stand to be able to open the journal (optional)",
type=bpy.types.Object, type=bpy.types.Object,
poll=idprops.poll_mesh_objects) 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): 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() version = exporter.mgr.getVer()
if version not in our_versions: if version not in our_versions:
# We aren't needed here # 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 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: if self.clickable_region is None:
# Create a region for the clickable's condition # Create a region for the clickable's condition
@ -163,14 +232,14 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
self.temp_rgn = self.clickable_region self.temp_rgn = self.clickable_region
# Generate the logic nodes # 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) tree.export(exporter, bo, so)
# Get rid of our temporary clickable region # Get rid of our temporary clickable region
if self.clickable_region is None: if self.clickable_region is None:
bpy.context.scene.objects.unlink(self.temp_rgn) 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 nodes = tree.nodes
# Assign journal script based on target version # Assign journal script based on target version
@ -188,36 +257,12 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
new_attr.attribute_name = attr["name"] new_attr.attribute_name = attr["name"]
journalnode.update() journalnode.update()
if version == pvPrime: if version <= pvPots:
self.create_prime_nodes(bo, nodes, journalnode) self._create_pots_nodes(bo, nodes, journalnode, age_name)
elif version == pvPots: else:
self.create_pots_nodes(bo, nodes, journalnode) self._create_moul_nodes(bo, nodes, journalnode, age_name)
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
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 = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = self.temp_rgn clickable_region.region_object = self.temp_rgn
@ -233,7 +278,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
srcfile = nodes.new("PlasmaAttribStringNode") srcfile = nodes.new("PlasmaAttribStringNode")
srcfile.link_output(journalnode, "pfm", "journalFileName") srcfile.link_output(journalnode, "pfm", "journalFileName")
srcfile.value = self.book_source_filename srcfile.value = self.key_name
guitype = nodes.new("PlasmaAttribBoolNode") guitype = nodes.new("PlasmaAttribBoolNode")
guitype.link_output(journalnode, "pfm", "isNotebook") guitype.link_output(journalnode, "pfm", "isNotebook")
@ -247,7 +292,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
height.link_output(journalnode, "pfm", "BookHeight") height.link_output(journalnode, "pfm", "BookHeight")
height.value_float = self.book_scale_h / 100.0 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 = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = self.temp_rgn clickable_region.region_object = self.temp_rgn
@ -275,7 +320,7 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
locpath = nodes.new("PlasmaAttribStringNode") locpath = nodes.new("PlasmaAttribStringNode")
locpath.link_output(journalnode, "pfm", "LocPath") 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 = nodes.new("PlasmaAttribStringNode")
guitype.link_output(journalnode, "pfm", "GUIType") guitype.link_output(journalnode, "pfm", "GUIType")

46
korman/ui/modifiers/gui.py

@ -36,22 +36,34 @@ def imagelibmod(modifier, layout, context):
def journalbookmod(modifier, layout, context): def journalbookmod(modifier, layout, context):
layout.prop_menu_enum(modifier, "versions") layout.prop_menu_enum(modifier, "versions")
layout.separator()
if not {"pvPrime", "pvMoul"}.isdisjoint(modifier.versions): split = layout.split()
layout.prop(modifier, "start_state") main_col = split.column()
if not {"pvPots", "pvMoul"}.isdisjoint(modifier.versions): main_col.label("Display Settings:")
layout.prop(modifier, "book_type") col = main_col.column()
row = layout.row(align=True) col.active = "pvMoul" in modifier.versions
row.label("Book Scaling:") col.prop(modifier, "start_state", text="")
row.prop(modifier, "book_scale_w", text="Width", slider=True) main_col.prop(modifier, "book_type", text="")
row.prop(modifier, "book_scale_h", text="Height", slider=True) main_col.separator()
main_col.label("Book Scaling:")
if "pvPrime" in modifier.versions: col = main_col.column(align=True)
layout.prop(modifier, "book_source_name", text="Name") col.prop(modifier, "book_scale_w", text="Width", slider=True)
if "pvPots" in modifier.versions: col.prop(modifier, "book_scale_h", text="Height", slider=True)
layout.prop(modifier, "book_source_filename", text="Filename")
if "pvMoul" in modifier.versions: main_col = split.column()
layout.prop(modifier, "book_source_locpath", text="LocPath") main_col.label("Content Translations:")
main_col.prop(modifier, "active_translation", text="")
layout.prop(modifier, "clickable_region") # 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="")

Loading…
Cancel
Save