Browse Source

Merge pull request #180 from Deledrius/linking_book_mod

Add a simple LinkingBook Modifier.
pull/181/head
Adam Johnson 5 years ago committed by GitHub
parent
commit
4d2b2d574b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      korman/exporter/utils.py
  2. 4
      korman/properties/modifiers/base.py
  3. 324
      korman/properties/modifiers/gui.py
  4. 45
      korman/ui/modifiers/gui.py

26
korman/exporter/utils.py

@ -13,6 +13,11 @@
# You should have received a copy of the GNU General Public License
# along with Korman. If not, see <http://www.gnu.org/licenses/>.
import bmesh
import bpy
from typing import Callable
from contextlib import contextmanager
from PyHSPlasma import *
def affine_parts(xform):
@ -45,3 +50,24 @@ def matrix44(blmat):
def quaternion(blquat):
"""Converts a mathutils.Quaternion to an hsQuat"""
return hsQuat(blquat.x, blquat.y, blquat.z, blquat.w)
@contextmanager
def bmesh_temporary_object(name : str, factory : Callable, page_name : str=None):
"""Creates a temporary object and mesh that exists only for the duration of
the context"""
mesh = bpy.data.meshes.new(name)
obj = bpy.data.objects.new(name, mesh)
obj.draw_type = "WIRE"
if page_name is not None:
obj.plasma_object.page = page_name
bpy.context.scene.objects.link(obj)
bm = bmesh.new()
try:
factory(bm)
bm.to_mesh(mesh)
yield obj
finally:
bm.free()
bpy.context.scene.objects.unlink(obj)

4
korman/properties/modifiers/base.py

@ -92,6 +92,10 @@ class PlasmaModifierLogicWiz:
def logicwiz(self, bo, tree):
pass
def export_logic(self, exporter, bo, so, **kwargs):
with self.generate_logic(bo, **kwargs) as tree:
tree.export(exporter, bo, so)
class PlasmaModifierUpgradable:
@property

324
korman/properties/modifiers/gui.py

