|
|
|
@ -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,310 @@ 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" }, |
|
|
|
|
) |
|
|
|
|
}, |
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|