You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

640 lines
30 KiB

# 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/>.
import bpy
import bmesh
from bpy.props import *
import mathutils
import math
from pathlib import Path
from PyHSPlasma import *
from ...addon_prefs import game_versions
from ...exporter import ExportError, utils
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz, PlasmaModifierUpgradable
from ... import idprops
journal_pfms = {
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" },
)
},
}
# 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):
image = bpy.props.PointerProperty(name="Image Item",
description="Image stored for export.",
type=bpy.types.Image,
options=set())
enabled = bpy.props.BoolProperty(name="Enabled",
description="Specifies whether this image will be stored during export.",
default=True,
options=set())
class PlasmaImageLibraryModifier(PlasmaModifierProperties):
pl_id = "imagelibmod"
bl_category = "GUI"
bl_label = "Image Library"
bl_description = "A collection of images to be stored for later use"
bl_icon = "RENDERLAYERS"
images = CollectionProperty(name="Images", type=ImageLibraryItem, options=set())
active_image_index = IntProperty(options={"HIDDEN"})
def export(self, exporter, bo, so):
if self.images:
ilmod = exporter.mgr.find_create_object(plImageLibMod, so=so, name=self.key_name)
for item in self.images:
if item.image and item.enabled:
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="Contents",
description="Text data block containing the text for this language",
type=bpy.types.Text,
poll=_poll_nonpytext,
options=set())
class TranslationMixin:
def export_localization(self, exporter):
translations = [i for i in self.translations if i.text_id is not None]
if not translations:
exporter.report.error("'{}': '{}' No content translations available. The localization will not be exported.",
self.id_data.name, self.bl_label, indent=1)
return
for i in translations:
exporter.locman.add_string(self.localization_set, self.key_name, i.language, i.text_id, indent=1)
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.translations)
if translation.language == _DEFAULT_LANGUAGE_NAME), (None, None))
if default is None:
default_idx = len(self.translations)
default = self.translations.add()
default.language = _DEFAULT_LANGUAGE_NAME
if self.active_translation_index < len(self.translations):
language = self.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.translations)
if translation.language == language_name), None)
if idx is None:
self.active_translation_index = len(self.translations)
translation = self.translations.add()
translation.language = language_name
else:
self.active_translation_index = idx
@property
def localization_set(self):
raise RuntimeError("TranslationMixin subclass needs a localization set getter!")
@property
def translations(self):
raise RuntimeError("TranslationMixin subclass needs a translation getter!")
class PlasmaJournalBookModifier(PlasmaModifierProperties, PlasmaModifierLogicWiz, TranslationMixin):
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")
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)
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=TranslationMixin._get_translation,
set=TranslationMixin._set_translation,
options=set())
def pre_export(self, exporter, bo):
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.",
bo.name, version, indent=2)
return
if self.clickable_region is None:
with utils.bmesh_object("{}_Journal_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
bmesh.ops.create_cube(bm, size=(6.0))
bmesh.ops.transform(bm, matrix=mathutils.Matrix.Translation(bo.matrix_world.translation - rgn_obj.matrix_world.translation),
space=rgn_obj.matrix_world, verts=bm.verts)
rgn_obj.plasma_object.enabled = True
rgn_obj.hide_render = True
yield rgn_obj
else:
# Use the region provided
rgn_obj = self.clickable_region
# Generate the logic nodes
yield self.convert_logic(bo, age_name=exporter.age_name, rgn_obj=rgn_obj, version=version)
def logicwiz(self, bo, tree, age_name, rgn_obj, version):
# Assign journal script based on target version
journal_pfm = journal_pfms[version]
journalnode = self._create_python_file_node(tree, journal_pfm["filename"], journal_pfm["attribs"])
if version <= pvPots:
self._create_pots_nodes(bo, tree.nodes, journalnode, age_name, rgn_obj)
else:
self._create_moul_nodes(bo, tree.nodes, journalnode, age_name, rgn_obj)
def _create_pots_nodes(self, clickable_object, nodes, journalnode, age_name, rgn_obj):
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = rgn_obj
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.key_name
guitype = nodes.new("PlasmaAttribBoolNode")
guitype.link_output(journalnode, "pfm", "isNotebook")
guitype.value = self.book_type == "bkNotebook"
width = nodes.new("PlasmaAttribIntNode")
width.link_output(journalnode, "pfm", "BookWidth")
width.value_float = self.book_scale_w / 100.0
height = nodes.new("PlasmaAttribIntNode")
height.link_output(journalnode, "pfm", "BookHeight")
height.value_float = self.book_scale_h / 100.0
def _create_moul_nodes(self, clickable_object, nodes, journalnode, age_name, rgn_obj):
clickable_region = nodes.new("PlasmaClickableRegionNode")
clickable_region.region_object = rgn_obj
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("PlasmaAttribIntNode")
width.link_output(journalnode, "pfm", "BookWidth")
width.value_float = self.book_scale_w / 100.0
height = nodes.new("PlasmaAttribIntNode")
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 = "{}.{}.{}".format(age_name, self.localization_set, self.key_name)
guitype = nodes.new("PlasmaAttribStringNode")
guitype.link_output(journalnode, "pfm", "GUIType")
guitype.value = self.book_type
@property
def localization_set(self):
return "Journals"
@property
def requires_actor(self):
# We are too late in the export to be harvested automatically, so let's be explicit
return True
@property
def translations(self):
# Backwards compatibility thunk.
return self.journal_translations
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 _check_version(self, *args) -> bool:
our_versions = frozenset((globals()[j] for j in self.versions))
return frozenset(args) & our_versions
def pre_export(self, exporter, bo):
if not self._check_version(exporter.mgr.getVer()):
# We aren't needed here
exporter.report.port("Object '{}' has a LinkingBookMod not enabled for export to the selected engine. Skipping.",
self.id_data.name, indent=2)
return
# Auto-generate a six-foot cube region around the clickable if none was provided.
if self.clickable_region is None:
with utils.bmesh_object("{}_LinkingBook_ClkRgn".format(self.key_name)) as (rgn_obj, bm):
bmesh.ops.create_cube(bm, size=(6.0))
rgn_offset = mathutils.Matrix.Translation(self.clickable.matrix_world.translation - rgn_obj.matrix_world.translation)
bmesh.ops.transform(bm, matrix=rgn_offset, space=rgn_obj.matrix_world, verts=bm.verts)
rgn_obj.plasma_object.enabled = True
rgn_obj.hide_render = True
yield rgn_obj
else:
rgn_obj = self.clickable_region
yield self.convert_logic(bo, age_name=exporter.age_name, version=exporter.mgr.getVer(), region=rgn_obj)
def export(self, exporter, bo, so):
if self._check_version(pvPrime, 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")
def harvest_actors(self):
if self.seek_point is not None:
yield self.seek_point.name
def logicwiz(self, bo, tree, age_name, version, region):
# Assign linking book script based on target version
linking_pfm = linking_pfms[version]
linkingnode = self._create_python_file_node(tree, linking_pfm["filename"], linking_pfm["attribs"])
if version <= pvPots:
self._create_pots_nodes(bo, tree.nodes, linkingnode, age_name, region)
else:
self._create_moul_nodes(bo, tree.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_settings.forward = "kPlayAuto"
anim_settings.stage_advance = "kAdvanceAuto"
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_settings.forward = "kPlayAuto"
anim_settings.stage_advance = "kAdvanceAuto"
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 sanity_check(self):
if self.clickable is None:
raise ExportError("{}: Linking Book modifier requires a clickable!", self.id_data.name)
if self.seek_point is None:
raise ExportError("{}: Linking Book modifier requires a seek point!", self.id_data.name)