diff --git a/korman/nodes/node_python.py b/korman/nodes/node_python.py
index 13a1066..e256e33 100644
--- a/korman/nodes/node_python.py
+++ b/korman/nodes/node_python.py
@@ -310,7 +310,7 @@ class PlasmaPythonFileNode(PlasmaNodeBase, bpy.types.Node):
self.inputs.remove(unconnected.pop())
-class PlasmaPythonFileNodeSocket(bpy.types.NodeSocket):
+class PlasmaPythonFileNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
attribute_id = IntProperty(options={"HIDDEN"})
@property
@@ -346,7 +346,7 @@ class PlasmaPythonFileNodeSocket(bpy.types.NodeSocket):
return self.node.attribute_map[self.attribute_id].attribute_arguments
-class PlasmaPythonAttribNodeSocket(bpy.types.NodeSocket):
+class PlasmaPythonAttribNodeSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
def draw(self, context, layout, node, text):
attrib = node.to_socket
if attrib is None:
diff --git a/korman/properties/modifiers/__init__.py b/korman/properties/modifiers/__init__.py
index ab55d08..7713ed9 100644
--- a/korman/properties/modifiers/__init__.py
+++ b/korman/properties/modifiers/__init__.py
@@ -18,6 +18,7 @@ import bpy
from .base import PlasmaModifierProperties
from .anim import *
from .avatar import *
+from .gui import *
from .logic import *
from .physics import *
from .region import *
diff --git a/korman/properties/modifiers/gui.py b/korman/properties/modifiers/gui.py
new file mode 100644
index 0000000..398a1a6
--- /dev/null
+++ b/korman/properties/modifiers/gui.py
@@ -0,0 +1,258 @@
+# 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
+import math
+import bmesh
+import mathutils
+from bpy.props import *
+from PyHSPlasma import *
+
+from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
+from .logic import game_versions
+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
+ "filename": "xSimpleJournal.py",
+ "attribs": (
+ { 'id': 1, 'type': "ptAttribActivator", "name": "bookClickable" },
+ { 'id': 2, 'type': "ptAttribString", "name": "journalFileName" },
+ { 'id': 3, 'type': "ptAttribBoolean", "name": "isNotebook" },
+ { 'id': 4, 'type': "ptAttribFloat", "name": "BookWidth" },
+ { 'id': 5, 'type': "ptAttribFloat", "name": "BookHeight" },
+ )
+ },
+ pvMoul : {
+ "filename": "xJournalBookGUIPopup.py",
+ "attribs": (
+ { 'id': 1, 'type': "ptAttribActivator", 'name': "actClickableBook" },
+ { 'id': 10, 'type': "ptAttribBoolean", 'name': "StartOpen" },
+ { 'id': 11, 'type': "ptAttribFloat", 'name': "BookWidth" },
+ { 'id': 12, 'type': "ptAttribFloat", 'name': "BookHeight" },
+ { 'id': 13, 'type': "ptAttribString", 'name': "LocPath" },
+ { 'id': 14, 'type': "ptAttribString", 'name': "GUIType" },
+ )
+ },
+}
+
+class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz):
+ pl_id = "journalbookmod"
+
+ bl_category = "GUI"
+ bl_label = "Journal"
+ bl_description = "Journal Book"
+ bl_icon = "WORDWRAP_ON"
+
+ versions = EnumProperty(name="Export Targets",
+ description="Plasma versions for which this journal exports",
+ items=game_versions,
+ options={"ENUM_FLAG"},
+ default={"pvMoul"})
+ start_state = EnumProperty(name="Start",
+ description="State of journal when activated",
+ items=[("OPEN", "Open", "Journal will start opened to first page"),
+ ("CLOSED", "Closed", "Journal will start closed showing cover")],
+ default="CLOSED")
+ book_type = EnumProperty(name="Book Type",
+ description="GUI type to be used for the journal",
+ items=[("bkBook", "Book", "A journal written on worn, yellowed paper"),
+ ("bkNotebook", "Notebook", "A journal written on white, lined paper")],
+ default="bkBook")
+ book_scale_w = IntProperty(name="Book Width Scale",
+ description="Width scale",
+ default=100, min=0, max=100,
+ subtype="PERCENTAGE")
+ book_scale_h = IntProperty(name="Book Height Scale",
+ 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 export(self, exporter, bo, so):
+ 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)
+ return
+
+ 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))
+ self.temp_rgn = bpy.data.objects.new("{}_Journal_ClkRgn".format(self.key_name), rgn_mesh)
+ bm = bmesh.new()
+ bmesh.ops.create_cube(bm, size=(6.0))
+ bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.location - self.temp_rgn.location), space=self.temp_rgn.matrix_world, verts=bm.verts)
+ bm.to_mesh(rgn_mesh)
+ bm.free()
+
+ # No need to enable the object as a Plasma object; we're exported automatically as part of the node tree.
+ # It does need a page, however, so we'll put it in the same place as the journal object itself.
+ self.temp_rgn.plasma_object.page = bo.plasma_object.page
+ bpy.context.scene.objects.link(self.temp_rgn)
+ else:
+ # Use the region provided
+ self.temp_rgn = self.clickable_region
+
+ # Generate the logic nodes
+ self.logicwiz(bo, version)
+
+ # Export the node tree
+ self.node_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, version):
+ tree = self.node_tree
+ nodes = tree.nodes
+ nodes.clear()
+
+ # Assign journal script based on target version
+ journal_pfm = journal_pfms[version]
+ journalnode = nodes.new("PlasmaPythonFileNode")
+ journalnode.filename = journal_pfm["filename"]
+
+ # Manually add required attributes to the PFM
+ journal_attribs = journal_pfm["attribs"]
+ for attr in journal_attribs:
+ new_attr = journalnode.attributes.add()
+ new_attr.attribute_id = attr["id"]
+ new_attr.attribute_type = attr["type"]
+ 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
+
+ def create_pots_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", "bookClickable")
+ clickable.clickable_object = clickable_object
+
+ srcfile = nodes.new("PlasmaAttribStringNode")
+ srcfile.link_output(journalnode, "pfm", "journalFileName")
+ srcfile.value = self.book_source_filename
+
+ guitype = nodes.new("PlasmaAttribBoolNode")
+ guitype.link_output(journalnode, "pfm", "isNotebook")
+ guitype.value = self.book_type == "bkNotebook"
+
+ width = nodes.new("PlasmaAttribNumericNode")
+ width.link_output(journalnode, "pfm", "BookWidth")
+ width.value_float = self.book_scale_w / 100.0
+
+ height = nodes.new("PlasmaAttribNumericNode")
+ height.link_output(journalnode, "pfm", "BookHeight")
+ height.value_float = self.book_scale_h / 100.0
+
+ def create_moul_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"
+
+ width = nodes.new("PlasmaAttribNumericNode")
+ width.link_output(journalnode, "pfm", "BookWidth")
+ width.value_float = self.book_scale_w / 100.0
+
+ height = nodes.new("PlasmaAttribNumericNode")
+ height.link_output(journalnode, "pfm", "BookHeight")
+ height.value_float = self.book_scale_h / 100.0
+
+ locpath = nodes.new("PlasmaAttribStringNode")
+ locpath.link_output(journalnode, "pfm", "LocPath")
+ locpath.value = self.book_source_locpath
+
+ guitype = nodes.new("PlasmaAttribStringNode")
+ guitype.link_output(journalnode, "pfm", "GUIType")
+ guitype.value = self.book_type
+
+ @property
+ def requires_actor(self):
+ # We are too late in the export to be harvested automatically, so let's be explicit
+ return True
diff --git a/korman/ui/modifiers/__init__.py b/korman/ui/modifiers/__init__.py
index ffbf492..aa6f7f6 100644
--- a/korman/ui/modifiers/__init__.py
+++ b/korman/ui/modifiers/__init__.py
@@ -15,6 +15,7 @@
from .anim import *
from .avatar import *
+from .gui import *
from .logic import *
from .physics import *
from .region import *
diff --git a/korman/ui/modifiers/gui.py b/korman/ui/modifiers/gui.py
new file mode 100644
index 0000000..abb96ee
--- /dev/null
+++ b/korman/ui/modifiers/gui.py
@@ -0,0 +1,38 @@
+# 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
+
+def journalbookmod(modifier, layout, context):
+ layout.prop_menu_enum(modifier, "versions")
+
+ if not {"pvPrime", "pvMoul"}.isdisjoint(modifier.versions):
+ layout.prop(modifier, "start_state")
+
+ 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")