@ -17,12 +17,15 @@ import bpy
import math
import bmesh
import mathutils
from pathlib import Path
from bpy.props import *
from PyHSPlasma import *
from ...addon_prefs import game_versions
from ...exporter import ExportError, utils
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable
from ... import idprops
from ...helpers import TemporaryObject
journal_pfms = {
@ -330,3 +333,324 @@ class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz
def requires_actor(self):
# We are too late in the export to be harvested automatically, so let's be explicit
return True
linking_pfms = {
pvPots : {
# Supplied by the OfflineKI script:
# https://gitlab.com/diafero/offline-ki/blob/master/offlineki/xSimpleLinkingBook.py
"filename": "xSimpleLinkingBook.py",
"attribs": (
{ 'id': 1, 'type': "ptAttribActivator", "name": "bookClickable" },
{ 'id': 2, 'type': "ptAttribString", "name": "destinationAge" },
{ 'id': 3, 'type': "ptAttribString", "name": "spawnPoint" },
{ 'id': 4, 'type': "ptAttribString", "name": "linkPanel" },
{ 'id': 5, 'type': "ptAttribString", "name": "bookCover" },
{ 'id': 6, 'type': "ptAttribString", "name": "stampTexture" },
{ 'id': 7, 'type': "ptAttribFloat", "name": "stampX" },
{ 'id': 8, 'type': "ptAttribFloat", "name": "stampY" },
{ 'id': 9, 'type': "ptAttribFloat", "name": "bookWidth" },
{ 'id': 10, 'type': "ptAttribFloat", "name": "BookHeight" },
{ 'id': 11, 'type': "ptAttribBehavior", "name": "msbSeekBeforeUI" },
{ 'id': 12, 'type': "ptAttribResponder", "name": "respOneShot" },
)
},
pvMoul : {
"filename": "xLinkingBookGUIPopup.py",
"attribs": (
{ 'id': 1, 'type': "ptAttribActivator", 'name': "actClickableBook" },
{ 'id': 2, 'type': "ptAttribBehavior", 'name': "SeekBehavior" },
{ 'id': 3, 'type': "ptAttribResponder", 'name': "respLinkResponder" },
{ 'id': 4, 'type': "ptAttribString", 'name': "TargetAge" },
{ 'id': 5, 'type': "ptAttribActivator", 'name': "actBookshelf" },
{ 'id': 6, 'type': "ptAttribActivator", 'name': "shareRegion" },
{ 'id': 7, 'type': "ptAttribBehavior", 'name': "shareBookSeek" },
{ 'id': 10, 'type': "ptAttribBoolean", 'name': "IsDRCStamped" },
{ 'id': 11, 'type': "ptAttribBoolean", 'name': "ForceThirdPerson" },
)
},
}
class PlasmaLinkingBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz):
pl_id = "linkingbookmod"
bl_category = "GUI"
bl_label = "Linking Book"
bl_description = "Linking Book"
bl_icon = "FILE_IMAGE"
versions = EnumProperty(name="Export Targets",
description="Plasma versions for which this journal exports",
items=game_versions,
options={"ENUM_FLAG"},
default={"pvMoul"})
# Link Info
link_type = EnumProperty(name="Linking Type",
description="The type of Link this Linking Book will use",
items=[
("kBasicLink", "Public Link", "Links to a public instance of the specified Age"),
("kOriginalBook", "Private Link", "Links to a new or existing private instance of the specified Age"),
("kSubAgeBook", "Closed Loop Link", "Links between instances of the specifed Age and the current one"),],
options=set(),
default="kOriginalBook")
age_name = StringProperty(name="Age Name",
description="Filename of the Age to link to (e.g. Garrison)",)
age_instance = StringProperty(name="Age Instance",
description="Friendly name of the Age to link to (e.g. Gahreesen)",)
age_uuid = StringProperty(name="Age GUID",
description="GUID for a specific instance (used with public Ages)",)
age_parent = StringProperty(name="Parent Age",
description="Name of the Child Age's parent Age",)
spawn_title = StringProperty(name="Spawn Title",
description="Title of the Spawn Point",
default="Default")
spawn_point = StringProperty(name="Spawn Point",
description="Name of the Spawn Point to arrive at after the link",
default="LinkInPointDefault")
anim_type = EnumProperty(name="Link Animation",
description="Type of Linking Animation to use",
items=[("LinkOut", "Standing", "The avatar steps up to the book and places their hand on the panel"),
("FishBookLinkOut", "Kneeling", "The avatar kneels in front of the book and places their hand on the panel"),],
default="LinkOut",
options=set())
link_destination = StringProperty(name="Linking Panel Name",
description="Optional: Name of Linking Panel to use for this link-in point if it differs from the Age Name",)
# Interactables
seek_point = PointerProperty(name="Seek Point",
description="The point the avatar will seek to before opening the Linking Book GUI",
type=bpy.types.Object,
poll=idprops.poll_empty_objects)
clickable_region = PointerProperty(name="Clickable Region",
description="The region in which the avatar must be standing before they can click on the Linking Book",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
clickable = PointerProperty(name="Clickable",
description="The object the avatar will click on to activate the Linking Book GUI",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
# -- Path of the Shell options --
# Popup Appearance
book_cover_image = PointerProperty(name="Book Cover",
description="Image to use for the Linking Book's cover (Optional: book starts open if left blank)",
type=bpy.types.Image,
options=set())
link_panel_image = PointerProperty(name="Linking Panel",
description="Image to use for the Linking Panel",
type=bpy.types.Image,
options=set())
stamp_image = PointerProperty(name="Stamp Image",
description="Image to use for the stamp on the page opposite the book's linking panel, if any",
type=bpy.types.Image,
options=set())
stamp_x = IntProperty(name="Stamp Position X",
description="X position of Stamp",
default=140,
subtype="UNSIGNED")
stamp_y = IntProperty(name="Stamp Position Y",
description="Y position of Stamp",
default=255,
subtype="UNSIGNED")
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 LinkingBookMod not enabled for export to the selected engine. Skipping.",
bo.name, indent=2)
return
if self.clickable is None:
raise ExportError("{}: Linking Book modifier requires a clickable!", bo.name)
if self.seek_point is None:
raise ExportError("{}: Linking Book modifier requires a seek point!", bo.name)
if version <= pvPots:
# Create ImageLibraryMod in which to store the Cover, Linking Panel, and Stamp images
ilmod = exporter.mgr.find_create_object(plImageLibMod, so=so, name=self.key_name)
user_images = (i for i in (self.book_cover_image, self.link_panel_image, self.stamp_image)
if i is not None)
for image in user_images:
exporter.mesh.material.export_prepared_image(owner=ilmod, image=image,
allowed_formats={"JPG", "PNG"}, extension="hsm")
# Auto-generate a six-foot cube region around the clickable if none was provided.
if self.clickable_region is None:
# Create a region for the clickable's condition
def _make_rgn(bm):
bmesh.ops.create_cube(bm, size=(6.0))
rgn_offset = mathutils.Matrix.Translation(self.clickable.location - bo.location)
bmesh.ops.transform(bm, matrix=rgn_offset, space=bo.matrix_world, verts=bm.verts)
with utils.bmesh_temporary_object("{}_LinkingBook_ClkRgn".format(self.key_name),
_make_rgn, self.clickable.plasma_object.page) as temp_rgn:
# Generate the logic nodes
self.export_logic(exporter, bo, so, age_name=exporter.age_name, version=version,
region=temp_rgn)
else:
# Generate the logic nodes
self.export_logic(exporter, bo, so, age_name=exporter.age_name, version=version,
region=self.clickable_region)
def logicwiz(self, bo, tree, age_name, version, region):
nodes = tree.nodes
# Assign linking book script based on target version
linking_pfm = linking_pfms[version]
linkingnode = nodes.new("PlasmaPythonFileNode")
with linkingnode.NoUpdate():
linkingnode.filename = linking_pfm["filename"]
# Manually add required attributes to the PFM
linking_attribs = linking_pfm["attribs"]
for attr in linking_attribs:
new_attr = linkingnode.attributes.add()
new_attr.attribute_id = attr["id"]
new_attr.attribute_type = attr["type"]
new_attr.attribute_name = attr["name"]
linkingnode.update()
if version <= pvPots:
self._create_pots_nodes(bo, nodes, linkingnode, age_name, region)
else:
self._create_moul_nodes(bo, nodes, linkingnode, age_name, region)
def _create_pots_nodes(self, clickable_object, nodes, linkingnode, age_name, clk_region):
# Clickable
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region
clickable = nodes.new("PlasmaClickableNode")
clickable.clickable_object = self.clickable
clickable.find_input_socket("facing").allow_simple = False
clickable.link_input(clickable_region, "satisfies", "region")
clickable.link_output(linkingnode, "satisfies", "bookClickable")
# Destination Age Name
age_name = nodes.new("PlasmaAttribStringNode")
age_name.value = self.age_name
age_name.link_output(linkingnode, "pfm", "destinationAge")
# Spawn Point Name
spawn_point = nodes.new("PlasmaAttribStringNode")
spawn_point.value = self.spawn_point
spawn_point.link_output(linkingnode, "pfm", "spawnPoint")
# Book Cover Image
if self.book_cover_image:
book_cover_name = nodes.new("PlasmaAttribStringNode")
book_cover_name.value = str(Path(self.book_cover_image.name).with_suffix(".hsm"))
book_cover_name.link_output(linkingnode, "pfm", "bookCover")
# Linking Panel Image
if self.link_panel_image:
linking_panel_name = nodes.new("PlasmaAttribStringNode")
linking_panel_name.value = str(Path(self.link_panel_image.name).with_suffix(".hsm"))
linking_panel_name.link_output(linkingnode, "pfm", "linkPanel")
# Stamp Image
if self.stamp_image:
stamp_texture_name = nodes.new("PlasmaAttribStringNode")
stamp_texture_name.value = str(Path(self.stamp_image.name).with_suffix(".hsm"))
stamp_texture_name.link_output(linkingnode, "pfm", "stampTexture")
# Stamp X Position
stamp_x = nodes.new("PlasmaAttribIntNode")
stamp_x.value = self.stamp_x
stamp_x.link_output(linkingnode, "pfm", "stampX")
# Stamp Y Position
stamp_y = nodes.new("PlasmaAttribIntNode")
stamp_y.value = self.stamp_y
stamp_y.link_output(linkingnode, "pfm", "stampY")
# MSB
seek = nodes.new("PlasmaSeekTargetNode")
seek.target = self.seek_point
anim_stage = nodes.new("PlasmaAnimStageNode")
anim_stage.anim_name = "LinkOut"
anim_settings = nodes.new("PlasmaAnimStageSettingsNode")
anim_stage.link_input(anim_settings, "stage", "stage_settings")
msb = nodes.new("PlasmaMultiStageBehaviorNode")
msb.link_input(seek, "seekers", "seek_target")
msb.link_input(anim_stage, "stage", "stage_refs")
msb.link_output(linkingnode, "hosts", "msbSeekBeforeUI")
# Responder
one_shot = nodes.new("PlasmaOneShotMsgNode")
one_shot.animation = self.anim_type
one_shot.marker = "touch"
one_shot.pos_object = self.seek_point
responder_state = nodes.new("PlasmaResponderStateNode")
responder_state.link_output(one_shot, "msgs", "sender")
responder = nodes.new("PlasmaResponderNode")
responder.link_output(responder_state, "state_refs", "resp")
responder.link_output(linkingnode, "keyref", "respOneShot")
def _create_moul_nodes(self, clickable_object, nodes, linkingnode, age_name, clk_region):
# Clickable
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = clk_region
clickable = nodes.new("PlasmaClickableNode")
clickable.clickable_object = self.clickable
clickable.find_input_socket("facing").allow_simple = False
clickable.link_input(clickable_region, "satisfies", "region")
clickable.link_output(linkingnode, "satisfies", "actClickableBook")
# MSB
seek = nodes.new("PlasmaSeekTargetNode")
seek.target = self.seek_point
anim_stage = nodes.new("PlasmaAnimStageNode")
anim_stage.anim_name = "LinkOut"
anim_settings = nodes.new("PlasmaAnimStageSettingsNode")
anim_stage.link_input(anim_settings, "stage", "stage_settings")
msb = nodes.new("PlasmaMultiStageBehaviorNode")
msb.link_input(seek, "seekers", "seek_target")
msb.link_input(anim_stage, "stage", "stage_refs")
msb.link_output(linkingnode, "hosts", "SeekBehavior")
# Responder
link_message = nodes.new("PlasmaLinkToAgeMsg")
link_message.rules = self.link_type
link_message.parent_filename = self.age_parent
link_message.age_filename = self.age_name
link_message.age_instance = self.age_instance
link_message.age_uuid = self.age_uuid
link_message.spawn_title = self.spawn_title
link_message.spawn_point = self.spawn_point
one_shot = nodes.new("PlasmaOneShotMsgNode")
one_shot.animation = self.anim_type
one_shot.marker = "touch"
one_shot.pos_object = self.seek_point
one_shot.link_output(link_message, "msgs", "sender")
responder_state = nodes.new("PlasmaResponderStateNode")
responder_state.link_output(one_shot, "msgs", "sender")
responder = nodes.new("PlasmaResponderNode")
responder.link_output(responder_state, "state_refs", "resp")
responder.link_output(linkingnode, "keyref", "respLinkResponder")
# Linking Panel Name
linking_panel_name = nodes.new("PlasmaAttribStringNode")
linking_panel_name.value = self.link_destination if self.link_destination else self.age_name
linking_panel_name.link_output(linkingnode, "pfm", "TargetAge")
def harvest_actors(self):
if self.seek_point is not None:
yield self.seek_point.name

45
korman/ui/modifiers/gui.py

@ -67,3 +67,48 @@ def journalbookmod(modifier, layout, context):
main_col.label("Clickable Region:")
main_col.prop(modifier, "clickable_region", text="")
def linkingbookmod(modifier, layout, context):
def row_alert(prop_name, **kwargs):
row = layout.row()
row.alert = not getattr(modifier, prop_name)
row.prop(modifier, prop_name, **kwargs)
layout.prop_menu_enum(modifier, "versions")
layout.separator()
row = layout.row()
row.alert = modifier.clickable is None
row.prop(modifier, "clickable")
layout.prop(modifier, "clickable_region")
if "pvMoul" in modifier.versions:
row_alert("seek_point")
layout.prop(modifier, "anim_type")
layout.separator()
layout.prop(modifier, "link_type")
row_alert("age_instance")
if modifier.link_type == "kChildAgeBook":
row_alert("age_parent")
if modifier.link_type == "kBasicLink":
row_alert("age_uuid")
row_alert("age_name")
if "pvMoul" in modifier.versions:
layout.separator()
layout.prop(modifier, "link_destination")
layout.prop(modifier, "spawn_title")
layout.prop(modifier, "spawn_point")
if "pvPots" in modifier.versions:
layout.separator()
layout.prop(modifier, "link_panel_image")
layout.prop(modifier, "book_cover_image")
layout.prop(modifier, "stamp_image")
if modifier.stamp_image:
row = layout.row(align=True)
row.label("Stamp Position:")
row.prop(modifier, "stamp_x", text="X")
row.prop(modifier, "stamp_y", text="Y")

Loading…
Cancel
Save