From c69c4e4c6f387865417aaa7889a32430e0e447ef Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 13 Dec 2025 16:09:31 -0600 Subject: [PATCH] Add link event trigger node. This is a helper node to abstract away the oddities of dealing with Python File Mods triggering logic. It's hopefully the first of many higher level logic nodes. --- korman/nodes/node_conditions.py | 99 +++++++++++++++++++++++++++++++++ korman/nodes/node_core.py | 7 +++ 2 files changed, 106 insertions(+) diff --git a/korman/nodes/node_conditions.py b/korman/nodes/node_conditions.py index 7d37d6d3..890e6871 100644 --- a/korman/nodes/node_conditions.py +++ b/korman/nodes/node_conditions.py @@ -395,6 +395,105 @@ def simple_mode(self): return (not self.is_linked and not self.is_output) +class PlasmaLinkEventNode(PlasmaNodeBase, bpy.types.Node): + bl_category = "CONDITIONS" + bl_idname = "PlasmaLinkEventNode" + bl_label = "Link Event" + + trigger_for = EnumProperty( + name="Trigger For", + items=[ + ("Me", "Local Player", "Trigger only for the local player"), + ("NPCs", "NPCs", "Trigger for NPCs (eg quabs)"), + ("Player Avatars", "Player Avatars", "Trigger for player controlled avatars"), + ("Everyone", "Everyone", "Trigger for all avatars"), + ], + default="Me", + options=set() + ) + trigger_on = EnumProperty( + name="Trigger On", + items=[ + ("Spawn", "Avatar Spawns", "Trigger when an avatar spawns"), + ("Link", "Avatar Links", "Trigger when an avatar links"), + ], + default="Spawn", + options=set() + ) + trigger_direction = EnumProperty( + name="Trigger Direction", + items=[ + ("In", "In", "Trigger when the avatar links/spawns into the Age"), + ("Out", "Out", "Trigger when the avatar links/spawns out of the Age"), + ], + default="In", + options=set() + ) + trigger_at = EnumProperty( + name="Trigger At", + items=[ + ("Start", "Event Start", "Trigger when the interesting event begins"), + ("End", "Event End", "Trigger when the interesting event ends"), + ], + default="Start", + options=set() + ) + + output_sockets: dict[str, dict[str, Any]] = { + "satisfies": { + "text": "Satisfies", + "type": "PlasmaConditionSocket", + "valid_link_sockets": {"PlasmaConditionSocket", "PlasmaPythonFileNodeSocket"}, + }, + } + + def draw_buttons(self, context, layout): + combo = (self.trigger_for, self.trigger_on, self.trigger_direction) + + layout.alert = combo == ("Me", "Spawn", "Out") + layout.prop(self, "trigger_for") + layout.alert = False + + layout.prop(self, "trigger_on") + layout.prop(self, "trigger_direction") + layout.prop(self, "trigger_at") + + def get_key(self, exporter: Exporter, so) -> plKey[plLogicModifier]: + return self._find_create_key(plLogicModifier, exporter, so=so) + + def export(self, exporter: Exporter, bo, so) -> None: + combo = (self.trigger_for, self.trigger_on, self.trigger_direction) + if combo == ("Me", "Spawn", "Out"): + self.raise_error("Cannot trigger when the local player spawns out") + + # There is no class in the engine that will fire when someone links in. There are messages, + # though, and they all get to Python in one way or another. So, to fire an event on link + # or spawn, we're going to use a helper script xLinkEventTrigger. It will send a notification + # to a kMultiTrigger LogicMod with a plActivatorActivatorConditionalObject. LMs forward + # all plNotifyMsgs to their conditions, and plAACO activates on any state==1.0 plNotifyMsg, + # assuming that the LogicMod is not already marked as triggered and all of the other conditions + # say that they're in a valid state. Once that LogicMod fires, anything we want can be fired + # off, like other PFMs or Responders. A potential optimization here is to avoid creation + # of the plAACO/plLM thunk if we only have responders attached. + activator = self._find_create_object(plActivatorActivatorConditionalObject, exporter, so=so) + logicmod = self._find_create_object(plLogicModifier, exporter, so=so) + logicmod.addCondition(activator.key) + logicmod.notify = self.generate_notify_msg(exporter, so, "satisfies") + logicmod.setLogicFlag(plLogicModifier.kMultiTrigger, True) + logicmod.setLogicFlag(plLogicModifier.kLocalElement, True) + + pfm = self._find_create_object(plPythonFileMod, exporter, so=so) + self._add_py_parameter(pfm, 1, plPythonParameter.kActivator, activator.key) + self._add_py_parameter(pfm, 100, plPythonParameter.kString, f"{self.trigger_on} {self.trigger_direction}") + self._add_py_parameter(pfm, 101, plPythonParameter.kString, self.trigger_for) + self._add_py_parameter(pfm, 102, plPythonParameter.kString, self.trigger_at) + pfm.filename = "xLinkEventTrigger" + + @property + def export_once(self): + return True + + class PlasmaVolumeReportNode(PlasmaNodeBase, bpy.types.Node): bl_category = "CONDITIONS" bl_idname = "PlasmaVolumeReportNode" diff --git a/korman/nodes/node_core.py b/korman/nodes/node_core.py index a875d5e4..51fec3c8 100644 --- a/korman/nodes/node_core.py +++ b/korman/nodes/node_core.py @@ -28,6 +28,13 @@ from ..exporter import Exporter class PlasmaNodeBase: + def _add_py_parameter(self, pfm: plPythonFileMod, id: int, param_type: int, value) -> None: + param = plPythonParameter() + param.id = id + param.valueType = param_type + param.value = value + pfm.addParameter(param) + def generate_notify_msg(self, exporter: Exporter, so: plSceneObject, socket_id: str, idname: Optional[str] = None) -> plNotifyMsg: notify = plNotifyMsg() notify.BCastFlags = (plMessage.kNetPropagate | plMessage.kLocalPropagate)