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